/* * 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 * 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.ssl; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.security.AccessController; import java.security.cert.Extension; 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.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 final ScheduledThreadPoolExecutor threadMgr; private final Cache responseCache; private final URI defaultResponder; private final boolean respOverride; private final int cacheCapacity; private final int cacheLifetime; private final boolean ignoreExtensions; /** * Create a StatusResponseManager with default parameters. */ StatusResponseManager() { int cap = AccessController.doPrivileged( new GetIntegerAction("jdk.tls.stapling.cacheSize", DEFAULT_CACHE_SIZE)); cacheCapacity = cap > 0 ? cap : 0; int life = AccessController.doPrivileged( new GetIntegerAction("jdk.tls.stapling.cacheLifetime", DEFAULT_CACHE_LIFETIME)); cacheLifetime = life > 0 ? life : 0; String uriStr = GetPropertyAction .privilegedGetProperty("jdk.tls.stapling.responderURI"); URI tmpURI; try { tmpURI = ((uriStr != null && !uriStr.isEmpty()) ? new URI(uriStr) : null); } catch (URISyntaxException urise) { tmpURI = null; } defaultResponder = tmpURI; respOverride = AccessController.doPrivileged( new GetBooleanAction("jdk.tls.stapling.responderOverride")); ignoreExtensions = AccessController.doPrivileged( new GetBooleanAction("jdk.tls.stapling.ignoreExtensions")); threadMgr = new ScheduledThreadPoolExecutor(DEFAULT_CORE_THREADS, new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = Executors.defaultThreadFactory().newThread(r); t.setDaemon(true); return t; } }, new ThreadPoolExecutor.DiscardPolicy()); threadMgr.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); threadMgr.setContinueExistingPeriodicTasksAfterShutdownPolicy( false); threadMgr.setKeepAliveTime(5000, TimeUnit.MILLISECONDS); threadMgr.allowCoreThreadTimeOut(true); responseCache = Cache.newSoftMemoryCache( cacheCapacity, cacheLifetime); } /** * Get the current cache lifetime setting * * @return the current cache lifetime value */ int getCacheLifetime() { return cacheLifetime; } /** * Get the current maximum cache size. * * @return the current maximum cache size */ int getCacheCapacity() { return cacheCapacity; } /** * Get the default OCSP responder URI, if previously set. * * @return the current default OCSP responder URI, or {@code null} if * it has not been set. */ URI getDefaultResponder() { return defaultResponder; } /** * Get the URI override setting * * @return {@code true} if URI override has been set, {@code false} * otherwise. */ boolean getURIOverride() { return respOverride; } /** * 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). */ boolean getIgnoreExtensions() { return ignoreExtensions; } /** * Clear the status response cache */ void clear() { if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Clearing response cache"); } responseCache.clear(); } /** * Returns the number of currently valid objects in the response cache. * * @return the number of valid objects in the response cache. */ int size() { return responseCache.size(); } /** * 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. * * @throws NullPointerException if {@code cert} is {@code null}. */ URI getURI(X509Certificate cert) { Objects.requireNonNull(cert); if (cert.getExtensionValue( PKIXExtensions.OCSPNoCheck_Id.toString()) != null) { if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "OCSP NoCheck extension found. OCSP will be skipped"); } return null; } else if (defaultResponder != null && respOverride) { 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); } } /** * Shutdown the thread pool */ void shutdown() { 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). * * @param type the type of request being made of the * {@code StatusResponseManager} * @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 CertStatusRequest} is provided. */ Map get(CertStatusRequestType type, CertStatusRequest request, X509Certificate[] chain, long delay, TimeUnit unit) { Map responseMap = new HashMap<>(); List requestList = new ArrayList<>(); 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 == 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())); ResponseCacheEntry cacheEntry = getFromCache(cid, ocspReq); if (cacheEntry != null) { responseMap.put(chain[0], cacheEntry.ocspBytes); } else { StatusInfo sInfo = new StatusInfo(chain[0], cid); requestList.add(new OCSPFetchCall(sInfo, ocspReq)); } } catch (IOException exc) { if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Exception during CertId creation: ", exc); } } } 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. 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. CertId cid = new CertId(chain[ctr + 1], new SerialNumber(chain[ctr].getSerialNumber())); 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) { if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Exception during CertId creation: ", exc); } } } } else { 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. if (!requestList.isEmpty()) { try { // Set a bunch of threads to go do the fetching List> 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 task : resultList) { 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 if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Exception when getting data: ", exc); } } } return Collections.unmodifiableMap(responseMap); } /** * Check the cache for a given {@code CertId}. * * @param cid the CertId of the response to look up * @param ocspRequest the OCSP request structure sent by the client * in the TLS status_request[_v2] hello extension. * * @return the {@code ResponseCacheEntry} for a specific CertId, or * {@code null} if it is not found or a nonce extension has been * requested by the caller. */ 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.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); // 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())) { if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "nextUpdate threshold exceeded, purging from cache"); } respEntry = null; } if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Check cache for SN" + cid.getSerialNumber() + ": " + (respEntry != null ? "HIT" : "MISS")); } return respEntry; } @Override public String toString() { StringBuilder sb = new StringBuilder("StatusResponseManager: "); sb.append("Core threads: ").append(threadMgr.getCorePoolSize()); sb.append(", Cache timeout: "); if (cacheLifetime > 0) { sb.append(cacheLifetime).append(" seconds"); } else { sb.append(" indefinite"); } sb.append(", Cache MaxSize: "); if (cacheCapacity > 0) { sb.append(cacheCapacity).append(" items"); } else { sb.append(" unbounded"); } sb.append(", Default URI: "); if (defaultResponder != null) { sb.append(defaultResponder); } else { sb.append("NONE"); } return sb.toString(); } /** * Inner class used to group request and response data. */ class StatusInfo { final X509Certificate cert; final CertId cid; final URI responder; ResponseCacheEntry responseData; /** * 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 certificate fails */ StatusInfo(X509Certificate subjectCert, X509Certificate issuerCert) throws IOException { this(subjectCert, new CertId(issuerCert, new SerialNumber(subjectCert.getSerialNumber()))); } /** * Create a StatusInfo object from an existing subject certificate * and its corresponding CertId. * * @param subjectCert the certificate to be checked for revocation * @param cid the CertId for {@code subjectCert} */ StatusInfo(X509Certificate subjectCert, CertId certId) { cert = subjectCert; cid = certId; responder = getURI(cert); responseData = null; } /** * Copy constructor (used primarily for rescheduling). * This will do a member-wise copy with the exception of the * responseData and extensions fields, which should not persist * in a rescheduled fetch. * * @param orig the original {@code StatusInfo} */ StatusInfo(StatusInfo orig) { this.cert = orig.cert; this.cid = orig.cid; this.responder = orig.responder; this.responseData = null; } /** * Return a String representation of the {@code StatusInfo} * * @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\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") : ""); return sb.toString(); } } /** * Static nested class used as the data kept in the response cache. */ class ResponseCacheEntry { final OCSPResponse.ResponseStatus status; final byte[] ocspBytes; final Date nextUpdate; final OCSPResponse.SingleResponse singleResp; final ResponderId respId; /** * 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. */ ResponseCacheEntry(byte[] responseBytes, CertId cid) throws IOException { Objects.requireNonNull(responseBytes, "Non-null responseBytes required"); Objects.requireNonNull(cid, "Non-null Cert ID required"); ocspBytes = responseBytes.clone(); OCSPResponse oResp = new OCSPResponse(ocspBytes); status = oResp.getResponseStatus(); respId = oResp.getResponderId(); singleResp = oResp.getSingleResponse(cid); if (status == OCSPResponse.ResponseStatus.SUCCESSFUL) { 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()); } } else { nextUpdate = null; } } } /** * Inner Callable class that does the actual work of looking up OCSP * responses, first looking at the cache and doing OCSP requests if * a cache miss occurs. */ class OCSPFetchCall implements Callable { StatusInfo statInfo; OCSPStatusRequest ocspRequest; List extensions; List responderIds; /** * A constructor that builds the OCSPFetchCall from the provided * 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. */ public OCSPFetchCall(StatusInfo info, OCSPStatusRequest request) { statInfo = Objects.requireNonNull(info, "Null StatusInfo not allowed"); ocspRequest = Objects.requireNonNull(request, "Null OCSPStatusRequest not allowed"); 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. */ @Override public StatusInfo call() { 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. if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Null URI detected, OCSP fetch aborted"); } return statInfo; } else { 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. // // ResponderId selection is a feature that will be // supported in the future. extsToSend = (ignoreExtensions || !responderIds.isEmpty()) ? Collections.emptyList() : extensions; byte[] respBytes = OCSP.getOCSPBytes( Collections.singletonList(statInfo.cid), statInfo.responder, extsToSend); if (respBytes != null) { // Place the data into the response cache cacheEntry = new ResponseCacheEntry(respBytes, statInfo.cid); // Get the response status and act on it appropriately 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 { if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "No data returned from OCSP Responder"); } } } catch (IOException ioe) { if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Caught exception: ", ioe); } } return statInfo; } /** * Add a response to the cache. * * @param certId The {@code CertId} for the OCSP response * @param entry A cache entry containing the response bytes and * the {@code OCSPResponse} built from those bytes. */ 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) { if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Not caching this OCSP response"); } } else { responseCache.put(certId, entry); 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. * 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. * * @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 * should be executed. A zero value means that the fetch * should happen immediately, while a value less than zero * indicates no rescheduling should be done. */ private long getNextTaskDelay(Date nextUpdate) { long delaySec; int lifetime = getCacheLifetime(); if (nextUpdate != null) { long nuDiffSec = (nextUpdate.getTime() - System.currentTimeMillis()) / 1000; delaySec = lifetime > 0 ? Long.min(nuDiffSec, lifetime) : nuDiffSec; } else { delaySec = lifetime > 0 ? lifetime : -1; } 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; } } }