Skip to content

Commit 2b4a688

Browse files
authored
Add OCSP stapling unit tests (#259)
Add a few tests to exercise OCSP stapling, using an httptest.Server acting as the OCSP responder. These tests are very simplistic: - a "good" OCSP response should be stapled - a "revoked" OCSP response should not be stapled - the DisableStapling option should be honored - OCSP stapling requires an issuing certificate No attempt is made to provide sensible timestamps, either in the test certificates or in the OCSP responses. This brings unit test coverage for ocsp.go from 21% to 70%.
1 parent 4574cfa commit 2b4a688

File tree

1 file changed

+158
-15
lines changed

1 file changed

+158
-15
lines changed

ocsp_test.go

+158-15
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,182 @@
11
package certmagic
22

33
import (
4+
"bytes"
45
"context"
6+
"crypto"
57
"errors"
8+
"io"
9+
"net/http"
10+
"net/http/httptest"
611
"testing"
12+
13+
"golang.org/x/crypto/ocsp"
714
)
815

9-
// certWithoutOCSPServer is a minimal self-signed certificate.
16+
const certWithOCSPServer = `-----BEGIN CERTIFICATE-----
17+
MIIBgjCCASegAwIBAgICIAAwCgYIKoZIzj0EAwIwEjEQMA4GA1UEAxMHVGVzdCBD
18+
QTAeFw0yMzAxMDExMjAwMDBaFw0yMzAyMDExMjAwMDBaMCAxHjAcBgNVBAMTFU9D
19+
U1AgVGVzdCBDZXJ0aWZpY2F0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIoe
20+
I/bjo34qony8LdRJD+Jhuk8/S8YHXRHl6rH9t5VFCFtX8lIPN/Ll1zCrQ2KB3Wlb
21+
fxSgiQyLrCpZyrdhVPSjXzBdMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAU+Eo3
22+
5sST4LRrwS4dueIdGBZ5d7IwLAYIKwYBBQUHAQEEIDAeMBwGCCsGAQUFBzABhhBv
23+
Y3NwLmV4YW1wbGUuY29tMAoGCCqGSM49BAMCA0kAMEYCIQDg94xY/+/VepESdvTT
24+
ykCwiWOS2aCpjyryrKpwMKkR0AIhAPc/+ZEz4W10OENxC1t+NUTvS8JbEGOwulkZ
25+
z9yfaLuD
26+
-----END CERTIFICATE-----`
27+
1028
const certWithoutOCSPServer = `-----BEGIN CERTIFICATE-----
11-
MIIBEDCBtqADAgECAgEBMAoGCCqGSM49BAMCMAAwIhgPMDAwMTAxMDEwMDAwMDBa
12-
GA8wMDAxMDEwMTAwMDAwMFowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJ0p
13-
7FKiv9p5rMMzntQeEBesKQnFR4XYFZ/SVlgJHFzd/QZ2sSxW+Mlbz78TTp4DMMIZ
14-
J0z/Tw2+6fWdvoCYCW2jHTAbMBkGA1UdEQEB/wQPMA2CC2V4YW1wbGUuY29tMAoG
15-
CCqGSM49BAMCA0kAMEYCIQDMbDvbJ/SXgRoblhBmt80F5iAyuOA0v20x0gpImK01
16-
oQIhANxdGJPvBaz0wOVBCSpd5jHbPxPxwqKZYJEes6y7eM+I
29+
MIIBUzCB+aADAgECAgIgADAKBggqhkjOPQQDAjASMRAwDgYDVQQDEwdUZXN0IENB
30+
MB4XDTIzMDEwMTEyMDAwMFoXDTIzMDIwMTEyMDAwMFowIDEeMBwGA1UEAxMVT0NT
31+
UCBUZXN0IENlcnRpZmljYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEih4j
32+
9uOjfiqifLwt1EkP4mG6Tz9LxgddEeXqsf23lUUIW1fyUg838uXXMKtDYoHdaVt/
33+
FKCJDIusKlnKt2FU9KMxMC8wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBT4Sjfm
34+
xJPgtGvBLh254h0YFnl3sjAKBggqhkjOPQQDAgNJADBGAiEA3rWetLGblfSuNZKf
35+
5CpZxhj3A0BjEocEh+2P+nAgIdUCIQDIgptabR1qTLQaF2u0hJsEX2IKuIUvYWH3
36+
6Lb92+zIHg==
1737
-----END CERTIFICATE-----`
1838

19-
const privateKey = `-----BEGIN EC PRIVATE KEY-----
39+
// certKey is the private key for both certWithOCSPServer and
40+
// certWithoutOCSPServer.
41+
const certKey = `-----BEGIN EC PRIVATE KEY-----
42+
MHcCAQEEINnVcgrSNh4HlThWlZpegq14M8G/p9NVDtdVjZrseUGLoAoGCCqGSM49
43+
AwEHoUQDQgAEih4j9uOjfiqifLwt1EkP4mG6Tz9LxgddEeXqsf23lUUIW1fyUg83
44+
8uXXMKtDYoHdaVt/FKCJDIusKlnKt2FU9A==
45+
-----END EC PRIVATE KEY-----`
46+
47+
// caCert is the issuing certificate for certWithOCSPServer and
48+
// certWithoutOCSPServer.
49+
const caCert = `-----BEGIN CERTIFICATE-----
50+
MIIBazCCARGgAwIBAgICEAAwCgYIKoZIzj0EAwIwEjEQMA4GA1UEAxMHVGVzdCBD
51+
QTAeFw0yMzAxMDExMjAwMDBaFw0yMzAyMDExMjAwMDBaMBIxEDAOBgNVBAMTB1Rl
52+
c3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASdKexSor/aeazDM57UHhAX
53+
rCkJxUeF2BWf0lZYCRxc3f0GdrEsVvjJW8+/E06eAzDCGSdM/08Nvun1nb6AmAlt
54+
o1cwVTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwkwDwYDVR0T
55+
AQH/BAUwAwEB/zAdBgNVHQ4EFgQU+Eo35sST4LRrwS4dueIdGBZ5d7IwCgYIKoZI
56+
zj0EAwIDSAAwRQIgGbA39+kETTB/YMLBFoC2fpZe1cDWfFB7TUdfINUqdH4CIQCR
57+
ByUFC8A+hRNkK5YNH78bgjnKk/88zUQF5ONy4oPGdQ==
58+
-----END CERTIFICATE-----`
59+
60+
const caKey = `-----BEGIN EC PRIVATE KEY-----
2061
MHcCAQEEIDJ59ptjq3MzILH4zn5IKoH1sYn+zrUeq2kD8+DD2x+OoAoGCCqGSM49
2162
AwEHoUQDQgAEnSnsUqK/2nmswzOe1B4QF6wpCcVHhdgVn9JWWAkcXN39BnaxLFb4
2263
yVvPvxNOngMwwhknTP9PDb7p9Z2+gJgJbQ==
2364
-----END EC PRIVATE KEY-----`
2465

25-
func TestOCSPServerNotSpecified(t *testing.T) {
26-
var config OCSPConfig
66+
func TestStapleOCSP(t *testing.T) {
67+
ctx := context.Background()
2768
storage := &FileStorage{Path: t.TempDir()}
2869

29-
pemCert := []byte(certWithoutOCSPServer)
30-
cert, err := makeCertificate(pemCert, []byte(privateKey))
70+
t.Run("disabled", func(t *testing.T) {
71+
cert := mustMakeCertificate(t, certWithOCSPServer, certKey)
72+
config := OCSPConfig{DisableStapling: true}
73+
err := stapleOCSP(ctx, config, storage, &cert, nil)
74+
if err != nil {
75+
t.Error("unexpected error:", err)
76+
} else if cert.Certificate.OCSPStaple != nil {
77+
t.Error("unexpected OCSP staple")
78+
}
79+
})
80+
t.Run("no OCSP server", func(t *testing.T) {
81+
cert := mustMakeCertificate(t, certWithoutOCSPServer, certKey)
82+
err := stapleOCSP(ctx, OCSPConfig{}, storage, &cert, nil)
83+
if !errors.Is(err, ErrNoOCSPServerSpecified) {
84+
t.Error("expected ErrNoOCSPServerSpecified in error", err)
85+
}
86+
})
87+
88+
// Start an OCSP responder test server.
89+
responses := make(map[string][]byte)
90+
responder := startOCSPResponder(t, responses)
91+
t.Cleanup(responder.Close)
92+
93+
ca := mustMakeCertificate(t, caCert, caKey)
94+
95+
// The certWithOCSPServer certificate has a bogus ocsp.example.com endpoint.
96+
// Use the ResponderOverrides option to point to the test server instead.
97+
config := OCSPConfig{
98+
ResponderOverrides: map[string]string{
99+
"ocsp.example.com": responder.URL,
100+
},
101+
}
102+
103+
t.Run("ok", func(t *testing.T) {
104+
cert := mustMakeCertificate(t, certWithOCSPServer, certKey)
105+
tpl := ocsp.Response{
106+
Status: ocsp.Good,
107+
SerialNumber: cert.Leaf.SerialNumber,
108+
}
109+
r, err := ocsp.CreateResponse(
110+
ca.Leaf, ca.Leaf, tpl, ca.PrivateKey.(crypto.Signer))
111+
if err != nil {
112+
t.Fatal("couldn't create OCSP response", err)
113+
}
114+
responses[cert.Leaf.SerialNumber.String()] = r
115+
116+
bundle := []byte(certWithOCSPServer + "\n" + caCert)
117+
err = stapleOCSP(ctx, config, storage, &cert, bundle)
118+
if err != nil {
119+
t.Error("unexpected error:", err)
120+
} else if !bytes.Equal(cert.Certificate.OCSPStaple, r) {
121+
t.Error("expected OCSP response to be stapled to certificate")
122+
}
123+
})
124+
t.Run("revoked", func(t *testing.T) {
125+
cert := mustMakeCertificate(t, certWithOCSPServer, certKey)
126+
tpl := ocsp.Response{
127+
Status: ocsp.Revoked,
128+
SerialNumber: cert.Leaf.SerialNumber,
129+
}
130+
r, err := ocsp.CreateResponse(
131+
ca.Leaf, ca.Leaf, tpl, ca.PrivateKey.(crypto.Signer))
132+
if err != nil {
133+
t.Fatal("couldn't create OCSP response", err)
134+
}
135+
responses[cert.Leaf.SerialNumber.String()] = r
136+
137+
bundle := []byte(certWithOCSPServer + "\n" + caCert)
138+
err = stapleOCSP(ctx, config, storage, &cert, bundle)
139+
if err != nil {
140+
t.Error("unexpected error:", err)
141+
} else if cert.Certificate.OCSPStaple != nil {
142+
t.Error("revoked OCSP response should not be stapled")
143+
}
144+
})
145+
t.Run("no issuing cert", func(t *testing.T) {
146+
cert := mustMakeCertificate(t, certWithOCSPServer, certKey)
147+
err := stapleOCSP(ctx, config, storage, &cert, nil)
148+
expected := "no OCSP stapling for [ocsp test certificate]: " +
149+
"no URL to issuing certificate"
150+
if err == nil || err.Error() != expected {
151+
t.Errorf("expected error %q but got %q", expected, err)
152+
}
153+
})
154+
}
155+
156+
func mustMakeCertificate(t *testing.T, cert, key string) Certificate {
157+
t.Helper()
158+
c, err := makeCertificate([]byte(cert), []byte(key))
31159
if err != nil {
32160
t.Fatal("couldn't make certificate:", err)
33161
}
162+
return c
163+
}
34164

35-
err = stapleOCSP(context.Background(), config, storage, &cert, pemCert)
36-
if !errors.Is(err, ErrNoOCSPServerSpecified) {
37-
t.Error("expected ErrOCSPServerNotSpecified in error", err)
165+
func startOCSPResponder(
166+
t *testing.T, responses map[string][]byte,
167+
) *httptest.Server {
168+
h := func(w http.ResponseWriter, r *http.Request) {
169+
ct := r.Header.Get("Content-Type")
170+
if ct != "application/ocsp-request" {
171+
t.Errorf("unexpected request Content-Type %q", ct)
172+
}
173+
b, _ := io.ReadAll(r.Body)
174+
request, err := ocsp.ParseRequest(b)
175+
if err != nil {
176+
t.Fatal(err)
177+
}
178+
w.Header().Set("Content-Type", "application/ocsp-response")
179+
w.Write(responses[request.SerialNumber.String()])
38180
}
181+
return httptest.NewServer(http.HandlerFunc(h))
39182
}

0 commit comments

Comments
 (0)