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
author igerasim
date Mon, 22 Oct 2018 03:05:39 +0100
parents e4925f09fe2e
children 6ec37856c7e5
files src/share/classes/sun/security/tools/jarsigner/Main.java src/share/classes/sun/security/tools/jarsigner/Resources.java src/share/classes/sun/security/util/SignatureFileVerifier.java test/lib/testlibrary/jdk/testlibrary/JarUtils.java test/lib/testlibrary/jdk/testlibrary/OutputAnalyzer.java test/lib/testlibrary/jdk/testlibrary/SecurityTools.java test/lib/testlibrary/jdk/testlibrary/Utils.java test/sun/security/tools/jarsigner/TimestampCheck.java test/sun/security/tools/jarsigner/Warning.java test/sun/security/tools/jarsigner/checkusage.sh test/sun/security/tools/jarsigner/warnings/Test.java test/sun/security/tools/jarsigner/weaksize.sh
diffstat 12 files changed, 561 insertions(+), 242 deletions(-) [+]
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