--- a/jdk/src/java.base/share/classes/sun/security/tools/keytool/Main.java Tue Mar 07 16:02:20 2017 +0300
+++ b/jdk/src/java.base/share/classes/sun/security/tools/keytool/Main.java Tue Mar 07 22:42:11 2017 +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<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,
@@ -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 {
+ printWeakWarnings(false);
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(rb.getString("the.certificate"), 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.certificate.request"), 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.generated.certificate"), cert);
}
private void doGenCRL(PrintStream out)
@@ -1385,6 +1411,7 @@
} else {
out.write(crl.getEncodedInternal());
}
+ checkWeak(rb.getString("the.generated.crl"), crl, privateKey);
}
/**
@@ -1431,6 +1458,8 @@
// Sign the request and base-64 encode it
request.encodeAndSign(subject, signature);
request.print(out);
+
+ checkWeak(rb.getString("the.generated.certificate.request"), 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(rb.getString("the.certificate"), cert);
}
/**
@@ -1729,6 +1759,8 @@
keyPass = promptForKeyPass(alias, null, storePass);
}
keyStore.setKeyEntry(alias, privKey, keyPass, chain);
+
+ checkWeak(rb.getString("the.generated.certificate"), 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
@@ -2092,6 +2127,10 @@
"The.destination.pkcs12.keystore.has.different.storepass.and.keypass.Please.retry.with.destkeypass.specified."));
}
}
+ Certificate c = srckeystore.getCertificate(alias);
+ if (c != null) {
+ checkWeak("<" + newAlias + ">", c);
+ }
return 1;
} catch (KeyStoreException kse) {
Object[] source2 = {alias, kse.toString()};
@@ -2154,7 +2193,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
@@ -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,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-----");
out.println(Base64.getMimeEncoder(64, CRLF).encodeToString(xcrl.getEncoded()));
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);
}
}
@@ -2362,8 +2418,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 +2445,7 @@
if (debug) {
out.println(req); // Just to see more, say, public key length...
}
+ checkWeak(rb.getString("the.certificate.request"), req);
}
/**
@@ -2425,6 +2485,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);
}
}
@@ -2458,7 +2527,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");
@@ -2467,12 +2540,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");
@@ -2481,6 +2557,7 @@
printX509Cert(x, out);
}
out.println();
+ checkWeak(oneInMany(rb.getString("the.tsa.certificate"), cc++, certs.size()), x);
}
}
}
@@ -2523,6 +2600,7 @@
printX509Cert((X509Certificate)cert, out);
out.println();
}
+ checkWeak(oneInMany(rb.getString("the.certificate"), i, chain.size()), cert);
} catch (Exception e) {
if (debug) {
e.printStackTrace();
@@ -2698,7 +2776,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 +2813,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 (KeyStoreUtil.isSelfSigned(cert)) {
@@ -2742,11 +2826,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 +2834,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) {
@@ -2764,6 +2845,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."));
}
@@ -2771,6 +2854,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."));
}
@@ -2784,6 +2869,7 @@
}
}
+ // Not found in this keystore and not self-signed
// Try to establish trust chain
try {
Certificate[] chain = establishCertChain(null, cert);
@@ -2795,6 +2881,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)) {
@@ -2933,6 +3021,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 +3047,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 +3056,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));
@@ -3003,12 +3108,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(); ) {
@@ -3017,7 +3122,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
}
@@ -3281,7 +3386,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 +3411,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
@@ -3358,9 +3466,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();
@@ -3369,33 +3479,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)
@@ -3423,30 +3542,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;
@@ -3457,7 +3583,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 +3601,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 (KeyStoreUtil.isSelfSigned(certToVerify)) {
+ private boolean buildChain(Pair<String,X509Certificate> certToVerify,
+ Vector<Pair<String,X509Certificate>> chain,
+ Hashtable<Principal, Vector<Pair<String,X509Certificate>>> 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<Certificate> vec = certs.get(issuer);
+ Vector<Pair<String,X509Certificate>> vec = certs.get(issuer);
if (vec == null) {
return false;
}
@@ -3485,13 +3622,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();
- issuerCerts.hasMoreElements(); ) {
- X509Certificate issuerCert
- = (X509Certificate)issuerCerts.nextElement();
- PublicKey issuerPubKey = issuerCert.getPublicKey();
+ for (Enumeration<Pair<String,X509Certificate>> issuerCerts = vec.elements();
+ issuerCerts.hasMoreElements(); ) {
+ Pair<String,X509Certificate> issuerCert = issuerCerts.nextElement();
+ PublicKey issuerPubKey = issuerCert.snd.getPublicKey();
try {
- certToVerify.verify(issuerPubKey);
+ certToVerify.snd.verify(issuerPubKey);
} catch (Exception e) {
continue;
}
@@ -3541,10 +3677,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();
@@ -3553,13 +3690,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);
@@ -4157,6 +4301,67 @@
return result;
}
+ 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/jdk/src/java.base/share/classes/sun/security/tools/keytool/Resources.java Tue Mar 07 16:02:20 2017 +0300
+++ b/jdk/src/java.base/share/classes/sun/security/tools/keytool/Resources.java Tue Mar 07 22:42:11 2017 +0800
@@ -360,8 +360,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 SHA1: {5}\n\t SHA256: {6}\nSignature algorithm name: {7}\nSubject Public Key Algorithm: {8} ({9,number,#})\nVersion: {10}"},
{"What.is.your.first.and.last.name.",
"What is your first and last name?"},
{"What.is.the.name.of.your.organizational.unit.",
@@ -428,16 +426,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: "},
@@ -446,7 +440,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 SHA1: {5}\n\t SHA256: {6}\nSignature algorithm name: {7}\nSubject Public Key Algorithm: {8}\nVersion: {9}"},
+ {"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."},
};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/tools/keytool/WeakAlg.java Tue Mar 07 22:42:11 2017 +0800
@@ -0,0 +1,557 @@
+/*
+ * 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 /test/lib
+ * @modules java.base/sun.security.tools.keytool
+ * java.base/sun.security.tools
+ * java.base/sun.security.util
+ * @run main/othervm/timeout=600 -Duser.language=en -Duser.country=US WeakAlg
+ */
+
+import jdk.test.lib.SecurityTools;
+import jdk.test.lib.process.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;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+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 = Stream.of(input).collect(Collectors.joining("\n")) + "\n";
+ }
+ 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));
+ }
+}