changeset 14140:896bbc5499ff

8245473: OCSP stapling support Reviewed-by: mbalao
author abakhtin
date Tue, 25 Aug 2020 08:30:00 -0700
parents b58fdaa80b5a
children 80cdee1da353
files src/share/classes/sun/security/provider/certpath/OCSPNonceExtension.java src/share/classes/sun/security/ssl/SSLContextImpl.java src/share/classes/sun/security/ssl/SSLSessionImpl.java src/share/classes/sun/security/ssl/X509TrustManagerImpl.java src/share/classes/sun/security/validator/PKIXValidator.java src/share/classes/sun/security/validator/SimpleValidator.java src/share/classes/sun/security/validator/Validator.java
diffstat 7 files changed, 272 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/sun/security/provider/certpath/OCSPNonceExtension.java	Tue Aug 25 08:30:00 2020 -0700
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2015, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package sun.security.provider.certpath;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.security.SecureRandom;
+
+import sun.security.x509.Extension;
+import sun.security.x509.PKIXExtensions;
+import sun.security.util.Debug;
+import sun.security.util.DerValue;
+
+/**
+ * Represent the OCSP Nonce Extension.
+ * This extension, if present, provides a nonce value in OCSP requests
+ * and responses.  This will cryptographically bind requests and responses
+ * and help to prevent replay attacks (see RFC 6960, section 4.4.1).
+ *
+ * @see Extension
+ */
+public final class OCSPNonceExtension extends Extension {
+
+    /**
+     * Attribute name.
+     */
+    private static final String EXTENSION_NAME = "OCSPNonce";
+    private byte[] nonceData = null;
+
+    /**
+     * Create an {@code OCSPNonceExtension} by providing the nonce length.
+     * The criticality is set to false, and the OID for the extension will
+     * be the value defined by "id-pkix-ocsp-nonce" from RFC 6960.
+     *
+     * @param length the number of random bytes composing the nonce
+     *
+     * @throws IOException if any errors happen during encoding of the
+     *      extension.
+     * @throws IllegalArgumentException if length is not a positive integer.
+     */
+    public OCSPNonceExtension(int length) throws IOException {
+        this(false, length);
+    }
+
+    /**
+     * Create an {@code OCSPNonceExtension} by providing the nonce length and
+     * criticality setting.  The OID for the extension will
+     * be the value defined by "id-pkix-ocsp-nonce" from RFC 6960.
+     *
+     * @param isCritical a boolean flag indicating whether the criticality bit
+     *      is set for this extension
+     * @param length the number of random bytes composing the nonce
+     *
+     * @throws IOException if any errors happen during encoding of the
+     *      extension.
+     * @throws IllegalArgumentException if length is not a positive integer.
+     */
+    public OCSPNonceExtension(boolean isCritical, int length)
+            throws IOException {
+        this.extensionId = PKIXExtensions.OCSPNonce_Id;
+        this.critical = isCritical;
+
+        if (length > 0) {
+            SecureRandom rng = new SecureRandom();
+            this.nonceData = new byte[length];
+            rng.nextBytes(nonceData);
+            this.extensionValue = new DerValue(DerValue.tag_OctetString,
+                    nonceData).toByteArray();
+        } else {
+            throw new IllegalArgumentException(
+                    "Length must be a positive integer");
+        }
+    }
+
+    /**
+     * Create an {@code OCSPNonceExtension} by providing a nonce value.
+     * The criticality is set to false, and the OID for the extension will
+     * be the value defined by "id-pkix-ocsp-nonce" from RFC 6960.
+     *
+     * @param incomingNonce The nonce data to be set for the extension.  This
+     *      must be a non-null array of at least one byte long.
+     *
+     * @throws IOException if any errors happen during encoding of the
+     *      extension.
+     * @throws IllegalArgumentException if the incomingNonce length is not a
+     *      positive integer.
+     * @throws NullPointerException if the incomingNonce is null.
+     */
+    public OCSPNonceExtension(byte[] incomingNonce) throws IOException {
+        this(false, incomingNonce);
+    }
+
+    /**
+     * Create an {@code OCSPNonceExtension} by providing a nonce value and
+     * criticality setting.  The OID for the extension will
+     * be the value defined by "id-pkix-ocsp-nonce" from RFC 6960.
+     *
+     * @param isCritical a boolean flag indicating whether the criticality bit
+     *      is set for this extension
+     * @param incomingNonce The nonce data to be set for the extension.  This
+     *      must be a non-null array of at least one byte long.
+     *
+     * @throws IOException if any errors happen during encoding of the
+     *      extension.
+     * @throws IllegalArgumentException if the incomingNonce length is not a
+     *      positive integer.
+     * @throws NullPointerException if the incomingNonce is null.
+     */
+    public OCSPNonceExtension(boolean isCritical, byte[] incomingNonce)
+            throws IOException {
+        this.extensionId = PKIXExtensions.OCSPNonce_Id;
+        this.critical = isCritical;
+
+        Objects.requireNonNull(incomingNonce, "Nonce data must be non-null");
+        if (incomingNonce.length > 0) {
+            this.nonceData = incomingNonce.clone();
+            this.extensionValue = new DerValue(DerValue.tag_OctetString,
+                    nonceData).toByteArray();
+        } else {
+            throw new IllegalArgumentException(
+                    "Nonce data must be at least 1 byte in length");
+        }
+    }
+
+    /**
+     * Return the nonce bytes themselves, without any DER encoding.
+     *
+     * @return A copy of the underlying nonce bytes
+     */
+    public byte[] getNonceValue() {
+        return nonceData.clone();
+    }
+
+    /**
+     * Returns a printable representation of the {@code OCSPNonceExtension}.
+     *
+     * @return a string representation of the extension.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(super.toString()).append(EXTENSION_NAME).append(": ");
+        sb.append((nonceData == null) ? "" : Debug.toString(nonceData));
+        sb.append("\n");
+        return sb.toString();
+    }
+
+    /**
+     * Return the name of the extension as a {@code String}
+     *
+     * @return the name of the extension
+     */
+    public String getName() {
+        return EXTENSION_NAME;
+    }
+}
--- a/src/share/classes/sun/security/ssl/SSLContextImpl.java	Tue Aug 25 09:23:47 2020 +0300
+++ b/src/share/classes/sun/security/ssl/SSLContextImpl.java	Tue Aug 25 08:30:00 2020 -0700
@@ -59,7 +59,7 @@
     private volatile HelloCookieManager.Builder helloCookieManagerBuilder;
 
     private final boolean clientEnableStapling = Utilities.getBooleanProperty(
-            "jdk.tls.client.enableStatusRequestExtension", true);
+            "jdk.tls.client.enableStatusRequestExtension", false);
     private final boolean serverEnableStapling = Utilities.getBooleanProperty(
             "jdk.tls.server.enableStatusRequestExtension", false);
     private static final Collection<CipherSuite> clientCustomizedCipherSuites =
--- a/src/share/classes/sun/security/ssl/SSLSessionImpl.java	Tue Aug 25 09:23:47 2020 +0300
+++ b/src/share/classes/sun/security/ssl/SSLSessionImpl.java	Tue Aug 25 08:30:00 2020 -0700
@@ -656,7 +656,6 @@
      * been presented by the server or non-certificate based server
      * authentication is used then an empty {@code List} is returned.
      */
-    @Override
     public List<byte[]> getStatusResponses() {
         if (statusResponses == null || statusResponses.isEmpty()) {
             return Collections.emptyList();
--- a/src/share/classes/sun/security/ssl/X509TrustManagerImpl.java	Tue Aug 25 09:23:47 2020 +0300
+++ b/src/share/classes/sun/security/ssl/X509TrustManagerImpl.java	Tue Aug 25 08:30:00 2020 -0700
@@ -215,9 +215,10 @@
 
             // Grab any stapled OCSP responses for use in validation
             List<byte[]> responseList = Collections.emptyList();
-            if (!checkClientTrusted && isExtSession) {
+            if (!checkClientTrusted && isExtSession &&
+                    session instanceof SSLSessionImpl) {
                 responseList =
-                        ((ExtendedSSLSession)session).getStatusResponses();
+                        ((SSLSessionImpl)session).getStatusResponses();
             }
              trustedChain = validate(v, chain, responseList,
                     constraints, checkClientTrusted ? null : authType);
@@ -269,9 +270,10 @@
 
             // Grab any stapled OCSP responses for use in validation
             List<byte[]> responseList = Collections.emptyList();
-            if (!checkClientTrusted && isExtSession) {
+            if (!checkClientTrusted && isExtSession &&
+                session instanceof SSLSessionImpl) {
                 responseList =
-                        ((ExtendedSSLSession)session).getStatusResponses();
+                        ((SSLSessionImpl)session).getStatusResponses();
             }
              trustedChain = validate(v, chain, responseList,
                     constraints, checkClientTrusted ? null : authType);
--- a/src/share/classes/sun/security/validator/PKIXValidator.java	Tue Aug 25 09:23:47 2020 +0300
+++ b/src/share/classes/sun/security/validator/PKIXValidator.java	Tue Aug 25 08:30:00 2020 -0700
@@ -215,6 +215,7 @@
     @Override
     X509Certificate[] engineValidate(X509Certificate[] chain,
             Collection<X509Certificate> otherCerts,
+            List<byte[]> responseList,
             AlgorithmConstraints constraints,
             Object parameter) throws CertificateException {
         if ((chain == null) || (chain.length == 0)) {
@@ -241,6 +242,11 @@
                     new AlgorithmChecker(constraints, null, variant));
         }
 
+        // attach it to the PKIXBuilderParameters.
+        if (!responseList.isEmpty()) {
+            addResponses(pkixParameters, chain, responseList);
+        }
+
         if (TRY_VALIDATOR) {
             // check that chain is in correct order and check if chain contains
             // trust anchor
@@ -450,4 +456,70 @@
                 ("PKIX path building failed: " + e.toString(), e);
         }
     }
+
+    /**
+     * For OCSP Stapling, add responses that came in during the handshake
+     * into a {@code PKIXRevocationChecker} so we can evaluate them.
+     *
+     * @param pkixParams the pkixParameters object that will be used in
+     * path validation.
+     * @param chain the chain of certificates to verify
+     * @param responseList a {@code List} of zero or more byte arrays, each
+     * one being a DER-encoded OCSP response (per RFC 6960).  Entries
+     * in the List must match the order of the certificates in the
+     * chain parameter.
+     */
+    private static void addResponses(PKIXBuilderParameters pkixParams,
+            X509Certificate[] chain, List<byte[]> responseList) {
+
+        if (pkixParams.isRevocationEnabled()) {
+            try {
+                // Make a modifiable copy of the CertPathChecker list
+                PKIXRevocationChecker revChecker = null;
+                List<PKIXCertPathChecker> checkerList =
+                        new ArrayList<>(pkixParams.getCertPathCheckers());
+
+                // Find the first PKIXRevocationChecker in the list
+                for (PKIXCertPathChecker checker : checkerList) {
+                    if (checker instanceof PKIXRevocationChecker) {
+                        revChecker = (PKIXRevocationChecker)checker;
+                        break;
+                    }
+                }
+
+                // If we still haven't found one, make one
+                if (revChecker == null) {
+                    revChecker = (PKIXRevocationChecker)CertPathValidator.
+                            getInstance("PKIX").getRevocationChecker();
+                    checkerList.add(revChecker);
+                }
+
+                // Each response in the list should be in parallel with
+                // the certificate list.  If there is a zero-length response
+                // treat it as being absent.  If the user has provided their
+                // own PKIXRevocationChecker with pre-populated responses, do
+                // not overwrite them with the ones from the handshake.
+                Map<X509Certificate, byte[]> responseMap =
+                        revChecker.getOcspResponses();
+                int limit = Integer.min(chain.length, responseList.size());
+                for (int idx = 0; idx < limit; idx++) {
+                    byte[] respBytes = responseList.get(idx);
+                    if (respBytes != null && respBytes.length > 0 &&
+                            !responseMap.containsKey(chain[idx])) {
+                        responseMap.put(chain[idx], respBytes);
+                    }
+                }
+
+                // Add the responses and push it all back into the
+                // PKIXBuilderParameters
+                revChecker.setOcspResponses(responseMap);
+                pkixParams.setCertPathCheckers(checkerList);
+            } catch (NoSuchAlgorithmException exc) {
+                // This should not occur, but if it does happen then
+                // stapled OCSP responses won't be part of revocation checking.
+                // Clients can still fall back to other means of revocation
+                // checking.
+            }
+        }
+    }
 }
--- a/src/share/classes/sun/security/validator/SimpleValidator.java	Tue Aug 25 09:23:47 2020 +0300
+++ b/src/share/classes/sun/security/validator/SimpleValidator.java	Tue Aug 25 08:30:00 2020 -0700
@@ -123,6 +123,7 @@
     @Override
     X509Certificate[] engineValidate(X509Certificate[] chain,
             Collection<X509Certificate> otherCerts,
+            List<byte[]> responseList,
             AlgorithmConstraints constraints,
             Object parameter) throws CertificateException {
         if ((chain == null) || (chain.length == 0)) {
--- a/src/share/classes/sun/security/validator/Validator.java	Tue Aug 25 09:23:47 2020 +0300
+++ b/src/share/classes/sun/security/validator/Validator.java	Tue Aug 25 08:30:00 2020 -0700
@@ -235,7 +235,8 @@
     public final X509Certificate[] validate(X509Certificate[] chain,
             Collection<X509Certificate> otherCerts, Object parameter)
             throws CertificateException {
-        return validate(chain, otherCerts, null, parameter);
+        return validate(chain, otherCerts, Collections.emptyList(), null,
+                parameter);
     }
 
     /**
@@ -244,6 +245,13 @@
      * @param chain the target certificate chain
      * @param otherCerts a Collection of additional X509Certificates that
      *        could be helpful for path building (or null)
+     * @param responseList a List of zero or more byte arrays, each
+     *        one being a DER-encoded OCSP response (per RFC 6960).  Entries
+     *        in the List must match the order of the certificates in the
+     *        chain parameter.  It is possible that fewer responses may be
+     *        in the list than are elements in {@code chain} and a missing
+     *        response for a matching element in {@code chain} can be
+     *        represented with a zero-length byte array.
      * @param constraints algorithm constraints for certification path
      *        processing
      * @param parameter an additional parameter with variant specific meaning.
@@ -257,9 +265,11 @@
      */
     public final X509Certificate[] validate(X509Certificate[] chain,
                 Collection<X509Certificate> otherCerts,
+                List<byte[]> responseList,
                 AlgorithmConstraints constraints,
                 Object parameter) throws CertificateException {
-        chain = engineValidate(chain, otherCerts, constraints, parameter);
+        chain = engineValidate(chain, otherCerts, responseList, constraints,
+                parameter);
 
         // omit EE extension check if EE cert is also trust anchor
         if (chain.length > 1) {
@@ -280,6 +290,7 @@
 
     abstract X509Certificate[] engineValidate(X509Certificate[] chain,
                 Collection<X509Certificate> otherCerts,
+                List<byte[]> responseList,
                 AlgorithmConstraints constraints,
                 Object parameter) throws CertificateException;