< 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 >