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