| 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
package sun.security.ssl;  | 
 | 
 | 
 | 
import java.io.IOException;  | 
 | 
import java.net.URI;  | 
 | 
import java.net.URISyntaxException;  | 
 | 
import java.security.AccessController;  | 
 | 
import java.security.cert.Extension;  | 
 | 
import java.security.cert.X509Certificate;  | 
 | 
import java.util.ArrayList;  | 
 | 
import java.util.Collections;  | 
 | 
import java.util.Date;  | 
 | 
import java.util.HashMap;  | 
 | 
import java.util.List;  | 
 | 
import java.util.Map;  | 
 | 
import java.util.Objects;  | 
 | 
import java.util.concurrent.Callable;  | 
 | 
import java.util.concurrent.ExecutionException;  | 
 | 
import java.util.concurrent.Executors;  | 
 | 
import java.util.concurrent.Future;  | 
 | 
import java.util.concurrent.ScheduledThreadPoolExecutor;  | 
 | 
import java.util.concurrent.ThreadFactory;  | 
 | 
import java.util.concurrent.ThreadPoolExecutor;  | 
 | 
import java.util.concurrent.TimeUnit;  | 
 | 
import sun.security.action.GetBooleanAction;  | 
 | 
import sun.security.action.GetIntegerAction;  | 
 | 
import sun.security.action.GetPropertyAction;  | 
 | 
import sun.security.provider.certpath.CertId;  | 
 | 
import sun.security.provider.certpath.OCSP;  | 
 | 
import sun.security.provider.certpath.OCSPResponse;  | 
 | 
import sun.security.provider.certpath.ResponderId;  | 
 | 
import sun.security.util.Cache;  | 
 | 
import sun.security.x509.PKIXExtensions;  | 
 | 
import sun.security.x509.SerialNumber;  | 
 | 
import sun.security.ssl.X509Authentication.X509Possession;  | 
 | 
import static sun.security.ssl.CertStatusExtension.*;  | 
 | 
 | 
 | 
final class StatusResponseManager { | 
 | 
    private static final int DEFAULT_CORE_THREADS = 8;  | 
 | 
    private static final int DEFAULT_CACHE_SIZE = 256;  | 
 | 
    private static final int DEFAULT_CACHE_LIFETIME = 3600;       | 
 | 
 | 
 | 
    private final ScheduledThreadPoolExecutor threadMgr;  | 
 | 
    private final Cache<CertId, ResponseCacheEntry> responseCache;  | 
 | 
    private final URI defaultResponder;  | 
 | 
    private final boolean respOverride;  | 
 | 
    private final int cacheCapacity;  | 
 | 
    private final int cacheLifetime;  | 
 | 
    private final boolean ignoreExtensions;  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    StatusResponseManager() { | 
 | 
        int cap = AccessController.doPrivileged(  | 
 | 
                new GetIntegerAction("jdk.tls.stapling.cacheSize", | 
 | 
                    DEFAULT_CACHE_SIZE));  | 
 | 
        cacheCapacity = cap > 0 ? cap : 0;  | 
 | 
 | 
 | 
        int life = AccessController.doPrivileged(  | 
 | 
                new GetIntegerAction("jdk.tls.stapling.cacheLifetime", | 
 | 
                    DEFAULT_CACHE_LIFETIME));  | 
 | 
        cacheLifetime = life > 0 ? life : 0;  | 
 | 
 | 
 | 
        String uriStr = GetPropertyAction  | 
 | 
                .privilegedGetProperty("jdk.tls.stapling.responderURI"); | 
 | 
        URI tmpURI;  | 
 | 
        try { | 
 | 
            tmpURI = ((uriStr != null && !uriStr.isEmpty()) ?  | 
 | 
                    new URI(uriStr) : null);  | 
 | 
        } catch (URISyntaxException urise) { | 
 | 
            tmpURI = null;  | 
 | 
        }  | 
 | 
        defaultResponder = tmpURI;  | 
 | 
 | 
 | 
        respOverride = AccessController.doPrivileged(  | 
 | 
                new GetBooleanAction("jdk.tls.stapling.responderOverride")); | 
 | 
        ignoreExtensions = AccessController.doPrivileged(  | 
 | 
                new GetBooleanAction("jdk.tls.stapling.ignoreExtensions")); | 
 | 
 | 
 | 
        threadMgr = new ScheduledThreadPoolExecutor(DEFAULT_CORE_THREADS,  | 
 | 
                new ThreadFactory() { | 
 | 
            @Override  | 
 | 
            public Thread newThread(Runnable r) { | 
 | 
                Thread t = Executors.defaultThreadFactory().newThread(r);  | 
 | 
                t.setDaemon(true);  | 
 | 
                return t;  | 
 | 
            }  | 
 | 
        }, new ThreadPoolExecutor.DiscardPolicy());  | 
 | 
        threadMgr.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);  | 
 | 
        threadMgr.setContinueExistingPeriodicTasksAfterShutdownPolicy(  | 
 | 
                false);  | 
 | 
        threadMgr.setKeepAliveTime(5000, TimeUnit.MILLISECONDS);  | 
 | 
        threadMgr.allowCoreThreadTimeOut(true);  | 
 | 
        responseCache = Cache.newSoftMemoryCache(  | 
 | 
                cacheCapacity, cacheLifetime);  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    int getCacheLifetime() { | 
 | 
        return cacheLifetime;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    int getCacheCapacity() { | 
 | 
        return cacheCapacity;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    URI getDefaultResponder() { | 
 | 
        return defaultResponder;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    boolean getURIOverride() { | 
 | 
        return respOverride;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    boolean getIgnoreExtensions() { | 
 | 
        return ignoreExtensions;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    void clear() { | 
 | 
        if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
            SSLLogger.fine("Clearing response cache"); | 
 | 
        }  | 
 | 
        responseCache.clear();  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    int size() { | 
 | 
        return responseCache.size();  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    URI getURI(X509Certificate cert) { | 
 | 
        Objects.requireNonNull(cert);  | 
 | 
 | 
 | 
        if (cert.getExtensionValue(  | 
 | 
                PKIXExtensions.OCSPNoCheck_Id.toString()) != null) { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                SSLLogger.fine(  | 
 | 
                    "OCSP NoCheck extension found.  OCSP will be skipped");  | 
 | 
            }  | 
 | 
            return null;  | 
 | 
        } else if (defaultResponder != null && respOverride) { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
              SSLLogger.fine(  | 
 | 
                    "Responder override: URI is " + defaultResponder);  | 
 | 
            }  | 
 | 
            return defaultResponder;  | 
 | 
        } else { | 
 | 
            URI certURI = OCSP.getResponderURI(cert);  | 
 | 
            return (certURI != null ? certURI : defaultResponder);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    void shutdown() { | 
 | 
        if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
            SSLLogger.fine("Shutting down " + threadMgr.getActiveCount() + | 
 | 
                    " active threads");  | 
 | 
        }  | 
 | 
        threadMgr.shutdown();  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    Map<X509Certificate, byte[]> get(CertStatusRequestType type,  | 
 | 
            CertStatusRequest request, X509Certificate[] chain, long delay,  | 
 | 
            TimeUnit unit) { | 
 | 
        Map<X509Certificate, byte[]> responseMap = new HashMap<>();  | 
 | 
        List<OCSPFetchCall> requestList = new ArrayList<>();  | 
 | 
 | 
 | 
        if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
            SSLLogger.fine(  | 
 | 
                "Beginning check: Type = " + type + ", Chain length = " +  | 
 | 
                chain.length);  | 
 | 
        }  | 
 | 
 | 
 | 
        // It is assumed that the caller has ordered the certs in the chain  | 
 | 
        // in the proper order (each certificate is issued by the next entry  | 
 | 
          | 
 | 
        if (chain.length < 2) { | 
 | 
            return Collections.emptyMap();  | 
 | 
        }  | 
 | 
 | 
 | 
        if (type == CertStatusRequestType.OCSP) { | 
 | 
            try { | 
 | 
                  | 
 | 
                OCSPStatusRequest ocspReq = (OCSPStatusRequest)request;  | 
 | 
                CertId cid = new CertId(chain[1],  | 
 | 
                        new SerialNumber(chain[0].getSerialNumber()));  | 
 | 
                ResponseCacheEntry cacheEntry = getFromCache(cid, ocspReq);  | 
 | 
                if (cacheEntry != null) { | 
 | 
                    responseMap.put(chain[0], cacheEntry.ocspBytes);  | 
 | 
                } else { | 
 | 
                    StatusInfo sInfo = new StatusInfo(chain[0], cid);  | 
 | 
                    requestList.add(new OCSPFetchCall(sInfo, ocspReq));  | 
 | 
                }  | 
 | 
            } catch (IOException exc) { | 
 | 
                if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                    SSLLogger.fine(  | 
 | 
                        "Exception during CertId creation: ", exc);  | 
 | 
                }  | 
 | 
            }  | 
 | 
        } else if (type == CertStatusRequestType.OCSP_MULTI) { | 
 | 
            // For type OCSP_MULTI, we check every cert in the chain that  | 
 | 
            // has a direct issuer at the next index.  We won't have an  | 
 | 
            // issuer certificate for the last certificate in the chain  | 
 | 
              | 
 | 
            OCSPStatusRequest ocspReq = (OCSPStatusRequest)request;  | 
 | 
            int ctr;  | 
 | 
            for (ctr = 0; ctr < chain.length - 1; ctr++) { | 
 | 
                try { | 
 | 
                    // The cert at "ctr" is the subject cert, "ctr + 1"  | 
 | 
                      | 
 | 
                    CertId cid = new CertId(chain[ctr + 1],  | 
 | 
                        new SerialNumber(chain[ctr].getSerialNumber()));  | 
 | 
                    ResponseCacheEntry cacheEntry =  | 
 | 
                        getFromCache(cid, ocspReq);  | 
 | 
                    if (cacheEntry != null) { | 
 | 
                        responseMap.put(chain[ctr], cacheEntry.ocspBytes);  | 
 | 
                    } else { | 
 | 
                        StatusInfo sInfo = new StatusInfo(chain[ctr], cid);  | 
 | 
                        requestList.add(new OCSPFetchCall(sInfo, ocspReq));  | 
 | 
                    }  | 
 | 
                } catch (IOException exc) { | 
 | 
                    if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                        SSLLogger.fine(  | 
 | 
                            "Exception during CertId creation: ", exc);  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }  | 
 | 
        } else { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                SSLLogger.fine("Unsupported status request type: " + type); | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        // If we were able to create one or more Fetches, go and run all  | 
 | 
        // of them in separate threads.  For all the threads that completed  | 
 | 
        // in the allotted time, put those status responses into the  | 
 | 
          | 
 | 
        if (!requestList.isEmpty()) { | 
 | 
            try { | 
 | 
                  | 
 | 
                List<Future<StatusInfo>> resultList =  | 
 | 
                        threadMgr.invokeAll(requestList, delay, unit);  | 
 | 
 | 
 | 
                // Go through the Futures and from any non-cancelled task,  | 
 | 
                  | 
 | 
                for (Future<StatusInfo> task : resultList) { | 
 | 
                    if (!task.isDone()) { | 
 | 
                        continue;  | 
 | 
                    }  | 
 | 
 | 
 | 
                    if (!task.isCancelled()) { | 
 | 
                        StatusInfo info = task.get();  | 
 | 
                        if (info != null && info.responseData != null) { | 
 | 
                            responseMap.put(info.cert,  | 
 | 
                                    info.responseData.ocspBytes);  | 
 | 
                        } else if (SSLLogger.isOn &&  | 
 | 
                                SSLLogger.isOn("respmgr")) { | 
 | 
                            SSLLogger.fine(  | 
 | 
                                "Completed task had no response data");  | 
 | 
                        }  | 
 | 
                    } else { | 
 | 
                        if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                            SSLLogger.fine("Found cancelled task"); | 
 | 
                        }  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            } catch (InterruptedException | ExecutionException exc) { | 
 | 
                  | 
 | 
                if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                    SSLLogger.fine("Exception when getting data: ", exc); | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        return Collections.unmodifiableMap(responseMap);  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private ResponseCacheEntry getFromCache(CertId cid,  | 
 | 
            OCSPStatusRequest ocspRequest) { | 
 | 
        // Determine if the nonce extension is present in the request.  If  | 
 | 
          | 
 | 
        for (Extension ext : ocspRequest.extensions) { | 
 | 
            if (ext.getId().equals(  | 
 | 
                    PKIXExtensions.OCSPNonce_Id.toString())) { | 
 | 
                if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                    SSLLogger.fine(  | 
 | 
                            "Nonce extension found, skipping cache check");  | 
 | 
                }  | 
 | 
                return null;  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        ResponseCacheEntry respEntry = responseCache.get(cid);  | 
 | 
 | 
 | 
        // If the response entry has a nextUpdate and it has expired  | 
 | 
        // before the cache expiration, purge it from the cache  | 
 | 
          | 
 | 
        if (respEntry != null && respEntry.nextUpdate != null &&  | 
 | 
                respEntry.nextUpdate.before(new Date())) { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                SSLLogger.fine(  | 
 | 
                    "nextUpdate threshold exceeded, purging from cache");  | 
 | 
            }  | 
 | 
            respEntry = null;  | 
 | 
        }  | 
 | 
 | 
 | 
        if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
            SSLLogger.fine(  | 
 | 
                    "Check cache for SN" + cid.getSerialNumber() + ": " +  | 
 | 
                    (respEntry != null ? "HIT" : "MISS"));  | 
 | 
        }  | 
 | 
        return respEntry;  | 
 | 
    }  | 
 | 
 | 
 | 
    @Override  | 
 | 
    public String toString() { | 
 | 
        StringBuilder sb = new StringBuilder("StatusResponseManager: "); | 
 | 
 | 
 | 
        sb.append("Core threads: ").append(threadMgr.getCorePoolSize()); | 
 | 
        sb.append(", Cache timeout: "); | 
 | 
        if (cacheLifetime > 0) { | 
 | 
            sb.append(cacheLifetime).append(" seconds"); | 
 | 
        } else { | 
 | 
            sb.append(" indefinite"); | 
 | 
        }  | 
 | 
 | 
 | 
        sb.append(", Cache MaxSize: "); | 
 | 
        if (cacheCapacity > 0) { | 
 | 
            sb.append(cacheCapacity).append(" items"); | 
 | 
        } else { | 
 | 
            sb.append(" unbounded"); | 
 | 
        }  | 
 | 
 | 
 | 
        sb.append(", Default URI: "); | 
 | 
        if (defaultResponder != null) { | 
 | 
            sb.append(defaultResponder);  | 
 | 
        } else { | 
 | 
            sb.append("NONE"); | 
 | 
        }  | 
 | 
 | 
 | 
        return sb.toString();  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    class StatusInfo { | 
 | 
        final X509Certificate cert;  | 
 | 
        final CertId cid;  | 
 | 
        final URI responder;  | 
 | 
        ResponseCacheEntry responseData;  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        StatusInfo(X509Certificate subjectCert, X509Certificate issuerCert)  | 
 | 
                throws IOException { | 
 | 
            this(subjectCert, new CertId(issuerCert,  | 
 | 
                    new SerialNumber(subjectCert.getSerialNumber())));  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        StatusInfo(X509Certificate subjectCert, CertId certId) { | 
 | 
            cert = subjectCert;  | 
 | 
            cid = certId;  | 
 | 
            responder = getURI(cert);  | 
 | 
            responseData = null;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        StatusInfo(StatusInfo orig) { | 
 | 
            this.cert = orig.cert;  | 
 | 
            this.cid = orig.cid;  | 
 | 
            this.responder = orig.responder;  | 
 | 
            this.responseData = null;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        @Override  | 
 | 
        public String toString() { | 
 | 
            StringBuilder sb = new StringBuilder("StatusInfo:"); | 
 | 
            sb.append("\n\tCert: ").append( | 
 | 
                    this.cert.getSubjectX500Principal());  | 
 | 
            sb.append("\n\tSerial: ").append(this.cert.getSerialNumber()); | 
 | 
            sb.append("\n\tResponder: ").append(this.responder); | 
 | 
            sb.append("\n\tResponse data: ").append( | 
 | 
                    this.responseData != null ?  | 
 | 
                        (this.responseData.ocspBytes.length + " bytes") :  | 
 | 
                        "<NULL>");  | 
 | 
            return sb.toString();  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    class ResponseCacheEntry { | 
 | 
        final OCSPResponse.ResponseStatus status;  | 
 | 
        final byte[] ocspBytes;  | 
 | 
        final Date nextUpdate;  | 
 | 
        final OCSPResponse.SingleResponse singleResp;  | 
 | 
        final ResponderId respId;  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        ResponseCacheEntry(byte[] responseBytes, CertId cid)  | 
 | 
                throws IOException { | 
 | 
            Objects.requireNonNull(responseBytes,  | 
 | 
                    "Non-null responseBytes required");  | 
 | 
            Objects.requireNonNull(cid, "Non-null Cert ID required");  | 
 | 
 | 
 | 
            ocspBytes = responseBytes.clone();  | 
 | 
            OCSPResponse oResp = new OCSPResponse(ocspBytes);  | 
 | 
            status = oResp.getResponseStatus();  | 
 | 
            respId = oResp.getResponderId();  | 
 | 
            singleResp = oResp.getSingleResponse(cid);  | 
 | 
            if (status == OCSPResponse.ResponseStatus.SUCCESSFUL) { | 
 | 
                if (singleResp != null) { | 
 | 
                    // Pull out the nextUpdate field in advance because the  | 
 | 
                      | 
 | 
                    nextUpdate = singleResp.getNextUpdate();  | 
 | 
                } else { | 
 | 
                    throw new IOException(  | 
 | 
                            "Unable to find SingleResponse for SN " +  | 
 | 
                            cid.getSerialNumber());  | 
 | 
                }  | 
 | 
            } else { | 
 | 
                nextUpdate = null;  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    class OCSPFetchCall implements Callable<StatusInfo> { | 
 | 
        StatusInfo statInfo;  | 
 | 
        OCSPStatusRequest ocspRequest;  | 
 | 
        List<Extension> extensions;  | 
 | 
        List<ResponderId> responderIds;  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        public OCSPFetchCall(StatusInfo info, OCSPStatusRequest request) { | 
 | 
            statInfo = Objects.requireNonNull(info,  | 
 | 
                    "Null StatusInfo not allowed");  | 
 | 
            ocspRequest = Objects.requireNonNull(request,  | 
 | 
                    "Null OCSPStatusRequest not allowed");  | 
 | 
            extensions = ocspRequest.extensions;  | 
 | 
            responderIds = ocspRequest.responderIds;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        @Override  | 
 | 
        public StatusInfo call() { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                SSLLogger.fine(  | 
 | 
                    "Starting fetch for SN " +  | 
 | 
                    statInfo.cid.getSerialNumber());  | 
 | 
            }  | 
 | 
            try { | 
 | 
                ResponseCacheEntry cacheEntry;  | 
 | 
                List<Extension> extsToSend;  | 
 | 
 | 
 | 
                if (statInfo.responder == null) { | 
 | 
                    // If we have no URI then there's nothing to do  | 
 | 
                      | 
 | 
                    if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                        SSLLogger.fine(  | 
 | 
                            "Null URI detected, OCSP fetch aborted");  | 
 | 
                    }  | 
 | 
                    return statInfo;  | 
 | 
                } else { | 
 | 
                    if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                        SSLLogger.fine(  | 
 | 
                            "Attempting fetch from " + statInfo.responder);  | 
 | 
                    }  | 
 | 
                }  | 
 | 
 | 
 | 
                // If the StatusResponseManager has been configured to not  | 
 | 
                // forward extensions, then set extensions to an empty  | 
 | 
                // list.  | 
 | 
                //  | 
 | 
                // We will forward the extensions unless one of two  | 
 | 
                // conditions occur:  | 
 | 
                // (1) The jdk.tls.stapling.ignoreExtensions property is  | 
 | 
                //     true, or  | 
 | 
                // (2) There is a non-empty ResponderId list.  | 
 | 
                //  | 
 | 
                // ResponderId selection is a feature that will be  | 
 | 
                  | 
 | 
                extsToSend = (ignoreExtensions || !responderIds.isEmpty()) ?  | 
 | 
                        Collections.emptyList() : extensions;  | 
 | 
 | 
 | 
                byte[] respBytes = OCSP.getOCSPBytes(  | 
 | 
                        Collections.singletonList(statInfo.cid),  | 
 | 
                        statInfo.responder, extsToSend);  | 
 | 
 | 
 | 
                if (respBytes != null) { | 
 | 
                      | 
 | 
                    cacheEntry = new ResponseCacheEntry(respBytes,  | 
 | 
                            statInfo.cid);  | 
 | 
 | 
 | 
                      | 
 | 
                    if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                        SSLLogger.fine("OCSP Status: " + cacheEntry.status + | 
 | 
                            " (" + respBytes.length + " bytes)"); | 
 | 
                    }  | 
 | 
                    if (cacheEntry.status ==  | 
 | 
                            OCSPResponse.ResponseStatus.SUCCESSFUL) { | 
 | 
                          | 
 | 
                        statInfo.responseData = cacheEntry;  | 
 | 
 | 
 | 
                          | 
 | 
                        addToCache(statInfo.cid, cacheEntry);  | 
 | 
                    }  | 
 | 
                } else { | 
 | 
                    if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                        SSLLogger.fine(  | 
 | 
                            "No data returned from OCSP Responder");  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            } catch (IOException ioe) { | 
 | 
                if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                    SSLLogger.fine("Caught exception: ", ioe); | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            return statInfo;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        private void addToCache(CertId certId, ResponseCacheEntry entry) { | 
 | 
            // If no cache lifetime has been set on entries then  | 
 | 
              | 
 | 
            if (entry.nextUpdate == null && cacheLifetime == 0) { | 
 | 
                if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                    SSLLogger.fine("Not caching this OCSP response"); | 
 | 
                }  | 
 | 
            } else { | 
 | 
                responseCache.put(certId, entry);  | 
 | 
                if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { | 
 | 
                    SSLLogger.fine(  | 
 | 
                        "Added response for SN " +  | 
 | 
                        certId.getSerialNumber() +  | 
 | 
                        " to cache");  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
         */  | 
 | 
        private long getNextTaskDelay(Date nextUpdate) { | 
 | 
            long delaySec;  | 
 | 
            int lifetime = getCacheLifetime();  | 
 | 
 | 
 | 
            if (nextUpdate != null) { | 
 | 
                long nuDiffSec = (nextUpdate.getTime() -  | 
 | 
                        System.currentTimeMillis()) / 1000;  | 
 | 
                delaySec = lifetime > 0 ? Long.min(nuDiffSec, lifetime) :  | 
 | 
                        nuDiffSec;  | 
 | 
            } else { | 
 | 
                delaySec = lifetime > 0 ? lifetime : -1;  | 
 | 
            }  | 
 | 
 | 
 | 
            return delaySec;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    static final StaplingParameters processStapling(  | 
 | 
            ServerHandshakeContext shc) { | 
 | 
        StaplingParameters params = null;  | 
 | 
        SSLExtension ext = null;  | 
 | 
        CertStatusRequestType type = null;  | 
 | 
        CertStatusRequest req = null;  | 
 | 
        Map<X509Certificate, byte[]> responses;  | 
 | 
 | 
 | 
        // If this feature has not been enabled, then no more processing  | 
 | 
        // is necessary.  Also we will only staple if we're doing a full  | 
 | 
          | 
 | 
        if (!shc.sslContext.isStaplingEnabled(false) || shc.isResumption) { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | 
 | 
                SSLLogger.fine("Staping disabled or is a resumed session"); | 
 | 
            }  | 
 | 
            return null;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        Map<SSLExtension, SSLExtension.SSLExtensionSpec> exts =  | 
 | 
                shc.handshakeExtensions;  | 
 | 
        CertStatusRequestSpec statReq = (CertStatusRequestSpec)exts.get(  | 
 | 
                SSLExtension.CH_STATUS_REQUEST);  | 
 | 
        CertStatusRequestV2Spec statReqV2 = (CertStatusRequestV2Spec)  | 
 | 
                exts.get(SSLExtension.CH_STATUS_REQUEST_V2);  | 
 | 
 | 
 | 
        // Determine which type of stapling we are doing and assert the  | 
 | 
        // proper extension in the server hello.  | 
 | 
        // Favor status_request_v2 over status_request and ocsp_multi  | 
 | 
        // over ocsp.  | 
 | 
        // If multiple ocsp or ocsp_multi types exist, select the first  | 
 | 
        // instance of a given type.  Also since we don't support ResponderId  | 
 | 
        // selection yet, only accept a request if the ResponderId field  | 
 | 
          | 
 | 
        if (statReqV2 != null && !shc.negotiatedProtocol.useTLS13PlusSpec()) { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { | 
 | 
                SSLLogger.fine("SH Processing status_request_v2 extension"); | 
 | 
            }  | 
 | 
              | 
 | 
            ext = SSLExtension.CH_STATUS_REQUEST_V2;  | 
 | 
            int ocspIdx = -1;  | 
 | 
            int ocspMultiIdx = -1;  | 
 | 
            CertStatusRequest[] reqItems = statReqV2.certStatusRequests;  | 
 | 
            for (int pos = 0; (pos < reqItems.length &&  | 
 | 
                    (ocspIdx == -1 || ocspMultiIdx == -1)); pos++) { | 
 | 
                CertStatusRequest item = reqItems[pos];  | 
 | 
                CertStatusRequestType curType =  | 
 | 
                        CertStatusRequestType.valueOf(item.statusType);  | 
 | 
                if (ocspIdx < 0 && curType == CertStatusRequestType.OCSP) { | 
 | 
                    OCSPStatusRequest ocspReq = (OCSPStatusRequest)item;  | 
 | 
                    // We currently only accept empty responder ID lists  | 
 | 
                      | 
 | 
                    if (ocspReq.responderIds.isEmpty()) { | 
 | 
                        ocspIdx = pos;  | 
 | 
                    }  | 
 | 
                } else if (ocspMultiIdx < 0 &&  | 
 | 
                        curType == CertStatusRequestType.OCSP_MULTI) { | 
 | 
                    OCSPStatusRequest ocspReq = (OCSPStatusRequest)item;  | 
 | 
                    // We currently only accept empty responder ID lists  | 
 | 
                      | 
 | 
                    if (ocspReq.responderIds.isEmpty()) { | 
 | 
                        ocspMultiIdx = pos;  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }  | 
 | 
            if (ocspMultiIdx >= 0) { | 
 | 
                req = reqItems[ocspMultiIdx];  | 
 | 
                type = CertStatusRequestType.valueOf(req.statusType);  | 
 | 
            } else if (ocspIdx >= 0) { | 
 | 
                req = reqItems[ocspIdx];  | 
 | 
                type = CertStatusRequestType.valueOf(req.statusType);  | 
 | 
            } else { | 
 | 
                if (SSLLogger.isOn &&  | 
 | 
                        SSLLogger.isOn("ssl,handshake")) { | 
 | 
                    SSLLogger.finest("Warning: No suitable request " + | 
 | 
                            "found in the status_request_v2 extension.");  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        // Only attempt to process a status_request extension if:  | 
 | 
        // * The status_request extension is set AND  | 
 | 
        // * either the status_request_v2 extension is not present OR  | 
 | 
        // * none of the underlying OCSPStatusRequest structures is  | 
 | 
        // suitable for stapling.  | 
 | 
        // If either of the latter two bullet items is true the ext,  | 
 | 
        // type and req variables should all be null.  If any are null  | 
 | 
          | 
 | 
        if ((statReq != null) &&  | 
 | 
                (ext == null || type == null || req == null)) { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { | 
 | 
                SSLLogger.fine("SH Processing status_request extension"); | 
 | 
            }  | 
 | 
            ext = SSLExtension.CH_STATUS_REQUEST;  | 
 | 
            type = CertStatusRequestType.valueOf(  | 
 | 
                    statReq.statusRequest.statusType);  | 
 | 
            if (type == CertStatusRequestType.OCSP) { | 
 | 
                // If the type is OCSP, then the request is guaranteed  | 
 | 
                  | 
 | 
                OCSPStatusRequest ocspReq =  | 
 | 
                        (OCSPStatusRequest)statReq.statusRequest;  | 
 | 
                if (ocspReq.responderIds.isEmpty()) { | 
 | 
                    req = ocspReq;  | 
 | 
                } else { | 
 | 
                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | 
 | 
                        SSLLogger.finest("Warning: No suitable request " + | 
 | 
                            "found in the status_request extension.");  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        // If, after walking through the extensions we were unable to  | 
 | 
        // find a suitable StatusRequest, then stapling is disabled.  | 
 | 
          | 
 | 
        if (type == null || req == null || ext == null) { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | 
 | 
                SSLLogger.fine("No suitable status_request or " + | 
 | 
                        "status_request_v2, stapling is disabled");  | 
 | 
            }  | 
 | 
            return null;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        X509Possession x509Possession = null;  | 
 | 
        for (SSLPossession possession : shc.handshakePossessions) { | 
 | 
            if (possession instanceof X509Possession) { | 
 | 
                x509Possession = (X509Possession)possession;  | 
 | 
                break;  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        if (x509Possession == null) {        | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | 
 | 
                SSLLogger.finest("Warning: no X.509 certificates found.  " + | 
 | 
                        "Stapling is disabled.");  | 
 | 
            }  | 
 | 
            return null;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        X509Certificate[] certs = x509Possession.popCerts;  | 
 | 
        StatusResponseManager statRespMgr =  | 
 | 
                shc.sslContext.getStatusResponseManager();  | 
 | 
        if (statRespMgr != null) { | 
 | 
            // For the purposes of the fetch from the SRM, override the  | 
 | 
            // type when it is TLS 1.3 so it always gets responses for  | 
 | 
            // all certs it can.  This should not change the type field  | 
 | 
              | 
 | 
            CertStatusRequestType fetchType =  | 
 | 
                    shc.negotiatedProtocol.useTLS13PlusSpec() ?  | 
 | 
                    CertStatusRequestType.OCSP_MULTI : type;  | 
 | 
            responses = statRespMgr.get(fetchType, req, certs,  | 
 | 
                    shc.statusRespTimeout, TimeUnit.MILLISECONDS);  | 
 | 
            if (!responses.isEmpty()) { | 
 | 
                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | 
 | 
                    SSLLogger.finest("Response manager returned " + | 
 | 
                            responses.size() + " entries.");  | 
 | 
                }  | 
 | 
                // If this RFC 6066-style stapling (SSL cert only) then the  | 
 | 
                  | 
 | 
                if (type == CertStatusRequestType.OCSP) { | 
 | 
                    byte[] respDER = responses.get(certs[0]);  | 
 | 
                    if (respDER == null || respDER.length <= 0) { | 
 | 
                        if (SSLLogger.isOn &&  | 
 | 
                                SSLLogger.isOn("ssl,handshake")) { | 
 | 
                            SSLLogger.finest("Warning: Null or zero-length " + | 
 | 
                                    "response found for leaf certificate. " +  | 
 | 
                                    "Stapling is disabled.");  | 
 | 
                        }  | 
 | 
                        return null;  | 
 | 
                    }  | 
 | 
                }  | 
 | 
                params = new StaplingParameters(ext, type, req, responses);  | 
 | 
            } else { | 
 | 
                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | 
 | 
                    SSLLogger.finest("Warning: no OCSP responses obtained.  " + | 
 | 
                            "Stapling is disabled.");  | 
 | 
                }  | 
 | 
            }  | 
 | 
        } else { | 
 | 
            // This should not happen, but if lazy initialization of the  | 
 | 
              | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | 
 | 
                SSLLogger.finest("Warning: lazy initialization " + | 
 | 
                        "of the StatusResponseManager failed.  " +  | 
 | 
                        "Stapling is disabled.");  | 
 | 
            }  | 
 | 
            params = null;  | 
 | 
        }  | 
 | 
 | 
 | 
        return params;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    static final class StaplingParameters { | 
 | 
        final SSLExtension statusRespExt;  | 
 | 
        final CertStatusRequestType statReqType;  | 
 | 
        final CertStatusRequest statReqData;  | 
 | 
        final Map<X509Certificate, byte[]> responseMap;  | 
 | 
 | 
 | 
        StaplingParameters(SSLExtension ext, CertStatusRequestType type,  | 
 | 
                CertStatusRequest req, Map<X509Certificate, byte[]> responses) { | 
 | 
            statusRespExt = ext;  | 
 | 
            statReqType = type;  | 
 | 
            statReqData = req;  | 
 | 
            responseMap = responses;  | 
 | 
        }  | 
 | 
    }  | 
 | 
}  | 
 | 
 |