1 /* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 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 26 package sun.security.ssl; 27 28 import java.io.IOException; 29 import java.net.URI; 30 import java.net.URISyntaxException; 31 import java.security.AccessController; 32 import java.security.cert.X509Certificate; 33 import java.security.cert.Extension; 34 import java.util.*; 35 import java.util.concurrent.*; 36 37 import sun.security.provider.certpath.CertId; 38 import sun.security.provider.certpath.OCSP; 39 import sun.security.provider.certpath.OCSPResponse; 40 import sun.security.provider.certpath.ResponderId; 41 import sun.security.util.Cache; 42 import sun.security.x509.PKIXExtensions; 43 import sun.security.x509.SerialNumber; 44 import sun.security.action.GetBooleanAction; 45 import sun.security.action.GetIntegerAction; 46 import sun.security.action.GetPropertyAction; 47 48 final class StatusResponseManager { 49 private static final int DEFAULT_CORE_THREADS = 8; 50 private static final int DEFAULT_CACHE_SIZE = 256; 51 private static final int DEFAULT_CACHE_LIFETIME = 3600; // seconds 52 private static final Debug debug = Debug.getInstance("ssl"); 53 54 private final ScheduledThreadPoolExecutor threadMgr; 55 private final Cache<CertId, ResponseCacheEntry> responseCache; 56 private final URI defaultResponder; 57 private final boolean respOverride; 58 private final int cacheCapacity; 59 private final int cacheLifetime; 60 private final boolean ignoreExtensions; 61 62 /** 63 * Create a StatusResponseManager with default parameters. 64 */ 65 StatusResponseManager() { 66 int cap = AccessController.doPrivileged( 67 new GetIntegerAction("jdk.tls.stapling.cacheSize", 68 DEFAULT_CACHE_SIZE)); 69 cacheCapacity = cap > 0 ? cap : 0; 70 71 int life = AccessController.doPrivileged( 72 new GetIntegerAction("jdk.tls.stapling.cacheLifetime", 73 DEFAULT_CACHE_LIFETIME)); 74 cacheLifetime = life > 0 ? life : 0; 75 76 String uriStr = AccessController.doPrivileged( 77 new GetPropertyAction("jdk.tls.stapling.responderURI")); 78 URI tmpURI; 79 try { 80 tmpURI = ((uriStr != null && !uriStr.isEmpty()) ? 81 new URI(uriStr) : null); 82 } catch (URISyntaxException urise) { 83 tmpURI = null; 84 } 85 defaultResponder = tmpURI; 86 87 respOverride = AccessController.doPrivileged( 88 new GetBooleanAction("jdk.tls.stapling.responderOverride")); 89 ignoreExtensions = AccessController.doPrivileged( 90 new GetBooleanAction("jdk.tls.stapling.ignoreExtensions")); 91 92 threadMgr = new ScheduledThreadPoolExecutor(DEFAULT_CORE_THREADS, 93 new ThreadFactory() { 94 @Override 95 public Thread newThread(Runnable r) { 96 Thread t = Executors.defaultThreadFactory().newThread(r); 97 t.setDaemon(true); 98 return t; 99 } 100 }); 101 threadMgr.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); 102 threadMgr.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); 103 threadMgr.setKeepAliveTime(5000, TimeUnit.MILLISECONDS); 104 responseCache = Cache.newSoftMemoryCache(cacheCapacity, cacheLifetime); 105 } 106 107 /** 108 * Get the current cache lifetime setting 109 * 110 * @return the current cache lifetime value 111 */ 112 int getCacheLifetime() { 113 return cacheLifetime; 114 } 115 116 /** 117 * Get the current maximum cache size. 118 * 119 * @return the current maximum cache size 120 */ 121 int getCacheCapacity() { 122 return cacheCapacity; 123 } 124 125 /** 126 * Get the default OCSP responder URI, if previously set. 127 * 128 * @return the current default OCSP responder URI, or {@code null} if 129 * it has not been set. 130 */ 131 URI getDefaultResponder() { 132 return defaultResponder; 133 } 134 135 /** 136 * Get the URI override setting 137 * 138 * @return {@code true} if URI override has been set, {@code false} 139 * otherwise. 140 */ 141 boolean getURIOverride() { 142 return respOverride; 143 } 144 145 /** 146 * Get the ignore extensions setting. 147 * 148 * @return {@code true} if the {@code StatusResponseManager} will not 149 * pass OCSP Extensions in the TLS {@code status_request[_v2]} extensions, 150 * {@code false} if extensions will be passed (the default). 151 */ 152 boolean getIgnoreExtensions() { 153 return ignoreExtensions; 154 } 155 156 /** 157 * Clear the status response cache 158 */ 159 void clear() { 160 debugLog("Clearing response cache"); 161 responseCache.clear(); 162 } 163 164 /** 165 * Returns the number of currently valid objects in the response cache. 166 * 167 * @return the number of valid objects in the response cache. 168 */ 169 int size() { 170 return responseCache.size(); 171 } 172 173 /** 174 * Obtain the URI use by the {@code StatusResponseManager} during lookups. 175 * This method takes into account not only the AIA extension from a 176 * certificate to be checked, but also any default URI and possible 177 * override settings for the response manager. 178 * 179 * @param cert the subject to get the responder URI from 180 * 181 * @return a {@code URI} containing the address to the OCSP responder, or 182 * {@code null} if no AIA extension exists in the certificate and no 183 * default responder has been configured. 184 * 185 * @throws NullPointerException if {@code cert} is {@code null}. 186 */ 187 URI getURI(X509Certificate cert) { 188 Objects.requireNonNull(cert); 189 190 if (cert.getExtensionValue( 191 PKIXExtensions.OCSPNoCheck_Id.toString()) != null) { 192 debugLog("OCSP NoCheck extension found. OCSP will be skipped"); 193 return null; 194 } else if (defaultResponder != null && respOverride) { 195 debugLog("Responder override: URI is " + defaultResponder); 196 return defaultResponder; 197 } else { 198 URI certURI = OCSP.getResponderURI(cert); 199 return (certURI != null ? certURI : defaultResponder); 200 } 201 } 202 203 /** 204 * Shutdown the thread pool 205 */ 206 void shutdown() { 207 debugLog("Shutting down " + threadMgr.getActiveCount() + 208 " active threads"); 209 threadMgr.shutdown(); 210 } 211 212 /** 213 * Get a list of responses for a chain of certificates. 214 * This will find OCSP responses from the cache, or failing that, directly 215 * contact the OCSP responder. It is assumed that the certificates in 216 * the provided chain are in their proper order (from end-entity to 217 * trust anchor). 218 * 219 * @param type the type of request being made of the 220 * {@code StatusResponseManager} 221 * @param request the {@code StatusRequest} from the status_request or 222 * status_request_v2 ClientHello extension. A value of {@code null} 223 * is interpreted as providing no responder IDs or extensions. 224 * @param chain an array of 2 or more certificates. Each certificate must 225 * be issued by the next certificate in the chain. 226 * @param delay the number of time units to delay before returning 227 * responses. 228 * @param unit the unit of time applied to the {@code delay} parameter 229 * 230 * @return an unmodifiable {@code Map} containing the certificate and 231 * its usually 232 * 233 * @throws SSLHandshakeException if an unsupported {@code StatusRequest} 234 * is provided. 235 */ 236 Map<X509Certificate, byte[]> get(StatusRequestType type, 237 StatusRequest request, X509Certificate[] chain, long delay, 238 TimeUnit unit) { 239 Map<X509Certificate, byte[]> responseMap = new HashMap<>(); 240 List<OCSPFetchCall> requestList = new ArrayList<>(); 241 242 debugLog("Beginning check: Type = " + type + ", Chain length = " + 243 chain.length); 244 245 // It is assumed that the caller has ordered the certs in the chain 246 // in the proper order (each certificate is issued by the next entry 247 // in the provided chain). 248 if (chain.length < 2) { 249 return Collections.emptyMap(); 250 } 251 252 if (type == StatusRequestType.OCSP) { 253 try { 254 // For type OCSP, we only check the end-entity certificate 255 OCSPStatusRequest ocspReq = (OCSPStatusRequest)request; 256 CertId cid = new CertId(chain[1], 257 new SerialNumber(chain[0].getSerialNumber())); 258 ResponseCacheEntry cacheEntry = getFromCache(cid, ocspReq); 259 if (cacheEntry != null) { 260 responseMap.put(chain[0], cacheEntry.ocspBytes); 261 } else { 262 StatusInfo sInfo = new StatusInfo(chain[0], cid); 263 requestList.add(new OCSPFetchCall(sInfo, ocspReq)); 264 } 265 } catch (IOException exc) { 266 debugLog("Exception during CertId creation: " + exc); 267 } 268 } else if (type == StatusRequestType.OCSP_MULTI) { 269 // For type OCSP_MULTI, we check every cert in the chain that 270 // has a direct issuer at the next index. We won't have an issuer 271 // certificate for the last certificate in the chain and will 272 // not be able to create a CertId because of that. 273 OCSPStatusRequest ocspReq = (OCSPStatusRequest)request; 274 int ctr; 275 for (ctr = 0; ctr < chain.length - 1; ctr++) { 276 try { 277 // The cert at "ctr" is the subject cert, "ctr + 1" is the 278 // issuer certificate. 279 CertId cid = new CertId(chain[ctr + 1], 280 new SerialNumber(chain[ctr].getSerialNumber())); 281 ResponseCacheEntry cacheEntry = getFromCache(cid, ocspReq); 282 if (cacheEntry != null) { 283 responseMap.put(chain[ctr], cacheEntry.ocspBytes); 284 } else { 285 StatusInfo sInfo = new StatusInfo(chain[ctr], cid); 286 requestList.add(new OCSPFetchCall(sInfo, ocspReq)); 287 } 288 } catch (IOException exc) { 289 debugLog("Exception during CertId creation: " + exc); 290 } 291 } 292 } else { 293 debugLog("Unsupported status request type: " + type); 294 } 295 296 // If we were able to create one or more Fetches, go and run all 297 // of them in separate threads. For all the threads that completed 298 // in the allotted time, put those status responses into the returned 299 // Map. 300 if (!requestList.isEmpty()) { 301 try { 302 // Set a bunch of threads to go do the fetching 303 List<Future<StatusInfo>> resultList = 304 threadMgr.invokeAll(requestList, delay, unit); 305 306 // Go through the Futures and from any non-cancelled task, 307 // get the bytes and attach them to the responseMap. 308 for (Future<StatusInfo> task : resultList) { 309 if (task.isDone()) { 310 if (!task.isCancelled()) { 311 StatusInfo info = task.get(); 312 if (info != null && info.responseData != null) { 313 responseMap.put(info.cert, 314 info.responseData.ocspBytes); 315 } else { 316 debugLog("Completed task had no response data"); 317 } 318 } else { 319 debugLog("Found cancelled task"); 320 } 321 } 322 } 323 } catch (InterruptedException | ExecutionException exc) { 324 // Not sure what else to do here 325 debugLog("Exception when getting data: " + exc); 326 } 327 } 328 329 return Collections.unmodifiableMap(responseMap); 330 } 331 332 /** 333 * Check the cache for a given {@code CertId}. 334 * 335 * @param cid the CertId of the response to look up 336 * @param ocspRequest the OCSP request structure sent by the client 337 * in the TLS status_request[_v2] hello extension. 338 * 339 * @return the {@code ResponseCacheEntry} for a specific CertId, or 340 * {@code null} if it is not found or a nonce extension has been 341 * requested by the caller. 342 */ 343 private ResponseCacheEntry getFromCache(CertId cid, 344 OCSPStatusRequest ocspRequest) { 345 // Determine if the nonce extension is present in the request. If 346 // so, then do not attempt to retrieve the response from the cache. 347 for (Extension ext : ocspRequest.getExtensions()) { 348 if (ext.getId().equals(PKIXExtensions.OCSPNonce_Id.toString())) { 349 debugLog("Nonce extension found, skipping cache check"); 350 return null; 351 } 352 } 353 354 ResponseCacheEntry respEntry = responseCache.get(cid); 355 356 // If the response entry has a nextUpdate and it has expired 357 // before the cache expiration, purge it from the cache 358 // and do not return it as a cache hit. 359 if (respEntry != null && respEntry.nextUpdate != null && 360 respEntry.nextUpdate.before(new Date())) { 361 debugLog("nextUpdate threshold exceeded, purging from cache"); 362 respEntry = null; 363 } 364 365 debugLog("Check cache for SN" + cid.getSerialNumber() + ": " + 366 (respEntry != null ? "HIT" : "MISS")); 367 return respEntry; 368 } 369 370 @Override 371 public String toString() { 372 StringBuilder sb = new StringBuilder("StatusResponseManager: "); 373 374 sb.append("Core threads: ").append(threadMgr.getCorePoolSize()); 375 sb.append(", Cache timeout: "); 376 if (cacheLifetime > 0) { 377 sb.append(cacheLifetime).append(" seconds"); 378 } else { 379 sb.append(" indefinite"); 380 } 381 382 sb.append(", Cache MaxSize: "); 383 if (cacheCapacity > 0) { 384 sb.append(cacheCapacity).append(" items"); 385 } else { 386 sb.append(" unbounded"); 387 } 388 389 sb.append(", Default URI: "); 390 if (defaultResponder != null) { 391 sb.append(defaultResponder); 392 } else { 393 sb.append("NONE"); 394 } 395 396 return sb.toString(); 397 } 398 399 /** 400 * Log messages through the SSL Debug facility. 401 * 402 * @param message the message to be displayed 403 */ 404 static void debugLog(String message) { 405 if (debug != null && Debug.isOn("respmgr")) { 406 StringBuilder sb = new StringBuilder(); 407 sb.append("[").append(Thread.currentThread().getName()); 408 sb.append("] ").append(message); 409 System.out.println(sb.toString()); 410 } 411 } 412 413 /** 414 * Inner class used to group request and response data. 415 */ 416 class StatusInfo { 417 final X509Certificate cert; 418 final CertId cid; 419 final URI responder; 420 ResponseCacheEntry responseData; 421 422 /** 423 * Create a StatusInfo object from certificate data. 424 * 425 * @param subjectCert the certificate to be checked for revocation 426 * @param issuerCert the issuer of the {@code subjectCert} 427 * 428 * @throws IOException if CertId creation from the certificates fails 429 */ 430 StatusInfo(X509Certificate subjectCert, X509Certificate issuerCert) 431 throws IOException { 432 this(subjectCert, new CertId(issuerCert, 433 new SerialNumber(subjectCert.getSerialNumber()))); 434 } 435 436 /** 437 * Create a StatusInfo object from an existing subject certificate 438 * and its corresponding CertId. 439 * 440 * @param subjectCert the certificate to be checked for revocation 441 * @param cid the CertId for {@code subjectCert} 442 */ 443 StatusInfo(X509Certificate subjectCert, CertId certId) { 444 cert = subjectCert; 445 cid = certId; 446 responder = getURI(cert); 447 responseData = null; 448 } 449 450 /** 451 * Copy constructor (used primarily for rescheduling). 452 * This will do a member-wise copy with the exception of the 453 * responseData and extensions fields, which should not persist 454 * in a rescheduled fetch. 455 * 456 * @param orig the original {@code StatusInfo} 457 */ 458 StatusInfo(StatusInfo orig) { 459 this.cert = orig.cert; 460 this.cid = orig.cid; 461 this.responder = orig.responder; 462 this.responseData = null; 463 } 464 465 /** 466 * Return a String representation of the {@code StatusInfo} 467 * 468 * @return a {@code String} representation of this object 469 */ 470 @Override 471 public String toString() { 472 StringBuilder sb = new StringBuilder("StatusInfo:"); 473 sb.append("\n\tCert: ").append(this.cert.getSubjectX500Principal()); 474 sb.append("\n\tSerial: ").append(this.cert.getSerialNumber()); 475 sb.append("\n\tResponder: ").append(this.responder); 476 sb.append("\n\tResponse data: ").append(this.responseData != null ? 477 (this.responseData.ocspBytes.length + " bytes") : "<NULL>"); 478 return sb.toString(); 479 } 480 } 481 482 /** 483 * Static nested class used as the data kept in the response cache. 484 */ 485 static class ResponseCacheEntry { 486 final OCSPResponse.ResponseStatus status; 487 final byte[] ocspBytes; 488 final Date nextUpdate; 489 final OCSPResponse.SingleResponse singleResp; 490 final ResponderId respId; 491 492 /** 493 * Create a new cache entry from the raw bytes of the response 494 * 495 * @param responseBytes the DER encoding for the OCSP response 496 * 497 * @throws IOException if an {@code OCSPResponse} cannot be created from 498 * the encoded bytes. 499 */ 500 ResponseCacheEntry(byte[] responseBytes, CertId cid) 501 throws IOException { 502 Objects.requireNonNull(responseBytes, 503 "Non-null responseBytes required"); 504 Objects.requireNonNull(cid, "Non-null Cert ID required"); 505 506 ocspBytes = responseBytes.clone(); 507 OCSPResponse oResp = new OCSPResponse(ocspBytes); 508 status = oResp.getResponseStatus(); 509 respId = oResp.getResponderId(); 510 singleResp = oResp.getSingleResponse(cid); 511 if (status == OCSPResponse.ResponseStatus.SUCCESSFUL) { 512 if (singleResp != null) { 513 // Pull out the nextUpdate field in advance because the 514 // Date is cloned. 515 nextUpdate = singleResp.getNextUpdate(); 516 } else { 517 throw new IOException("Unable to find SingleResponse for " + 518 "SN " + cid.getSerialNumber()); 519 } 520 } else { 521 nextUpdate = null; 522 } 523 } 524 } 525 526 /** 527 * Inner Callable class that does the actual work of looking up OCSP 528 * responses, first looking at the cache and doing OCSP requests if 529 * a cache miss occurs. 530 */ 531 class OCSPFetchCall implements Callable<StatusInfo> { 532 StatusInfo statInfo; 533 OCSPStatusRequest ocspRequest; 534 List<Extension> extensions; 535 List<ResponderId> responderIds; 536 537 /** 538 * A constructor that builds the OCSPFetchCall from the provided 539 * StatusInfo and information from the status_request[_v2] extension. 540 * 541 * @param info the {@code StatusInfo} containing the subject 542 * certificate, CertId, and other supplemental info. 543 * @param request the {@code OCSPStatusRequest} containing any 544 * responder IDs and extensions. 545 */ 546 public OCSPFetchCall(StatusInfo info, OCSPStatusRequest request) { 547 statInfo = Objects.requireNonNull(info, 548 "Null StatusInfo not allowed"); 549 ocspRequest = Objects.requireNonNull(request, 550 "Null OCSPStatusRequest not allowed"); 551 extensions = ocspRequest.getExtensions(); 552 responderIds = ocspRequest.getResponderIds(); 553 } 554 555 /** 556 * Get an OCSP response, either from the cache or from a responder. 557 * 558 * @return The StatusInfo object passed into the {@code OCSPFetchCall} 559 * constructor, with the {@code responseData} field filled in with the 560 * response or {@code null} if no response can be obtained. 561 */ 562 @Override 563 public StatusInfo call() { 564 debugLog("Starting fetch for SN " + statInfo.cid.getSerialNumber()); 565 try { 566 ResponseCacheEntry cacheEntry; 567 List<Extension> extsToSend; 568 569 if (statInfo.responder == null) { 570 // If we have no URI then there's nothing to do but return 571 debugLog("Null URI detected, OCSP fetch aborted."); 572 return statInfo; 573 } else { 574 debugLog("Attempting fetch from " + statInfo.responder); 575 } 576 577 // If the StatusResponseManager has been configured to not 578 // forward extensions, then set extensions to an empty list. 579 // We will forward the extensions unless one of two conditions 580 // occur: (1) The jdk.tls.stapling.ignoreExtensions property is 581 // true or (2) There is a non-empty ResponderId list. 582 // ResponderId selection is a feature that will be 583 // supported in the future. 584 extsToSend = (ignoreExtensions || !responderIds.isEmpty()) ? 585 Collections.emptyList() : extensions; 586 587 byte[] respBytes = OCSP.getOCSPBytes( 588 Collections.singletonList(statInfo.cid), 589 statInfo.responder, extsToSend); 590 591 if (respBytes != null) { 592 // Place the data into the response cache 593 cacheEntry = new ResponseCacheEntry(respBytes, 594 statInfo.cid); 595 596 // Get the response status and act on it appropriately 597 debugLog("OCSP Status: " + cacheEntry.status + 598 " (" + respBytes.length + " bytes)"); 599 if (cacheEntry.status == 600 OCSPResponse.ResponseStatus.SUCCESSFUL) { 601 // Set the response in the returned StatusInfo 602 statInfo.responseData = cacheEntry; 603 604 // Add the response to the cache (if applicable) 605 addToCache(statInfo.cid, cacheEntry); 606 } 607 } else { 608 debugLog("No data returned from OCSP Responder"); 609 } 610 } catch (IOException ioe) { 611 debugLog("Caught exception: " + ioe); 612 } 613 614 return statInfo; 615 } 616 617 /** 618 * Add a response to the cache. 619 * 620 * @param certId The {@code CertId} for the OCSP response 621 * @param entry A cache entry containing the response bytes and 622 * the {@code OCSPResponse} built from those bytes. 623 */ 624 private void addToCache(CertId certId, ResponseCacheEntry entry) { 625 // If no cache lifetime has been set on entries then 626 // don't cache this response if there is no nextUpdate field 627 if (entry.nextUpdate == null && cacheLifetime == 0) { 628 debugLog("Not caching this OCSP response"); 629 } else { 630 responseCache.put(certId, entry); 631 debugLog("Added response for SN " + certId.getSerialNumber() + 632 " to cache"); 633 } 634 } 635 636 /** 637 * Determine the delay to use when scheduling the task that will 638 * update the OCSP response. This is the shorter time between the 639 * cache lifetime and the nextUpdate. If no nextUpdate is present in 640 * the response, then only the cache lifetime is used. 641 * If cache timeouts are disabled (a zero value) and there's no 642 * nextUpdate, then the entry is not cached and no rescheduling will 643 * take place. 644 * 645 * @param nextUpdate a {@code Date} object corresponding to the 646 * next update time from a SingleResponse. 647 * 648 * @return the number of seconds of delay before the next fetch 649 * should be executed. A zero value means that the fetch 650 * should happen immediately, while a value less than zero 651 * indicates no rescheduling should be done. 652 */ 653 private long getNextTaskDelay(Date nextUpdate) { 654 long delaySec; 655 int lifetime = getCacheLifetime(); 656 657 if (nextUpdate != null) { 658 long nuDiffSec = (nextUpdate.getTime() - 659 System.currentTimeMillis()) / 1000; 660 delaySec = lifetime > 0 ? Long.min(nuDiffSec, lifetime) : 661 nuDiffSec; 662 } else { 663 delaySec = lifetime > 0 ? lifetime : -1; 664 } 665 666 return delaySec; 667 } 668 } 669 }