From 6347d0a2e5e2a3d417ee1369618ec72b127ecfba Mon Sep 17 00:00:00 2001 From: Steve Clark Date: Fri, 22 Mar 2024 09:00:26 -0400 Subject: [PATCH 1/2] Validate OCSP response is signed by expected issuer and serial number matches request - There was a bug in the OCSP response signature logic, it properly verified but kept around the ocspRes object around so we ignored the errors found and passed the response object back up the stack. - Now extract the verification logic into a dedicated function, if it returns an error, blank the ocspRes response as we can't trust it. - Address an issue that the OCSP requests from multiple servers were clobbering each others responses as the index loop variable was not properly captured. - Add a missing validation that the response was for the serial number we requested --- builtin/credential/cert/backend_test.go | 187 ++++++++++++++++++++++++ builtin/credential/cert/path_login.go | 4 + changelog/change-me.txt | 3 + sdk/helper/ocsp/client.go | 164 +++++++++++++-------- sdk/helper/ocsp/ocsp_test.go | 86 ++++++++++- 5 files changed, 379 insertions(+), 65 deletions(-) create mode 100644 changelog/change-me.txt diff --git a/builtin/credential/cert/backend_test.go b/builtin/credential/cert/backend_test.go index d56bd25a4593..6260c368e8c0 100644 --- a/builtin/credential/cert/backend_test.go +++ b/builtin/credential/cert/backend_test.go @@ -2346,6 +2346,98 @@ func TestBackend_CertUpgrade(t *testing.T) { } } +// TestOCSPFailOpenWithBadIssuer validates we fail all different types of cert auth +// login scenarios if we encounter an OCSP verification error +func TestOCSPFailOpenWithBadIssuer(t *testing.T) { + caFile := "test-fixtures/root/rootcacert.pem" + pemCa, err := os.ReadFile(caFile) + require.NoError(t, err, "failed reading in file %s", caFile) + caTLS := loadCerts(t, caFile, "test-fixtures/root/rootcakey.pem") + leafTLS := loadCerts(t, "test-fixtures/keys/cert.pem", "test-fixtures/keys/key.pem") + + rootConfig := &rootcerts.Config{ + CAFile: caFile, + } + rootCAs, err := rootcerts.LoadCACerts(rootConfig) + connState, err := testConnStateWithCert(leafTLS, rootCAs) + require.NoError(t, err, "error testing connection state: %v", err) + + badCa, badCaKey := createCa(t) + + // Setup an OCSP handler + ocspHandler := func(ca *x509.Certificate, caKey crypto.Signer) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + now := time.Now() + ocspRes := ocsp.Response{ + SerialNumber: leafTLS.Leaf.SerialNumber, + ThisUpdate: now.Add(-1 * time.Hour), + NextUpdate: now.Add(30 * time.Minute), + Status: ocsp.Good, + } + response, err := ocsp.CreateResponse(ca, ca, ocspRes, caKey) + if err != nil { + t.Fatalf("failed generating OCSP response: %v", err) + } + _, _ = w.Write(response) + }) + } + goodTs := httptest.NewServer(ocspHandler(caTLS.Leaf, caTLS.PrivateKey.(crypto.Signer))) + badTs := httptest.NewServer(ocspHandler(badCa, badCaKey)) + defer goodTs.Close() + defer badTs.Close() + + steps := []logicaltest.TestStep{ + // step 1/2: This should fail as we get a response from a bad root, even with ocsp_fail_open is set to true + testAccStepCertWithExtraParams(t, "web", pemCa, "foo", allowed{names: "cert.example.com"}, false, + map[string]interface{}{ + "ocsp_enabled": true, + "ocsp_servers_override": []string{badTs.URL}, + "ocsp_query_all_servers": false, + "ocsp_fail_open": true, + }), + testAccStepLoginInvalid(t, connState), + // step 3/4: This should fail as we query all the servers which will get a response with an invalid signature + testAccStepCertWithExtraParams(t, "web", pemCa, "foo", allowed{names: "cert.example.com"}, false, + map[string]interface{}{ + "ocsp_enabled": true, + "ocsp_servers_override": []string{goodTs.URL, badTs.URL}, + "ocsp_query_all_servers": true, + "ocsp_fail_open": true, + }), + testAccStepLoginInvalid(t, connState), + // step 5/6: This should fail as we will query the OCSP server with the bad root key first. + testAccStepCertWithExtraParams(t, "web", pemCa, "foo", allowed{names: "cert.example.com"}, false, + map[string]interface{}{ + "ocsp_enabled": true, + "ocsp_servers_override": []string{badTs.URL, goodTs.URL}, + "ocsp_query_all_servers": false, + "ocsp_fail_open": true, + }), + testAccStepLoginInvalid(t, connState), + // step 7/8: This should pass as we will only query the first server with the valid root signature + testAccStepCertWithExtraParams(t, "web", pemCa, "foo", allowed{names: "cert.example.com"}, false, + map[string]interface{}{ + "ocsp_enabled": true, + "ocsp_servers_override": []string{goodTs.URL, badTs.URL}, + "ocsp_query_all_servers": false, + "ocsp_fail_open": true, + }), + testAccStepLogin(t, connState), + } + + // Setup a new factory everytime to avoid OCSP caching from influencing the test + for i := 0; i < len(steps); i += 2 { + setup := i + execute := i + 1 + t.Run(fmt.Sprintf("steps-%d-%d", setup+1, execute+1), func(t *testing.T) { + logicaltest.Test(t, logicaltest.TestCase{ + CredentialBackend: testFactory(t), + Steps: []logicaltest.TestStep{steps[setup], steps[execute]}, + }) + }) + } +} + // TestOCSPWithMixedValidResponses validates the expected behavior of multiple OCSP servers configured, // with and without ocsp_query_all_servers enabled or disabled. func TestOCSPWithMixedValidResponses(t *testing.T) { @@ -2402,6 +2494,14 @@ func TestOCSPWithMixedValidResponses(t *testing.T) { "ocsp_query_all_servers": false, }), testAccStepLoginInvalid(t, connState), + // step 5/6: This should fail as we will query all the OCSP servers and prefer the revoke response + testAccStepCertWithExtraParams(t, "web", pemCa, "foo", + allowed{names: "cert.example.com"}, false, map[string]interface{}{ + "ocsp_enabled": true, + "ocsp_servers_override": []string{goodTs.URL, revokeTs.URL}, + "ocsp_query_all_servers": true, + }), + testAccStepLoginInvalid(t, connState), } // Setup a new factory everytime to avoid OCSP caching from influencing the test @@ -2496,6 +2596,28 @@ func TestOCSPFailOpenWithGoodResponse(t *testing.T) { "ocsp_max_retries": 0, }), testAccStepLogin(t, connState), + // Step 9/10 With a single positive response, query all servers set to true and fail open true, pass validation + // as fail open is true + testAccStepCertWithExtraParams(t, "web", pemCa, "foo", allowed{names: "cert.example.com"}, false, + map[string]interface{}{ + "ocsp_enabled": true, + "ocsp_servers_override": []string{ts.URL, "http://127.0.0.1:30001"}, + "ocsp_fail_open": true, + "ocsp_query_all_servers": true, + "ocsp_max_retries": 0, + }), + testAccStepLogin(t, connState), + // Step 11/12 With a single positive response, query all servers set to true and fail open false, fail validation + // as not all servers agree + testAccStepCertWithExtraParams(t, "web", pemCa, "foo", + allowed{names: "cert.example.com"}, false, map[string]interface{}{ + "ocsp_enabled": true, + "ocsp_servers_override": []string{ts.URL, "http://127.0.0.1:30001"}, + "ocsp_fail_open": false, + "ocsp_query_all_servers": true, + "ocsp_max_retries": 0, + }), + testAccStepLoginInvalid(t, connState), } // Setup a new factory everytime to avoid OCSP caching from influencing the test @@ -2567,6 +2689,26 @@ func TestOCSPFailOpenWithRevokeResponse(t *testing.T) { "ocsp_max_retries": 0, }), testAccStepLoginInvalid(t, connState), + // Step 5/6 With a single revoke response, query all servers set to true and fail open false, fail validation + testAccStepCertWithExtraParams(t, "web", pemCa, "foo", + allowed{names: "cert.example.com"}, false, map[string]interface{}{ + "ocsp_enabled": true, + "ocsp_servers_override": []string{ts.URL, "http://127.0.0.1:30001"}, + "ocsp_fail_open": false, + "ocsp_query_all_servers": true, + "ocsp_max_retries": 0, + }), + testAccStepLoginInvalid(t, connState), + // Step 7/8 With a single revoke response, query all servers set to true and fail open true, fail validation + testAccStepCertWithExtraParams(t, "web", pemCa, "foo", allowed{names: "cert.example.com"}, false, + map[string]interface{}{ + "ocsp_enabled": true, + "ocsp_servers_override": []string{ts.URL, "http://127.0.0.1:30001"}, + "ocsp_fail_open": true, + "ocsp_query_all_servers": true, + "ocsp_max_retries": 0, + }), + testAccStepLoginInvalid(t, connState), } // Setup a new factory everytime to avoid OCSP caching from influencing the test @@ -2638,6 +2780,26 @@ func TestOCSPFailOpenWithUnknownResponse(t *testing.T) { "ocsp_max_retries": 0, }), testAccStepLoginInvalid(t, connState), + // Step 5/6 With a single unknown response, query all servers set to true and fail open true, fail validation + testAccStepCertWithExtraParams(t, "web", pemCa, "foo", allowed{names: "cert.example.com"}, false, + map[string]interface{}{ + "ocsp_enabled": true, + "ocsp_servers_override": []string{ts.URL, "http://127.0.0.1:30001"}, + "ocsp_fail_open": true, + "ocsp_query_all_servers": true, + "ocsp_max_retries": 0, + }), + testAccStepLogin(t, connState), + // Step 7/8 With a single unknown response, query all servers set to true and fail open false, fail validation + testAccStepCertWithExtraParams(t, "web", pemCa, "foo", + allowed{names: "cert.example.com"}, false, map[string]interface{}{ + "ocsp_enabled": true, + "ocsp_servers_override": []string{ts.URL, "http://127.0.0.1:30001"}, + "ocsp_fail_open": false, + "ocsp_query_all_servers": true, + "ocsp_max_retries": 0, + }), + testAccStepLoginInvalid(t, connState), } // Setup a new factory everytime to avoid OCSP caching from influencing the test @@ -2751,3 +2913,28 @@ func loadCerts(t *testing.T, certFile, certKey string) tls.Certificate { return caTLS } + +func createCa(t *testing.T) (*x509.Certificate, *ecdsa.PrivateKey) { + rootCaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err, "failed generated root key for CA") + + // Validate we reject CSRs that contain CN that aren't in the original order + cr := &x509.Certificate{ + Subject: pkix.Name{CommonName: "Root Cert"}, + SerialNumber: big.NewInt(1), + IsCA: true, + BasicConstraintsValid: true, + SignatureAlgorithm: x509.ECDSAWithSHA256, + NotBefore: time.Now().Add(-1 * time.Second), + NotAfter: time.Now().AddDate(1, 0, 0), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning}, + } + rootCaBytes, err := x509.CreateCertificate(rand.Reader, cr, cr, &rootCaKey.PublicKey, rootCaKey) + require.NoError(t, err, "failed generating root ca") + + rootCa, err := x509.ParseCertificate(rootCaBytes) + require.NoError(t, err, "failed parsing root ca") + + return rootCa, rootCaKey +} diff --git a/builtin/credential/cert/path_login.go b/builtin/credential/cert/path_login.go index 55f161a9fe00..33f62818ec09 100644 --- a/builtin/credential/cert/path_login.go +++ b/builtin/credential/cert/path_login.go @@ -687,6 +687,10 @@ func (b *backend) checkForCertInOCSP(ctx context.Context, clientCert *x509.Certi defer b.ocspClientMutex.RUnlock() err := b.ocspClient.VerifyLeafCertificate(ctx, clientCert, chain[1], conf) if err != nil { + if ocsp.IsOcspVerificationError(err) { + // We don't want anything to override an OCSP verification error + return false, err + } if conf.OcspFailureMode == ocsp.FailOpenTrue { onlyNetworkErrors := b.handleOcspErrorInFailOpen(err) if onlyNetworkErrors { diff --git a/changelog/change-me.txt b/changelog/change-me.txt new file mode 100644 index 000000000000..31a219247ae3 --- /dev/null +++ b/changelog/change-me.txt @@ -0,0 +1,3 @@ +```release-note:security +auth/cert: validate OCSP response was signed by the expected issuer and serial number matched request +``` diff --git a/sdk/helper/ocsp/client.go b/sdk/helper/ocsp/client.go index c55f109a7ebf..f42ec6d277ca 100644 --- a/sdk/helper/ocsp/client.go +++ b/sdk/helper/ocsp/client.go @@ -76,6 +76,15 @@ const ( cacheExpire = float64(24 * 60 * 60) ) +// ErrOcspIssuerVerification indicates an error verifying the identity of an OCSP response occurred +type ErrOcspIssuerVerification struct { + Err error +} + +func (e *ErrOcspIssuerVerification) Error() string { + return fmt.Sprintf("ocsp response verification error: %v", e.Err) +} + type ocspCachedResponse struct { time float64 producedAt float64 @@ -308,6 +317,7 @@ func (c *Client) retryOCSP( ocspHost *url.URL, headers map[string]string, reqBody []byte, + subject, issuer *x509.Certificate, ) (ocspRes *ocsp.Response, ocspResBytes []byte, ocspS *ocspStatus, retErr error) { doRequest := func(request *retryablehttp.Request) (*http.Response, error) { @@ -379,66 +389,19 @@ func (c *Client) retryOCSP( continue } - // Above, we use the unsafe issuer=nil parameter to ocsp.ParseResponse - // because Go's library does the wrong thing. - // - // Here, we lack a full chain, but we know we trust the parent issuer, - // so if the Go library incorrectly discards useful certificates, we - // likely cannot verify this without passing through the full chain - // back to the root. - // - // Instead, take one of two paths: 1. if there is no certificate in - // the ocspRes, verify the OCSP response directly with our trusted - // issuer certificate, or 2. if there is a certificate, either verify - // it directly matches our trusted issuer certificate, or verify it - // is signed by our trusted issuer certificate. - // - // See also: https://github.com/golang/go/issues/59641 - // - // This addresses the !!unsafe!! behavior above. - if ocspRes.Certificate == nil { - if err := ocspRes.CheckSignatureFrom(issuer); err != nil { - err = fmt.Errorf("error directly verifying signature on %v OCSP response: %w", method, err) - retErr = multierror.Append(retErr, err) - continue - } - } else { - // Because we have at least one certificate here, we know that - // Go's ocsp library verified the signature from this certificate - // onto the response and it was valid. Now we need to know we trust - // this certificate. There's two ways we can do this: - // - // 1. Via confirming issuer == ocspRes.Certificate, or - // 2. Via confirming ocspRes.Certificate.CheckSignatureFrom(issuer). - if !bytes.Equal(issuer.Raw, ocspRes.Raw) { - // 1 must not hold, so 2 holds; verify the signature. - if err := ocspRes.Certificate.CheckSignatureFrom(issuer); err != nil { - err = fmt.Errorf("error checking chain of trust on %v OCSP response via %v failed: %w", method, issuer.Subject.String(), err) - retErr = multierror.Append(retErr, err) - continue - } + if err := validateOCSPParsedResponse(ocspRes, subject, issuer); err != nil { + err = fmt.Errorf("error validating %v OCSP response: %w", method, err) - // Verify the OCSP responder certificate is still valid and - // contains the required EKU since it is a delegated OCSP - // responder certificate. - if ocspRes.Certificate.NotAfter.Before(time.Now()) { - err := fmt.Errorf("error checking delegated OCSP responder on %v OCSP response: certificate has expired", method) - retErr = multierror.Append(retErr, err) - continue - } - haveEKU := false - for _, ku := range ocspRes.Certificate.ExtKeyUsage { - if ku == x509.ExtKeyUsageOCSPSigning { - haveEKU = true - break - } - } - if !haveEKU { - err := fmt.Errorf("error checking delegated OCSP responder on %v OCSP response: certificate lacks the OCSP Signing EKU", method) - retErr = multierror.Append(retErr, err) - continue - } + if IsOcspVerificationError(err) { + // We want to immediately give up on a verification error to a response + // and inform the user something isn't correct + return nil, nil, nil, err } + + retErr = multierror.Append(retErr, err) + // Clear the response out as we can't trust it. + ocspRes = nil + continue } // While we haven't validated the signature on the OCSP response, we @@ -473,6 +436,80 @@ func (c *Client) retryOCSP( return } +func IsOcspVerificationError(err error) bool { + errOcspIssuer := &ErrOcspIssuerVerification{} + return errors.As(err, &errOcspIssuer) +} + +func validateOCSPParsedResponse(ocspRes *ocsp.Response, subject, issuer *x509.Certificate) error { + // Above, we use the unsafe issuer=nil parameter to ocsp.ParseResponse + // because Go's library does the wrong thing. + // + // Here, we lack a full chain, but we know we trust the parent issuer, + // so if the Go library incorrectly discards useful certificates, we + // likely cannot verify this without passing through the full chain + // back to the root. + // + // Instead, take one of two paths: 1. if there is no certificate in + // the ocspRes, verify the OCSP response directly with our trusted + // issuer certificate, or 2. if there is a certificate, either verify + // it directly matches our trusted issuer certificate, or verify it + // is signed by our trusted issuer certificate. + // + // See also: https://github.com/golang/go/issues/59641 + // + // This addresses the !!unsafe!! behavior above. + if ocspRes.Certificate == nil { + if err := ocspRes.CheckSignatureFrom(issuer); err != nil { + return &ErrOcspIssuerVerification{fmt.Errorf("error directly verifying signature: %w", err)} + } + } else { + // Because we have at least one certificate here, we know that + // Go's ocsp library verified the signature from this certificate + // onto the response and it was valid. Now we need to know we trust + // this certificate. There's two ways we can do this: + // + // 1. Via confirming issuer == ocspRes.Certificate, or + // 2. Via confirming ocspRes.Certificate.CheckSignatureFrom(issuer). + if !bytes.Equal(issuer.Raw, ocspRes.Raw) { + // 1 must not hold, so 2 holds; verify the signature. + if err := ocspRes.Certificate.CheckSignatureFrom(issuer); err != nil { + return &ErrOcspIssuerVerification{fmt.Errorf("error checking chain of trust %v failed: %w", issuer.Subject.String(), err)} + } + + // Verify the OCSP responder certificate is still valid and + // contains the required EKU since it is a delegated OCSP + // responder certificate. + if ocspRes.Certificate.NotAfter.Before(time.Now()) { + return &ErrOcspIssuerVerification{fmt.Errorf("error checking delegated OCSP responder OCSP response: certificate has expired")} + } + haveEKU := false + for _, ku := range ocspRes.Certificate.ExtKeyUsage { + if ku == x509.ExtKeyUsageOCSPSigning { + haveEKU = true + break + } + } + if !haveEKU { + return &ErrOcspIssuerVerification{fmt.Errorf("error checking delegated OCSP responder: certificate lacks the OCSP Signing EKU")} + } + } + } + + // Verify the response was for our original subject + if ocspRes.SerialNumber == nil || subject.SerialNumber == nil { + return &ErrOcspIssuerVerification{fmt.Errorf("OCSP response or cert did not contain a serial number")} + } + if ocspRes.SerialNumber.Cmp(subject.SerialNumber) != 0 { + return &ErrOcspIssuerVerification{fmt.Errorf( + "OCSP response serial number %s did not match the leaf certificate serial number %s", + certutil.GetHexFormatted(ocspRes.SerialNumber.Bytes(), ":"), + certutil.GetHexFormatted(subject.SerialNumber.Bytes(), ":"))} + } + + return nil +} + // GetRevocationStatus checks the certificate revocation status for subject using issuer certificate. func (c *Client) GetRevocationStatus(ctx context.Context, subject, issuer *x509.Certificate, conf *VerifyConfig) (*ocspStatus, error) { status, ocspReq, encodedCertID, err := c.validateWithCache(subject, issuer, conf) @@ -520,12 +557,12 @@ func (c *Client) GetRevocationStatus(ctx context.Context, subject, issuer *x509. ocspClient.HTTPClient.Timeout = timeout ocspClient.HTTPClient.Transport = newInsecureOcspTransport(conf.ExtraCas) - doRequest := func() error { + doRequest := func(i int) error { if conf.QueryAllServers { defer wg.Done() } ocspRes, _, ocspS, err := c.retryOCSP( - ctx, ocspClient, retryablehttp.NewRequest, u, headers, ocspReq, issuer) + ctx, ocspClient, retryablehttp.NewRequest, u, headers, ocspReq, subject, issuer) ocspResponses[i] = ocspRes if err != nil { errors[i] = err @@ -553,9 +590,9 @@ func (c *Client) GetRevocationStatus(ctx context.Context, subject, issuer *x509. } if conf.QueryAllServers { wg.Add(1) - go doRequest() + go doRequest(i) } else { - err = doRequest() + err = doRequest(i) if err == nil { break } @@ -570,6 +607,9 @@ func (c *Client) GetRevocationStatus(ctx context.Context, subject, issuer *x509. var firstError error for i := range ocspHosts { if errors[i] != nil { + if IsOcspVerificationError(errors[i]) { + return nil, errors[i] + } if firstError == nil { firstError = errors[i] } diff --git a/sdk/helper/ocsp/ocsp_test.go b/sdk/helper/ocsp/ocsp_test.go index 9aa1d7c9f8b1..326a5b233647 100644 --- a/sdk/helper/ocsp/ocsp_test.go +++ b/sdk/helper/ocsp/ocsp_test.go @@ -298,11 +298,14 @@ func TestUnitValidOCSPResponse(t *testing.T) { // OCSP response conditions func TestUnitBadOCSPResponses(t *testing.T) { rootCaKey, rootCa, leafCert := createCaLeafCerts(t) + rootCaKey2, rootCa2, _ := createCaLeafCerts(t) type tests struct { name string ocspRes ocsp.Response maxAge time.Duration + ca *x509.Certificate + caKey *ecdsa.PrivateKey errContains string } @@ -310,6 +313,30 @@ func TestUnitBadOCSPResponses(t *testing.T) { ctx := context.Background() tt := []tests{ + { + name: "bad-signing-issuer", + ocspRes: ocsp.Response{ + SerialNumber: leafCert.SerialNumber, + ThisUpdate: now.Add(-1 * time.Hour), + NextUpdate: now.Add(30 * time.Minute), + Status: ocsp.Good, + }, + ca: rootCa2, + caKey: rootCaKey2, + errContains: "error directly verifying signature", + }, + { + name: "incorrect-serial-number", + ocspRes: ocsp.Response{ + SerialNumber: big.NewInt(1000), + ThisUpdate: now.Add(-1 * time.Hour), + NextUpdate: now.Add(30 * time.Minute), + Status: ocsp.Good, + }, + ca: rootCa, + caKey: rootCaKey, + errContains: "did not match the leaf certificate serial number", + }, { name: "expired-next-update", ocspRes: ocsp.Response{ @@ -374,7 +401,15 @@ func TestUnitBadOCSPResponses(t *testing.T) { for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { ocspHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - response := buildOcspResponse(t, rootCa, rootCaKey, tc.ocspRes) + useCa := rootCa + useCaKey := rootCaKey + if tc.ca != nil { + useCa = tc.ca + } + if tc.caKey != nil { + useCaKey = tc.caKey + } + response := buildOcspResponse(t, useCa, useCaKey, tc.ocspRes) _, _ = w.Write(response) }) ts := httptest.NewServer(ocspHandler) @@ -449,6 +484,51 @@ func TestUnitZeroNextUpdateAreNotCached(t *testing.T) { require.Equal(t, uint32(2), numQueries.Load()) } +// TestUnitResponsesAreCached verify that the OCSP responses are properly cached when +// querying for the same leaf certificates +func TestUnitResponsesAreCached(t *testing.T) { + rootCaKey, rootCa, leafCert := createCaLeafCerts(t) + numQueries := &atomic.Uint32{} + ocspHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + numQueries.Add(1) + now := time.Now() + ocspRes := ocsp.Response{ + SerialNumber: leafCert.SerialNumber, + ThisUpdate: now.Add(-1 * time.Hour), + NextUpdate: now.Add(1 * time.Hour), + Status: ocsp.Good, + } + response := buildOcspResponse(t, rootCa, rootCaKey, ocspRes) + _, _ = w.Write(response) + }) + ts1 := httptest.NewServer(ocspHandler) + ts2 := httptest.NewServer(ocspHandler) + defer ts1.Close() + defer ts2.Close() + + logFactory := func() hclog.Logger { + return hclog.NewNullLogger() + } + client := New(logFactory, 100) + + config := &VerifyConfig{ + OcspEnabled: true, + OcspServersOverride: []string{ts1.URL, ts2.URL}, + QueryAllServers: true, + } + + _, err := client.GetRevocationStatus(context.Background(), leafCert, rootCa, config) + require.NoError(t, err, "Failed fetching revocation status") + // Make sure that we queried both servers and not the cache + require.Equal(t, uint32(2), numQueries.Load()) + + // These query should be cached and not influence our counter + _, err = client.GetRevocationStatus(context.Background(), leafCert, rootCa, config) + require.NoError(t, err, "Failed fetching revocation status second time") + + require.Equal(t, uint32(2), numQueries.Load()) +} + func buildOcspResponse(t *testing.T, ca *x509.Certificate, caKey *ecdsa.PrivateKey, ocspRes ocsp.Response) []byte { response, err := ocsp.CreateResponse(ca, ca, ocspRes, caKey) if err != nil { @@ -598,7 +678,7 @@ func TestOCSPRetry(t *testing.T) { context.TODO(), client, fakeRequestFunc, dummyOCSPHost, - make(map[string]string), []byte{0}, certs[len(certs)-1]) + make(map[string]string), []byte{0}, certs[0], certs[len(certs)-1]) if err == nil { fmt.Printf("should fail: %v, %v, %v\n", res, b, st) } @@ -613,7 +693,7 @@ func TestOCSPRetry(t *testing.T) { context.TODO(), client, fakeRequestFunc, dummyOCSPHost, - make(map[string]string), []byte{0}, certs[len(certs)-1]) + make(map[string]string), []byte{0}, certs[0], certs[len(certs)-1]) if err == nil { fmt.Printf("should fail: %v, %v, %v\n", res, b, st) } From c8a08d37c740909e88fc1f1493a360c72357c7ca Mon Sep 17 00:00:00 2001 From: Steve Clark Date: Fri, 22 Mar 2024 09:06:04 -0400 Subject: [PATCH 2/2] Add cl --- changelog/{change-me.txt => 26091.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog/{change-me.txt => 26091.txt} (100%) diff --git a/changelog/change-me.txt b/changelog/26091.txt similarity index 100% rename from changelog/change-me.txt rename to changelog/26091.txt