362 lines
12 KiB
Go
362 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
|
|
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
|
|
"github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
|
|
"github.com/letsencrypt/boulder/cmd"
|
|
|
|
"github.com/letsencrypt/boulder/core"
|
|
"github.com/letsencrypt/boulder/mocks"
|
|
"github.com/letsencrypt/boulder/sa"
|
|
"github.com/letsencrypt/boulder/sa/satest"
|
|
"github.com/letsencrypt/boulder/test"
|
|
"github.com/letsencrypt/boulder/test/vars"
|
|
)
|
|
|
|
type mockCA struct{}
|
|
|
|
func (ca *mockCA) IssueCertificate(csr x509.CertificateRequest, regID int64) (core.Certificate, error) {
|
|
return core.Certificate{}, nil
|
|
}
|
|
|
|
func (ca *mockCA) GenerateOCSP(xferObj core.OCSPSigningRequest) (ocsp []byte, err error) {
|
|
ocsp = []byte{1, 2, 3}
|
|
return
|
|
}
|
|
|
|
type mockPub struct {
|
|
sa core.StorageAuthority
|
|
}
|
|
|
|
func (p *mockPub) SubmitToCT(_ []byte) error {
|
|
return p.sa.AddSCTReceipt(core.SignedCertificateTimestamp{
|
|
SCTVersion: 0,
|
|
LogID: "id",
|
|
Timestamp: 0,
|
|
Extensions: []byte{},
|
|
Signature: []byte{0},
|
|
CertificateSerial: "00",
|
|
})
|
|
}
|
|
|
|
var log = mocks.UseMockLog()
|
|
|
|
func setup(t *testing.T) (*OCSPUpdater, core.StorageAuthority, *gorp.DbMap, clock.FakeClock, func()) {
|
|
dbMap, err := sa.NewDbMap(vars.DBConnSA)
|
|
test.AssertNotError(t, err, "Failed to create dbMap")
|
|
|
|
fc := clock.NewFake()
|
|
fc.Add(1 * time.Hour)
|
|
|
|
sa, err := sa.NewSQLStorageAuthority(dbMap, fc)
|
|
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(parsedCert.Raw, reg.ID)
|
|
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
|
|
|
|
status, err := sa.GetCertificateStatus(core.SerialToString(parsedCert.SerialNumber))
|
|
test.AssertNotError(t, err, "Couldn't get the core.CertificateStatus from the database")
|
|
|
|
meta, err := updater.generateResponse(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(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(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(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(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)
|
|
|
|
updater.generateOCSPResponses(certs)
|
|
|
|
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(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(core.SerialToString(parsedCert.SerialNumber))
|
|
test.AssertNotError(t, err, "Couldn't get the core.Certificate from the database")
|
|
|
|
meta, err := updater.generateResponse(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(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(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(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(parsedCert.Raw, reg.ID)
|
|
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
|
|
|
|
prev := fc.Now().Add(-time.Hour)
|
|
updater.newCertificateTick(10)
|
|
|
|
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(parsedCert.Raw, reg.ID)
|
|
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
|
|
|
|
updater.ocspMinTimeToExpiry = 1 * time.Hour
|
|
updater.oldOCSPResponsesTick(10)
|
|
|
|
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, _, _, 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(parsedCert.Raw, reg.ID)
|
|
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
|
|
|
|
updater.numLogs = 1
|
|
updater.oldestIssuedSCT = 1 * time.Hour
|
|
updater.missingReceiptsTick(10)
|
|
|
|
count, err := updater.getNumberOfReceipts("00")
|
|
test.AssertNotError(t, err, "Couldn't get number of receipts")
|
|
test.AssertEquals(t, count, 1)
|
|
}
|
|
|
|
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(parsedCert.Raw, reg.ID)
|
|
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
|
|
|
|
err = sa.MarkCertificateRevoked(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)
|
|
|
|
updater.revokedCertificatesTick(10)
|
|
|
|
status, err := sa.GetCertificateStatus(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(parsedCert.Raw, reg.ID)
|
|
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
|
|
|
|
status, err := sa.GetCertificateStatus(core.SerialToString(parsedCert.SerialNumber))
|
|
test.AssertNotError(t, err, "Failed to get certificate status")
|
|
|
|
err = sa.MarkCertificateRevoked(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(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(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(_ int) error { return core.ServiceUnavailableError("sad HSM") },
|
|
}
|
|
|
|
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(_ int) error { return nil }
|
|
start = l.clk.Now()
|
|
l.tick()
|
|
test.AssertEquals(t, l.failures, 0)
|
|
test.AssertEquals(t, l.clk.Now(), start)
|
|
}
|