changeset 8788:d2ce6ebdf7fb

8171319: keytool should print out warnings when reading or generating cert/cert req using weak algorithms Reviewed-by: coffeys
author weijun
date Mon, 20 Nov 2017 17:44:05 +0000
parents c763d3f1b018
children 57e2285a0e8b
files src/share/classes/sun/security/pkcs10/PKCS10.java src/share/classes/sun/security/provider/certpath/BasicChecker.java src/share/classes/sun/security/tools/keytool/Main.java src/share/classes/sun/security/tools/keytool/Resources.java src/share/classes/sun/security/x509/X509CRLImpl.java test/sun/security/tools/keytool/WeakAlg.java
diffstat 6 files changed, 874 insertions(+), 85 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/sun/security/pkcs10/PKCS10.java	Mon Nov 20 17:39:01 2017 +0000
+++ b/src/share/classes/sun/security/pkcs10/PKCS10.java	Mon Nov 20 17:44:05 2017 +0000
@@ -167,7 +167,8 @@
         // key and signature algorithm we found.
         //
         try {
-            sig = Signature.getInstance(id.getName());
+            sigAlg = id.getName();
+            sig = Signature.getInstance(sigAlg);
             sig.initVerify(subjectPublicKeyInfo);
             sig.update(data);
             if (!sig.verify(sigData))
@@ -218,6 +219,7 @@
         signature.update(certificateRequestInfo, 0,
                 certificateRequestInfo.length);
         sig = signature.sign();
+        sigAlg = signature.getAlgorithm();
 
         /*
          * Build guts of SIGNED macro
@@ -251,6 +253,11 @@
         { return subjectPublicKeyInfo; }
 
     /**
+     * Returns the signature algorithm.
+     */
+    public String getSigAlg() { return sigAlg; }
+
+    /**
      * Returns the additional attributes requested.
      */
     public PKCS10Attributes getAttributes()
@@ -348,6 +355,7 @@
 
     private X500Name            subject;
     private PublicKey           subjectPublicKeyInfo;
+    private String              sigAlg;
     private PKCS10Attributes    attributeSet;
     private byte[]              encoded;        // signed
 }
--- a/src/share/classes/sun/security/provider/certpath/BasicChecker.java	Mon Nov 20 17:39:01 2017 +0000
+++ b/src/share/classes/sun/security/provider/certpath/BasicChecker.java	Mon Nov 20 17:44:05 2017 +0000
@@ -51,7 +51,7 @@
 
 /**
  * BasicChecker is a PKIXCertPathChecker that checks the basic information
- * on a PKIX certificate, namely the signature, timestamp, and subject/issuer
+ * on a PKIX certificate, namely the signature, validity, and subject/issuer
  * name chaining.
  *
  * @since       1.4
@@ -115,7 +115,7 @@
     }
 
     /**
-     * Performs the signature, timestamp, and subject/issuer name chaining
+     * Performs the signature, validity, and subject/issuer name chaining
      * checks on the certificate using its internal state. This method does
      * not remove any critical extensions from the Collection.
      *
@@ -130,7 +130,7 @@
         X509Certificate currCert = (X509Certificate)cert;
 
         if (!sigOnly) {
-            verifyTimestamp(currCert);
+            verifyValidity(currCert);
             verifyNameChaining(currCert);
         }
         verifySignature(currCert);
@@ -166,12 +166,12 @@
     }
 
     /**
-     * Internal method to verify the timestamp on a certificate
+     * Internal method to verify the validity on a certificate
      */
-    private void verifyTimestamp(X509Certificate cert)
+    private void verifyValidity(X509Certificate cert)
         throws CertPathValidatorException
     {
-        String msg = "timestamp";
+        String msg = "validity";
         if (debug != null)
             debug.println("---checking " + msg + ":" + date.toString() + "...");
 
--- a/src/share/classes/sun/security/tools/keytool/Main.java	Mon Nov 20 17:39:01 2017 +0000
+++ b/src/share/classes/sun/security/tools/keytool/Main.java	Mon Nov 20 17:44:05 2017 +0000
@@ -27,6 +27,7 @@
 
 import java.io.*;
 import java.security.CodeSigner;
+import java.security.CryptoPrimitive;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.MessageDigest;
@@ -66,6 +67,7 @@
 import javax.security.auth.x500.X500Principal;
 
 import sun.misc.BASE64Encoder;
+import sun.security.util.DisabledAlgorithmConstraints;
 import sun.security.util.KeyUtil;
 import sun.security.util.ObjectIdentifier;
 import sun.security.pkcs10.PKCS10;
@@ -147,6 +149,7 @@
     private boolean kssave = false;
     private boolean noprompt = false;
     private boolean trustcacerts = false;
+    private boolean nowarn = false;
     private boolean protectedPath = false;
     private boolean srcprotectedPath = false;
     private CertificateFactory cf = null;
@@ -159,6 +162,16 @@
     private List<String> ids = new ArrayList<>();   // used in GENCRL
     private List<String> v3ext = new ArrayList<>();
 
+    // Warnings on weak algorithms
+    private List<String> weakWarnings = new ArrayList<>();
+
+    private static final DisabledAlgorithmConstraints DISABLED_CHECK =
+            new DisabledAlgorithmConstraints(
+                    DisabledAlgorithmConstraints.PROPERTY_CERTPATH_DISABLED_ALGS);
+
+    private static final Set<CryptoPrimitive> SIG_PRIMITIVE_SET = Collections
+            .unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
+
     enum Command {
         CERTREQ("Generates.a.certificate.request",
             ALIAS, SIGALG, FILEOUT, KEYPASS, KEYSTORE, DNAME,
@@ -313,7 +326,7 @@
     private static final String NONE = "NONE";
     private static final String P11KEYSTORE = "PKCS11";
     private static final String P12KEYSTORE = "PKCS12";
-    private final String keyAlias = "mykey";
+    private static final String keyAlias = "mykey";
 
     // for i18n
     private static final java.util.ResourceBundle rb =
@@ -349,6 +362,7 @@
                 throw e;
             }
         } finally {
+            printWeakWarnings(false);
             for (char[] pass : passwords) {
                 if (pass != null) {
                     Arrays.fill(pass, ' ');
@@ -414,12 +428,10 @@
                 command = GENKEYPAIR;
             } else if (collator.compare(flags, "-import") == 0) {
                 command = IMPORTCERT;
-            }
-            /*
-             * Help
-             */
-            else if (collator.compare(flags, "-help") == 0) {
+            } else if (collator.compare(flags, "-help") == 0) {
                 help = true;
+            } else if (collator.compare(flags, "-nowarn") == 0) {
+                nowarn = true;
             }
 
             /*
@@ -1047,11 +1059,11 @@
         } else if (command == LIST) {
             if (storePass == null
                     && !KeyStoreUtil.isWindowsKeyStore(storetype)) {
-                printWarning();
+                printNoIntegrityWarning();
             }
 
             if (alias != null) {
-                doPrintEntry(alias, out);
+                doPrintEntry(rb.getString("the.certificate"), alias, out);
             } else {
                 doPrintEntries(out);
             }
@@ -1148,6 +1160,12 @@
             throws Exception {
 
 
+        if (keyStore.containsAlias(alias) == false) {
+            MessageFormat form = new MessageFormat
+                    (rb.getString("Alias.alias.does.not.exist"));
+            Object[] source = {alias};
+            throw new Exception(form.format(source));
+        }
         Certificate signerCert = keyStore.getCertificate(alias);
         byte[] encoded = signerCert.getEncoded();
         X509CertImpl signerCertImpl = new X509CertImpl(encoded);
@@ -1201,6 +1219,8 @@
         byte[] rawReq = new BASE64Decoder().decodeBuffer(new String(sb));
         PKCS10 req = new PKCS10(rawReq);
 
+        checkWeak(rb.getString("the.certificate.request"), req);
+
         info.set(X509CertInfo.KEY, new CertificateX509Key(req.getSubjectPublicKeyInfo()));
         info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(
                 dname==null?req.getSubjectName():new X500Name(dname)));
@@ -1230,6 +1250,9 @@
                 }
             }
         }
+
+        checkWeak(rb.getString("the.issuer"), keyStore.getCertificateChain(alias));
+        checkWeak(rb.getString("the.generated.certificate"), cert);
     }
 
     private void doGenCRL(PrintStream out)
@@ -1280,6 +1303,7 @@
         } else {
             out.write(crl.getEncodedInternal());
         }
+        checkWeak(rb.getString("the.generated.crl"), crl, privateKey);
     }
 
     /**
@@ -1326,6 +1350,8 @@
         // Sign the request and base-64 encode it
         request.encodeAndSign(subject, signature);
         request.print(out);
+
+        checkWeak(rb.getString("the.generated.certificate.request"), request);
     }
 
     /**
@@ -1349,7 +1375,7 @@
     {
         if (storePass == null
                 && !KeyStoreUtil.isWindowsKeyStore(storetype)) {
-            printWarning();
+            printNoIntegrityWarning();
         }
         if (alias == null) {
             alias = keyAlias;
@@ -1369,6 +1395,7 @@
             throw new Exception(form.format(source));
         }
         dumpCert(cert, out);
+        checkWeak(rb.getString("the.certificate"), cert);
     }
 
     /**
@@ -1555,6 +1582,8 @@
             keyPass = promptForKeyPass(alias, null, storePass);
         }
         keyStore.setKeyEntry(alias, privKey, keyPass, chain);
+
+        checkWeak(rb.getString("the.generated.certificate"), chain[0]);
     }
 
     /**
@@ -1636,7 +1665,7 @@
     /**
      * Prints a single keystore entry.
      */
-    private void doPrintEntry(String alias, PrintStream out)
+    private void doPrintEntry(String label, String alias, PrintStream out)
         throws Exception
     {
         if (keyStore.containsAlias(alias) == false) {
@@ -1707,12 +1736,14 @@
                         } else {
                             dumpCert(chain[i], out);
                         }
+                        checkWeak(label, chain[i]);
                     }
                 } else {
                     // Print the digest of the user cert only
                     out.println
                         (rb.getString("Certificate.fingerprint.SHA1.") +
                         getCertFingerPrint("SHA1", chain[0]));
+                    checkWeak(label, chain[0]);
                 }
             }
         } else if (keyStore.entryInstanceOf(alias,
@@ -1735,6 +1766,7 @@
                 out.println(rb.getString("Certificate.fingerprint.SHA1.")
                             + getCertFingerPrint("SHA1", cert));
             }
+            checkWeak(label, cert);
         } else {
             out.println(rb.getString("Unknown.Entry.Type"));
         }
@@ -1811,7 +1843,7 @@
 
         if (srcstorePass == null
                 && !KeyStoreUtil.isWindowsKeyStore(srcstoretype)) {
-            // anti refactoring, copied from printWarning(),
+            // anti refactoring, copied from printNoIntegrityWarning(),
             // but change 2 lines
             System.err.println();
             System.err.println(rb.getString
@@ -1900,6 +1932,10 @@
 
         try {
             keyStore.setEntry(newAlias, entry, pp);
+            Certificate c = srckeystore.getCertificate(alias);
+            if (c != null) {
+                checkWeak("<" + newAlias + ">", c);
+            }
             return 1;
         } catch (KeyStoreException kse) {
             Object[] source2 = {alias, kse.toString()};
@@ -1962,7 +1998,7 @@
         for (Enumeration<String> e = keyStore.aliases();
                                         e.hasMoreElements(); ) {
             String alias = e.nextElement();
-            doPrintEntry(alias, out);
+            doPrintEntry("<" + alias + ">", alias, out);
             if (verbose || rfc) {
                 out.println(rb.getString("NEWLINE"));
                 out.println(rb.getString
@@ -2112,19 +2148,28 @@
         for (CRL crl: loadCRLs(src)) {
             printCRL(crl, out);
             String issuer = null;
+            Certificate signer = null;
             if (caks != null) {
                 issuer = verifyCRL(caks, crl);
                 if (issuer != null) {
+                    signer = caks.getCertificate(issuer);
                     out.printf(rb.getString(
-                            "verified.by.s.in.s"), issuer, "cacerts");
+                            "verified.by.s.in.s.weak"),
+                            issuer,
+                            "cacerts",
+                            withWeak(signer.getPublicKey()));
                     out.println();
                 }
             }
             if (issuer == null && keyStore != null) {
                 issuer = verifyCRL(keyStore, crl);
                 if (issuer != null) {
+                    signer = keyStore.getCertificate(issuer);
                     out.printf(rb.getString(
-                            "verified.by.s.in.s"), issuer, "keystore");
+                            "verified.by.s.in.s.weak"),
+                            issuer,
+                            "keystore",
+                            withWeak(signer.getPublicKey()));
                     out.println();
                 }
             }
@@ -2136,18 +2181,26 @@
                 out.println(rb.getString
                         ("STARNN"));
             }
+            checkWeak(rb.getString("the.crl"), crl, signer == null ? null : signer.getPublicKey());
         }
     }
 
     private void printCRL(CRL crl, PrintStream out)
             throws Exception {
+        X509CRL xcrl = (X509CRL)crl;
         if (rfc) {
-            X509CRL xcrl = (X509CRL)crl;
             out.println("-----BEGIN X509 CRL-----");
             new BASE64Encoder().encodeBuffer(xcrl.getEncoded(), out);
             out.println("-----END X509 CRL-----");
         } else {
-            out.println(crl.toString());
+            String s;
+            if (crl instanceof X509CRLImpl) {
+                X509CRLImpl x509crl = (X509CRLImpl) crl;
+                s = x509crl.toStringWithAlgName(withWeak("" + x509crl.getSigAlgId()));
+            } else {
+                s = crl.toString();
+            }
+            out.println(s);
         }
     }
 
@@ -2174,8 +2227,11 @@
         PKCS10 req = new PKCS10(new BASE64Decoder().decodeBuffer(new String(sb)));
 
         PublicKey pkey = req.getSubjectPublicKeyInfo();
-        out.printf(rb.getString("PKCS.10.Certificate.Request.Version.1.0.Subject.s.Public.Key.s.format.s.key."),
-                req.getSubjectName(), pkey.getFormat(), pkey.getAlgorithm());
+        out.printf(rb.getString("PKCS.10.with.weak"),
+                req.getSubjectName(),
+                pkey.getFormat(),
+                withWeak(pkey),
+                withWeak(req.getSigAlg()));
         for (PKCS10Attribute attr: req.getAttributes().getAttributes()) {
             ObjectIdentifier oid = attr.getAttributeId();
             if (oid.equals((Object)PKCS9Attribute.EXTENSION_REQUEST_OID)) {
@@ -2191,6 +2247,7 @@
         if (debug) {
             out.println(req);   // Just to see more, say, public key length...
         }
+        checkWeak(rb.getString("the.certificate.request"), req);
     }
 
     /**
@@ -2228,6 +2285,15 @@
             if (i < (certs.length-1)) {
                 out.println();
             }
+            checkWeak(oneInMany(rb.getString("the.certificate"), i, certs.length), x509Cert);
+        }
+    }
+
+    private static String oneInMany(String label, int i, int num) {
+        if (num == 1) {
+            return label;
+        } else {
+            return String.format(rb.getString("one.in.many"), label, i+1, num);
         }
     }
 
@@ -2257,7 +2323,11 @@
                             out.println();
                             out.println(rb.getString("Signature."));
                             out.println();
-                            for (Certificate cert: signer.getSignerCertPath().getCertificates()) {
+
+                            List<? extends Certificate> certs
+                                    = signer.getSignerCertPath().getCertificates();
+                            int cc = 0;
+                            for (Certificate cert: certs) {
                                 X509Certificate x = (X509Certificate)cert;
                                 if (rfc) {
                                     out.println(rb.getString("Certificate.owner.") + x.getSubjectDN() + "\n");
@@ -2266,12 +2336,15 @@
                                     printX509Cert(x, out);
                                 }
                                 out.println();
+                                checkWeak(oneInMany(rb.getString("the.certificate"), cc++, certs.size()), x);
                             }
                             Timestamp ts = signer.getTimestamp();
                             if (ts != null) {
                                 out.println(rb.getString("Timestamp."));
                                 out.println();
-                                for (Certificate cert: ts.getSignerCertPath().getCertificates()) {
+                                certs = ts.getSignerCertPath().getCertificates();
+                                cc = 0;
+                                for (Certificate cert: certs) {
                                     X509Certificate x = (X509Certificate)cert;
                                     if (rfc) {
                                         out.println(rb.getString("Certificate.owner.") + x.getSubjectDN() + "\n");
@@ -2280,6 +2353,7 @@
                                         printX509Cert(x, out);
                                     }
                                     out.println();
+                                    checkWeak(oneInMany(rb.getString("the.tsa.certificate"), cc++, certs.size()), x);
                                 }
                             }
                         }
@@ -2324,6 +2398,7 @@
                         printX509Cert((X509Certificate)cert, out);
                         out.println();
                     }
+                    checkWeak(oneInMany(rb.getString("the.certificate"), i, chain.size()), cert);
                 } catch (Exception e) {
                     if (debug) {
                         e.printStackTrace();
@@ -2499,7 +2574,7 @@
         }
 
         // Now store the newly established chain in the keystore. The new
-        // chain replaces the old one.
+        // chain replaces the old one. The chain can be null if user chooses no.
         if (newChain != null) {
             keyStore.setKeyEntry(alias, privKey,
                                  (keyPass != null) ? keyPass : storePass,
@@ -2536,6 +2611,12 @@
             throw new Exception(rb.getString("Input.not.an.X.509.certificate"));
         }
 
+        if (noprompt) {
+            keyStore.setCertificateEntry(alias, cert);
+            checkWeak(rb.getString("the.input"), cert);
+            return true;
+        }
+
         // if certificate is self-signed, make sure it verifies
         boolean selfSigned = false;
         if (isSelfSigned(cert)) {
@@ -2543,11 +2624,6 @@
             selfSigned = true;
         }
 
-        if (noprompt) {
-            keyStore.setCertificateEntry(alias, cert);
-            return true;
-        }
-
         // check if cert already exists in keystore
         String reply = null;
         String trustalias = keyStore.getCertificateAlias(cert);
@@ -2556,6 +2632,8 @@
                 ("Certificate.already.exists.in.keystore.under.alias.trustalias."));
             Object[] source = {trustalias};
             System.err.println(form.format(source));
+            checkWeak(rb.getString("the.input"), cert);
+            printWeakWarnings(true);
             reply = getYesNoReply
                 (rb.getString("Do.you.still.want.to.add.it.no."));
         } else if (selfSigned) {
@@ -2565,6 +2643,8 @@
                         ("Certificate.already.exists.in.system.wide.CA.keystore.under.alias.trustalias."));
                 Object[] source = {trustalias};
                 System.err.println(form.format(source));
+                checkWeak(rb.getString("the.input"), cert);
+                printWeakWarnings(true);
                 reply = getYesNoReply
                         (rb.getString("Do.you.still.want.to.add.it.to.your.own.keystore.no."));
             }
@@ -2572,6 +2652,8 @@
                 // Print the cert and ask user if they really want to add
                 // it to their keystore
                 printX509Cert(cert, System.out);
+                checkWeak(rb.getString("the.input"), cert);
+                printWeakWarnings(true);
                 reply = getYesNoReply
                         (rb.getString("Trust.this.certificate.no."));
             }
@@ -2585,6 +2667,7 @@
             }
         }
 
+        // Not found in this keystore and not self-signed
         // Try to establish trust chain
         try {
             Certificate[] chain = establishCertChain(null, cert);
@@ -2596,6 +2679,8 @@
             // Print the cert and ask user if they really want to add it to
             // their keystore
             printX509Cert(cert, System.out);
+            checkWeak(rb.getString("the.input"), cert);
+            printWeakWarnings(true);
             reply = getYesNoReply
                 (rb.getString("Trust.this.certificate.no."));
             if ("YES".equals(reply)) {
@@ -2734,6 +2819,24 @@
         return keyPass;
     }
 
+    private String withWeak(String alg) {
+        if (DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, alg, null)) {
+            return alg;
+        } else {
+            return String.format(rb.getString("with.weak"), alg);
+        }
+    }
+
+    private String withWeak(PublicKey key) {
+        if (DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, key)) {
+            return String.format(rb.getString("key.bit"),
+                    KeyUtil.getKeySize(key), key.getAlgorithm());
+        } else {
+            return String.format(rb.getString("key.bit.weak"),
+                    KeyUtil.getKeySize(key), key.getAlgorithm());
+        }
+    }
+
     /**
      * Prints a certificate in a human readable format.
      */
@@ -2759,7 +2862,7 @@
         */
 
         MessageFormat form = new MessageFormat
-                (rb.getString(".PATTERN.printX509Cert"));
+                (rb.getString(".PATTERN.printX509Cert.with.weak"));
         PublicKey pkey = cert.getPublicKey();
         Object[] source = {cert.getSubjectDN().toString(),
                         cert.getIssuerDN().toString(),
@@ -2769,10 +2872,9 @@
                         getCertFingerPrint("MD5", cert),
                         getCertFingerPrint("SHA1", cert),
                         getCertFingerPrint("SHA-256", cert),
-                        cert.getSigAlgName(),
-                        pkey.getAlgorithm(),
-                        KeyUtil.getKeySize(pkey),
-                        cert.getVersion(),
+                        withWeak(cert.getSigAlgName()),
+                        withWeak(pkey),
+                        cert.getVersion()
                         };
         out.println(form.format(source));
 
@@ -2841,12 +2943,12 @@
      * @param ks the keystore to search with, not null
      * @return <code>cert</code> itself if it's already inside <code>ks</code>,
      * or a certificate inside <code>ks</code> who signs <code>cert</code>,
-     * or null otherwise.
+     * or null otherwise. A label is added.
      */
-    private static Certificate getTrustedSigner(Certificate cert, KeyStore ks)
-            throws Exception {
+    private static Pair<String,Certificate>
+            getTrustedSigner(Certificate cert, KeyStore ks) throws Exception {
         if (ks.getCertificateAlias(cert) != null) {
-            return cert;
+            return new Pair<>("", cert);
         }
         for (Enumeration<String> aliases = ks.aliases();
                 aliases.hasMoreElements(); ) {
@@ -2855,7 +2957,7 @@
             if (trustedCert != null) {
                 try {
                     cert.verify(trustedCert.getPublicKey());
-                    return trustedCert;
+                    return new Pair<>(name, trustedCert);
                 } catch (Exception e) {
                     // Not verified, skip to the next one
                 }
@@ -3120,7 +3222,7 @@
     /**
      * Prints warning about missing integrity check.
      */
-    private void printWarning() {
+    private void printNoIntegrityWarning() {
         System.err.println();
         System.err.println(rb.getString
             (".WARNING.WARNING.WARNING."));
@@ -3145,6 +3247,9 @@
                                         Certificate[] replyCerts)
         throws Exception
     {
+
+        checkWeak(rb.getString("reply"), replyCerts);
+
         // order the certs in the reply (bottom-up).
         // we know that all certs in the reply are of type X.509, because
         // we parsed them using an X.509 certificate factory
@@ -3192,9 +3297,11 @@
 
         // do we trust the cert at the top?
         Certificate topCert = replyCerts[replyCerts.length-1];
-        Certificate root = getTrustedSigner(topCert, keyStore);
+        boolean fromKeyStore = true;
+        Pair<String,Certificate> root = getTrustedSigner(topCert, keyStore);
         if (root == null && trustcacerts && caks != null) {
             root = getTrustedSigner(topCert, caks);
+            fromKeyStore = false;
         }
         if (root == null) {
             System.err.println();
@@ -3203,33 +3310,42 @@
             printX509Cert((X509Certificate)topCert, System.out);
             System.err.println();
             System.err.print(rb.getString(".is.not.trusted."));
+            printWeakWarnings(true);
             String reply = getYesNoReply
                     (rb.getString("Install.reply.anyway.no."));
             if ("NO".equals(reply)) {
                 return null;
             }
         } else {
-            if (root != topCert) {
+            if (root.snd != topCert) {
                 // append the root CA cert to the chain
                 Certificate[] tmpCerts =
                     new Certificate[replyCerts.length+1];
                 System.arraycopy(replyCerts, 0, tmpCerts, 0,
                                  replyCerts.length);
-                tmpCerts[tmpCerts.length-1] = root;
+                tmpCerts[tmpCerts.length-1] = root.snd;
                 replyCerts = tmpCerts;
+                checkWeak(String.format(rb.getString(fromKeyStore ?
+                                            "alias.in.keystore" :
+                                            "alias.in.cacerts"),
+                                        root.fst),
+                          root.snd);
             }
         }
-
         return replyCerts;
     }
 
     /**
      * Establishes a certificate chain (using trusted certificates in the
-     * keystore), starting with the user certificate
+     * keystore and cacerts), starting with the reply (certToVerify)
      * and ending at a self-signed certificate found in the keystore.
      *
-     * @param userCert the user certificate of the alias
-     * @param certToVerify the single certificate provided in the reply
+     * @param userCert optional existing certificate, mostly likely be the
+     *                 original self-signed cert created by -genkeypair.
+     *                 It must have the same public key as certToVerify
+     *                 but cannot be the same cert.
+     * @param certToVerify the starting certificate to build the chain
+     * @returns the established chain, might be null if user decides not
      */
     private Certificate[] establishCertChain(Certificate userCert,
                                              Certificate certToVerify)
@@ -3257,30 +3373,37 @@
         // Use the subject distinguished name as the key into the hash table.
         // All certificates associated with the same subject distinguished
         // name are stored in the same hash table entry as a vector.
-        Hashtable<Principal, Vector<Certificate>> certs = null;
+        Hashtable<Principal, Vector<Pair<String,X509Certificate>>> certs = null;
         if (keyStore.size() > 0) {
-            certs = new Hashtable<Principal, Vector<Certificate>>(11);
+            certs = new Hashtable<>(11);
             keystorecerts2Hashtable(keyStore, certs);
         }
         if (trustcacerts) {
             if (caks!=null && caks.size()>0) {
                 if (certs == null) {
-                    certs = new Hashtable<Principal, Vector<Certificate>>(11);
+                    certs = new Hashtable<>(11);
                 }
                 keystorecerts2Hashtable(caks, certs);
             }
         }
 
         // start building chain
-        Vector<Certificate> chain = new Vector<>(2);
-        if (buildChain((X509Certificate)certToVerify, chain, certs)) {
-            Certificate[] newChain = new Certificate[chain.size()];
+        Vector<Pair<String,X509Certificate>> chain = new Vector<>(2);
+        if (buildChain(
+                new Pair<>(rb.getString("the.input"),
+                           (X509Certificate) certToVerify),
+                chain, certs)) {
+            for (Pair<String,X509Certificate> p : chain) {
+                checkWeak(p.fst, p.snd);
+            }
+            Certificate[] newChain =
+                    new Certificate[chain.size()];
             // buildChain() returns chain with self-signed root-cert first and
             // user-cert last, so we need to invert the chain before we store
             // it
             int j=0;
             for (int i=chain.size()-1; i>=0; i--) {
-                newChain[j] = chain.elementAt(i);
+                newChain[j] = chain.elementAt(i).snd;
                 j++;
             }
             return newChain;
@@ -3291,7 +3414,17 @@
     }
 
     /**
-     * Recursively tries to establish chain from pool of trusted certs.
+     * Recursively tries to establish chain from pool of certs starting from
+     * certToVerify until a self-signed cert is found, and fill the certs found
+     * into chain. Each cert in the chain signs the next one.
+     *
+     * This method is able to recover from an error, say, if certToVerify
+     * is signed by certA but certA has no issuer in certs and itself is not
+     * self-signed, the method can try another certB that also signs
+     * certToVerify and look for signer of certB, etc, etc.
+     *
+     * Each cert in chain comes with a label showing its origin. The label is
+     * used in the warning message when the cert is considered a risk.
      *
      * @param certToVerify the cert that needs to be verified.
      * @param chain the chain that's being built.
@@ -3299,19 +3432,20 @@
      *
      * @return true if successful, false otherwise.
      */
-    private boolean buildChain(X509Certificate certToVerify,
-                        Vector<Certificate> chain,
-                        Hashtable<Principal, Vector<Certificate>> certs) {
-        Principal issuer = certToVerify.getIssuerDN();
-        if (isSelfSigned(certToVerify)) {
+    private boolean buildChain(Pair<String,X509Certificate> certToVerify,
+            Vector<Pair<String,X509Certificate>> chain,
+            Hashtable<Principal, Vector<Pair<String,X509Certificate>>> certs) {
+        if (isSelfSigned(certToVerify.snd)) {
             // reached self-signed root cert;
             // no verification needed because it's trusted.
             chain.addElement(certToVerify);
             return true;
         }
 
+        Principal issuer = certToVerify.snd.getIssuerDN();
+
         // Get the issuer's certificate(s)
-        Vector<Certificate> vec = certs.get(issuer);
+        Vector<Pair<String,X509Certificate>> vec = certs.get(issuer);
         if (vec == null) {
             return false;
         }
@@ -3319,13 +3453,12 @@
         // Try out each certificate in the vector, until we find one
         // whose public key verifies the signature of the certificate
         // in question.
-        for (Enumeration<Certificate> issuerCerts = vec.elements();
+        for (Enumeration<Pair<String,X509Certificate>> issuerCerts = vec.elements();
              issuerCerts.hasMoreElements(); ) {
-            X509Certificate issuerCert
-                = (X509Certificate)issuerCerts.nextElement();
-            PublicKey issuerPubKey = issuerCert.getPublicKey();
+            Pair<String,X509Certificate> issuerCert = issuerCerts.nextElement();
+            PublicKey issuerPubKey = issuerCert.snd.getPublicKey();
             try {
-                certToVerify.verify(issuerPubKey);
+                certToVerify.snd.verify(issuerPubKey);
             } catch (Exception e) {
                 continue;
             }
@@ -3374,10 +3507,11 @@
     /**
      * Stores the (leaf) certificates of a keystore in a hashtable.
      * All certs belonging to the same CA are stored in a vector that
-     * in turn is stored in the hashtable, keyed by the CA's subject DN
+     * in turn is stored in the hashtable, keyed by the CA's subject DN.
+     * Each cert comes with a string label that shows its origin and alias.
      */
     private void keystorecerts2Hashtable(KeyStore ks,
-                Hashtable<Principal, Vector<Certificate>> hash)
+                Hashtable<Principal, Vector<Pair<String,X509Certificate>>> hash)
         throws Exception {
 
         for (Enumeration<String> aliases = ks.aliases();
@@ -3386,13 +3520,20 @@
             Certificate cert = ks.getCertificate(alias);
             if (cert != null) {
                 Principal subjectDN = ((X509Certificate)cert).getSubjectDN();
-                Vector<Certificate> vec = hash.get(subjectDN);
+                Pair<String,X509Certificate> pair = new Pair<>(
+                        String.format(
+                                rb.getString(ks == caks ?
+                                        "alias.in.cacerts" :
+                                        "alias.in.keystore"),
+                                alias),
+                        (X509Certificate)cert);
+                Vector<Pair<String,X509Certificate>> vec = hash.get(subjectDN);
                 if (vec == null) {
-                    vec = new Vector<Certificate>();
-                    vec.addElement(cert);
+                    vec = new Vector<>();
+                    vec.addElement(pair);
                 } else {
-                    if (!vec.contains(cert)) {
-                        vec.addElement(cert);
+                    if (!vec.contains(pair)) {
+                        vec.addElement(pair);
                     }
                 }
                 hash.put(subjectDN, vec);
@@ -3965,6 +4106,67 @@
         return ext;
     }
 
+    private void checkWeak(String label, String sigAlg, Key key) {
+
+        if (!DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, sigAlg, null)) {
+            weakWarnings.add(String.format(
+                    rb.getString("whose.sigalg.risk"), label, sigAlg));
+        }
+        if (key != null && !DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, key)) {
+            weakWarnings.add(String.format(
+                    rb.getString("whose.key.risk"),
+                    label,
+                    String.format(rb.getString("key.bit"),
+                            KeyUtil.getKeySize(key), key.getAlgorithm())));
+        }
+    }
+
+    private void checkWeak(String label, Certificate[] certs) {
+        for (int i = 0; i < certs.length; i++) {
+            Certificate cert = certs[i];
+            if (cert instanceof X509Certificate) {
+                X509Certificate xc = (X509Certificate)cert;
+                String fullLabel = label;
+                if (certs.length > 1) {
+                    fullLabel = oneInMany(label, i, certs.length);
+                }
+                checkWeak(fullLabel, xc.getSigAlgName(), xc.getPublicKey());
+            }
+        }
+    }
+
+    private void checkWeak(String label, Certificate cert) {
+        if (cert instanceof X509Certificate) {
+            X509Certificate xc = (X509Certificate)cert;
+            checkWeak(label, xc.getSigAlgName(), xc.getPublicKey());
+        }
+    }
+
+    private void checkWeak(String label, PKCS10 p10) {
+        checkWeak(label, p10.getSigAlg(), p10.getSubjectPublicKeyInfo());
+    }
+
+    private void checkWeak(String label, CRL crl, Key key) {
+        if (crl instanceof X509CRLImpl) {
+            X509CRLImpl impl = (X509CRLImpl)crl;
+            checkWeak(label, impl.getSigAlgName(), key);
+        }
+    }
+
+    private void printWeakWarnings(boolean newLine) {
+        if (!weakWarnings.isEmpty() && !nowarn) {
+            System.err.println("\nWarning:");
+            for (String warning : weakWarnings) {
+                System.err.println(warning);
+            }
+            if (newLine) {
+                // When calling before a yes/no prompt, add a new line
+                System.err.println();
+            }
+        }
+        weakWarnings.clear();
+    }
+
     /**
      * Prints the usage of this tool.
      */
--- a/src/share/classes/sun/security/tools/keytool/Resources.java	Mon Nov 20 17:39:01 2017 +0000
+++ b/src/share/classes/sun/security/tools/keytool/Resources.java	Mon Nov 20 17:44:05 2017 +0000
@@ -334,8 +334,6 @@
         {"Enter.alias.name.", "Enter alias name:  "},
         {".RETURN.if.same.as.for.otherAlias.",
                 "\t(RETURN if same as for <{0}>)"},
-        {".PATTERN.printX509Cert",
-                "Owner: {0}\nIssuer: {1}\nSerial number: {2}\nValid from: {3} until: {4}\nCertificate fingerprints:\n\t MD5:  {5}\n\t SHA1: {6}\n\t SHA256: {7}\nSignature algorithm name: {8}\nSubject Public Key Algorithm: {9} ({10,number,#})\nVersion: {11}"},
         {"What.is.your.first.and.last.name.",
                 "What is your first and last name?"},
         {"What.is.the.name.of.your.organizational.unit.",
@@ -402,16 +400,12 @@
         {"Please.provide.keysize.for.secret.key.generation",
                 "Please provide -keysize for secret key generation"},
 
-        {"verified.by.s.in.s", "Verified by %s in %s"},
         {"warning.not.verified.make.sure.keystore.is.correct",
             "WARNING: not verified. Make sure -keystore is correct."},
 
         {"Extensions.", "Extensions: "},
         {".Empty.value.", "(Empty value)"},
         {"Extension.Request.", "Extension Request:"},
-        {"PKCS.10.Certificate.Request.Version.1.0.Subject.s.Public.Key.s.format.s.key.",
-                "PKCS #10 Certificate Request (Version 1.0)\n" +
-                "Subject: %s\nPublic Key: %s format %s key\n"},
         {"Unknown.keyUsage.type.", "Unknown keyUsage type: "},
         {"Unknown.extendedkeyUsage.type.", "Unknown extendedkeyUsage type: "},
         {"Unknown.AccessDescription.type.", "Unknown AccessDescription type: "},
@@ -420,7 +414,34 @@
                  "This extension cannot be marked as critical. "},
         {"Odd.number.of.hex.digits.found.", "Odd number of hex digits found: "},
         {"Unknown.extension.type.", "Unknown extension type: "},
-        {"command.{0}.is.ambiguous.", "command {0} is ambiguous:"}
+        {"command.{0}.is.ambiguous.", "command {0} is ambiguous:"},
+
+        // 8171319: keytool should print out warnings when reading or
+        // generating cert/cert req using weak algorithms
+        {"the.certificate.request", "The certificate request"},
+        {"the.issuer", "The issuer"},
+        {"the.generated.certificate", "The generated certificate"},
+        {"the.generated.crl", "The generated CRL"},
+        {"the.generated.certificate.request", "The generated certificate request"},
+        {"the.certificate", "The certificate"},
+        {"the.crl", "The CRL"},
+        {"the.tsa.certificate", "The TSA certificate"},
+        {"the.input", "The input"},
+        {"reply", "Reply"},
+        {"one.in.many", "%s #%d of %d"},
+        {"alias.in.cacerts", "Issuer <%s> in cacerts"},
+        {"alias.in.keystore", "Issuer <%s>"},
+        {"with.weak", "%s (weak)"},
+        {"key.bit", "%d-bit %s key"},
+        {"key.bit.weak", "%d-bit %s key (weak)"},
+        {".PATTERN.printX509Cert.with.weak",
+                "Owner: {0}\nIssuer: {1}\nSerial number: {2}\nValid from: {3} until: {4}\nCertificate fingerprints:\n\t MD5:  {5}\n\t SHA1: {6}\n\t SHA256: {7}\nSignature algorithm name: {8}\nSubject Public Key Algorithm: {9}\nVersion: {10}"},
+        {"PKCS.10.with.weak",
+                "PKCS #10 Certificate Request (Version 1.0)\n" +
+                        "Subject: %s\nFormat: %s\nPublic Key: %s\nSignature algorithm: %s\n"},
+        {"verified.by.s.in.s.weak", "Verified by %s in %s with a %s"},
+        {"whose.sigalg.risk", "%s uses the %s signature algorithm which is considered a security risk."},
+        {"whose.key.risk", "%s uses a %s which is considered a security risk."},
     };
 
 
--- a/src/share/classes/sun/security/x509/X509CRLImpl.java	Mon Nov 20 17:39:01 2017 +0000
+++ b/src/share/classes/sun/security/x509/X509CRLImpl.java	Mon Nov 20 17:44:05 2017 +0000
@@ -480,10 +480,15 @@
      * @return value of this CRL in a printable form.
      */
     public String toString() {
+        return toStringWithAlgName("" + sigAlgId);
+    }
+
+    // Specifically created for keytool to append a (weak) label to sigAlg
+    public String toStringWithAlgName(String name) {
         StringBuffer sb = new StringBuffer();
         sb.append("X.509 CRL v" + (version+1) + "\n");
         if (sigAlgId != null)
-            sb.append("Signature Algorithm: " + sigAlgId.toString() +
+            sb.append("Signature Algorithm: " + name.toString() +
                   ", OID=" + (sigAlgId.getOID()).toString() + "\n");
         if (issuer != null)
             sb.append("Issuer: " + issuer.toString() + "\n");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/tools/keytool/WeakAlg.java	Mon Nov 20 17:44:05 2017 +0000
@@ -0,0 +1,553 @@
+/*
+ * Copyright (c) 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8171319
+ * @summary keytool should print out warnings when reading or generating
+  *         cert/cert req using weak algorithms
+ * @library /lib/testlibrary
+ * @compile -XDignore.symbol.file WeakAlg.java
+ * @run main/othervm/timeout=600 -Duser.language=en -Duser.country=US WeakAlg
+ */
+
+import jdk.testlibrary.SecurityTools;
+import jdk.testlibrary.OutputAnalyzer;
+import sun.security.tools.KeyStoreUtil;
+import sun.security.util.DisabledAlgorithmConstraints;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.security.CryptoPrimitive;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+public class WeakAlg {
+
+    public static void main(String[] args) throws Throwable {
+
+        rm("ks");
+
+        // -genkeypair, and -printcert, -list -alias, -exportcert
+        // (w/ different formats)
+        checkGenKeyPair("a", "-keyalg RSA -sigalg MD5withRSA", "MD5withRSA");
+        checkGenKeyPair("b", "-keyalg RSA -keysize 512", "512-bit RSA key");
+        checkGenKeyPair("c", "-keyalg RSA", null);
+
+        kt("-list")
+                .shouldContain("Warning:")
+                .shouldMatch("<a>.*MD5withRSA.*risk")
+                .shouldMatch("<b>.*512-bit RSA key.*risk");
+        kt("-list -v")
+                .shouldContain("Warning:")
+                .shouldMatch("<a>.*MD5withRSA.*risk")
+                .shouldContain("MD5withRSA (weak)")
+                .shouldMatch("<b>.*512-bit RSA key.*risk")
+                .shouldContain("512-bit RSA key (weak)");
+
+        // Multiple warnings for multiple cert in -printcert or -list or -exportcert
+
+        // -certreq, -printcertreq, -gencert
+        checkCertReq("a", "", null);
+        gencert("c-a", "")
+                .shouldNotContain("Warning"); // new sigalg is not weak
+        gencert("c-a", "-sigalg MD2withRSA")
+                .shouldContain("Warning:")
+                .shouldMatch("The generated certificate.*MD2withRSA.*risk");
+
+        checkCertReq("a", "-sigalg MD5withRSA", "MD5withRSA");
+        gencert("c-a", "")
+                .shouldContain("Warning:")
+                .shouldMatch("The certificate request.*MD5withRSA.*risk");
+        gencert("c-a", "-sigalg MD2withRSA")
+                .shouldContain("Warning:")
+                .shouldMatch("The certificate request.*MD5withRSA.*risk")
+                .shouldMatch("The generated certificate.*MD2withRSA.*risk");
+
+        checkCertReq("b", "", "512-bit RSA key");
+        gencert("c-b", "")
+                .shouldContain("Warning:")
+                .shouldMatch("The certificate request.*512-bit RSA key.*risk")
+                .shouldMatch("The generated certificate.*512-bit RSA key.*risk");
+
+        checkCertReq("c", "", null);
+        gencert("a-c", "")
+                .shouldContain("Warning:")
+                .shouldMatch("The issuer.*MD5withRSA.*risk");
+
+        // but the new cert is not weak
+        kt("-printcert -file a-c.cert")
+                .shouldNotContain("Warning")
+                .shouldNotContain("weak");
+
+        gencert("b-c", "")
+                .shouldContain("Warning:")
+                .shouldMatch("The issuer.*512-bit RSA key.*risk");
+
+        // -importcert
+        checkImport();
+
+        // -importkeystore
+        checkImportKeyStore();
+
+        // -gencrl, -printcrl
+
+        checkGenCRL("a", "", null);
+        checkGenCRL("a", "-sigalg MD5withRSA", "MD5withRSA");
+        checkGenCRL("b", "", "512-bit RSA key");
+        checkGenCRL("c", "", null);
+
+        kt("-delete -alias b");
+        kt("-printcrl -file b.crl")
+                .shouldContain("WARNING: not verified");
+    }
+
+    static void checkImportKeyStore() throws Exception {
+
+        saveStore();
+
+        rm("ks");
+        kt("-importkeystore -srckeystore ks2 -srcstorepass changeit")
+                .shouldContain("3 entries successfully imported")
+                .shouldContain("Warning")
+                .shouldMatch("<b>.*512-bit RSA key.*risk")
+                .shouldMatch("<a>.*MD5withRSA.*risk");
+
+        rm("ks");
+        kt("-importkeystore -srckeystore ks2 -srcstorepass changeit -srcalias a")
+                .shouldContain("Warning")
+                .shouldMatch("<a>.*MD5withRSA.*risk");
+
+        reStore();
+    }
+
+    static void checkImport() throws Exception {
+
+        saveStore();
+
+        // add trusted cert
+
+        // cert already in
+        kt("-importcert -alias d -file a.cert", "no")
+                .shouldContain("Certificate already exists in keystore")
+                .shouldContain("Warning")
+                .shouldMatch("The input.*MD5withRSA.*risk")
+                .shouldContain("Do you still want to add it?");
+        kt("-importcert -alias d -file a.cert -noprompt")
+                .shouldContain("Warning")
+                .shouldMatch("The input.*MD5withRSA.*risk")
+                .shouldNotContain("[no]");
+
+        // cert is self-signed
+        kt("-delete -alias a");
+        kt("-delete -alias d");
+        kt("-importcert -alias d -file a.cert", "no")
+                .shouldContain("Warning")
+                .shouldContain("MD5withRSA (weak)")
+                .shouldMatch("The input.*MD5withRSA.*risk")
+                .shouldContain("Trust this certificate?");
+        kt("-importcert -alias d -file a.cert -noprompt")
+                .shouldContain("Warning")
+                .shouldMatch("The input.*MD5withRSA.*risk")
+                .shouldNotContain("[no]");
+
+        // cert is self-signed cacerts
+        String weakSigAlgCA = null;
+        KeyStore ks = KeyStoreUtil.getCacertsKeyStore();
+        if (ks != null) {
+            DisabledAlgorithmConstraints disabledCheck =
+                    new DisabledAlgorithmConstraints(
+                            DisabledAlgorithmConstraints.PROPERTY_CERTPATH_DISABLED_ALGS);
+            Set<CryptoPrimitive> sigPrimitiveSet = Collections
+                    .unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
+
+            for (String s : Collections.list(ks.aliases())) {
+                if (ks.isCertificateEntry(s)) {
+                    X509Certificate c = (X509Certificate)ks.getCertificate(s);
+                    String sigAlg = c.getSigAlgName();
+                    if (!disabledCheck.permits(sigPrimitiveSet, sigAlg, null)) {
+                        weakSigAlgCA = sigAlg;
+                        Files.write(Paths.get("ca.cert"),
+                                ks.getCertificate(s).getEncoded());
+                        break;
+                    }
+                }
+            }
+        }
+        if (weakSigAlgCA != null) {
+            kt("-delete -alias d");
+            kt("-importcert -alias d -trustcacerts -file ca.cert", "no")
+                    .shouldContain("Certificate already exists in system-wide CA")
+                    .shouldContain("Warning")
+                    .shouldMatch("The input.*" + weakSigAlgCA + ".*risk")
+                    .shouldContain("Do you still want to add it to your own keystore?");
+            kt("-importcert -alias d -file ca.cert -noprompt")
+                    .shouldContain("Warning")
+                    .shouldMatch("The input.*" + weakSigAlgCA + ".*risk")
+                    .shouldNotContain("[no]");
+        }
+
+        // a non self-signed weak cert
+        reStore();
+        certreq("b", "");
+        gencert("c-b", "");
+        kt("-importcert -alias d -file c-b.cert")   // weak only, no prompt
+                .shouldContain("Warning")
+                .shouldNotContain("512-bit RSA key (weak)")
+                .shouldMatch("The input.*512-bit RSA key.*risk")
+                .shouldNotContain("[no]");
+
+        kt("-delete -alias b");
+        kt("-delete -alias c");
+        kt("-delete -alias d");
+
+        kt("-importcert -alias d -file c-b.cert", "no") // weak and not trusted
+                .shouldContain("Warning")
+                .shouldContain("512-bit RSA key (weak)")
+                .shouldMatch("The input.*512-bit RSA key.*risk")
+                .shouldContain("Trust this certificate?");
+        kt("-importcert -alias d -file c-b.cert -noprompt")
+                .shouldContain("Warning")
+                .shouldMatch("The input.*512-bit RSA key.*risk")
+                .shouldNotContain("[no]");
+
+        // a non self-signed strong cert
+        reStore();
+        certreq("a", "");
+        gencert("c-a", "");
+        kt("-importcert -alias d -file c-a.cert") // trusted
+                .shouldNotContain("Warning")
+                .shouldNotContain("[no]");
+
+        kt("-delete -alias a");
+        kt("-delete -alias c");
+        kt("-delete -alias d");
+
+        kt("-importcert -alias d -file c-a.cert", "no") // not trusted
+                .shouldNotContain("Warning")
+                .shouldContain("Trust this certificate?");
+        kt("-importcert -alias d -file c-a.cert -noprompt")
+                .shouldNotContain("Warning")
+                .shouldNotContain("[no]");
+
+        // install reply
+
+        reStore();
+
+        gencert("a-b", "");
+        gencert("b-c", "");
+
+        // Full chain with root
+        cat("a-a-b-c.cert", "b-c.cert", "a-b.cert", "a.cert");
+        kt("-importcert -alias c -file a-a-b-c.cert")   // only weak
+                .shouldContain("Warning")
+                .shouldMatch("Reply #2 of 3.*512-bit RSA key.*risk")
+                .shouldMatch("Reply #3 of 3.*MD5withRSA.*risk")
+                .shouldNotContain("[no]");
+
+        // Without root
+        cat("a-b-c.cert", "b-c.cert", "a-b.cert");
+        kt("-importcert -alias c -file a-b-c.cert")     // only weak
+                .shouldContain("Warning")
+                .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk")
+                .shouldMatch("Issuer <a>.*MD5withRSA.*risk")
+                .shouldNotContain("[no]");
+
+        reStore();
+        gencert("b-a", "");
+
+        kt("-importcert -alias a -file b-a.cert")
+                .shouldContain("Warning")
+                .shouldMatch("Issuer <b>.*512-bit RSA key.*risk")
+                .shouldNotContain("[no]");
+
+        kt("-importcert -alias a -file c-a.cert")
+                .shouldNotContain("Warning");
+
+        kt("-importcert -alias b -file c-b.cert")
+                .shouldContain("Warning")
+                .shouldMatch("The input.*512-bit RSA key.*risk")
+                .shouldNotContain("[no]");
+
+        reStore();
+        gencert("b-a", "");
+
+        cat("c-b-a.cert", "b-a.cert", "c-b.cert");
+
+        kt("-printcert -file c-b-a.cert")
+                .shouldContain("Warning")
+                .shouldMatch("The certificate #2 of 2.*512-bit RSA key.*risk");
+
+        kt("-delete -alias b");
+
+        kt("-importcert -alias a -file c-b-a.cert")
+                .shouldContain("Warning")
+                .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk")
+                .shouldNotContain("[no]");
+
+        kt("-delete -alias c");
+        kt("-importcert -alias a -file c-b-a.cert", "no")
+                .shouldContain("Top-level certificate in reply:")
+                .shouldContain("512-bit RSA key (weak)")
+                .shouldContain("Warning")
+                .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk")
+                .shouldContain("Install reply anyway?");
+        kt("-importcert -alias a -file c-b-a.cert -noprompt")
+                .shouldContain("Warning")
+                .shouldMatch("Reply #2 of 2.*512-bit RSA key.*risk")
+                .shouldNotContain("[no]");
+
+        reStore();
+    }
+
+    private static void cat(String dest, String... src) throws IOException {
+        System.out.println("---------------------------------------------");
+        System.out.printf("$ cat ");
+
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        for (String s : src) {
+            System.out.printf(s + " ");
+            bout.write(Files.readAllBytes(Paths.get(s)));
+        }
+        Files.write(Paths.get(dest), bout.toByteArray());
+        System.out.println("> " + dest);
+    }
+
+    static void checkGenCRL(String alias, String options, String bad) {
+
+        OutputAnalyzer oa = kt("-gencrl -alias " + alias
+                + " -id 1 -file " + alias + ".crl " + options);
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The generated CRL.*" + bad + ".*risk");
+        }
+
+        oa = kt("-printcrl -file " + alias + ".crl");
+        if (bad == null) {
+            oa.shouldNotContain("Warning")
+                    .shouldContain("Verified by " + alias + " in keystore")
+                    .shouldNotContain("(weak");
+        } else {
+            oa.shouldContain("Warning:")
+                    .shouldMatch("The CRL.*" + bad + ".*risk")
+                    .shouldContain("Verified by " + alias + " in keystore")
+                    .shouldContain(bad + " (weak)");
+        }
+    }
+
+    static void checkCertReq(
+            String alias, String options, String bad) {
+
+        OutputAnalyzer oa = certreq(alias, options);
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The generated certificate request.*" + bad + ".*risk");
+        }
+
+        oa = kt("-printcertreq -file " + alias + ".req");
+        if (bad == null) {
+            oa.shouldNotContain("Warning")
+                    .shouldNotContain("(weak)");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The certificate request.*" + bad + ".*risk")
+                    .shouldContain(bad + " (weak)");
+        }
+    }
+
+    static void checkGenKeyPair(
+            String alias, String options, String bad) {
+
+        OutputAnalyzer oa = genkeypair(alias, options);
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The generated certificate.*" + bad + ".*risk");
+        }
+
+        oa = kt("-exportcert -alias " + alias + " -file " + alias + ".cert");
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The certificate.*" + bad + ".*risk");
+        }
+
+        oa = kt("-exportcert -rfc -alias " + alias + " -file " + alias + ".cert");
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The certificate.*" + bad + ".*risk");
+        }
+
+        oa = kt("-printcert -rfc -file " + alias + ".cert");
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The certificate.*" + bad + ".*risk");
+        }
+
+        oa = kt("-list -alias " + alias);
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldMatch("The certificate.*" + bad + ".*risk");
+        }
+
+        // With cert content
+
+        oa = kt("-printcert -file " + alias + ".cert");
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldContain(bad + " (weak)")
+                    .shouldMatch("The certificate.*" + bad + ".*risk");
+        }
+
+        oa = kt("-list -v -alias " + alias);
+        if (bad == null) {
+            oa.shouldNotContain("Warning");
+        } else {
+            oa.shouldContain("Warning")
+                    .shouldContain(bad + " (weak)")
+                    .shouldMatch("The certificate.*" + bad + ".*risk");
+        }
+    }
+
+    // This is slow, but real keytool process is launched.
+    static OutputAnalyzer kt1(String cmd, String... input) {
+        cmd = "-keystore ks -storepass changeit " +
+                "-keypass changeit " + cmd;
+        System.out.println("---------------------------------------------");
+        try {
+            SecurityTools.setResponse(input);
+            return SecurityTools.keytool(cmd);
+        } catch (Throwable e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    // Fast keytool execution by directly calling its main() method
+    static OutputAnalyzer kt(String cmd, String... input) {
+        PrintStream out = System.out;
+        PrintStream err = System.err;
+        InputStream ins = System.in;
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        ByteArrayOutputStream berr = new ByteArrayOutputStream();
+        boolean succeed = true;
+        try {
+            cmd = "-keystore ks -storepass changeit " +
+                    "-keypass changeit " + cmd;
+            System.out.println("---------------------------------------------");
+            System.out.println("$ keytool " + cmd);
+            System.out.println();
+            String feed = "";
+            if (input.length > 0) {
+                feed = SecurityTools.joiner("", "\n", "\n", (Object[]) input);
+            }
+            System.setIn(new ByteArrayInputStream(feed.getBytes()));
+            System.setOut(new PrintStream(bout));
+            System.setErr(new PrintStream(berr));
+            sun.security.tools.keytool.Main.main(
+                    cmd.trim().split("\\s+"));
+        } catch (Exception e) {
+            // Might be a normal exception when -debug is on or
+            // SecurityException (thrown by jtreg) when System.exit() is called
+            if (!(e instanceof SecurityException)) {
+                e.printStackTrace();
+            }
+            succeed = false;
+        } finally {
+            System.setOut(out);
+            System.setErr(err);
+            System.setIn(ins);
+        }
+        String sout = new String(bout.toByteArray());
+        String serr = new String(berr.toByteArray());
+        System.out.println("STDOUT:\n" + sout + "\nSTDERR:\n" + serr);
+        if (!succeed) {
+            throw new RuntimeException();
+        }
+        return new OutputAnalyzer(sout, serr);
+    }
+
+    static OutputAnalyzer genkeypair(String alias, String options) {
+        return kt("-genkeypair -alias " + alias + " -dname CN=" + alias
+                + " -keyalg RSA -storetype JKS " + options);
+    }
+
+    static OutputAnalyzer certreq(String alias, String options) {
+        return kt("-certreq -alias " + alias
+                + " -file " + alias + ".req " + options);
+    }
+
+    static OutputAnalyzer exportcert(String alias) {
+        return kt("-exportcert -alias " + alias + " -file " + alias + ".cert");
+    }
+
+    static OutputAnalyzer gencert(String relation, String options) {
+        int pos = relation.indexOf("-");
+        String issuer = relation.substring(0, pos);
+        String subject = relation.substring(pos + 1);
+        return kt(" -gencert -alias " + issuer + " -infile " + subject
+                + ".req -outfile " + relation + ".cert " + options);
+    }
+
+    static void saveStore() throws IOException {
+        System.out.println("---------------------------------------------");
+        System.out.println("$ cp ks ks2");
+        Files.copy(Paths.get("ks"), Paths.get("ks2"),
+                StandardCopyOption.REPLACE_EXISTING);
+    }
+
+    static void reStore() throws IOException {
+        System.out.println("---------------------------------------------");
+        System.out.println("$ cp ks2 ks");
+        Files.copy(Paths.get("ks2"), Paths.get("ks"),
+                StandardCopyOption.REPLACE_EXISTING);
+    }
+
+    static void rm(String s) throws IOException {
+        System.out.println("---------------------------------------------");
+        System.out.println("$ rm " + s);
+        Files.deleteIfExists(Paths.get(s));
+    }
+}