Skip to content

Commit c7496f1

Browse files
authored
More validation of delegated OCSP responders (#378)
* More validation of delegated OCSP responders * Add tests
1 parent cdc4eb2 commit c7496f1

2 files changed

Lines changed: 93 additions & 0 deletions

File tree

ocsp.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ func getOCSPForCert(ocspConfig OCSPConfig, bundle []byte) ([]byte, *ocsp.Respons
235235
return nil, nil, fmt.Errorf("parsing OCSP response: %v", err)
236236
}
237237

238+
if err := validateOCSPResponder(ocspRes, issuerCert); err != nil {
239+
return nil, nil, fmt.Errorf("OCSP responder authorization check failed: %v", err)
240+
}
241+
238242
return ocspResBytes, ocspRes, nil
239243
}
240244

@@ -253,3 +257,26 @@ func freshOCSP(resp *ocsp.Response) bool {
253257
refreshTime := resp.ThisUpdate.Add(nextUpdate.Sub(resp.ThisUpdate) / 2)
254258
return time.Now().Before(refreshTime)
255259
}
260+
261+
// validateOCSPResponder enforces RFC 6960 §4.2.2.2: "Systems or applications that
262+
// rely on OCSP responses MUST be capable of detecting and enforcing the use of the
263+
// id-kp-OCSPSigning value." An issuer-signed response (where the embedded Certificate
264+
// field is nil, meaning the issuer signed directly) is always acceptable.
265+
func validateOCSPResponder(ocspResp *ocsp.Response, issuerCert *x509.Certificate) error {
266+
respCert := ocspResp.Certificate
267+
268+
// if response was signed directly by the issuer, or embedded responder cert IS the issuer, accept
269+
if respCert == nil || respCert.Equal(issuerCert) {
270+
// Response was signed directly by the issuer — always valid.
271+
return nil
272+
}
273+
274+
// RFC 6960 §4.2.2.2 requires id-kp-OCSPSigning for delegated responders
275+
for _, eku := range respCert.ExtKeyUsage {
276+
if eku == x509.ExtKeyUsageOCSPSigning {
277+
return nil
278+
}
279+
}
280+
281+
return fmt.Errorf("OCSP responder certificate (subject: %s) is not the issuer and does not carry id-kp-OCSPSigning", respCert.Subject)
282+
}

ocsp_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import (
44
"bytes"
55
"context"
66
"crypto"
7+
"crypto/x509"
8+
"crypto/x509/pkix"
79
"errors"
810
"io"
911
"net/http"
1012
"net/http/httptest"
13+
"strings"
1114
"testing"
1215

1316
"golang.org/x/crypto/ocsp"
@@ -153,6 +156,69 @@ func TestStapleOCSP(t *testing.T) {
153156
})
154157
}
155158

159+
func TestValidateOCSPResponder(t *testing.T) {
160+
issuer := mustMakeCertificate(t, caCert, caKey).Leaf
161+
162+
tests := []struct {
163+
name string
164+
resp *ocsp.Response
165+
wantErr string
166+
}{
167+
{
168+
name: "issuer signed response with no embedded cert",
169+
resp: &ocsp.Response{Certificate: nil},
170+
},
171+
{
172+
name: "embedded responder cert is issuer cert",
173+
resp: &ocsp.Response{Certificate: issuer},
174+
},
175+
{
176+
name: "delegated responder with OCSP signing eku",
177+
resp: &ocsp.Response{Certificate: &x509.Certificate{
178+
Subject: pkix.Name{CommonName: "Delegated OCSP Responder"},
179+
ExtKeyUsage: []x509.ExtKeyUsage{
180+
x509.ExtKeyUsageServerAuth,
181+
x509.ExtKeyUsageOCSPSigning,
182+
},
183+
}},
184+
},
185+
{
186+
name: "delegated responder without OCSP signing eku",
187+
resp: &ocsp.Response{Certificate: &x509.Certificate{
188+
Subject: pkix.Name{CommonName: "Not Authorized"},
189+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
190+
}},
191+
wantErr: "does not carry id-kp-OCSPSigning",
192+
},
193+
{
194+
name: "delegated responder with empty eku",
195+
resp: &ocsp.Response{Certificate: &x509.Certificate{
196+
Subject: pkix.Name{CommonName: "No EKU"},
197+
}},
198+
wantErr: "does not carry id-kp-OCSPSigning",
199+
},
200+
}
201+
202+
for _, tc := range tests {
203+
t.Run(tc.name, func(t *testing.T) {
204+
err := validateOCSPResponder(tc.resp, issuer)
205+
if tc.wantErr == "" {
206+
if err != nil {
207+
t.Fatalf("unexpected error: %v", err)
208+
}
209+
return
210+
}
211+
212+
if err == nil {
213+
t.Fatalf("expected error containing %q, got nil", tc.wantErr)
214+
}
215+
if !strings.Contains(err.Error(), tc.wantErr) {
216+
t.Fatalf("expected error containing %q, got %q", tc.wantErr, err.Error())
217+
}
218+
})
219+
}
220+
}
221+
156222
func mustMakeCertificate(t *testing.T, cert, key string) Certificate {
157223
t.Helper()
158224
c, err := makeCertificate([]byte(cert), []byte(key))

0 commit comments

Comments
 (0)