提交 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 {
private boolean noTimestamp = false;
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 boolean hasExpiredCert = false;
private boolean notYetValidCert = false;
private boolean chainNotValidated = false;
private boolean tsaChainNotValidated = false;
private boolean notSignedByAlias = false;
private boolean aliasNotInStore = false;
private boolean hasUnsignedEntry = false;
......@@ -189,6 +198,7 @@ public class Main {
private boolean signerSelfSigned = false;
private Throwable chainNotValidatedReason = null;
private Throwable tsaChainNotValidatedReason = null;
private boolean seeWeak = false;
......@@ -279,7 +289,8 @@ public class Main {
if (strict) {
int exitCode = 0;
if (weakAlg != 0 || chainNotValidated || hasExpiredCert || notYetValidCert || signerSelfSigned) {
if (weakAlg != 0 || chainNotValidated
|| hasExpiredCert || notYetValidCert || signerSelfSigned) {
exitCode |= 4;
}
if (badKeyUsage || badExtendedKeyUsage || badNetscapeCertType) {
......@@ -291,6 +302,9 @@ public class Main {
if (notSignedByAlias || aliasNotInStore) {
exitCode |= 32;
}
if (tsaChainNotValidated) {
exitCode |= 64;
}
if (exitCode != 0) {
System.exit(exitCode);
}
......@@ -811,6 +825,9 @@ public class Main {
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
// must be generated so seeWeak can be updated.
if (!digestMap.isEmpty()
......@@ -839,6 +856,7 @@ public class Main {
PublicKey key = signer.getPublicKey();
PKCS7 tsToken = si.getTsToken();
if (tsToken != null) {
hasTimestampBlock = true;
SignerInfo tsSi = tsToken.getSignerInfos()[0];
X509Certificate tsSigner = tsSi.getCertificate(tsToken);
byte[] encTsTokenInfo = tsToken.getContentInfo().getData();
......@@ -921,7 +939,7 @@ public class Main {
if (badKeyUsage || badExtendedKeyUsage || badNetscapeCertType ||
notYetValidCert || chainNotValidated || hasExpiredCert ||
hasUnsignedEntry || signerSelfSigned || (weakAlg != 0) ||
aliasNotInStore || notSignedByAlias) {
aliasNotInStore || notSignedByAlias || tsaChainNotValidated) {
if (strict) {
System.out.println(rb.getString("jar.verified.with.signer.errors."));
......@@ -971,10 +989,16 @@ public class Main {
if (chainNotValidated) {
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()));
}
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) {
System.out.println(
rb.getString("This.jar.contains.signed.entries.which.is.not.signed.by.the.specified.alias.es."));
......@@ -1002,8 +1026,15 @@ public class Main {
"This.jar.contains.entries.whose.signer.certificate.will.expire.within.six.months."));
}
if (noTimestamp) {
System.out.println(
String.format(rb.getString("no.timestamp.verifying"), expireDate));
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(
String.format(rb.getString("no.timestamp.verifying"), expireDate));
}
}
}
if (warningAppeared || errorAppeared) {
......@@ -1054,16 +1085,23 @@ public class Main {
private static MessageFormat expiredTimeForm = 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> ")"]
* [<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,
Date timestamp, boolean checkUsage) {
String printCert(boolean isTsCert, String tab, Certificate c,
Date timestamp, boolean checkUsage) throws Exception {
StringBuilder certStr = new StringBuilder();
String space = rb.getString("SPACE");
......@@ -1083,7 +1121,7 @@ public class Main {
certStr.append(space).append(alias);
}
if (checkValidityPeriod && x509Cert != null) {
if (x509Cert != null) {
certStr.append("\n").append(tab).append("[");
Date notAfter = x509Cert.getNotAfter();
......@@ -1096,7 +1134,7 @@ public class Main {
x509Cert.checkValidity();
// test if cert will expire within six months
if (notAfter.getTime() < System.currentTimeMillis() + SIX_MONTHS) {
hasExpiringCert = true;
if (!isTsCert) hasExpiringCert = true;
if (expiringTimeForm == null) {
expiringTimeForm = new MessageFormat(
rb.getString("certificate.will.expire.on"));
......@@ -1117,7 +1155,7 @@ public class Main {
certStr.append(validityTimeForm.format(source));
}
} catch (CertificateExpiredException cee) {
hasExpiredCert = true;
if (!isTsCert) hasExpiredCert = true;
if (expiredTimeForm == null) {
expiredTimeForm = new MessageFormat(
......@@ -1127,7 +1165,7 @@ public class Main {
certStr.append(expiredTimeForm.format(source));
} catch (CertificateNotYetValidException cnyve) {
notYetValidCert = true;
if (!isTsCert) notYetValidCert = true;
if (notYetTimeForm == null) {
notYetTimeForm = new MessageFormat(
......@@ -1534,7 +1572,7 @@ public class Main {
tsaURI);
}
System.out.println(rb.getString("TSA.certificate.") +
printCert("", tsaCert, false, null, false));
printCert(true, "", tsaCert, null, false));
}
if (signingMechanism != null) {
System.out.println(
......@@ -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
// the try clause.
// try {
......@@ -1626,8 +1688,10 @@ public class Main {
}
boolean warningAppeared = false;
if (weakAlg != 0 || badKeyUsage || badExtendedKeyUsage || badNetscapeCertType ||
notYetValidCert || chainNotValidated || hasExpiredCert || signerSelfSigned) {
if (weakAlg != 0 || badKeyUsage || badExtendedKeyUsage
|| badNetscapeCertType || notYetValidCert
|| chainNotValidated || tsaChainNotValidated
|| hasExpiredCert || signerSelfSigned) {
if (strict) {
System.out.println(rb.getString("jar.signed.with.signer.errors."));
System.out.println();
......@@ -1664,10 +1728,16 @@ public class Main {
if (chainNotValidated) {
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()));
}
if (tsaChainNotValidated) {
System.out.println(String.format(
rb.getString("The.tsa.certificate.chain.is.invalid.reason.1"),
tsaChainNotValidatedReason.getLocalizedMessage()));
}
if (signerSelfSigned) {
System.out.println(
rb.getString("The.signer.s.certificate.is.self.signed."));
......@@ -1763,18 +1833,18 @@ public class Main {
/**
* 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)) {
return cacheForSignerInfo.get(signer);
}
StringBuffer s = new StringBuffer();
StringBuilder sb = new StringBuilder();
List<? extends Certificate> certs = signer.getSignerCertPath().getCertificates();
// display the signature timestamp, if present
Date timestamp;
Timestamp ts = signer.getTimestamp();
if (ts != null) {
s.append(printTimestamp(tab, ts));
s.append('\n');
sb.append(printTimestamp(tab, ts));
sb.append('\n');
timestamp = ts.getTimestamp();
} else {
timestamp = null;
......@@ -1783,24 +1853,41 @@ public class Main {
// display the certificate(s). The first one is end-entity cert and
// its KeyUsage should be checked.
boolean first = true;
sb.append(tab).append(rb.getString("...Signer")).append('\n');
for (Certificate c : certs) {
s.append(printCert(tab, c, true, timestamp, first));
s.append('\n');
sb.append(printCert(false, tab, c, timestamp, first));
sb.append('\n');
first = false;
}
try {
validateCertChain(certs);
validateCertChain(Validator.VAR_CODE_SIGNING, certs, ts);
} catch (Exception e) {
chainNotValidated = true;
chainNotValidatedReason = e;
s.append(tab).append(rb.getString(".CertPath.not.validated."))
.append(e.getLocalizedMessage()).append("]\n"); // TODO
sb.append(tab).append(rb.getString(".Invalid.certificate.chain."))
.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
&& KeyStoreUtil.isSelfSigned((X509Certificate)certs.get(0))) {
signerSelfSigned = true;
}
String result = s.toString();
String result = sb.toString();
cacheForSignerInfo.put(signer, result);
return result;
}
......@@ -2043,7 +2130,7 @@ public class Main {
}
}
void getAliasInfo(String alias) {
void getAliasInfo(String alias) throws Exception {
Key key = null;
......@@ -2089,10 +2176,11 @@ public class Main {
// We don't meant to print anything, the next call
// checks validity and keyUsage etc
printCert("", certChain[0], true, null, true);
printCert(false, "", certChain[0], null, true);
try {
validateCertChain(Arrays.asList(certChain));
validateCertChain(Validator.VAR_CODE_SIGNING,
Arrays.asList(certChain), null);
} catch (Exception e) {
chainNotValidated = true;
chainNotValidatedReason = e;
......@@ -2153,17 +2241,31 @@ public class Main {
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 {
Validator.getInstance(Validator.TYPE_PKIX,
Validator.VAR_CODE_SIGNING,
variant,
pkixParameters)
.validate(certs.toArray(new X509Certificate[certs.size()]));
.validate(certs.toArray(new X509Certificate[certs.size()]),
null, parameter);
} catch (Exception e) {
if (debug) {
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,
if (e.getCause() != null &&
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.
*
* This code is free software; you can redistribute it and/or modify it
......@@ -199,7 +199,8 @@ public class Resources extends java.util.ListResourceBundle {
{"certificate.is.not.valid.until",
"certificate is not valid until {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"},
{"TSA.location.", "TSA location: "},
......@@ -216,6 +217,8 @@ public class Resources extends java.util.ListResourceBundle {
{"entry.was.signed.on", "entry was signed on {0}"},
{"Warning.", "Warning: "},
{"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.entries.whose.signer.certificate.has.expired.",
......@@ -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."},
{".{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 not validated. Reason: %s"},
{"The.signer.s.certificate.chain.is.invalid.reason.1",
"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.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."},
{"This.jar.contains.entries.whose.certificate.chain.is.not.validated.reason.1",
"This jar contains entries whose certificate chain is not validated. Reason: %s"},
{"This.jar.contains.entries.whose.certificate.chain.is.invalid.reason.1",
"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 -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",
"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: "},
{"Cannot.find.environment.variable.",
"Cannot find environment variable: "},
......
......@@ -718,7 +718,8 @@ public class SignatureFileVerifier {
if (signers == null) {
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()));
if (debug != null) {
......
......@@ -49,10 +49,7 @@ public class SecurityTools {
launcher.addToolArg(arg);
}
}
String[] cmds = launcher.getCommand();
String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" "));
System.out.println("Command line: [" + cmdLine + "]");
return new ProcessBuilder(cmds);
return new ProcessBuilder(launcher.getCommand());
}
// keytool
......@@ -69,7 +66,7 @@ public class SecurityTools {
pb.redirectInput(ProcessBuilder.Redirect.from(new File(RESPONSE_FILE)));
try {
return ProcessTools.executeProcess(pb);
return execute(pb);
} catch (Throwable t) {
throw new RuntimeException("keytool failure: " + t);
} finally {
......@@ -101,11 +98,20 @@ public class SecurityTools {
public static OutputAnalyzer jarsigner(List<String> args)
throws Exception {
return execute(getProcessBuilder("jarsigner", args));
}
private static OutputAnalyzer execute(ProcessBuilder pb) throws Exception {
try {
return ProcessTools.executeProcess(
getProcessBuilder("jarsigner", args));
OutputAnalyzer oa = ProcessTools.executeCommand(pb);
System.out.println("Exit value: " + oa.getExitValue());
return oa;
} catch (Throwable t) {
throw new RuntimeException("jarsigner error: " + t);
if (t instanceof Exception) {
throw (Exception) t;
} else {
throw new Exception(t);
}
}
}
......
/*
* 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.
*
* This code is free software; you can redistribute it and/or modify it
......@@ -81,14 +81,14 @@ public class Warning {
issueCert("b", "-sigalg MD5withRSA");
run("jarsigner", "a.jar b")
.shouldMatch("chain is not validated. Reason:.*MD5withRSA");
.shouldMatch("chain is invalid. Reason:.*MD5withRSA");
recreateJar();
newCert("c", "-keysize 512");
issueCert("c");
run("jarsigner", "a.jar c")
.shouldContain("chain is not validated. " +
.shouldContain("chain is invalid. " +
"Reason: Algorithm constraints check failed");
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.
#
# This code is free software; you can redistribute it and/or modify it
......@@ -91,7 +91,7 @@ echo $RESULT
#[ $RESULT = 0 ] || exit 2
# Test 3: When no keystore is specified, the error is only
# "chain not validated"
# "chain invalid"
$JARSIGNER -strict -verify a.jar
RESULT=$?
......@@ -99,7 +99,7 @@ echo $RESULT
#[ $RESULT = 4 ] || exit 3
# 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
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.
*
* This code is free software; you can redistribute it and/or modify it
......@@ -63,7 +63,7 @@ public abstract class Test {
static final String CHAIN_NOT_VALIDATED_VERIFYING_WARNING
= "This jar contains entries "
+ "whose certificate chain is not validated.";
+ "whose certificate chain is invalid.";
static final String ALIAS_NOT_IN_STORE_VERIFYING_WARNING
= "This jar contains signed entries "
......@@ -95,7 +95,7 @@ public abstract class Test {
+ "doesn't allow code signing.";
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
= "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.
#
# This code is free software; you can redistribute it and/or modify it
......@@ -52,9 +52,9 @@ $KT -certreq -alias signer | \
$JAR cvf a.jar ks
# 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
$JS a.jar signer | grep "chain is not validated" || exit 2
$JS a.jar signer | grep "chain is invalid" || exit 2
exit 0
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册