| /* | |
| * Copyright (c) 2024, 2025, 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.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 jdk.test.lib.security.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 /test/lib | |
| * @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); | |
| } | |
| } | |
| } |