8171319: keytool should print out warnings when reading or generating cert/cert req using weak algorithms
Reviewed-by: coffeys
This commit is contained in:
parent
080a88360a
commit
6b3143e831
@ -167,7 +167,8 @@ public class PKCS10 {
|
||||
// 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 @@ public class PKCS10 {
|
||||
signature.update(certificateRequestInfo, 0,
|
||||
certificateRequestInfo.length);
|
||||
sig = signature.sign();
|
||||
sigAlg = signature.getAlgorithm();
|
||||
|
||||
/*
|
||||
* Build guts of SIGNED macro
|
||||
@ -250,6 +252,11 @@ public class PKCS10 {
|
||||
public PublicKey getSubjectPublicKeyInfo()
|
||||
{ return subjectPublicKeyInfo; }
|
||||
|
||||
/**
|
||||
* Returns the signature algorithm.
|
||||
*/
|
||||
public String getSigAlg() { return sigAlg; }
|
||||
|
||||
/**
|
||||
* Returns the additional attributes requested.
|
||||
*/
|
||||
@ -348,6 +355,7 @@ public class PKCS10 {
|
||||
|
||||
private X500Name subject;
|
||||
private PublicKey subjectPublicKeyInfo;
|
||||
private String sigAlg;
|
||||
private PKCS10Attributes attributeSet;
|
||||
private byte[] encoded; // signed
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ import sun.security.util.Debug;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -125,7 +125,7 @@ class BasicChecker extends PKIXCertPathChecker {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@ -141,7 +141,7 @@ class BasicChecker extends PKIXCertPathChecker {
|
||||
X509Certificate currCert = (X509Certificate)cert;
|
||||
|
||||
if (!sigOnly) {
|
||||
verifyTimestamp(currCert);
|
||||
verifyValidity(currCert);
|
||||
verifyNameChaining(currCert);
|
||||
}
|
||||
verifySignature(currCert);
|
||||
@ -177,12 +177,12 @@ class BasicChecker extends PKIXCertPathChecker {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() + "...");
|
||||
|
||||
|
@ -27,6 +27,7 @@ package sun.security.tools.keytool;
|
||||
|
||||
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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
printWeakWarnings(false);
|
||||
for (char[] pass : passwords) {
|
||||
if (pass != null) {
|
||||
Arrays.fill(pass, ' ');
|
||||
@ -476,6 +489,8 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
} 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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkWeak(rb.getString("the.issuer"), keyStore.getCertificateChain(alias));
|
||||
checkWeak(rb.getString("the.generated.certificate"), cert);
|
||||
}
|
||||
|
||||
private void doGenCRL(PrintStream out)
|
||||
@ -1385,6 +1411,7 @@ public final class Main {
|
||||
} else {
|
||||
out.write(crl.getEncodedInternal());
|
||||
}
|
||||
checkWeak(rb.getString("the.generated.crl"), crl, privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1431,6 +1458,8 @@ public final class Main {
|
||||
// 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 @@ public final class Main {
|
||||
{
|
||||
if (storePass == null
|
||||
&& !KeyStoreUtil.isWindowsKeyStore(storetype)) {
|
||||
printWarning();
|
||||
printNoIntegrityWarning();
|
||||
}
|
||||
if (alias == null) {
|
||||
alias = keyAlias;
|
||||
@ -1474,6 +1503,7 @@ public final class Main {
|
||||
throw new Exception(form.format(source));
|
||||
}
|
||||
dumpCert(cert, out);
|
||||
checkWeak(rb.getString("the.certificate"), cert);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1729,6 +1759,8 @@ public final class Main {
|
||||
keyPass = promptForKeyPass(alias, null, storePass);
|
||||
}
|
||||
keyStore.setKeyEntry(alias, privKey, keyPass, chain);
|
||||
|
||||
checkWeak(rb.getString("the.generated.certificate"), chain[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1810,7 +1842,7 @@ public final class Main {
|
||||
/**
|
||||
* 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 @@ public final class Main {
|
||||
} 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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
|
||||
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 @@ public final class Main {
|
||||
"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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
if (debug) {
|
||||
out.println(req); // Just to see more, say, public key length...
|
||||
}
|
||||
checkWeak(rb.getString("the.certificate.request"), req);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2425,6 +2485,15 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
printX509Cert(x, out);
|
||||
}
|
||||
out.println();
|
||||
checkWeak(oneInMany(rb.getString("the.tsa.certificate"), cc++, certs.size()), x);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2523,6 +2600,7 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
}
|
||||
|
||||
// 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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
("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 @@ public final class Main {
|
||||
("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 @@ public final class Main {
|
||||
// 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 @@ public final class Main {
|
||||
}
|
||||
}
|
||||
|
||||
// Not found in this keystore and not self-signed
|
||||
// Try to establish trust chain
|
||||
try {
|
||||
Certificate[] chain = establishCertChain(null, cert);
|
||||
@ -2795,6 +2881,8 @@ public final class Main {
|
||||
// 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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
{
|
||||
|
||||
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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
* @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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
/**
|
||||
* 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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
|
||||
// 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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
// 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 @@ public final class Main {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public final class Main {
|
||||
*
|
||||
* @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 @@ public final class Main {
|
||||
// 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 @@ public final class Main {
|
||||
/**
|
||||
* 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 @@ public final class Main {
|
||||
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 @@ public final class Main {
|
||||
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.
|
||||
*/
|
||||
|
@ -360,8 +360,6 @@ public class Resources extends java.util.ListResourceBundle {
|
||||
{"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 @@ public class Resources extends java.util.ListResourceBundle {
|
||||
{"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 @@ public class Resources extends java.util.ListResourceBundle {
|
||||
"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."},
|
||||
};
|
||||
|
||||
|
||||
|
@ -536,13 +536,18 @@ public class X509CRLImpl extends X509CRL implements DerEncoder {
|
||||
* @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) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("X.509 CRL v")
|
||||
.append(version+1)
|
||||
.append('\n');
|
||||
if (sigAlgId != null)
|
||||
sb.append("Signature Algorithm: ")
|
||||
.append(sigAlgId)
|
||||
.append(name)
|
||||
.append(", OID=")
|
||||
.append(sigAlgId.getOID())
|
||||
.append('\n');
|
||||
|
@ -43,6 +43,7 @@ import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import jdk.test.lib.SecurityTools;
|
||||
import jdk.testlibrary.*;
|
||||
import jdk.testlibrary.JarUtils;
|
||||
import sun.security.pkcs.ContentInfo;
|
||||
@ -66,6 +67,7 @@ import sun.security.x509.X500Name;
|
||||
* java.base/sun.security.util
|
||||
* java.base/sun.security.tools.keytool
|
||||
* @library /lib/testlibrary
|
||||
* @library /test/lib
|
||||
* @run main/timeout=600 TimestampCheck
|
||||
*/
|
||||
public class TimestampCheck {
|
||||
@ -457,6 +459,18 @@ public class TimestampCheck {
|
||||
verify(file, "-J-Djava.security.debug=jar")
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldMatch("SignatureException:.*disabled");
|
||||
|
||||
// For 8171319: keytool should print out warnings when reading or
|
||||
// generating cert/cert req using weak algorithms.
|
||||
// Must call keytool the command, otherwise doPrintCert() might not
|
||||
// be able to reset "jdk.certpath.disabledAlgorithms".
|
||||
String sout = SecurityTools.keytool("-printcert -jarfile weak.jar")
|
||||
.stderrShouldContain("The TSA certificate uses a 512-bit RSA key" +
|
||||
" which is considered a security risk.")
|
||||
.getStdout();
|
||||
if (sout.indexOf("weak", sout.indexOf("Timestamp:")) < 0) {
|
||||
throw new RuntimeException("timestamp not weak: " + sout);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkHalfWeak(String file) throws Throwable {
|
||||
|
557
jdk/test/sun/security/tools/keytool/WeakAlg.java
Normal file
557
jdk/test/sun/security/tools/keytool/WeakAlg.java
Normal file
@ -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));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user