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 }