-
-
Notifications
You must be signed in to change notification settings - Fork 286
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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%.
- Loading branch information
1 parent
4574cfa
commit 2b4a688
Showing
1 changed file
with
158 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,182 @@ | ||
package certmagic | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"crypto" | ||
"errors" | ||
"io" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"golang.org/x/crypto/ocsp" | ||
) | ||
|
||
// certWithoutOCSPServer is a minimal self-signed certificate. | ||
const certWithOCSPServer = `-----BEGIN CERTIFICATE----- | ||
MIIBgjCCASegAwIBAgICIAAwCgYIKoZIzj0EAwIwEjEQMA4GA1UEAxMHVGVzdCBD | ||
QTAeFw0yMzAxMDExMjAwMDBaFw0yMzAyMDExMjAwMDBaMCAxHjAcBgNVBAMTFU9D | ||
U1AgVGVzdCBDZXJ0aWZpY2F0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIoe | ||
I/bjo34qony8LdRJD+Jhuk8/S8YHXRHl6rH9t5VFCFtX8lIPN/Ll1zCrQ2KB3Wlb | ||
fxSgiQyLrCpZyrdhVPSjXzBdMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAU+Eo3 | ||
5sST4LRrwS4dueIdGBZ5d7IwLAYIKwYBBQUHAQEEIDAeMBwGCCsGAQUFBzABhhBv | ||
Y3NwLmV4YW1wbGUuY29tMAoGCCqGSM49BAMCA0kAMEYCIQDg94xY/+/VepESdvTT | ||
ykCwiWOS2aCpjyryrKpwMKkR0AIhAPc/+ZEz4W10OENxC1t+NUTvS8JbEGOwulkZ | ||
z9yfaLuD | ||
-----END CERTIFICATE-----` | ||
|
||
const certWithoutOCSPServer = `-----BEGIN CERTIFICATE----- | ||
MIIBEDCBtqADAgECAgEBMAoGCCqGSM49BAMCMAAwIhgPMDAwMTAxMDEwMDAwMDBa | ||
GA8wMDAxMDEwMTAwMDAwMFowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJ0p | ||
7FKiv9p5rMMzntQeEBesKQnFR4XYFZ/SVlgJHFzd/QZ2sSxW+Mlbz78TTp4DMMIZ | ||
J0z/Tw2+6fWdvoCYCW2jHTAbMBkGA1UdEQEB/wQPMA2CC2V4YW1wbGUuY29tMAoG | ||
CCqGSM49BAMCA0kAMEYCIQDMbDvbJ/SXgRoblhBmt80F5iAyuOA0v20x0gpImK01 | ||
oQIhANxdGJPvBaz0wOVBCSpd5jHbPxPxwqKZYJEes6y7eM+I | ||
MIIBUzCB+aADAgECAgIgADAKBggqhkjOPQQDAjASMRAwDgYDVQQDEwdUZXN0IENB | ||
MB4XDTIzMDEwMTEyMDAwMFoXDTIzMDIwMTEyMDAwMFowIDEeMBwGA1UEAxMVT0NT | ||
UCBUZXN0IENlcnRpZmljYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEih4j | ||
9uOjfiqifLwt1EkP4mG6Tz9LxgddEeXqsf23lUUIW1fyUg838uXXMKtDYoHdaVt/ | ||
FKCJDIusKlnKt2FU9KMxMC8wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBT4Sjfm | ||
xJPgtGvBLh254h0YFnl3sjAKBggqhkjOPQQDAgNJADBGAiEA3rWetLGblfSuNZKf | ||
5CpZxhj3A0BjEocEh+2P+nAgIdUCIQDIgptabR1qTLQaF2u0hJsEX2IKuIUvYWH3 | ||
6Lb92+zIHg== | ||
-----END CERTIFICATE-----` | ||
|
||
const privateKey = `-----BEGIN EC PRIVATE KEY----- | ||
// certKey is the private key for both certWithOCSPServer and | ||
// certWithoutOCSPServer. | ||
const certKey = `-----BEGIN EC PRIVATE KEY----- | ||
MHcCAQEEINnVcgrSNh4HlThWlZpegq14M8G/p9NVDtdVjZrseUGLoAoGCCqGSM49 | ||
AwEHoUQDQgAEih4j9uOjfiqifLwt1EkP4mG6Tz9LxgddEeXqsf23lUUIW1fyUg83 | ||
8uXXMKtDYoHdaVt/FKCJDIusKlnKt2FU9A== | ||
-----END EC PRIVATE KEY-----` | ||
|
||
// caCert is the issuing certificate for certWithOCSPServer and | ||
// certWithoutOCSPServer. | ||
const caCert = `-----BEGIN CERTIFICATE----- | ||
MIIBazCCARGgAwIBAgICEAAwCgYIKoZIzj0EAwIwEjEQMA4GA1UEAxMHVGVzdCBD | ||
QTAeFw0yMzAxMDExMjAwMDBaFw0yMzAyMDExMjAwMDBaMBIxEDAOBgNVBAMTB1Rl | ||
c3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASdKexSor/aeazDM57UHhAX | ||
rCkJxUeF2BWf0lZYCRxc3f0GdrEsVvjJW8+/E06eAzDCGSdM/08Nvun1nb6AmAlt | ||
o1cwVTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwkwDwYDVR0T | ||
AQH/BAUwAwEB/zAdBgNVHQ4EFgQU+Eo35sST4LRrwS4dueIdGBZ5d7IwCgYIKoZI | ||
zj0EAwIDSAAwRQIgGbA39+kETTB/YMLBFoC2fpZe1cDWfFB7TUdfINUqdH4CIQCR | ||
ByUFC8A+hRNkK5YNH78bgjnKk/88zUQF5ONy4oPGdQ== | ||
-----END CERTIFICATE-----` | ||
|
||
const caKey = `-----BEGIN EC PRIVATE KEY----- | ||
MHcCAQEEIDJ59ptjq3MzILH4zn5IKoH1sYn+zrUeq2kD8+DD2x+OoAoGCCqGSM49 | ||
AwEHoUQDQgAEnSnsUqK/2nmswzOe1B4QF6wpCcVHhdgVn9JWWAkcXN39BnaxLFb4 | ||
yVvPvxNOngMwwhknTP9PDb7p9Z2+gJgJbQ== | ||
-----END EC PRIVATE KEY-----` | ||
|
||
func TestOCSPServerNotSpecified(t *testing.T) { | ||
var config OCSPConfig | ||
func TestStapleOCSP(t *testing.T) { | ||
ctx := context.Background() | ||
storage := &FileStorage{Path: t.TempDir()} | ||
|
||
pemCert := []byte(certWithoutOCSPServer) | ||
cert, err := makeCertificate(pemCert, []byte(privateKey)) | ||
t.Run("disabled", func(t *testing.T) { | ||
cert := mustMakeCertificate(t, certWithOCSPServer, certKey) | ||
config := OCSPConfig{DisableStapling: true} | ||
err := stapleOCSP(ctx, config, storage, &cert, nil) | ||
if err != nil { | ||
t.Error("unexpected error:", err) | ||
} else if cert.Certificate.OCSPStaple != nil { | ||
t.Error("unexpected OCSP staple") | ||
} | ||
}) | ||
t.Run("no OCSP server", func(t *testing.T) { | ||
cert := mustMakeCertificate(t, certWithoutOCSPServer, certKey) | ||
err := stapleOCSP(ctx, OCSPConfig{}, storage, &cert, nil) | ||
if !errors.Is(err, ErrNoOCSPServerSpecified) { | ||
t.Error("expected ErrNoOCSPServerSpecified in error", err) | ||
} | ||
}) | ||
|
||
// Start an OCSP responder test server. | ||
responses := make(map[string][]byte) | ||
responder := startOCSPResponder(t, responses) | ||
t.Cleanup(responder.Close) | ||
|
||
ca := mustMakeCertificate(t, caCert, caKey) | ||
|
||
// The certWithOCSPServer certificate has a bogus ocsp.example.com endpoint. | ||
// Use the ResponderOverrides option to point to the test server instead. | ||
config := OCSPConfig{ | ||
ResponderOverrides: map[string]string{ | ||
"ocsp.example.com": responder.URL, | ||
}, | ||
} | ||
|
||
t.Run("ok", func(t *testing.T) { | ||
cert := mustMakeCertificate(t, certWithOCSPServer, certKey) | ||
tpl := ocsp.Response{ | ||
Status: ocsp.Good, | ||
SerialNumber: cert.Leaf.SerialNumber, | ||
} | ||
r, err := ocsp.CreateResponse( | ||
ca.Leaf, ca.Leaf, tpl, ca.PrivateKey.(crypto.Signer)) | ||
if err != nil { | ||
t.Fatal("couldn't create OCSP response", err) | ||
} | ||
responses[cert.Leaf.SerialNumber.String()] = r | ||
|
||
bundle := []byte(certWithOCSPServer + "\n" + caCert) | ||
err = stapleOCSP(ctx, config, storage, &cert, bundle) | ||
if err != nil { | ||
t.Error("unexpected error:", err) | ||
} else if !bytes.Equal(cert.Certificate.OCSPStaple, r) { | ||
t.Error("expected OCSP response to be stapled to certificate") | ||
} | ||
}) | ||
t.Run("revoked", func(t *testing.T) { | ||
cert := mustMakeCertificate(t, certWithOCSPServer, certKey) | ||
tpl := ocsp.Response{ | ||
Status: ocsp.Revoked, | ||
SerialNumber: cert.Leaf.SerialNumber, | ||
} | ||
r, err := ocsp.CreateResponse( | ||
ca.Leaf, ca.Leaf, tpl, ca.PrivateKey.(crypto.Signer)) | ||
if err != nil { | ||
t.Fatal("couldn't create OCSP response", err) | ||
} | ||
responses[cert.Leaf.SerialNumber.String()] = r | ||
|
||
bundle := []byte(certWithOCSPServer + "\n" + caCert) | ||
err = stapleOCSP(ctx, config, storage, &cert, bundle) | ||
if err != nil { | ||
t.Error("unexpected error:", err) | ||
} else if cert.Certificate.OCSPStaple != nil { | ||
t.Error("revoked OCSP response should not be stapled") | ||
} | ||
}) | ||
t.Run("no issuing cert", func(t *testing.T) { | ||
cert := mustMakeCertificate(t, certWithOCSPServer, certKey) | ||
err := stapleOCSP(ctx, config, storage, &cert, nil) | ||
expected := "no OCSP stapling for [ocsp test certificate]: " + | ||
"no URL to issuing certificate" | ||
if err == nil || err.Error() != expected { | ||
t.Errorf("expected error %q but got %q", expected, err) | ||
} | ||
}) | ||
} | ||
|
||
func mustMakeCertificate(t *testing.T, cert, key string) Certificate { | ||
t.Helper() | ||
c, err := makeCertificate([]byte(cert), []byte(key)) | ||
if err != nil { | ||
t.Fatal("couldn't make certificate:", err) | ||
} | ||
return c | ||
} | ||
|
||
err = stapleOCSP(context.Background(), config, storage, &cert, pemCert) | ||
if !errors.Is(err, ErrNoOCSPServerSpecified) { | ||
t.Error("expected ErrOCSPServerNotSpecified in error", err) | ||
func startOCSPResponder( | ||
t *testing.T, responses map[string][]byte, | ||
) *httptest.Server { | ||
h := func(w http.ResponseWriter, r *http.Request) { | ||
ct := r.Header.Get("Content-Type") | ||
if ct != "application/ocsp-request" { | ||
t.Errorf("unexpected request Content-Type %q", ct) | ||
} | ||
b, _ := io.ReadAll(r.Body) | ||
request, err := ocsp.ParseRequest(b) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
w.Header().Set("Content-Type", "application/ocsp-response") | ||
w.Write(responses[request.SerialNumber.String()]) | ||
} | ||
return httptest.NewServer(http.HandlerFunc(h)) | ||
} |