2023-07-18 11:42:48 +10:00
|
|
|
package certmagic
|
|
|
|
|
|
|
|
import (
|
2023-11-29 14:26:51 +10:00
|
|
|
"bytes"
|
2023-07-18 11:42:48 +10:00
|
|
|
"context"
|
2023-11-29 14:26:51 +10:00
|
|
|
"crypto"
|
2023-07-18 11:42:48 +10:00
|
|
|
"errors"
|
2023-11-29 14:26:51 +10:00
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2023-07-18 11:42:48 +10:00
|
|
|
"testing"
|
2023-11-29 14:26:51 +10:00
|
|
|
|
|
|
|
"golang.org/x/crypto/ocsp"
|
2023-07-18 11:42:48 +10:00
|
|
|
)
|
|
|
|
|
2023-11-29 14:26:51 +10:00
|
|
|
const certWithOCSPServer = `-----BEGIN CERTIFICATE-----
|
|
|
|
MIIBgjCCASegAwIBAgICIAAwCgYIKoZIzj0EAwIwEjEQMA4GA1UEAxMHVGVzdCBD
|
|
|
|
QTAeFw0yMzAxMDExMjAwMDBaFw0yMzAyMDExMjAwMDBaMCAxHjAcBgNVBAMTFU9D
|
|
|
|
U1AgVGVzdCBDZXJ0aWZpY2F0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIoe
|
|
|
|
I/bjo34qony8LdRJD+Jhuk8/S8YHXRHl6rH9t5VFCFtX8lIPN/Ll1zCrQ2KB3Wlb
|
|
|
|
fxSgiQyLrCpZyrdhVPSjXzBdMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAU+Eo3
|
|
|
|
5sST4LRrwS4dueIdGBZ5d7IwLAYIKwYBBQUHAQEEIDAeMBwGCCsGAQUFBzABhhBv
|
|
|
|
Y3NwLmV4YW1wbGUuY29tMAoGCCqGSM49BAMCA0kAMEYCIQDg94xY/+/VepESdvTT
|
|
|
|
ykCwiWOS2aCpjyryrKpwMKkR0AIhAPc/+ZEz4W10OENxC1t+NUTvS8JbEGOwulkZ
|
|
|
|
z9yfaLuD
|
|
|
|
-----END CERTIFICATE-----`
|
|
|
|
|
2023-07-18 11:42:48 +10:00
|
|
|
const certWithoutOCSPServer = `-----BEGIN CERTIFICATE-----
|
2023-11-29 14:26:51 +10:00
|
|
|
MIIBUzCB+aADAgECAgIgADAKBggqhkjOPQQDAjASMRAwDgYDVQQDEwdUZXN0IENB
|
|
|
|
MB4XDTIzMDEwMTEyMDAwMFoXDTIzMDIwMTEyMDAwMFowIDEeMBwGA1UEAxMVT0NT
|
|
|
|
UCBUZXN0IENlcnRpZmljYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEih4j
|
|
|
|
9uOjfiqifLwt1EkP4mG6Tz9LxgddEeXqsf23lUUIW1fyUg838uXXMKtDYoHdaVt/
|
|
|
|
FKCJDIusKlnKt2FU9KMxMC8wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBT4Sjfm
|
|
|
|
xJPgtGvBLh254h0YFnl3sjAKBggqhkjOPQQDAgNJADBGAiEA3rWetLGblfSuNZKf
|
|
|
|
5CpZxhj3A0BjEocEh+2P+nAgIdUCIQDIgptabR1qTLQaF2u0hJsEX2IKuIUvYWH3
|
|
|
|
6Lb92+zIHg==
|
2023-07-18 11:42:48 +10:00
|
|
|
-----END CERTIFICATE-----`
|
|
|
|
|
2023-11-29 14:26:51 +10:00
|
|
|
// 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-----
|
2023-07-18 11:42:48 +10:00
|
|
|
MHcCAQEEIDJ59ptjq3MzILH4zn5IKoH1sYn+zrUeq2kD8+DD2x+OoAoGCCqGSM49
|
|
|
|
AwEHoUQDQgAEnSnsUqK/2nmswzOe1B4QF6wpCcVHhdgVn9JWWAkcXN39BnaxLFb4
|
|
|
|
yVvPvxNOngMwwhknTP9PDb7p9Z2+gJgJbQ==
|
|
|
|
-----END EC PRIVATE KEY-----`
|
|
|
|
|
2023-11-29 14:26:51 +10:00
|
|
|
func TestStapleOCSP(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
2023-07-18 11:42:48 +10:00
|
|
|
storage := &FileStorage{Path: t.TempDir()}
|
|
|
|
|
2023-11-29 14:26:51 +10:00
|
|
|
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))
|
2023-07-18 11:42:48 +10:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal("couldn't make certificate:", err)
|
|
|
|
}
|
2023-11-29 14:26:51 +10:00
|
|
|
return c
|
|
|
|
}
|
2023-07-18 11:42:48 +10:00
|
|
|
|
2023-11-29 14:26:51 +10:00
|
|
|
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()])
|
2023-07-18 11:42:48 +10:00
|
|
|
}
|
2023-11-29 14:26:51 +10:00
|
|
|
return httptest.NewServer(http.HandlerFunc(h))
|
2023-07-18 11:42:48 +10:00
|
|
|
}
|