--- old/src/java.base/share/classes/sun/security/ssl/StatusResponseManager.java 2018-05-11 15:06:13.718077700 -0700 +++ new/src/java.base/share/classes/sun/security/ssl/StatusResponseManager.java 2018-05-11 15:06:13.236915700 -0700 @@ -1,5 +1,5 @@ /* - * 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 @@ -22,18 +22,32 @@ * 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; @@ -41,15 +55,13 @@ 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 static final int DEFAULT_CACHE_LIFETIME = 3600; // seconds private final ScheduledThreadPoolExecutor threadMgr; private final Cache responseCache; @@ -99,10 +111,12 @@ } }, 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); } /** @@ -147,8 +161,8 @@ * 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; @@ -158,7 +172,9 @@ * Clear the status response cache */ void clear() { - debugLog("Clearing response cache"); + if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + SSLLogger.fine("Clearing response cache"); + } responseCache.clear(); } @@ -172,16 +188,18 @@ } /** - * 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}. */ @@ -190,10 +208,16 @@ 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); @@ -205,25 +229,29 @@ * Shutdown the thread pool */ void shutdown() { - debugLog("Shutting down " + threadMgr.getActiveCount() + - " active threads"); + 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 @@ -231,17 +259,20 @@ * @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 get(StatusRequestType type, - StatusRequest request, X509Certificate[] chain, long delay, + Map get(CertStatusRequestType type, + CertStatusRequest request, X509Certificate[] chain, long delay, TimeUnit unit) { Map responseMap = new HashMap<>(); List 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 @@ -250,7 +281,7 @@ 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; @@ -264,22 +295,26 @@ 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); + new SerialNumber(chain[ctr].getSerialNumber())); + ResponseCacheEntry cacheEntry = + getFromCache(cid, ocspReq); if (cacheEntry != null) { responseMap.put(chain[ctr], cacheEntry.ocspBytes); } else { @@ -287,17 +322,22 @@ 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 @@ -307,23 +347,31 @@ // Go through the Futures and from any non-cancelled task, // get the bytes and attach them to the responseMap. for (Future task : resultList) { - if (task.isDone()) { - 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 { - debugLog("Found cancelled task"); + 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 if (SSLLogger.isOn && + SSLLogger.isOn("respmgr")) { + SSLLogger.fine( + "Completed task had no response data"); + } + } else { + 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); + } } } @@ -345,9 +393,13 @@ 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; } } @@ -359,12 +411,18 @@ // 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() + ": " + - (respEntry != null ? "HIT" : "MISS")); + if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + SSLLogger.fine( + "Check cache for SN" + cid.getSerialNumber() + ": " + + (respEntry != null ? "HIT" : "MISS")); + } return respEntry; } @@ -398,20 +456,6 @@ } /** - * 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 { @@ -426,7 +470,7 @@ * @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 { @@ -471,11 +515,14 @@ @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") : ""); + sb.append("\n\tResponse data: ").append( + this.responseData != null ? + (this.responseData.ocspBytes.length + " bytes") : + ""); return sb.toString(); } } @@ -483,7 +530,7 @@ /** * 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; @@ -495,8 +542,8 @@ * * @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 { @@ -515,8 +562,9 @@ // 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; @@ -537,7 +585,8 @@ /** * 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. @@ -549,37 +598,54 @@ "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 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()) ? @@ -595,8 +661,10 @@ 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 @@ -606,10 +674,15 @@ 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; @@ -626,22 +699,28 @@ // 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. @@ -667,4 +746,218 @@ return delaySec; } } + + static final StaplingParameters processStapling( + ServerHandshakeContext shc) { + StaplingParameters params = null; + SSLExtension ext = null; + CertStatusRequestType type = null; + CertStatusRequest req = null; + Map 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 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 responseMap; + + StaplingParameters(SSLExtension ext, CertStatusRequestType type, + CertStatusRequest req, Map responses) { + statusRespExt = ext; + statReqType = type; + statReqData = req; + responseMap = responses; + } + } } +