author | wetmore |
Fri, 11 May 2018 15:53:12 -0700 | |
branch | JDK-8145252-TLS13-branch |
changeset 56542 | 56aaa6cb3693 |
parent 47216 | 71c04702a3d5 |
child 51398 | 3c389a284345 |
permissions | -rw-r--r-- |
32032 | 1 |
/* |
56542 | 2 |
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. |
32032 | 3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 |
* |
|
5 |
* This code is free software; you can redistribute it and/or modify it |
|
6 |
* under the terms of the GNU General Public License version 2 only, as |
|
7 |
* published by the Free Software Foundation. Oracle designates this |
|
8 |
* particular file as subject to the "Classpath" exception as provided |
|
9 |
* by Oracle in the LICENSE file that accompanied this code. |
|
10 |
* |
|
11 |
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 |
* version 2 for more details (a copy is included in the LICENSE file that |
|
15 |
* accompanied this code). |
|
16 |
* |
|
17 |
* You should have received a copy of the GNU General Public License version |
|
18 |
* 2 along with this work; if not, write to the Free Software Foundation, |
|
19 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 |
* |
|
21 |
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 |
* or visit www.oracle.com if you need additional information or have any |
|
23 |
* questions. |
|
24 |
*/ |
|
25 |
package sun.security.ssl; |
|
26 |
||
27 |
import java.io.IOException; |
|
28 |
import java.net.URI; |
|
29 |
import java.net.URISyntaxException; |
|
30 |
import java.security.AccessController; |
|
56542 | 31 |
import java.security.cert.Extension; |
32032 | 32 |
import java.security.cert.X509Certificate; |
56542 | 33 |
import java.util.ArrayList; |
34 |
import java.util.Collections; |
|
35 |
import java.util.Date; |
|
36 |
import java.util.HashMap; |
|
37 |
import java.util.List; |
|
38 |
import java.util.Map; |
|
39 |
import java.util.Objects; |
|
40 |
import java.util.concurrent.Callable; |
|
41 |
import java.util.concurrent.ExecutionException; |
|
42 |
import java.util.concurrent.Executors; |
|
43 |
import java.util.concurrent.Future; |
|
44 |
import java.util.concurrent.ScheduledThreadPoolExecutor; |
|
45 |
import java.util.concurrent.ThreadFactory; |
|
46 |
import java.util.concurrent.ThreadPoolExecutor; |
|
47 |
import java.util.concurrent.TimeUnit; |
|
48 |
import sun.security.action.GetBooleanAction; |
|
49 |
import sun.security.action.GetIntegerAction; |
|
50 |
import sun.security.action.GetPropertyAction; |
|
32032 | 51 |
import sun.security.provider.certpath.CertId; |
52 |
import sun.security.provider.certpath.OCSP; |
|
53 |
import sun.security.provider.certpath.OCSPResponse; |
|
54 |
import sun.security.provider.certpath.ResponderId; |
|
55 |
import sun.security.util.Cache; |
|
56 |
import sun.security.x509.PKIXExtensions; |
|
57 |
import sun.security.x509.SerialNumber; |
|
56542 | 58 |
import sun.security.ssl.X509Authentication.X509Possession; |
59 |
import static sun.security.ssl.CertStatusExtension.*; |
|
32032 | 60 |
|
61 |
final class StatusResponseManager { |
|
62 |
private static final int DEFAULT_CORE_THREADS = 8; |
|
63 |
private static final int DEFAULT_CACHE_SIZE = 256; |
|
56542 | 64 |
private static final int DEFAULT_CACHE_LIFETIME = 3600; // seconds |
32032 | 65 |
|
66 |
private final ScheduledThreadPoolExecutor threadMgr; |
|
67 |
private final Cache<CertId, ResponseCacheEntry> responseCache; |
|
68 |
private final URI defaultResponder; |
|
69 |
private final boolean respOverride; |
|
70 |
private final int cacheCapacity; |
|
71 |
private final int cacheLifetime; |
|
72 |
private final boolean ignoreExtensions; |
|
73 |
||
74 |
/** |
|
75 |
* Create a StatusResponseManager with default parameters. |
|
76 |
*/ |
|
77 |
StatusResponseManager() { |
|
78 |
int cap = AccessController.doPrivileged( |
|
79 |
new GetIntegerAction("jdk.tls.stapling.cacheSize", |
|
80 |
DEFAULT_CACHE_SIZE)); |
|
81 |
cacheCapacity = cap > 0 ? cap : 0; |
|
82 |
||
83 |
int life = AccessController.doPrivileged( |
|
84 |
new GetIntegerAction("jdk.tls.stapling.cacheLifetime", |
|
85 |
DEFAULT_CACHE_LIFETIME)); |
|
86 |
cacheLifetime = life > 0 ? life : 0; |
|
87 |
||
37781
71ed5645f17c
8155775: Re-examine naming of privileged methods to access System properties
redestad
parents:
37593
diff
changeset
|
88 |
String uriStr = GetPropertyAction |
71ed5645f17c
8155775: Re-examine naming of privileged methods to access System properties
redestad
parents:
37593
diff
changeset
|
89 |
.privilegedGetProperty("jdk.tls.stapling.responderURI"); |
32032 | 90 |
URI tmpURI; |
91 |
try { |
|
92 |
tmpURI = ((uriStr != null && !uriStr.isEmpty()) ? |
|
93 |
new URI(uriStr) : null); |
|
94 |
} catch (URISyntaxException urise) { |
|
95 |
tmpURI = null; |
|
96 |
} |
|
97 |
defaultResponder = tmpURI; |
|
98 |
||
99 |
respOverride = AccessController.doPrivileged( |
|
100 |
new GetBooleanAction("jdk.tls.stapling.responderOverride")); |
|
101 |
ignoreExtensions = AccessController.doPrivileged( |
|
102 |
new GetBooleanAction("jdk.tls.stapling.ignoreExtensions")); |
|
103 |
||
104 |
threadMgr = new ScheduledThreadPoolExecutor(DEFAULT_CORE_THREADS, |
|
105 |
new ThreadFactory() { |
|
106 |
@Override |
|
107 |
public Thread newThread(Runnable r) { |
|
108 |
Thread t = Executors.defaultThreadFactory().newThread(r); |
|
109 |
t.setDaemon(true); |
|
110 |
return t; |
|
111 |
} |
|
39317
fbda4d400372
8143302: javax/net/ssl/Stapling/SSLSocketWithStapling.java fails intermittently: Server died
jnimeh
parents:
37781
diff
changeset
|
112 |
}, new ThreadPoolExecutor.DiscardPolicy()); |
32032 | 113 |
threadMgr.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); |
56542 | 114 |
threadMgr.setContinueExistingPeriodicTasksAfterShutdownPolicy( |
115 |
false); |
|
32032 | 116 |
threadMgr.setKeepAliveTime(5000, TimeUnit.MILLISECONDS); |
39317
fbda4d400372
8143302: javax/net/ssl/Stapling/SSLSocketWithStapling.java fails intermittently: Server died
jnimeh
parents:
37781
diff
changeset
|
117 |
threadMgr.allowCoreThreadTimeOut(true); |
56542 | 118 |
responseCache = Cache.newSoftMemoryCache( |
119 |
cacheCapacity, cacheLifetime); |
|
32032 | 120 |
} |
121 |
||
122 |
/** |
|
123 |
* Get the current cache lifetime setting |
|
124 |
* |
|
125 |
* @return the current cache lifetime value |
|
126 |
*/ |
|
127 |
int getCacheLifetime() { |
|
128 |
return cacheLifetime; |
|
129 |
} |
|
130 |
||
131 |
/** |
|
132 |
* Get the current maximum cache size. |
|
133 |
* |
|
134 |
* @return the current maximum cache size |
|
135 |
*/ |
|
136 |
int getCacheCapacity() { |
|
137 |
return cacheCapacity; |
|
138 |
} |
|
139 |
||
140 |
/** |
|
141 |
* Get the default OCSP responder URI, if previously set. |
|
142 |
* |
|
143 |
* @return the current default OCSP responder URI, or {@code null} if |
|
144 |
* it has not been set. |
|
145 |
*/ |
|
146 |
URI getDefaultResponder() { |
|
147 |
return defaultResponder; |
|
148 |
} |
|
149 |
||
150 |
/** |
|
151 |
* Get the URI override setting |
|
152 |
* |
|
153 |
* @return {@code true} if URI override has been set, {@code false} |
|
154 |
* otherwise. |
|
155 |
*/ |
|
156 |
boolean getURIOverride() { |
|
157 |
return respOverride; |
|
158 |
} |
|
159 |
||
160 |
/** |
|
161 |
* Get the ignore extensions setting. |
|
162 |
* |
|
163 |
* @return {@code true} if the {@code StatusResponseManager} will not |
|
56542 | 164 |
* pass OCSP Extensions in the TLS {@code status_request[_v2]} |
165 |
* extensions, {@code false} if extensions will be passed (the default). |
|
32032 | 166 |
*/ |
167 |
boolean getIgnoreExtensions() { |
|
168 |
return ignoreExtensions; |
|
169 |
} |
|
170 |
||
171 |
/** |
|
172 |
* Clear the status response cache |
|
173 |
*/ |
|
174 |
void clear() { |
|
56542 | 175 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
176 |
SSLLogger.fine("Clearing response cache"); |
|
177 |
} |
|
32032 | 178 |
responseCache.clear(); |
179 |
} |
|
180 |
||
181 |
/** |
|
182 |
* Returns the number of currently valid objects in the response cache. |
|
183 |
* |
|
184 |
* @return the number of valid objects in the response cache. |
|
185 |
*/ |
|
186 |
int size() { |
|
187 |
return responseCache.size(); |
|
188 |
} |
|
189 |
||
190 |
/** |
|
56542 | 191 |
* Obtain the URI use by the {@code StatusResponseManager} during |
192 |
* lookups. |
|
193 |
* |
|
32032 | 194 |
* This method takes into account not only the AIA extension from a |
195 |
* certificate to be checked, but also any default URI and possible |
|
196 |
* override settings for the response manager. |
|
197 |
* |
|
198 |
* @param cert the subject to get the responder URI from |
|
199 |
* |
|
56542 | 200 |
* @return a {@code URI} containing the address to the OCSP responder, |
201 |
* or {@code null} if no AIA extension exists in the certificate |
|
202 |
* and no default responder has been configured. |
|
32032 | 203 |
* |
204 |
* @throws NullPointerException if {@code cert} is {@code null}. |
|
205 |
*/ |
|
206 |
URI getURI(X509Certificate cert) { |
|
207 |
Objects.requireNonNull(cert); |
|
208 |
||
209 |
if (cert.getExtensionValue( |
|
210 |
PKIXExtensions.OCSPNoCheck_Id.toString()) != null) { |
|
56542 | 211 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
212 |
SSLLogger.fine( |
|
213 |
"OCSP NoCheck extension found. OCSP will be skipped"); |
|
214 |
} |
|
32032 | 215 |
return null; |
216 |
} else if (defaultResponder != null && respOverride) { |
|
56542 | 217 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
218 |
SSLLogger.fine( |
|
219 |
"Responder override: URI is " + defaultResponder); |
|
220 |
} |
|
32032 | 221 |
return defaultResponder; |
222 |
} else { |
|
223 |
URI certURI = OCSP.getResponderURI(cert); |
|
224 |
return (certURI != null ? certURI : defaultResponder); |
|
225 |
} |
|
226 |
} |
|
227 |
||
228 |
/** |
|
229 |
* Shutdown the thread pool |
|
230 |
*/ |
|
231 |
void shutdown() { |
|
56542 | 232 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
233 |
SSLLogger.fine("Shutting down " + threadMgr.getActiveCount() + |
|
234 |
" active threads"); |
|
235 |
} |
|
32032 | 236 |
threadMgr.shutdown(); |
237 |
} |
|
238 |
||
239 |
/** |
|
240 |
* Get a list of responses for a chain of certificates. |
|
56542 | 241 |
* |
242 |
* This will find OCSP responses from the cache, or failing that, |
|
243 |
* directly contact the OCSP responder. It is assumed that the |
|
244 |
* certificates in the provided chain are in their proper order |
|
245 |
* (from end-entity to trust anchor). |
|
32032 | 246 |
* |
247 |
* @param type the type of request being made of the |
|
248 |
* {@code StatusResponseManager} |
|
56542 | 249 |
* @param request the {@code CertStatusRequest} from the |
250 |
* status_request or status_request_v2 ClientHello extension. |
|
251 |
* A value of {@code null} is interpreted as providing no |
|
252 |
* responder IDs or extensions. |
|
253 |
* @param chain an array of 2 or more certificates. Each certificate |
|
254 |
* must be issued by the next certificate in the chain. |
|
32032 | 255 |
* @param delay the number of time units to delay before returning |
256 |
* responses. |
|
257 |
* @param unit the unit of time applied to the {@code delay} parameter |
|
258 |
* |
|
259 |
* @return an unmodifiable {@code Map} containing the certificate and |
|
260 |
* its usually |
|
261 |
* |
|
56542 | 262 |
* @throws SSLHandshakeException if an unsupported |
263 |
* {@code CertStatusRequest} is provided. |
|
32032 | 264 |
*/ |
56542 | 265 |
Map<X509Certificate, byte[]> get(CertStatusRequestType type, |
266 |
CertStatusRequest request, X509Certificate[] chain, long delay, |
|
32032 | 267 |
TimeUnit unit) { |
268 |
Map<X509Certificate, byte[]> responseMap = new HashMap<>(); |
|
269 |
List<OCSPFetchCall> requestList = new ArrayList<>(); |
|
270 |
||
56542 | 271 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
272 |
SSLLogger.fine( |
|
273 |
"Beginning check: Type = " + type + ", Chain length = " + |
|
32032 | 274 |
chain.length); |
56542 | 275 |
} |
32032 | 276 |
|
277 |
// It is assumed that the caller has ordered the certs in the chain |
|
278 |
// in the proper order (each certificate is issued by the next entry |
|
279 |
// in the provided chain). |
|
280 |
if (chain.length < 2) { |
|
281 |
return Collections.emptyMap(); |
|
282 |
} |
|
283 |
||
56542 | 284 |
if (type == CertStatusRequestType.OCSP) { |
32032 | 285 |
try { |
286 |
// For type OCSP, we only check the end-entity certificate |
|
287 |
OCSPStatusRequest ocspReq = (OCSPStatusRequest)request; |
|
288 |
CertId cid = new CertId(chain[1], |
|
289 |
new SerialNumber(chain[0].getSerialNumber())); |
|
290 |
ResponseCacheEntry cacheEntry = getFromCache(cid, ocspReq); |
|
291 |
if (cacheEntry != null) { |
|
292 |
responseMap.put(chain[0], cacheEntry.ocspBytes); |
|
293 |
} else { |
|
294 |
StatusInfo sInfo = new StatusInfo(chain[0], cid); |
|
295 |
requestList.add(new OCSPFetchCall(sInfo, ocspReq)); |
|
296 |
} |
|
297 |
} catch (IOException exc) { |
|
56542 | 298 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
299 |
SSLLogger.fine( |
|
300 |
"Exception during CertId creation: ", exc); |
|
301 |
} |
|
32032 | 302 |
} |
56542 | 303 |
} else if (type == CertStatusRequestType.OCSP_MULTI) { |
32032 | 304 |
// For type OCSP_MULTI, we check every cert in the chain that |
56542 | 305 |
// has a direct issuer at the next index. We won't have an |
306 |
// issuer certificate for the last certificate in the chain |
|
307 |
// and will not be able to create a CertId because of that. |
|
32032 | 308 |
OCSPStatusRequest ocspReq = (OCSPStatusRequest)request; |
309 |
int ctr; |
|
310 |
for (ctr = 0; ctr < chain.length - 1; ctr++) { |
|
311 |
try { |
|
56542 | 312 |
// The cert at "ctr" is the subject cert, "ctr + 1" |
313 |
// is the issuer certificate. |
|
32032 | 314 |
CertId cid = new CertId(chain[ctr + 1], |
56542 | 315 |
new SerialNumber(chain[ctr].getSerialNumber())); |
316 |
ResponseCacheEntry cacheEntry = |
|
317 |
getFromCache(cid, ocspReq); |
|
32032 | 318 |
if (cacheEntry != null) { |
319 |
responseMap.put(chain[ctr], cacheEntry.ocspBytes); |
|
320 |
} else { |
|
321 |
StatusInfo sInfo = new StatusInfo(chain[ctr], cid); |
|
322 |
requestList.add(new OCSPFetchCall(sInfo, ocspReq)); |
|
323 |
} |
|
324 |
} catch (IOException exc) { |
|
56542 | 325 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
326 |
SSLLogger.fine( |
|
327 |
"Exception during CertId creation: ", exc); |
|
328 |
} |
|
32032 | 329 |
} |
330 |
} |
|
331 |
} else { |
|
56542 | 332 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
333 |
SSLLogger.fine("Unsupported status request type: " + type); |
|
334 |
} |
|
32032 | 335 |
} |
336 |
||
337 |
// If we were able to create one or more Fetches, go and run all |
|
338 |
// of them in separate threads. For all the threads that completed |
|
56542 | 339 |
// in the allotted time, put those status responses into the |
340 |
// returned Map. |
|
32032 | 341 |
if (!requestList.isEmpty()) { |
342 |
try { |
|
343 |
// Set a bunch of threads to go do the fetching |
|
344 |
List<Future<StatusInfo>> resultList = |
|
345 |
threadMgr.invokeAll(requestList, delay, unit); |
|
346 |
||
347 |
// Go through the Futures and from any non-cancelled task, |
|
348 |
// get the bytes and attach them to the responseMap. |
|
349 |
for (Future<StatusInfo> task : resultList) { |
|
56542 | 350 |
if (!task.isDone()) { |
351 |
continue; |
|
352 |
} |
|
353 |
||
354 |
if (!task.isCancelled()) { |
|
355 |
StatusInfo info = task.get(); |
|
356 |
if (info != null && info.responseData != null) { |
|
357 |
responseMap.put(info.cert, |
|
358 |
info.responseData.ocspBytes); |
|
359 |
} else if (SSLLogger.isOn && |
|
360 |
SSLLogger.isOn("respmgr")) { |
|
361 |
SSLLogger.fine( |
|
362 |
"Completed task had no response data"); |
|
363 |
} |
|
364 |
} else { |
|
365 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
|
366 |
SSLLogger.fine("Found cancelled task"); |
|
32032 | 367 |
} |
368 |
} |
|
369 |
} |
|
370 |
} catch (InterruptedException | ExecutionException exc) { |
|
371 |
// Not sure what else to do here |
|
56542 | 372 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
373 |
SSLLogger.fine("Exception when getting data: ", exc); |
|
374 |
} |
|
32032 | 375 |
} |
376 |
} |
|
377 |
||
378 |
return Collections.unmodifiableMap(responseMap); |
|
379 |
} |
|
380 |
||
381 |
/** |
|
382 |
* Check the cache for a given {@code CertId}. |
|
383 |
* |
|
384 |
* @param cid the CertId of the response to look up |
|
385 |
* @param ocspRequest the OCSP request structure sent by the client |
|
386 |
* in the TLS status_request[_v2] hello extension. |
|
387 |
* |
|
388 |
* @return the {@code ResponseCacheEntry} for a specific CertId, or |
|
389 |
* {@code null} if it is not found or a nonce extension has been |
|
390 |
* requested by the caller. |
|
391 |
*/ |
|
392 |
private ResponseCacheEntry getFromCache(CertId cid, |
|
393 |
OCSPStatusRequest ocspRequest) { |
|
394 |
// Determine if the nonce extension is present in the request. If |
|
395 |
// so, then do not attempt to retrieve the response from the cache. |
|
56542 | 396 |
for (Extension ext : ocspRequest.extensions) { |
397 |
if (ext.getId().equals( |
|
398 |
PKIXExtensions.OCSPNonce_Id.toString())) { |
|
399 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
|
400 |
SSLLogger.fine( |
|
401 |
"Nonce extension found, skipping cache check"); |
|
402 |
} |
|
32032 | 403 |
return null; |
404 |
} |
|
405 |
} |
|
406 |
||
407 |
ResponseCacheEntry respEntry = responseCache.get(cid); |
|
408 |
||
409 |
// If the response entry has a nextUpdate and it has expired |
|
410 |
// before the cache expiration, purge it from the cache |
|
411 |
// and do not return it as a cache hit. |
|
412 |
if (respEntry != null && respEntry.nextUpdate != null && |
|
413 |
respEntry.nextUpdate.before(new Date())) { |
|
56542 | 414 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
415 |
SSLLogger.fine( |
|
416 |
"nextUpdate threshold exceeded, purging from cache"); |
|
417 |
} |
|
32032 | 418 |
respEntry = null; |
419 |
} |
|
420 |
||
56542 | 421 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
422 |
SSLLogger.fine( |
|
423 |
"Check cache for SN" + cid.getSerialNumber() + ": " + |
|
424 |
(respEntry != null ? "HIT" : "MISS")); |
|
425 |
} |
|
32032 | 426 |
return respEntry; |
427 |
} |
|
428 |
||
429 |
@Override |
|
430 |
public String toString() { |
|
431 |
StringBuilder sb = new StringBuilder("StatusResponseManager: "); |
|
432 |
||
433 |
sb.append("Core threads: ").append(threadMgr.getCorePoolSize()); |
|
434 |
sb.append(", Cache timeout: "); |
|
435 |
if (cacheLifetime > 0) { |
|
436 |
sb.append(cacheLifetime).append(" seconds"); |
|
437 |
} else { |
|
438 |
sb.append(" indefinite"); |
|
439 |
} |
|
440 |
||
441 |
sb.append(", Cache MaxSize: "); |
|
442 |
if (cacheCapacity > 0) { |
|
443 |
sb.append(cacheCapacity).append(" items"); |
|
444 |
} else { |
|
445 |
sb.append(" unbounded"); |
|
446 |
} |
|
447 |
||
448 |
sb.append(", Default URI: "); |
|
449 |
if (defaultResponder != null) { |
|
450 |
sb.append(defaultResponder); |
|
451 |
} else { |
|
452 |
sb.append("NONE"); |
|
453 |
} |
|
454 |
||
455 |
return sb.toString(); |
|
456 |
} |
|
457 |
||
458 |
/** |
|
459 |
* Inner class used to group request and response data. |
|
460 |
*/ |
|
461 |
class StatusInfo { |
|
462 |
final X509Certificate cert; |
|
463 |
final CertId cid; |
|
464 |
final URI responder; |
|
465 |
ResponseCacheEntry responseData; |
|
466 |
||
467 |
/** |
|
468 |
* Create a StatusInfo object from certificate data. |
|
469 |
* |
|
470 |
* @param subjectCert the certificate to be checked for revocation |
|
471 |
* @param issuerCert the issuer of the {@code subjectCert} |
|
472 |
* |
|
56542 | 473 |
* @throws IOException if CertId creation from the certificate fails |
32032 | 474 |
*/ |
475 |
StatusInfo(X509Certificate subjectCert, X509Certificate issuerCert) |
|
476 |
throws IOException { |
|
477 |
this(subjectCert, new CertId(issuerCert, |
|
478 |
new SerialNumber(subjectCert.getSerialNumber()))); |
|
479 |
} |
|
480 |
||
481 |
/** |
|
482 |
* Create a StatusInfo object from an existing subject certificate |
|
483 |
* and its corresponding CertId. |
|
484 |
* |
|
485 |
* @param subjectCert the certificate to be checked for revocation |
|
486 |
* @param cid the CertId for {@code subjectCert} |
|
487 |
*/ |
|
488 |
StatusInfo(X509Certificate subjectCert, CertId certId) { |
|
489 |
cert = subjectCert; |
|
490 |
cid = certId; |
|
491 |
responder = getURI(cert); |
|
492 |
responseData = null; |
|
493 |
} |
|
494 |
||
495 |
/** |
|
496 |
* Copy constructor (used primarily for rescheduling). |
|
497 |
* This will do a member-wise copy with the exception of the |
|
498 |
* responseData and extensions fields, which should not persist |
|
499 |
* in a rescheduled fetch. |
|
500 |
* |
|
501 |
* @param orig the original {@code StatusInfo} |
|
502 |
*/ |
|
503 |
StatusInfo(StatusInfo orig) { |
|
504 |
this.cert = orig.cert; |
|
505 |
this.cid = orig.cid; |
|
506 |
this.responder = orig.responder; |
|
507 |
this.responseData = null; |
|
508 |
} |
|
509 |
||
510 |
/** |
|
511 |
* Return a String representation of the {@code StatusInfo} |
|
512 |
* |
|
513 |
* @return a {@code String} representation of this object |
|
514 |
*/ |
|
515 |
@Override |
|
516 |
public String toString() { |
|
517 |
StringBuilder sb = new StringBuilder("StatusInfo:"); |
|
56542 | 518 |
sb.append("\n\tCert: ").append( |
519 |
this.cert.getSubjectX500Principal()); |
|
32032 | 520 |
sb.append("\n\tSerial: ").append(this.cert.getSerialNumber()); |
521 |
sb.append("\n\tResponder: ").append(this.responder); |
|
56542 | 522 |
sb.append("\n\tResponse data: ").append( |
523 |
this.responseData != null ? |
|
524 |
(this.responseData.ocspBytes.length + " bytes") : |
|
525 |
"<NULL>"); |
|
32032 | 526 |
return sb.toString(); |
527 |
} |
|
528 |
} |
|
529 |
||
530 |
/** |
|
531 |
* Static nested class used as the data kept in the response cache. |
|
532 |
*/ |
|
56542 | 533 |
class ResponseCacheEntry { |
32032 | 534 |
final OCSPResponse.ResponseStatus status; |
535 |
final byte[] ocspBytes; |
|
536 |
final Date nextUpdate; |
|
537 |
final OCSPResponse.SingleResponse singleResp; |
|
538 |
final ResponderId respId; |
|
539 |
||
540 |
/** |
|
541 |
* Create a new cache entry from the raw bytes of the response |
|
542 |
* |
|
543 |
* @param responseBytes the DER encoding for the OCSP response |
|
544 |
* |
|
56542 | 545 |
* @throws IOException if an {@code OCSPResponse} cannot be |
546 |
* created from the encoded bytes. |
|
32032 | 547 |
*/ |
548 |
ResponseCacheEntry(byte[] responseBytes, CertId cid) |
|
549 |
throws IOException { |
|
550 |
Objects.requireNonNull(responseBytes, |
|
551 |
"Non-null responseBytes required"); |
|
552 |
Objects.requireNonNull(cid, "Non-null Cert ID required"); |
|
553 |
||
554 |
ocspBytes = responseBytes.clone(); |
|
555 |
OCSPResponse oResp = new OCSPResponse(ocspBytes); |
|
556 |
status = oResp.getResponseStatus(); |
|
557 |
respId = oResp.getResponderId(); |
|
558 |
singleResp = oResp.getSingleResponse(cid); |
|
559 |
if (status == OCSPResponse.ResponseStatus.SUCCESSFUL) { |
|
560 |
if (singleResp != null) { |
|
561 |
// Pull out the nextUpdate field in advance because the |
|
562 |
// Date is cloned. |
|
563 |
nextUpdate = singleResp.getNextUpdate(); |
|
564 |
} else { |
|
56542 | 565 |
throw new IOException( |
566 |
"Unable to find SingleResponse for SN " + |
|
567 |
cid.getSerialNumber()); |
|
32032 | 568 |
} |
569 |
} else { |
|
570 |
nextUpdate = null; |
|
571 |
} |
|
572 |
} |
|
573 |
} |
|
574 |
||
575 |
/** |
|
576 |
* Inner Callable class that does the actual work of looking up OCSP |
|
577 |
* responses, first looking at the cache and doing OCSP requests if |
|
578 |
* a cache miss occurs. |
|
579 |
*/ |
|
580 |
class OCSPFetchCall implements Callable<StatusInfo> { |
|
581 |
StatusInfo statInfo; |
|
582 |
OCSPStatusRequest ocspRequest; |
|
583 |
List<Extension> extensions; |
|
584 |
List<ResponderId> responderIds; |
|
585 |
||
586 |
/** |
|
587 |
* A constructor that builds the OCSPFetchCall from the provided |
|
56542 | 588 |
* StatusInfo and information from the status_request[_v2] |
589 |
* extension. |
|
32032 | 590 |
* |
591 |
* @param info the {@code StatusInfo} containing the subject |
|
592 |
* certificate, CertId, and other supplemental info. |
|
593 |
* @param request the {@code OCSPStatusRequest} containing any |
|
594 |
* responder IDs and extensions. |
|
595 |
*/ |
|
596 |
public OCSPFetchCall(StatusInfo info, OCSPStatusRequest request) { |
|
597 |
statInfo = Objects.requireNonNull(info, |
|
598 |
"Null StatusInfo not allowed"); |
|
599 |
ocspRequest = Objects.requireNonNull(request, |
|
600 |
"Null OCSPStatusRequest not allowed"); |
|
56542 | 601 |
extensions = ocspRequest.extensions; |
602 |
responderIds = ocspRequest.responderIds; |
|
32032 | 603 |
} |
604 |
||
605 |
/** |
|
606 |
* Get an OCSP response, either from the cache or from a responder. |
|
607 |
* |
|
56542 | 608 |
* @return The StatusInfo object passed into the |
609 |
* {@code OCSPFetchCall} constructor, with the |
|
610 |
* {@code responseData} field filled in with the response |
|
611 |
* or {@code null} if no response can be obtained. |
|
32032 | 612 |
*/ |
613 |
@Override |
|
614 |
public StatusInfo call() { |
|
56542 | 615 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
616 |
SSLLogger.fine( |
|
617 |
"Starting fetch for SN " + |
|
618 |
statInfo.cid.getSerialNumber()); |
|
619 |
} |
|
32032 | 620 |
try { |
621 |
ResponseCacheEntry cacheEntry; |
|
622 |
List<Extension> extsToSend; |
|
623 |
||
624 |
if (statInfo.responder == null) { |
|
56542 | 625 |
// If we have no URI then there's nothing to do |
626 |
// but return. |
|
627 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
|
628 |
SSLLogger.fine( |
|
629 |
"Null URI detected, OCSP fetch aborted"); |
|
630 |
} |
|
32032 | 631 |
return statInfo; |
632 |
} else { |
|
56542 | 633 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
634 |
SSLLogger.fine( |
|
635 |
"Attempting fetch from " + statInfo.responder); |
|
636 |
} |
|
32032 | 637 |
} |
638 |
||
639 |
// If the StatusResponseManager has been configured to not |
|
56542 | 640 |
// forward extensions, then set extensions to an empty |
641 |
// list. |
|
642 |
// |
|
643 |
// We will forward the extensions unless one of two |
|
644 |
// conditions occur: |
|
645 |
// (1) The jdk.tls.stapling.ignoreExtensions property is |
|
646 |
// true, or |
|
647 |
// (2) There is a non-empty ResponderId list. |
|
648 |
// |
|
32032 | 649 |
// ResponderId selection is a feature that will be |
650 |
// supported in the future. |
|
651 |
extsToSend = (ignoreExtensions || !responderIds.isEmpty()) ? |
|
652 |
Collections.emptyList() : extensions; |
|
653 |
||
654 |
byte[] respBytes = OCSP.getOCSPBytes( |
|
655 |
Collections.singletonList(statInfo.cid), |
|
656 |
statInfo.responder, extsToSend); |
|
657 |
||
658 |
if (respBytes != null) { |
|
659 |
// Place the data into the response cache |
|
660 |
cacheEntry = new ResponseCacheEntry(respBytes, |
|
661 |
statInfo.cid); |
|
662 |
||
663 |
// Get the response status and act on it appropriately |
|
56542 | 664 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
665 |
SSLLogger.fine("OCSP Status: " + cacheEntry.status + |
|
32032 | 666 |
" (" + respBytes.length + " bytes)"); |
56542 | 667 |
} |
32032 | 668 |
if (cacheEntry.status == |
669 |
OCSPResponse.ResponseStatus.SUCCESSFUL) { |
|
670 |
// Set the response in the returned StatusInfo |
|
671 |
statInfo.responseData = cacheEntry; |
|
672 |
||
673 |
// Add the response to the cache (if applicable) |
|
674 |
addToCache(statInfo.cid, cacheEntry); |
|
675 |
} |
|
676 |
} else { |
|
56542 | 677 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
678 |
SSLLogger.fine( |
|
679 |
"No data returned from OCSP Responder"); |
|
680 |
} |
|
32032 | 681 |
} |
682 |
} catch (IOException ioe) { |
|
56542 | 683 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
684 |
SSLLogger.fine("Caught exception: ", ioe); |
|
685 |
} |
|
32032 | 686 |
} |
687 |
||
688 |
return statInfo; |
|
689 |
} |
|
690 |
||
691 |
/** |
|
692 |
* Add a response to the cache. |
|
693 |
* |
|
694 |
* @param certId The {@code CertId} for the OCSP response |
|
695 |
* @param entry A cache entry containing the response bytes and |
|
696 |
* the {@code OCSPResponse} built from those bytes. |
|
697 |
*/ |
|
698 |
private void addToCache(CertId certId, ResponseCacheEntry entry) { |
|
699 |
// If no cache lifetime has been set on entries then |
|
700 |
// don't cache this response if there is no nextUpdate field |
|
701 |
if (entry.nextUpdate == null && cacheLifetime == 0) { |
|
56542 | 702 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
703 |
SSLLogger.fine("Not caching this OCSP response"); |
|
704 |
} |
|
32032 | 705 |
} else { |
706 |
responseCache.put(certId, entry); |
|
56542 | 707 |
if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { |
708 |
SSLLogger.fine( |
|
709 |
"Added response for SN " + |
|
710 |
certId.getSerialNumber() + |
|
32032 | 711 |
" to cache"); |
56542 | 712 |
} |
32032 | 713 |
} |
714 |
} |
|
715 |
||
716 |
/** |
|
717 |
* Determine the delay to use when scheduling the task that will |
|
718 |
* update the OCSP response. This is the shorter time between the |
|
56542 | 719 |
* cache lifetime and the nextUpdate. If no nextUpdate is present |
720 |
* in the response, then only the cache lifetime is used. |
|
32032 | 721 |
* If cache timeouts are disabled (a zero value) and there's no |
56542 | 722 |
* nextUpdate, then the entry is not cached and no rescheduling |
723 |
* will take place. |
|
32032 | 724 |
* |
725 |
* @param nextUpdate a {@code Date} object corresponding to the |
|
726 |
* next update time from a SingleResponse. |
|
727 |
* |
|
728 |
* @return the number of seconds of delay before the next fetch |
|
729 |
* should be executed. A zero value means that the fetch |
|
730 |
* should happen immediately, while a value less than zero |
|
731 |
* indicates no rescheduling should be done. |
|
732 |
*/ |
|
733 |
private long getNextTaskDelay(Date nextUpdate) { |
|
734 |
long delaySec; |
|
735 |
int lifetime = getCacheLifetime(); |
|
736 |
||
737 |
if (nextUpdate != null) { |
|
738 |
long nuDiffSec = (nextUpdate.getTime() - |
|
739 |
System.currentTimeMillis()) / 1000; |
|
740 |
delaySec = lifetime > 0 ? Long.min(nuDiffSec, lifetime) : |
|
741 |
nuDiffSec; |
|
742 |
} else { |
|
743 |
delaySec = lifetime > 0 ? lifetime : -1; |
|
744 |
} |
|
745 |
||
746 |
return delaySec; |
|
747 |
} |
|
748 |
} |
|
56542 | 749 |
|
750 |
static final StaplingParameters processStapling( |
|
751 |
ServerHandshakeContext shc) { |
|
752 |
StaplingParameters params = null; |
|
753 |
SSLExtension ext = null; |
|
754 |
CertStatusRequestType type = null; |
|
755 |
CertStatusRequest req = null; |
|
756 |
Map<X509Certificate, byte[]> responses; |
|
757 |
||
758 |
// If this feature has not been enabled, then no more processing |
|
759 |
// is necessary. Also we will only staple if we're doing a full |
|
760 |
// handshake. |
|
761 |
if (!shc.sslContext.isStaplingEnabled(false) || shc.isResumption) { |
|
762 |
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
763 |
SSLLogger.fine("Staping disabled or is a resumed session"); |
|
764 |
} |
|
765 |
return null; |
|
766 |
} |
|
767 |
||
768 |
// Check if the client has asserted the status_request[_v2] extension(s) |
|
769 |
Map<SSLExtension, SSLExtension.SSLExtensionSpec> exts = |
|
770 |
shc.handshakeExtensions; |
|
771 |
CertStatusRequestSpec statReq = (CertStatusRequestSpec)exts.get( |
|
772 |
SSLExtension.CH_STATUS_REQUEST); |
|
773 |
CertStatusRequestV2Spec statReqV2 = (CertStatusRequestV2Spec) |
|
774 |
exts.get(SSLExtension.CH_STATUS_REQUEST_V2); |
|
775 |
||
776 |
// Determine which type of stapling we are doing and assert the |
|
777 |
// proper extension in the server hello. |
|
778 |
// Favor status_request_v2 over status_request and ocsp_multi |
|
779 |
// over ocsp. |
|
780 |
// If multiple ocsp or ocsp_multi types exist, select the first |
|
781 |
// instance of a given type. Also since we don't support ResponderId |
|
782 |
// selection yet, only accept a request if the ResponderId field |
|
783 |
// is empty. Finally, we'll only do this in (D)TLS 1.2 or earlier. |
|
784 |
if (statReqV2 != null && !shc.negotiatedProtocol.useTLS13PlusSpec()) { |
|
785 |
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { |
|
786 |
SSLLogger.fine("SH Processing status_request_v2 extension"); |
|
787 |
} |
|
788 |
// RFC 6961 stapling |
|
789 |
ext = SSLExtension.CH_STATUS_REQUEST_V2; |
|
790 |
int ocspIdx = -1; |
|
791 |
int ocspMultiIdx = -1; |
|
792 |
CertStatusRequest[] reqItems = statReqV2.certStatusRequests; |
|
793 |
for (int pos = 0; (pos < reqItems.length && |
|
794 |
(ocspIdx == -1 || ocspMultiIdx == -1)); pos++) { |
|
795 |
CertStatusRequest item = reqItems[pos]; |
|
796 |
CertStatusRequestType curType = |
|
797 |
CertStatusRequestType.valueOf(item.statusType); |
|
798 |
if (ocspIdx < 0 && curType == CertStatusRequestType.OCSP) { |
|
799 |
OCSPStatusRequest ocspReq = (OCSPStatusRequest)item; |
|
800 |
// We currently only accept empty responder ID lists |
|
801 |
// but may support them in the future |
|
802 |
if (ocspReq.responderIds.isEmpty()) { |
|
803 |
ocspIdx = pos; |
|
804 |
} |
|
805 |
} else if (ocspMultiIdx < 0 && |
|
806 |
curType == CertStatusRequestType.OCSP_MULTI) { |
|
807 |
OCSPStatusRequest ocspReq = (OCSPStatusRequest)item; |
|
808 |
// We currently only accept empty responder ID lists |
|
809 |
// but may support them in the future |
|
810 |
if (ocspReq.responderIds.isEmpty()) { |
|
811 |
ocspMultiIdx = pos; |
|
812 |
} |
|
813 |
} |
|
814 |
} |
|
815 |
if (ocspMultiIdx >= 0) { |
|
816 |
req = reqItems[ocspMultiIdx]; |
|
817 |
type = CertStatusRequestType.valueOf(req.statusType); |
|
818 |
} else if (ocspIdx >= 0) { |
|
819 |
req = reqItems[ocspIdx]; |
|
820 |
type = CertStatusRequestType.valueOf(req.statusType); |
|
821 |
} else { |
|
822 |
if (SSLLogger.isOn && |
|
823 |
SSLLogger.isOn("ssl,handshake")) { |
|
824 |
SSLLogger.finest("Warning: No suitable request " + |
|
825 |
"found in the status_request_v2 extension."); |
|
826 |
} |
|
827 |
} |
|
828 |
} |
|
829 |
||
830 |
// Only attempt to process a status_request extension if: |
|
831 |
// * The status_request extension is set AND |
|
832 |
// * either the status_request_v2 extension is not present OR |
|
833 |
// * none of the underlying OCSPStatusRequest structures is |
|
834 |
// suitable for stapling. |
|
835 |
// If either of the latter two bullet items is true the ext, |
|
836 |
// type and req variables should all be null. If any are null |
|
837 |
// we will try processing an asserted status_request. |
|
838 |
if ((statReq != null) && |
|
839 |
(ext == null || type == null || req == null)) { |
|
840 |
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { |
|
841 |
SSLLogger.fine("SH Processing status_request extension"); |
|
842 |
} |
|
843 |
ext = SSLExtension.CH_STATUS_REQUEST; |
|
844 |
type = CertStatusRequestType.valueOf( |
|
845 |
statReq.statusRequest.statusType); |
|
846 |
if (type == CertStatusRequestType.OCSP) { |
|
847 |
// If the type is OCSP, then the request is guaranteed |
|
848 |
// to be OCSPStatusRequest |
|
849 |
OCSPStatusRequest ocspReq = |
|
850 |
(OCSPStatusRequest)statReq.statusRequest; |
|
851 |
if (ocspReq.responderIds.isEmpty()) { |
|
852 |
req = ocspReq; |
|
853 |
} else { |
|
854 |
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
855 |
SSLLogger.finest("Warning: No suitable request " + |
|
856 |
"found in the status_request extension."); |
|
857 |
} |
|
858 |
} |
|
859 |
} |
|
860 |
} |
|
861 |
||
862 |
// If, after walking through the extensions we were unable to |
|
863 |
// find a suitable StatusRequest, then stapling is disabled. |
|
864 |
// The ext, type and req variables must have been set to continue. |
|
865 |
if (type == null || req == null || ext == null) { |
|
866 |
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
867 |
SSLLogger.fine("No suitable status_request or " + |
|
868 |
"status_request_v2, stapling is disabled"); |
|
869 |
} |
|
870 |
return null; |
|
871 |
} |
|
872 |
||
873 |
// Get the cert chain since we'll need it for OCSP checking |
|
874 |
X509Possession x509Possession = null; |
|
875 |
for (SSLPossession possession : shc.handshakePossessions) { |
|
876 |
if (possession instanceof X509Possession) { |
|
877 |
x509Possession = (X509Possession)possession; |
|
878 |
break; |
|
879 |
} |
|
880 |
} |
|
881 |
||
882 |
if (x509Possession == null) { // unlikely |
|
883 |
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
884 |
SSLLogger.finest("Warning: no X.509 certificates found. " + |
|
885 |
"Stapling is disabled."); |
|
886 |
} |
|
887 |
return null; |
|
888 |
} |
|
889 |
||
890 |
// Get the OCSP responses from the StatusResponseManager |
|
891 |
X509Certificate[] certs = x509Possession.popCerts; |
|
892 |
StatusResponseManager statRespMgr = |
|
893 |
shc.sslContext.getStatusResponseManager(); |
|
894 |
if (statRespMgr != null) { |
|
895 |
// For the purposes of the fetch from the SRM, override the |
|
896 |
// type when it is TLS 1.3 so it always gets responses for |
|
897 |
// all certs it can. This should not change the type field |
|
898 |
// in the StaplingParameters though. |
|
899 |
CertStatusRequestType fetchType = |
|
900 |
shc.negotiatedProtocol.useTLS13PlusSpec() ? |
|
901 |
CertStatusRequestType.OCSP_MULTI : type; |
|
902 |
responses = statRespMgr.get(fetchType, req, certs, |
|
903 |
shc.statusRespTimeout, TimeUnit.MILLISECONDS); |
|
904 |
if (!responses.isEmpty()) { |
|
905 |
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
906 |
SSLLogger.finest("Response manager returned " + |
|
907 |
responses.size() + " entries."); |
|
908 |
} |
|
909 |
// If this RFC 6066-style stapling (SSL cert only) then the |
|
910 |
// response cannot be zero length |
|
911 |
if (type == CertStatusRequestType.OCSP) { |
|
912 |
byte[] respDER = responses.get(certs[0]); |
|
913 |
if (respDER == null || respDER.length <= 0) { |
|
914 |
if (SSLLogger.isOn && |
|
915 |
SSLLogger.isOn("ssl,handshake")) { |
|
916 |
SSLLogger.finest("Warning: Null or zero-length " + |
|
917 |
"response found for leaf certificate. " + |
|
918 |
"Stapling is disabled."); |
|
919 |
} |
|
920 |
return null; |
|
921 |
} |
|
922 |
} |
|
923 |
params = new StaplingParameters(ext, type, req, responses); |
|
924 |
} else { |
|
925 |
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
926 |
SSLLogger.finest("Warning: no OCSP responses obtained. " + |
|
927 |
"Stapling is disabled."); |
|
928 |
} |
|
929 |
} |
|
930 |
} else { |
|
931 |
// This should not happen, but if lazy initialization of the |
|
932 |
// StatusResponseManager doesn't occur we should turn off stapling. |
|
933 |
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
934 |
SSLLogger.finest("Warning: lazy initialization " + |
|
935 |
"of the StatusResponseManager failed. " + |
|
936 |
"Stapling is disabled."); |
|
937 |
} |
|
938 |
params = null; |
|
939 |
} |
|
940 |
||
941 |
return params; |
|
942 |
} |
|
943 |
||
944 |
/** |
|
945 |
* Inner class used to hold stapling parameters needed by the handshaker |
|
946 |
* when stapling is active. |
|
947 |
*/ |
|
948 |
static final class StaplingParameters { |
|
949 |
final SSLExtension statusRespExt; |
|
950 |
final CertStatusRequestType statReqType; |
|
951 |
final CertStatusRequest statReqData; |
|
952 |
final Map<X509Certificate, byte[]> responseMap; |
|
953 |
||
954 |
StaplingParameters(SSLExtension ext, CertStatusRequestType type, |
|
955 |
CertStatusRequest req, Map<X509Certificate, byte[]> responses) { |
|
956 |
statusRespExt = ext; |
|
957 |
statReqType = type; |
|
958 |
statReqData = req; |
|
959 |
responseMap = responses; |
|
960 |
} |
|
961 |
} |
|
32032 | 962 |
} |
56542 | 963 |