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 }