8180289: jarsigner treats timestamped signed jar invalid after the signer cert expires
Reviewed-by: mullan
This commit is contained in:
parent
2dfe7fb8ab
commit
69db146335
src
java.base/share/classes/sun/security/util
jdk.jartool/share/classes/sun/security/tools/jarsigner
test
jdk/sun/security/tools/jarsigner
lib/jdk/test/lib
@ -724,7 +724,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) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 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
|
||||
@ -162,11 +162,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;
|
||||
@ -176,6 +185,7 @@ public class Main {
|
||||
private boolean signerSelfSigned = false;
|
||||
|
||||
private Throwable chainNotValidatedReason = null;
|
||||
private Throwable tsaChainNotValidatedReason = null;
|
||||
|
||||
private boolean seeWeak = false;
|
||||
|
||||
@ -266,7 +276,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) {
|
||||
@ -278,6 +289,9 @@ public class Main {
|
||||
if (notSignedByAlias || aliasNotInStore) {
|
||||
exitCode |= 32;
|
||||
}
|
||||
if (tsaChainNotValidated) {
|
||||
exitCode |= 64;
|
||||
}
|
||||
if (exitCode != 0) {
|
||||
System.exit(exitCode);
|
||||
}
|
||||
@ -864,6 +878,9 @@ public class Main {
|
||||
signerSelfSigned = false;
|
||||
}
|
||||
|
||||
// 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()
|
||||
@ -892,6 +909,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();
|
||||
@ -967,7 +985,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."));
|
||||
@ -1019,10 +1037,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."));
|
||||
@ -1050,8 +1074,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) {
|
||||
@ -1106,16 +1137,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");
|
||||
@ -1135,7 +1173,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();
|
||||
@ -1148,7 +1186,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"));
|
||||
@ -1169,7 +1207,7 @@ public class Main {
|
||||
certStr.append(validityTimeForm.format(source));
|
||||
}
|
||||
} catch (CertificateExpiredException cee) {
|
||||
hasExpiredCert = true;
|
||||
if (!isTsCert) hasExpiredCert = true;
|
||||
|
||||
if (expiredTimeForm == null) {
|
||||
expiredTimeForm = new MessageFormat(
|
||||
@ -1179,7 +1217,7 @@ public class Main {
|
||||
certStr.append(expiredTimeForm.format(source));
|
||||
|
||||
} catch (CertificateNotYetValidException cnyve) {
|
||||
notYetValidCert = true;
|
||||
if (!isTsCert) notYetValidCert = true;
|
||||
|
||||
if (notYetTimeForm == null) {
|
||||
notYetTimeForm = new MessageFormat(
|
||||
@ -1398,7 +1436,7 @@ public class Main {
|
||||
System.out.println(rb.getString("TSA.location.") + tsaUrl);
|
||||
} else if (tsaCert != null) {
|
||||
System.out.println(rb.getString("TSA.certificate.") +
|
||||
printCert("", tsaCert, false, null, false));
|
||||
printCert(true, "", tsaCert, null, false));
|
||||
}
|
||||
}
|
||||
builder.tsa(tsaURI);
|
||||
@ -1458,6 +1496,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 {
|
||||
@ -1487,8 +1549,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();
|
||||
@ -1525,10 +1589,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."));
|
||||
@ -1600,7 +1670,7 @@ 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);
|
||||
}
|
||||
@ -1620,18 +1690,35 @@ public class Main {
|
||||
// display the certificate(sb). 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) {
|
||||
sb.append(printCert(tab, c, true, timestamp, first));
|
||||
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;
|
||||
sb.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))) {
|
||||
@ -1841,7 +1928,7 @@ public class Main {
|
||||
}
|
||||
}
|
||||
|
||||
void getAliasInfo(String alias) {
|
||||
void getAliasInfo(String alias) throws Exception {
|
||||
|
||||
Key key = null;
|
||||
|
||||
@ -1887,10 +1974,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;
|
||||
@ -1949,17 +2037,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) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000, 2014, 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
|
||||
@ -207,7 +207,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: "},
|
||||
@ -224,6 +225,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.",
|
||||
@ -258,20 +261,26 @@ 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."},
|
||||
{"The.1.signing.key.has.a.keysize.of.2.which.is.considered.a.security.risk.",
|
||||
"The %s signing key has a keysize of %d which 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)."},
|
||||
{"no.timestamp.verifying",
|
||||
"This jar contains signatures that do not include a timestamp. Without a 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)."},
|
||||
{"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: "},
|
||||
|
@ -22,6 +22,8 @@
|
||||
*/
|
||||
|
||||
import com.sun.net.httpserver.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -35,16 +37,17 @@ import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Signature;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import jdk.test.lib.SecurityTools;
|
||||
import jdk.testlibrary.*;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.test.lib.util.JarUtils;
|
||||
import sun.security.pkcs.ContentInfo;
|
||||
import sun.security.pkcs.PKCS7;
|
||||
@ -59,7 +62,7 @@ import sun.security.x509.X500Name;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 6543842 6543440 6939248 8009636 8024302 8163304 8169911
|
||||
* @bug 6543842 6543440 6939248 8009636 8024302 8163304 8169911 8180289
|
||||
* @summary checking response of timestamp
|
||||
* @modules java.base/sun.security.pkcs
|
||||
* java.base/sun.security.timestamp
|
||||
@ -123,12 +126,12 @@ public class TimestampCheck {
|
||||
byte[] sign(byte[] input, String path) throws Exception {
|
||||
|
||||
DerValue value = new DerValue(input);
|
||||
System.err.println("\nIncoming Request\n===================");
|
||||
System.err.println("Version: " + value.data.getInteger());
|
||||
System.out.println("\nIncoming Request\n===================");
|
||||
System.out.println("Version: " + value.data.getInteger());
|
||||
DerValue messageImprint = value.data.getDerValue();
|
||||
AlgorithmId aid = AlgorithmId.parse(
|
||||
messageImprint.data.getDerValue());
|
||||
System.err.println("AlgorithmId: " + aid);
|
||||
System.out.println("AlgorithmId: " + aid);
|
||||
|
||||
ObjectIdentifier policyId = new ObjectIdentifier(defaultPolicyId);
|
||||
BigInteger nonce = null;
|
||||
@ -136,23 +139,22 @@ public class TimestampCheck {
|
||||
DerValue v = value.data.getDerValue();
|
||||
if (v.tag == DerValue.tag_Integer) {
|
||||
nonce = v.getBigInteger();
|
||||
System.err.println("nonce: " + nonce);
|
||||
System.out.println("nonce: " + nonce);
|
||||
} 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) {
|
||||
policyId = v.getOID();
|
||||
System.err.println("PolicyID: " + policyId);
|
||||
System.out.println("PolicyID: " + policyId);
|
||||
}
|
||||
}
|
||||
|
||||
System.err.println("\nResponse\n===================");
|
||||
System.out.println("\nResponse\n===================");
|
||||
KeyStore ks = KeyStore.getInstance(
|
||||
new File(keystore), "changeit".toCharArray());
|
||||
|
||||
String alias = "ts";
|
||||
if (path.startsWith("bad") || path.equals("weak")) {
|
||||
alias = "ts" + path;
|
||||
}
|
||||
// If path starts with "ts", use the TSA it points to.
|
||||
// Otherwise, always use "ts".
|
||||
String alias = path.startsWith("ts") ? path : "ts";
|
||||
|
||||
if (path.equals("diffpolicy")) {
|
||||
policyId = new ObjectIdentifier(defaultPolicyId);
|
||||
@ -199,8 +201,11 @@ public class TimestampCheck {
|
||||
|
||||
tst.putInteger(1);
|
||||
|
||||
Calendar cal = Calendar.getInstance();
|
||||
tst.putGeneralizedTime(cal.getTime());
|
||||
Instant instant = Instant.now();
|
||||
if (path.equals("tsold")) {
|
||||
instant = instant.minus(20, ChronoUnit.DAYS);
|
||||
}
|
||||
tst.putGeneralizedTime(Date.from(instant));
|
||||
|
||||
if (path.equals("diffnonce")) {
|
||||
tst.putInteger(1234);
|
||||
@ -227,10 +232,10 @@ public class TimestampCheck {
|
||||
"1.2.840.113549.1.9.16.1.4"),
|
||||
new DerValue(tstInfo2.toByteArray()));
|
||||
|
||||
System.err.println("Signing...");
|
||||
System.err.println(new X500Name(signer
|
||||
System.out.println("Signing...");
|
||||
System.out.println(new X500Name(signer
|
||||
.getIssuerX500Principal().getName()));
|
||||
System.err.println(signer.getSerialNumber());
|
||||
System.out.println(signer.getSerialNumber());
|
||||
|
||||
SignerInfo signerInfo = new SignerInfo(
|
||||
new X500Name(signer.getIssuerX500Principal().getName()),
|
||||
@ -303,23 +308,51 @@ public class TimestampCheck {
|
||||
|
||||
prepare();
|
||||
|
||||
try (Handler tsa = Handler.init(0, "tsks");) {
|
||||
try (Handler tsa = Handler.init(0, "ks");) {
|
||||
tsa.start();
|
||||
int port = tsa.getPort();
|
||||
host = "http://localhost:" + port + "/";
|
||||
|
||||
if (args.length == 0) { // Run this test
|
||||
sign("none")
|
||||
|
||||
sign("normal")
|
||||
.shouldNotContain("Warning")
|
||||
.shouldHaveExitValue(0);
|
||||
|
||||
verify("normal.jar")
|
||||
.shouldNotContain("Warning")
|
||||
.shouldHaveExitValue(0);
|
||||
|
||||
// Simulate signing at a previous date:
|
||||
// 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);
|
||||
|
||||
signVerbose(null, "unsigned.jar", "none.jar", "signer")
|
||||
.shouldContain("is not timestamped")
|
||||
.shouldHaveExitValue(0);
|
||||
|
||||
sign("badku")
|
||||
.shouldHaveExitValue(0);
|
||||
signVerbose(null, "unsigned.jar", "badku.jar", "badku")
|
||||
.shouldHaveExitValue(8);
|
||||
checkBadKU("badku.jar");
|
||||
|
||||
sign("normal")
|
||||
.shouldNotContain("is not timestamped")
|
||||
.shouldHaveExitValue(0);
|
||||
// 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")
|
||||
.shouldHaveExitValue(1);
|
||||
@ -331,11 +364,11 @@ public class TimestampCheck {
|
||||
.shouldHaveExitValue(1);
|
||||
sign("fullchain")
|
||||
.shouldHaveExitValue(0); // Success, 6543440 solved.
|
||||
sign("bad1")
|
||||
sign("tsbad1")
|
||||
.shouldHaveExitValue(1);
|
||||
sign("bad2")
|
||||
sign("tsbad2")
|
||||
.shouldHaveExitValue(1);
|
||||
sign("bad3")
|
||||
sign("tsbad3")
|
||||
.shouldHaveExitValue(1);
|
||||
sign("nocert")
|
||||
.shouldHaveExitValue(1);
|
||||
@ -347,116 +380,173 @@ public class TimestampCheck {
|
||||
sign("diffpolicy", "-tsapolicyid", "1.2.3")
|
||||
.shouldHaveExitValue(1);
|
||||
|
||||
sign("tsaalg", "-tsadigestalg", "SHA")
|
||||
sign("sha1alg", "-tsadigestalg", "SHA")
|
||||
.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")
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldHaveExitValue(68)
|
||||
.shouldMatch("MD5.*-digestalg.*risk")
|
||||
.shouldMatch("MD5.*-tsadigestalg.*risk")
|
||||
.shouldMatch("MD5withRSA.*-sigalg.*risk");
|
||||
checkWeak("weak.jar");
|
||||
checkWeak("tsweak.jar");
|
||||
|
||||
signWithAliasAndTsa("halfWeak", "old.jar", "old", "-digestalg", "MD5")
|
||||
.shouldHaveExitValue(0);
|
||||
signVerbose("tsweak", "unsigned.jar", "tsweak2.jar", "signer")
|
||||
.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");
|
||||
|
||||
// sign with DSA key
|
||||
signWithAliasAndTsa("sign1", "old.jar", "dsakey")
|
||||
signVerbose("normal", "unsigned.jar", "sign1.jar", "dsakey")
|
||||
.shouldHaveExitValue(0);
|
||||
// sign with RSAkeysize < 1024
|
||||
signWithAliasAndTsa("sign2", "sign1.jar", "weakkeysize")
|
||||
.shouldHaveExitValue(0);
|
||||
signVerbose("normal", "sign1.jar", "sign2.jar", "weakkeysize")
|
||||
.shouldHaveExitValue(4);
|
||||
checkMultiple("sign2.jar");
|
||||
|
||||
// When .SF or .RSA is missing or invalid
|
||||
checkMissingOrInvalidFiles("normal.jar");
|
||||
|
||||
if (Files.exists(Paths.get("ts2.cert"))) {
|
||||
checkInvalidTsaCertKeyUsage();
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = normal.getInputStream(
|
||||
normal.getJarEntry("META-INF/SIGNER.RSA")).readAllBytes();
|
||||
}
|
||||
|
||||
JarUtils.updateJar("normal.jar", "ts2.jar",
|
||||
Map.of("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)
|
||||
throws Throwable {
|
||||
JarUtils.updateJar(s, "1.jar", "-", "META-INF/OLD.SF");
|
||||
|
||||
JarUtils.updateJar(s, "1.jar", Map.of("META-INF/SIGNER.SF", Boolean.FALSE));
|
||||
verify("1.jar", "-verbose")
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldHaveExitValue(16)
|
||||
.shouldContain("treated as unsigned")
|
||||
.shouldContain("Missing signature-related file META-INF/OLD.SF");
|
||||
JarUtils.updateJar(s, "2.jar", "-", "META-INF/OLD.RSA");
|
||||
.shouldContain("Missing signature-related file META-INF/SIGNER.SF");
|
||||
JarUtils.updateJar(s, "2.jar", Map.of("META-INF/SIGNER.RSA", Boolean.FALSE));
|
||||
verify("2.jar", "-verbose")
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldHaveExitValue(16)
|
||||
.shouldContain("treated as unsigned")
|
||||
.shouldContain("Missing block file for signature-related file META-INF/OLD.SF");
|
||||
JarUtils.updateJar(s, "3.jar", "META-INF/OLD.SF");
|
||||
.shouldContain("Missing block file for signature-related file META-INF/SIGNER.SF");
|
||||
JarUtils.updateJar(s, "3.jar", Map.of("META-INF/SIGNER.SF", "dummy"));
|
||||
verify("3.jar", "-verbose")
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldHaveExitValue(16)
|
||||
.shouldContain("treated as unsigned")
|
||||
.shouldContain("Unparsable signature-related file META-INF/OLD.SF");
|
||||
JarUtils.updateJar(s, "4.jar", "META-INF/OLD.RSA");
|
||||
.shouldContain("Unparsable signature-related file META-INF/SIGNER.SF");
|
||||
JarUtils.updateJar(s, "4.jar", Map.of("META-INF/SIGNER.RSA", "dummy"));
|
||||
verify("4.jar", "-verbose")
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldHaveExitValue(16)
|
||||
.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)
|
||||
throws Throwable {
|
||||
JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jarsigner")
|
||||
.addVMArg("-Duser.language=en")
|
||||
.addVMArg("-Duser.country=US")
|
||||
.addToolArg("-keystore")
|
||||
.addToolArg("tsks")
|
||||
.addToolArg("-storepass")
|
||||
.addToolArg("changeit");
|
||||
for (String s : extra) {
|
||||
if (s.startsWith("-J")) {
|
||||
launcher.addVMArg(s.substring(2));
|
||||
} else {
|
||||
launcher.addToolArg(s);
|
||||
}
|
||||
}
|
||||
return ProcessTools.executeCommand(launcher.getCommand());
|
||||
throws Exception {
|
||||
List<String> args = new ArrayList<>(
|
||||
List.of("-keystore", "ks", "-storepass", "changeit"));
|
||||
args.addAll(extra);
|
||||
return SecurityTools.jarsigner(args);
|
||||
}
|
||||
|
||||
static OutputAnalyzer verify(String file, String... extra)
|
||||
throws Throwable {
|
||||
throws Exception {
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add("-verify");
|
||||
args.add("-strict");
|
||||
args.add(file);
|
||||
args.addAll(Arrays.asList(extra));
|
||||
return jarsigner(args);
|
||||
}
|
||||
|
||||
static void checkBadKU(String file) throws Throwable {
|
||||
static void checkBadKU(String file) throws Exception {
|
||||
verify(file)
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldHaveExitValue(16)
|
||||
.shouldContain("treated as unsigned")
|
||||
.shouldContain("re-run jarsigner with debug enabled");
|
||||
verify(file, "-verbose")
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldHaveExitValue(16)
|
||||
.shouldContain("Signed by")
|
||||
.shouldContain("treated as unsigned")
|
||||
.shouldContain("re-run jarsigner with debug enabled");
|
||||
verify(file, "-J-Djava.security.debug=jar")
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldHaveExitValue(16)
|
||||
.shouldContain("SignatureException: Key usage restricted")
|
||||
.shouldContain("treated as unsigned")
|
||||
.shouldContain("re-run jarsigner with debug enabled");
|
||||
}
|
||||
|
||||
static void checkWeak(String file) throws Throwable {
|
||||
static void checkWeak(String file) throws Exception {
|
||||
verify(file)
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldHaveExitValue(16)
|
||||
.shouldContain("treated as unsigned")
|
||||
.shouldMatch("weak algorithm that is now disabled.")
|
||||
.shouldMatch("Re-run jarsigner with the -verbose option for more details");
|
||||
verify(file, "-verbose")
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldHaveExitValue(16)
|
||||
.shouldContain("treated as unsigned")
|
||||
.shouldMatch("weak algorithm that is now disabled by")
|
||||
.shouldMatch("Digest algorithm: .*weak")
|
||||
@ -465,14 +555,14 @@ public class TimestampCheck {
|
||||
.shouldNotMatch("Timestamp signature algorithm: .*weak.*weak")
|
||||
.shouldMatch("Timestamp signature algorithm: .*key.*weak");
|
||||
verify(file, "-J-Djava.security.debug=jar")
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldHaveExitValue(16)
|
||||
.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")
|
||||
String sout = SecurityTools.keytool("-printcert -jarfile " + file)
|
||||
.stderrShouldContain("The TSA certificate uses a 512-bit RSA key" +
|
||||
" which is considered a security risk.")
|
||||
.getStdout();
|
||||
@ -481,14 +571,14 @@ public class TimestampCheck {
|
||||
}
|
||||
}
|
||||
|
||||
static void checkHalfWeak(String file) throws Throwable {
|
||||
static void checkHalfWeak(String file) throws Exception {
|
||||
verify(file)
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldHaveExitValue(16)
|
||||
.shouldContain("treated as unsigned")
|
||||
.shouldMatch("weak algorithm that is now disabled.")
|
||||
.shouldMatch("Re-run jarsigner with the -verbose option for more details");
|
||||
verify(file, "-verbose")
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldHaveExitValue(16)
|
||||
.shouldContain("treated as unsigned")
|
||||
.shouldMatch("weak algorithm that is now disabled by")
|
||||
.shouldMatch("Digest algorithm: .*weak")
|
||||
@ -498,7 +588,7 @@ public class TimestampCheck {
|
||||
.shouldNotMatch("Timestamp signature algorithm: .*key.*weak");
|
||||
}
|
||||
|
||||
static void checkMultiple(String file) throws Throwable {
|
||||
static void checkMultiple(String file) throws Exception {
|
||||
verify(file)
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldContain("jar verified");
|
||||
@ -515,7 +605,7 @@ public class TimestampCheck {
|
||||
static void checkTimestamp(String file, String policyId, String digestAlg)
|
||||
throws Exception {
|
||||
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)) {
|
||||
byte[] content = is.readAllBytes();
|
||||
PKCS7 p7 = new PKCS7(content);
|
||||
@ -541,22 +631,32 @@ public class TimestampCheck {
|
||||
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
|
||||
*/
|
||||
static OutputAnalyzer sign(String path, String... extra)
|
||||
throws Throwable {
|
||||
String alias = path.equals("badku") ? "badku" : "old";
|
||||
return signWithAliasAndTsa(path, "old.jar", alias, extra);
|
||||
throws Exception {
|
||||
return signVerbose(
|
||||
path,
|
||||
"unsigned.jar",
|
||||
path + ".jar",
|
||||
"signer",
|
||||
extra);
|
||||
}
|
||||
|
||||
static OutputAnalyzer signWithAliasAndTsa (String path, String jar,
|
||||
String alias, String...extra) throws Throwable {
|
||||
static OutputAnalyzer signVerbose(
|
||||
String path, // TSA URL path
|
||||
String oldJar,
|
||||
String newJar,
|
||||
String alias, // signer
|
||||
String...extra) throws Exception {
|
||||
which++;
|
||||
System.err.println("\n>> Test #" + which + ": " + Arrays.toString(extra));
|
||||
List<String> args = List.of("-J-Djava.security.egd=file:/dev/./urandom",
|
||||
"-debug", "-signedjar", path + ".jar", jar, alias);
|
||||
args = new ArrayList<>(args);
|
||||
if (!path.equals("none") && !path.equals("badku")) {
|
||||
System.out.println("\n>> Test #" + which);
|
||||
List<String> args = new ArrayList<>(List.of(
|
||||
"-strict", "-verbose", "-debug", "-signedjar", newJar, oldJar, alias));
|
||||
if (path != null) {
|
||||
args.add("-tsa");
|
||||
args.add(host + path);
|
||||
}
|
||||
@ -565,24 +665,50 @@ public class TimestampCheck {
|
||||
}
|
||||
|
||||
static void prepare() throws Exception {
|
||||
JarUtils.createJar("old.jar", "A");
|
||||
Files.deleteIfExists(Paths.get("tsks"));
|
||||
keytool("-alias ca -genkeypair -ext bc -dname CN=CA");
|
||||
keytool("-alias old -genkeypair -dname CN=old");
|
||||
JarUtils.createJar("unsigned.jar", "A");
|
||||
Files.deleteIfExists(Paths.get("ks"));
|
||||
keytool("-alias signer -genkeypair -ext bc -dname CN=signer");
|
||||
keytool("-alias oldsigner -genkeypair -dname CN=oldsigner");
|
||||
keytool("-alias dsakey -genkeypair -keyalg DSA -dname CN=dsakey");
|
||||
keytool("-alias weakkeysize -genkeypair -keysize 512 -dname CN=weakkeysize");
|
||||
keytool("-alias badku -genkeypair -dname CN=badku");
|
||||
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 tsbad2 -genkeypair -dname CN=tsbad2");
|
||||
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("weakkeysize");
|
||||
gencert("badku", "-ext ku:critical=keyAgreement");
|
||||
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("tsbad1");
|
||||
gencert("tsbad2", "-ext eku=ts");
|
||||
@ -601,7 +727,7 @@ public class TimestampCheck {
|
||||
}
|
||||
|
||||
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;
|
||||
sun.security.tools.keytool.Main.main(cmd.split(" "));
|
||||
}
|
||||
|
@ -83,14 +83,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();
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# 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=$?
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 2015, 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.";
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# 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
|
||||
@ -54,9 +54,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
|
||||
|
@ -52,10 +52,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
|
||||
@ -72,7 +69,7 @@ public class SecurityTools {
|
||||
pb.redirectInput(ProcessBuilder.Redirect.from(new File(RESPONSE_FILE)));
|
||||
|
||||
try {
|
||||
return ProcessTools.executeProcess(pb);
|
||||
return execute(pb);
|
||||
} finally {
|
||||
Files.delete(p);
|
||||
}
|
||||
@ -102,8 +99,21 @@ public class SecurityTools {
|
||||
|
||||
public static OutputAnalyzer jarsigner(List<String> args)
|
||||
throws Exception {
|
||||
return ProcessTools.executeProcess(
|
||||
getProcessBuilder("jarsigner", args));
|
||||
return execute(getProcessBuilder("jarsigner", args));
|
||||
}
|
||||
|
||||
private static OutputAnalyzer execute(ProcessBuilder pb) throws Exception {
|
||||
try {
|
||||
OutputAnalyzer oa = ProcessTools.executeCommand(pb);
|
||||
System.out.println("Exit value: " + oa.getExitValue());
|
||||
return oa;
|
||||
} catch (Throwable t) {
|
||||
if (t instanceof Exception) {
|
||||
throw (Exception) t;
|
||||
} else {
|
||||
throw new Exception(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only call this if there is no white space in every argument
|
||||
|
@ -27,9 +27,13 @@ import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
@ -79,70 +83,93 @@ public final class JarUtils {
|
||||
*/
|
||||
public static void updateJar(String src, String dest, String... files)
|
||||
throws IOException {
|
||||
Map<String,Object> changes = new HashMap<>();
|
||||
boolean update = true;
|
||||
for (String file : files) {
|
||||
if (file.equals("-")) {
|
||||
update = false;
|
||||
} else if (update) {
|
||||
try {
|
||||
Path p = Paths.get(file);
|
||||
if (Files.exists(p)) {
|
||||
changes.put(file, p);
|
||||
} else {
|
||||
changes.put(file, file);
|
||||
}
|
||||
} catch (InvalidPathException e) {
|
||||
// Fallback if file not a valid Path.
|
||||
changes.put(file, file);
|
||||
}
|
||||
} else {
|
||||
changes.put(file, Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
updateJar(src, dest, changes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update content of a jar file.
|
||||
*
|
||||
* @param src the original jar file name
|
||||
* @param dest the new jar file name
|
||||
* @param changes a map of changes, key is jar entry name, value is content.
|
||||
* Value can be Path, byte[] or String. If key exists in
|
||||
* src but value is Boolean FALSE. The entry is removed.
|
||||
* Existing entries in src not a key is unmodified.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void updateJar(String src, String dest,
|
||||
Map<String,Object> changes)
|
||||
throws IOException {
|
||||
|
||||
// What if input changes is immutable?
|
||||
changes = new HashMap<>(changes);
|
||||
|
||||
System.out.printf("Creating %s from %s...\n", dest, src);
|
||||
try (JarOutputStream jos = new JarOutputStream(
|
||||
new FileOutputStream(dest))) {
|
||||
|
||||
// copy each old entry into destination unless the entry name
|
||||
// is in the updated list
|
||||
List<String> updatedFiles = new ArrayList<>();
|
||||
try (JarFile srcJarFile = new JarFile(src)) {
|
||||
Enumeration<JarEntry> entries = srcJarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
String name = entry.getName();
|
||||
boolean found = false;
|
||||
boolean update = true;
|
||||
for (String file : files) {
|
||||
if (file.equals("-")) {
|
||||
update = false;
|
||||
} else if (name.equals(file)) {
|
||||
updatedFiles.add(file);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
if (update) {
|
||||
System.out.println(String.format("Updating %s with %s",
|
||||
dest, name));
|
||||
jos.putNextEntry(new JarEntry(name));
|
||||
try (FileInputStream fis = new FileInputStream(name)) {
|
||||
fis.transferTo(jos);
|
||||
} catch (FileNotFoundException e) {
|
||||
jos.write(name.getBytes());
|
||||
}
|
||||
} else {
|
||||
System.out.println(String.format("Removing %s from %s",
|
||||
name, dest));
|
||||
}
|
||||
if (changes.containsKey(name)) {
|
||||
System.out.println(String.format("- Update %s", name));
|
||||
updateEntry(jos, name, changes.get(name));
|
||||
changes.remove(name);
|
||||
} else {
|
||||
System.out.println(String.format("Copying %s to %s",
|
||||
name, dest));
|
||||
System.out.println(String.format("- Copy %s", name));
|
||||
jos.putNextEntry(entry);
|
||||
srcJarFile.getInputStream(entry).transferTo(jos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// append new files
|
||||
for (String file : files) {
|
||||
if (file.equals("-")) {
|
||||
break;
|
||||
}
|
||||
if (!updatedFiles.contains(file)) {
|
||||
System.out.println(String.format("Adding %s with %s",
|
||||
dest, file));
|
||||
jos.putNextEntry(new JarEntry(file));
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
fis.transferTo(jos);
|
||||
} catch (FileNotFoundException e) {
|
||||
jos.write(file.getBytes());
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, Object> e : changes.entrySet()) {
|
||||
System.out.println(String.format("- Add %s", e.getKey()));
|
||||
updateEntry(jos, e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
private static void updateEntry(JarOutputStream jos, String name, Object content)
|
||||
throws IOException {
|
||||
if (content instanceof Boolean) {
|
||||
if (((Boolean) content).booleanValue()) {
|
||||
throw new RuntimeException("Boolean value must be FALSE");
|
||||
}
|
||||
} else {
|
||||
jos.putNextEntry(new JarEntry(name));
|
||||
if (content instanceof Path) {
|
||||
Files.newInputStream((Path) content).transferTo(jos);
|
||||
} else if (content instanceof byte[]) {
|
||||
jos.write((byte[]) content);
|
||||
} else if (content instanceof String) {
|
||||
jos.write(((String) content).getBytes());
|
||||
} else {
|
||||
throw new RuntimeException("Unknown type " + content.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user