/* * Copyright (c) 2020, 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. */ /** * @test * @bug 8179503 8328638 * @summary Java should support GET OCSP calls * @library /javax/net/ssl/templates /java/security/testlibrary * @build SimpleOCSPServer * @modules java.base/sun.security.util * java.base/sun.security.provider.certpath * java.base/sun.security.x509 * @run main/othervm GetAndPostTests * @run main/othervm -Dcom.sun.security.ocsp.useget=false GetAndPostTests * @run main/othervm -Dcom.sun.security.ocsp.useget=foo GetAndPostTests */ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyStore; import java.security.PrivateKey; import java.security.SecureRandom; import java.security.cert.CertPath; import java.security.cert.CertPathValidator; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.Extension; import java.security.cert.PKIXCertPathChecker; import java.security.cert.PKIXParameters; import java.security.cert.PKIXRevocationChecker; import java.security.cert.TrustAnchor; import java.security.cert.X509Certificate; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; import sun.security.testlibrary.SimpleOCSPServer; import sun.security.util.DerOutputStream; import sun.security.util.DerValue; import sun.security.util.ObjectIdentifier; public class GetAndPostTests { private static final String PASS = "passphrase"; private static final int SERVER_WAIT_SECS = 60; private static CertificateFactory certFac; public static void main(String args[]) throws Exception { SimpleOCSPServer ocspResponder = null; try { certFac = CertificateFactory.getInstance("X.509"); // Read in the certificates and keys needed for this test and // create the keystore for the SimpleOCSPServer. For the purposes // of this test, the CA certificate will also be the OCSP responder // signing certificate. SSLSocketTemplate.Cert certAuth = SSLSocketTemplate.Cert.CA_ECDSA_SECP256R1; X509Certificate caCert = pem2Cert(certAuth.certStr); PrivateKey caKey = pem2Key(certAuth.privKeyStr, certAuth.keyAlgo); X509Certificate endEntCert = pem2Cert(SSLSocketTemplate.Cert.EE_ECDSA_SECP256R1.certStr); KeyStore.Builder keyStoreBuilder = KeyStore.Builder.newInstance("PKCS12", null, new KeyStore.PasswordProtection(PASS.toCharArray())); KeyStore ocspStore = keyStoreBuilder.getKeyStore(); Certificate[] ocspChain = {caCert}; ocspStore.setKeyEntry("ocspsigner", caKey, PASS.toCharArray(), ocspChain); // Create the certificate path we'll use for cert path validation. CertPath path = certFac.generateCertPath(List.of(endEntCert)); // Next, create and start the OCSP responder. Obtain the socket // address so we can set that in the PKIXRevocationChecker since // these certificates do not have AIA extensions on them. ocspResponder = new SimpleOCSPServer(ocspStore, PASS, "ocspsigner", null); ocspResponder.setSignatureAlgorithm("SHA256WithECDSA"); ocspResponder.enableLog(true); ocspResponder.setNextUpdateInterval(3600); ocspResponder.updateStatusDb(Map.of( endEntCert.getSerialNumber(), new SimpleOCSPServer.CertStatusInfo( SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD))); startOcspServer(ocspResponder); int ocspPort = ocspResponder.getPort(); URI ocspURI = new URI("http://localhost:" + ocspPort); System.out.println("Configured CPV to connect to " + ocspURI); // Create the PKIXParameters needed for path validation and // configure any necessary OCSP parameters to control the OCSP // request size. Set anchors = Set.of(new TrustAnchor(caCert, null)); CertPathValidator validator = CertPathValidator.getInstance("PKIX"); PKIXRevocationChecker revChkr = (PKIXRevocationChecker)validator.getRevocationChecker(); revChkr.setOcspResponder(ocspURI); revChkr.setOptions(Set.of( PKIXRevocationChecker.Option.ONLY_END_ENTITY, PKIXRevocationChecker.Option.NO_FALLBACK)); PKIXParameters params = new PKIXParameters(anchors); params.setRevocationEnabled(true); params.setDate(new Date(1590926400000L)); // 05/31/2020 @ 12:00:00Z params.addCertPathChecker(revChkr); System.out.println("Test 1: Request < 255 bytes, HTTP GET"); validator.validate(path, params); System.out.println("Test 2: Request > 255 bytes, HTTP POST"); // Modify the PKIXRevocationChecker to include a bogus non-critical // request extension that makes the request large enough to be // issued as an HTTP POST. List chkrList = params.getCertPathCheckers(); for (PKIXCertPathChecker chkr : chkrList) { if (chkr instanceof PKIXRevocationChecker) { ((PKIXRevocationChecker)chkr).setOcspExtensions( List.of(new BogusExtension("1.2.3.4.5.6.7.8.9", false, 256))); } } params.setCertPathCheckers(chkrList); validator.validate(path, params); } finally { if (ocspResponder != null) { ocspResponder.stop(); } } } private static void startOcspServer(SimpleOCSPServer ocspResponder) throws InterruptedException, IOException { ocspResponder.start(); if (!ocspResponder.awaitServerReady(SERVER_WAIT_SECS, TimeUnit.SECONDS)) { throw new RuntimeException("Server not ready after " + SERVER_WAIT_SECS + " seconds."); } } /** * Create an X509Certificate object from its PEM encoding * * @param pemCert the base64 encoded certificate * * @return the corresponding X509Certificate object from the PEM encoding. * * @throws IOException if any InputStream or Base64 decoding failures occur. * @throws CertificateException if any certificate parsing errors occur. */ private static X509Certificate pem2Cert(String pemCert) throws IOException, CertificateException { return (X509Certificate)certFac.generateCertificate( new ByteArrayInputStream(pemCert.getBytes())); } /** * Create a private key from its PEM-encoded PKCS#8 representation. * * @param pemKey the private key in PEM-encoded PKCS#8 unencrypted format * @param algorithm the private key algorithm * * @return the PrivateKey extracted from the PKCS#8 encoding. * * @throws GeneralSecurityException if any errors take place during * decoding or parsing. */ private static PrivateKey pem2Key(String pemKey, String algorithm) throws GeneralSecurityException { byte[] p8Der = Base64.getMimeDecoder().decode(pemKey); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(p8Der, algorithm); KeyFactory kf = KeyFactory.getInstance(algorithm); return kf.generatePrivate(spec); } /** * The BogusOcspExtension is an extension with random data in the * extension value field. It is used in this test to expand the size * of the OCSP request so it crosses the boundary that forces an HTTP * POST operation instead of a GET. */ private static class BogusExtension implements Extension { private final ObjectIdentifier oid; private final boolean critical; private final byte[] data; public BogusExtension(String oidStr, boolean isCrit, int size) throws IOException { // For this test we don't need anything larger than 10K if (size > 0 && size <= 10240) { data = new byte[size]; } else { throw new IllegalArgumentException( "Size must be 0 < X <= 10240"); } oid = ObjectIdentifier.of(oidStr); SecureRandom sr = new SecureRandom(); sr.nextBytes(data); critical = isCrit; } @Override public String getId() { return oid.toString(); } @Override public boolean isCritical() { return critical; } @Override public byte[] getValue() { return data.clone(); } @Override public void encode(OutputStream out) throws IOException { Objects.requireNonNull(out, "Non-null OutputStream required"); DerOutputStream dos1 = new DerOutputStream(); DerOutputStream dos2 = new DerOutputStream(); dos1.putOID(oid); if (critical) { dos1.putBoolean(critical); } dos1.putOctetString(data); dos2.write(DerValue.tag_Sequence, dos1); out.write(dos2.toByteArray()); } } }