|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
|
|
private final ScheduledThreadPoolExecutor threadMgr; |
|
private final Cache<CertId, ResponseCacheEntry> responseCache; |
|
private final URI defaultResponder; |
|
private final boolean respOverride; |
|
private final int cacheCapacity; |
|
private final int cacheLifetime; |
|
private final boolean ignoreExtensions; |
|
|
|
|
|
|
|
*/ |
|
StatusResponseManager() { |
|
@SuppressWarnings("removal") |
|
int cap = AccessController.doPrivileged( |
|
new GetIntegerAction("jdk.tls.stapling.cacheSize", |
|
DEFAULT_CACHE_SIZE)); |
|
cacheCapacity = cap > 0 ? cap : 0; |
|
|
|
@SuppressWarnings("removal") |
|
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 = GetBooleanAction |
|
.privilegedGetProperty("jdk.tls.stapling.responderOverride"); |
|
ignoreExtensions = GetBooleanAction |
|
.privilegedGetProperty("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); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int getCacheLifetime() { |
|
return cacheLifetime; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int getCacheCapacity() { |
|
return cacheCapacity; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
URI getDefaultResponder() { |
|
return defaultResponder; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
boolean getURIOverride() { |
|
return respOverride; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
boolean getIgnoreExtensions() { |
|
return ignoreExtensions; |
|
} |
|
|
|
|
|
|
|
*/ |
|
void clear() { |
|
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
|
SSLLogger.fine("Clearing response cache"); |
|
} |
|
responseCache.clear(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int size() { |
|
return responseCache.size(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
void shutdown() { |
|
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
|
SSLLogger.fine("Shutting down " + threadMgr.getActiveCount() + |
|
" active threads"); |
|
} |
|
threadMgr.shutdown(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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<>(); |
|
|
|
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 |
|
|
|
if (chain.length < 2) { |
|
return Collections.emptyMap(); |
|
} |
|
|
|
if (type == CertStatusRequestType.OCSP) { |
|
try { |
|
|
|
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 |
|
|
|
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" |
|
|
|
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 |
|
|
|
if (!requestList.isEmpty()) { |
|
try { |
|
|
|
List<Future<StatusInfo>> resultList = |
|
threadMgr.invokeAll(requestList, delay, unit); |
|
|
|
// Go through the Futures and from any non-cancelled task, |
|
|
|
for (Future<StatusInfo> 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) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
|
SSLLogger.fine("Exception when getting data: ", exc); |
|
} |
|
} |
|
} |
|
|
|
return Collections.unmodifiableMap(responseMap); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private ResponseCacheEntry getFromCache(CertId cid, |
|
OCSPStatusRequest ocspRequest) { |
|
// Determine if the nonce extension is present in the request. If |
|
|
|
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 |
|
|
|
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(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
class StatusInfo { |
|
final X509Certificate cert; |
|
final CertId cid; |
|
final URI responder; |
|
ResponseCacheEntry responseData; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
StatusInfo(X509Certificate subjectCert, X509Certificate issuerCert) |
|
throws IOException { |
|
this(subjectCert, new CertId(issuerCert, |
|
new SerialNumber(subjectCert.getSerialNumber()))); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
StatusInfo(X509Certificate subjectCert, CertId certId) { |
|
cert = subjectCert; |
|
cid = certId; |
|
responder = getURI(cert); |
|
responseData = null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
StatusInfo(StatusInfo orig) { |
|
this.cert = orig.cert; |
|
this.cid = orig.cid; |
|
this.responder = orig.responder; |
|
this.responseData = null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@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") : |
|
"<NULL>"); |
|
return sb.toString(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
static class ResponseCacheEntry { |
|
final OCSPResponse.ResponseStatus status; |
|
final byte[] ocspBytes; |
|
final Date nextUpdate; |
|
final OCSPResponse.SingleResponse singleResp; |
|
final ResponderId respId; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
nextUpdate = singleResp.getNextUpdate(); |
|
} else { |
|
throw new IOException( |
|
"Unable to find SingleResponse for SN " + |
|
cid.getSerialNumber()); |
|
} |
|
} else { |
|
nextUpdate = null; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
class OCSPFetchCall implements Callable<StatusInfo> { |
|
StatusInfo statInfo; |
|
OCSPStatusRequest ocspRequest; |
|
List<Extension> extensions; |
|
List<ResponderId> responderIds; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public StatusInfo call() { |
|
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 |
|
|
|
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 |
|
|
|
extsToSend = (ignoreExtensions || !responderIds.isEmpty()) ? |
|
Collections.emptyList() : extensions; |
|
|
|
byte[] respBytes = OCSP.getOCSPBytes( |
|
Collections.singletonList(statInfo.cid), |
|
statInfo.responder, extsToSend); |
|
|
|
if (respBytes != null) { |
|
|
|
cacheEntry = new ResponseCacheEntry(respBytes, |
|
statInfo.cid); |
|
|
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
|
SSLLogger.fine("OCSP Status: " + cacheEntry.status + |
|
" (" + respBytes.length + " bytes)"); |
|
} |
|
if (cacheEntry.status == |
|
OCSPResponse.ResponseStatus.SUCCESSFUL) { |
|
|
|
statInfo.responseData = cacheEntry; |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void addToCache(CertId certId, ResponseCacheEntry entry) { |
|
// If no cache lifetime has been set on entries then |
|
|
|
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"); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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<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 |
|
|
|
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; |
|
} |
|
|
|
|
|
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 |
|
|
|
if (statReqV2 != null && !shc.negotiatedProtocol.useTLS13PlusSpec()) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { |
|
SSLLogger.fine("SH Processing status_request_v2 extension"); |
|
} |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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. |
|
|
|
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; |
|
} |
|
|
|
|
|
X509Possession x509Possession = null; |
|
for (SSLPossession possession : shc.handshakePossessions) { |
|
if (possession instanceof X509Possession) { |
|
x509Possession = (X509Possession)possession; |
|
break; |
|
} |
|
} |
|
|
|
if (x509Possession == null) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.finest("Warning: no X.509 certificates found. " + |
|
"Stapling is disabled."); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.finest("Warning: lazy initialization " + |
|
"of the StatusResponseManager failed. " + |
|
"Stapling is disabled."); |
|
} |
|
params = null; |
|
} |
|
|
|
return params; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
} |
|
} |
|
} |
|
|