TimestampCheck.java 23.2 KB
Newer Older
1
/*
2
 * Copyright (c) 2003, 2016, 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 25
 */

import com.sun.net.httpserver.*;
import java.io.ByteArrayOutputStream;
26
import java.io.File;
27 28
import java.io.FileInputStream;
import java.io.IOException;
29
import java.io.InputStream;
30 31 32
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.InetSocketAddress;
33 34
import java.nio.file.Files;
import java.nio.file.Paths;
35 36 37 38 39
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
40 41
import java.util.ArrayList;
import java.util.Arrays;
42
import java.util.Calendar;
43
import java.util.List;
44 45 46 47
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import sun.misc.IOUtils;
48 49
import jdk.testlibrary.*;
import jdk.testlibrary.JarUtils;
50 51
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
52
import sun.security.pkcs.PKCS9Attribute;
53
import sun.security.pkcs.SignerInfo;
54
import sun.security.timestamp.TimestampToken;
55 56 57 58 59 60
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;

61 62
/*
 * @test
R
rpatil 已提交
63
 * @bug 6543842 6543440 6939248 8009636 8024302 8163304 8169911 8169688 8171121
64 65 66 67 68 69 70 71 72
 * @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
 * @run main/timeout=600 TimestampCheck
 */
73 74
public class TimestampCheck {

75 76
    static final String defaultPolicyId = "2.3.4";
    static String host = null;
77

78 79 80 81 82 83
    static class Handler implements HttpHandler, AutoCloseable {

        private final HttpServer httpServer;
        private final String keystore;

        @Override
84 85 86 87 88 89 90 91 92 93 94
        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 {
95
                String path = t.getRequestURI().getPath().substring(1);
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
                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
         */
115
        byte[] sign(byte[] input, String path) throws Exception {
116 117 118 119 120 121 122 123
            DerValue value = new DerValue(input);
            System.err.println("\nIncoming Request\n===================");
            System.err.println("Version: " + value.data.getInteger());
            DerValue messageImprint = value.data.getDerValue();
            AlgorithmId aid = AlgorithmId.parse(
                    messageImprint.data.getDerValue());
            System.err.println("AlgorithmId: " + aid);

124
            ObjectIdentifier policyId = new ObjectIdentifier(defaultPolicyId);
125 126 127 128 129 130 131 132
            BigInteger nonce = null;
            while (value.data.available() > 0) {
                DerValue v = value.data.getDerValue();
                if (v.tag == DerValue.tag_Integer) {
                    nonce = v.getBigInteger();
                    System.err.println("nonce: " + nonce);
                } else if (v.tag == DerValue.tag_Boolean) {
                    System.err.println("certReq: " + v.getBoolean());
133 134 135
                } else if (v.tag == DerValue.tag_ObjectId) {
                    policyId = v.getOID();
                    System.err.println("PolicyID: " + policyId);
136 137 138 139
                }
            }

            System.err.println("\nResponse\n===================");
140 141 142 143
            FileInputStream is = new FileInputStream(keystore);
            KeyStore ks = KeyStore.getInstance("JCEKS");
            ks.load(is, "changeit".toCharArray());
            is.close();
144 145

            String alias = "ts";
146 147 148
            if (path.startsWith("bad") || path.equals("weak")) {
                alias = "ts" + path;
            }
149

150
            if (path.equals("diffpolicy")) {
151 152 153
                policyId = new ObjectIdentifier(defaultPolicyId);
            }

154 155 156 157 158
            DerOutputStream statusInfo = new DerOutputStream();
            statusInfo.putInteger(0);

            AlgorithmId[] algorithms = {aid};
            Certificate[] chain = ks.getCertificateChain(alias);
159
            X509Certificate[] signerCertificateChain;
160
            X509Certificate signer = (X509Certificate)chain[0];
161 162

            if (path.equals("fullchain")) {   // Only case 5 uses full chain
163 164 165 166
                signerCertificateChain = new X509Certificate[chain.length];
                for (int i=0; i<chain.length; i++) {
                    signerCertificateChain[i] = (X509Certificate)chain[i];
                }
167
            } else if (path.equals("nocert")) {
168 169 170 171 172 173 174 175 176
                signerCertificateChain = new X509Certificate[0];
            } else {
                signerCertificateChain = new X509Certificate[1];
                signerCertificateChain[0] = (X509Certificate)chain[0];
            }

            DerOutputStream tst = new DerOutputStream();

            tst.putInteger(1);
177
            tst.putOID(policyId);
178

179
            if (!path.equals("baddigest") && !path.equals("diffalg")) {
180 181 182
                tst.putDerValue(messageImprint);
            } else {
                byte[] data = messageImprint.toByteArray();
183
                if (path.equals("diffalg")) {
184 185 186 187 188 189 190 191 192 193 194 195 196 197
                    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);

            Calendar cal = Calendar.getInstance();
            tst.putGeneralizedTime(cal.getTime());

198
            if (path.equals("diffnonce")) {
199
                tst.putInteger(1234);
200 201
            } else if (path.equals("nononce")) {
                // no noce
202 203 204 205 206 207 208 209 210 211
            } else {
                tst.putInteger(nonce);
            }

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

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

212 213
            // Always use the same algorithm at timestamp signing
            // so it is different from the hash algorithm.
214
            Signature sig = Signature.getInstance("SHA1withRSA");
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
            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()));

            System.err.println("Signing...");
            System.err.println(new X500Name(signer
                    .getIssuerX500Principal().getName()));
            System.err.println(signer.getSerialNumber());

            SignerInfo signerInfo = new SignerInfo(
                    new X500Name(signer.getIssuerX500Principal().getName()),
                    signer.getSerialNumber(),
231
                    AlgorithmId.get("SHA-1"), AlgorithmId.get("RSA"), sig.sign());
232 233

            SignerInfo[] signerInfos = {signerInfo};
234 235
            PKCS7 p7 = new PKCS7(algorithms, contentInfo,
                    signerCertificateChain, signerInfos);
236 237 238 239 240 241 242 243 244 245 246 247 248
            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();
        }

249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
        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;
        }
267

268 269 270 271 272
        /**
         * Start TSA service.
         */
        void start() {
            httpServer.start();
273 274
        }

275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
        /**
         * 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();
        }
    }
294 295 296 297 298 299

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

        prepare();

        try (Handler tsa = Handler.init(0, "tsks");) {
300 301 302
            tsa.start();
            int port = tsa.getPort();

303
            host = "http://localhost:" + port + "/";
304

305
            if (args.length == 0) {         // Run this test
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
                sign("none")
                        .shouldContain("is not timestamped")
                        .shouldHaveExitValue(0);

                sign("badku")
                        .shouldHaveExitValue(0);
                checkBadKU("badku.jar");

                sign("normal")
                        .shouldNotContain("is not timestamped")
                        .shouldHaveExitValue(0);

                sign("nononce")
                        .shouldHaveExitValue(1);
                sign("diffnonce")
                        .shouldHaveExitValue(1);
                sign("baddigest")
                        .shouldHaveExitValue(1);
                sign("diffalg")
                        .shouldHaveExitValue(1);
                sign("fullchain")
                        .shouldHaveExitValue(0);   // Success, 6543440 solved.
                sign("bad1")
                        .shouldHaveExitValue(1);
                sign("bad2")
                        .shouldHaveExitValue(1);
                sign("bad3")
                        .shouldHaveExitValue(1);
                sign("nocert")
                        .shouldHaveExitValue(1);

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

                sign("diffpolicy", "-tsapolicyid", "1.2.3")
                        .shouldHaveExitValue(1);

                sign("tsaalg", "-tsadigestalg", "SHA")
                        .shouldHaveExitValue(0);
                checkTimestamp("tsaalg.jar", defaultPolicyId, "SHA-1");

R
rpatil 已提交
348 349
                sign("weak", "-digestalg", "MD5",
                                "-sigalg", "MD5withRSA", "-tsadigestalg", "MD5")
350 351 352
                        .shouldHaveExitValue(0);
                checkWeak("weak.jar");

353 354 355 356 357 358 359 360 361 362 363 364
                signWithAliasAndTsa("halfWeak", "old.jar", "old", "-digestalg", "MD5")
                        .shouldHaveExitValue(0);
                checkHalfWeak("halfWeak.jar");

                // sign with DSA key
                signWithAliasAndTsa("sign1", "old.jar", "dsakey")
                        .shouldHaveExitValue(0);
                // sign with RSAkeysize < 1024
                signWithAliasAndTsa("sign2", "sign1.jar", "weakkeysize")
                        .shouldHaveExitValue(0);
                checkMultiple("sign2.jar");

365 366
                // When .SF or .RSA is missing or invalid
                checkMissingOrInvalidFiles("normal.jar");
367 368 369 370
            } else {                        // Run as a standalone server
                System.err.println("Press Enter to quit server");
                System.in.read();
            }
371 372 373
        }
    }

374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
    private static void checkMissingOrInvalidFiles(String s)
            throws Throwable {
        JarUtils.updateJar(s, "1.jar", "-", "META-INF/OLD.SF");
        verify("1.jar", "-verbose")
                .shouldHaveExitValue(0)
                .shouldContain("treated as unsigned")
                .shouldContain("Missing signature-related file META-INF/OLD.SF");
        JarUtils.updateJar(s, "2.jar", "-", "META-INF/OLD.RSA");
        verify("2.jar", "-verbose")
                .shouldHaveExitValue(0)
                .shouldContain("treated as unsigned")
                .shouldContain("Missing block file for signature-related file META-INF/OLD.SF");
        JarUtils.updateJar(s, "3.jar", "META-INF/OLD.SF");
        verify("3.jar", "-verbose")
                .shouldHaveExitValue(0)
                .shouldContain("treated as unsigned")
                .shouldContain("Unparsable signature-related file META-INF/OLD.SF");
        JarUtils.updateJar(s, "4.jar", "META-INF/OLD.RSA");
        verify("4.jar", "-verbose")
                .shouldHaveExitValue(0)
                .shouldContain("treated as unsigned")
                .shouldContain("Unparsable signature-related file META-INF/OLD.RSA");
    }

    static OutputAnalyzer jarsigner(List<String> extra)
            throws Throwable {
        JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jarsigner")
                .addVMArg("-Duser.language=en")
                .addVMArg("-Duser.country=US")
                .addToolArg("-keystore")
                .addToolArg("tsks")
                .addToolArg("-storepass")
                .addToolArg("changeit");
        for (String s : extra) {
            if (s.startsWith("-J")) {
                launcher.addVMArg(s.substring(2));
            } else {
                launcher.addToolArg(s);
            }
        }
        System.err.println("COMMAND: ");
        for (String cmd : launcher.getCommand()) {
            System.err.print(cmd + " ");
        }
        System.err.println();
        return ProcessTools.executeCommand(launcher.getCommand());
    }

    static OutputAnalyzer verify(String file, String... extra)
            throws Throwable {
        List<String> args = new ArrayList<>();
        args.add("-verify");
        args.add(file);
        args.addAll(Arrays.asList(extra));
        return jarsigner(args);
    }

    static void checkBadKU(String file) throws Throwable {
        System.err.println("BadKU: " + file);
        verify(file)
                .shouldHaveExitValue(0)
                .shouldContain("treated as unsigned")
                .shouldContain("re-run jarsigner with debug enabled");
        verify(file, "-verbose")
                .shouldHaveExitValue(0)
                .shouldContain("Signed by")
                .shouldContain("treated as unsigned")
                .shouldContain("re-run jarsigner with debug enabled");
        verify(file, "-J-Djava.security.debug=jar")
                .shouldHaveExitValue(0)
                .shouldContain("SignatureException: Key usage restricted")
                .shouldContain("treated as unsigned")
                .shouldContain("re-run jarsigner with debug enabled");
    }

    static void checkWeak(String file) throws Throwable {
        verify(file)
                .shouldHaveExitValue(0)
                .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")
                .shouldHaveExitValue(0)
                .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")
                .shouldHaveExitValue(0)
                .shouldMatch("SignatureException:.*Disabled");
    }

469 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
    static void checkHalfWeak(String file) throws Throwable {
        verify(file)
                .shouldHaveExitValue(0)
                .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")
                .shouldHaveExitValue(0)
                .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");
     }

    static void checkMultiple(String file) throws Throwable {
        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");
     }

500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
    static void checkTimestamp(String file, String policyId, String digestAlg)
            throws Exception {
        try (JarFile jf = new JarFile(file)) {
            JarEntry je = jf.getJarEntry("META-INF/OLD.RSA");
            try (InputStream is = jf.getInputStream(je)) {
                byte[] content = IOUtils.readFully(is, -1, true);
                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");
                }
            }
523 524 525
        }
    }

526 527
    static int which = 0;

528
    /**
529
     * @param extra more args given to jarsigner
530
     */
531 532
    static OutputAnalyzer sign(String path, String... extra)
            throws Throwable {
533 534 535 536 537 538
        String alias = path.equals("badku") ? "badku" : "old";
        return signWithAliasAndTsa(path, "old.jar", alias, extra);
    }

    static OutputAnalyzer signWithAliasAndTsa (String path, String jar,
            String alias, String...extra) throws Throwable {
539 540 541 542 543 544 545
        which++;
        System.err.println("\n>> Test #" + which + ": " + Arrays.toString(extra));
        List<String> args = new ArrayList<>();
        args.add("-J-Djava.security.egd=file:/dev/./urandom");
        args.add("-debug");
        args.add("-signedjar");
        args.add(path + ".jar");
546 547
        args.add(jar);
        args.add(alias);
548 549 550 551 552 553 554
        if (!path.equals("none") && !path.equals("badku")) {
            args.add("-tsa");
            args.add(host + path);
         }
        args.addAll(Arrays.asList(extra));
        return jarsigner(args);
    }
W
weijun 已提交
555

556 557 558 559 560
    static void prepare() throws Exception {
        jdk.testlibrary.JarUtils.createJar("old.jar", "A");
        Files.deleteIfExists(Paths.get("tsks"));
        keytool("-alias ca -genkeypair -ext bc -dname CN=CA");
        keytool("-alias old -genkeypair -dname CN=old");
561 562
        keytool("-alias dsakey -genkeypair -keyalg DSA -dname CN=dsakey");
        keytool("-alias weakkeysize -genkeypair -keysize 512 -dname CN=weakkeysize");
563 564 565 566 567 568 569 570
        keytool("-alias badku -genkeypair -dname CN=badku");
        keytool("-alias ts -genkeypair -dname CN=ts");
        keytool("-alias tsweak -genkeypair -keysize 512 -dname CN=tsbad1");
        keytool("-alias tsbad1 -genkeypair -dname CN=tsbad1");
        keytool("-alias tsbad2 -genkeypair -dname CN=tsbad2");
        keytool("-alias tsbad3 -genkeypair -dname CN=tsbad3");

        gencert("old");
571 572
        gencert("dsakey");
        gencert("weakkeysize");
573 574 575 576 577 578 579 580 581 582 583 584 585 586
        gencert("badku", "-ext ku:critical=keyAgreement");
        gencert("ts", "-ext eku:critical=ts");
        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 已提交
587
        }
588 589 590 591 592 593 594 595
        keytool(genCmd);
        keytool("-alias " + alias + " -importcert -file " + alias + ".cert");
    }

    static void keytool(String cmd) throws Exception {
        cmd = "-keystore tsks -storepass changeit -keypass changeit " +
                "-keyalg rsa -validity 200 " + cmd;
        sun.security.tools.keytool.Main.main(cmd.split(" "));
596 597
    }
}