< prev index next >

src/java.base/share/classes/sun/security/ssl/StatusResponseManager.java

Print this page

        

@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, 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

@@ -20,38 +20,50 @@
  *
  * 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.ssl;
 
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.security.AccessController;
-import java.security.cert.X509Certificate;
 import java.security.cert.Extension;
-import java.util.*;
-import java.util.concurrent.*;
-
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import sun.security.action.GetBooleanAction;
+import sun.security.action.GetIntegerAction;
+import sun.security.action.GetPropertyAction;
 import sun.security.provider.certpath.CertId;
 import sun.security.provider.certpath.OCSP;
 import sun.security.provider.certpath.OCSPResponse;
 import sun.security.provider.certpath.ResponderId;
 import sun.security.util.Cache;
 import sun.security.x509.PKIXExtensions;
 import sun.security.x509.SerialNumber;
-import sun.security.action.GetBooleanAction;
-import sun.security.action.GetIntegerAction;
-import sun.security.action.GetPropertyAction;
+import sun.security.ssl.X509Authentication.X509Possession;
+import static sun.security.ssl.CertStatusExtension.*;
 
 final class StatusResponseManager {
     private static final int DEFAULT_CORE_THREADS = 8;
     private static final int DEFAULT_CACHE_SIZE = 256;
     private static final int DEFAULT_CACHE_LIFETIME = 3600;         // seconds
-    private static final Debug debug = Debug.getInstance("ssl");
 
     private final ScheduledThreadPoolExecutor threadMgr;
     private final Cache<CertId, ResponseCacheEntry> responseCache;
     private final URI defaultResponder;
     private final boolean respOverride;

@@ -97,14 +109,16 @@
                 t.setDaemon(true);
                 return t;
             }
         }, new ThreadPoolExecutor.DiscardPolicy());
         threadMgr.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
-        threadMgr.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
+        threadMgr.setContinueExistingPeriodicTasksAfterShutdownPolicy(
+                false);
         threadMgr.setKeepAliveTime(5000, TimeUnit.MILLISECONDS);
         threadMgr.allowCoreThreadTimeOut(true);
-        responseCache = Cache.newSoftMemoryCache(cacheCapacity, cacheLifetime);
+        responseCache = Cache.newSoftMemoryCache(
+                cacheCapacity, cacheLifetime);
     }
 
     /**
      * Get the current cache lifetime setting
      *

@@ -145,22 +159,24 @@
 
     /**
      * Get the ignore extensions setting.
      *
      * @return {@code true} if the {@code StatusResponseManager} will not
-     * pass OCSP Extensions in the TLS {@code status_request[_v2]} extensions,
-     * {@code false} if extensions will be passed (the default).
+     * pass OCSP Extensions in the TLS {@code status_request[_v2]}
+     * extensions, {@code false} if extensions will be passed (the default).
      */
     boolean getIgnoreExtensions() {
         return ignoreExtensions;
     }
 
     /**
      * Clear the status response cache
      */
     void clear() {
-        debugLog("Clearing response cache");
+        if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+            SSLLogger.fine("Clearing response cache");
+        }
         responseCache.clear();
     }
 
     /**
      * Returns the number of currently valid objects in the response cache.

@@ -170,32 +186,40 @@
     int size() {
         return responseCache.size();
     }
 
     /**
-     * Obtain the URI use by the {@code StatusResponseManager} during lookups.
+     * Obtain the URI use by the {@code StatusResponseManager} during
+     * lookups.
+     *
      * This method takes into account not only the AIA extension from a
      * certificate to be checked, but also any default URI and possible
      * override settings for the response manager.
      *
      * @param cert the subject to get the responder URI from
      *
-     * @return a {@code URI} containing the address to the OCSP responder, or
-     *      {@code null} if no AIA extension exists in the certificate and no
-     *      default responder has been configured.
+     * @return a {@code URI} containing the address to the OCSP responder,
+     *      or {@code null} if no AIA extension exists in the certificate
+     *      and no default responder has been configured.
      *
      * @throws NullPointerException if {@code cert} is {@code null}.
      */
     URI getURI(X509Certificate cert) {
         Objects.requireNonNull(cert);
 
         if (cert.getExtensionValue(
                 PKIXExtensions.OCSPNoCheck_Id.toString()) != null) {
-            debugLog("OCSP NoCheck extension found.  OCSP will be skipped");
+            if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                SSLLogger.fine(
+                    "OCSP NoCheck extension found.  OCSP will be skipped");
+            }
             return null;
         } else if (defaultResponder != null && respOverride) {
-            debugLog("Responder override: URI is " + defaultResponder);
+            if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+              SSLLogger.fine(
+                    "Responder override: URI is " + defaultResponder);
+            }
             return defaultResponder;
         } else {
             URI certURI = OCSP.getResponderURI(cert);
             return (certURI != null ? certURI : defaultResponder);
         }

@@ -203,56 +227,63 @@
 
     /**
      * Shutdown the thread pool
      */
     void shutdown() {
-        debugLog("Shutting down " + threadMgr.getActiveCount() +
+        if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+            SSLLogger.fine("Shutting down " + threadMgr.getActiveCount() +
                 " active threads");
+        }
         threadMgr.shutdown();
     }
 
     /**
      * Get a list of responses for a chain of certificates.
-     * This will find OCSP responses from the cache, or failing that, directly
-     * contact the OCSP responder.  It is assumed that the certificates in
-     * the provided chain are in their proper order (from end-entity to
-     * trust anchor).
+     *
+     * This will find OCSP responses from the cache, or failing that,
+     * directly contact the OCSP responder.  It is assumed that the
+     * certificates in the provided chain are in their proper order
+     * (from end-entity to trust anchor).
      *
      * @param type the type of request being made of the
      *      {@code StatusResponseManager}
-     * @param request the {@code StatusRequest} from the status_request or
-     *      status_request_v2 ClientHello extension.  A value of {@code null}
-     *      is interpreted as providing no responder IDs or extensions.
-     * @param chain an array of 2 or more certificates.  Each certificate must
-     *      be issued by the next certificate in the chain.
+     * @param request the {@code CertStatusRequest} from the
+     *      status_request or status_request_v2 ClientHello extension.
+     *      A value of {@code null} is interpreted as providing no
+     *      responder IDs or extensions.
+     * @param chain an array of 2 or more certificates.  Each certificate
+     *      must be issued by the next certificate in the chain.
      * @param delay the number of time units to delay before returning
      *      responses.
      * @param unit the unit of time applied to the {@code delay} parameter
      *
      * @return an unmodifiable {@code Map} containing the certificate and
      *      its usually
      *
-     * @throws SSLHandshakeException if an unsupported {@code StatusRequest}
-     *      is provided.
+     * @throws SSLHandshakeException if an unsupported
+     *      {@code CertStatusRequest} is provided.
      */
-    Map<X509Certificate, byte[]> get(StatusRequestType type,
-            StatusRequest request, X509Certificate[] chain, long delay,
+    Map<X509Certificate, byte[]> get(CertStatusRequestType type,
+            CertStatusRequest request, X509Certificate[] chain, long delay,
             TimeUnit unit) {
         Map<X509Certificate, byte[]> responseMap = new HashMap<>();
         List<OCSPFetchCall> requestList = new ArrayList<>();
 
-        debugLog("Beginning check: Type = " + type + ", Chain length = " +
+        if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+            SSLLogger.fine(
+                "Beginning check: Type = " + type + ", Chain length = " +
                 chain.length);
+        }
 
         // It is assumed that the caller has ordered the certs in the chain
         // in the proper order (each certificate is issued by the next entry
         // in the provided chain).
         if (chain.length < 2) {
             return Collections.emptyMap();
         }
 
-        if (type == StatusRequestType.OCSP) {
+        if (type == CertStatusRequestType.OCSP) {
             try {
                 // For type OCSP, we only check the end-entity certificate
                 OCSPStatusRequest ocspReq = (OCSPStatusRequest)request;
                 CertId cid = new CertId(chain[1],
                         new SerialNumber(chain[0].getSerialNumber()));

@@ -262,70 +293,87 @@
                 } else {
                     StatusInfo sInfo = new StatusInfo(chain[0], cid);
                     requestList.add(new OCSPFetchCall(sInfo, ocspReq));
                 }
             } catch (IOException exc) {
-                debugLog("Exception during CertId creation: " + exc);
+                if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                    SSLLogger.fine(
+                        "Exception during CertId creation: ", exc);
+                }
             }
-        } else if (type == StatusRequestType.OCSP_MULTI) {
+        } else if (type == CertStatusRequestType.OCSP_MULTI) {
             // For type OCSP_MULTI, we check every cert in the chain that
-            // has a direct issuer at the next index.  We won't have an issuer
-            // certificate for the last certificate in the chain and will
-            // not be able to create a CertId because of that.
+            // has a direct issuer at the next index.  We won't have an
+            // issuer certificate for the last certificate in the chain
+            // and will not be able to create a CertId because of that.
             OCSPStatusRequest ocspReq = (OCSPStatusRequest)request;
             int ctr;
             for (ctr = 0; ctr < chain.length - 1; ctr++) {
                 try {
-                    // The cert at "ctr" is the subject cert, "ctr + 1" is the
-                    // issuer certificate.
+                    // The cert at "ctr" is the subject cert, "ctr + 1"
+                    // is the issuer certificate.
                     CertId cid = new CertId(chain[ctr + 1],
                             new SerialNumber(chain[ctr].getSerialNumber()));
-                    ResponseCacheEntry cacheEntry = getFromCache(cid, ocspReq);
+                    ResponseCacheEntry cacheEntry =
+                        getFromCache(cid, ocspReq);
                     if (cacheEntry != null) {
                         responseMap.put(chain[ctr], cacheEntry.ocspBytes);
                     } else {
                         StatusInfo sInfo = new StatusInfo(chain[ctr], cid);
                         requestList.add(new OCSPFetchCall(sInfo, ocspReq));
                     }
                 } catch (IOException exc) {
-                    debugLog("Exception during CertId creation: " + exc);
+                    if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                        SSLLogger.fine(
+                            "Exception during CertId creation: ", exc);
+                    }
                 }
             }
         } else {
-            debugLog("Unsupported status request type: " + type);
+            if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                SSLLogger.fine("Unsupported status request type: " + type);
+            }
         }
 
         // If we were able to create one or more Fetches, go and run all
         // of them in separate threads.  For all the threads that completed
-        // in the allotted time, put those status responses into the returned
-        // Map.
+        // in the allotted time, put those status responses into the
+        // returned Map.
         if (!requestList.isEmpty()) {
             try {
                 // Set a bunch of threads to go do the fetching
                 List<Future<StatusInfo>> resultList =
                         threadMgr.invokeAll(requestList, delay, unit);
 
                 // Go through the Futures and from any non-cancelled task,
                 // get the bytes and attach them to the responseMap.
                 for (Future<StatusInfo> task : resultList) {
-                    if (task.isDone()) {
+                    if (!task.isDone()) {
+                        continue;
+                    }
+
                         if (!task.isCancelled()) {
                             StatusInfo info = task.get();
                             if (info != null && info.responseData != null) {
                                 responseMap.put(info.cert,
                                         info.responseData.ocspBytes);
-                            } else {
-                                debugLog("Completed task had no response data");
+                        } else if (SSLLogger.isOn &&
+                                SSLLogger.isOn("respmgr")) {
+                            SSLLogger.fine(
+                                "Completed task had no response data");
                             }
                         } else {
-                            debugLog("Found cancelled task");
+                        if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                            SSLLogger.fine("Found cancelled task");
                         }
                     }
                 }
             } catch (InterruptedException | ExecutionException exc) {
                 // Not sure what else to do here
-                debugLog("Exception when getting data: " + exc);
+                if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                    SSLLogger.fine("Exception when getting data: ", exc);
+                }
             }
         }
 
         return Collections.unmodifiableMap(responseMap);
     }

@@ -343,13 +391,17 @@
      */
     private ResponseCacheEntry getFromCache(CertId cid,
             OCSPStatusRequest ocspRequest) {
         // Determine if the nonce extension is present in the request.  If
         // so, then do not attempt to retrieve the response from the cache.
-        for (Extension ext : ocspRequest.getExtensions()) {
-            if (ext.getId().equals(PKIXExtensions.OCSPNonce_Id.toString())) {
-                debugLog("Nonce extension found, skipping cache check");
+        for (Extension ext : ocspRequest.extensions) {
+            if (ext.getId().equals(
+                    PKIXExtensions.OCSPNonce_Id.toString())) {
+                if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                    SSLLogger.fine(
+                            "Nonce extension found, skipping cache check");
+                }
                 return null;
             }
         }
 
         ResponseCacheEntry respEntry = responseCache.get(cid);

@@ -357,16 +409,22 @@
         // If the response entry has a nextUpdate and it has expired
         // before the cache expiration, purge it from the cache
         // and do not return it as a cache hit.
         if (respEntry != null && respEntry.nextUpdate != null &&
                 respEntry.nextUpdate.before(new Date())) {
-            debugLog("nextUpdate threshold exceeded, purging from cache");
+            if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                SSLLogger.fine(
+                    "nextUpdate threshold exceeded, purging from cache");
+            }
             respEntry = null;
         }
 
-        debugLog("Check cache for SN" + cid.getSerialNumber() + ": " +
+        if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+            SSLLogger.fine(
+                    "Check cache for SN" + cid.getSerialNumber() + ": " +
                 (respEntry != null ? "HIT" : "MISS"));
+        }
         return respEntry;
     }
 
     @Override
     public String toString() {

@@ -396,24 +454,10 @@
 
         return sb.toString();
     }
 
     /**
-     * Log messages through the SSL Debug facility.
-     *
-     * @param message the message to be displayed
-     */
-    static void debugLog(String message) {
-        if (debug != null && Debug.isOn("respmgr")) {
-            StringBuilder sb = new StringBuilder();
-            sb.append("[").append(Thread.currentThread().getName());
-            sb.append("] ").append(message);
-            System.out.println(sb.toString());
-        }
-    }
-
-    /**
      * Inner class used to group request and response data.
      */
     class StatusInfo {
         final X509Certificate cert;
         final CertId cid;

@@ -424,11 +468,11 @@
          * Create a StatusInfo object from certificate data.
          *
          * @param subjectCert the certificate to be checked for revocation
          * @param issuerCert the issuer of the {@code subjectCert}
          *
-         * @throws IOException if CertId creation from the certificates fails
+         * @throws IOException if CertId creation from the certificate fails
          */
         StatusInfo(X509Certificate subjectCert, X509Certificate issuerCert)
                 throws IOException {
             this(subjectCert, new CertId(issuerCert,
                     new SerialNumber(subjectCert.getSerialNumber())));

@@ -469,23 +513,26 @@
          * @return a {@code String} representation of this object
          */
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder("StatusInfo:");
-            sb.append("\n\tCert: ").append(this.cert.getSubjectX500Principal());
+            sb.append("\n\tCert: ").append(
+                    this.cert.getSubjectX500Principal());
             sb.append("\n\tSerial: ").append(this.cert.getSerialNumber());
             sb.append("\n\tResponder: ").append(this.responder);
-            sb.append("\n\tResponse data: ").append(this.responseData != null ?
-                    (this.responseData.ocspBytes.length + " bytes") : "<NULL>");
+            sb.append("\n\tResponse data: ").append(
+                    this.responseData != null ?
+                        (this.responseData.ocspBytes.length + " bytes") :
+                        "<NULL>");
             return sb.toString();
         }
     }
 
     /**
      * Static nested class used as the data kept in the response cache.
      */
-    static class ResponseCacheEntry {
+    class ResponseCacheEntry {
         final OCSPResponse.ResponseStatus status;
         final byte[] ocspBytes;
         final Date nextUpdate;
         final OCSPResponse.SingleResponse singleResp;
         final ResponderId respId;

@@ -493,12 +540,12 @@
         /**
          * Create a new cache entry from the raw bytes of the response
          *
          * @param responseBytes the DER encoding for the OCSP response
          *
-         * @throws IOException if an {@code OCSPResponse} cannot be created from
-         *      the encoded bytes.
+         * @throws IOException if an {@code OCSPResponse} cannot be
+         *         created from the encoded bytes.
          */
         ResponseCacheEntry(byte[] responseBytes, CertId cid)
                 throws IOException {
             Objects.requireNonNull(responseBytes,
                     "Non-null responseBytes required");

@@ -513,12 +560,13 @@
                 if (singleResp != null) {
                     // Pull out the nextUpdate field in advance because the
                     // Date is cloned.
                     nextUpdate = singleResp.getNextUpdate();
                 } else {
-                    throw new IOException("Unable to find SingleResponse for " +
-                            "SN " + cid.getSerialNumber());
+                    throw new IOException(
+                            "Unable to find SingleResponse for SN " +
+                            cid.getSerialNumber());
                 }
             } else {
                 nextUpdate = null;
             }
         }

@@ -535,11 +583,12 @@
         List<Extension> extensions;
         List<ResponderId> responderIds;
 
         /**
          * A constructor that builds the OCSPFetchCall from the provided
-         * StatusInfo and information from the status_request[_v2] extension.
+         * StatusInfo and information from the status_request[_v2]
+         * extension.
          *
          * @param info the {@code StatusInfo} containing the subject
          * certificate, CertId, and other supplemental info.
          * @param request the {@code OCSPStatusRequest} containing any
          * responder IDs and extensions.

@@ -547,41 +596,58 @@
         public OCSPFetchCall(StatusInfo info, OCSPStatusRequest request) {
             statInfo = Objects.requireNonNull(info,
                     "Null StatusInfo not allowed");
             ocspRequest = Objects.requireNonNull(request,
                     "Null OCSPStatusRequest not allowed");
-            extensions = ocspRequest.getExtensions();
-            responderIds = ocspRequest.getResponderIds();
+            extensions = ocspRequest.extensions;
+            responderIds = ocspRequest.responderIds;
         }
 
         /**
          * Get an OCSP response, either from the cache or from a responder.
          *
-         * @return The StatusInfo object passed into the {@code OCSPFetchCall}
-         * constructor, with the {@code responseData} field filled in with the
-         * response or {@code null} if no response can be obtained.
+         * @return The StatusInfo object passed into the
+         *         {@code OCSPFetchCall} constructor, with the
+         *         {@code responseData} field filled in with the response
+         *         or {@code null} if no response can be obtained.
          */
         @Override
         public StatusInfo call() {
-            debugLog("Starting fetch for SN " + statInfo.cid.getSerialNumber());
+            if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                SSLLogger.fine(
+                    "Starting fetch for SN " +
+                    statInfo.cid.getSerialNumber());
+            }
             try {
                 ResponseCacheEntry cacheEntry;
                 List<Extension> extsToSend;
 
                 if (statInfo.responder == null) {
-                    // If we have no URI then there's nothing to do but return
-                    debugLog("Null URI detected, OCSP fetch aborted.");
+                    // If we have no URI then there's nothing to do
+                    // but return.
+                    if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                        SSLLogger.fine(
+                            "Null URI detected, OCSP fetch aborted");
+                    }
                     return statInfo;
                 } else {
-                    debugLog("Attempting fetch from " + statInfo.responder);
+                    if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                        SSLLogger.fine(
+                            "Attempting fetch from " + statInfo.responder);
+                    }
                 }
 
                 // If the StatusResponseManager has been configured to not
-                // forward extensions, then set extensions to an empty list.
-                // We will forward the extensions unless one of two conditions
-                // occur: (1) The jdk.tls.stapling.ignoreExtensions property is
-                // true or (2) There is a non-empty ResponderId list.
+                // forward extensions, then set extensions to an empty
+                // list.
+                //
+                // We will forward the extensions unless one of two
+                // conditions occur:
+                // (1) The jdk.tls.stapling.ignoreExtensions property is
+                //     true, or
+                // (2) There is a non-empty ResponderId list.
+                //
                 // ResponderId selection is a feature that will be
                 // supported in the future.
                 extsToSend = (ignoreExtensions || !responderIds.isEmpty()) ?
                         Collections.emptyList() : extensions;
 

@@ -593,25 +659,32 @@
                     // Place the data into the response cache
                     cacheEntry = new ResponseCacheEntry(respBytes,
                             statInfo.cid);
 
                     // Get the response status and act on it appropriately
-                    debugLog("OCSP Status: " + cacheEntry.status +
+                    if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                        SSLLogger.fine("OCSP Status: " + cacheEntry.status +
                             " (" + respBytes.length + " bytes)");
+                    }
                     if (cacheEntry.status ==
                             OCSPResponse.ResponseStatus.SUCCESSFUL) {
                         // Set the response in the returned StatusInfo
                         statInfo.responseData = cacheEntry;
 
                         // Add the response to the cache (if applicable)
                         addToCache(statInfo.cid, cacheEntry);
                     }
                 } else {
-                    debugLog("No data returned from OCSP Responder");
+                    if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                        SSLLogger.fine(
+                            "No data returned from OCSP Responder");
+                    }
                 }
             } catch (IOException ioe) {
-                debugLog("Caught exception: " + ioe);
+                if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                    SSLLogger.fine("Caught exception: ", ioe);
+                }
             }
 
             return statInfo;
         }
 

@@ -624,26 +697,32 @@
          */
         private void addToCache(CertId certId, ResponseCacheEntry entry) {
             // If no cache lifetime has been set on entries then
             // don't cache this response if there is no nextUpdate field
             if (entry.nextUpdate == null && cacheLifetime == 0) {
-                debugLog("Not caching this OCSP response");
+                if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                    SSLLogger.fine("Not caching this OCSP response");
+                }
             } else {
                 responseCache.put(certId, entry);
-                debugLog("Added response for SN " + certId.getSerialNumber() +
+                if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) {
+                    SSLLogger.fine(
+                        "Added response for SN " +
+                        certId.getSerialNumber() +
                         " to cache");
             }
         }
+        }
 
         /**
          * Determine the delay to use when scheduling the task that will
          * update the OCSP response.  This is the shorter time between the
-         * cache lifetime and the nextUpdate.  If no nextUpdate is present in
-         * the response, then only the cache lifetime is used.
+         * cache lifetime and the nextUpdate.  If no nextUpdate is present
+         * in the response, then only the cache lifetime is used.
          * If cache timeouts are disabled (a zero value) and there's no
-         * nextUpdate, then the entry is not cached and no rescheduling will
-         * take place.
+         * nextUpdate, then the entry is not cached and no rescheduling
+         * will take place.
          *
          * @param nextUpdate a {@code Date} object corresponding to the
          *      next update time from a SingleResponse.
          *
          * @return the number of seconds of delay before the next fetch

@@ -665,6 +744,220 @@
             }
 
             return delaySec;
         }
     }
+
+    static final StaplingParameters processStapling(
+            ServerHandshakeContext shc) {
+        StaplingParameters params = null;
+        SSLExtension ext = null;
+        CertStatusRequestType type = null;
+        CertStatusRequest req = null;
+        Map<X509Certificate, byte[]> responses;
+
+        // If this feature has not been enabled, then no more processing
+        // is necessary.  Also we will only staple if we're doing a full
+        // handshake.
+        if (!shc.sslContext.isStaplingEnabled(false) || shc.isResumption) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.fine("Staping disabled or is a resumed session");
+            }
+            return null;
+        }
+
+        // Check if the client has asserted the status_request[_v2] extension(s)
+        Map<SSLExtension, SSLExtension.SSLExtensionSpec> exts =
+                shc.handshakeExtensions;
+        CertStatusRequestSpec statReq = (CertStatusRequestSpec)exts.get(
+                SSLExtension.CH_STATUS_REQUEST);
+        CertStatusRequestV2Spec statReqV2 = (CertStatusRequestV2Spec)
+                exts.get(SSLExtension.CH_STATUS_REQUEST_V2);
+
+        // Determine which type of stapling we are doing and assert the
+        // proper extension in the server hello.
+        // Favor status_request_v2 over status_request and ocsp_multi
+        // over ocsp.
+        // If multiple ocsp or ocsp_multi types exist, select the first
+        // instance of a given type.  Also since we don't support ResponderId
+        // selection yet, only accept a request if the ResponderId field
+        // is empty.  Finally, we'll only do this in (D)TLS 1.2 or earlier.
+        if (statReqV2 != null && !shc.negotiatedProtocol.useTLS13PlusSpec()) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) {
+                SSLLogger.fine("SH Processing status_request_v2 extension");
+            }
+            // RFC 6961 stapling
+            ext = SSLExtension.CH_STATUS_REQUEST_V2;
+            int ocspIdx = -1;
+            int ocspMultiIdx = -1;
+            CertStatusRequest[] reqItems = statReqV2.certStatusRequests;
+            for (int pos = 0; (pos < reqItems.length &&
+                    (ocspIdx == -1 || ocspMultiIdx == -1)); pos++) {
+                CertStatusRequest item = reqItems[pos];
+                CertStatusRequestType curType =
+                        CertStatusRequestType.valueOf(item.statusType);
+                if (ocspIdx < 0 && curType == CertStatusRequestType.OCSP) {
+                    OCSPStatusRequest ocspReq = (OCSPStatusRequest)item;
+                    // We currently only accept empty responder ID lists
+                    // but may support them in the future
+                    if (ocspReq.responderIds.isEmpty()) {
+                        ocspIdx = pos;
+                    }
+                } else if (ocspMultiIdx < 0 &&
+                        curType == CertStatusRequestType.OCSP_MULTI) {
+                    OCSPStatusRequest ocspReq = (OCSPStatusRequest)item;
+                    // We currently only accept empty responder ID lists
+                    // but may support them in the future
+                    if (ocspReq.responderIds.isEmpty()) {
+                        ocspMultiIdx = pos;
+                    }
+                }
+            }
+            if (ocspMultiIdx >= 0) {
+                req = reqItems[ocspMultiIdx];
+                type = CertStatusRequestType.valueOf(req.statusType);
+            } else if (ocspIdx >= 0) {
+                req = reqItems[ocspIdx];
+                type = CertStatusRequestType.valueOf(req.statusType);
+            } else {
+                if (SSLLogger.isOn &&
+                        SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.finest("Warning: No suitable request " +
+                            "found in the status_request_v2 extension.");
+                }
+            }
+        }
+
+        // Only attempt to process a status_request extension if:
+        // * The status_request extension is set AND
+        // * either the status_request_v2 extension is not present OR
+        // * none of the underlying OCSPStatusRequest structures is
+        // suitable for stapling.
+        // If either of the latter two bullet items is true the ext,
+        // type and req variables should all be null.  If any are null
+        // we will try processing an asserted status_request.
+        if ((statReq != null) &&
+                (ext == null || type == null || req == null)) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) {
+                SSLLogger.fine("SH Processing status_request extension");
+            }
+            ext = SSLExtension.CH_STATUS_REQUEST;
+            type = CertStatusRequestType.valueOf(
+                    statReq.statusRequest.statusType);
+            if (type == CertStatusRequestType.OCSP) {
+                // If the type is OCSP, then the request is guaranteed
+                // to be OCSPStatusRequest
+                OCSPStatusRequest ocspReq =
+                        (OCSPStatusRequest)statReq.statusRequest;
+                if (ocspReq.responderIds.isEmpty()) {
+                    req = ocspReq;
+                } else {
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                        SSLLogger.finest("Warning: No suitable request " +
+                            "found in the status_request extension.");
+                    }
+                }
+            }
+        }
+
+        // If, after walking through the extensions we were unable to
+        // find a suitable StatusRequest, then stapling is disabled.
+        // The ext, type and req variables must have been set to continue.
+        if (type == null || req == null || ext == null) {
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.fine("No suitable status_request or " +
+                        "status_request_v2, stapling is disabled");
+            }
+            return null;
+        }
+
+        // Get the cert chain since we'll need it for OCSP checking
+        X509Possession x509Possession = null;
+        for (SSLPossession possession : shc.handshakePossessions) {
+            if (possession instanceof X509Possession) {
+                x509Possession = (X509Possession)possession;
+                break;
+            }
+        }
+
+        if (x509Possession == null) {       // unlikely
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.finest("Warning: no X.509 certificates found.  " +
+                        "Stapling is disabled.");
+            }
+            return null;
+        }
+
+        // Get the OCSP responses from the StatusResponseManager
+        X509Certificate[] certs = x509Possession.popCerts;
+        StatusResponseManager statRespMgr =
+                shc.sslContext.getStatusResponseManager();
+        if (statRespMgr != null) {
+            // For the purposes of the fetch from the SRM, override the
+            // type when it is TLS 1.3 so it always gets responses for
+            // all certs it can.  This should not change the type field
+            // in the StaplingParameters though.
+            CertStatusRequestType fetchType =
+                    shc.negotiatedProtocol.useTLS13PlusSpec() ?
+                    CertStatusRequestType.OCSP_MULTI : type;
+            responses = statRespMgr.get(fetchType, req, certs,
+                    shc.statusRespTimeout, TimeUnit.MILLISECONDS);
+            if (!responses.isEmpty()) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.finest("Response manager returned " +
+                            responses.size() + " entries.");
+                }
+                // If this RFC 6066-style stapling (SSL cert only) then the
+                // response cannot be zero length
+                if (type == CertStatusRequestType.OCSP) {
+                    byte[] respDER = responses.get(certs[0]);
+                    if (respDER == null || respDER.length <= 0) {
+                        if (SSLLogger.isOn &&
+                                SSLLogger.isOn("ssl,handshake")) {
+                            SSLLogger.finest("Warning: Null or zero-length " +
+                                    "response found for leaf certificate. " +
+                                    "Stapling is disabled.");
+                        }
+                        return null;
+                    }
+                }
+                params = new StaplingParameters(ext, type, req, responses);
+            } else {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.finest("Warning: no OCSP responses obtained.  " +
+                            "Stapling is disabled.");
+                }
+            }
+        } else {
+            // This should not happen, but if lazy initialization of the
+            // StatusResponseManager doesn't occur we should turn off stapling.
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.finest("Warning: lazy initialization " +
+                        "of the StatusResponseManager failed.  " +
+                        "Stapling is disabled.");
+            }
+            params = null;
+        }
+
+        return params;
+    }
+
+    /**
+     * Inner class used to hold stapling parameters needed by the handshaker
+     * when stapling is active.
+     */
+    static final class StaplingParameters {
+        final SSLExtension statusRespExt;
+        final CertStatusRequestType statReqType;
+        final CertStatusRequest statReqData;
+        final Map<X509Certificate, byte[]> responseMap;
+
+        StaplingParameters(SSLExtension ext, CertStatusRequestType type,
+                CertStatusRequest req, Map<X509Certificate, byte[]> responses) {
+            statusRespExt = ext;
+            statReqType = type;
+            statReqData = req;
+            responseMap = responses;
+        }
+    }
 }
+
< prev index next >