提交 e81bcc45 编写于 作者: I igerasim

8180289: jarsigner treats timestamped signed jar invalid after the signer cert expires

Reviewed-by: mullan
上级 53c39c77
...@@ -175,11 +175,20 @@ public class Main { ...@@ -175,11 +175,20 @@ public class Main {
private boolean noTimestamp = false; private boolean noTimestamp = false;
private Date expireDate = new Date(0L); // used in noTimestamp warning private Date expireDate = new Date(0L); // used in noTimestamp warning
// Severe warnings // Severe warnings.
// jarsigner used to check signer cert chain validity and key usages
// itself and set various warnings. Later CertPath validation is
// added but chainNotValidated is only flagged when no other existing
// warnings are set. TSA cert chain check is added separately and
// only tsaChainNotValidated is set, i.e. has no affect on hasExpiredCert,
// notYetValidCert, or any badXyzUsage.
private int weakAlg = 0; // 1. digestalg, 2. sigalg, 4. tsadigestalg private int weakAlg = 0; // 1. digestalg, 2. sigalg, 4. tsadigestalg
private boolean hasExpiredCert = false; private boolean hasExpiredCert = false;
private boolean notYetValidCert = false; private boolean notYetValidCert = false;
private boolean chainNotValidated = false; private boolean chainNotValidated = false;
private boolean tsaChainNotValidated = false;
private boolean notSignedByAlias = false; private boolean notSignedByAlias = false;
private boolean aliasNotInStore = false; private boolean aliasNotInStore = false;
private boolean hasUnsignedEntry = false; private boolean hasUnsignedEntry = false;
...@@ -189,6 +198,7 @@ public class Main { ...@@ -189,6 +198,7 @@ public class Main {
private boolean signerSelfSigned = false; private boolean signerSelfSigned = false;
private Throwable chainNotValidatedReason = null; private Throwable chainNotValidatedReason = null;
private Throwable tsaChainNotValidatedReason = null;
private boolean seeWeak = false; private boolean seeWeak = false;
...@@ -279,7 +289,8 @@ public class Main { ...@@ -279,7 +289,8 @@ public class Main {
if (strict) { if (strict) {
int exitCode = 0; int exitCode = 0;
if (weakAlg != 0 || chainNotValidated || hasExpiredCert || notYetValidCert || signerSelfSigned) { if (weakAlg != 0 || chainNotValidated
|| hasExpiredCert || notYetValidCert || signerSelfSigned) {
exitCode |= 4; exitCode |= 4;
} }
if (badKeyUsage || badExtendedKeyUsage || badNetscapeCertType) { if (badKeyUsage || badExtendedKeyUsage || badNetscapeCertType) {
...@@ -291,6 +302,9 @@ public class Main { ...@@ -291,6 +302,9 @@ public class Main {
if (notSignedByAlias || aliasNotInStore) { if (notSignedByAlias || aliasNotInStore) {
exitCode |= 32; exitCode |= 32;
} }
if (tsaChainNotValidated) {
exitCode |= 64;
}
if (exitCode != 0) { if (exitCode != 0) {
System.exit(exitCode); System.exit(exitCode);
} }
...@@ -811,6 +825,9 @@ public class Main { ...@@ -811,6 +825,9 @@ public class Main {
System.out.println(rb.getString("no.manifest.")); System.out.println(rb.getString("no.manifest."));
} }
// If there is a time stamp block inside the PKCS7 block file
boolean hasTimestampBlock = false;
// Even if the verbose option is not specified, all out strings // Even if the verbose option is not specified, all out strings
// must be generated so seeWeak can be updated. // must be generated so seeWeak can be updated.
if (!digestMap.isEmpty() if (!digestMap.isEmpty()
...@@ -839,6 +856,7 @@ public class Main { ...@@ -839,6 +856,7 @@ public class Main {
PublicKey key = signer.getPublicKey(); PublicKey key = signer.getPublicKey();
PKCS7 tsToken = si.getTsToken(); PKCS7 tsToken = si.getTsToken();
if (tsToken != null) { if (tsToken != null) {
hasTimestampBlock = true;
SignerInfo tsSi = tsToken.getSignerInfos()[0]; SignerInfo tsSi = tsToken.getSignerInfos()[0];
X509Certificate tsSigner = tsSi.getCertificate(tsToken); X509Certificate tsSigner = tsSi.getCertificate(tsToken);
byte[] encTsTokenInfo = tsToken.getContentInfo().getData(); byte[] encTsTokenInfo = tsToken.getContentInfo().getData();
...@@ -921,7 +939,7 @@ public class Main { ...@@ -921,7 +939,7 @@ public class Main {
if (badKeyUsage || badExtendedKeyUsage || badNetscapeCertType || if (badKeyUsage || badExtendedKeyUsage || badNetscapeCertType ||
notYetValidCert || chainNotValidated || hasExpiredCert || notYetValidCert || chainNotValidated || hasExpiredCert ||
hasUnsignedEntry || signerSelfSigned || (weakAlg != 0) || hasUnsignedEntry || signerSelfSigned || (weakAlg != 0) ||
aliasNotInStore || notSignedByAlias) { aliasNotInStore || notSignedByAlias || tsaChainNotValidated) {
if (strict) { if (strict) {
System.out.println(rb.getString("jar.verified.with.signer.errors.")); System.out.println(rb.getString("jar.verified.with.signer.errors."));
...@@ -971,10 +989,16 @@ public class Main { ...@@ -971,10 +989,16 @@ public class Main {
if (chainNotValidated) { if (chainNotValidated) {
System.out.println(String.format( System.out.println(String.format(
rb.getString("This.jar.contains.entries.whose.certificate.chain.is.not.validated.reason.1"), rb.getString("This.jar.contains.entries.whose.certificate.chain.is.invalid.reason.1"),
chainNotValidatedReason.getLocalizedMessage())); chainNotValidatedReason.getLocalizedMessage()));
} }
if (tsaChainNotValidated) {
System.out.println(String.format(
rb.getString("This.jar.contains.entries.whose.tsa.certificate.chain.is.invalid.reason.1"),
tsaChainNotValidatedReason.getLocalizedMessage()));
}
if (notSignedByAlias) { if (notSignedByAlias) {
System.out.println( System.out.println(
rb.getString("This.jar.contains.signed.entries.which.is.not.signed.by.the.specified.alias.es.")); rb.getString("This.jar.contains.signed.entries.which.is.not.signed.by.the.specified.alias.es."));
...@@ -1002,10 +1026,17 @@ public class Main { ...@@ -1002,10 +1026,17 @@ public class Main {
"This.jar.contains.entries.whose.signer.certificate.will.expire.within.six.months.")); "This.jar.contains.entries.whose.signer.certificate.will.expire.within.six.months."));
} }
if (noTimestamp) { if (noTimestamp) {
if (hasTimestampBlock) {
// JarSigner API has not seen the timestamp,
// might have ignored it due to weak alg, etc.
System.out.println(
String.format(rb.getString("bad.timestamp.verifying"), expireDate));
} else {
System.out.println( System.out.println(
String.format(rb.getString("no.timestamp.verifying"), expireDate)); String.format(rb.getString("no.timestamp.verifying"), expireDate));
} }
} }
}
if (warningAppeared || errorAppeared) { if (warningAppeared || errorAppeared) {
if (! (verbose != null && showcerts)) { if (! (verbose != null && showcerts)) {
System.out.println(); System.out.println();
...@@ -1054,16 +1085,23 @@ public class Main { ...@@ -1054,16 +1085,23 @@ public class Main {
private static MessageFormat expiredTimeForm = null; private static MessageFormat expiredTimeForm = null;
private static MessageFormat expiringTimeForm = null; private static MessageFormat expiringTimeForm = null;
/* /**
* Display some details about a certificate: * Returns a string about a certificate:
* *
* [<tab>] <cert-type> [", " <subject-DN>] [" (" <keystore-entry-alias> ")"] * [<tab>] <cert-type> [", " <subject-DN>] [" (" <keystore-entry-alias> ")"]
* [<validity-period> | <expiry-warning>] * [<validity-period> | <expiry-warning>]
* [<key-usage-warning>]
* *
* Note: no newline character at the end * Note: no newline character at the end.
*
* When isTsCert is true, this method sets global flags like hasExpiredCert,
* notYetValidCert, badKeyUsage, badExtendedKeyUsage, badNetscapeCertType.
*
* @param isTsCert true if c is in the TSA cert chain, false otherwise.
* @param checkUsage true to check code signer keyUsage
*/ */
String printCert(String tab, Certificate c, boolean checkValidityPeriod, String printCert(boolean isTsCert, String tab, Certificate c,
Date timestamp, boolean checkUsage) { Date timestamp, boolean checkUsage) throws Exception {
StringBuilder certStr = new StringBuilder(); StringBuilder certStr = new StringBuilder();
String space = rb.getString("SPACE"); String space = rb.getString("SPACE");
...@@ -1083,7 +1121,7 @@ public class Main { ...@@ -1083,7 +1121,7 @@ public class Main {
certStr.append(space).append(alias); certStr.append(space).append(alias);
} }
if (checkValidityPeriod && x509Cert != null) { if (x509Cert != null) {
certStr.append("\n").append(tab).append("["); certStr.append("\n").append(tab).append("[");
Date notAfter = x509Cert.getNotAfter(); Date notAfter = x509Cert.getNotAfter();
...@@ -1096,7 +1134,7 @@ public class Main { ...@@ -1096,7 +1134,7 @@ public class Main {
x509Cert.checkValidity(); x509Cert.checkValidity();
// test if cert will expire within six months // test if cert will expire within six months
if (notAfter.getTime() < System.currentTimeMillis() + SIX_MONTHS) { if (notAfter.getTime() < System.currentTimeMillis() + SIX_MONTHS) {
hasExpiringCert = true; if (!isTsCert) hasExpiringCert = true;
if (expiringTimeForm == null) { if (expiringTimeForm == null) {
expiringTimeForm = new MessageFormat( expiringTimeForm = new MessageFormat(
rb.getString("certificate.will.expire.on")); rb.getString("certificate.will.expire.on"));
...@@ -1117,7 +1155,7 @@ public class Main { ...@@ -1117,7 +1155,7 @@ public class Main {
certStr.append(validityTimeForm.format(source)); certStr.append(validityTimeForm.format(source));
} }
} catch (CertificateExpiredException cee) { } catch (CertificateExpiredException cee) {
hasExpiredCert = true; if (!isTsCert) hasExpiredCert = true;
if (expiredTimeForm == null) { if (expiredTimeForm == null) {
expiredTimeForm = new MessageFormat( expiredTimeForm = new MessageFormat(
...@@ -1127,7 +1165,7 @@ public class Main { ...@@ -1127,7 +1165,7 @@ public class Main {
certStr.append(expiredTimeForm.format(source)); certStr.append(expiredTimeForm.format(source));
} catch (CertificateNotYetValidException cnyve) { } catch (CertificateNotYetValidException cnyve) {
notYetValidCert = true; if (!isTsCert) notYetValidCert = true;
if (notYetTimeForm == null) { if (notYetTimeForm == null) {
notYetTimeForm = new MessageFormat( notYetTimeForm = new MessageFormat(
...@@ -1534,7 +1572,7 @@ public class Main { ...@@ -1534,7 +1572,7 @@ public class Main {
tsaURI); tsaURI);
} }
System.out.println(rb.getString("TSA.certificate.") + System.out.println(rb.getString("TSA.certificate.") +
printCert("", tsaCert, false, null, false)); printCert(true, "", tsaCert, null, false));
} }
if (signingMechanism != null) { if (signingMechanism != null) {
System.out.println( System.out.println(
...@@ -1597,6 +1635,30 @@ public class Main { ...@@ -1597,6 +1635,30 @@ public class Main {
} }
} }
// The JarSigner API always accepts the timestamp received.
// We need to extract the certs from the signed jar to
// validate it.
if (!noTimestamp) {
try (JarFile check = new JarFile(signedJarFile)) {
PKCS7 p7 = new PKCS7(check.getInputStream(check.getEntry(
"META-INF/" + sigfile + "." + privateKey.getAlgorithm())));
SignerInfo si = p7.getSignerInfos()[0];
PKCS7 tsToken = si.getTsToken();
SignerInfo tsSi = tsToken.getSignerInfos()[0];
try {
validateCertChain(Validator.VAR_TSA_SERVER,
tsSi.getCertificateChain(tsToken), null);
} catch (Exception e) {
tsaChainNotValidated = true;
tsaChainNotValidatedReason = e;
}
} catch (Exception e) {
if (debug) {
e.printStackTrace();
}
}
}
// no IOException thrown in the follow try clause, so disable // no IOException thrown in the follow try clause, so disable
// the try clause. // the try clause.
// try { // try {
...@@ -1626,8 +1688,10 @@ public class Main { ...@@ -1626,8 +1688,10 @@ public class Main {
} }
boolean warningAppeared = false; boolean warningAppeared = false;
if (weakAlg != 0 || badKeyUsage || badExtendedKeyUsage || badNetscapeCertType || if (weakAlg != 0 || badKeyUsage || badExtendedKeyUsage
notYetValidCert || chainNotValidated || hasExpiredCert || signerSelfSigned) { || badNetscapeCertType || notYetValidCert
|| chainNotValidated || tsaChainNotValidated
|| hasExpiredCert || signerSelfSigned) {
if (strict) { if (strict) {
System.out.println(rb.getString("jar.signed.with.signer.errors.")); System.out.println(rb.getString("jar.signed.with.signer.errors."));
System.out.println(); System.out.println();
...@@ -1664,10 +1728,16 @@ public class Main { ...@@ -1664,10 +1728,16 @@ public class Main {
if (chainNotValidated) { if (chainNotValidated) {
System.out.println(String.format( System.out.println(String.format(
rb.getString("The.signer.s.certificate.chain.is.not.validated.reason.1"), rb.getString("The.signer.s.certificate.chain.is.invalid.reason.1"),
chainNotValidatedReason.getLocalizedMessage())); chainNotValidatedReason.getLocalizedMessage()));
} }
if (tsaChainNotValidated) {
System.out.println(String.format(
rb.getString("The.tsa.certificate.chain.is.invalid.reason.1"),
tsaChainNotValidatedReason.getLocalizedMessage()));
}
if (signerSelfSigned) { if (signerSelfSigned) {
System.out.println( System.out.println(
rb.getString("The.signer.s.certificate.is.self.signed.")); rb.getString("The.signer.s.certificate.is.self.signed."));
...@@ -1763,18 +1833,18 @@ public class Main { ...@@ -1763,18 +1833,18 @@ public class Main {
/** /**
* Returns a string of singer info, with a newline at the end * Returns a string of singer info, with a newline at the end
*/ */
private String signerInfo(CodeSigner signer, String tab) { private String signerInfo(CodeSigner signer, String tab) throws Exception {
if (cacheForSignerInfo.containsKey(signer)) { if (cacheForSignerInfo.containsKey(signer)) {
return cacheForSignerInfo.get(signer); return cacheForSignerInfo.get(signer);
} }
StringBuffer s = new StringBuffer(); StringBuilder sb = new StringBuilder();
List<? extends Certificate> certs = signer.getSignerCertPath().getCertificates(); List<? extends Certificate> certs = signer.getSignerCertPath().getCertificates();
// display the signature timestamp, if present // display the signature timestamp, if present
Date timestamp; Date timestamp;
Timestamp ts = signer.getTimestamp(); Timestamp ts = signer.getTimestamp();
if (ts != null) { if (ts != null) {
s.append(printTimestamp(tab, ts)); sb.append(printTimestamp(tab, ts));
s.append('\n'); sb.append('\n');
timestamp = ts.getTimestamp(); timestamp = ts.getTimestamp();
} else { } else {
timestamp = null; timestamp = null;
...@@ -1783,24 +1853,41 @@ public class Main { ...@@ -1783,24 +1853,41 @@ public class Main {
// display the certificate(s). The first one is end-entity cert and // display the certificate(s). The first one is end-entity cert and
// its KeyUsage should be checked. // its KeyUsage should be checked.
boolean first = true; boolean first = true;
sb.append(tab).append(rb.getString("...Signer")).append('\n');
for (Certificate c : certs) { for (Certificate c : certs) {
s.append(printCert(tab, c, true, timestamp, first)); sb.append(printCert(false, tab, c, timestamp, first));
s.append('\n'); sb.append('\n');
first = false; first = false;
} }
try { try {
validateCertChain(certs); validateCertChain(Validator.VAR_CODE_SIGNING, certs, ts);
} catch (Exception e) { } catch (Exception e) {
chainNotValidated = true; chainNotValidated = true;
chainNotValidatedReason = e; chainNotValidatedReason = e;
s.append(tab).append(rb.getString(".CertPath.not.validated.")) sb.append(tab).append(rb.getString(".Invalid.certificate.chain."))
.append(e.getLocalizedMessage()).append("]\n"); // TODO .append(e.getLocalizedMessage()).append("]\n");
}
if (ts != null) {
sb.append(tab).append(rb.getString("...TSA")).append('\n');
for (Certificate c : ts.getSignerCertPath().getCertificates()) {
sb.append(printCert(true, tab, c, timestamp, false));
sb.append('\n');
}
try {
validateCertChain(Validator.VAR_TSA_SERVER,
ts.getSignerCertPath().getCertificates(), null);
} catch (Exception e) {
tsaChainNotValidated = true;
tsaChainNotValidatedReason = e;
sb.append(tab).append(rb.getString(".Invalid.TSA.certificate.chain."))
.append(e.getLocalizedMessage()).append("]\n");
}
} }
if (certs.size() == 1 if (certs.size() == 1
&& KeyStoreUtil.isSelfSigned((X509Certificate)certs.get(0))) { && KeyStoreUtil.isSelfSigned((X509Certificate)certs.get(0))) {
signerSelfSigned = true; signerSelfSigned = true;
} }
String result = s.toString(); String result = sb.toString();
cacheForSignerInfo.put(signer, result); cacheForSignerInfo.put(signer, result);
return result; return result;
} }
...@@ -2043,7 +2130,7 @@ public class Main { ...@@ -2043,7 +2130,7 @@ public class Main {
} }
} }
void getAliasInfo(String alias) { void getAliasInfo(String alias) throws Exception {
Key key = null; Key key = null;
...@@ -2089,10 +2176,11 @@ public class Main { ...@@ -2089,10 +2176,11 @@ public class Main {
// We don't meant to print anything, the next call // We don't meant to print anything, the next call
// checks validity and keyUsage etc // checks validity and keyUsage etc
printCert("", certChain[0], true, null, true); printCert(false, "", certChain[0], null, true);
try { try {
validateCertChain(Arrays.asList(certChain)); validateCertChain(Validator.VAR_CODE_SIGNING,
Arrays.asList(certChain), null);
} catch (Exception e) { } catch (Exception e) {
chainNotValidated = true; chainNotValidated = true;
chainNotValidatedReason = e; chainNotValidatedReason = e;
...@@ -2153,17 +2241,31 @@ public class Main { ...@@ -2153,17 +2241,31 @@ public class Main {
System.exit(1); System.exit(1);
} }
void validateCertChain(List<? extends Certificate> certs) throws Exception { /**
* Validates a cert chain.
*
* @param parameter this might be a timestamp
*/
void validateCertChain(String variant, List<? extends Certificate> certs,
Object parameter)
throws Exception {
try { try {
Validator.getInstance(Validator.TYPE_PKIX, Validator.getInstance(Validator.TYPE_PKIX,
Validator.VAR_CODE_SIGNING, variant,
pkixParameters) pkixParameters)
.validate(certs.toArray(new X509Certificate[certs.size()])); .validate(certs.toArray(new X509Certificate[certs.size()]),
null, parameter);
} catch (Exception e) { } catch (Exception e) {
if (debug) { if (debug) {
e.printStackTrace(); e.printStackTrace();
} }
if (e instanceof ValidatorException) {
// Exception might be dismissed if another warning flag
// is already set by printCert. This is only done for
// code signing certs.
if (variant.equals(Validator.VAR_CODE_SIGNING) &&
e instanceof ValidatorException) {
// Throw cause if it's CertPathValidatorException, // Throw cause if it's CertPathValidatorException,
if (e.getCause() != null && if (e.getCause() != null &&
e.getCause() instanceof CertPathValidatorException) { e.getCause() instanceof CertPathValidatorException) {
......
/* /*
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -199,7 +199,8 @@ public class Resources extends java.util.ListResourceBundle { ...@@ -199,7 +199,8 @@ public class Resources extends java.util.ListResourceBundle {
{"certificate.is.not.valid.until", {"certificate.is.not.valid.until",
"certificate is not valid until {0}"}, "certificate is not valid until {0}"},
{"certificate.will.expire.on", "certificate will expire on {0}"}, {"certificate.will.expire.on", "certificate will expire on {0}"},
{".CertPath.not.validated.", "[CertPath not validated: "}, {".Invalid.certificate.chain.", "[Invalid certificate chain: "},
{".Invalid.TSA.certificate.chain.", "[Invalid TSA certificate chain: "},
{"requesting.a.signature.timestamp", {"requesting.a.signature.timestamp",
"requesting a signature timestamp"}, "requesting a signature timestamp"},
{"TSA.location.", "TSA location: "}, {"TSA.location.", "TSA location: "},
...@@ -216,6 +217,8 @@ public class Resources extends java.util.ListResourceBundle { ...@@ -216,6 +217,8 @@ public class Resources extends java.util.ListResourceBundle {
{"entry.was.signed.on", "entry was signed on {0}"}, {"entry.was.signed.on", "entry was signed on {0}"},
{"Warning.", "Warning: "}, {"Warning.", "Warning: "},
{"Error.", "Error: "}, {"Error.", "Error: "},
{"...Signer", ">>> Signer"},
{"...TSA", ">>> TSA"},
{"This.jar.contains.unsigned.entries.which.have.not.been.integrity.checked.", {"This.jar.contains.unsigned.entries.which.have.not.been.integrity.checked.",
"This jar contains unsigned entries which have not been integrity-checked. "}, "This jar contains unsigned entries which have not been integrity-checked. "},
{"This.jar.contains.entries.whose.signer.certificate.has.expired.", {"This.jar.contains.entries.whose.signer.certificate.has.expired.",
...@@ -250,18 +253,24 @@ public class Resources extends java.util.ListResourceBundle { ...@@ -250,18 +253,24 @@ public class Resources extends java.util.ListResourceBundle {
"This jar contains entries whose signer certificate's NetscapeCertType extension doesn't allow code signing."}, "This jar contains entries whose signer certificate's NetscapeCertType extension doesn't allow code signing."},
{".{0}.extension.does.not.support.code.signing.", {".{0}.extension.does.not.support.code.signing.",
"[{0} extension does not support code signing]"}, "[{0} extension does not support code signing]"},
{"The.signer.s.certificate.chain.is.not.validated.reason.1", {"The.signer.s.certificate.chain.is.invalid.reason.1",
"The signer's certificate chain is not validated. Reason: %s"}, "The signer's certificate chain is invalid. Reason: %s"},
{"The.tsa.certificate.chain.is.invalid.reason.1",
"The TSA certificate chain is invalid. Reason: %s"},
{"The.signer.s.certificate.is.self.signed.", {"The.signer.s.certificate.is.self.signed.",
"The signer's certificate is self-signed."}, "The signer's certificate is self-signed."},
{"The.1.algorithm.specified.for.the.2.option.is.considered.a.security.risk.", {"The.1.algorithm.specified.for.the.2.option.is.considered.a.security.risk.",
"The %1$s algorithm specified for the %2$s option is considered a security risk."}, "The %1$s algorithm specified for the %2$s option is considered a security risk."},
{"This.jar.contains.entries.whose.certificate.chain.is.not.validated.reason.1", {"This.jar.contains.entries.whose.certificate.chain.is.invalid.reason.1",
"This jar contains entries whose certificate chain is not validated. Reason: %s"}, "This jar contains entries whose certificate chain is invalid. Reason: %s"},
{"This.jar.contains.entries.whose.tsa.certificate.chain.is.invalid.reason.1",
"This jar contains entries whose TSA certificate chain is invalid. Reason: %s"},
{"no.timestamp.signing", {"no.timestamp.signing",
"No -tsa or -tsacert is provided and this jar is not timestamped. Without a timestamp, users may not be able to validate this jar after the signer certificate's expiration date (%1$tY-%1$tm-%1$td) or after any future revocation date."}, "No -tsa or -tsacert is provided and this jar is not timestamped. Without a timestamp, users may not be able to validate this jar after the signer certificate's expiration date (%1$tY-%1$tm-%1$td) or after any future revocation date."},
{"no.timestamp.verifying", {"no.timestamp.verifying",
"This jar contains signatures that does not include a timestamp. Without a timestamp, users may not be able to validate this jar after the signer certificate's expiration date (%1$tY-%1$tm-%1$td) or after any future revocation date."}, "This jar contains signatures that does not include a timestamp. Without a timestamp, users may not be able to validate this jar after the signer certificate's expiration date (%1$tY-%1$tm-%1$td) or after any future revocation date."},
{"bad.timestamp.verifying",
"This jar contains signatures that include an invalid timestamp. Without a valid timestamp, users may not be able to validate this jar after any of the signer certificates expire (as early as %1$tY-%1$tm-%1$td).\nRerun jarsigner with -J-Djava.security.debug=jar for more information."},
{"Unknown.password.type.", "Unknown password type: "}, {"Unknown.password.type.", "Unknown password type: "},
{"Cannot.find.environment.variable.", {"Cannot.find.environment.variable.",
"Cannot find environment variable: "}, "Cannot find environment variable: "},
......
...@@ -718,7 +718,8 @@ public class SignatureFileVerifier { ...@@ -718,7 +718,8 @@ public class SignatureFileVerifier {
if (signers == null) { if (signers == null) {
signers = new ArrayList<>(); signers = new ArrayList<>();
} }
// Append the new code signer // Append the new code signer. If timestamp is invalid, this
// jar will be treated as unsigned.
signers.add(new CodeSigner(certChain, info.getTimestamp())); signers.add(new CodeSigner(certChain, info.getTimestamp()));
if (debug != null) { if (debug != null) {
......
...@@ -49,10 +49,7 @@ public class SecurityTools { ...@@ -49,10 +49,7 @@ public class SecurityTools {
launcher.addToolArg(arg); launcher.addToolArg(arg);
} }
} }
String[] cmds = launcher.getCommand(); return new ProcessBuilder(launcher.getCommand());
String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" "));
System.out.println("Command line: [" + cmdLine + "]");
return new ProcessBuilder(cmds);
} }
// keytool // keytool
...@@ -69,7 +66,7 @@ public class SecurityTools { ...@@ -69,7 +66,7 @@ public class SecurityTools {
pb.redirectInput(ProcessBuilder.Redirect.from(new File(RESPONSE_FILE))); pb.redirectInput(ProcessBuilder.Redirect.from(new File(RESPONSE_FILE)));
try { try {
return ProcessTools.executeProcess(pb); return execute(pb);
} catch (Throwable t) { } catch (Throwable t) {
throw new RuntimeException("keytool failure: " + t); throw new RuntimeException("keytool failure: " + t);
} finally { } finally {
...@@ -101,11 +98,20 @@ public class SecurityTools { ...@@ -101,11 +98,20 @@ public class SecurityTools {
public static OutputAnalyzer jarsigner(List<String> args) public static OutputAnalyzer jarsigner(List<String> args)
throws Exception { throws Exception {
return execute(getProcessBuilder("jarsigner", args));
}
private static OutputAnalyzer execute(ProcessBuilder pb) throws Exception {
try { try {
return ProcessTools.executeProcess( OutputAnalyzer oa = ProcessTools.executeCommand(pb);
getProcessBuilder("jarsigner", args)); System.out.println("Exit value: " + oa.getExitValue());
return oa;
} catch (Throwable t) { } catch (Throwable t) {
throw new RuntimeException("jarsigner error: " + t); if (t instanceof Exception) {
throw (Exception) t;
} else {
throw new Exception(t);
}
} }
} }
......
/* /*
* Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -22,6 +22,8 @@ ...@@ -22,6 +22,8 @@
*/ */
import com.sun.net.httpserver.*; import com.sun.net.httpserver.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
...@@ -36,16 +38,18 @@ import java.security.KeyStore; ...@@ -36,16 +38,18 @@ import java.security.KeyStore;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.Signature; import java.security.Signature;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.time.Instant;
import java.util.Arrays; import java.time.temporal.ChronoUnit;
import java.util.Calendar; import java.util.*;
import java.util.List;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import sun.misc.IOUtils; import sun.misc.IOUtils;
import jdk.testlibrary.*; import jdk.testlibrary.SecurityTools;
import jdk.testlibrary.OutputAnalyzer;
import jdk.testlibrary.JarUtils; import jdk.testlibrary.JarUtils;
import sun.security.pkcs.ContentInfo; import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7; import sun.security.pkcs.PKCS7;
...@@ -58,9 +62,12 @@ import sun.security.util.ObjectIdentifier; ...@@ -58,9 +62,12 @@ import sun.security.util.ObjectIdentifier;
import sun.security.x509.AlgorithmId; import sun.security.x509.AlgorithmId;
import sun.security.x509.X500Name; import sun.security.x509.X500Name;
import jdk.testlibrary.Utils;
/* /*
* @test * @test
* @bug 6543842 6543440 6939248 8009636 8024302 8163304 8169911 8169688 8171121 * @bug 6543842 6543440 6939248 8009636 8024302 8163304 8169911 8169688 8171121
* 8180289
* @summary checking response of timestamp * @summary checking response of timestamp
* @modules java.base/sun.security.pkcs * @modules java.base/sun.security.pkcs
* java.base/sun.security.timestamp * java.base/sun.security.timestamp
...@@ -68,7 +75,7 @@ import sun.security.x509.X500Name; ...@@ -68,7 +75,7 @@ import sun.security.x509.X500Name;
* java.base/sun.security.util * java.base/sun.security.util
* java.base/sun.security.tools.keytool * java.base/sun.security.tools.keytool
* @library /lib/testlibrary * @library /lib/testlibrary
* @run main/timeout=600 TimestampCheck * @run main/othervm/timeout=600 TimestampCheck
*/ */
public class TimestampCheck { public class TimestampCheck {
...@@ -114,12 +121,12 @@ public class TimestampCheck { ...@@ -114,12 +121,12 @@ public class TimestampCheck {
*/ */
byte[] sign(byte[] input, String path) throws Exception { byte[] sign(byte[] input, String path) throws Exception {
DerValue value = new DerValue(input); DerValue value = new DerValue(input);
System.err.println("\nIncoming Request\n==================="); System.out.println("\nIncoming Request\n===================");
System.err.println("Version: " + value.data.getInteger()); System.out.println("Version: " + value.data.getInteger());
DerValue messageImprint = value.data.getDerValue(); DerValue messageImprint = value.data.getDerValue();
AlgorithmId aid = AlgorithmId.parse( AlgorithmId aid = AlgorithmId.parse(
messageImprint.data.getDerValue()); messageImprint.data.getDerValue());
System.err.println("AlgorithmId: " + aid); System.out.println("AlgorithmId: " + aid);
ObjectIdentifier policyId = new ObjectIdentifier(defaultPolicyId); ObjectIdentifier policyId = new ObjectIdentifier(defaultPolicyId);
BigInteger nonce = null; BigInteger nonce = null;
...@@ -127,25 +134,24 @@ public class TimestampCheck { ...@@ -127,25 +134,24 @@ public class TimestampCheck {
DerValue v = value.data.getDerValue(); DerValue v = value.data.getDerValue();
if (v.tag == DerValue.tag_Integer) { if (v.tag == DerValue.tag_Integer) {
nonce = v.getBigInteger(); nonce = v.getBigInteger();
System.err.println("nonce: " + nonce); System.out.println("nonce: " + nonce);
} else if (v.tag == DerValue.tag_Boolean) { } else if (v.tag == DerValue.tag_Boolean) {
System.err.println("certReq: " + v.getBoolean()); System.out.println("certReq: " + v.getBoolean());
} else if (v.tag == DerValue.tag_ObjectId) { } else if (v.tag == DerValue.tag_ObjectId) {
policyId = v.getOID(); policyId = v.getOID();
System.err.println("PolicyID: " + policyId); System.out.println("PolicyID: " + policyId);
} }
} }
System.err.println("\nResponse\n==================="); System.out.println("\nResponse\n===================");
FileInputStream is = new FileInputStream(keystore); FileInputStream is = new FileInputStream(keystore);
KeyStore ks = KeyStore.getInstance("JCEKS"); KeyStore ks = KeyStore.getInstance("JCEKS");
ks.load(is, "changeit".toCharArray()); ks.load(is, "changeit".toCharArray());
is.close(); is.close();
String alias = "ts"; // If path starts with "ts", use the TSA it points to.
if (path.startsWith("bad") || path.equals("weak")) { // Otherwise, always use "ts".
alias = "ts" + path; String alias = path.startsWith("ts") ? path : "ts";
}
if (path.equals("diffpolicy")) { if (path.equals("diffpolicy")) {
policyId = new ObjectIdentifier(defaultPolicyId); policyId = new ObjectIdentifier(defaultPolicyId);
...@@ -192,8 +198,11 @@ public class TimestampCheck { ...@@ -192,8 +198,11 @@ public class TimestampCheck {
tst.putInteger(1); tst.putInteger(1);
Calendar cal = Calendar.getInstance(); Instant instant = Instant.now();
tst.putGeneralizedTime(cal.getTime()); if (path.equals("tsold")) {
instant = instant.minus(20, ChronoUnit.DAYS);
}
tst.putGeneralizedTime(Date.from(instant));
if (path.equals("diffnonce")) { if (path.equals("diffnonce")) {
tst.putInteger(1234); tst.putInteger(1234);
...@@ -220,10 +229,10 @@ public class TimestampCheck { ...@@ -220,10 +229,10 @@ public class TimestampCheck {
"1.2.840.113549.1.9.16.1.4"), "1.2.840.113549.1.9.16.1.4"),
new DerValue(tstInfo2.toByteArray())); new DerValue(tstInfo2.toByteArray()));
System.err.println("Signing..."); System.out.println("Signing...");
System.err.println(new X500Name(signer System.out.println(new X500Name(signer
.getIssuerX500Principal().getName())); .getIssuerX500Principal().getName()));
System.err.println(signer.getSerialNumber()); System.out.println(signer.getSerialNumber());
SignerInfo signerInfo = new SignerInfo( SignerInfo signerInfo = new SignerInfo(
new X500Name(signer.getIssuerX500Principal().getName()), new X500Name(signer.getIssuerX500Principal().getName()),
...@@ -296,25 +305,53 @@ public class TimestampCheck { ...@@ -296,25 +305,53 @@ public class TimestampCheck {
prepare(); prepare();
try (Handler tsa = Handler.init(0, "tsks");) { try (Handler tsa = Handler.init(0, "ks");) {
tsa.start(); tsa.start();
int port = tsa.getPort(); int port = tsa.getPort();
host = "http://localhost:" + port + "/"; host = "http://localhost:" + port + "/";
if (args.length == 0) { // Run this test if (args.length == 0) { // Run this test
sign("none")
.shouldContain("is not timestamped") sign("normal")
.shouldNotContain("Warning")
.shouldHaveExitValue(0); .shouldHaveExitValue(0);
sign("badku") verify("normal.jar")
.shouldNotContain("Warning")
.shouldHaveExitValue(0); .shouldHaveExitValue(0);
checkBadKU("badku.jar");
sign("normal") // Simulate signing at a previous date:
.shouldNotContain("is not timestamped") // 1. tsold will create a timestamp of 20 days ago.
// 2. oldsigner expired 10 days ago.
// jarsigner will show a warning at signing.
signVerbose("tsold", "unsigned.jar", "tsold.jar", "oldsigner")
.shouldHaveExitValue(4);
// It verifies perfectly.
verify("tsold.jar", "-verbose", "-certs")
.shouldNotContain("Warning")
.shouldHaveExitValue(0); .shouldHaveExitValue(0);
signVerbose(null, "unsigned.jar", "none.jar", "signer")
.shouldContain("is not timestamped")
.shouldHaveExitValue(0);
signVerbose(null, "unsigned.jar", "badku.jar", "badku")
.shouldHaveExitValue(8);
checkBadKU("badku.jar");
// 8180289: unvalidated TSA cert chain
sign("tsnoca")
.shouldContain("TSA certificate chain is invalid")
.shouldHaveExitValue(64);
verify("tsnoca.jar", "-verbose", "-certs")
.shouldHaveExitValue(64)
.shouldContain("jar verified")
.shouldContain("Invalid TSA certificate chain")
.shouldContain("TSA certificate chain is invalid");
sign("nononce") sign("nononce")
.shouldHaveExitValue(1); .shouldHaveExitValue(1);
sign("diffnonce") sign("diffnonce")
...@@ -325,11 +362,11 @@ public class TimestampCheck { ...@@ -325,11 +362,11 @@ public class TimestampCheck {
.shouldHaveExitValue(1); .shouldHaveExitValue(1);
sign("fullchain") sign("fullchain")
.shouldHaveExitValue(0); // Success, 6543440 solved. .shouldHaveExitValue(0); // Success, 6543440 solved.
sign("bad1") sign("tsbad1")
.shouldHaveExitValue(1); .shouldHaveExitValue(1);
sign("bad2") sign("tsbad2")
.shouldHaveExitValue(1); .shouldHaveExitValue(1);
sign("bad3") sign("tsbad3")
.shouldHaveExitValue(1); .shouldHaveExitValue(1);
sign("nocert") sign("nocert")
.shouldHaveExitValue(1); .shouldHaveExitValue(1);
...@@ -341,119 +378,171 @@ public class TimestampCheck { ...@@ -341,119 +378,171 @@ public class TimestampCheck {
sign("diffpolicy", "-tsapolicyid", "1.2.3") sign("diffpolicy", "-tsapolicyid", "1.2.3")
.shouldHaveExitValue(1); .shouldHaveExitValue(1);
sign("tsaalg", "-tsadigestalg", "SHA") sign("sha1alg", "-tsadigestalg", "SHA")
.shouldHaveExitValue(0); .shouldHaveExitValue(0);
checkTimestamp("tsaalg.jar", defaultPolicyId, "SHA-1"); checkTimestamp("sha1alg.jar", defaultPolicyId, "SHA-1");
sign("weak", "-digestalg", "MD5", sign("tsweak", "-digestalg", "MD5",
"-sigalg", "MD5withRSA", "-tsadigestalg", "MD5") "-sigalg", "MD5withRSA", "-tsadigestalg", "MD5")
.shouldHaveExitValue(0); .shouldHaveExitValue(68);
checkWeak("weak.jar"); checkWeak("tsweak.jar");
signWithAliasAndTsa("halfWeak", "old.jar", "old", "-digestalg", "MD5") signVerbose("tsweak", "unsigned.jar", "tsweak2.jar", "signer")
.shouldHaveExitValue(0); .shouldHaveExitValue(64)
.shouldContain("TSA certificate chain is invalid");
// Weak timestamp is an error and jar treated unsigned
verify("tsweak2.jar", "-verbose")
.shouldHaveExitValue(16)
.shouldContain("treated as unsigned")
.shouldMatch("Timestamp.*512.*weak");
signVerbose("normal", "unsigned.jar", "halfWeak.jar", "signer",
"-digestalg", "MD5")
.shouldHaveExitValue(4);
checkHalfWeak("halfWeak.jar"); checkHalfWeak("halfWeak.jar");
// sign with DSA key // sign with DSA key
signWithAliasAndTsa("sign1", "old.jar", "dsakey") signVerbose("normal", "unsigned.jar", "sign1.jar", "dsakey")
.shouldHaveExitValue(0); .shouldHaveExitValue(0);
// sign with RSAkeysize < 1024 // sign with RSAkeysize < 1024
signWithAliasAndTsa("sign2", "sign1.jar", "weakkeysize") signVerbose("normal", "sign1.jar", "sign2.jar", "weakkeysize")
.shouldHaveExitValue(0); .shouldHaveExitValue(4);
checkMultiple("sign2.jar"); checkMultiple("sign2.jar");
// When .SF or .RSA is missing or invalid // When .SF or .RSA is missing or invalid
checkMissingOrInvalidFiles("normal.jar"); checkMissingOrInvalidFiles("normal.jar");
if (Files.exists(Paths.get("ts2.cert"))) {
checkInvalidTsaCertKeyUsage();
}
} else { // Run as a standalone server } else { // Run as a standalone server
System.err.println("Press Enter to quit server"); System.out.println("Press Enter to quit server");
System.in.read(); System.in.read();
} }
} }
} }
private static void checkInvalidTsaCertKeyUsage() throws Exception {
// Hack: Rewrite the TSA cert inside normal.jar into ts2.jar.
// Both the cert and the serial number must be rewritten.
byte[] tsCert = Files.readAllBytes(Paths.get("ts.cert"));
byte[] ts2Cert = Files.readAllBytes(Paths.get("ts2.cert"));
byte[] tsSerial = getCert(tsCert)
.getSerialNumber().toByteArray();
byte[] ts2Serial = getCert(ts2Cert)
.getSerialNumber().toByteArray();
byte[] oldBlock;
try (JarFile normal = new JarFile("normal.jar")) {
oldBlock = Utils.readAllBytes(normal.getInputStream(
normal.getJarEntry("META-INF/SIGNER.RSA")));
}
JarUtils.updateJar("normal.jar", "ts2.jar",
mapOf("META-INF/SIGNER.RSA",
updateBytes(updateBytes(oldBlock, tsCert, ts2Cert),
tsSerial, ts2Serial)));
verify("ts2.jar", "-verbose", "-certs")
.shouldHaveExitValue(64)
.shouldContain("jar verified")
.shouldContain("Invalid TSA certificate chain: Extended key usage does not permit use for TSA server");
}
public static X509Certificate getCert(byte[] data)
throws CertificateException, IOException {
return (X509Certificate)
CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(data));
}
private static byte[] updateBytes(byte[] old, byte[] from, byte[] to) {
int pos = 0;
while (true) {
if (pos + from.length > old.length) {
return null;
}
if (Arrays.equals(Arrays.copyOfRange(old, pos, pos+from.length), from)) {
byte[] result = old.clone();
System.arraycopy(to, 0, result, pos, from.length);
return result;
}
pos++;
}
}
private static void checkMissingOrInvalidFiles(String s) private static void checkMissingOrInvalidFiles(String s)
throws Throwable { throws Throwable {
JarUtils.updateJar(s, "1.jar", "-", "META-INF/OLD.SF");
JarUtils.updateJar(s, "1.jar", mapOf("META-INF/SIGNER.SF", Boolean.FALSE));
verify("1.jar", "-verbose") verify("1.jar", "-verbose")
.shouldHaveExitValue(0) .shouldHaveExitValue(16)
.shouldContain("treated as unsigned") .shouldContain("treated as unsigned")
.shouldContain("Missing signature-related file META-INF/OLD.SF"); .shouldContain("Missing signature-related file META-INF/SIGNER.SF");
JarUtils.updateJar(s, "2.jar", "-", "META-INF/OLD.RSA"); JarUtils.updateJar(s, "2.jar", mapOf("META-INF/SIGNER.RSA", Boolean.FALSE));
verify("2.jar", "-verbose") verify("2.jar", "-verbose")
.shouldHaveExitValue(0) .shouldHaveExitValue(16)
.shouldContain("treated as unsigned") .shouldContain("treated as unsigned")
.shouldContain("Missing block file for signature-related file META-INF/OLD.SF"); .shouldContain("Missing block file for signature-related file META-INF/SIGNER.SF");
JarUtils.updateJar(s, "3.jar", "META-INF/OLD.SF"); JarUtils.updateJar(s, "3.jar", mapOf("META-INF/SIGNER.SF", "dummy"));
verify("3.jar", "-verbose") verify("3.jar", "-verbose")
.shouldHaveExitValue(0) .shouldHaveExitValue(16)
.shouldContain("treated as unsigned") .shouldContain("treated as unsigned")
.shouldContain("Unparsable signature-related file META-INF/OLD.SF"); .shouldContain("Unparsable signature-related file META-INF/SIGNER.SF");
JarUtils.updateJar(s, "4.jar", "META-INF/OLD.RSA"); JarUtils.updateJar(s, "4.jar", mapOf("META-INF/SIGNER.RSA", "dummy"));
verify("4.jar", "-verbose") verify("4.jar", "-verbose")
.shouldHaveExitValue(0) .shouldHaveExitValue(16)
.shouldContain("treated as unsigned") .shouldContain("treated as unsigned")
.shouldContain("Unparsable signature-related file META-INF/OLD.RSA"); .shouldContain("Unparsable signature-related file META-INF/SIGNER.RSA");
} }
static OutputAnalyzer jarsigner(List<String> extra) static OutputAnalyzer jarsigner(List<String> extra)
throws Throwable { throws Exception {
JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jarsigner") List<String> args = new ArrayList<>(
.addVMArg("-Duser.language=en") listOf("-keystore", "ks", "-storepass", "changeit"));
.addVMArg("-Duser.country=US") args.addAll(extra);
.addToolArg("-keystore") return SecurityTools.jarsigner(args);
.addToolArg("tsks")
.addToolArg("-storepass")
.addToolArg("changeit");
for (String s : extra) {
if (s.startsWith("-J")) {
launcher.addVMArg(s.substring(2));
} else {
launcher.addToolArg(s);
}
}
System.err.println("COMMAND: ");
for (String cmd : launcher.getCommand()) {
System.err.print(cmd + " ");
}
System.err.println();
return ProcessTools.executeCommand(launcher.getCommand());
} }
static OutputAnalyzer verify(String file, String... extra) static OutputAnalyzer verify(String file, String... extra)
throws Throwable { throws Exception {
List<String> args = new ArrayList<>(); List<String> args = new ArrayList<>();
args.add("-verify"); args.add("-verify");
args.add("-strict");
args.add(file); args.add(file);
args.addAll(Arrays.asList(extra)); args.addAll(Arrays.asList(extra));
return jarsigner(args); return jarsigner(args);
} }
static void checkBadKU(String file) throws Throwable { static void checkBadKU(String file) throws Exception {
System.err.println("BadKU: " + file); System.err.println("BadKU: " + file);
verify(file) verify(file)
.shouldHaveExitValue(0) .shouldHaveExitValue(16)
.shouldContain("treated as unsigned") .shouldContain("treated as unsigned")
.shouldContain("re-run jarsigner with debug enabled"); .shouldContain("re-run jarsigner with debug enabled");
verify(file, "-verbose") verify(file, "-verbose")
.shouldHaveExitValue(0) .shouldHaveExitValue(16)
.shouldContain("Signed by") .shouldContain("Signed by")
.shouldContain("treated as unsigned") .shouldContain("treated as unsigned")
.shouldContain("re-run jarsigner with debug enabled"); .shouldContain("re-run jarsigner with debug enabled");
verify(file, "-J-Djava.security.debug=jar") verify(file, "-J-Djava.security.debug=jar")
.shouldHaveExitValue(0) .shouldHaveExitValue(16)
.shouldContain("SignatureException: Key usage restricted") .shouldContain("SignatureException: Key usage restricted")
.shouldContain("treated as unsigned") .shouldContain("treated as unsigned")
.shouldContain("re-run jarsigner with debug enabled"); .shouldContain("re-run jarsigner with debug enabled");
} }
static void checkWeak(String file) throws Throwable { static void checkWeak(String file) throws Exception {
verify(file) verify(file)
.shouldHaveExitValue(0) .shouldHaveExitValue(16)
.shouldContain("treated as unsigned") .shouldContain("treated as unsigned")
.shouldMatch("weak algorithm that is now disabled.") .shouldMatch("weak algorithm that is now disabled.")
.shouldMatch("Re-run jarsigner with the -verbose option for more details"); .shouldMatch("Re-run jarsigner with the -verbose option for more details");
verify(file, "-verbose") verify(file, "-verbose")
.shouldHaveExitValue(0) .shouldHaveExitValue(16)
.shouldContain("treated as unsigned") .shouldContain("treated as unsigned")
.shouldMatch("weak algorithm that is now disabled by") .shouldMatch("weak algorithm that is now disabled by")
.shouldMatch("Digest algorithm: .*weak") .shouldMatch("Digest algorithm: .*weak")
...@@ -462,18 +551,18 @@ public class TimestampCheck { ...@@ -462,18 +551,18 @@ public class TimestampCheck {
.shouldNotMatch("Timestamp signature algorithm: .*weak.*weak") .shouldNotMatch("Timestamp signature algorithm: .*weak.*weak")
.shouldMatch("Timestamp signature algorithm: .*key.*weak"); .shouldMatch("Timestamp signature algorithm: .*key.*weak");
verify(file, "-J-Djava.security.debug=jar") verify(file, "-J-Djava.security.debug=jar")
.shouldHaveExitValue(0) .shouldHaveExitValue(16)
.shouldMatch("SignatureException:.*disabled"); .shouldMatch("SignatureException:.*disabled");
} }
static void checkHalfWeak(String file) throws Throwable { static void checkHalfWeak(String file) throws Exception {
verify(file) verify(file)
.shouldHaveExitValue(0) .shouldHaveExitValue(16)
.shouldContain("treated as unsigned") .shouldContain("treated as unsigned")
.shouldMatch("weak algorithm that is now disabled.") .shouldMatch("weak algorithm that is now disabled.")
.shouldMatch("Re-run jarsigner with the -verbose option for more details"); .shouldMatch("Re-run jarsigner with the -verbose option for more details");
verify(file, "-verbose") verify(file, "-verbose")
.shouldHaveExitValue(0) .shouldHaveExitValue(16)
.shouldContain("treated as unsigned") .shouldContain("treated as unsigned")
.shouldMatch("weak algorithm that is now disabled by") .shouldMatch("weak algorithm that is now disabled by")
.shouldMatch("Digest algorithm: .*weak") .shouldMatch("Digest algorithm: .*weak")
...@@ -483,7 +572,7 @@ public class TimestampCheck { ...@@ -483,7 +572,7 @@ public class TimestampCheck {
.shouldNotMatch("Timestamp signature algorithm: .*key.*weak"); .shouldNotMatch("Timestamp signature algorithm: .*key.*weak");
} }
static void checkMultiple(String file) throws Throwable { static void checkMultiple(String file) throws Exception {
verify(file) verify(file)
.shouldHaveExitValue(0) .shouldHaveExitValue(0)
.shouldContain("jar verified"); .shouldContain("jar verified");
...@@ -500,7 +589,7 @@ public class TimestampCheck { ...@@ -500,7 +589,7 @@ public class TimestampCheck {
static void checkTimestamp(String file, String policyId, String digestAlg) static void checkTimestamp(String file, String policyId, String digestAlg)
throws Exception { throws Exception {
try (JarFile jf = new JarFile(file)) { try (JarFile jf = new JarFile(file)) {
JarEntry je = jf.getJarEntry("META-INF/OLD.RSA"); JarEntry je = jf.getJarEntry("META-INF/SIGNER.RSA");
try (InputStream is = jf.getInputStream(je)) { try (InputStream is = jf.getInputStream(je)) {
byte[] content = IOUtils.readFully(is, -1, true); byte[] content = IOUtils.readFully(is, -1, true);
PKCS7 p7 = new PKCS7(content); PKCS7 p7 = new PKCS7(content);
...@@ -526,26 +615,38 @@ public class TimestampCheck { ...@@ -526,26 +615,38 @@ public class TimestampCheck {
static int which = 0; static int which = 0;
/** /**
* Sign with a TSA path. Always use alias "signer" to sign "unsigned.jar".
* The signed jar name is always path.jar.
*
* @param extra more args given to jarsigner * @param extra more args given to jarsigner
*/ */
static OutputAnalyzer sign(String path, String... extra) static OutputAnalyzer sign(String path, String... extra)
throws Throwable { throws Exception {
String alias = path.equals("badku") ? "badku" : "old"; return signVerbose(
return signWithAliasAndTsa(path, "old.jar", alias, extra); path,
} "unsigned.jar",
path + ".jar",
static OutputAnalyzer signWithAliasAndTsa (String path, String jar, "signer",
String alias, String...extra) throws Throwable { extra);
}
static OutputAnalyzer signVerbose(
String path, // TSA URL path
String oldJar,
String newJar,
String alias, // signer
String...extra) throws Exception {
which++; which++;
System.err.println("\n>> Test #" + which + ": " + Arrays.toString(extra)); System.out.println("\n>> Test #" + which);
List<String> args = new ArrayList<>(); List<String> args = new ArrayList<>();
args.add("-J-Djava.security.egd=file:/dev/./urandom"); args.add("-strict");
args.add("-verbose");
args.add("-debug"); args.add("-debug");
args.add("-signedjar"); args.add("-signedjar");
args.add(path + ".jar"); args.add(newJar);
args.add(jar); args.add(oldJar);
args.add(alias); args.add(alias);
if (!path.equals("none") && !path.equals("badku")) { if (path != null) {
args.add("-tsa"); args.add("-tsa");
args.add(host + path); args.add(host + path);
} }
...@@ -554,24 +655,50 @@ public class TimestampCheck { ...@@ -554,24 +655,50 @@ public class TimestampCheck {
} }
static void prepare() throws Exception { static void prepare() throws Exception {
jdk.testlibrary.JarUtils.createJar("old.jar", "A"); JarUtils.createJar("unsigned.jar", "A");
Files.deleteIfExists(Paths.get("tsks")); Files.deleteIfExists(Paths.get("ks"));
keytool("-alias ca -genkeypair -ext bc -dname CN=CA"); keytool("-alias signer -genkeypair -ext bc -dname CN=signer");
keytool("-alias old -genkeypair -dname CN=old"); keytool("-alias oldsigner -genkeypair -dname CN=oldsigner");
keytool("-alias dsakey -genkeypair -keyalg DSA -dname CN=dsakey"); keytool("-alias dsakey -genkeypair -keyalg DSA -dname CN=dsakey");
keytool("-alias weakkeysize -genkeypair -keysize 512 -dname CN=weakkeysize"); keytool("-alias weakkeysize -genkeypair -keysize 512 -dname CN=weakkeysize");
keytool("-alias badku -genkeypair -dname CN=badku"); keytool("-alias badku -genkeypair -dname CN=badku");
keytool("-alias ts -genkeypair -dname CN=ts"); keytool("-alias ts -genkeypair -dname CN=ts");
keytool("-alias tsweak -genkeypair -keysize 512 -dname CN=tsbad1"); keytool("-alias tsold -genkeypair -dname CN=tsold");
keytool("-alias tsweak -genkeypair -keysize 512 -dname CN=tsweak");
keytool("-alias tsbad1 -genkeypair -dname CN=tsbad1"); keytool("-alias tsbad1 -genkeypair -dname CN=tsbad1");
keytool("-alias tsbad2 -genkeypair -dname CN=tsbad2"); keytool("-alias tsbad2 -genkeypair -dname CN=tsbad2");
keytool("-alias tsbad3 -genkeypair -dname CN=tsbad3"); keytool("-alias tsbad3 -genkeypair -dname CN=tsbad3");
keytool("-alias tsnoca -genkeypair -dname CN=tsnoca");
gencert("old"); // tsnoca's issuer will be removed from keystore later
keytool("-alias ca -genkeypair -ext bc -dname CN=CA");
gencert("tsnoca", "-ext eku:critical=ts");
keytool("-delete -alias ca");
keytool("-alias ca -genkeypair -ext bc -dname CN=CA -startdate -40d");
gencert("signer");
gencert("oldsigner", "-startdate -30d -validity 20");
gencert("dsakey"); gencert("dsakey");
gencert("weakkeysize"); gencert("weakkeysize");
gencert("badku", "-ext ku:critical=keyAgreement"); gencert("badku", "-ext ku:critical=keyAgreement");
gencert("ts", "-ext eku:critical=ts"); gencert("ts", "-ext eku:critical=ts");
// Issue another cert for "ts" with a different EKU.
// Length should be the same. Try several times.
keytool("-gencert -alias ca -infile ts.req -outfile ts2.cert " +
"-ext eku:critical=1.3.6.1.5.5.7.3.9");
for (int i = 0; i < 5; i++) {
if (Files.size(Paths.get("ts.cert")) != Files.size(Paths.get("ts2.cert"))) {
Files.delete(Paths.get("ts2.cert"));
System.out.println("Warning: cannot create same length");
} else {
break;
}
}
gencert("tsold", "-ext eku:critical=ts -startdate -40d -validity 45");
gencert("tsweak", "-ext eku:critical=ts"); gencert("tsweak", "-ext eku:critical=ts");
gencert("tsbad1"); gencert("tsbad1");
gencert("tsbad2", "-ext eku=ts"); gencert("tsbad2", "-ext eku=ts");
...@@ -590,8 +717,16 @@ public class TimestampCheck { ...@@ -590,8 +717,16 @@ public class TimestampCheck {
} }
static void keytool(String cmd) throws Exception { static void keytool(String cmd) throws Exception {
cmd = "-keystore tsks -storepass changeit -keypass changeit " + cmd = "-keystore ks -storepass changeit -keypass changeit " +
"-keyalg rsa -validity 200 " + cmd; "-keyalg rsa -validity 200 " + cmd;
sun.security.tools.keytool.Main.main(cmd.split(" ")); sun.security.tools.keytool.Main.main(cmd.split(" "));
} }
static <K,V> Map<K,V> mapOf(K k1, V v1) {
return Collections.singletonMap(k1, v1);
}
static <E> List<E> listOf(E... elements) {
return Arrays.asList(elements);
}
} }
/* /*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -81,14 +81,14 @@ public class Warning { ...@@ -81,14 +81,14 @@ public class Warning {
issueCert("b", "-sigalg MD5withRSA"); issueCert("b", "-sigalg MD5withRSA");
run("jarsigner", "a.jar b") run("jarsigner", "a.jar b")
.shouldMatch("chain is not validated. Reason:.*MD5withRSA"); .shouldMatch("chain is invalid. Reason:.*MD5withRSA");
recreateJar(); recreateJar();
newCert("c", "-keysize 512"); newCert("c", "-keysize 512");
issueCert("c"); issueCert("c");
run("jarsigner", "a.jar c") run("jarsigner", "a.jar c")
.shouldContain("chain is not validated. " + .shouldContain("chain is invalid. " +
"Reason: Algorithm constraints check failed"); "Reason: Algorithm constraints check failed");
recreateJar(); recreateJar();
......
# #
# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. # Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
# #
# This code is free software; you can redistribute it and/or modify it # This code is free software; you can redistribute it and/or modify it
...@@ -91,7 +91,7 @@ echo $RESULT ...@@ -91,7 +91,7 @@ echo $RESULT
#[ $RESULT = 0 ] || exit 2 #[ $RESULT = 0 ] || exit 2
# Test 3: When no keystore is specified, the error is only # Test 3: When no keystore is specified, the error is only
# "chain not validated" # "chain invalid"
$JARSIGNER -strict -verify a.jar $JARSIGNER -strict -verify a.jar
RESULT=$? RESULT=$?
...@@ -99,7 +99,7 @@ echo $RESULT ...@@ -99,7 +99,7 @@ echo $RESULT
#[ $RESULT = 4 ] || exit 3 #[ $RESULT = 4 ] || exit 3
# Test 4: When unrelated keystore is specified, the error is # Test 4: When unrelated keystore is specified, the error is
# "chain not validated" and "not alias in keystore" # "chain invalid" and "not alias in keystore"
$JARSIGNER -keystore unrelated.jks -strict -verify a.jar $JARSIGNER -keystore unrelated.jks -strict -verify a.jar
RESULT=$? RESULT=$?
......
/* /*
* Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -63,7 +63,7 @@ public abstract class Test { ...@@ -63,7 +63,7 @@ public abstract class Test {
static final String CHAIN_NOT_VALIDATED_VERIFYING_WARNING static final String CHAIN_NOT_VALIDATED_VERIFYING_WARNING
= "This jar contains entries " = "This jar contains entries "
+ "whose certificate chain is not validated."; + "whose certificate chain is invalid.";
static final String ALIAS_NOT_IN_STORE_VERIFYING_WARNING static final String ALIAS_NOT_IN_STORE_VERIFYING_WARNING
= "This jar contains signed entries " = "This jar contains signed entries "
...@@ -95,7 +95,7 @@ public abstract class Test { ...@@ -95,7 +95,7 @@ public abstract class Test {
+ "doesn't allow code signing."; + "doesn't allow code signing.";
static final String CHAIN_NOT_VALIDATED_SIGNING_WARNING static final String CHAIN_NOT_VALIDATED_SIGNING_WARNING
= "The signer's certificate chain is not validated."; = "The signer's certificate chain is invalid.";
static final String HAS_EXPIRING_CERT_SIGNING_WARNING static final String HAS_EXPIRING_CERT_SIGNING_WARNING
= "The signer certificate will expire within six months."; = "The signer certificate will expire within six months.";
......
# #
# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. # Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
# #
# This code is free software; you can redistribute it and/or modify it # This code is free software; you can redistribute it and/or modify it
...@@ -52,9 +52,9 @@ $KT -certreq -alias signer | \ ...@@ -52,9 +52,9 @@ $KT -certreq -alias signer | \
$JAR cvf a.jar ks $JAR cvf a.jar ks
# We always trust a TrustedCertificateEntry # We always trust a TrustedCertificateEntry
$JS a.jar ca | grep "chain is not validated" && exit 1 $JS a.jar ca | grep "chain is invalid" && exit 1
# An end-entity cert must follow algorithm constraints # An end-entity cert must follow algorithm constraints
$JS a.jar signer | grep "chain is not validated" || exit 2 $JS a.jar signer | grep "chain is invalid" || exit 2
exit 0 exit 0
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册