diff --git a/changelog/_14723.txt b/changelog/_14723.txt new file mode 100644 index 0000000000..3ebdf36727 --- /dev/null +++ b/changelog/_14723.txt @@ -0,0 +1,3 @@ +```release-note:bug +client/ocsp: Adds a grace period to renew the cached entry for OCSP response. +``` \ No newline at end of file diff --git a/sdk/helper/ocsp/client.go b/sdk/helper/ocsp/client.go index 71f75f168a..6b9e844be7 100644 --- a/sdk/helper/ocsp/client.go +++ b/sdk/helper/ocsp/client.go @@ -924,12 +924,28 @@ func (c *Client) extractOCSPCacheResponseValue(cacheValue *ocspCachedResponse, s sdkOcspStatus := internalStatusCodeToSDK(cacheValue.status) - return validateOCSP(conf, &ocsp.Response{ + status, err := validateOCSP(conf, &ocsp.Response{ ProducedAt: time.Unix(int64(cacheValue.producedAt), 0).UTC(), ThisUpdate: time.Unix(int64(cacheValue.thisUpdate), 0).UTC(), NextUpdate: time.Unix(int64(cacheValue.nextUpdate), 0).UTC(), Status: sdkOcspStatus, }) + if err != nil { + return nil, err + } + + // If this cached OCSP response is going to expire in the next 15 seconds treat this as a missed cache. This + // prevents an error where the OCSP response is valid now, but is not valid after OCSP responses from other certs + // has returned. OCSP timeouts are generally 5-15 seconds. + if curTime.Add(15 * time.Second).After(time.Unix(int64(cacheValue.nextUpdate), 0).UTC()) { + return &ocspStatus{ + code: ocspCacheExpired, + err: fmt.Errorf("ocsp response expires within 15 seconds. treating as expired to provide grade period. current: %v, nextUpdate time: %v", + time.Unix(int64(currentTime), 0).UTC(), time.Unix(int64(cacheValue.nextUpdate), 0).UTC()), + }, nil + } + + return status, nil } func internalStatusCodeToSDK(internalStatusCode ocspStatusCode) int { diff --git a/sdk/helper/ocsp/ocsp_test.go b/sdk/helper/ocsp/ocsp_test.go index fcd868e2d6..50a6ec8fc7 100644 --- a/sdk/helper/ocsp/ocsp_test.go +++ b/sdk/helper/ocsp/ocsp_test.go @@ -208,6 +208,13 @@ func TestUnitCheckOCSPResponseCache(t *testing.T) { if err == nil && isValidOCSPStatus(ost.code) { t.Fatalf("should have failed.") } + + // buffer-time check + c.ocspResponseCache.Add(dummyKey, &ocspCachedResponse{time: float64(currentTime), nextUpdate: float64(currentTime)}) + ost, err = c.checkOCSPResponseCache(&dummyKey, subject, issuer, conf) + if err == nil && isValidOCSPStatus(ost.code) { + t.Fatalf("should have failed.") + } } // TestUnitValidOCSPResponse validates various combinations of acceptable OCSP responses