boulder/ca/ocsp_test.go

245 lines
8.2 KiB
Go

package ca
import (
"context"
"crypto/x509"
"encoding/hex"
"testing"
"time"
"golang.org/x/crypto/ocsp"
capb "github.com/letsencrypt/boulder/ca/proto"
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/issuance"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/metrics"
"github.com/letsencrypt/boulder/test"
)
func TestImplementationOCSP(t *testing.T) {
t.Parallel()
test.AssertImplementsGRPCServer(t, &ocspImpl{}, capb.UnimplementedOCSPGeneratorServer{})
}
func serial(t *testing.T) []byte {
serial, err := hex.DecodeString("aabbccddeeffaabbccddeeff000102030405")
if err != nil {
t.Fatal(err)
}
return serial
}
func TestOCSP(t *testing.T) {
t.Parallel()
testCtx := setup(t)
ca, err := NewCertificateAuthorityImpl(
&mockSA{},
testCtx.pa,
testCtx.boulderIssuers,
testCtx.defaultCertProfileName,
testCtx.certProfiles,
testCtx.lints,
nil,
testCtx.certExpiry,
testCtx.certBackdate,
testCtx.serialPrefix,
testCtx.maxNames,
testCtx.keyPolicy,
testCtx.logger,
testCtx.stats,
testCtx.signatureCount,
testCtx.signErrorCount,
testCtx.fc)
test.AssertNotError(t, err, "Failed to create CA")
ocspi := testCtx.ocsp
// Issue a certificate from an RSA issuer, request OCSP from the same issuer,
// and make sure it works.
rsaCertPB, err := ca.IssuePrecertificate(ctx, &capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID})
test.AssertNotError(t, err, "Failed to issue certificate")
rsaCert, err := x509.ParseCertificate(rsaCertPB.DER)
test.AssertNotError(t, err, "Failed to parse rsaCert")
rsaIssuerID := issuance.IssuerNameID(rsaCert)
rsaOCSPPB, err := ocspi.GenerateOCSP(ctx, &capb.GenerateOCSPRequest{
Serial: core.SerialToString(rsaCert.SerialNumber),
IssuerID: int64(rsaIssuerID),
Status: string(core.OCSPStatusGood),
})
test.AssertNotError(t, err, "Failed to generate OCSP")
rsaOCSP, err := ocsp.ParseResponse(rsaOCSPPB.Response, ca.issuers.byNameID[rsaIssuerID].Cert.Certificate)
test.AssertNotError(t, err, "Failed to parse / validate OCSP for rsaCert")
test.AssertEquals(t, rsaOCSP.Status, 0)
test.AssertEquals(t, rsaOCSP.RevocationReason, 0)
test.AssertEquals(t, rsaOCSP.SerialNumber.Cmp(rsaCert.SerialNumber), 0)
// Check that a different issuer cannot validate the OCSP response
_, err = ocsp.ParseResponse(rsaOCSPPB.Response, ca.issuers.byAlg[x509.ECDSA][0].Cert.Certificate)
test.AssertError(t, err, "Parsed / validated OCSP for rsaCert, but should not have")
// Issue a certificate from an ECDSA issuer, request OCSP from the same issuer,
// and make sure it works.
ecdsaCertPB, err := ca.IssuePrecertificate(ctx, &capb.IssueCertificateRequest{Csr: ECDSACSR, RegistrationID: arbitraryRegID})
test.AssertNotError(t, err, "Failed to issue certificate")
ecdsaCert, err := x509.ParseCertificate(ecdsaCertPB.DER)
test.AssertNotError(t, err, "Failed to parse ecdsaCert")
ecdsaIssuerID := issuance.IssuerNameID(ecdsaCert)
ecdsaOCSPPB, err := ocspi.GenerateOCSP(ctx, &capb.GenerateOCSPRequest{
Serial: core.SerialToString(ecdsaCert.SerialNumber),
IssuerID: int64(ecdsaIssuerID),
Status: string(core.OCSPStatusGood),
})
test.AssertNotError(t, err, "Failed to generate OCSP")
ecdsaOCSP, err := ocsp.ParseResponse(ecdsaOCSPPB.Response, ca.issuers.byNameID[ecdsaIssuerID].Cert.Certificate)
test.AssertNotError(t, err, "Failed to parse / validate OCSP for ecdsaCert")
test.AssertEquals(t, ecdsaOCSP.Status, 0)
test.AssertEquals(t, ecdsaOCSP.RevocationReason, 0)
test.AssertEquals(t, ecdsaOCSP.SerialNumber.Cmp(ecdsaCert.SerialNumber), 0)
// GenerateOCSP with a bad IssuerID should fail.
_, err = ocspi.GenerateOCSP(context.Background(), &capb.GenerateOCSPRequest{
Serial: core.SerialToString(rsaCert.SerialNumber),
IssuerID: int64(666),
Status: string(core.OCSPStatusGood),
})
test.AssertError(t, err, "GenerateOCSP didn't fail with invalid IssuerID")
// GenerateOCSP with a bad Serial should fail.
_, err = ocspi.GenerateOCSP(context.Background(), &capb.GenerateOCSPRequest{
Serial: "BADDECAF",
IssuerID: int64(rsaIssuerID),
Status: string(core.OCSPStatusGood),
})
test.AssertError(t, err, "GenerateOCSP didn't fail with invalid Serial")
// GenerateOCSP with a valid-but-nonexistent Serial should *not* fail.
_, err = ocspi.GenerateOCSP(context.Background(), &capb.GenerateOCSPRequest{
Serial: "03DEADBEEFBADDECAFFADEFACECAFE30",
IssuerID: int64(rsaIssuerID),
Status: string(core.OCSPStatusGood),
})
test.AssertNotError(t, err, "GenerateOCSP failed with fake-but-valid Serial")
}
// Set up an ocspLogQueue with a very long period and a large maxLen,
// to ensure any buffered entries get flushed on `.stop()`.
func TestOcspLogFlushOnExit(t *testing.T) {
t.Parallel()
log := blog.NewMock()
stats := metrics.NoopRegisterer
queue := newOCSPLogQueue(4000, 10000*time.Millisecond, stats, log)
go queue.loop()
queue.enqueue(serial(t), time.Now(), ocsp.Good, ocsp.Unspecified)
queue.stop()
expected := []string{
"INFO: [AUDIT] OCSP signed: aabbccddeeffaabbccddeeff000102030405:_,",
}
test.AssertDeepEquals(t, log.GetAll(), expected)
}
// Ensure log lines are sent when they exceed maxLen.
func TestOcspFlushOnLength(t *testing.T) {
t.Parallel()
log := blog.NewMock()
stats := metrics.NoopRegisterer
queue := newOCSPLogQueue(100, 100*time.Millisecond, stats, log)
go queue.loop()
for range 5 {
queue.enqueue(serial(t), time.Now(), ocsp.Good, ocsp.Unspecified)
}
queue.stop()
expected := []string{
"INFO: [AUDIT] OCSP signed: aabbccddeeffaabbccddeeff000102030405:_,aabbccddeeffaabbccddeeff000102030405:_,",
"INFO: [AUDIT] OCSP signed: aabbccddeeffaabbccddeeff000102030405:_,aabbccddeeffaabbccddeeff000102030405:_,",
"INFO: [AUDIT] OCSP signed: aabbccddeeffaabbccddeeff000102030405:_,",
}
test.AssertDeepEquals(t, log.GetAll(), expected)
}
// Ensure log lines are sent after a timeout.
func TestOcspFlushOnTimeout(t *testing.T) {
t.Parallel()
log := blog.NewWaitingMock()
stats := metrics.NoopRegisterer
queue := newOCSPLogQueue(90000, 10*time.Millisecond, stats, log)
go queue.loop()
queue.enqueue(serial(t), time.Now(), ocsp.Good, ocsp.Unspecified)
expected := "INFO: [AUDIT] OCSP signed: aabbccddeeffaabbccddeeff000102030405:_,"
logLines, err := log.WaitForMatch("OCSP signed", 50*time.Millisecond)
test.AssertNotError(t, err, "error in mock log")
test.AssertDeepEquals(t, logLines, expected)
queue.stop()
}
// If the deadline passes and nothing has been logged, we should not log a blank line.
func TestOcspNoEmptyLines(t *testing.T) {
t.Parallel()
log := blog.NewMock()
stats := metrics.NoopRegisterer
queue := newOCSPLogQueue(90000, 10*time.Millisecond, stats, log)
go queue.loop()
time.Sleep(50 * time.Millisecond)
queue.stop()
test.AssertDeepEquals(t, log.GetAll(), []string{})
}
// If the maxLogLen is shorter than one entry, log everything immediately.
func TestOcspLogWhenMaxLogLenIsShort(t *testing.T) {
t.Parallel()
log := blog.NewMock()
stats := metrics.NoopRegisterer
queue := newOCSPLogQueue(3, 10000*time.Millisecond, stats, log)
go queue.loop()
queue.enqueue(serial(t), time.Now(), ocsp.Good, ocsp.Unspecified)
queue.stop()
expected := []string{
"INFO: [AUDIT] OCSP signed: aabbccddeeffaabbccddeeff000102030405:_,",
}
test.AssertDeepEquals(t, log.GetAll(), expected)
}
// Enqueueing entries after stop causes panic.
func TestOcspLogPanicsOnEnqueueAfterStop(t *testing.T) {
t.Parallel()
log := blog.NewMock()
stats := metrics.NoopRegisterer
queue := newOCSPLogQueue(4000, 10000*time.Millisecond, stats, log)
go queue.loop()
queue.stop()
defer func() {
if r := recover(); r == nil {
t.Errorf("The code did not panic")
}
}()
queue.enqueue(serial(t), time.Now(), ocsp.Good, ocsp.Unspecified)
}
// Ensure revoke reason gets set.
func TestOcspRevokeReasonIsSet(t *testing.T) {
t.Parallel()
log := blog.NewMock()
stats := metrics.NoopRegisterer
queue := newOCSPLogQueue(100, 100*time.Millisecond, stats, log)
go queue.loop()
queue.enqueue(serial(t), time.Now(), ocsp.Revoked, ocsp.KeyCompromise)
queue.enqueue(serial(t), time.Now(), ocsp.Revoked, ocsp.CACompromise)
queue.stop()
expected := []string{
"INFO: [AUDIT] OCSP signed: aabbccddeeffaabbccddeeff000102030405:1,aabbccddeeffaabbccddeeff000102030405:2,",
}
test.AssertDeepEquals(t, log.GetAll(), expected)
}