diff --git a/src/share/classes/sun/security/tools/jarsigner/Main.java b/src/share/classes/sun/security/tools/jarsigner/Main.java index 303a04878f127c641642c7cb3966ec4296a1ffff..57dc512e64606d4fdf2a757feb82713080083c27 100644 --- a/src/share/classes/sun/security/tools/jarsigner/Main.java +++ b/src/share/classes/sun/security/tools/jarsigner/Main.java @@ -175,11 +175,20 @@ public class Main { private boolean noTimestamp = false; private Date expireDate = new Date(0L); // used in noTimestamp warning - // Severe warnings + // Severe warnings. + + // jarsigner used to check signer cert chain validity and key usages + // itself and set various warnings. Later CertPath validation is + // added but chainNotValidated is only flagged when no other existing + // warnings are set. TSA cert chain check is added separately and + // only tsaChainNotValidated is set, i.e. has no affect on hasExpiredCert, + // notYetValidCert, or any badXyzUsage. + private int weakAlg = 0; // 1. digestalg, 2. sigalg, 4. tsadigestalg private boolean hasExpiredCert = false; private boolean notYetValidCert = false; private boolean chainNotValidated = false; + private boolean tsaChainNotValidated = false; private boolean notSignedByAlias = false; private boolean aliasNotInStore = false; private boolean hasUnsignedEntry = false; @@ -189,6 +198,7 @@ public class Main { private boolean signerSelfSigned = false; private Throwable chainNotValidatedReason = null; + private Throwable tsaChainNotValidatedReason = null; private boolean seeWeak = false; @@ -279,7 +289,8 @@ public class Main { if (strict) { int exitCode = 0; - if (weakAlg != 0 || chainNotValidated || hasExpiredCert || notYetValidCert || signerSelfSigned) { + if (weakAlg != 0 || chainNotValidated + || hasExpiredCert || notYetValidCert || signerSelfSigned) { exitCode |= 4; } if (badKeyUsage || badExtendedKeyUsage || badNetscapeCertType) { @@ -291,6 +302,9 @@ public class Main { if (notSignedByAlias || aliasNotInStore) { exitCode |= 32; } + if (tsaChainNotValidated) { + exitCode |= 64; + } if (exitCode != 0) { System.exit(exitCode); } @@ -811,6 +825,9 @@ public class Main { System.out.println(rb.getString("no.manifest.")); } + // If there is a time stamp block inside the PKCS7 block file + boolean hasTimestampBlock = false; + // Even if the verbose option is not specified, all out strings // must be generated so seeWeak can be updated. if (!digestMap.isEmpty() @@ -839,6 +856,7 @@ public class Main { PublicKey key = signer.getPublicKey(); PKCS7 tsToken = si.getTsToken(); if (tsToken != null) { + hasTimestampBlock = true; SignerInfo tsSi = tsToken.getSignerInfos()[0]; X509Certificate tsSigner = tsSi.getCertificate(tsToken); byte[] encTsTokenInfo = tsToken.getContentInfo().getData(); @@ -921,7 +939,7 @@ public class Main { if (badKeyUsage || badExtendedKeyUsage || badNetscapeCertType || notYetValidCert || chainNotValidated || hasExpiredCert || hasUnsignedEntry || signerSelfSigned || (weakAlg != 0) || - aliasNotInStore || notSignedByAlias) { + aliasNotInStore || notSignedByAlias || tsaChainNotValidated) { if (strict) { System.out.println(rb.getString("jar.verified.with.signer.errors.")); @@ -971,10 +989,16 @@ public class Main { if (chainNotValidated) { System.out.println(String.format( - rb.getString("This.jar.contains.entries.whose.certificate.chain.is.not.validated.reason.1"), + rb.getString("This.jar.contains.entries.whose.certificate.chain.is.invalid.reason.1"), chainNotValidatedReason.getLocalizedMessage())); } + if (tsaChainNotValidated) { + System.out.println(String.format( + rb.getString("This.jar.contains.entries.whose.tsa.certificate.chain.is.invalid.reason.1"), + tsaChainNotValidatedReason.getLocalizedMessage())); + } + if (notSignedByAlias) { System.out.println( rb.getString("This.jar.contains.signed.entries.which.is.not.signed.by.the.specified.alias.es.")); @@ -1002,8 +1026,15 @@ public class Main { "This.jar.contains.entries.whose.signer.certificate.will.expire.within.six.months.")); } if (noTimestamp) { - System.out.println( - String.format(rb.getString("no.timestamp.verifying"), expireDate)); + if (hasTimestampBlock) { + // JarSigner API has not seen the timestamp, + // might have ignored it due to weak alg, etc. + System.out.println( + String.format(rb.getString("bad.timestamp.verifying"), expireDate)); + } else { + System.out.println( + String.format(rb.getString("no.timestamp.verifying"), expireDate)); + } } } if (warningAppeared || errorAppeared) { @@ -1054,16 +1085,23 @@ public class Main { private static MessageFormat expiredTimeForm = null; private static MessageFormat expiringTimeForm = null; - /* - * Display some details about a certificate: + /** + * Returns a string about a certificate: * * [] [", " ] [" (" ")"] * [ | ] + * [] + * + * Note: no newline character at the end. * - * Note: no newline character at the end + * When isTsCert is true, this method sets global flags like hasExpiredCert, + * notYetValidCert, badKeyUsage, badExtendedKeyUsage, badNetscapeCertType. + * + * @param isTsCert true if c is in the TSA cert chain, false otherwise. + * @param checkUsage true to check code signer keyUsage */ - String printCert(String tab, Certificate c, boolean checkValidityPeriod, - Date timestamp, boolean checkUsage) { + String printCert(boolean isTsCert, String tab, Certificate c, + Date timestamp, boolean checkUsage) throws Exception { StringBuilder certStr = new StringBuilder(); String space = rb.getString("SPACE"); @@ -1083,7 +1121,7 @@ public class Main { certStr.append(space).append(alias); } - if (checkValidityPeriod && x509Cert != null) { + if (x509Cert != null) { certStr.append("\n").append(tab).append("["); Date notAfter = x509Cert.getNotAfter(); @@ -1096,7 +1134,7 @@ public class Main { x509Cert.checkValidity(); // test if cert will expire within six months if (notAfter.getTime() < System.currentTimeMillis() + SIX_MONTHS) { - hasExpiringCert = true; + if (!isTsCert) hasExpiringCert = true; if (expiringTimeForm == null) { expiringTimeForm = new MessageFormat( rb.getString("certificate.will.expire.on")); @@ -1117,7 +1155,7 @@ public class Main { certStr.append(validityTimeForm.format(source)); } } catch (CertificateExpiredException cee) { - hasExpiredCert = true; + if (!isTsCert) hasExpiredCert = true; if (expiredTimeForm == null) { expiredTimeForm = new MessageFormat( @@ -1127,7 +1165,7 @@ public class Main { certStr.append(expiredTimeForm.format(source)); } catch (CertificateNotYetValidException cnyve) { - notYetValidCert = true; + if (!isTsCert) notYetValidCert = true; if (notYetTimeForm == null) { notYetTimeForm = new MessageFormat( @@ -1534,7 +1572,7 @@ public class Main { tsaURI); } System.out.println(rb.getString("TSA.certificate.") + - printCert("", tsaCert, false, null, false)); + printCert(true, "", tsaCert, null, false)); } if (signingMechanism != null) { System.out.println( @@ -1597,6 +1635,30 @@ public class Main { } } + // The JarSigner API always accepts the timestamp received. + // We need to extract the certs from the signed jar to + // validate it. + if (!noTimestamp) { + try (JarFile check = new JarFile(signedJarFile)) { + PKCS7 p7 = new PKCS7(check.getInputStream(check.getEntry( + "META-INF/" + sigfile + "." + privateKey.getAlgorithm()))); + SignerInfo si = p7.getSignerInfos()[0]; + PKCS7 tsToken = si.getTsToken(); + SignerInfo tsSi = tsToken.getSignerInfos()[0]; + try { + validateCertChain(Validator.VAR_TSA_SERVER, + tsSi.getCertificateChain(tsToken), null); + } catch (Exception e) { + tsaChainNotValidated = true; + tsaChainNotValidatedReason = e; + } + } catch (Exception e) { + if (debug) { + e.printStackTrace(); + } + } + } + // no IOException thrown in the follow try clause, so disable // the try clause. // try { @@ -1626,8 +1688,10 @@ public class Main { } boolean warningAppeared = false; - if (weakAlg != 0 || badKeyUsage || badExtendedKeyUsage || badNetscapeCertType || - notYetValidCert || chainNotValidated || hasExpiredCert || signerSelfSigned) { + if (weakAlg != 0 || badKeyUsage || badExtendedKeyUsage + || badNetscapeCertType || notYetValidCert + || chainNotValidated || tsaChainNotValidated + || hasExpiredCert || signerSelfSigned) { if (strict) { System.out.println(rb.getString("jar.signed.with.signer.errors.")); System.out.println(); @@ -1664,10 +1728,16 @@ public class Main { if (chainNotValidated) { System.out.println(String.format( - rb.getString("The.signer.s.certificate.chain.is.not.validated.reason.1"), + rb.getString("The.signer.s.certificate.chain.is.invalid.reason.1"), chainNotValidatedReason.getLocalizedMessage())); } + if (tsaChainNotValidated) { + System.out.println(String.format( + rb.getString("The.tsa.certificate.chain.is.invalid.reason.1"), + tsaChainNotValidatedReason.getLocalizedMessage())); + } + if (signerSelfSigned) { System.out.println( rb.getString("The.signer.s.certificate.is.self.signed.")); @@ -1763,18 +1833,18 @@ public class Main { /** * Returns a string of singer info, with a newline at the end */ - private String signerInfo(CodeSigner signer, String tab) { + private String signerInfo(CodeSigner signer, String tab) throws Exception { if (cacheForSignerInfo.containsKey(signer)) { return cacheForSignerInfo.get(signer); } - StringBuffer s = new StringBuffer(); + StringBuilder sb = new StringBuilder(); List certs = signer.getSignerCertPath().getCertificates(); // display the signature timestamp, if present Date timestamp; Timestamp ts = signer.getTimestamp(); if (ts != null) { - s.append(printTimestamp(tab, ts)); - s.append('\n'); + sb.append(printTimestamp(tab, ts)); + sb.append('\n'); timestamp = ts.getTimestamp(); } else { timestamp = null; @@ -1783,24 +1853,41 @@ public class Main { // display the certificate(s). The first one is end-entity cert and // its KeyUsage should be checked. boolean first = true; + sb.append(tab).append(rb.getString("...Signer")).append('\n'); for (Certificate c : certs) { - s.append(printCert(tab, c, true, timestamp, first)); - s.append('\n'); + sb.append(printCert(false, tab, c, timestamp, first)); + sb.append('\n'); first = false; } try { - validateCertChain(certs); + validateCertChain(Validator.VAR_CODE_SIGNING, certs, ts); } catch (Exception e) { chainNotValidated = true; chainNotValidatedReason = e; - s.append(tab).append(rb.getString(".CertPath.not.validated.")) - .append(e.getLocalizedMessage()).append("]\n"); // TODO + sb.append(tab).append(rb.getString(".Invalid.certificate.chain.")) + .append(e.getLocalizedMessage()).append("]\n"); + } + if (ts != null) { + sb.append(tab).append(rb.getString("...TSA")).append('\n'); + for (Certificate c : ts.getSignerCertPath().getCertificates()) { + sb.append(printCert(true, tab, c, timestamp, false)); + sb.append('\n'); + } + try { + validateCertChain(Validator.VAR_TSA_SERVER, + ts.getSignerCertPath().getCertificates(), null); + } catch (Exception e) { + tsaChainNotValidated = true; + tsaChainNotValidatedReason = e; + sb.append(tab).append(rb.getString(".Invalid.TSA.certificate.chain.")) + .append(e.getLocalizedMessage()).append("]\n"); + } } if (certs.size() == 1 && KeyStoreUtil.isSelfSigned((X509Certificate)certs.get(0))) { signerSelfSigned = true; } - String result = s.toString(); + String result = sb.toString(); cacheForSignerInfo.put(signer, result); return result; } @@ -2043,7 +2130,7 @@ public class Main { } } - void getAliasInfo(String alias) { + void getAliasInfo(String alias) throws Exception { Key key = null; @@ -2089,10 +2176,11 @@ public class Main { // We don't meant to print anything, the next call // checks validity and keyUsage etc - printCert("", certChain[0], true, null, true); + printCert(false, "", certChain[0], null, true); try { - validateCertChain(Arrays.asList(certChain)); + validateCertChain(Validator.VAR_CODE_SIGNING, + Arrays.asList(certChain), null); } catch (Exception e) { chainNotValidated = true; chainNotValidatedReason = e; @@ -2153,17 +2241,31 @@ public class Main { System.exit(1); } - void validateCertChain(List certs) throws Exception { + /** + * Validates a cert chain. + * + * @param parameter this might be a timestamp + */ + void validateCertChain(String variant, List certs, + Object parameter) + throws Exception { try { Validator.getInstance(Validator.TYPE_PKIX, - Validator.VAR_CODE_SIGNING, + variant, pkixParameters) - .validate(certs.toArray(new X509Certificate[certs.size()])); + .validate(certs.toArray(new X509Certificate[certs.size()]), + null, parameter); } catch (Exception e) { if (debug) { e.printStackTrace(); } - if (e instanceof ValidatorException) { + + // Exception might be dismissed if another warning flag + // is already set by printCert. This is only done for + // code signing certs. + + if (variant.equals(Validator.VAR_CODE_SIGNING) && + e instanceof ValidatorException) { // Throw cause if it's CertPathValidatorException, if (e.getCause() != null && e.getCause() instanceof CertPathValidatorException) { diff --git a/src/share/classes/sun/security/tools/jarsigner/Resources.java b/src/share/classes/sun/security/tools/jarsigner/Resources.java index 21f842fc9cdce8139d486163dae21c544732ed42..7a6ea5cbeb6e171f5fff20cd20c0332121f2fa00 100644 --- a/src/share/classes/sun/security/tools/jarsigner/Resources.java +++ b/src/share/classes/sun/security/tools/jarsigner/Resources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -199,7 +199,8 @@ public class Resources extends java.util.ListResourceBundle { {"certificate.is.not.valid.until", "certificate is not valid until {0}"}, {"certificate.will.expire.on", "certificate will expire on {0}"}, - {".CertPath.not.validated.", "[CertPath not validated: "}, + {".Invalid.certificate.chain.", "[Invalid certificate chain: "}, + {".Invalid.TSA.certificate.chain.", "[Invalid TSA certificate chain: "}, {"requesting.a.signature.timestamp", "requesting a signature timestamp"}, {"TSA.location.", "TSA location: "}, @@ -216,6 +217,8 @@ public class Resources extends java.util.ListResourceBundle { {"entry.was.signed.on", "entry was signed on {0}"}, {"Warning.", "Warning: "}, {"Error.", "Error: "}, + {"...Signer", ">>> Signer"}, + {"...TSA", ">>> TSA"}, {"This.jar.contains.unsigned.entries.which.have.not.been.integrity.checked.", "This jar contains unsigned entries which have not been integrity-checked. "}, {"This.jar.contains.entries.whose.signer.certificate.has.expired.", @@ -250,18 +253,24 @@ public class Resources extends java.util.ListResourceBundle { "This jar contains entries whose signer certificate's NetscapeCertType extension doesn't allow code signing."}, {".{0}.extension.does.not.support.code.signing.", "[{0} extension does not support code signing]"}, - {"The.signer.s.certificate.chain.is.not.validated.reason.1", - "The signer's certificate chain is not validated. Reason: %s"}, + {"The.signer.s.certificate.chain.is.invalid.reason.1", + "The signer's certificate chain is invalid. Reason: %s"}, + {"The.tsa.certificate.chain.is.invalid.reason.1", + "The TSA certificate chain is invalid. Reason: %s"}, {"The.signer.s.certificate.is.self.signed.", "The signer's certificate is self-signed."}, {"The.1.algorithm.specified.for.the.2.option.is.considered.a.security.risk.", "The %1$s algorithm specified for the %2$s option is considered a security risk."}, - {"This.jar.contains.entries.whose.certificate.chain.is.not.validated.reason.1", - "This jar contains entries whose certificate chain is not validated. Reason: %s"}, + {"This.jar.contains.entries.whose.certificate.chain.is.invalid.reason.1", + "This jar contains entries whose certificate chain is invalid. Reason: %s"}, + {"This.jar.contains.entries.whose.tsa.certificate.chain.is.invalid.reason.1", + "This jar contains entries whose TSA certificate chain is invalid. Reason: %s"}, {"no.timestamp.signing", "No -tsa or -tsacert is provided and this jar is not timestamped. Without a timestamp, users may not be able to validate this jar after the signer certificate's expiration date (%1$tY-%1$tm-%1$td) or after any future revocation date."}, {"no.timestamp.verifying", "This jar contains signatures that does not include a timestamp. Without a timestamp, users may not be able to validate this jar after the signer certificate's expiration date (%1$tY-%1$tm-%1$td) or after any future revocation date."}, + {"bad.timestamp.verifying", + "This jar contains signatures that include an invalid timestamp. Without a valid timestamp, users may not be able to validate this jar after any of the signer certificates expire (as early as %1$tY-%1$tm-%1$td).\nRerun jarsigner with -J-Djava.security.debug=jar for more information."}, {"Unknown.password.type.", "Unknown password type: "}, {"Cannot.find.environment.variable.", "Cannot find environment variable: "}, diff --git a/src/share/classes/sun/security/util/SignatureFileVerifier.java b/src/share/classes/sun/security/util/SignatureFileVerifier.java index 0663f0573ae35823427a3abbc503151bb292a354..ee5c28832172bf3dc0bffc67840144a73e4456d4 100644 --- a/src/share/classes/sun/security/util/SignatureFileVerifier.java +++ b/src/share/classes/sun/security/util/SignatureFileVerifier.java @@ -718,7 +718,8 @@ public class SignatureFileVerifier { if (signers == null) { signers = new ArrayList<>(); } - // Append the new code signer + // Append the new code signer. If timestamp is invalid, this + // jar will be treated as unsigned. signers.add(new CodeSigner(certChain, info.getTimestamp())); if (debug != null) { diff --git a/test/lib/testlibrary/jdk/testlibrary/SecurityTools.java b/test/lib/testlibrary/jdk/testlibrary/SecurityTools.java index 6eca88d2601da36c92cdebc4f2b1185567befc7b..71a4b852deb0a21faf654035962eec5baaa63e08 100644 --- a/test/lib/testlibrary/jdk/testlibrary/SecurityTools.java +++ b/test/lib/testlibrary/jdk/testlibrary/SecurityTools.java @@ -49,10 +49,7 @@ public class SecurityTools { launcher.addToolArg(arg); } } - String[] cmds = launcher.getCommand(); - String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" ")); - System.out.println("Command line: [" + cmdLine + "]"); - return new ProcessBuilder(cmds); + return new ProcessBuilder(launcher.getCommand()); } // keytool @@ -69,7 +66,7 @@ public class SecurityTools { pb.redirectInput(ProcessBuilder.Redirect.from(new File(RESPONSE_FILE))); try { - return ProcessTools.executeProcess(pb); + return execute(pb); } catch (Throwable t) { throw new RuntimeException("keytool failure: " + t); } finally { @@ -101,11 +98,20 @@ public class SecurityTools { public static OutputAnalyzer jarsigner(List args) throws Exception { + return execute(getProcessBuilder("jarsigner", args)); + } + + private static OutputAnalyzer execute(ProcessBuilder pb) throws Exception { try { - return ProcessTools.executeProcess( - getProcessBuilder("jarsigner", args)); + OutputAnalyzer oa = ProcessTools.executeCommand(pb); + System.out.println("Exit value: " + oa.getExitValue()); + return oa; } catch (Throwable t) { - throw new RuntimeException("jarsigner error: " + t); + if (t instanceof Exception) { + throw (Exception) t; + } else { + throw new Exception(t); + } } } diff --git a/test/sun/security/tools/jarsigner/TimestampCheck.java b/test/sun/security/tools/jarsigner/TimestampCheck.java index 2d4760283722405787fe16b465965a77258a36de..ca676f414ed9363b5d3939d4763eb527c3087896 100644 --- a/test/sun/security/tools/jarsigner/TimestampCheck.java +++ b/test/sun/security/tools/jarsigner/TimestampCheck.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,6 +22,8 @@ */ import com.sun.net.httpserver.*; + +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -36,16 +38,18 @@ import java.security.KeyStore; import java.security.PrivateKey; import java.security.Signature; import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.List; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; import sun.misc.IOUtils; -import jdk.testlibrary.*; +import jdk.testlibrary.SecurityTools; +import jdk.testlibrary.OutputAnalyzer; import jdk.testlibrary.JarUtils; import sun.security.pkcs.ContentInfo; import sun.security.pkcs.PKCS7; @@ -58,9 +62,12 @@ import sun.security.util.ObjectIdentifier; import sun.security.x509.AlgorithmId; import sun.security.x509.X500Name; +import jdk.testlibrary.Utils; + /* * @test * @bug 6543842 6543440 6939248 8009636 8024302 8163304 8169911 8169688 8171121 + * 8180289 * @summary checking response of timestamp * @modules java.base/sun.security.pkcs * java.base/sun.security.timestamp @@ -68,7 +75,7 @@ import sun.security.x509.X500Name; * java.base/sun.security.util * java.base/sun.security.tools.keytool * @library /lib/testlibrary - * @run main/timeout=600 TimestampCheck + * @run main/othervm/timeout=600 TimestampCheck */ public class TimestampCheck { @@ -114,12 +121,12 @@ public class TimestampCheck { */ byte[] sign(byte[] input, String path) throws Exception { DerValue value = new DerValue(input); - System.err.println("\nIncoming Request\n==================="); - System.err.println("Version: " + value.data.getInteger()); + System.out.println("\nIncoming Request\n==================="); + System.out.println("Version: " + value.data.getInteger()); DerValue messageImprint = value.data.getDerValue(); AlgorithmId aid = AlgorithmId.parse( messageImprint.data.getDerValue()); - System.err.println("AlgorithmId: " + aid); + System.out.println("AlgorithmId: " + aid); ObjectIdentifier policyId = new ObjectIdentifier(defaultPolicyId); BigInteger nonce = null; @@ -127,25 +134,24 @@ public class TimestampCheck { DerValue v = value.data.getDerValue(); if (v.tag == DerValue.tag_Integer) { nonce = v.getBigInteger(); - System.err.println("nonce: " + nonce); + System.out.println("nonce: " + nonce); } else if (v.tag == DerValue.tag_Boolean) { - System.err.println("certReq: " + v.getBoolean()); + System.out.println("certReq: " + v.getBoolean()); } else if (v.tag == DerValue.tag_ObjectId) { policyId = v.getOID(); - System.err.println("PolicyID: " + policyId); + System.out.println("PolicyID: " + policyId); } } - System.err.println("\nResponse\n==================="); + System.out.println("\nResponse\n==================="); FileInputStream is = new FileInputStream(keystore); KeyStore ks = KeyStore.getInstance("JCEKS"); ks.load(is, "changeit".toCharArray()); is.close(); - String alias = "ts"; - if (path.startsWith("bad") || path.equals("weak")) { - alias = "ts" + path; - } + // If path starts with "ts", use the TSA it points to. + // Otherwise, always use "ts". + String alias = path.startsWith("ts") ? path : "ts"; if (path.equals("diffpolicy")) { policyId = new ObjectIdentifier(defaultPolicyId); @@ -192,8 +198,11 @@ public class TimestampCheck { tst.putInteger(1); - Calendar cal = Calendar.getInstance(); - tst.putGeneralizedTime(cal.getTime()); + Instant instant = Instant.now(); + if (path.equals("tsold")) { + instant = instant.minus(20, ChronoUnit.DAYS); + } + tst.putGeneralizedTime(Date.from(instant)); if (path.equals("diffnonce")) { tst.putInteger(1234); @@ -220,10 +229,10 @@ public class TimestampCheck { "1.2.840.113549.1.9.16.1.4"), new DerValue(tstInfo2.toByteArray())); - System.err.println("Signing..."); - System.err.println(new X500Name(signer + System.out.println("Signing..."); + System.out.println(new X500Name(signer .getIssuerX500Principal().getName())); - System.err.println(signer.getSerialNumber()); + System.out.println(signer.getSerialNumber()); SignerInfo signerInfo = new SignerInfo( new X500Name(signer.getIssuerX500Principal().getName()), @@ -296,25 +305,53 @@ public class TimestampCheck { prepare(); - try (Handler tsa = Handler.init(0, "tsks");) { + try (Handler tsa = Handler.init(0, "ks");) { tsa.start(); int port = tsa.getPort(); host = "http://localhost:" + port + "/"; if (args.length == 0) { // Run this test - sign("none") - .shouldContain("is not timestamped") + + sign("normal") + .shouldNotContain("Warning") .shouldHaveExitValue(0); - sign("badku") + verify("normal.jar") + .shouldNotContain("Warning") .shouldHaveExitValue(0); - checkBadKU("badku.jar"); - sign("normal") - .shouldNotContain("is not timestamped") + // Simulate signing at a previous date: + // 1. tsold will create a timestamp of 20 days ago. + // 2. oldsigner expired 10 days ago. + // jarsigner will show a warning at signing. + signVerbose("tsold", "unsigned.jar", "tsold.jar", "oldsigner") + .shouldHaveExitValue(4); + + // It verifies perfectly. + verify("tsold.jar", "-verbose", "-certs") + .shouldNotContain("Warning") .shouldHaveExitValue(0); + signVerbose(null, "unsigned.jar", "none.jar", "signer") + .shouldContain("is not timestamped") + .shouldHaveExitValue(0); + + signVerbose(null, "unsigned.jar", "badku.jar", "badku") + .shouldHaveExitValue(8); + checkBadKU("badku.jar"); + + // 8180289: unvalidated TSA cert chain + sign("tsnoca") + .shouldContain("TSA certificate chain is invalid") + .shouldHaveExitValue(64); + + verify("tsnoca.jar", "-verbose", "-certs") + .shouldHaveExitValue(64) + .shouldContain("jar verified") + .shouldContain("Invalid TSA certificate chain") + .shouldContain("TSA certificate chain is invalid"); + sign("nononce") .shouldHaveExitValue(1); sign("diffnonce") @@ -325,11 +362,11 @@ public class TimestampCheck { .shouldHaveExitValue(1); sign("fullchain") .shouldHaveExitValue(0); // Success, 6543440 solved. - sign("bad1") + sign("tsbad1") .shouldHaveExitValue(1); - sign("bad2") + sign("tsbad2") .shouldHaveExitValue(1); - sign("bad3") + sign("tsbad3") .shouldHaveExitValue(1); sign("nocert") .shouldHaveExitValue(1); @@ -341,119 +378,171 @@ public class TimestampCheck { sign("diffpolicy", "-tsapolicyid", "1.2.3") .shouldHaveExitValue(1); - sign("tsaalg", "-tsadigestalg", "SHA") + sign("sha1alg", "-tsadigestalg", "SHA") .shouldHaveExitValue(0); - checkTimestamp("tsaalg.jar", defaultPolicyId, "SHA-1"); + checkTimestamp("sha1alg.jar", defaultPolicyId, "SHA-1"); - sign("weak", "-digestalg", "MD5", + sign("tsweak", "-digestalg", "MD5", "-sigalg", "MD5withRSA", "-tsadigestalg", "MD5") - .shouldHaveExitValue(0); - checkWeak("weak.jar"); - - signWithAliasAndTsa("halfWeak", "old.jar", "old", "-digestalg", "MD5") - .shouldHaveExitValue(0); + .shouldHaveExitValue(68); + checkWeak("tsweak.jar"); + + signVerbose("tsweak", "unsigned.jar", "tsweak2.jar", "signer") + .shouldHaveExitValue(64) + .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"); + + signVerbose("normal", "unsigned.jar", "halfWeak.jar", "signer", + "-digestalg", "MD5") + .shouldHaveExitValue(4); checkHalfWeak("halfWeak.jar"); // sign with DSA key - signWithAliasAndTsa("sign1", "old.jar", "dsakey") + signVerbose("normal", "unsigned.jar", "sign1.jar", "dsakey") .shouldHaveExitValue(0); // sign with RSAkeysize < 1024 - signWithAliasAndTsa("sign2", "sign1.jar", "weakkeysize") - .shouldHaveExitValue(0); + signVerbose("normal", "sign1.jar", "sign2.jar", "weakkeysize") + .shouldHaveExitValue(4); checkMultiple("sign2.jar"); // When .SF or .RSA is missing or invalid checkMissingOrInvalidFiles("normal.jar"); + + if (Files.exists(Paths.get("ts2.cert"))) { + checkInvalidTsaCertKeyUsage(); + } } else { // Run as a standalone server - System.err.println("Press Enter to quit server"); + System.out.println("Press Enter to quit server"); System.in.read(); } } } + 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++; + } + } + private static void checkMissingOrInvalidFiles(String s) throws Throwable { - JarUtils.updateJar(s, "1.jar", "-", "META-INF/OLD.SF"); + + JarUtils.updateJar(s, "1.jar", mapOf("META-INF/SIGNER.SF", Boolean.FALSE)); verify("1.jar", "-verbose") - .shouldHaveExitValue(0) + .shouldHaveExitValue(16) .shouldContain("treated as unsigned") - .shouldContain("Missing signature-related file META-INF/OLD.SF"); - JarUtils.updateJar(s, "2.jar", "-", "META-INF/OLD.RSA"); + .shouldContain("Missing signature-related file META-INF/SIGNER.SF"); + JarUtils.updateJar(s, "2.jar", mapOf("META-INF/SIGNER.RSA", Boolean.FALSE)); verify("2.jar", "-verbose") - .shouldHaveExitValue(0) + .shouldHaveExitValue(16) .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"); + .shouldContain("Missing block file for signature-related file META-INF/SIGNER.SF"); + JarUtils.updateJar(s, "3.jar", mapOf("META-INF/SIGNER.SF", "dummy")); verify("3.jar", "-verbose") - .shouldHaveExitValue(0) + .shouldHaveExitValue(16) .shouldContain("treated as unsigned") - .shouldContain("Unparsable signature-related file META-INF/OLD.SF"); - JarUtils.updateJar(s, "4.jar", "META-INF/OLD.RSA"); + .shouldContain("Unparsable signature-related file META-INF/SIGNER.SF"); + JarUtils.updateJar(s, "4.jar", mapOf("META-INF/SIGNER.RSA", "dummy")); verify("4.jar", "-verbose") - .shouldHaveExitValue(0) + .shouldHaveExitValue(16) .shouldContain("treated as unsigned") - .shouldContain("Unparsable signature-related file META-INF/OLD.RSA"); + .shouldContain("Unparsable signature-related file META-INF/SIGNER.RSA"); } static OutputAnalyzer jarsigner(List 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()); + throws Exception { + List args = new ArrayList<>( + listOf("-keystore", "ks", "-storepass", "changeit")); + args.addAll(extra); + return SecurityTools.jarsigner(args); } static OutputAnalyzer verify(String file, String... extra) - throws Throwable { + throws Exception { List args = new ArrayList<>(); args.add("-verify"); + args.add("-strict"); args.add(file); args.addAll(Arrays.asList(extra)); return jarsigner(args); } - static void checkBadKU(String file) throws Throwable { + static void checkBadKU(String file) throws Exception { System.err.println("BadKU: " + file); verify(file) - .shouldHaveExitValue(0) + .shouldHaveExitValue(16) .shouldContain("treated as unsigned") .shouldContain("re-run jarsigner with debug enabled"); verify(file, "-verbose") - .shouldHaveExitValue(0) + .shouldHaveExitValue(16) .shouldContain("Signed by") .shouldContain("treated as unsigned") .shouldContain("re-run jarsigner with debug enabled"); verify(file, "-J-Djava.security.debug=jar") - .shouldHaveExitValue(0) + .shouldHaveExitValue(16) .shouldContain("SignatureException: Key usage restricted") .shouldContain("treated as unsigned") .shouldContain("re-run jarsigner with debug enabled"); } - static void checkWeak(String file) throws Throwable { + static void checkWeak(String file) throws Exception { verify(file) - .shouldHaveExitValue(0) + .shouldHaveExitValue(16) .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) + .shouldHaveExitValue(16) .shouldContain("treated as unsigned") .shouldMatch("weak algorithm that is now disabled by") .shouldMatch("Digest algorithm: .*weak") @@ -462,18 +551,18 @@ public class TimestampCheck { .shouldNotMatch("Timestamp signature algorithm: .*weak.*weak") .shouldMatch("Timestamp signature algorithm: .*key.*weak"); verify(file, "-J-Djava.security.debug=jar") - .shouldHaveExitValue(0) + .shouldHaveExitValue(16) .shouldMatch("SignatureException:.*disabled"); } - static void checkHalfWeak(String file) throws Throwable { + static void checkHalfWeak(String file) throws Exception { verify(file) - .shouldHaveExitValue(0) + .shouldHaveExitValue(16) .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) + .shouldHaveExitValue(16) .shouldContain("treated as unsigned") .shouldMatch("weak algorithm that is now disabled by") .shouldMatch("Digest algorithm: .*weak") @@ -483,7 +572,7 @@ public class TimestampCheck { .shouldNotMatch("Timestamp signature algorithm: .*key.*weak"); } - static void checkMultiple(String file) throws Throwable { + static void checkMultiple(String file) throws Exception { verify(file) .shouldHaveExitValue(0) .shouldContain("jar verified"); @@ -500,7 +589,7 @@ public class TimestampCheck { 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"); + JarEntry je = jf.getJarEntry("META-INF/SIGNER.RSA"); try (InputStream is = jf.getInputStream(je)) { byte[] content = IOUtils.readFully(is, -1, true); PKCS7 p7 = new PKCS7(content); @@ -526,26 +615,38 @@ public class TimestampCheck { static int which = 0; /** + * Sign with a TSA path. Always use alias "signer" to sign "unsigned.jar". + * The signed jar name is always path.jar. + * * @param extra more args given to jarsigner */ static OutputAnalyzer sign(String path, String... extra) - throws Throwable { - String alias = path.equals("badku") ? "badku" : "old"; - return signWithAliasAndTsa(path, "old.jar", alias, extra); + throws Exception { + return signVerbose( + path, + "unsigned.jar", + path + ".jar", + "signer", + extra); } - static OutputAnalyzer signWithAliasAndTsa (String path, String jar, - String alias, String...extra) throws Throwable { + static OutputAnalyzer signVerbose( + String path, // TSA URL path + String oldJar, + String newJar, + String alias, // signer + String...extra) throws Exception { which++; - System.err.println("\n>> Test #" + which + ": " + Arrays.toString(extra)); + System.out.println("\n>> Test #" + which); List args = new ArrayList<>(); - args.add("-J-Djava.security.egd=file:/dev/./urandom"); + args.add("-strict"); + args.add("-verbose"); args.add("-debug"); args.add("-signedjar"); - args.add(path + ".jar"); - args.add(jar); + args.add(newJar); + args.add(oldJar); args.add(alias); - if (!path.equals("none") && !path.equals("badku")) { + if (path != null) { args.add("-tsa"); args.add(host + path); } @@ -554,24 +655,50 @@ public class TimestampCheck { } 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"); + 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"); keytool("-alias dsakey -genkeypair -keyalg DSA -dname CN=dsakey"); keytool("-alias weakkeysize -genkeypair -keysize 512 -dname CN=weakkeysize"); 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 tsold -genkeypair -dname CN=tsold"); + keytool("-alias tsweak -genkeypair -keysize 512 -dname CN=tsweak"); keytool("-alias tsbad1 -genkeypair -dname CN=tsbad1"); keytool("-alias tsbad2 -genkeypair -dname CN=tsbad2"); keytool("-alias tsbad3 -genkeypair -dname CN=tsbad3"); + keytool("-alias tsnoca -genkeypair -dname CN=tsnoca"); + + // 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"); - gencert("old"); + gencert("signer"); + gencert("oldsigner", "-startdate -30d -validity 20"); gencert("dsakey"); gencert("weakkeysize"); gencert("badku", "-ext ku:critical=keyAgreement"); gencert("ts", "-ext eku:critical=ts"); + + + // Issue another cert for "ts" with a different EKU. + // Length should be the same. Try several times. + keytool("-gencert -alias ca -infile ts.req -outfile ts2.cert " + + "-ext eku:critical=1.3.6.1.5.5.7.3.9"); + for (int i = 0; i < 5; i++) { + 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; + } + } + + gencert("tsold", "-ext eku:critical=ts -startdate -40d -validity 45"); + gencert("tsweak", "-ext eku:critical=ts"); gencert("tsbad1"); gencert("tsbad2", "-ext eku=ts"); @@ -590,8 +717,16 @@ public class TimestampCheck { } static void keytool(String cmd) throws Exception { - cmd = "-keystore tsks -storepass changeit -keypass changeit " + + cmd = "-keystore ks -storepass changeit -keypass changeit " + "-keyalg rsa -validity 200 " + cmd; sun.security.tools.keytool.Main.main(cmd.split(" ")); } + + static Map mapOf(K k1, V v1) { + return Collections.singletonMap(k1, v1); + } + + static List listOf(E... elements) { + return Arrays.asList(elements); + } } diff --git a/test/sun/security/tools/jarsigner/Warning.java b/test/sun/security/tools/jarsigner/Warning.java index e5c58edcb846d759e08a5f45e07fee20cf319aeb..095d2053438b502b5d4f5bc3d9051faf153ebb31 100644 --- a/test/sun/security/tools/jarsigner/Warning.java +++ b/test/sun/security/tools/jarsigner/Warning.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -81,14 +81,14 @@ public class Warning { issueCert("b", "-sigalg MD5withRSA"); run("jarsigner", "a.jar b") - .shouldMatch("chain is not validated. Reason:.*MD5withRSA"); + .shouldMatch("chain is invalid. Reason:.*MD5withRSA"); recreateJar(); newCert("c", "-keysize 512"); issueCert("c"); run("jarsigner", "a.jar c") - .shouldContain("chain is not validated. " + + .shouldContain("chain is invalid. " + "Reason: Algorithm constraints check failed"); recreateJar(); diff --git a/test/sun/security/tools/jarsigner/checkusage.sh b/test/sun/security/tools/jarsigner/checkusage.sh index ff2d1c53f38a0d04d2ca9c4639c15e7285fd2de0..5debcc0bc7fcfbe5a3c1a61cc7634e47b57d47ff 100644 --- a/test/sun/security/tools/jarsigner/checkusage.sh +++ b/test/sun/security/tools/jarsigner/checkusage.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -91,7 +91,7 @@ echo $RESULT #[ $RESULT = 0 ] || exit 2 # Test 3: When no keystore is specified, the error is only -# "chain not validated" +# "chain invalid" $JARSIGNER -strict -verify a.jar RESULT=$? @@ -99,7 +99,7 @@ echo $RESULT #[ $RESULT = 4 ] || exit 3 # Test 4: When unrelated keystore is specified, the error is -# "chain not validated" and "not alias in keystore" +# "chain invalid" and "not alias in keystore" $JARSIGNER -keystore unrelated.jks -strict -verify a.jar RESULT=$? diff --git a/test/sun/security/tools/jarsigner/warnings/Test.java b/test/sun/security/tools/jarsigner/warnings/Test.java index 999789a5e7404754fb185365b3349956007f72d0..5688d6126cfe4a367bf4657f06b0a06ef96ab64f 100644 --- a/test/sun/security/tools/jarsigner/warnings/Test.java +++ b/test/sun/security/tools/jarsigner/warnings/Test.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -63,7 +63,7 @@ public abstract class Test { static final String CHAIN_NOT_VALIDATED_VERIFYING_WARNING = "This jar contains entries " - + "whose certificate chain is not validated."; + + "whose certificate chain is invalid."; static final String ALIAS_NOT_IN_STORE_VERIFYING_WARNING = "This jar contains signed entries " @@ -95,7 +95,7 @@ public abstract class Test { + "doesn't allow code signing."; static final String CHAIN_NOT_VALIDATED_SIGNING_WARNING - = "The signer's certificate chain is not validated."; + = "The signer's certificate chain is invalid."; static final String HAS_EXPIRING_CERT_SIGNING_WARNING = "The signer certificate will expire within six months."; diff --git a/test/sun/security/tools/jarsigner/weaksize.sh b/test/sun/security/tools/jarsigner/weaksize.sh index c5a06b5661cb44b32cc659522989a7ae69cb4a37..28aad3dce69deae4d975099f726416506c59d720 100644 --- a/test/sun/security/tools/jarsigner/weaksize.sh +++ b/test/sun/security/tools/jarsigner/weaksize.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -52,9 +52,9 @@ $KT -certreq -alias signer | \ $JAR cvf a.jar ks # We always trust a TrustedCertificateEntry -$JS a.jar ca | grep "chain is not validated" && exit 1 +$JS a.jar ca | grep "chain is invalid" && exit 1 # An end-entity cert must follow algorithm constraints -$JS a.jar signer | grep "chain is not validated" || exit 2 +$JS a.jar signer | grep "chain is invalid" || exit 2 exit 0