< prev index next >

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

Print this page

        

*** 1,7 **** /* ! * Copyright (c) 2015, 2016, 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 --- 1,7 ---- /* ! * 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,57 **** * * 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 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; 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; --- 20,69 ---- * * 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<CertId, ResponseCacheEntry> responseCache; private final URI defaultResponder; private final boolean respOverride;
*** 97,110 **** 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 * --- 109,124 ---- 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 *
*** 145,166 **** /** * 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() { ! debugLog("Clearing response cache"); responseCache.clear(); } /** * Returns the number of currently valid objects in the response cache. --- 159,182 ---- /** * 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.
*** 170,201 **** 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) { ! debugLog("OCSP NoCheck extension found. OCSP will be skipped"); return null; } else if (defaultResponder != null && respOverride) { ! debugLog("Responder override: URI is " + defaultResponder); return defaultResponder; } else { URI certURI = OCSP.getResponderURI(cert); return (certURI != null ? certURI : defaultResponder); } --- 186,225 ---- 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); }
*** 203,258 **** /** * Shutdown the thread pool */ void shutdown() { ! debugLog("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 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 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. */ ! Map<X509Certificate, byte[]> get(StatusRequestType type, ! StatusRequest 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 = " + 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) { 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())); --- 227,289 ---- /** * 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<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 // 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()));
*** 262,331 **** } else { StatusInfo sInfo = new StatusInfo(chain[0], cid); requestList.add(new OCSPFetchCall(sInfo, ocspReq)); } } catch (IOException exc) { ! debugLog("Exception during CertId creation: " + exc); } ! } else if (type == StatusRequestType.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) { ! debugLog("Exception during CertId creation: " + exc); } } } else { ! debugLog("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<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.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"); } } } } catch (InterruptedException | ExecutionException exc) { // Not sure what else to do here ! debugLog("Exception when getting data: " + exc); } } return Collections.unmodifiableMap(responseMap); } --- 293,379 ---- } 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<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()) { ! 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); }
*** 343,355 **** */ 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"); return null; } } ResponseCacheEntry respEntry = responseCache.get(cid); --- 391,407 ---- */ 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);
*** 357,372 **** // 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"); respEntry = null; } ! debugLog("Check cache for SN" + cid.getSerialNumber() + ": " + (respEntry != null ? "HIT" : "MISS")); return respEntry; } @Override public String toString() { --- 409,430 ---- // 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() {
*** 396,419 **** 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; --- 454,463 ----
*** 424,434 **** * 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 */ StatusInfo(X509Certificate subjectCert, X509Certificate issuerCert) throws IOException { this(subjectCert, new CertId(issuerCert, new SerialNumber(subjectCert.getSerialNumber()))); --- 468,478 ---- * 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())));
*** 469,491 **** * @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") : "<NULL>"); return sb.toString(); } } /** * Static nested class used as the data kept in the response cache. */ ! static class ResponseCacheEntry { final OCSPResponse.ResponseStatus status; final byte[] ocspBytes; final Date nextUpdate; final OCSPResponse.SingleResponse singleResp; final ResponderId respId; --- 513,538 ---- * @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") : ! "<NULL>"); 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;
*** 493,504 **** /** * 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"); --- 540,551 ---- /** * 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");
*** 513,524 **** 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; } } --- 560,572 ---- 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; } }
*** 535,545 **** List<Extension> extensions; List<ResponderId> 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. --- 583,594 ---- List<Extension> extensions; List<ResponderId> 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.
*** 547,587 **** 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(); } /** * 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() { ! debugLog("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."); return statInfo; } else { ! debugLog("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; --- 596,653 ---- 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<Extension> 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;
*** 593,617 **** // 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 + " (" + 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"); } } catch (IOException ioe) { ! debugLog("Caught exception: " + ioe); } return statInfo; } --- 659,690 ---- // 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; }
*** 624,649 **** */ 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"); } else { responseCache.put(certId, entry); ! debugLog("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 --- 697,728 ---- */ 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
*** 665,670 **** --- 744,963 ---- } 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 >