correct ca and sa revocation code and tests
The ca's TestRevoke was failing occasionally. The test was saying "has the certificate's OCSPLastUpdated been set to a time within the last second?" as a way to see if the revocation updated the OCSPLastUpdated. OCSPLastUpdated was not being set on revocation, but the test still passed most of the time. The test still passed most of the time because the creation of the certificate (which also sets the OCSPLastUpdated) has usually happened within the last second. So, even without revocation, the OCSPLastUpdated was set to something in the last second because the test is fast. Threading a clock.FakeClock through the CA induced the test to fail consistently. Debugging and threading a FakeClock through the SA caused changes in times reported but did not fix the test because the OCSPLastUpdated was simply not being updated. There were not tests for the sa.MarkCertificateRevoked API that was being called by ca.RevokeCertificate. Now the SA has tests for its MarkCertificateRevoked method. It uses a fake clock to ensure not just that OCSPLastUpdated is set correctly, but that RevokedDate is, as well. The test also checks for the CertificateStatus's status and RevocationCode changes. The SA and CA now use Clocks throughout instead of time.Now() allowing for more reliable and expansive testing in the future. The CA had to gain a public Clock field in order for the RA to use the CertificateAuthorityImpl struct without using its constructor function. Otherwise, the field would be nil and cause panics in the RA tests. The RA tests are similarly also panicking when the CAImpl attempts to log something with its private, nil-in-those-tests log field but we're getting "lucky" because the RA tests only cause the CAImpl to log when they are broken. There is a TODO there to make the CAImpl's constructor function take just what it needs to operate instead of taking large config objects and doing file IO and such. The Clk field should be made private and the log field filled in for the RA tests. Fixes #734.
This commit is contained in:
parent
8cfa7e5f32
commit
40d1c446d9
|
@ -15,6 +15,7 @@ import (
|
|||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
|
@ -52,6 +53,7 @@ type CertificateAuthorityImpl struct {
|
|||
SA core.StorageAuthority
|
||||
PA core.PolicyAuthority
|
||||
DB core.CertificateAuthorityDatabase
|
||||
Clk clock.Clock // TODO(jmhodges): should be private, like log
|
||||
log *blog.AuditLogger
|
||||
Prefix int // Prepended to the serial number
|
||||
ValidityPeriod time.Duration
|
||||
|
@ -66,7 +68,7 @@ type CertificateAuthorityImpl struct {
|
|||
// using CFSSL's authenticated signature scheme. A CA created in this way
|
||||
// issues for a single profile on the remote signer, which is indicated
|
||||
// by name in this constructor.
|
||||
func NewCertificateAuthorityImpl(cadb core.CertificateAuthorityDatabase, config cmd.CAConfig, issuerCert string) (*CertificateAuthorityImpl, error) {
|
||||
func NewCertificateAuthorityImpl(cadb core.CertificateAuthorityDatabase, config cmd.CAConfig, clk clock.Clock, issuerCert string) (*CertificateAuthorityImpl, error) {
|
||||
var ca *CertificateAuthorityImpl
|
||||
var err error
|
||||
logger := blog.GetAuditLogger()
|
||||
|
@ -125,6 +127,7 @@ func NewCertificateAuthorityImpl(cadb core.CertificateAuthorityDatabase, config
|
|||
profile: config.Profile,
|
||||
DB: cadb,
|
||||
Prefix: config.SerialPrefix,
|
||||
Clk: clk,
|
||||
log: logger,
|
||||
NotAfter: issuer.NotAfter,
|
||||
}
|
||||
|
@ -212,7 +215,7 @@ func (ca *CertificateAuthorityImpl) RevokeCertificate(serial string, reasonCode
|
|||
Certificate: cert,
|
||||
Status: string(core.OCSPStatusRevoked),
|
||||
Reason: int(reasonCode),
|
||||
RevokedAt: time.Now(),
|
||||
RevokedAt: ca.Clk.Now(),
|
||||
}
|
||||
ocspResponse, err := ca.OCSPSigner.Sign(signRequest)
|
||||
if err != nil {
|
||||
|
@ -292,7 +295,7 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
|
|||
}
|
||||
}
|
||||
|
||||
notAfter := time.Now().Add(ca.ValidityPeriod)
|
||||
notAfter := ca.Clk.Now().Add(ca.ValidityPeriod)
|
||||
|
||||
if ca.NotAfter.Before(notAfter) {
|
||||
// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
|
||||
cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
ocspConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp/config"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/mocks"
|
||||
"github.com/letsencrypt/boulder/policy"
|
||||
|
@ -112,6 +113,7 @@ type testCtx struct {
|
|||
caConfig cmd.CAConfig
|
||||
reg core.Registration
|
||||
pa core.PolicyAuthority
|
||||
fc clock.FakeClock
|
||||
cleanUp func()
|
||||
}
|
||||
|
||||
|
@ -121,7 +123,9 @@ func setup(t *testing.T) *testCtx {
|
|||
if err != nil {
|
||||
t.Fatalf("Failed to create dbMap: %s", err)
|
||||
}
|
||||
ssa, err := sa.NewSQLStorageAuthority(dbMap)
|
||||
fc := clock.NewFake()
|
||||
fc.Add(1 * time.Hour)
|
||||
ssa, err := sa.NewSQLStorageAuthority(dbMap, fc)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create SA: %s", err)
|
||||
}
|
||||
|
@ -188,7 +192,7 @@ func setup(t *testing.T) *testCtx {
|
|||
},
|
||||
},
|
||||
}
|
||||
return &testCtx{cadb, ssa, caConfig, reg, pa, cleanUp}
|
||||
return &testCtx{cadb, ssa, caConfig, reg, pa, fc, cleanUp}
|
||||
}
|
||||
|
||||
func TestFailNoSerial(t *testing.T) {
|
||||
|
@ -196,31 +200,33 @@ func TestFailNoSerial(t *testing.T) {
|
|||
defer ctx.cleanUp()
|
||||
|
||||
ctx.caConfig.SerialPrefix = 0
|
||||
_, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
_, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, ctx.fc, caCertFile)
|
||||
test.AssertError(t, err, "CA should have failed with no SerialPrefix")
|
||||
}
|
||||
|
||||
func TestRevoke(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
ca.PA = ctx.pa
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, ctx.fc, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
ca.MaxKeySize = 4096
|
||||
|
||||
csr, _ := x509.ParseCertificateRequest(CNandSANCSR)
|
||||
certObj, err := ca.IssueCertificate(*csr, ctx.reg.ID, FarFuture)
|
||||
test.AssertNotError(t, err, "Failed to sign certificate")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(certObj.DER)
|
||||
test.AssertNotError(t, err, "Certificate failed to parse")
|
||||
serialString := core.SerialToString(cert.SerialNumber)
|
||||
|
||||
beforeRevoke, err := ctx.sa.GetCertificateStatus(serialString)
|
||||
test.AssertNotError(t, err, "Failed to get cert status")
|
||||
|
||||
ctx.fc.Add(1 * time.Hour)
|
||||
|
||||
err = ca.RevokeCertificate(serialString, 0)
|
||||
test.AssertNotError(t, err, "Revocation failed")
|
||||
|
||||
|
@ -228,15 +234,22 @@ func TestRevoke(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Failed to get cert status")
|
||||
|
||||
test.AssertEquals(t, status.Status, core.OCSPStatusRevoked)
|
||||
secondAgo := time.Now().Add(-time.Second)
|
||||
test.Assert(t, status.OCSPLastUpdated.After(secondAgo),
|
||||
fmt.Sprintf("OCSP LastUpdated was more than a second old: %v", status.OCSPLastUpdated))
|
||||
|
||||
if !ctx.fc.Now().Equal(status.OCSPLastUpdated) {
|
||||
t.Errorf("OCSPLastUpdated, expected %s, got %s",
|
||||
ctx.fc.Now(),
|
||||
status.OCSPLastUpdated)
|
||||
}
|
||||
if !status.OCSPLastUpdated.After(beforeRevoke.OCSPLastUpdated) {
|
||||
t.Errorf("OCSPLastUpdated, before revocation: %s; after: %s", beforeRevoke.OCSPLastUpdated, status.OCSPLastUpdated)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIssueCertificate(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, ctx.fc, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
@ -313,7 +326,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
func TestRejectNoName(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, ctx.fc, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
@ -330,7 +343,7 @@ func TestRejectNoName(t *testing.T) {
|
|||
func TestRejectTooManyNames(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, ctx.fc, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
@ -344,7 +357,7 @@ func TestRejectTooManyNames(t *testing.T) {
|
|||
func TestDeduplication(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, ctx.fc, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
@ -374,7 +387,7 @@ func TestDeduplication(t *testing.T) {
|
|||
func TestRejectValidityTooLong(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, ctx.fc, caCertFile)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
@ -387,7 +400,7 @@ func TestRejectValidityTooLong(t *testing.T) {
|
|||
|
||||
// Test that the CA rejects CSRs that would expire after the intermediate cert
|
||||
csr, _ = x509.ParseCertificateRequest(NoCNCSR)
|
||||
ca.NotAfter = time.Now()
|
||||
ca.NotAfter = ctx.fc.Now()
|
||||
_, err = ca.IssueCertificate(*csr, 1, FarFuture)
|
||||
test.AssertEquals(t, err.Error(), "Cannot issue a certificate that expires after the intermediate certificate.")
|
||||
}
|
||||
|
@ -395,7 +408,7 @@ func TestRejectValidityTooLong(t *testing.T) {
|
|||
func TestShortKey(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, ctx.fc, caCertFile)
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
ca.MaxKeySize = 4096
|
||||
|
@ -409,7 +422,7 @@ func TestShortKey(t *testing.T) {
|
|||
func TestRejectBadAlgorithm(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caDB, ctx.caConfig, ctx.fc, caCertFile)
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
ca.MaxKeySize = 4096
|
||||
|
|
|
@ -7,6 +7,7 @@ package main
|
|||
|
||||
import (
|
||||
"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/ca"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
|
@ -43,7 +44,7 @@ func main() {
|
|||
pa, err := policy.NewPolicyAuthorityImpl(paDbMap, c.PA.EnforcePolicyWhitelist)
|
||||
cmd.FailOnError(err, "Couldn't create PA")
|
||||
|
||||
cai, err := ca.NewCertificateAuthorityImpl(cadb, c.CA, c.Common.IssuerCert)
|
||||
cai, err := ca.NewCertificateAuthorityImpl(cadb, c.CA, clock.Default(), c.Common.IssuerCert)
|
||||
cmd.FailOnError(err, "Failed to create CA impl")
|
||||
cai.MaxKeySize = c.Common.MaxKeySize
|
||||
cai.PA = pa
|
||||
|
|
|
@ -7,7 +7,7 @@ package main
|
|||
|
||||
import (
|
||||
"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/cmd"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/rpc"
|
||||
|
@ -34,7 +34,7 @@ func main() {
|
|||
dbMap, err := sa.NewDbMap(c.SA.DBConnect)
|
||||
cmd.FailOnError(err, "Couldn't connect to SA database")
|
||||
|
||||
sai, err := sa.NewSQLStorageAuthority(dbMap)
|
||||
sai, err := sa.NewSQLStorageAuthority(dbMap, clock.Default())
|
||||
cmd.FailOnError(err, "Failed to create SA impl")
|
||||
sai.SetSQLDebug(c.SQL.SQLDebug)
|
||||
|
||||
|
|
|
@ -74,14 +74,14 @@ type certChecker struct {
|
|||
clock clock.Clock
|
||||
}
|
||||
|
||||
func newChecker(saDbMap *gorp.DbMap, paDbMap *gorp.DbMap, enforceWhitelist bool) certChecker {
|
||||
func newChecker(saDbMap *gorp.DbMap, paDbMap *gorp.DbMap, clk clock.Clock, enforceWhitelist bool) certChecker {
|
||||
pa, err := policy.NewPolicyAuthorityImpl(paDbMap, enforceWhitelist)
|
||||
cmd.FailOnError(err, "Failed to create PA")
|
||||
c := certChecker{
|
||||
pa: pa,
|
||||
dbMap: saDbMap,
|
||||
certs: make(chan core.Certificate, batchSize),
|
||||
clock: clock.Default(),
|
||||
clock: clk,
|
||||
}
|
||||
c.issuedReport.Entries = make(map[string]reportEntry)
|
||||
return c
|
||||
|
@ -234,7 +234,7 @@ func main() {
|
|||
paDbMap, err := sa.NewDbMap(c.PA.DBConnect)
|
||||
cmd.FailOnError(err, "Could not connect to policy database")
|
||||
|
||||
checker := newChecker(saDbMap, paDbMap, c.PA.EnforcePolicyWhitelist)
|
||||
checker := newChecker(saDbMap, paDbMap, clock.Default(), c.PA.EnforcePolicyWhitelist)
|
||||
auditlogger.Info("# Getting certificates issued in the last 90 days")
|
||||
|
||||
// Since we grab certificates in batches we don't want this to block, when it
|
||||
|
|
|
@ -49,7 +49,7 @@ func BenchmarkCheckCert(b *testing.B) {
|
|||
os.Exit(1)
|
||||
}()
|
||||
|
||||
checker := newChecker(saDbMap, paDbMap, false)
|
||||
checker := newChecker(saDbMap, paDbMap, clock.Default(), false)
|
||||
testKey, _ := rsa.GenerateKey(rand.Reader, 1024)
|
||||
expiry := time.Now().AddDate(0, 0, 1)
|
||||
serial := big.NewInt(1337)
|
||||
|
@ -87,10 +87,10 @@ func TestCheckCert(t *testing.T) {
|
|||
}()
|
||||
|
||||
testKey, _ := rsa.GenerateKey(rand.Reader, 1024)
|
||||
checker := newChecker(saDbMap, paDbMap, false)
|
||||
fc := clock.NewFake()
|
||||
fc.Add(time.Hour * 24 * 90)
|
||||
checker.clock = fc
|
||||
|
||||
checker := newChecker(saDbMap, paDbMap, fc, false)
|
||||
|
||||
issued := checker.clock.Now().Add(-time.Hour * 24 * 45)
|
||||
goodExpiry := issued.Add(checkPeriod)
|
||||
|
@ -146,13 +146,16 @@ func TestGetAndProcessCerts(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Couldn't connect to database")
|
||||
paDbMap, err := sa.NewDbMap(paDbConnStr)
|
||||
test.AssertNotError(t, err, "Couldn't connect to policy database")
|
||||
checker := newChecker(saDbMap, paDbMap, false)
|
||||
sa, err := sa.NewSQLStorageAuthority(saDbMap)
|
||||
fc := clock.NewFake()
|
||||
|
||||
checker := newChecker(saDbMap, paDbMap, fc, false)
|
||||
sa, err := sa.NewSQLStorageAuthority(saDbMap, fc)
|
||||
test.AssertNotError(t, err, "Couldn't create SA to insert certificates")
|
||||
saCleanUp := test.ResetTestDatabase(t, saDbMap.Db)
|
||||
paCleanUp := test.ResetTestDatabase(t, paDbMap.Db)
|
||||
defer func() {
|
||||
saDbMap.TruncateTables()
|
||||
paDbMap.TruncateTables()
|
||||
test.AssertNotError(t, err, "Failed to truncate tables")
|
||||
saCleanUp()
|
||||
paCleanUp()
|
||||
}()
|
||||
|
||||
testKey, _ := rsa.GenerateKey(rand.Reader, 1024)
|
||||
|
|
|
@ -456,7 +456,8 @@ func setup(t *testing.T, nagTimes []time.Duration) *testCtx {
|
|||
if err != nil {
|
||||
t.Fatalf("Couldn't connect the database: %s", err)
|
||||
}
|
||||
ssa, err := sa.NewSQLStorageAuthority(dbMap)
|
||||
fc := clock.NewFake()
|
||||
ssa, err := sa.NewSQLStorageAuthority(dbMap, fc)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create SQLStorageAuthority: %s", err)
|
||||
}
|
||||
|
@ -464,7 +465,6 @@ func setup(t *testing.T, nagTimes []time.Duration) *testCtx {
|
|||
|
||||
stats, _ := statsd.NewNoopClient(nil)
|
||||
mc := &mockMail{}
|
||||
fc := clock.NewFake()
|
||||
|
||||
m := &mailer{
|
||||
log: blog.GetAuditLogger(),
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/local"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
|
||||
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
"github.com/letsencrypt/boulder/ca"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
|
@ -144,11 +145,13 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
|
|||
err = json.Unmarshal(ShortKeyJSON, &ShortKey)
|
||||
test.AssertNotError(t, err, "Failed to unmarshall JWK")
|
||||
|
||||
fc := clock.NewFake()
|
||||
|
||||
dbMap, err := sa.NewDbMap(saDBConnStr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create dbMap: %s", err)
|
||||
}
|
||||
ssa, err := sa.NewSQLStorageAuthority(dbMap)
|
||||
ssa, err := sa.NewSQLStorageAuthority(dbMap, fc)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create SA: %s", err)
|
||||
}
|
||||
|
@ -193,6 +196,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
|
|||
ValidityPeriod: time.Hour * 2190,
|
||||
NotAfter: time.Now().Add(time.Hour * 8761),
|
||||
MaxKeySize: 4096,
|
||||
Clk: fc,
|
||||
}
|
||||
cleanUp := func() {
|
||||
saDBCleanUp()
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
|
||||
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
|
@ -27,6 +28,7 @@ const getChallengesQuery = "SELECT * FROM challenges WHERE authorizationID = :au
|
|||
// SQLStorageAuthority defines a Storage Authority
|
||||
type SQLStorageAuthority struct {
|
||||
dbMap *gorp.DbMap
|
||||
clk clock.Clock
|
||||
log *blog.AuditLogger
|
||||
}
|
||||
|
||||
|
@ -49,13 +51,14 @@ type authzModel struct {
|
|||
|
||||
// NewSQLStorageAuthority provides persistence using a SQL backend for
|
||||
// Boulder. It will modify the given gorp.DbMap by adding relevent tables.
|
||||
func NewSQLStorageAuthority(dbMap *gorp.DbMap) (*SQLStorageAuthority, error) {
|
||||
func NewSQLStorageAuthority(dbMap *gorp.DbMap, clk clock.Clock) (*SQLStorageAuthority, error) {
|
||||
logger := blog.GetAuditLogger()
|
||||
|
||||
logger.Notice("Storage Authority Starting")
|
||||
|
||||
ssa := &SQLStorageAuthority{
|
||||
dbMap: dbMap,
|
||||
clk: clk,
|
||||
log: logger,
|
||||
}
|
||||
|
||||
|
@ -287,7 +290,6 @@ func (ssa *SQLStorageAuthority) GetCertificateStatus(serial string) (status core
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
status = *certificateStats.(*core.CertificateStatus)
|
||||
return
|
||||
}
|
||||
|
@ -318,7 +320,7 @@ func (ssa *SQLStorageAuthority) UpdateOCSP(serial string, ocspResponse []byte) (
|
|||
return
|
||||
}
|
||||
|
||||
timeStamp := time.Now()
|
||||
timeStamp := ssa.clk.Now()
|
||||
|
||||
// Record the response.
|
||||
ocspResp := &core.OCSPResponse{Serial: serial, CreatedAt: timeStamp, Response: ocspResponse}
|
||||
|
@ -358,7 +360,8 @@ func (ssa *SQLStorageAuthority) MarkCertificateRevoked(serial string, ocspRespon
|
|||
return
|
||||
}
|
||||
|
||||
ocspResp := &core.OCSPResponse{Serial: serial, CreatedAt: time.Now(), Response: ocspResponse}
|
||||
ocspResp := &core.OCSPResponse{Serial: serial, CreatedAt: ssa.clk.Now(), Response: ocspResponse}
|
||||
|
||||
err = tx.Insert(ocspResp)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
@ -375,18 +378,25 @@ func (ssa *SQLStorageAuthority) MarkCertificateRevoked(serial string, ocspRespon
|
|||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
now := ssa.clk.Now()
|
||||
status := statusObj.(*core.CertificateStatus)
|
||||
status.Status = core.OCSPStatusRevoked
|
||||
status.RevokedDate = time.Now()
|
||||
status.RevokedDate = now
|
||||
status.RevokedReason = reasonCode
|
||||
status.OCSPLastUpdated = now
|
||||
|
||||
_, err = tx.Update(status)
|
||||
n, err := tx.Update(status)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
tx.Rollback()
|
||||
err = errors.New("No certificate updated. Maybe the lock column was off?")
|
||||
return
|
||||
}
|
||||
err = tx.Commit()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -561,7 +571,7 @@ func (ssa *SQLStorageAuthority) AddCertificate(certDER []byte, regID int64) (dig
|
|||
Serial: serial,
|
||||
Digest: digest,
|
||||
DER: certDER,
|
||||
Issued: time.Now(),
|
||||
Issued: ssa.clk.Now(),
|
||||
Expires: parsedCertificate.NotAfter,
|
||||
}
|
||||
certStatus := &core.CertificateStatus{
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
|
||||
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/mocks"
|
||||
|
@ -29,18 +30,21 @@ var log = mocks.UseMockLog()
|
|||
|
||||
// initSA constructs a SQLStorageAuthority and a clean up function
|
||||
// that should be defer'ed to the end of the test.
|
||||
func initSA(t *testing.T) (*SQLStorageAuthority, func()) {
|
||||
func initSA(t *testing.T) (*SQLStorageAuthority, clock.FakeClock, func()) {
|
||||
dbMap, err := NewDbMap(dbConnStr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create dbMap: %s", err)
|
||||
}
|
||||
|
||||
sa, err := NewSQLStorageAuthority(dbMap)
|
||||
fc := clock.NewFake()
|
||||
fc.Add(1 * time.Hour)
|
||||
|
||||
sa, err := NewSQLStorageAuthority(dbMap, fc)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create SA: %s", err)
|
||||
}
|
||||
cleanUp := test.ResetTestDatabase(t, dbMap.Db)
|
||||
return sa, cleanUp
|
||||
return sa, fc, cleanUp
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -52,7 +56,7 @@ var (
|
|||
)
|
||||
|
||||
func TestAddRegistration(t *testing.T) {
|
||||
sa, cleanUp := initSA(t)
|
||||
sa, _, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
jwk := satest.GoodJWK()
|
||||
|
@ -104,7 +108,7 @@ func TestAddRegistration(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNoSuchRegistrationErrors(t *testing.T) {
|
||||
sa, cleanUp := initSA(t)
|
||||
sa, _, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
_, err := sa.GetRegistration(100)
|
||||
|
@ -125,7 +129,7 @@ func TestNoSuchRegistrationErrors(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAddAuthorization(t *testing.T) {
|
||||
sa, cleanUp := initSA(t)
|
||||
sa, _, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
reg := satest.CreateWorkingRegistration(t, sa)
|
||||
|
@ -196,7 +200,7 @@ func CreateDomainAuthWithRegId(t *testing.T, domainName string, sa *SQLStorageAu
|
|||
|
||||
// Ensure we get only valid authorization with correct RegID
|
||||
func TestGetLatestValidAuthorizationBasic(t *testing.T) {
|
||||
sa, cleanUp := initSA(t)
|
||||
sa, _, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
// attempt to get unauthorized domain
|
||||
|
@ -228,7 +232,7 @@ func TestGetLatestValidAuthorizationBasic(t *testing.T) {
|
|||
|
||||
// Ensure we get the latest valid authorization for an ident
|
||||
func TestGetLatestValidAuthorizationMultiple(t *testing.T) {
|
||||
sa, cleanUp := initSA(t)
|
||||
sa, _, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
domain := "example.org"
|
||||
|
@ -283,7 +287,7 @@ func TestGetLatestValidAuthorizationMultiple(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAddCertificate(t *testing.T) {
|
||||
sa, cleanUp := initSA(t)
|
||||
sa, _, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
reg := satest.CreateWorkingRegistration(t, sa)
|
||||
|
@ -338,7 +342,7 @@ func TestAddCertificate(t *testing.T) {
|
|||
// TestGetCertificateByShortSerial tests some failure conditions for GetCertificate.
|
||||
// Success conditions are tested above in TestAddCertificate.
|
||||
func TestGetCertificateByShortSerial(t *testing.T) {
|
||||
sa, cleanUp := initSA(t)
|
||||
sa, _, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
_, err := sa.GetCertificateByShortSerial("")
|
||||
|
@ -357,7 +361,7 @@ func TestDeniedCSR(t *testing.T) {
|
|||
csrBytes, _ := x509.CreateCertificateRequest(rand.Reader, template, key)
|
||||
csr, _ := x509.ParseCertificateRequest(csrBytes)
|
||||
|
||||
sa, cleanUp := initSA(t)
|
||||
sa, _, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
exists, err := sa.AlreadyDeniedCSR(append(csr.DNSNames, csr.Subject.CommonName))
|
||||
|
@ -366,7 +370,7 @@ func TestDeniedCSR(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUpdateOCSP(t *testing.T) {
|
||||
sa, cleanUp := initSA(t)
|
||||
sa, fc, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
reg := satest.CreateWorkingRegistration(t, sa)
|
||||
|
@ -378,15 +382,21 @@ func TestUpdateOCSP(t *testing.T) {
|
|||
|
||||
serial := "00000000000000000000000000021bd4"
|
||||
const ocspResponse = "this is a fake OCSP response"
|
||||
|
||||
certificateStatusObj, err := sa.dbMap.Get(core.CertificateStatus{}, serial)
|
||||
beforeUpdate := certificateStatusObj.(*core.CertificateStatus)
|
||||
|
||||
fc.Add(1 * time.Hour)
|
||||
|
||||
err = sa.UpdateOCSP(serial, []byte(ocspResponse))
|
||||
test.AssertNotError(t, err, "UpdateOCSP failed")
|
||||
|
||||
certificateStatusObj, err := sa.dbMap.Get(core.CertificateStatus{}, serial)
|
||||
certificateStatusObj, err = sa.dbMap.Get(core.CertificateStatus{}, serial)
|
||||
certificateStatus := certificateStatusObj.(*core.CertificateStatus)
|
||||
test.AssertNotError(t, err, "Failed to fetch certificate status")
|
||||
test.Assert(t,
|
||||
certificateStatus.OCSPLastUpdated.After(time.Now().Add(-time.Second)),
|
||||
"OCSP last updated too old.")
|
||||
certificateStatus.OCSPLastUpdated.After(beforeUpdate.OCSPLastUpdated),
|
||||
fmt.Sprintf("UpdateOCSP did not take. before: %s; after: %s", beforeUpdate.OCSPLastUpdated, certificateStatus.OCSPLastUpdated))
|
||||
|
||||
var fetchedOcspResponse core.OCSPResponse
|
||||
err = sa.dbMap.SelectOne(&fetchedOcspResponse,
|
||||
|
@ -395,3 +405,55 @@ func TestUpdateOCSP(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Failed to fetch OCSP response")
|
||||
test.AssertEquals(t, ocspResponse, string(fetchedOcspResponse.Response))
|
||||
}
|
||||
|
||||
func TestMarkCertificateRevoked(t *testing.T) {
|
||||
sa, fc, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
reg := satest.CreateWorkingRegistration(t, sa)
|
||||
// Add a cert to the DB to test with.
|
||||
certDER, err := ioutil.ReadFile("www.eff.org.der")
|
||||
test.AssertNotError(t, err, "Couldn't read example cert DER")
|
||||
_, err = sa.AddCertificate(certDER, reg.ID)
|
||||
test.AssertNotError(t, err, "Couldn't add www.eff.org.der")
|
||||
|
||||
serial := "00000000000000000000000000021bd4"
|
||||
const ocspResponse = "this is a fake OCSP response"
|
||||
|
||||
certificateStatusObj, err := sa.dbMap.Get(core.CertificateStatus{}, serial)
|
||||
beforeStatus := certificateStatusObj.(*core.CertificateStatus)
|
||||
test.AssertEquals(t, beforeStatus.Status, core.OCSPStatusGood)
|
||||
|
||||
fc.Add(1 * time.Hour)
|
||||
|
||||
code := core.RevocationCode(1)
|
||||
err = sa.MarkCertificateRevoked(serial, []byte(ocspResponse), code)
|
||||
test.AssertNotError(t, err, "MarkCertificateRevoked failed")
|
||||
|
||||
certificateStatusObj, err = sa.dbMap.Get(core.CertificateStatus{}, serial)
|
||||
afterStatus := certificateStatusObj.(*core.CertificateStatus)
|
||||
test.AssertNotError(t, err, "Failed to fetch certificate status")
|
||||
|
||||
if code != afterStatus.RevokedReason {
|
||||
t.Errorf("RevokedReasons, expected %s, got %s", code, afterStatus.RevokedReason)
|
||||
}
|
||||
if !fc.Now().Equal(afterStatus.RevokedDate) {
|
||||
t.Errorf("RevokedData, expected %s, got %s", fc.Now(), afterStatus.RevokedDate)
|
||||
}
|
||||
if !fc.Now().Equal(afterStatus.OCSPLastUpdated) {
|
||||
t.Errorf("OCSPLastUpdated, expected %s, got %s", fc.Now(), afterStatus.OCSPLastUpdated)
|
||||
}
|
||||
|
||||
if !afterStatus.OCSPLastUpdated.After(beforeStatus.OCSPLastUpdated) {
|
||||
t.Errorf("OCSPLastUpdated did not update correctly. before: %s; after: %s",
|
||||
beforeStatus.OCSPLastUpdated, afterStatus.OCSPLastUpdated)
|
||||
}
|
||||
var fetched core.OCSPResponse
|
||||
err = sa.dbMap.SelectOne(&fetched,
|
||||
`SELECT * from ocspResponses where serial = ? order by createdAt DESC limit 1;`,
|
||||
serial)
|
||||
test.AssertNotError(t, err, "Failed to fetch OCSP response")
|
||||
if ocspResponse != string(fetched.Response) {
|
||||
t.Errorf("OCSPResponse response, expected %#v, got %#v", ocspResponse, string(fetched.Response))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue