certmagic/ocsp_test.go

183 lines
6.2 KiB
Go
Raw Permalink Normal View History

package certmagic
import (
"bytes"
"context"
"crypto"
"errors"
"io"
"net/http"
"net/http/httptest"
"testing"
"golang.org/x/crypto/ocsp"
)
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-----
MIIBUzCB+aADAgECAgIgADAKBggqhkjOPQQDAjASMRAwDgYDVQQDEwdUZXN0IENB
MB4XDTIzMDEwMTEyMDAwMFoXDTIzMDIwMTEyMDAwMFowIDEeMBwGA1UEAxMVT0NT
UCBUZXN0IENlcnRpZmljYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEih4j
9uOjfiqifLwt1EkP4mG6Tz9LxgddEeXqsf23lUUIW1fyUg838uXXMKtDYoHdaVt/
FKCJDIusKlnKt2FU9KMxMC8wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBT4Sjfm
xJPgtGvBLh254h0YFnl3sjAKBggqhkjOPQQDAgNJADBGAiEA3rWetLGblfSuNZKf
5CpZxhj3A0BjEocEh+2P+nAgIdUCIQDIgptabR1qTLQaF2u0hJsEX2IKuIUvYWH3
6Lb92+zIHg==
-----END CERTIFICATE-----`
// 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 TestStapleOCSP(t *testing.T) {
ctx := context.Background()
storage := &FileStorage{Path: t.TempDir()}
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
}
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))
}