TimestampCheck.java 37.2 KB
Newer Older
1
/*
2
 * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
19 20 21
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
22 23 24
 */

import com.sun.net.httpserver.*;
25 26

import java.io.ByteArrayInputStream;
27
import java.io.ByteArrayOutputStream;
28
import java.io.File;
29 30
import java.io.FileInputStream;
import java.io.IOException;
31
import java.io.InputStream;
32 33 34
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.InetSocketAddress;
35 36
import java.nio.file.Files;
import java.nio.file.Paths;
37 38 39 40
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.Certificate;
41 42
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
43
import java.security.cert.X509Certificate;
44 45 46
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
47 48 49 50
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import sun.misc.IOUtils;
51 52
import jdk.testlibrary.SecurityTools;
import jdk.testlibrary.OutputAnalyzer;
53
import jdk.testlibrary.JarUtils;
54 55
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
56
import sun.security.pkcs.PKCS9Attribute;
57
import sun.security.pkcs.SignerInfo;
58
import sun.security.timestamp.TimestampToken;
59 60 61 62 63 64
import sun.security.util.DerOutputStream;
import sun.security.util.DerValue;
import sun.security.util.ObjectIdentifier;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X500Name;

65 66
import jdk.testlibrary.Utils;

67 68
/*
 * @test
R
rpatil 已提交
69
 * @bug 6543842 6543440 6939248 8009636 8024302 8163304 8169911 8169688 8171121
70
 *      8180289
71 72 73 74 75 76 77
 * @summary checking response of timestamp
 * @modules java.base/sun.security.pkcs
 *          java.base/sun.security.timestamp
 *          java.base/sun.security.x509
 *          java.base/sun.security.util
 *          java.base/sun.security.tools.keytool
 * @library /lib/testlibrary
78
 * @compile -XDignore.symbol.file TimestampCheck.java
79
 * @run main/othervm/timeout=600 TimestampCheck
80
 */
81 82
public class TimestampCheck {

83 84
    static final String defaultPolicyId = "2.3.4";
    static String host = null;
85

86 87 88 89 90 91
    static class Handler implements HttpHandler, AutoCloseable {

        private final HttpServer httpServer;
        private final String keystore;

        @Override
92 93 94 95 96 97 98 99 100 101 102
        public void handle(HttpExchange t) throws IOException {
            int len = 0;
            for (String h: t.getRequestHeaders().keySet()) {
                if (h.equalsIgnoreCase("Content-length")) {
                    len = Integer.valueOf(t.getRequestHeaders().get(h).get(0));
                }
            }
            byte[] input = new byte[len];
            t.getRequestBody().read(input);

            try {
103
                String path = t.getRequestURI().getPath().substring(1);
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
                byte[] output = sign(input, path);
                Headers out = t.getResponseHeaders();
                out.set("Content-Type", "application/timestamp-reply");

                t.sendResponseHeaders(200, output.length);
                OutputStream os = t.getResponseBody();
                os.write(output);
            } catch (Exception e) {
                e.printStackTrace();
                t.sendResponseHeaders(500, 0);
            }
            t.close();
        }

        /**
         * @param input The data to sign
         * @param path different cases to simulate, impl on URL path
         * @returns the signed
         */
123
        byte[] sign(byte[] input, String path) throws Exception {
124
            DerValue value = new DerValue(input);
125 126
            System.out.println("#\n# Incoming Request\n===================");
            System.out.println("# Version: " + value.data.getInteger());
127 128 129
            DerValue messageImprint = value.data.getDerValue();
            AlgorithmId aid = AlgorithmId.parse(
                    messageImprint.data.getDerValue());
130
            System.out.println("# AlgorithmId: " + aid);
131

132
            ObjectIdentifier policyId = new ObjectIdentifier(defaultPolicyId);
133 134 135 136 137
            BigInteger nonce = null;
            while (value.data.available() > 0) {
                DerValue v = value.data.getDerValue();
                if (v.tag == DerValue.tag_Integer) {
                    nonce = v.getBigInteger();
138
                    System.out.println("# nonce: " + nonce);
139
                } else if (v.tag == DerValue.tag_Boolean) {
140
                    System.out.println("# certReq: " + v.getBoolean());
141 142
                } else if (v.tag == DerValue.tag_ObjectId) {
                    policyId = v.getOID();
143
                    System.out.println("# PolicyID: " + policyId);
144 145 146
                }
            }

147
            System.out.println("#\n# Response\n===================");
148 149 150 151
            FileInputStream is = new FileInputStream(keystore);
            KeyStore ks = KeyStore.getInstance("JCEKS");
            ks.load(is, "changeit".toCharArray());
            is.close();
152

153 154 155
            // If path starts with "ts", use the TSA it points to.
            // Otherwise, always use "ts".
            String alias = path.startsWith("ts") ? path : "ts";
156

157
            if (path.equals("diffpolicy")) {
158 159 160
                policyId = new ObjectIdentifier(defaultPolicyId);
            }

161 162 163 164 165
            DerOutputStream statusInfo = new DerOutputStream();
            statusInfo.putInteger(0);

            AlgorithmId[] algorithms = {aid};
            Certificate[] chain = ks.getCertificateChain(alias);
166
            X509Certificate[] signerCertificateChain;
167
            X509Certificate signer = (X509Certificate)chain[0];
168 169

            if (path.equals("fullchain")) {   // Only case 5 uses full chain
170 171 172 173
                signerCertificateChain = new X509Certificate[chain.length];
                for (int i=0; i<chain.length; i++) {
                    signerCertificateChain[i] = (X509Certificate)chain[i];
                }
174
            } else if (path.equals("nocert")) {
175 176 177 178 179 180 181 182 183
                signerCertificateChain = new X509Certificate[0];
            } else {
                signerCertificateChain = new X509Certificate[1];
                signerCertificateChain[0] = (X509Certificate)chain[0];
            }

            DerOutputStream tst = new DerOutputStream();

            tst.putInteger(1);
184
            tst.putOID(policyId);
185

186
            if (!path.equals("baddigest") && !path.equals("diffalg")) {
187 188 189
                tst.putDerValue(messageImprint);
            } else {
                byte[] data = messageImprint.toByteArray();
190
                if (path.equals("diffalg")) {
191 192 193 194 195 196 197 198 199 200 201
                    data[6] = (byte)0x01;
                } else {
                    data[data.length-1] = (byte)0x01;
                    data[data.length-2] = (byte)0x02;
                    data[data.length-3] = (byte)0x03;
                }
                tst.write(data);
            }

            tst.putInteger(1);

202 203 204 205 206
            Instant instant = Instant.now();
            if (path.equals("tsold")) {
                instant = instant.minus(20, ChronoUnit.DAYS);
            }
            tst.putGeneralizedTime(Date.from(instant));
207

208
            if (path.equals("diffnonce")) {
209
                tst.putInteger(1234);
210 211
            } else if (path.equals("nononce")) {
                // no noce
212 213 214 215 216 217 218 219 220 221
            } else {
                tst.putInteger(nonce);
            }

            DerOutputStream tstInfo = new DerOutputStream();
            tstInfo.write(DerValue.tag_Sequence, tst);

            DerOutputStream tstInfo2 = new DerOutputStream();
            tstInfo2.putOctetString(tstInfo.toByteArray());

222 223
            // Always use the same algorithm at timestamp signing
            // so it is different from the hash algorithm.
224
            Signature sig = Signature.getInstance("SHA1withRSA");
225 226 227 228 229 230 231 232
            sig.initSign((PrivateKey)(ks.getKey(
                    alias, "changeit".toCharArray())));
            sig.update(tstInfo.toByteArray());

            ContentInfo contentInfo = new ContentInfo(new ObjectIdentifier(
                    "1.2.840.113549.1.9.16.1.4"),
                    new DerValue(tstInfo2.toByteArray()));

233 234
            System.out.println("# Signing...");
            System.out.println("# " + new X500Name(signer
235
                    .getIssuerX500Principal().getName()));
236
            System.out.println("# " + signer.getSerialNumber());
237 238 239 240

            SignerInfo signerInfo = new SignerInfo(
                    new X500Name(signer.getIssuerX500Principal().getName()),
                    signer.getSerialNumber(),
241
                    AlgorithmId.get("SHA-1"), AlgorithmId.get("RSA"), sig.sign());
242 243

            SignerInfo[] signerInfos = {signerInfo};
244 245
            PKCS7 p7 = new PKCS7(algorithms, contentInfo,
                    signerCertificateChain, signerInfos);
246 247 248 249 250 251 252 253 254 255 256 257 258
            ByteArrayOutputStream p7out = new ByteArrayOutputStream();
            p7.encodeSignedData(p7out);

            DerOutputStream response = new DerOutputStream();
            response.write(DerValue.tag_Sequence, statusInfo);
            response.putDerValue(new DerValue(p7out.toByteArray()));

            DerOutputStream out = new DerOutputStream();
            out.write(DerValue.tag_Sequence, response);

            return out.toByteArray();
        }

259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
        private Handler(HttpServer httpServer, String keystore) {
            this.httpServer = httpServer;
            this.keystore = keystore;
        }

        /**
         * Initialize TSA instance.
         *
         * Extended Key Info extension of certificate that is used for
         * signing TSA responses should contain timeStamping value.
         */
        static Handler init(int port, String keystore) throws IOException {
            HttpServer httpServer = HttpServer.create(
                    new InetSocketAddress(port), 0);
            Handler tsa = new Handler(httpServer, keystore);
            httpServer.createContext("/", tsa);
            return tsa;
        }
277

278 279 280 281 282
        /**
         * Start TSA service.
         */
        void start() {
            httpServer.start();
283 284
        }

285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
        /**
         * Stop TSA service.
         */
        void stop() {
            httpServer.stop(0);
        }

        /**
         * Return server port number.
         */
        int getPort() {
            return httpServer.getAddress().getPort();
        }

        @Override
        public void close() throws Exception {
            stop();
        }
    }
304 305 306

    public static void main(String[] args) throws Throwable {

307
        try (Handler tsa = Handler.init(0, "ks");) {
308 309 310
            tsa.start();
            int port = tsa.getPort();

311
            host = "http://localhost:" + port + "/";
312

313
            if (args.length == 0) {         // Run this test
314

315 316
                prepare();

317 318
                sign("normal")
                        .shouldNotContain("Warning")
319 320
                        .shouldContain("The signer certificate will expire on")
                        .shouldContain("The timestamp will expire on")
321 322
                        .shouldHaveExitValue(0);

323 324
                verify("normal.jar")
                        .shouldNotContain("Warning")
325 326
                        .shouldHaveExitValue(0);

327 328 329 330 331 332
                verify("normal.jar", "-verbose")
                        .shouldNotContain("Warning")
                        .shouldContain("The signer certificate will expire on")
                        .shouldContain("The timestamp will expire on")
                        .shouldHaveExitValue(0);

333 334 335 336
                // Simulate signing at a previous date:
                // 1. tsold will create a timestamp of 20 days ago.
                // 2. oldsigner expired 10 days ago.
                signVerbose("tsold", "unsigned.jar", "tsold.jar", "oldsigner")
337 338 339 340
                        .shouldNotContain("Warning")
                        .shouldMatch("signer certificate expired on .*. "
                                + "However, the JAR will be valid")
                        .shouldHaveExitValue(0);
341 342 343 344

                // It verifies perfectly.
                verify("tsold.jar", "-verbose", "-certs")
                        .shouldNotContain("Warning")
345 346
                        .shouldMatch("signer certificate expired on .*. "
                                + "However, the JAR will be valid")
347 348
                        .shouldHaveExitValue(0);

349
                // No timestamp
350 351
                signVerbose(null, "unsigned.jar", "none.jar", "signer")
                        .shouldContain("is not timestamped")
352 353 354 355 356 357
                        .shouldContain("The signer certificate will expire on")
                        .shouldHaveExitValue(0);

                verify("none.jar", "-verbose")
                        .shouldContain("do not include a timestamp")
                        .shouldContain("The signer certificate will expire on")
358 359
                        .shouldHaveExitValue(0);

360 361
                // Error cases

362
                signVerbose(null, "unsigned.jar", "badku.jar", "badku")
363
                        .shouldContain("KeyUsage extension doesn't allow code signing")
364 365 366 367 368
                        .shouldHaveExitValue(8);
                checkBadKU("badku.jar");

                // 8180289: unvalidated TSA cert chain
                sign("tsnoca")
369 370
                        .shouldContain("The TSA certificate chain is invalid. "
                                + "Reason: Path does not chain with any of the trust anchors")
371 372 373 374 375
                        .shouldHaveExitValue(64);

                verify("tsnoca.jar", "-verbose", "-certs")
                        .shouldHaveExitValue(64)
                        .shouldContain("jar verified")
376 377 378 379
                        .shouldContain("Invalid TSA certificate chain: "
                                + "Path does not chain with any of the trust anchors")
                        .shouldContain("TSA certificate chain is invalid."
                                + " Reason: Path does not chain with any of the trust anchors");
380

381
                sign("nononce")
382
                        .shouldContain("Nonce missing in timestamp token")
383 384
                        .shouldHaveExitValue(1);
                sign("diffnonce")
385
                        .shouldContain("Nonce changed in timestamp token")
386 387
                        .shouldHaveExitValue(1);
                sign("baddigest")
388
                        .shouldContain("Digest octets changed in timestamp token")
389 390
                        .shouldHaveExitValue(1);
                sign("diffalg")
391
                        .shouldContain("Digest algorithm not")
392
                        .shouldHaveExitValue(1);
393

394 395
                sign("fullchain")
                        .shouldHaveExitValue(0);   // Success, 6543440 solved.
396

397
                sign("tsbad1")
398
                        .shouldContain("Certificate is not valid for timestamping")
399
                        .shouldHaveExitValue(1);
400
                sign("tsbad2")
401
                        .shouldContain("Certificate is not valid for timestamping")
402
                        .shouldHaveExitValue(1);
403
                sign("tsbad3")
404
                        .shouldContain("Certificate is not valid for timestamping")
405 406
                        .shouldHaveExitValue(1);
                sign("nocert")
407
                        .shouldContain("Certificate not included in timestamp token")
408 409 410 411 412 413 414
                        .shouldHaveExitValue(1);

                sign("policy", "-tsapolicyid",  "1.2.3")
                        .shouldHaveExitValue(0);
                checkTimestamp("policy.jar", "1.2.3", "SHA-256");

                sign("diffpolicy", "-tsapolicyid", "1.2.3")
415
                        .shouldContain("TSAPolicyID changed in timestamp token")
416 417
                        .shouldHaveExitValue(1);

418
                sign("sha1alg", "-tsadigestalg", "SHA")
419
                        .shouldHaveExitValue(0);
420
                checkTimestamp("sha1alg.jar", defaultPolicyId, "SHA-1");
421

422
                sign("tsweak", "-digestalg", "MD5",
R
rpatil 已提交
423
                                "-sigalg", "MD5withRSA", "-tsadigestalg", "MD5")
424 425
                        .shouldHaveExitValue(68)
                        .shouldContain("The timestamp is invalid. Without a valid timestamp");
426 427 428 429
                checkWeak("tsweak.jar");

                signVerbose("tsweak", "unsigned.jar", "tsweak2.jar", "signer")
                        .shouldHaveExitValue(64)
430
                        .shouldContain("The timestamp is invalid. Without a valid timestamp")
431 432 433 434 435 436 437 438
                        .shouldContain("TSA certificate chain is invalid");

                // Weak timestamp is an error and jar treated unsigned
                verify("tsweak2.jar", "-verbose")
                        .shouldHaveExitValue(16)
                        .shouldContain("treated as unsigned")
                        .shouldMatch("Timestamp.*512.*weak");

439
                // Algorithm used in signing is weak
440 441
                signVerbose("normal", "unsigned.jar", "halfWeak.jar", "signer",
                        "-digestalg", "MD5")
442
                        .shouldContain("-digestalg option is considered a security risk")
443
                        .shouldHaveExitValue(4);
444 445 446
                checkHalfWeak("halfWeak.jar");

                // sign with DSA key
447
                signVerbose("normal", "unsigned.jar", "sign1.jar", "dsakey")
448
                        .shouldHaveExitValue(0);
449

450
                // sign with RSAkeysize < 1024
451
                signVerbose("normal", "sign1.jar", "sign2.jar", "weakkeysize")
452
                        .shouldContain("Algorithm constraints check failed on keysize")
453
                        .shouldHaveExitValue(4);
454 455
                checkMultiple("sign2.jar");

456 457 458
                // 8191438: jarsigner should print when a timestamp will expire
                checkExpiration();

459 460
                // When .SF or .RSA is missing or invalid
                checkMissingOrInvalidFiles("normal.jar");
461 462 463 464

                if (Files.exists(Paths.get("ts2.cert"))) {
                    checkInvalidTsaCertKeyUsage();
                }
465
            } else {                        // Run as a standalone server
466 467
                System.out.println("TSA started at " + host
                        + ". Press Enter to quit server");
468 469
                System.in.read();
            }
470 471 472
        }
    }

473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
    private static void checkExpiration() throws Exception {

        // Warning when expired or expiring
        signVerbose(null, "unsigned.jar", "expired.jar", "expired")
                .shouldContain("signer certificate has expired")
                .shouldHaveExitValue(4);
        verify("expired.jar")
                .shouldContain("signer certificate has expired")
                .shouldHaveExitValue(4);
        signVerbose(null, "unsigned.jar", "expiring.jar", "expiring")
                .shouldContain("signer certificate will expire within")
                .shouldHaveExitValue(0);
        verify("expiring.jar")
                .shouldContain("signer certificate will expire within")
                .shouldHaveExitValue(0);
        // Info for long
        signVerbose(null, "unsigned.jar", "long.jar", "long")
                .shouldNotContain("signer certificate has expired")
                .shouldNotContain("signer certificate will expire within")
                .shouldContain("signer certificate will expire on")
                .shouldHaveExitValue(0);
        verify("long.jar")
                .shouldNotContain("signer certificate has expired")
                .shouldNotContain("signer certificate will expire within")
                .shouldNotContain("The signer certificate will expire")
                .shouldHaveExitValue(0);
        verify("long.jar", "-verbose")
                .shouldContain("The signer certificate will expire")
                .shouldHaveExitValue(0);

        // Both expired
        signVerbose("tsexpired", "unsigned.jar",
                "tsexpired-expired.jar", "expired")
                .shouldContain("The signer certificate has expired.")
                .shouldContain("The timestamp has expired.")
                .shouldHaveExitValue(4);
        verify("tsexpired-expired.jar")
                .shouldContain("signer certificate has expired")
                .shouldContain("timestamp has expired.")
                .shouldHaveExitValue(4);

        // TS expired but signer still good
        signVerbose("tsexpired", "unsigned.jar",
                "tsexpired-long.jar", "long")
                .shouldContain("The timestamp expired on")
                .shouldHaveExitValue(0);
        verify("tsexpired-long.jar")
                .shouldMatch("timestamp expired on.*However, the JAR will be valid")
                .shouldNotContain("Error")
                .shouldHaveExitValue(0);

        signVerbose("tsexpired", "unsigned.jar",
                "tsexpired-ca.jar", "ca")
                .shouldContain("The timestamp has expired.")
                .shouldHaveExitValue(4);
        verify("tsexpired-ca.jar")
                .shouldNotContain("timestamp has expired")
                .shouldNotContain("Error")
                .shouldHaveExitValue(0);

        // Warning when expiring
        sign("tsexpiring")
                .shouldContain("timestamp will expire within")
                .shouldHaveExitValue(0);
        verify("tsexpiring.jar")
                .shouldContain("timestamp will expire within")
                .shouldNotContain("still valid")
                .shouldHaveExitValue(0);

        signVerbose("tsexpiring", "unsigned.jar",
                "tsexpiring-ca.jar", "ca")
                .shouldContain("self-signed")
                .stderrShouldNotMatch("The.*expir")
                .shouldHaveExitValue(4); // self-signed
        verify("tsexpiring-ca.jar")
                .stderrShouldNotMatch("The.*expir")
                .shouldHaveExitValue(0);

        signVerbose("tsexpiringsoon", "unsigned.jar",
                "tsexpiringsoon-long.jar", "long")
                .shouldContain("The timestamp will expire")
                .shouldHaveExitValue(0);
        verify("tsexpiringsoon-long.jar")
                .shouldMatch("timestamp will expire.*However, the JAR will be valid until")
                .shouldHaveExitValue(0);

        // Info for long
        sign("tslong")
                .shouldNotContain("timestamp has expired")
                .shouldNotContain("timestamp will expire within")
                .shouldContain("timestamp will expire on")
                .shouldContain("signer certificate will expire on")
                .shouldHaveExitValue(0);
        verify("tslong.jar")
                .shouldNotContain("timestamp has expired")
                .shouldNotContain("timestamp will expire within")
                .shouldNotContain("timestamp will expire on")
                .shouldNotContain("signer certificate will expire on")
                .shouldHaveExitValue(0);
        verify("tslong.jar", "-verbose")
                .shouldContain("timestamp will expire on")
                .shouldContain("signer certificate will expire on")
                .shouldHaveExitValue(0);
    }

578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
    private static void checkInvalidTsaCertKeyUsage() throws Exception {

        // Hack: Rewrite the TSA cert inside normal.jar into ts2.jar.

        // Both the cert and the serial number must be rewritten.
        byte[] tsCert = Files.readAllBytes(Paths.get("ts.cert"));
        byte[] ts2Cert = Files.readAllBytes(Paths.get("ts2.cert"));
        byte[] tsSerial = getCert(tsCert)
                .getSerialNumber().toByteArray();
        byte[] ts2Serial = getCert(ts2Cert)
                .getSerialNumber().toByteArray();

        byte[] oldBlock;
        try (JarFile normal = new JarFile("normal.jar")) {
            oldBlock = Utils.readAllBytes(normal.getInputStream(
                    normal.getJarEntry("META-INF/SIGNER.RSA")));
        }

        JarUtils.updateJar("normal.jar", "ts2.jar",
                mapOf("META-INF/SIGNER.RSA",
                        updateBytes(updateBytes(oldBlock, tsCert, ts2Cert),
                                tsSerial, ts2Serial)));

        verify("ts2.jar", "-verbose", "-certs")
                .shouldHaveExitValue(64)
                .shouldContain("jar verified")
                .shouldContain("Invalid TSA certificate chain: Extended key usage does not permit use for TSA server");
    }

    public static X509Certificate getCert(byte[] data)
            throws CertificateException, IOException {
        return (X509Certificate)
                CertificateFactory.getInstance("X.509")
                        .generateCertificate(new ByteArrayInputStream(data));
    }

    private static byte[] updateBytes(byte[] old, byte[] from, byte[] to) {
        int pos = 0;
        while (true) {
            if (pos + from.length > old.length) {
                return null;
            }
            if (Arrays.equals(Arrays.copyOfRange(old, pos, pos+from.length), from)) {
                byte[] result = old.clone();
                System.arraycopy(to, 0, result, pos, from.length);
                return result;
            }
            pos++;
        }
    }

629 630
    private static void checkMissingOrInvalidFiles(String s)
            throws Throwable {
631 632

        JarUtils.updateJar(s, "1.jar", mapOf("META-INF/SIGNER.SF", Boolean.FALSE));
633
        verify("1.jar", "-verbose")
634
                .shouldHaveExitValue(16)
635
                .shouldContain("treated as unsigned")
636 637
                .shouldContain("Missing signature-related file META-INF/SIGNER.SF");
        JarUtils.updateJar(s, "2.jar", mapOf("META-INF/SIGNER.RSA", Boolean.FALSE));
638
        verify("2.jar", "-verbose")
639
                .shouldHaveExitValue(16)
640
                .shouldContain("treated as unsigned")
641 642
                .shouldContain("Missing block file for signature-related file META-INF/SIGNER.SF");
        JarUtils.updateJar(s, "3.jar", mapOf("META-INF/SIGNER.SF", "dummy"));
643
        verify("3.jar", "-verbose")
644
                .shouldHaveExitValue(16)
645
                .shouldContain("treated as unsigned")
646 647
                .shouldContain("Unparsable signature-related file META-INF/SIGNER.SF");
        JarUtils.updateJar(s, "4.jar", mapOf("META-INF/SIGNER.RSA", "dummy"));
648
        verify("4.jar", "-verbose")
649
                .shouldHaveExitValue(16)
650
                .shouldContain("treated as unsigned")
651
                .shouldContain("Unparsable signature-related file META-INF/SIGNER.RSA");
652 653 654
    }

    static OutputAnalyzer jarsigner(List<String> extra)
655 656 657 658 659
            throws Exception {
        List<String> args = new ArrayList<>(
                listOf("-keystore", "ks", "-storepass", "changeit"));
        args.addAll(extra);
        return SecurityTools.jarsigner(args);
660 661 662
    }

    static OutputAnalyzer verify(String file, String... extra)
663
            throws Exception {
664 665
        List<String> args = new ArrayList<>();
        args.add("-verify");
666
        args.add("-strict");
667 668 669 670 671
        args.add(file);
        args.addAll(Arrays.asList(extra));
        return jarsigner(args);
    }

672
    static void checkBadKU(String file) throws Exception {
673 674
        System.err.println("BadKU: " + file);
        verify(file)
675
                .shouldHaveExitValue(16)
676 677 678
                .shouldContain("treated as unsigned")
                .shouldContain("re-run jarsigner with debug enabled");
        verify(file, "-verbose")
679
                .shouldHaveExitValue(16)
680 681 682 683
                .shouldContain("Signed by")
                .shouldContain("treated as unsigned")
                .shouldContain("re-run jarsigner with debug enabled");
        verify(file, "-J-Djava.security.debug=jar")
684
                .shouldHaveExitValue(16)
685 686 687 688 689
                .shouldContain("SignatureException: Key usage restricted")
                .shouldContain("treated as unsigned")
                .shouldContain("re-run jarsigner with debug enabled");
    }

690
    static void checkWeak(String file) throws Exception {
691
        verify(file)
692
                .shouldHaveExitValue(16)
693 694 695 696
                .shouldContain("treated as unsigned")
                .shouldMatch("weak algorithm that is now disabled.")
                .shouldMatch("Re-run jarsigner with the -verbose option for more details");
        verify(file, "-verbose")
697
                .shouldHaveExitValue(16)
698 699 700 701 702 703 704 705
                .shouldContain("treated as unsigned")
                .shouldMatch("weak algorithm that is now disabled by")
                .shouldMatch("Digest algorithm: .*weak")
                .shouldMatch("Signature algorithm: .*weak")
                .shouldMatch("Timestamp digest algorithm: .*weak")
                .shouldNotMatch("Timestamp signature algorithm: .*weak.*weak")
                .shouldMatch("Timestamp signature algorithm: .*key.*weak");
        verify(file, "-J-Djava.security.debug=jar")
706
                .shouldHaveExitValue(16)
707
                .shouldMatch("SignatureException:.*disabled");
708 709
    }

710
    static void checkHalfWeak(String file) throws Exception {
711
        verify(file)
712
                .shouldHaveExitValue(16)
713 714 715 716
                .shouldContain("treated as unsigned")
                .shouldMatch("weak algorithm that is now disabled.")
                .shouldMatch("Re-run jarsigner with the -verbose option for more details");
        verify(file, "-verbose")
717
                .shouldHaveExitValue(16)
718 719 720 721 722 723 724 725 726
                .shouldContain("treated as unsigned")
                .shouldMatch("weak algorithm that is now disabled by")
                .shouldMatch("Digest algorithm: .*weak")
                .shouldNotMatch("Signature algorithm: .*weak")
                .shouldNotMatch("Timestamp digest algorithm: .*weak")
                .shouldNotMatch("Timestamp signature algorithm: .*weak.*weak")
                .shouldNotMatch("Timestamp signature algorithm: .*key.*weak");
     }

727
    static void checkMultiple(String file) throws Exception {
728 729 730 731 732 733 734 735 736 737 738 739 740
        verify(file)
                .shouldHaveExitValue(0)
                .shouldContain("jar verified");
        verify(file, "-verbose", "-certs")
                .shouldHaveExitValue(0)
                .shouldContain("jar verified")
                .shouldMatch("X.509.*CN=dsakey")
                .shouldNotMatch("X.509.*CN=weakkeysize")
                .shouldMatch("Signed by .*CN=dsakey")
                .shouldMatch("Signed by .*CN=weakkeysize")
                .shouldMatch("Signature algorithm: .*key.*weak");
     }

741 742 743
    static void checkTimestamp(String file, String policyId, String digestAlg)
            throws Exception {
        try (JarFile jf = new JarFile(file)) {
744
            JarEntry je = jf.getJarEntry("META-INF/SIGNER.RSA");
745
            try (InputStream is = jf.getInputStream(je)) {
746
                byte[] content = IOUtils.readAllBytes(is);
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763
                PKCS7 p7 = new PKCS7(content);
                SignerInfo[] si = p7.getSignerInfos();
                if (si == null || si.length == 0) {
                    throw new Exception("Not signed");
                }
                PKCS9Attribute p9 = si[0].getUnauthenticatedAttributes()
                        .getAttribute(PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID);
                PKCS7 tsToken = new PKCS7((byte[]) p9.getValue());
                TimestampToken tt =
                        new TimestampToken(tsToken.getContentInfo().getData());
                if (!tt.getHashAlgorithm().toString().equals(digestAlg)) {
                    throw new Exception("Digest alg different");
                }
                if (!tt.getPolicyID().equals(policyId)) {
                    throw new Exception("policyId different");
                }
            }
764 765 766
        }
    }

767 768
    static int which = 0;

769
    /**
770 771 772
     * Sign with a TSA path. Always use alias "signer" to sign "unsigned.jar".
     * The signed jar name is always path.jar.
     *
773
     * @param extra more args given to jarsigner
774
     */
775
    static OutputAnalyzer sign(String path, String... extra)
776 777 778 779 780 781 782
            throws Exception {
        return signVerbose(
                path,
                "unsigned.jar",
                path + ".jar",
                "signer",
                extra);
783 784
    }

785 786 787 788 789 790
    static OutputAnalyzer signVerbose(
            String path,    // TSA URL path
            String oldJar,
            String newJar,
            String alias,   // signer
            String...extra) throws Exception {
791
        which++;
792
        System.out.println("\n>> Test #" + which);
793
        List<String> args = new ArrayList<>();
794 795
        args.add("-strict");
        args.add("-verbose");
796 797
        args.add("-debug");
        args.add("-signedjar");
798 799
        args.add(newJar);
        args.add(oldJar);
800
        args.add(alias);
801
        if (path != null) {
802 803 804 805 806 807
            args.add("-tsa");
            args.add(host + path);
         }
        args.addAll(Arrays.asList(extra));
        return jarsigner(args);
    }
W
weijun 已提交
808

809
    static void prepare() throws Exception {
810 811 812 813
        JarUtils.createJar("unsigned.jar", "A");
        Files.deleteIfExists(Paths.get("ks"));
        keytool("-alias signer -genkeypair -ext bc -dname CN=signer");
        keytool("-alias oldsigner -genkeypair -dname CN=oldsigner");
814 815
        keytool("-alias dsakey -genkeypair -keyalg DSA -dname CN=dsakey");
        keytool("-alias weakkeysize -genkeypair -keysize 512 -dname CN=weakkeysize");
816 817
        keytool("-alias badku -genkeypair -dname CN=badku");
        keytool("-alias ts -genkeypair -dname CN=ts");
818 819
        keytool("-alias tsold -genkeypair -dname CN=tsold");
        keytool("-alias tsweak -genkeypair -keysize 512 -dname CN=tsweak");
820 821 822
        keytool("-alias tsbad1 -genkeypair -dname CN=tsbad1");
        keytool("-alias tsbad2 -genkeypair -dname CN=tsbad2");
        keytool("-alias tsbad3 -genkeypair -dname CN=tsbad3");
823 824
        keytool("-alias tsnoca -genkeypair -dname CN=tsnoca");

825 826 827 828 829 830 831 832
        keytool("-alias expired -genkeypair -dname CN=expired");
        keytool("-alias expiring -genkeypair -dname CN=expiring");
        keytool("-alias long -genkeypair -dname CN=long");
        keytool("-alias tsexpired -genkeypair -dname CN=tsexpired");
        keytool("-alias tsexpiring -genkeypair -dname CN=tsexpiring");
        keytool("-alias tsexpiringsoon -genkeypair -dname CN=tsexpiringsoon");
        keytool("-alias tslong -genkeypair -dname CN=tslong");

833 834 835 836 837
        // tsnoca's issuer will be removed from keystore later
        keytool("-alias ca -genkeypair -ext bc -dname CN=CA");
        gencert("tsnoca", "-ext eku:critical=ts");
        keytool("-delete -alias ca");
        keytool("-alias ca -genkeypair -ext bc -dname CN=CA -startdate -40d");
838

839 840
        gencert("signer");
        gencert("oldsigner", "-startdate -30d -validity 20");
841 842
        gencert("dsakey");
        gencert("weakkeysize");
843
        gencert("badku", "-ext ku:critical=keyAgreement");
844 845 846 847 848 849 850 851 852
        gencert("ts", "-ext eku:critical=ts -validity 500");

        gencert("expired", "-validity 10 -startdate -12d");
        gencert("expiring", "-validity 178");
        gencert("long", "-validity 182");
        gencert("tsexpired", "-ext eku:critical=ts -validity 10 -startdate -12d");
        gencert("tsexpiring", "-ext eku:critical=ts -validity 364");
        gencert("tsexpiringsoon", "-ext eku:critical=ts -validity 170"); // earlier than expiring
        gencert("tslong", "-ext eku:critical=ts -validity 367");
853 854 855


        for (int i = 0; i < 5; i++) {
856 857 858 859 860 861 862 863
            // Issue another cert for "ts" with a different EKU.
            // Length might be different because serial number is
            // random. Try several times until a cert with the same
            // length is generated so we can substitute ts.cert
            // embedded in the PKCS7 block with ts2.cert.
            // If cannot create one, related test will be ignored.
            keytool("-gencert -alias ca -infile ts.req -outfile ts2.cert " +
                    "-ext eku:critical=1.3.6.1.5.5.7.3.9");
864 865 866 867 868 869 870 871
            if (Files.size(Paths.get("ts.cert")) != Files.size(Paths.get("ts2.cert"))) {
                Files.delete(Paths.get("ts2.cert"));
                System.out.println("Warning: cannot create same length");
            } else {
                break;
            }
        }

872
        gencert("tsold", "-ext eku:critical=ts -startdate -40d -validity 500");
873

874 875 876 877 878 879 880 881 882 883 884 885
        gencert("tsweak", "-ext eku:critical=ts");
        gencert("tsbad1");
        gencert("tsbad2", "-ext eku=ts");
        gencert("tsbad3", "-ext eku:critical=cs");
    }

    static void gencert(String alias, String... extra) throws Exception {
        keytool("-alias " + alias + " -certreq -file " + alias + ".req");
        String genCmd = "-gencert -alias ca -infile " +
                alias + ".req -outfile " + alias + ".cert";
        for (String s : extra) {
            genCmd += " " + s;
W
weijun 已提交
886
        }
887 888 889 890 891
        keytool(genCmd);
        keytool("-alias " + alias + " -importcert -file " + alias + ".cert");
    }

    static void keytool(String cmd) throws Exception {
892
        cmd = "-keystore ks -storepass changeit -keypass changeit " +
893 894
                "-keyalg rsa -validity 200 " + cmd;
        sun.security.tools.keytool.Main.main(cmd.split(" "));
895
    }
896 897 898 899 900 901 902 903

    static <K,V> Map<K,V> mapOf(K k1, V v1) {
        return Collections.singletonMap(k1, v1);
    }

    static <E> List<E> listOf(E... elements) {
        return Arrays.asList(elements);
    }
904
}