# HG changeset patch # User weijun # Date 1511199845 0 # Node ID d2ce6ebdf7fbea99759d631293668b53ef486667 # Parent c763d3f1b018da11d8af2f834298d759c7f33cb9 8171319: keytool should print out warnings when reading or generating cert/cert req using weak algorithms Reviewed-by: coffeys diff -r c763d3f1b018 -r d2ce6ebdf7fb src/share/classes/sun/security/pkcs10/PKCS10.java --- 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 } diff -r c763d3f1b018 -r d2ce6ebdf7fb src/share/classes/sun/security/provider/certpath/BasicChecker.java --- 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() + "..."); diff -r c763d3f1b018 -r d2ce6ebdf7fb src/share/classes/sun/security/tools/keytool/Main.java --- 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 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, @@ -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 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 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 cert itself if it's already inside ks, * or a certificate inside ks who signs cert, - * or null otherwise. + * or null otherwise. A label is added. */ - private static Certificate getTrustedSigner(Certificate cert, KeyStore ks) - throws Exception { + private static Pair + getTrustedSigner(Certificate cert, KeyStore ks) throws Exception { if (ks.getCertificateAlias(cert) != null) { - return cert; + return new Pair<>("", cert); } for (Enumeration 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 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> 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); + } + 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 chain, - Hashtable> certs) { - Principal issuer = certToVerify.getIssuerDN(); - if (isSelfSigned(certToVerify)) { + private boolean buildChain(Pair certToVerify, + Vector> chain, + Hashtable>> 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 vec = certs.get(issuer); + Vector> 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 issuerCerts = vec.elements(); + for (Enumeration> issuerCerts = vec.elements(); issuerCerts.hasMoreElements(); ) { - X509Certificate issuerCert - = (X509Certificate)issuerCerts.nextElement(); - PublicKey issuerPubKey = issuerCert.getPublicKey(); + Pair 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> hash) + Hashtable>> hash) throws Exception { for (Enumeration aliases = ks.aliases(); @@ -3386,13 +3520,20 @@ Certificate cert = ks.getCertificate(alias); if (cert != null) { Principal subjectDN = ((X509Certificate)cert).getSubjectDN(); - Vector vec = hash.get(subjectDN); + Pair pair = new Pair<>( + String.format( + rb.getString(ks == caks ? + "alias.in.cacerts" : + "alias.in.keystore"), + 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); @@ -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. */ diff -r c763d3f1b018 -r d2ce6ebdf7fb src/share/classes/sun/security/tools/keytool/Resources.java --- 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."}, }; diff -r c763d3f1b018 -r d2ce6ebdf7fb src/share/classes/sun/security/x509/X509CRLImpl.java --- 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"); diff -r c763d3f1b018 -r d2ce6ebdf7fb test/sun/security/tools/keytool/WeakAlg.java --- /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(".*MD5withRSA.*risk") + .shouldMatch(".*512-bit RSA key.*risk"); + kt("-list -v") + .shouldContain("Warning:") + .shouldMatch(".*MD5withRSA.*risk") + .shouldContain("MD5withRSA (weak)") + .shouldMatch(".*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(".*512-bit RSA key.*risk") + .shouldMatch(".*MD5withRSA.*risk"); + + rm("ks"); + kt("-importkeystore -srckeystore ks2 -srcstorepass changeit -srcalias a") + .shouldContain("Warning") + .shouldMatch(".*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 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 .*MD5withRSA.*risk") + .shouldNotContain("[no]"); + + reStore(); + gencert("b-a", ""); + + kt("-importcert -alias a -file b-a.cert") + .shouldContain("Warning") + .shouldMatch("Issuer .*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)); + } +}