Mercurial > hg > release > icedtea7-forest-2.6 > jdk
changeset 9660:52425d2592c8
8180289: jarsigner treats timestamped signed jar invalid after the signer cert expires
Summary: Includes backported test changes from 8189969
Reviewed-by: mullan
line wrap: on
line diff
--- a/src/share/classes/sun/security/tools/jarsigner/Main.java Sat Oct 20 19:46:54 2018 +0100 +++ b/src/share/classes/sun/security/tools/jarsigner/Main.java Mon Oct 22 03:05:39 2018 +0100 @@ -175,11 +175,20 @@ 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 @@ private boolean signerSelfSigned = false; private Throwable chainNotValidatedReason = null; + private Throwable tsaChainNotValidatedReason = null; private boolean seeWeak = false; @@ -279,7 +289,8 @@ 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 @@ if (notSignedByAlias || aliasNotInStore) { exitCode |= 32; } + if (tsaChainNotValidated) { + exitCode |= 64; + } if (exitCode != 0) { System.exit(exitCode); } @@ -816,6 +830,9 @@ 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() @@ -845,6 +862,7 @@ 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(); @@ -929,7 +947,7 @@ 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.")); @@ -979,10 +997,16 @@ 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.")); @@ -1010,8 +1034,15 @@ "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) { @@ -1062,16 +1093,23 @@ private static MessageFormat expiredTimeForm = null; private static MessageFormat expiringTimeForm = null; - /* - * Display some details about a certificate: + /** + * Returns a string about a certificate: * * [<tab>] <cert-type> [", " <subject-DN>] [" (" <keystore-entry-alias> ")"] * [<validity-period> | <expiry-warning>] + * [<key-usage-warning>] * - * 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"); @@ -1091,7 +1129,7 @@ certStr.append(space).append(alias); } - if (checkValidityPeriod && x509Cert != null) { + if (x509Cert != null) { certStr.append("\n").append(tab).append("["); Date notAfter = x509Cert.getNotAfter(); @@ -1104,7 +1142,7 @@ 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")); @@ -1125,7 +1163,7 @@ certStr.append(validityTimeForm.format(source)); } } catch (CertificateExpiredException cee) { - hasExpiredCert = true; + if (!isTsCert) hasExpiredCert = true; if (expiredTimeForm == null) { expiredTimeForm = new MessageFormat( @@ -1135,7 +1173,7 @@ certStr.append(expiredTimeForm.format(source)); } catch (CertificateNotYetValidException cnyve) { - notYetValidCert = true; + if (!isTsCert) notYetValidCert = true; if (notYetTimeForm == null) { notYetTimeForm = new MessageFormat( @@ -1544,7 +1582,7 @@ tsaURI); } System.out.println(rb.getString("TSA.certificate.") + - printCert("", tsaCert, false, null, false)); + printCert(true, "", tsaCert, null, false)); } if (signingMechanism != null) { System.out.println( @@ -1607,6 +1645,30 @@ } } + // 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 { @@ -1636,8 +1698,10 @@ } 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(); @@ -1674,10 +1738,16 @@ 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.")); @@ -1773,18 +1843,18 @@ /** * 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<? extends Certificate> 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; @@ -1793,24 +1863,41 @@ // 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; } @@ -2053,7 +2140,7 @@ } } - void getAliasInfo(String alias) { + void getAliasInfo(String alias) throws Exception { Key key = null; @@ -2099,10 +2186,11 @@ // 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; @@ -2163,17 +2251,31 @@ System.exit(1); } - void validateCertChain(List<? extends Certificate> certs) throws Exception { + /** + * Validates a cert chain. + * + * @param parameter this might be a timestamp + */ + void validateCertChain(String variant, List<? extends Certificate> 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) {
--- a/src/share/classes/sun/security/tools/jarsigner/Resources.java Sat Oct 20 19:46:54 2018 +0100 +++ b/src/share/classes/sun/security/tools/jarsigner/Resources.java Mon Oct 22 03:05:39 2018 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2012, 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 @@ {"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 @@ {"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 @@ "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: "},
--- a/src/share/classes/sun/security/util/SignatureFileVerifier.java Sat Oct 20 19:46:54 2018 +0100 +++ b/src/share/classes/sun/security/util/SignatureFileVerifier.java Mon Oct 22 03:05:39 2018 +0100 @@ -725,7 +725,8 @@ 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) {
--- a/test/lib/testlibrary/jdk/testlibrary/JarUtils.java Sat Oct 20 19:46:54 2018 +0100 +++ b/test/lib/testlibrary/jdk/testlibrary/JarUtils.java Mon Oct 22 03:05:39 2018 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -23,15 +23,17 @@ package jdk.testlibrary; -import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; -import java.util.ArrayList; +import java.nio.file.Paths; import java.util.Enumeration; -import java.util.List; +import java.util.HashMap; +import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; @@ -42,31 +44,24 @@ */ public final class JarUtils { - /** * Create jar file with specified files. If a specified file does not exist, * a new jar entry will be created with the file name itself as the content. */ - public static void createJar(String dest, Path filesLocation, - String... fileNames) throws IOException { + public static void createJar(String dest, String... files) + throws IOException { try (JarOutputStream jos = new JarOutputStream( new FileOutputStream(dest), new Manifest())) { - for (String fileName : fileNames) { + for (String file : files) { System.out.println(String.format("Adding %s to %s", - fileName, dest)); + file, dest)); // add an archive entry, and write a file - jos.putNextEntry(new JarEntry(fileName)); - File file; - if (filesLocation != null) { - file = filesLocation.resolve(fileName).toFile(); - } else { - file = new File(fileName); - } + jos.putNextEntry(new JarEntry(file)); try (FileInputStream fis = new FileInputStream(file)) { - Utils.transferBetweenStreams(fis, jos); + Utils.transferTo(fis, jos); } catch (FileNotFoundException e) { - jos.write(fileName.getBytes()); + jos.write(file.getBytes()); } } } @@ -74,14 +69,6 @@ } /** - * Create jar file with specified files from current directory. - */ - public static void createJar(String dest, String... files) - throws IOException { - createJar(dest, null, files); - } - - /** * Add or remove specified files to existing jar file. If a specified file * to be updated or added does not exist, the jar entry will be created * with the file name itself as the content. @@ -96,70 +83,93 @@ */ public static void updateJar(String src, String dest, String... files) throws IOException { + Map<String,Object> changes = new HashMap<>(); + boolean update = true; + for (String file : files) { + if (file.equals("-")) { + update = false; + } else if (update) { + try { + Path p = Paths.get(file); + if (Files.exists(p)) { + changes.put(file, p); + } else { + changes.put(file, file); + } + } catch (InvalidPathException e) { + // Fallback if file not a valid Path. + changes.put(file, file); + } + } else { + changes.put(file, Boolean.FALSE); + } + } + updateJar(src, dest, changes); + } + + /** + * Update content of a jar file. + * + * @param src the original jar file name + * @param dest the new jar file name + * @param changes a map of changes, key is jar entry name, value is content. + * Value can be Path, byte[] or String. If key exists in + * src but value is Boolean FALSE. The entry is removed. + * Existing entries in src not a key is unmodified. + * @throws IOException + */ + public static void updateJar(String src, String dest, + Map<String,Object> changes) + throws IOException { + + // What if input changes is immutable? + changes = new HashMap<>(changes); + + System.out.printf("Creating %s from %s...\n", dest, src); try (JarOutputStream jos = new JarOutputStream( new FileOutputStream(dest))) { - // copy each old entry into destination unless the entry name - // is in the updated list - List<String> updatedFiles = new ArrayList<>(); try (JarFile srcJarFile = new JarFile(src)) { Enumeration<JarEntry> entries = srcJarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); String name = entry.getName(); - boolean found = false; - boolean update = true; - for (String file : files) { - if (file.equals("-")) { - update = false; - } else if (name.equals(file)) { - updatedFiles.add(file); - found = true; - break; - } - } - - if (found) { - if (update) { - System.out.println(String.format("Updating %s with %s", - dest, name)); - jos.putNextEntry(new JarEntry(name)); - try (FileInputStream fis = new FileInputStream(name)) { - Utils.transferBetweenStreams(fis, jos); - } catch (FileNotFoundException e) { - jos.write(name.getBytes()); - } - } else { - System.out.println(String.format("Removing %s from %s", - name, dest)); - } + if (changes.containsKey(name)) { + System.out.println(String.format("- Update %s", name)); + updateEntry(jos, name, changes.get(name)); + changes.remove(name); } else { - System.out.println(String.format("Copying %s to %s", - name, dest)); + System.out.println(String.format("- Copy %s", name)); jos.putNextEntry(entry); - Utils.transferBetweenStreams(srcJarFile. - getInputStream(entry), jos); + Utils.transferTo(srcJarFile.getInputStream(entry), jos); } } } - - // append new files - for (String file : files) { - if (file.equals("-")) { - break; - } - if (!updatedFiles.contains(file)) { - System.out.println(String.format("Adding %s with %s", - dest, file)); - jos.putNextEntry(new JarEntry(file)); - try (FileInputStream fis = new FileInputStream(file)) { - Utils.transferBetweenStreams(fis, jos); - } catch (FileNotFoundException e) { - jos.write(file.getBytes()); - } - } + for (Map.Entry<String, Object> e : changes.entrySet()) { + System.out.println(String.format("- Add %s", e.getKey())); + updateEntry(jos, e.getKey(), e.getValue()); } } System.out.println(); } -} \ No newline at end of file + + private static void updateEntry(JarOutputStream jos, String name, Object content) + throws IOException { + if (content instanceof Boolean) { + if (((Boolean) content).booleanValue()) { + throw new RuntimeException("Boolean value must be FALSE"); + } + } else { + jos.putNextEntry(new JarEntry(name)); + if (content instanceof Path) { + Utils.transferTo(Files.newInputStream((Path) content), jos); + } else if (content instanceof byte[]) { + jos.write((byte[]) content); + } else if (content instanceof String) { + jos.write(((String) content).getBytes()); + } else { + throw new RuntimeException("Unknown type " + content.getClass()); + } + } + } +}
--- a/test/lib/testlibrary/jdk/testlibrary/OutputAnalyzer.java Sat Oct 20 19:46:54 2018 +0100 +++ b/test/lib/testlibrary/jdk/testlibrary/OutputAnalyzer.java Mon Oct 22 03:05:39 2018 +0100 @@ -362,6 +362,21 @@ } /** + * Verify the exit value of the process + * + * @param notExpectedExitValue Unexpected exit value from process + * @throws RuntimeException If the exit value from the process did match the expected value + */ + public OutputAnalyzer shouldNotHaveExitValue(int notExpectedExitValue) { + if (getExitValue() == notExpectedExitValue) { + reportDiagnosticSummary(); + throw new RuntimeException("Unexpected to get exit value of [" + + notExpectedExitValue + "]\n"); + } + return this; + } + + /** * Report summary that will help to diagnose the problem Currently includes: * - standard input produced by the process under test - standard output - * exit code Note: the command line is printed by the ProcessTools
--- a/test/lib/testlibrary/jdk/testlibrary/SecurityTools.java Sat Oct 20 19:46:54 2018 +0100 +++ b/test/lib/testlibrary/jdk/testlibrary/SecurityTools.java Mon Oct 22 03:05:39 2018 +0100 @@ -47,10 +47,7 @@ launcher.addToolArg(arg); } } - String[] cmds = launcher.getCommand(); - String cmdLine = joiner(" ", (Object[]) cmds); - System.out.println("Command line: [" + cmdLine + "]"); - return new ProcessBuilder(cmds); + return new ProcessBuilder(launcher.getCommand()); } // keytool @@ -67,7 +64,7 @@ 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 { @@ -98,11 +95,20 @@ public static OutputAnalyzer jarsigner(List<String> 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); + } } }
--- a/test/lib/testlibrary/jdk/testlibrary/Utils.java Sat Oct 20 19:46:54 2018 +0100 +++ b/test/lib/testlibrary/jdk/testlibrary/Utils.java Mon Oct 22 03:05:39 2018 +0100 @@ -35,6 +35,7 @@ import java.util.List; import java.util.Arrays; import java.util.Collections; +import java.util.Objects; import java.util.regex.Pattern; import java.util.regex.Matcher; @@ -59,6 +60,9 @@ public static final String JAVA_OPTIONS = System.getProperty("test.java.opts", "").trim(); + private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; + private static final int DEFAULT_BUFFER_SIZE = 8192; + private Utils() { // Private constructor to prevent class instantiation } @@ -232,6 +236,40 @@ } } + /** + * Helper method to read all bytes from InputStream + * + * @param is InputStream to read from + * @return array of bytes + * @throws IOException + */ + public static byte[] readAllBytes(InputStream is) throws IOException { + byte[] buf = new byte[DEFAULT_BUFFER_SIZE]; + int capacity = buf.length; + int nread = 0; + int n; + for (;;) { + // read to EOF which may read more or less than initial buffer size + while ((n = is.read(buf, nread, capacity - nread)) > 0) + nread += n; + + // if the last call to read returned -1, then we're done + if (n < 0) + break; + + // need to allocate a larger buffer + if (capacity <= MAX_BUFFER_SIZE - capacity) { + capacity = capacity << 1; + } else { + if (capacity == MAX_BUFFER_SIZE) + throw new OutOfMemoryError("Required array size too large"); + capacity = MAX_BUFFER_SIZE; + } + buf = Arrays.copyOf(buf, capacity); + } + return (capacity == nread) ? buf : Arrays.copyOf(buf, nread); + } + private static final int BUFFER_SIZE = 1024; /** @@ -259,7 +297,7 @@ * @throws NullPointerException if {@code in} or {@code out} is {@code null} * */ - public static long transferBetweenStreams(InputStream in, OutputStream out) + public static long transferTo(InputStream in, OutputStream out) throws IOException { long transferred = 0; byte[] buffer = new byte[BUFFER_SIZE];
--- a/test/sun/security/tools/jarsigner/TimestampCheck.java Sat Oct 20 19:46:54 2018 +0100 +++ b/test/sun/security/tools/jarsigner/TimestampCheck.java Mon Oct 22 03:05:39 2018 +0100 @@ -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,21 @@ 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.Collections; import java.util.List; +import java.util.Map; 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 +65,12 @@ 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 +78,7 @@ * 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 +124,12 @@ */ 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 +137,24 @@ 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); @@ -196,6 +205,9 @@ tst.putInteger(1); Calendar cal = Calendar.getInstance(); + if (path.equals("tsold")) { + cal.add(Calendar.DAY_OF_MONTH, -20); + } tst.putGeneralizedTime(cal.getTime()); if (path.equals("diffnonce")) { @@ -223,10 +235,10 @@ "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()), @@ -299,24 +311,52 @@ 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") + + sign("normal") + .shouldNotContain("Warning") + .shouldHaveExitValue(0); + + verify("normal.jar") + .shouldNotContain("Warning") + .shouldHaveExitValue(0); + + // 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); - sign("badku") - .shouldHaveExitValue(0); + signVerbose(null, "unsigned.jar", "badku.jar", "badku") + .shouldHaveExitValue(8); checkBadKU("badku.jar"); - sign("normal") - .shouldNotContain("is not timestamped") - .shouldHaveExitValue(0); + // 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); @@ -328,11 +368,11 @@ .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); @@ -344,119 +384,171 @@ 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"); + .shouldHaveExitValue(68); + checkWeak("tsweak.jar"); + + signVerbose("tsweak", "unsigned.jar", "tsweak2.jar", "signer") + .shouldHaveExitValue(64) + .shouldContain("TSA certificate chain is invalid"); - signWithAliasAndTsa("halfWeak", "old.jar", "old", "-digestalg", "MD5") - .shouldHaveExitValue(0); + // 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<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()); + throws Exception { + List<String> 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<String> 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") @@ -465,18 +557,18 @@ .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") @@ -486,7 +578,7 @@ .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"); @@ -503,7 +595,7 @@ 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); @@ -529,26 +621,38 @@ 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<String> 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); } @@ -557,24 +661,50 @@ } 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"); - gencert("old"); + // 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("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"); @@ -593,8 +723,16 @@ } 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 <K> Map<K,Object> mapOf(K k1, Object v1) { + return Collections.singletonMap(k1, v1); + } + + static <E> List<E> listOf(E... elements) { + return Arrays.asList(elements); + } }
--- a/test/sun/security/tools/jarsigner/Warning.java Sat Oct 20 19:46:54 2018 +0100 +++ b/test/sun/security/tools/jarsigner/Warning.java Mon Oct 22 03:05:39 2018 +0100 @@ -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 @@ 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();
--- a/test/sun/security/tools/jarsigner/checkusage.sh Sat Oct 20 19:46:54 2018 +0100 +++ b/test/sun/security/tools/jarsigner/checkusage.sh Mon Oct 22 03:05:39 2018 +0100 @@ -1,5 +1,5 @@ # -# Copyright (c) 2010, 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 @@ #[ $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 @@ #[ $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=$?
--- a/test/sun/security/tools/jarsigner/warnings/Test.java Sat Oct 20 19:46:54 2018 +0100 +++ b/test/sun/security/tools/jarsigner/warnings/Test.java Mon Oct 22 03:05:39 2018 +0100 @@ -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 @@ 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 @@ + "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.";
--- a/test/sun/security/tools/jarsigner/weaksize.sh Sat Oct 20 19:46:54 2018 +0100 +++ b/test/sun/security/tools/jarsigner/weaksize.sh Mon Oct 22 03:05:39 2018 +0100 @@ -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 @@ $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