Skip to content

Commit 2c47d42

Browse files
committed
Add fix for Go x/crypto/ocsp failure case
When calling ocsp.ParseRequest(req, issue) with a non-nil issuer on a ocsp request which _unknowingly_ contains an entry in the BasicOCSPResponse's certs field, Go incorrectly assumes that the issuer is a direct parent of the _first_ certificate in the certs field, discarding the rest. As documented in the Go issue, this is not a valid assumption and thus causes OCSP verification to fail in Vault with an error like: > bad OCSP signature: crypto/rsa: verification error which ultimately leads to a cert auth login error of: > no chain matching all constraints could be found for this login certificate We address this by using the unsafe issuer=nil argument, taking on the task of validating the OCSP response's signature as best we can in the absence of full chain information on either side (both the trusted certificate whose OCSP response we're verifying and the lack of any additional certs the OCSP responder may have sent). See also: golang/go#59641 Signed-off-by: Alexander Scheel <[email protected]>
1 parent 3acbedd commit 2c47d42

File tree

1 file changed

+63
-1
lines changed

1 file changed

+63
-1
lines changed

sdk/helper/ocsp/client.go

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,13 +347,75 @@ func (c *Client) retryOCSP(
347347
// endpoint might return invalid results for e.g., GET but return
348348
// valid results for POST on retry. This could happen if e.g., the
349349
// server responds with JSON.
350-
ocspRes, err = ocsp.ParseResponse(ocspResBytes, issuer)
350+
ocspRes, err = ocsp.ParseResponse(ocspResBytes /*issuer = */, nil /* !!unsafe!! */)
351351
if err != nil {
352352
err = fmt.Errorf("error parsing %v OCSP response: %w", method, err)
353353
retErr = multierror.Append(retErr, err)
354354
continue
355355
}
356356

357+
// Above, we use the unsafe issuer=nil parameter to ocsp.ParseResponse
358+
// because Go's library does the wrong thing.
359+
//
360+
// Here, we lack a full chain, but we know we trust the parent issuer,
361+
// so if the Go library incorrectly discards useful certificates, we
362+
// likely cannot verify this without passing through the full chain
363+
// back to the root.
364+
//
365+
// Instead, take one of two paths: 1. if there is no certificate in
366+
// the ocspRes, verify the OCSP response directly with our trusted
367+
// issuer certificate, or 2. if there is a certificate, either verify
368+
// it directly matches our trusted issuer certificate, or verify it
369+
// is signed by our trusted issuer certificate.
370+
//
371+
// See also: https://github.com/golang/go/issues/59641
372+
//
373+
// This addresses the !!unsafe!! behavior above.
374+
if ocspRes.Certificate == nil {
375+
if err := ocspRes.CheckSignatureFrom(issuer); err != nil {
376+
err = fmt.Errorf("error directly verifying signature on %v OCSP response: %w", method, err)
377+
retErr = multierror.Append(retErr, err)
378+
continue
379+
}
380+
} else {
381+
// Because we have at least one certificate here, we know that
382+
// Go's ocsp library verified the signature from this certificate
383+
// onto the response and it was valid. Now we need to know we trust
384+
// this certificate. There's two ways we can do this:
385+
//
386+
// 1. Via confirming issuer == ocspRes.Certificate, or
387+
// 2. Via confirming ocspRes.Certificate.CheckSignatureFrom(issuer).
388+
if !bytes.Equal(issuer.Raw, ocspRes.Raw) {
389+
// 1 must not hold, so 2 holds; verify the signature.
390+
if err := ocspRes.Certificate.CheckSignatureFrom(issuer); err != nil {
391+
err = fmt.Errorf("error checking chain of trust on %v OCSP response via %v failed: %w", method, issuer.Subject.String(), err)
392+
retErr = multierror.Append(retErr, err)
393+
continue
394+
}
395+
396+
// Verify the OCSP responder certificate is still valid and
397+
// contains the required EKU since it is a delegated OCSP
398+
// responder certificate.
399+
if ocspRes.Certificate.NotAfter.Before(time.Now()) {
400+
err := fmt.Errorf("error checking delegated OCSP responder on %v OCSP response: certificate has expired", method)
401+
retErr = multierror.Append(retErr, err)
402+
continue
403+
}
404+
haveEKU := false
405+
for _, ku := range ocspRes.Certificate.ExtKeyUsage {
406+
if ku == x509.ExtKeyUsageOCSPSigning {
407+
haveEKU = true
408+
break
409+
}
410+
}
411+
if !haveEKU {
412+
err := fmt.Errorf("error checking delegated OCSP responder on %v OCSP response: certificate lacks the OCSP Signing EKU", method)
413+
retErr = multierror.Append(retErr, err)
414+
continue
415+
}
416+
}
417+
}
418+
357419
// While we haven't validated the signature on the OCSP response, we
358420
// got what we presume is a definitive answer and simply changing
359421
// methods will likely not help us in that regard. Use this status

0 commit comments

Comments
 (0)