boulder/cmd/ocsp-updater/main_test.go

391 lines
14 KiB
Go

package main
import (
"crypto/x509"
"errors"
"testing"
"time"
"golang.org/x/net/context"
"github.com/cactus/go-statsd-client/statsd"
"github.com/jmhodges/clock"
"gopkg.in/gorp.v1"
"github.com/letsencrypt/boulder/cmd"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/sa"
"github.com/letsencrypt/boulder/sa/satest"
"github.com/letsencrypt/boulder/test"
"github.com/letsencrypt/boulder/test/vars"
)
var ctx = context.Background()
type mockCA struct{}
func (ca *mockCA) IssueCertificate(_ context.Context, csr x509.CertificateRequest, regID int64) (core.Certificate, error) {
return core.Certificate{}, nil
}
func (ca *mockCA) GenerateOCSP(_ context.Context, xferObj core.OCSPSigningRequest) (ocsp []byte, err error) {
ocsp = []byte{1, 2, 3}
return
}
type mockPub struct {
sa core.StorageAuthority
}
func (p *mockPub) SubmitToCT(_ context.Context, _ []byte) error {
sct := core.SignedCertificateTimestamp{
SCTVersion: 0,
LogID: "id",
Timestamp: 0,
Extensions: []byte{},
Signature: []byte{0},
CertificateSerial: "00",
}
err := p.sa.AddSCTReceipt(ctx, sct)
if err != nil {
return err
}
sct.LogID = "another-id"
return p.sa.AddSCTReceipt(ctx, sct)
}
var log = blog.UseMock()
func setup(t *testing.T) (*OCSPUpdater, core.StorageAuthority, *gorp.DbMap, clock.FakeClock, func()) {
dbMap, err := sa.NewDbMap(vars.DBConnSA, 0)
test.AssertNotError(t, err, "Failed to create dbMap")
sa.SetSQLDebug(dbMap, log)
fc := clock.NewFake()
fc.Add(1 * time.Hour)
sa, err := sa.NewSQLStorageAuthority(dbMap, fc, log)
test.AssertNotError(t, err, "Failed to create SA")
cleanUp := test.ResetSATestDatabase(t)
stats, _ := statsd.NewNoopClient(nil)
updater, err := newUpdater(
stats,
fc,
dbMap,
&mockCA{},
&mockPub{sa},
sa,
cmd.OCSPUpdaterConfig{
NewCertificateBatchSize: 1,
OldOCSPBatchSize: 1,
MissingSCTBatchSize: 1,
NewCertificateWindow: cmd.ConfigDuration{Duration: time.Second},
OldOCSPWindow: cmd.ConfigDuration{Duration: time.Second},
MissingSCTWindow: cmd.ConfigDuration{Duration: time.Second},
},
0,
"",
)
return updater, sa, dbMap, fc, cleanUp
}
func TestGenerateAndStoreOCSPResponse(t *testing.T) {
updater, sa, _, _, cleanUp := setup(t)
defer cleanUp()
reg := satest.CreateWorkingRegistration(t, sa)
parsedCert, err := core.LoadCert("test-cert.pem")
test.AssertNotError(t, err, "Couldn't read test certificate")
_, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID)
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
status, err := sa.GetCertificateStatus(ctx, core.SerialToString(parsedCert.SerialNumber))
test.AssertNotError(t, err, "Couldn't get the core.CertificateStatus from the database")
meta, err := updater.generateResponse(ctx, status)
test.AssertNotError(t, err, "Couldn't generate OCSP response")
err = updater.storeResponse(meta)
test.AssertNotError(t, err, "Couldn't store certificate status")
secondMeta, err := updater.generateRevokedResponse(ctx, status)
test.AssertNotError(t, err, "Couldn't generate revoked OCSP response")
err = updater.storeResponse(secondMeta)
test.AssertNotError(t, err, "Couldn't store certificate status")
newStatus, err := sa.GetCertificateStatus(ctx, status.Serial)
test.AssertNotError(t, err, "Couldn't retrieve certificate status")
test.AssertByteEquals(t, meta.OCSPResponse, newStatus.OCSPResponse)
}
func TestGenerateOCSPResponses(t *testing.T) {
updater, sa, _, fc, cleanUp := setup(t)
defer cleanUp()
reg := satest.CreateWorkingRegistration(t, sa)
parsedCert, err := core.LoadCert("test-cert.pem")
test.AssertNotError(t, err, "Couldn't read test certificate")
_, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID)
test.AssertNotError(t, err, "Couldn't add test-cert.pem")
parsedCert, err = core.LoadCert("test-cert-b.pem")
test.AssertNotError(t, err, "Couldn't read test certificate")
_, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID)
test.AssertNotError(t, err, "Couldn't add test-cert-b.pem")
earliest := fc.Now().Add(-time.Hour)
certs, err := updater.findStaleOCSPResponses(earliest, 10)
test.AssertNotError(t, err, "Couldn't find stale responses")
test.AssertEquals(t, len(certs), 2)
err = updater.generateOCSPResponses(ctx, certs)
test.AssertNotError(t, err, "Couldn't generate OCSP responses")
certs, err = updater.findStaleOCSPResponses(earliest, 10)
test.AssertNotError(t, err, "Failed to find stale responses")
test.AssertEquals(t, len(certs), 0)
}
func TestFindStaleOCSPResponses(t *testing.T) {
updater, sa, _, fc, cleanUp := setup(t)
defer cleanUp()
reg := satest.CreateWorkingRegistration(t, sa)
parsedCert, err := core.LoadCert("test-cert.pem")
test.AssertNotError(t, err, "Couldn't read test certificate")
_, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID)
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
earliest := fc.Now().Add(-time.Hour)
certs, err := updater.findStaleOCSPResponses(earliest, 10)
test.AssertNotError(t, err, "Couldn't find certificate")
test.AssertEquals(t, len(certs), 1)
status, err := sa.GetCertificateStatus(ctx, core.SerialToString(parsedCert.SerialNumber))
test.AssertNotError(t, err, "Couldn't get the core.Certificate from the database")
meta, err := updater.generateResponse(ctx, status)
test.AssertNotError(t, err, "Couldn't generate OCSP response")
err = updater.storeResponse(meta)
test.AssertNotError(t, err, "Couldn't store OCSP response")
certs, err = updater.findStaleOCSPResponses(earliest, 10)
test.AssertNotError(t, err, "Failed to find stale responses")
test.AssertEquals(t, len(certs), 0)
}
func TestGetCertificatesWithMissingResponses(t *testing.T) {
updater, sa, _, _, cleanUp := setup(t)
defer cleanUp()
reg := satest.CreateWorkingRegistration(t, sa)
cert, err := core.LoadCert("test-cert.pem")
test.AssertNotError(t, err, "Couldn't read test certificate")
_, err = sa.AddCertificate(ctx, cert.Raw, reg.ID)
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
statuses, err := updater.getCertificatesWithMissingResponses(10)
test.AssertNotError(t, err, "Couldn't get status")
test.AssertEquals(t, len(statuses), 1)
}
func TestFindRevokedCertificatesToUpdate(t *testing.T) {
updater, sa, _, _, cleanUp := setup(t)
defer cleanUp()
reg := satest.CreateWorkingRegistration(t, sa)
cert, err := core.LoadCert("test-cert.pem")
test.AssertNotError(t, err, "Couldn't read test certificate")
_, err = sa.AddCertificate(ctx, cert.Raw, reg.ID)
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
statuses, err := updater.findRevokedCertificatesToUpdate(10)
test.AssertNotError(t, err, "Failed to find revoked certificates")
test.AssertEquals(t, len(statuses), 0)
err = sa.MarkCertificateRevoked(ctx, core.SerialToString(cert.SerialNumber), core.RevocationCode(1))
test.AssertNotError(t, err, "Failed to revoke certificate")
statuses, err = updater.findRevokedCertificatesToUpdate(10)
test.AssertNotError(t, err, "Failed to find revoked certificates")
test.AssertEquals(t, len(statuses), 1)
}
func TestNewCertificateTick(t *testing.T) {
updater, sa, _, fc, cleanUp := setup(t)
defer cleanUp()
reg := satest.CreateWorkingRegistration(t, sa)
parsedCert, err := core.LoadCert("test-cert.pem")
test.AssertNotError(t, err, "Couldn't read test certificate")
_, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID)
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
prev := fc.Now().Add(-time.Hour)
err = updater.newCertificateTick(ctx, 10)
test.AssertNotError(t, err, "Couldn't run newCertificateTick")
certs, err := updater.findStaleOCSPResponses(prev, 10)
test.AssertNotError(t, err, "Failed to find stale responses")
test.AssertEquals(t, len(certs), 0)
}
func TestOldOCSPResponsesTick(t *testing.T) {
updater, sa, _, fc, cleanUp := setup(t)
defer cleanUp()
reg := satest.CreateWorkingRegistration(t, sa)
parsedCert, err := core.LoadCert("test-cert.pem")
test.AssertNotError(t, err, "Couldn't read test certificate")
_, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID)
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
updater.ocspMinTimeToExpiry = 1 * time.Hour
err = updater.oldOCSPResponsesTick(ctx, 10)
test.AssertNotError(t, err, "Couldn't run oldOCSPResponsesTick")
certs, err := updater.findStaleOCSPResponses(fc.Now().Add(-updater.ocspMinTimeToExpiry), 10)
test.AssertNotError(t, err, "Failed to find stale responses")
test.AssertEquals(t, len(certs), 0)
}
func TestMissingReceiptsTick(t *testing.T) {
updater, sa, _, fc, cleanUp := setup(t)
defer cleanUp()
reg := satest.CreateWorkingRegistration(t, sa)
parsedCert, err := core.LoadCert("test-cert.pem")
test.AssertNotError(t, err, "Couldn't read test certificate")
fc.Set(parsedCert.NotBefore.Add(time.Minute))
_, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID)
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
updater.numLogs = 1
updater.oldestIssuedSCT = 2 * time.Hour
serials, err := updater.getSerialsIssuedSince(fc.Now().Add(-2*time.Hour), 1)
test.AssertNotError(t, err, "Failed to retrieve serials")
test.AssertEquals(t, len(serials), 1)
err = updater.missingReceiptsTick(ctx, 5)
test.AssertNotError(t, err, "Failed to run missingReceiptsTick")
count, err := updater.getNumberOfReceipts("00")
test.AssertNotError(t, err, "Couldn't get number of receipts")
test.AssertEquals(t, count, 2)
// make sure we don't spin forever after reducing the
// number of logs we submit to
updater.numLogs = 1
err = updater.missingReceiptsTick(ctx, 10)
test.AssertNotError(t, err, "Failed to run missingReceiptsTick")
}
func TestRevokedCertificatesTick(t *testing.T) {
updater, sa, _, _, cleanUp := setup(t)
defer cleanUp()
reg := satest.CreateWorkingRegistration(t, sa)
parsedCert, err := core.LoadCert("test-cert.pem")
test.AssertNotError(t, err, "Couldn't read test certificate")
_, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID)
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
err = sa.MarkCertificateRevoked(ctx, core.SerialToString(parsedCert.SerialNumber), core.RevocationCode(1))
test.AssertNotError(t, err, "Failed to revoke certificate")
statuses, err := updater.findRevokedCertificatesToUpdate(10)
test.AssertNotError(t, err, "Failed to find revoked certificates")
test.AssertEquals(t, len(statuses), 1)
err = updater.revokedCertificatesTick(ctx, 10)
test.AssertNotError(t, err, "Failed to run revokedCertificatesTick")
status, err := sa.GetCertificateStatus(ctx, core.SerialToString(parsedCert.SerialNumber))
test.AssertNotError(t, err, "Failed to get certificate status")
test.AssertEquals(t, status.Status, core.OCSPStatusRevoked)
test.Assert(t, len(status.OCSPResponse) != 0, "Certificate status doesn't contain OCSP response")
}
func TestStoreResponseGuard(t *testing.T) {
updater, sa, _, _, cleanUp := setup(t)
defer cleanUp()
reg := satest.CreateWorkingRegistration(t, sa)
parsedCert, err := core.LoadCert("test-cert.pem")
test.AssertNotError(t, err, "Couldn't read test certificate")
_, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID)
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
status, err := sa.GetCertificateStatus(ctx, core.SerialToString(parsedCert.SerialNumber))
test.AssertNotError(t, err, "Failed to get certificate status")
err = sa.MarkCertificateRevoked(ctx, core.SerialToString(parsedCert.SerialNumber), 0)
test.AssertNotError(t, err, "Failed to revoked certificate")
// Attempt to update OCSP response where status.Status is good but stored status
// is revoked, this should fail silently
status.OCSPResponse = []byte{0, 1, 1}
err = updater.storeResponse(&status)
test.AssertNotError(t, err, "Failed to update certificate status")
// Make sure the OCSP response hasn't actually changed
unchangedStatus, err := sa.GetCertificateStatus(ctx, core.SerialToString(parsedCert.SerialNumber))
test.AssertNotError(t, err, "Failed to get certificate status")
test.AssertEquals(t, len(unchangedStatus.OCSPResponse), 0)
// Changing the status to the stored status should allow the update to occur
status.Status = core.OCSPStatusRevoked
err = updater.storeResponse(&status)
test.AssertNotError(t, err, "Failed to updated certificate status")
// Make sure the OCSP response has been updated
changedStatus, err := sa.GetCertificateStatus(ctx, core.SerialToString(parsedCert.SerialNumber))
test.AssertNotError(t, err, "Failed to get certificate status")
test.AssertEquals(t, len(changedStatus.OCSPResponse), 3)
}
func TestLoopTickBackoff(t *testing.T) {
fc := clock.NewFake()
stats, _ := statsd.NewNoopClient(nil)
l := looper{
clk: fc,
stats: stats,
failureBackoffFactor: 1.5,
failureBackoffMax: 10 * time.Minute,
tickDur: time.Minute,
tickFunc: func(context.Context, int) error { return errors.New("baddie") },
}
start := l.clk.Now()
l.tick()
// Expected to sleep for 1m
backoff := float64(60000000000)
maxJittered := backoff * 1.2
test.AssertBetween(t, l.clk.Now().Sub(start).Nanoseconds(), int64(backoff), int64(maxJittered))
start = l.clk.Now()
l.tick()
// Expected to sleep for 1m30s
backoff = 90000000000
maxJittered = backoff * 1.2
test.AssertBetween(t, l.clk.Now().Sub(start).Nanoseconds(), int64(backoff), int64(maxJittered))
l.failures = 6
start = l.clk.Now()
l.tick()
// Expected to sleep for 11m23.4375s, should be truncated to 10m
backoff = 600000000000
maxJittered = backoff * 1.2
test.AssertBetween(t, l.clk.Now().Sub(start).Nanoseconds(), int64(backoff), int64(maxJittered))
l.tickFunc = func(context.Context, int) error { return nil }
start = l.clk.Now()
l.tick()
test.AssertEquals(t, l.failures, 0)
test.AssertEquals(t, l.clk.Now(), start)
}