--- old/src/java.base/share/classes/sun/security/tools/keytool/Main.java 2017-01-23 18:01:56.365535100 +0800 +++ new/src/java.base/share/classes/sun/security/tools/keytool/Main.java 2017-01-23 18:01:56.190750600 +0800 @@ -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; @@ -156,6 +157,7 @@ private boolean protectedPath = false; private boolean srcprotectedPath = false; private boolean cacerts = false; + private boolean nowarn = false; private CertificateFactory cf = null; private KeyStore caks = null; // "cacerts" keystore private char[] srcstorePass = null; @@ -166,6 +168,16 @@ private List ids = new ArrayList<>(); // used in GENCRL private List v3ext = new ArrayList<>(); + // Warnings on weak algorithms + private List weakWarnings = new ArrayList<>(); + + private static final DisabledAlgorithmConstraints DISABLED_CHECK = + new DisabledAlgorithmConstraints( + DisabledAlgorithmConstraints.PROPERTY_CERTPATH_DISABLED_ALGS); + + private static final Set SIG_PRIMITIVE_SET = Collections + .unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE)); + enum Command { CERTREQ("Generates.a.certificate.request", ALIAS, SIGALG, FILEOUT, KEYPASS, KEYSTORE, DNAME, @@ -351,7 +363,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 = @@ -387,6 +399,7 @@ throw e; } } finally { + printWeakWarningsWithoutNewLine(); for (char[] pass : passwords) { if (pass != null) { Arrays.fill(pass, ' '); @@ -476,6 +489,8 @@ help = true; } else if (collator.compare(flags, "-conf") == 0) { i++; + } else if (collator.compare(flags, "-nowarn") == 0) { + nowarn = true; } else if (collator.compare(flags, "-keystore") == 0) { ksfname = args[++i]; if (new File(ksfname).getCanonicalPath().equals( @@ -1152,11 +1167,11 @@ } else if (command == LIST) { if (storePass == null && !KeyStoreUtil.isWindowsKeyStore(storetype)) { - printWarning(); + printNoIntegrityWarning(); } if (alias != null) { - doPrintEntry(alias, out); + doPrintEntry(null, alias, out); } else { doPrintEntries(out); } @@ -1253,6 +1268,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); @@ -1306,6 +1327,8 @@ byte[] rawReq = Pem.decode(new String(sb)); PKCS10 req = new PKCS10(rawReq); + checkWeak(rb.getString("the.input"), req); + info.set(X509CertInfo.KEY, new CertificateX509Key(req.getSubjectPublicKeyInfo())); info.set(X509CertInfo.SUBJECT, dname==null?req.getSubjectName():new X500Name(dname)); @@ -1335,6 +1358,9 @@ } } } + + checkWeak(rb.getString("the.issuer"), keyStore.getCertificateChain(alias)); + checkWeak(rb.getString("the.output"), cert); } private void doGenCRL(PrintStream out) @@ -1385,6 +1411,7 @@ } else { out.write(crl.getEncodedInternal()); } + checkWeak(null, crl, privateKey); } /** @@ -1431,6 +1458,8 @@ // Sign the request and base-64 encode it request.encodeAndSign(subject, signature); request.print(out); + + checkWeak(null, request); } /** @@ -1454,7 +1483,7 @@ { if (storePass == null && !KeyStoreUtil.isWindowsKeyStore(storetype)) { - printWarning(); + printNoIntegrityWarning(); } if (alias == null) { alias = keyAlias; @@ -1474,6 +1503,7 @@ throw new Exception(form.format(source)); } dumpCert(cert, out); + checkWeak(null, cert); } /** @@ -1729,6 +1759,8 @@ keyPass = promptForKeyPass(alias, null, storePass); } keyStore.setKeyEntry(alias, privKey, keyPass, chain); + + checkWeak(null, chain[0]); } /** @@ -1810,7 +1842,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) { @@ -1881,12 +1913,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.SHA.256.") + getCertFingerPrint("SHA-256", chain[0])); + checkWeak(label, chain); } } } else if (keyStore.entryInstanceOf(alias, @@ -1909,6 +1943,7 @@ out.println(rb.getString("Certificate.fingerprint.SHA.256.") + getCertFingerPrint("SHA-256", cert)); } + checkWeak(label, cert); } else { out.println(rb.getString("Unknown.Entry.Type")); } @@ -1992,7 +2027,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 @@ -2115,6 +2150,10 @@ Object[] source = {alias}; MessageFormat form = new MessageFormat(rb.getString("Entry.for.alias.alias.successfully.imported.")); System.err.println(form.format(source)); + Certificate c = srckeystore.getCertificate(alias); + if (c != null) { + checkWeak(null, c); + } } else if (result == 2) { if (!noprompt) { String reply = getYesNoReply("Do you want to quit the import process? [no]: "); @@ -2154,7 +2193,7 @@ for (Enumeration 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 @@ -2300,19 +2339,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(); } } @@ -2324,18 +2372,30 @@ out.println(rb.getString ("STARNN")); } + checkWeak(null, 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-----"); out.println(Base64.getMimeEncoder(64, CRLF).encodeToString(xcrl.getEncoded())); out.println("-----END X509 CRL-----"); } else { - out.println(crl.toString()); + // Hack: Inject "(weak)" into X509CRLImpl::toString. + String s = xcrl.toString(); + String title = "Signature Algorithm: "; + int pos = s.indexOf(title); + int pos2 = s.indexOf(",", pos); + if (pos > 0 && pos2 > 0) { + pos += title.length(); + s = s.substring(0, pos) + + withWeak(s.substring(pos, pos2)) + + s.substring(pos2); + } + out.println(s); } } @@ -2362,8 +2422,11 @@ PKCS10 req = new PKCS10(Pem.decode(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(PKCS9Attribute.EXTENSION_REQUEST_OID)) { @@ -2386,6 +2449,7 @@ if (debug) { out.println(req); // Just to see more, say, public key length... } + checkWeak(null, req); } /** @@ -2425,6 +2489,15 @@ if (i < (certs.length-1)) { out.println(); } + checkWeak(oneInMany(i, certs.length), x509Cert); + } + } + + private static String oneInMany(int i, int num) { + if (num == 1) { + return null; + } else { + return String.format(rb.getString("one.in.many"), i+1, num); } } @@ -2458,7 +2531,11 @@ out.println(); out.println(rb.getString("Signature.")); out.println(); - for (Certificate cert: signer.getSignerCertPath().getCertificates()) { + + List 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"); @@ -2467,12 +2544,15 @@ printX509Cert(x, out); } out.println(); + checkWeak(oneInMany(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"); @@ -2481,6 +2561,7 @@ printX509Cert(x, out); } out.println(); + checkWeak(oneInMany(cc++, certs.size()), x); } } } @@ -2523,6 +2604,7 @@ printX509Cert((X509Certificate)cert, out); out.println(); } + checkWeak(oneInMany(i, chain.size()), cert); } catch (Exception e) { if (debug) { e.printStackTrace(); @@ -2698,7 +2780,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, @@ -2735,6 +2817,12 @@ throw new Exception(rb.getString("Input.not.an.X.509.certificate")); } + if (noprompt) { + keyStore.setCertificateEntry(alias, cert); + checkWeak(null, cert); + return true; + } + // if certificate is self-signed, make sure it verifies boolean selfSigned = false; if (KeyStoreUtil.isSelfSigned(cert)) { @@ -2742,11 +2830,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); @@ -2755,6 +2838,8 @@ ("Certificate.already.exists.in.keystore.under.alias.trustalias.")); Object[] source = {trustalias}; System.err.println(form.format(source)); + checkWeak(null, cert); + printWeakWarnings(); reply = getYesNoReply (rb.getString("Do.you.still.want.to.add.it.no.")); } else if (selfSigned) { @@ -2764,6 +2849,8 @@ ("Certificate.already.exists.in.system.wide.CA.keystore.under.alias.trustalias.")); Object[] source = {trustalias}; System.err.println(form.format(source)); + checkWeak(null, cert); + printWeakWarnings(); reply = getYesNoReply (rb.getString("Do.you.still.want.to.add.it.to.your.own.keystore.no.")); } @@ -2771,6 +2858,8 @@ // Print the cert and ask user if they really want to add // it to their keystore printX509Cert(cert, System.out); + checkWeak(null, cert); + printWeakWarnings(); reply = getYesNoReply (rb.getString("Trust.this.certificate.no.")); } @@ -2784,6 +2873,7 @@ } } + // Not found in this keystore and not self-signed // Try to establish trust chain try { Certificate[] chain = establishCertChain(null, cert); @@ -2795,6 +2885,8 @@ // Print the cert and ask user if they really want to add it to // their keystore printX509Cert(cert, System.out); + checkWeak(null, cert); + printWeakWarnings(); reply = getYesNoReply (rb.getString("Trust.this.certificate.no.")); if ("YES".equals(reply)) { @@ -2933,6 +3025,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. */ @@ -2941,7 +3051,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(), @@ -2950,10 +3060,9 @@ cert.getNotAfter().toString(), getCertFingerPrint("SHA-1", 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)); @@ -3281,7 +3390,7 @@ /** * Prints warning about missing integrity check. */ - private void printWarning() { + private void printNoIntegrityWarning() { System.err.println(); System.err.println(rb.getString (".WARNING.WARNING.WARNING.")); @@ -3306,6 +3415,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 @@ -3369,6 +3481,7 @@ printX509Cert((X509Certificate)topCert, System.out); System.err.println(); System.err.print(rb.getString(".is.not.trusted.")); + printWeakWarnings(); String reply = getYesNoReply (rb.getString("Install.reply.anyway.no.")); if ("NO".equals(reply)) { @@ -3383,6 +3496,15 @@ replyCerts.length); tmpCerts[tmpCerts.length-1] = root; replyCerts = tmpCerts; + checkWeak(rb.getString("root"), root); + } + if (!weakWarnings.isEmpty()) { + printWeakWarnings(); + String reply = getYesNoReply + (rb.getString("Install.reply.anyway.no.")); + if ("NO".equals(reply)) { + return null; + } } } @@ -3391,11 +3513,15 @@ /** * 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) @@ -3423,30 +3549,46 @@ // 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> certs = null; + Hashtable>> certs = null; if (keyStore.size() > 0) { - certs = new Hashtable>(11); + certs = new Hashtable<>(11); keystorecerts2Hashtable(keyStore, certs); } if (trustcacerts) { if (caks!=null && caks.size()>0) { if (certs == null) { - certs = new Hashtable>(11); + certs = new Hashtable<>(11); } keystorecerts2Hashtable(caks, certs); } } // start building chain - Vector chain = new Vector<>(2); - if (buildChain((X509Certificate)certToVerify, chain, certs)) { - Certificate[] newChain = new Certificate[chain.size()]; + Vector> chain = new Vector<>(2); + if (buildChain( + new Pair<>(rb.getString("the.input"), + (X509Certificate) certToVerify), + chain, certs)) { + for (Pair p : chain) { + checkWeak(p.fst, p.snd); + } + if (!weakWarnings.isEmpty() && !noprompt) { + printWeakWarnings(); + String reply = getYesNoReply + (rb.getString("Trust.this.certificate.no.")); + if (!"YES".equals(reply)) { + return null; + } + + } + 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; @@ -3457,7 +3599,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. @@ -3465,19 +3617,20 @@ * * @return true if successful, false otherwise. */ - private boolean buildChain(X509Certificate certToVerify, - Vector chain, - Hashtable> certs) { - Principal issuer = certToVerify.getIssuerDN(); - if (KeyStoreUtil.isSelfSigned(certToVerify)) { + private boolean buildChain(Pair certToVerify, + Vector> chain, + Hashtable>> certs) { + if (KeyStoreUtil.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 vec = certs.get(issuer); + Vector> vec = certs.get(issuer); if (vec == null) { return false; } @@ -3485,13 +3638,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 issuerCerts = vec.elements(); - issuerCerts.hasMoreElements(); ) { - X509Certificate issuerCert - = (X509Certificate)issuerCerts.nextElement(); - PublicKey issuerPubKey = issuerCert.getPublicKey(); + for (Enumeration> issuerCerts = vec.elements(); + issuerCerts.hasMoreElements(); ) { + Pair issuerCert = issuerCerts.nextElement(); + PublicKey issuerPubKey = issuerCert.snd.getPublicKey(); try { - certToVerify.verify(issuerPubKey); + certToVerify.snd.verify(issuerPubKey); } catch (Exception e) { continue; } @@ -3541,10 +3693,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> hash) + Hashtable>> hash) throws Exception { for (Enumeration aliases = ks.aliases(); @@ -3553,13 +3706,19 @@ Certificate cert = ks.getCertificate(alias); if (cert != null) { Principal subjectDN = ((X509Certificate)cert).getSubjectDN(); - Vector vec = hash.get(subjectDN); + Pair pair = new Pair<>( + ks == caks ? + (String.format(rb.getString("alias.in.cacerts"), + alias)) : + ("<" + alias + ">"), + (X509Certificate)cert); + Vector> vec = hash.get(subjectDN); if (vec == null) { - vec = new Vector(); - 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); @@ -4157,6 +4316,89 @@ return result; } + private void checkWeak(String label, String sigAlg, Key key) { + + if (!DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, sigAlg, null)) { + if (label == null) { + weakWarnings.add(String.format( + rb.getString("sigalg.risk"), sigAlg)); + } else { + weakWarnings.add(String.format( + rb.getString("whose.sigalg.risk"), label, sigAlg)); + } + } + if (key != null && !DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, key)) { + if (label == null) { + weakWarnings.add(String.format( + rb.getString("key.risk"), + String.format(rb.getString("key.bit"), + KeyUtil.getKeySize(key), key.getAlgorithm()))); + } else { + 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(i, certs.length); + if (label != null) { + fullLabel = String.format(rb.getString("label.which"), label, fullLabel); + } + } + 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 printWeakWarningsWithoutNewLine() { + if (!weakWarnings.isEmpty() && !nowarn) { + System.err.println("\nWarning:"); + for (String warning : weakWarnings) { + System.err.println(warning); + } + } + weakWarnings.clear(); + } + + private void printWeakWarnings() { + if (!weakWarnings.isEmpty() && !nowarn) { + System.err.println("\nWarning:"); + for (String warning : weakWarnings) { + System.err.println(warning); + } + System.err.println(); + } + weakWarnings.clear(); + } + /** * Prints the usage of this tool. */