232 lines
10 KiB
Java

/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Files;
import java.nio.file.Path;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorException.BasicReason;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.security.cert.X509CRL;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import sun.security.x509.AuthorityKeyIdentifierExtension;
import sun.security.x509.CRLDistributionPointsExtension;
import sun.security.x509.CRLExtensions;
import sun.security.x509.CRLNumberExtension;
import sun.security.x509.DistributionPoint;
import sun.security.x509.Extension;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNames;
import sun.security.x509.KeyIdentifier;
import sun.security.x509.URIName;
import sun.security.x509.X500Name;
import sun.security.x509.X509CRLEntryImpl;
import sun.security.x509.X509CRLImpl;
import static sun.security.x509.X509CRLImpl.TBSCertList;
import sun.security.testlibrary.CertificateBuilder;
/*
* @test
* @bug 8200566
* @summary Check that CRL validation continues to check other CRLs in
* CRLDP extension after CRL fetching errors and exhibits same
* behavior (fails because cert is revoked) whether CRL cache is
* fresh or stale.
* @modules java.base/sun.security.x509
* java.base/sun.security.util
* @library ../../../../../java/security/testlibrary
* @build CertificateBuilder CheckAllCRLs
* @run main/othervm -Dcom.sun.security.enableCRLDP=true CheckAllCRLs
*/
public class CheckAllCRLs {
public static void main(String[] args) throws Exception {
CertificateBuilder cb = new CertificateBuilder();
// Create CA cert
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
KeyPair rootKeyPair = kpg.genKeyPair();
X509Certificate rootCert = createCert(cb, "CN=Root CA",
rootKeyPair, rootKeyPair, null, "SHA384withRSA", true, false);
// Create EE cert. This EE cert will contain a CRL Distribution
// Points extension with two DistributionPoints - one will be a HTTP
// URL to a non-existant HTTP server, and the other will be a File
// URL to a file containing the CRL.
KeyPair eeKeyPair = kpg.genKeyPair();
X509Certificate eeCert1 = createCert(cb, "CN=End Entity",
rootKeyPair, eeKeyPair, rootCert, "SHA384withRSA", false, true);
// Create another EE cert. This EE cert is similar in that it contains
// a CRL Distribution Points extension but with one DistributionPoint
// containing 2 GeneralName URLs as above.
X509Certificate eeCert2 = createCert(cb, "CN=End Entity",
rootKeyPair, eeKeyPair, rootCert, "SHA384withRSA", false, false);
// Create a CRL with no revoked certificates and store it in a file
X509CRL crl = createCRL(new X500Name("CN=Root CA"), rootKeyPair,
"SHA384withRSA");
Files.write(Path.of("root.crl"), crl.getEncoded());
// Validate path containing eeCert1
System.out.println("Validating cert with CRLDP containing one "
+ "DistributionPoint with 2 entries, the first non-existent");
validatePath(eeCert1, rootCert);
// Validate path containing eeCert2
System.out.println("Validating cert with CRLDP containing two "
+ "DistributionPoints with 1 entry each, the first non-existent");
validatePath(eeCert2, rootCert);
}
private static X509Certificate createCert(CertificateBuilder cb,
String subjectDN, KeyPair issuerKeyPair, KeyPair subjectKeyPair,
X509Certificate issuerCert, String sigAlg, boolean isCA,
boolean twoDPs) throws Exception {
cb.setSubjectName(subjectDN);
cb.setPublicKey(subjectKeyPair.getPublic());
cb.setSerialNumber(new BigInteger("1"));
if (isCA) {
// Make a 3 year validity starting from 60 days ago
long start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60);
long end = start + TimeUnit.DAYS.toMillis(1085);
cb.setValidity(new Date(start), new Date(end));
cb.addBasicConstraintsExt(true, true, -1);
cb.addKeyUsageExt(new boolean[]
{false, false, false, false, false, true, true, false, false});
} else {
// Make a 1 year validity starting from 7 days ago
long start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7);
long end = start + TimeUnit.DAYS.toMillis(365);
cb.setValidity(new Date(start), new Date(end));
cb.addAuthorityKeyIdExt(issuerKeyPair.getPublic());
cb.addKeyUsageExt(new boolean[]
{true, false, false, false, false, false, false, false, false});
cb.addExtendedKeyUsageExt(List.of("1.3.6.1.5.5.7.3.1"));
GeneralName first = new GeneralName(new URIName(
"http://127.0.0.1:48180/crl/will/always/fail/root.crl"));
GeneralName second = new GeneralName(new URIName("file:./root.crl"));
if (twoDPs) {
GeneralNames gn1 = new GeneralNames().add(first);
DistributionPoint dp1 = new DistributionPoint(gn1, null, null);
GeneralNames gn2 = new GeneralNames().add(second);
DistributionPoint dp2 = new DistributionPoint(gn2, null, null);
cb.addExtension(new CRLDistributionPointsExtension(List.of(dp1, dp2)));
} else {
GeneralNames gn = new GeneralNames().add(first).add(second);
DistributionPoint dp = new DistributionPoint(gn, null, null);
cb.addExtension(new CRLDistributionPointsExtension(List.of(dp)));
}
}
cb.addSubjectKeyIdExt(subjectKeyPair.getPublic());
// return signed cert
return cb.build(issuerCert, issuerKeyPair.getPrivate(), sigAlg);
}
private static X509CRL createCRL(X500Name caIssuer, KeyPair caKeyPair,
String sigAlg) throws Exception {
CRLExtensions crlExts = new CRLExtensions();
// add AuthorityKeyIdentifier extension
KeyIdentifier kid = new KeyIdentifier(caKeyPair.getPublic());
Extension ext = new AuthorityKeyIdentifierExtension(kid, null, null);
crlExts.setExtension(ext.getId(),
new AuthorityKeyIdentifierExtension(kid, null, null));
// add CRLNumber extension
ext = new CRLNumberExtension(1);
crlExts.setExtension(ext.getId(), ext);
// revoke cert
X509CRLEntryImpl crlEntry =
new X509CRLEntryImpl(new BigInteger("1"), new Date());
// Create a 1 year validity CRL starting from 7 days ago
long start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7);
long end = start + TimeUnit.DAYS.toMillis(365);
TBSCertList tcl = new TBSCertList(caIssuer, new Date(start),
new Date(end), new X509CRLEntryImpl[]{ crlEntry }, crlExts);
// return signed CRL
return X509CRLImpl.newSigned(tcl, caKeyPair.getPrivate(), sigAlg);
}
private static void validatePath(X509Certificate eeCert,
X509Certificate rootCert) throws Exception {
// Create certification path and set up PKIXParameters.
CertificateFactory cf = CertificateFactory.getInstance("X.509");
CertPath cp = cf.generateCertPath(List.of(eeCert));
PKIXParameters pp =
new PKIXParameters(Set.of(new TrustAnchor(rootCert, null)));
pp.setRevocationEnabled(true);
CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
// Validate path twice in succession, making sure we get consistent
// results the second time when the CRL cache is fresh.
System.out.println("First time validating path");
validate(cpv, cp, pp);
System.out.println("Second time validating path");
validate(cpv, cp, pp);
// CRL lookup cache time is 30s. Sleep for 35 seconds to ensure
// cache is stale, and validate one more time to ensure we get
// consistent results.
System.out.println("Waiting for CRL cache to be cleared");
Thread.sleep(30500);
System.out.println("Third time validating path");
validate(cpv, cp, pp);
}
private static void validate(CertPathValidator cpv, CertPath cp,
PKIXParameters pp) throws Exception {
try {
cpv.validate(cp, pp);
throw new Exception("Validation passed unexpectedly");
} catch (CertPathValidatorException cpve) {
if (cpve.getReason() != BasicReason.REVOKED) {
throw new Exception("Validation failed with unexpected reason", cpve);
}
System.out.println("Validation failed as expected: " + cpve);
}
}
}