From f8f9a158c728f5e16d42848348100f50c8378df5 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Thu, 19 Apr 2018 13:25:12 -0400 Subject: [PATCH] orphan-finder: set cert issued date based on notbefore. (#3651) The Boulder orphan-finder command uses the SA's AddCertificate RPC to add orphaned certificates it finds back to the DB. Prior to this commit this RPC always set the core.Certificate.Issued field to the current time. For the orphan-finder case this meant that the Issued date would incorrectly be set to when the certificate was found, not when it was actually issued. This could cause cert-checker to alarm based on the unusual delta between the cert NotBefore and the core.Certificate.Issued value. This PR updates the AddCertificate RPC to accept an optional issued timestamp in the request arguments. In the SA layer we address deployability concerns by setting a default value of the current time when none is explicitly provided. This matches the classic behaviour and will let an old RA communicate with a new SA. This PR updates the orphan-finder to provide an explicit issued time to sa.AddCertificate. The explicit issued time is calculated using the found certificate's NotBefore and the configured backdate. This lets the orphan-finder set the true issued time in the core.Certificate object, avoiding any cert-checker alarms. Resolves #3624 --- ca/ca.go | 5 +- ca/ca_test.go | 2 +- cmd/cert-checker/main_test.go | 2 +- cmd/ocsp-updater/main_test.go | 32 ++--- cmd/orphan-finder/main.go | 41 ++++-- cmd/orphan-finder/main_test.go | 110 ++++++++++++--- core/interfaces.go | 2 +- grpc/sa-wrappers.go | 32 ++++- mocks/mocks.go | 2 +- sa/proto/sa.pb.go | 202 +++++++++++++++------------- sa/proto/sa.proto | 4 + sa/sa.go | 17 ++- sa/sa_test.go | 24 ++-- test/config-next/orphan-finder.json | 2 + 14 files changed, 311 insertions(+), 166 deletions(-) diff --git a/ca/ca.go b/ca/ca.go index f5db860ec18..ea33caa265b 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -98,7 +98,7 @@ const ( ) type certificateStorage interface { - AddCertificate(context.Context, []byte, int64, []byte) (string, error) + AddCertificate(context.Context, []byte, int64, []byte, *time.Time) (string, error) } type certificateType string @@ -663,7 +663,8 @@ func (ca *CertificateAuthorityImpl) generateOCSPAndStoreCertificate( // and generate the initial response in this case. } - _, err = ca.sa.AddCertificate(ctx, certDER, regID, ocspResp) + now := ca.clk.Now() + _, err = ca.sa.AddCertificate(ctx, certDER, regID, ocspResp, &now) if err != nil { err = berrors.InternalServerError(err.Error()) // Note: This log line is parsed by cmd/orphan-finder. If you make any diff --git a/ca/ca_test.go b/ca/ca_test.go index e594b1d9304..62b4cb0fafa 100644 --- a/ca/ca_test.go +++ b/ca/ca_test.go @@ -166,7 +166,7 @@ type mockSA struct { certificate core.Certificate } -func (m *mockSA) AddCertificate(ctx context.Context, der []byte, _ int64, _ []byte) (string, error) { +func (m *mockSA) AddCertificate(ctx context.Context, der []byte, _ int64, _ []byte, _ *time.Time) (string, error) { m.certificate.DER = der return "", nil } diff --git a/cmd/cert-checker/main_test.go b/cmd/cert-checker/main_test.go index 934740ff628..bf508827c9e 100644 --- a/cmd/cert-checker/main_test.go +++ b/cmd/cert-checker/main_test.go @@ -286,7 +286,7 @@ func TestGetAndProcessCerts(t *testing.T) { rawCert.SerialNumber = big.NewInt(mrand.Int63()) certDER, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey) test.AssertNotError(t, err, "Couldn't create certificate") - _, err = sa.AddCertificate(context.Background(), certDER, reg.ID, nil) + _, err = sa.AddCertificate(context.Background(), certDER, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add certificate") } diff --git a/cmd/ocsp-updater/main_test.go b/cmd/ocsp-updater/main_test.go index 995a731282a..288263487a7 100644 --- a/cmd/ocsp-updater/main_test.go +++ b/cmd/ocsp-updater/main_test.go @@ -186,7 +186,7 @@ func TestGenerateAndStoreOCSPResponse(t *testing.T) { 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, nil) + _, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.pem") status, err := sa.GetCertificateStatus(ctx, core.SerialToString(parsedCert.SerialNumber)) @@ -214,11 +214,11 @@ func TestGenerateOCSPResponses(t *testing.T) { reg := satest.CreateWorkingRegistration(t, sa) parsedCertA, err := core.LoadCert("test-cert.pem") test.AssertNotError(t, err, "Couldn't read test certificate") - _, err = sa.AddCertificate(ctx, parsedCertA.Raw, reg.ID, nil) + _, err = sa.AddCertificate(ctx, parsedCertA.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.pem") parsedCertB, err := core.LoadCert("test-cert-b.pem") test.AssertNotError(t, err, "Couldn't read test certificate") - _, err = sa.AddCertificate(ctx, parsedCertB.Raw, reg.ID, nil) + _, err = sa.AddCertificate(ctx, parsedCertB.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert-b.pem") // We need to set a fake "ocspLastUpdated" value for the two certs we created @@ -263,7 +263,7 @@ func TestFindStaleOCSPResponses(t *testing.T) { 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, nil) + _, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.pem") // We need to set a fake "ocspLastUpdated" value for the cert we created @@ -300,11 +300,11 @@ func TestFindStaleOCSPResponsesStaleMaxAge(t *testing.T) { reg := satest.CreateWorkingRegistration(t, sa) parsedCertA, err := core.LoadCert("test-cert.pem") test.AssertNotError(t, err, "Couldn't read test certificate") - _, err = sa.AddCertificate(ctx, parsedCertA.Raw, reg.ID, nil) + _, err = sa.AddCertificate(ctx, parsedCertA.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.pem") parsedCertB, err := core.LoadCert("test-cert-b.pem") test.AssertNotError(t, err, "Couldn't read test certificate") - _, err = sa.AddCertificate(ctx, parsedCertB.Raw, reg.ID, nil) + _, err = sa.AddCertificate(ctx, parsedCertB.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert-b.pem") // Set a "ocspLastUpdated" value of 3 days ago for parsedCertA @@ -340,7 +340,7 @@ func TestGetCertificatesWithMissingResponses(t *testing.T) { 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, nil) + _, err = sa.AddCertificate(ctx, cert.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.pem") statuses, err := updater.getCertificatesWithMissingResponses(10) @@ -355,7 +355,7 @@ func TestFindRevokedCertificatesToUpdate(t *testing.T) { 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, nil) + _, err = sa.AddCertificate(ctx, cert.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.pem") statuses, err := updater.findRevokedCertificatesToUpdate(10) @@ -377,7 +377,7 @@ func TestNewCertificateTick(t *testing.T) { 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, nil) + _, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.pem") prev := fc.Now().Add(-time.Hour) @@ -396,7 +396,7 @@ func TestOldOCSPResponsesTick(t *testing.T) { 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, nil) + _, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.pem") updater.ocspMinTimeToExpiry = 1 * time.Hour @@ -428,7 +428,7 @@ func TestOldOCSPResponsesTickIsExpired(t *testing.T) { serial := core.SerialToString(parsedCert.SerialNumber) // Add a new test certificate - _, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID, nil) + _, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.pem") // We need to set a fake "ocspLastUpdated" value for the cert we created @@ -471,7 +471,7 @@ func TestMissingReceiptsTick(t *testing.T) { 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, nil) + _, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.pem") updater.oldestIssuedSCT = 2 * time.Hour @@ -512,7 +512,7 @@ func TestMissingOnlyReceiptsTick(t *testing.T) { 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, nil) + _, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.pem") updater.oldestIssuedSCT = 2 * time.Hour @@ -605,7 +605,7 @@ func TestRevokedCertificatesTick(t *testing.T) { 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, nil) + _, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.pem") err = sa.MarkCertificateRevoked(ctx, core.SerialToString(parsedCert.SerialNumber), revocation.KeyCompromise) @@ -631,7 +631,7 @@ func TestStoreResponseGuard(t *testing.T) { 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, nil) + _, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.pem") status, err := sa.GetCertificateStatus(ctx, core.SerialToString(parsedCert.SerialNumber)) @@ -716,7 +716,7 @@ func TestGetSubmittedReceipts(t *testing.T) { 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, nil) + _, err = sa.AddCertificate(ctx, parsedCert.Raw, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.pem") // Before adding any SCTs, there should be no receipts or errors for serial 00 diff --git a/cmd/orphan-finder/main.go b/cmd/orphan-finder/main.go index 00751e08058..a4229256dd0 100644 --- a/cmd/orphan-finder/main.go +++ b/cmd/orphan-finder/main.go @@ -11,6 +11,7 @@ import ( "regexp" "strconv" "strings" + "time" "golang.org/x/net/context" @@ -41,11 +42,15 @@ type config struct { TLS cmd.TLSConfig SAService *cmd.GRPCClientConfig Syslog cmd.SyslogConfig - Features map[string]bool + // Backdate specifies how to adjust a certificate's NotBefore date to get back + // to the original issued date. It should match the value used in + // `test/config/ca.json` for the CA "backdate" value. + Backdate cmd.ConfigDuration + Features map[string]bool } type certificateStorage interface { - AddCertificate(context.Context, []byte, int64, []byte) (string, error) + AddCertificate(context.Context, []byte, int64, []byte, *time.Time) (string, error) GetCertificate(ctx context.Context, serial string) (core.Certificate, error) } @@ -55,20 +60,22 @@ var ( errAlreadyExists = fmt.Errorf("Certificate already exists in DB") ) -func checkDER(sai certificateStorage, der []byte) error { +var backdateDuration time.Duration + +func checkDER(sai certificateStorage, der []byte) (*x509.Certificate, error) { ctx := context.Background() cert, err := x509.ParseCertificate(der) if err != nil { - return fmt.Errorf("Failed to parse DER: %s", err) + return nil, fmt.Errorf("Failed to parse DER: %s", err) } _, err = sai.GetCertificate(ctx, core.SerialToString(cert.SerialNumber)) if err == nil { - return errAlreadyExists + return nil, errAlreadyExists } if berrors.Is(err, berrors.NotFound) { - return nil + return cert, nil } - return fmt.Errorf("Existing certificate lookup failed: %s", err) + return nil, fmt.Errorf("Existing certificate lookup failed: %s", err) } func parseLogLine(sa certificateStorage, logger blog.Logger, line string) (found bool, added bool) { @@ -86,7 +93,7 @@ func parseLogLine(sa certificateStorage, logger blog.Logger, line string) (found logger.AuditErr(fmt.Sprintf("Couldn't decode hex: %s, [%s]", err, line)) return true, false } - err = checkDER(sa, der) + cert, err := checkDER(sa, der) if err != nil { logFunc := logger.Err if err == errAlreadyExists { @@ -107,8 +114,13 @@ func parseLogLine(sa certificateStorage, logger blog.Logger, line string) (found return true, false } // OCSP-Updater will do the first response generation for this cert so pass an - // empty OCSP response - _, err = sa.AddCertificate(ctx, der, int64(regID), nil) + // empty OCSP response. We use `cert.NotBefore` as the issued date to avoid + // the SA tagging this certificate with an issued date of the current time + // when we know it was an orphan issued in the past. Because certificates are + // backdated we need to add the backdate duration to find the true issued + // time. + issuedDate := cert.NotBefore.Add(backdateDuration) + _, err = sa.AddCertificate(ctx, der, int64(regID), nil, &issuedDate) if err != nil { logger.AuditErr(fmt.Sprintf("Failed to store certificate: %s, [%s]", err, line)) return true, false @@ -133,6 +145,8 @@ func setup(configFile string) (blog.Logger, core.StorageAuthority) { conn, err := bgrpc.ClientSetup(conf.SAService, tlsConfig, clientMetrics) cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA") sac := bgrpc.NewStorageAuthorityClient(sapb.NewStorageAuthorityClient(conn)) + + backdateDuration = conf.Backdate.Duration return logger, sac } @@ -192,9 +206,12 @@ func main() { } der, err := ioutil.ReadFile(*derPath) cmd.FailOnError(err, "Failed to read DER file") - err = checkDER(sa, der) + cert, err := checkDER(sa, der) cmd.FailOnError(err, "Pre-AddCertificate checks failed") - _, err = sa.AddCertificate(ctx, der, int64(*regID), nil) + // Because certificates are backdated we need to add the backdate duration + // to find the true issued time. + issuedDate := cert.NotBefore.Add(1 * backdateDuration) + _, err = sa.AddCertificate(ctx, der, int64(*regID), nil, &issuedDate) cmd.FailOnError(err, "Failed to add certificate to database") default: diff --git a/cmd/orphan-finder/main_test.go b/cmd/orphan-finder/main_test.go index 133c970d5a7..632c0cbd226 100644 --- a/cmd/orphan-finder/main_test.go +++ b/cmd/orphan-finder/main_test.go @@ -1,6 +1,9 @@ package main import ( + "crypto/x509" + "encoding/hex" + "fmt" "testing" "time" @@ -18,10 +21,19 @@ var log = blog.UseMock() type mockSA struct { certificate core.Certificate + clk clock.FakeClock } -func (m *mockSA) AddCertificate(ctx context.Context, der []byte, _ int64, _ []byte) (string, error) { +func (m *mockSA) AddCertificate(ctx context.Context, der []byte, regID int64, _ []byte, issued *time.Time) (string, error) { m.certificate.DER = der + m.certificate.RegistrationID = regID + + if issued == nil { + m.certificate.Issued = m.clk.Now() + } else { + m.certificate.Issued = *issued + } + return "", nil } @@ -47,29 +59,83 @@ func TestParseLine(t *testing.T) { fc.Set(time.Date(2015, 3, 4, 5, 0, 0, 0, time.UTC)) sa := &mockSA{} - found, added := parseLogLine(sa, log, "") - test.AssertEquals(t, found, false) - test.AssertEquals(t, added, false) - - found, added = parseLogLine(sa, log, "0000-00-00T00:00:00+00:00 hostname boulder-ca[pid]: [AUDIT] Failed RPC to store at SA, orphaning certificate: cert=[] err=[context deadline exceeded], regID=[1337]") - test.AssertEquals(t, found, true) - test.AssertEquals(t, added, false) - - found, added = parseLogLine(sa, log, "0000-00-00T00:00:00+00:00 hostname boulder-ca[pid]: [AUDIT] Failed RPC to store at SA, orphaning certificate: cert=[deadbeef] err=[context deadline exceeded], regID=[]") - test.AssertEquals(t, found, true) - test.AssertEquals(t, added, false) + // Set an example backdate duration (this is normally read from config) + backdateDuration = time.Hour + + testCertDER := "3082045b30820343a003020102021300ffa0160630d618b2eb5c0510824b14274856300d06092a864886f70d01010b0500301f311d301b06035504030c146861707079206861636b65722066616b65204341301e170d3135313030333035323130305a170d3136303130313035323130305a3018311630140603550403130d6578616d706c652e636f2e626e30820122300d06092a864886f70d01010105000382010f003082010a02820101009ea3f1d21fade5596e36a6a77095a94758e4b72466b7444ada4f7c4cf6fde9b1d470b93b65c1fdd896917f248ccae49b57c80dc21c64b010699432130d059d2d8392346e8a179c7c947835549c64a7a5680c518faf0a5cbea48e684fca6304775c8fa9239c34f1d5cb2d063b098bd1c17183c7521efc884641b2f0b41402ac87c7076848d4347cef59dd5a9c174ad25467db933c95ef48c578ba762f527b21666a198fb5e1fe2d8299b4dceb1791e96ad075e3ecb057c776d764fad8f0829d43c32ddf985a3a36fade6966cec89468721a1ec47ab38eac8da4514060ded51d283a787b7c69971bda01f49f76baa41b1f9b4348aa4279e0fa55645d6616441f0d0203010001a382019530820191300e0603551d0f0101ff0404030205a0301d0603551d250416301406082b0601050507030106082b06010505070302300c0603551d130101ff04023000301d0603551d0e04160414369d0c100452b9eb3ffe7ae852e9e839a3ae5adb301f0603551d23041830168014fb784f12f96015832c9f177f3419b32e36ea4189306a06082b06010505070101045e305c302606082b06010505073001861a687474703a2f2f6c6f63616c686f73743a343030322f6f637370303206082b060105050730028626687474703a2f2f6c6f63616c686f73743a343030302f61636d652f6973737565722d6365727430180603551d110411300f820d6578616d706c652e636f2e626e30270603551d1f0420301e301ca01aa0188616687474703a2f2f6578616d706c652e636f6d2f63726c30630603551d20045c305a300a060667810c0102013000304c06032a03043045302206082b060105050702011616687474703a2f2f6578616d706c652e636f6d2f637073301f06082b0601050507020230130c11446f20576861742054686f752057696c74300d06092a864886f70d01010b05000382010100bbb4b994971cafa2e56e2258db46d88bfb361d8bfcd75521c03174e471eaa9f3ff2e719059bb57cc064079496d8550577c127baa84a18e792ddd36bf4f7b874b6d40d1d14288c15d38e4d6be25eb7805b1c3756b3735702eb4585d1886bc8af2c14086d3ce506e55184913c83aaaa8dfe6160bd035e42cda6d97697ed3ee3124c9bf9620a9fe6602191c1b746533c1d4a30023bbe902cb4aa661901177ed924eb836c94cc062dd0ce439c4ece9ee1dfe0499a42cbbcb2ea7243c59f4df4fdd7058229bacf9a640632dbd776b21633137b2df1c41f0765a66f448777aeec7ed4c0cdeb9d8a2356ff813820a287e11d52efde1aa543b4ef2ee992a7a9d5ccf7da4" + + testCases := []struct { + Name string + LogLine string + ExpectFound bool + ExpectAdded bool + ExpectNoErrors bool + }{ + { + Name: "Empty line", + LogLine: "", + ExpectFound: false, + ExpectAdded: false, + ExpectNoErrors: false, + }, + { + Name: "Empty cert in line", + LogLine: "0000-00-00T00:00:00+00:00 hostname boulder-ca[pid]: [AUDIT] Failed RPC to store at SA, orphaning certificate: cert=[] err=[context deadline exceeded], regID=[1337], orderID=[0]", + ExpectFound: true, + ExpectAdded: false, + ExpectNoErrors: false, + }, + { + Name: "Invalid cert in line", + LogLine: "0000-00-00T00:00:00+00:00 hostname boulder-ca[pid]: [AUDIT] Failed RPC to store at SA, orphaning certificate: cert=[deadbeef] err=[context deadline exceeded], regID=[], orderID=[]", + ExpectFound: true, + ExpectAdded: false, + ExpectNoErrors: false, + }, + { + Name: "Valid cert in line", + LogLine: fmt.Sprintf("0000-00-00T00:00:00+00:00 hostname boulder-ca[pid]: [AUDIT] Failed RPC to store at SA, orphaning certificate: cert=[%s] err=[context deadline exceeded], regID=[1001], orderID=[0]", testCertDER), + ExpectFound: true, + ExpectAdded: true, + ExpectNoErrors: true, + }, + { + Name: "Already inserted cert in line", + LogLine: fmt.Sprintf("0000-00-00T00:00:00+00:00 hostname boulder-ca[pid]: [AUDIT] Failed RPC to store at SA, orphaning certificate: cert=[%s] err=[context deadline exceeded], regID=[1001], orderID=[0]", testCertDER), + ExpectFound: true, + // ExpectAdded is false because we have already added this cert in the + // previous "Valid cert in line" test case. + ExpectAdded: false, + ExpectNoErrors: true, + }, + } - log.Clear() - found, added = parseLogLine(sa, log, "0000-00-00T00:00:00+00:00 hostname boulder-ca[pid]: [AUDIT] Failed RPC to store at SA, orphaning certificate: cert=[3082045b30820343a003020102021300ffa0160630d618b2eb5c0510824b14274856300d06092a864886f70d01010b0500301f311d301b06035504030c146861707079206861636b65722066616b65204341301e170d3135313030333035323130305a170d3136303130313035323130305a3018311630140603550403130d6578616d706c652e636f2e626e30820122300d06092a864886f70d01010105000382010f003082010a02820101009ea3f1d21fade5596e36a6a77095a94758e4b72466b7444ada4f7c4cf6fde9b1d470b93b65c1fdd896917f248ccae49b57c80dc21c64b010699432130d059d2d8392346e8a179c7c947835549c64a7a5680c518faf0a5cbea48e684fca6304775c8fa9239c34f1d5cb2d063b098bd1c17183c7521efc884641b2f0b41402ac87c7076848d4347cef59dd5a9c174ad25467db933c95ef48c578ba762f527b21666a198fb5e1fe2d8299b4dceb1791e96ad075e3ecb057c776d764fad8f0829d43c32ddf985a3a36fade6966cec89468721a1ec47ab38eac8da4514060ded51d283a787b7c69971bda01f49f76baa41b1f9b4348aa4279e0fa55645d6616441f0d0203010001a382019530820191300e0603551d0f0101ff0404030205a0301d0603551d250416301406082b0601050507030106082b06010505070302300c0603551d130101ff04023000301d0603551d0e04160414369d0c100452b9eb3ffe7ae852e9e839a3ae5adb301f0603551d23041830168014fb784f12f96015832c9f177f3419b32e36ea4189306a06082b06010505070101045e305c302606082b06010505073001861a687474703a2f2f6c6f63616c686f73743a343030322f6f637370303206082b060105050730028626687474703a2f2f6c6f63616c686f73743a343030302f61636d652f6973737565722d6365727430180603551d110411300f820d6578616d706c652e636f2e626e30270603551d1f0420301e301ca01aa0188616687474703a2f2f6578616d706c652e636f6d2f63726c30630603551d20045c305a300a060667810c0102013000304c06032a03043045302206082b060105050702011616687474703a2f2f6578616d706c652e636f6d2f637073301f06082b0601050507020230130c11446f20576861742054686f752057696c74300d06092a864886f70d01010b05000382010100bbb4b994971cafa2e56e2258db46d88bfb361d8bfcd75521c03174e471eaa9f3ff2e719059bb57cc064079496d8550577c127baa84a18e792ddd36bf4f7b874b6d40d1d14288c15d38e4d6be25eb7805b1c3756b3735702eb4585d1886bc8af2c14086d3ce506e55184913c83aaaa8dfe6160bd035e42cda6d97697ed3ee3124c9bf9620a9fe6602191c1b746533c1d4a30023bbe902cb4aa661901177ed924eb836c94cc062dd0ce439c4ece9ee1dfe0499a42cbbcb2ea7243c59f4df4fdd7058229bacf9a640632dbd776b21633137b2df1c41f0765a66f448777aeec7ed4c0cdeb9d8a2356ff813820a287e11d52efde1aa543b4ef2ee992a7a9d5ccf7da4] err=[context deadline exceeded], regID=[1001]") - test.AssertEquals(t, found, true) - test.AssertEquals(t, added, true) - checkNoErrors(t) + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + log.Clear() + found, added := parseLogLine(sa, log, tc.LogLine) + test.AssertEquals(t, found, tc.ExpectFound) + test.AssertEquals(t, added, tc.ExpectAdded) + logs := log.GetAllMatching("ERR:") + if tc.ExpectNoErrors { + test.AssertEquals(t, len(logs), 0) + } + }) + } - log.Clear() - found, added = parseLogLine(sa, log, "0000-00-00T00:00:00+00:00 hostname boulder-ca[pid]: [AUDIT] Failed RPC to store at SA, orphaning certificate: cert=[3082045b30820343a003020102021300ffa0160630d618b2eb5c0510824b14274856300d06092a864886f70d01010b0500301f311d301b06035504030c146861707079206861636b65722066616b65204341301e170d3135313030333035323130305a170d3136303130313035323130305a3018311630140603550403130d6578616d706c652e636f2e626e30820122300d06092a864886f70d01010105000382010f003082010a02820101009ea3f1d21fade5596e36a6a77095a94758e4b72466b7444ada4f7c4cf6fde9b1d470b93b65c1fdd896917f248ccae49b57c80dc21c64b010699432130d059d2d8392346e8a179c7c947835549c64a7a5680c518faf0a5cbea48e684fca6304775c8fa9239c34f1d5cb2d063b098bd1c17183c7521efc884641b2f0b41402ac87c7076848d4347cef59dd5a9c174ad25467db933c95ef48c578ba762f527b21666a198fb5e1fe2d8299b4dceb1791e96ad075e3ecb057c776d764fad8f0829d43c32ddf985a3a36fade6966cec89468721a1ec47ab38eac8da4514060ded51d283a787b7c69971bda01f49f76baa41b1f9b4348aa4279e0fa55645d6616441f0d0203010001a382019530820191300e0603551d0f0101ff0404030205a0301d0603551d250416301406082b0601050507030106082b06010505070302300c0603551d130101ff04023000301d0603551d0e04160414369d0c100452b9eb3ffe7ae852e9e839a3ae5adb301f0603551d23041830168014fb784f12f96015832c9f177f3419b32e36ea4189306a06082b06010505070101045e305c302606082b06010505073001861a687474703a2f2f6c6f63616c686f73743a343030322f6f637370303206082b060105050730028626687474703a2f2f6c6f63616c686f73743a343030302f61636d652f6973737565722d6365727430180603551d110411300f820d6578616d706c652e636f2e626e30270603551d1f0420301e301ca01aa0188616687474703a2f2f6578616d706c652e636f6d2f63726c30630603551d20045c305a300a060667810c0102013000304c06032a03043045302206082b060105050702011616687474703a2f2f6578616d706c652e636f6d2f637073301f06082b0601050507020230130c11446f20576861742054686f752057696c74300d06092a864886f70d01010b05000382010100bbb4b994971cafa2e56e2258db46d88bfb361d8bfcd75521c03174e471eaa9f3ff2e719059bb57cc064079496d8550577c127baa84a18e792ddd36bf4f7b874b6d40d1d14288c15d38e4d6be25eb7805b1c3756b3735702eb4585d1886bc8af2c14086d3ce506e55184913c83aaaa8dfe6160bd035e42cda6d97697ed3ee3124c9bf9620a9fe6602191c1b746533c1d4a30023bbe902cb4aa661901177ed924eb836c94cc062dd0ce439c4ece9ee1dfe0499a42cbbcb2ea7243c59f4df4fdd7058229bacf9a640632dbd776b21633137b2df1c41f0765a66f448777aeec7ed4c0cdeb9d8a2356ff813820a287e11d52efde1aa543b4ef2ee992a7a9d5ccf7da4] err=[context deadline exceeded], regID=[1001]") - test.AssertEquals(t, found, true) - test.AssertEquals(t, added, false) - checkNoErrors(t) + // Decode the test cert DER we added above to get the certificate serial + der, _ := hex.DecodeString(testCertDER) + testCert, _ := x509.ParseCertificate(der) + testCertSerial := core.SerialToString(testCert.SerialNumber) + + // Fetch the certificate from the mock SA + cert, err := sa.GetCertificate(context.Background(), testCertSerial) + // It should not error + test.AssertNotError(t, err, "Error getting test certificate from SA") + // The orphan cert should have been added with the correct registration ID from the log line + test.AssertEquals(t, cert.RegistrationID, int64(1001)) + // The Issued timestamp should be the certificate's NotBefore timestamp offset by the backdateDuration + test.AssertEquals(t, cert.Issued, testCert.NotBefore.Add(backdateDuration)) } func TestNotOrphan(t *testing.T) { diff --git a/core/interfaces.go b/core/interfaces.go index fd09563c8c3..900bdea8445 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -147,7 +147,7 @@ type StorageAdder interface { UpdatePendingAuthorization(ctx context.Context, authz Authorization) error FinalizeAuthorization(ctx context.Context, authz Authorization) error MarkCertificateRevoked(ctx context.Context, serial string, reasonCode revocation.Reason) error - AddCertificate(ctx context.Context, der []byte, regID int64, ocsp []byte) (digest string, err error) + AddCertificate(ctx context.Context, der []byte, regID int64, ocsp []byte, issued *time.Time) (digest string, err error) AddSCTReceipt(ctx context.Context, sct SignedCertificateTimestamp) error RevokeAuthorizationsByDomain(ctx context.Context, domain AcmeIdentifier) (finalized, pending int64, err error) DeactivateRegistration(ctx context.Context, id int64) error diff --git a/grpc/sa-wrappers.go b/grpc/sa-wrappers.go index 0bfcbad53d6..853ade0ca79 100644 --- a/grpc/sa-wrappers.go +++ b/grpc/sa-wrappers.go @@ -442,11 +442,21 @@ func (sac StorageAuthorityClientWrapper) MarkCertificateRevoked(ctx context.Cont return nil } -func (sac StorageAuthorityClientWrapper) AddCertificate(ctx context.Context, der []byte, regID int64, ocspResponse []byte) (string, error) { +func (sac StorageAuthorityClientWrapper) AddCertificate( + ctx context.Context, + der []byte, + regID int64, + ocspResponse []byte, + issued *time.Time) (string, error) { + issuedTS := int64(0) + if issued != nil { + issuedTS = issued.UnixNano() + } response, err := sac.inner.AddCertificate(ctx, &sapb.AddCertificateRequest{ - Der: der, - RegID: ®ID, - Ocsp: ocspResponse, + Der: der, + RegID: ®ID, + Ocsp: ocspResponse, + Issued: &issuedTS, }) if err != nil { return "", err @@ -1014,11 +1024,23 @@ func (sas StorageAuthorityServerWrapper) MarkCertificateRevoked(ctx context.Cont } func (sas StorageAuthorityServerWrapper) AddCertificate(ctx context.Context, request *sapb.AddCertificateRequest) (*sapb.AddCertificateResponse, error) { + // NOTE(@cpu): We allow `request.Issued` to be nil here for deployability aid. + // This allows a RA that hasn't been updated to send this parameter to operate + // correctly. We replace the nil value with a default in the SA's + // `AddCertificate` impl if request == nil || request.Der == nil || request.RegID == nil { return nil, errIncompleteRequest } - digest, err := sas.inner.AddCertificate(ctx, request.Der, *request.RegID, request.Ocsp) + var issued *time.Time + // If the request.Issued int64 pointer isn't nil, create a pointer to + // a time.Time instance with its value. + if request.Issued != nil && *request.Issued != 0 { + reqIssued := time.Unix(0, *request.Issued) + issued = &reqIssued + } + + digest, err := sas.inner.AddCertificate(ctx, request.Der, *request.RegID, request.Ocsp, issued) if err != nil { return nil, err } diff --git a/mocks/mocks.go b/mocks/mocks.go index 070145280aa..063e298c888 100644 --- a/mocks/mocks.go +++ b/mocks/mocks.go @@ -309,7 +309,7 @@ func (sa *StorageAuthority) GetCertificateStatus(_ context.Context, serial strin } // AddCertificate is a mock -func (sa *StorageAuthority) AddCertificate(_ context.Context, certDER []byte, regID int64, _ []byte) (digest string, err error) { +func (sa *StorageAuthority) AddCertificate(_ context.Context, certDER []byte, regID int64, _ []byte, _ *time.Time) (digest string, err error) { return } diff --git a/sa/proto/sa.pb.go b/sa/proto/sa.pb.go index 5d3ca8652e5..01ef1110c61 100644 --- a/sa/proto/sa.pb.go +++ b/sa/proto/sa.pb.go @@ -680,7 +680,11 @@ type AddCertificateRequest struct { RegID *int64 `protobuf:"varint,2,opt,name=regID" json:"regID,omitempty"` // A signed OCSP response for the certificate contained in "der". // Note: The certificate status in the OCSP response is assumed to be 0 (good). - Ocsp []byte `protobuf:"bytes,3,opt,name=ocsp" json:"ocsp,omitempty"` + Ocsp []byte `protobuf:"bytes,3,opt,name=ocsp" json:"ocsp,omitempty"` + // An optional issued time. When not present the SA defaults to using + // the current time. The orphan-finder uses this parameter to add + // certificates with the correct historic issued date + Issued *int64 `protobuf:"varint,4,opt,name=issued" json:"issued,omitempty"` XXX_unrecognized []byte `json:"-"` } @@ -710,6 +714,13 @@ func (m *AddCertificateRequest) GetOcsp() []byte { return nil } +func (m *AddCertificateRequest) GetIssued() int64 { + if m != nil && m.Issued != nil { + return *m.Issued + } + return 0 +} + type AddCertificateResponse struct { Digest *string `protobuf:"bytes,1,opt,name=digest" json:"digest,omitempty"` XXX_unrecognized []byte `json:"-"` @@ -2402,14 +2413,14 @@ var _StorageAuthority_serviceDesc = grpc.ServiceDesc{ func init() { proto1.RegisterFile("sa/proto/sa.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 1760 bytes of a gzipped FileDescriptorProto + // 1769 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xef, 0x72, 0x1b, 0xb7, 0x11, 0xe7, 0x1f, 0xd3, 0x96, 0x56, 0x7f, 0x2c, 0xc1, 0x92, 0x7c, 0x39, 0x4b, 0xb2, 0x8c, 0xb8, 0xae, 0x32, 0xed, 0x28, 0xae, 0xda, 0x49, 0x3a, 0xa3, 0xba, 0xad, 0x64, 0xc9, 0x8c, 0x62, 0x5b, 0x56, 0x8f, 0x8e, 0x92, 0x69, 0x67, 0x3a, 0x03, 0xf3, 0x60, 0x1a, 0x35, 0x75, 0xc7, 0x00, 0xa0, 0x64, 0xf9, 0x05, 0xda, 0x27, 0xe8, 0xf4, 0x63, 0x9f, 0xa3, 0xef, 0xd2, 0x37, 0xe8, 0x0b, 0xf4, 0x5b, 0x07, 0x0b, 0x1c, 0xef, 0x0f, 0xef, 0xc8, 0x78, 0xdc, 0xc9, 0xb7, 0xdb, 0xc5, 0xee, 0x6f, - 0x17, 0x8b, 0xc5, 0xe2, 0x47, 0xc2, 0xb2, 0x62, 0x9f, 0x0f, 0x64, 0xac, 0xe3, 0xcf, 0x15, 0xdb, + 0x17, 0xc0, 0x62, 0xf7, 0x47, 0xc2, 0xb2, 0x62, 0x9f, 0x0f, 0x64, 0xac, 0xe3, 0xcf, 0x15, 0xdb, 0xc1, 0x0f, 0xd2, 0x50, 0xcc, 0x5f, 0xed, 0xc6, 0x92, 0xbb, 0x05, 0xf3, 0x69, 0x97, 0xe8, 0x16, 0x2c, 0x06, 0xbc, 0x27, 0x94, 0x96, 0x4c, 0x8b, 0x38, 0x3a, 0x3e, 0x24, 0x8b, 0xd0, 0x10, 0xa1, 0x57, 0xdf, 0xaa, 0x6f, 0x37, 0x83, 0x86, 0x08, 0xe9, 0x26, 0xc0, 0xd7, 0x9d, 0x17, 0x27, 0xdf, @@ -2421,96 +2432,97 @@ var fileDescriptor0 = []byte{ 0x35, 0x67, 0xac, 0x3f, 0xe4, 0x5e, 0x13, 0x0d, 0x8b, 0x6a, 0xb2, 0x09, 0x70, 0xc1, 0xfa, 0x22, 0xfc, 0x26, 0xd2, 0xa2, 0xef, 0x5d, 0xc3, 0xa8, 0x19, 0x0d, 0x55, 0xb0, 0xd1, 0xe6, 0xfa, 0xcc, 0x28, 0x72, 0x99, 0xab, 0x0f, 0x4d, 0xdd, 0x83, 0x1b, 0x61, 0x7c, 0xce, 0x44, 0xa4, 0xbc, 0xc6, - 0x56, 0x73, 0x7b, 0x36, 0x48, 0x44, 0x53, 0xd4, 0x28, 0xbe, 0xc4, 0x04, 0x9b, 0x81, 0xf9, 0xa4, - 0xff, 0xac, 0xc3, 0xad, 0x92, 0x90, 0xe4, 0xd7, 0xd0, 0xc2, 0xd4, 0xbc, 0xfa, 0x56, 0x73, 0x7b, - 0x6e, 0x97, 0xee, 0x28, 0xb6, 0x53, 0x62, 0xb7, 0xf3, 0x9c, 0x0d, 0x8e, 0xfa, 0xfc, 0x9c, 0x47, - 0x3a, 0xb0, 0x0e, 0xfe, 0x0b, 0x80, 0x54, 0x49, 0xd6, 0xe0, 0xba, 0x0d, 0xee, 0x4e, 0xc9, 0x49, - 0xe4, 0x33, 0x68, 0xb1, 0xa1, 0x7e, 0xf3, 0x1e, 0xab, 0x3a, 0xb7, 0x7b, 0x6b, 0x07, 0x5b, 0x25, - 0x7f, 0x62, 0xd6, 0x82, 0xfe, 0xb7, 0x01, 0xcb, 0x8f, 0xb9, 0x34, 0xa5, 0xec, 0x32, 0xcd, 0x3b, - 0x9a, 0xe9, 0xa1, 0x32, 0xc0, 0x8a, 0x4b, 0xc1, 0xfa, 0x09, 0xb0, 0x95, 0xc8, 0x0e, 0x10, 0x35, - 0x7c, 0xa5, 0xba, 0x52, 0xbc, 0xe2, 0x72, 0x7f, 0x30, 0x90, 0xf1, 0x05, 0x0f, 0x31, 0xca, 0x4c, - 0x50, 0xb2, 0x82, 0x38, 0x88, 0xe8, 0x8e, 0xcd, 0x49, 0xe6, 0x5c, 0xe3, 0xae, 0x1a, 0x3c, 0x63, - 0x4a, 0x7f, 0x33, 0x08, 0x99, 0xe6, 0xa1, 0x3b, 0xb2, 0xa2, 0x9a, 0x6c, 0xc1, 0x9c, 0xe4, 0x17, - 0xf1, 0x5b, 0x1e, 0x1e, 0x32, 0xcd, 0xbd, 0x16, 0x5a, 0x65, 0x55, 0xe4, 0x3e, 0x2c, 0x38, 0x31, - 0xe0, 0x4c, 0xc5, 0x91, 0x77, 0x1d, 0x6d, 0xf2, 0x4a, 0xf2, 0x2b, 0x58, 0xed, 0x33, 0xa5, 0x8f, - 0xde, 0x0d, 0x84, 0x3d, 0xca, 0x13, 0xd6, 0xeb, 0xf0, 0x48, 0x7b, 0x37, 0xd0, 0xba, 0x7c, 0x91, - 0x50, 0x98, 0x37, 0x09, 0x05, 0x5c, 0x0d, 0xe2, 0x48, 0x71, 0x6f, 0x06, 0x2f, 0x4c, 0x4e, 0x47, - 0x7c, 0x98, 0x89, 0x62, 0xbd, 0xff, 0x5a, 0x73, 0xe9, 0xcd, 0x22, 0xd8, 0x48, 0x26, 0xeb, 0x30, - 0x2b, 0x14, 0xc2, 0xf2, 0xd0, 0x03, 0x2c, 0x53, 0xaa, 0xa0, 0x5b, 0x70, 0xbd, 0x63, 0xeb, 0x5a, - 0x51, 0x6f, 0xba, 0x07, 0xad, 0x80, 0x45, 0x3d, 0x0c, 0xc2, 0x99, 0xec, 0x0b, 0xae, 0xb4, 0xeb, - 0xcb, 0x91, 0x6c, 0x9c, 0xfb, 0x4c, 0x9b, 0x95, 0x06, 0xae, 0x38, 0x89, 0x6e, 0x40, 0xeb, 0x71, - 0x3c, 0x8c, 0x34, 0x59, 0x81, 0x56, 0xd7, 0x7c, 0x38, 0x4f, 0x2b, 0xd0, 0xef, 0xe0, 0x2e, 0x2e, - 0x67, 0x4e, 0x5f, 0x1d, 0x5c, 0x9d, 0xb0, 0x73, 0x3e, 0xba, 0x13, 0x77, 0xa1, 0x25, 0x4d, 0x78, - 0x74, 0x9c, 0xdb, 0x9d, 0x35, 0x7d, 0x8a, 0xf9, 0x04, 0x56, 0x6f, 0x90, 0x23, 0xe3, 0xe0, 0xae, - 0x82, 0x15, 0xe8, 0x5f, 0xeb, 0x30, 0x8f, 0xd0, 0x0e, 0x8e, 0xfc, 0x0e, 0xe6, 0xbb, 0x19, 0xd9, - 0xb5, 0xfd, 0x1d, 0x03, 0x97, 0xb5, 0xcb, 0xf6, 0x7b, 0xce, 0xc1, 0xff, 0x22, 0xd7, 0xf6, 0x04, - 0xae, 0x99, 0x40, 0xae, 0x56, 0xf8, 0x9d, 0xee, 0xb1, 0x91, 0xdd, 0xe3, 0x29, 0x6c, 0x60, 0x80, - 0xec, 0x70, 0x54, 0x07, 0x57, 0xc7, 0xa7, 0xc9, 0x0e, 0xcd, 0x8c, 0x1b, 0xb8, 0x39, 0xd8, 0x10, - 0x83, 0x74, 0xc7, 0x8d, 0xf2, 0x1d, 0xd3, 0xbf, 0xd5, 0xe1, 0x1e, 0x42, 0x1e, 0x47, 0x17, 0x1f, - 0x3f, 0x4c, 0x7c, 0x98, 0x79, 0x13, 0x2b, 0x8d, 0xbb, 0xb1, 0x13, 0x70, 0x24, 0xa7, 0xa9, 0x34, - 0x2b, 0x52, 0xe9, 0x00, 0xc1, 0x4c, 0x5e, 0xc8, 0x90, 0xcb, 0x51, 0xe8, 0x75, 0x98, 0x65, 0x5d, - 0xdc, 0xfd, 0x28, 0x6a, 0xaa, 0x98, 0xbe, 0xbf, 0x43, 0x58, 0x69, 0x73, 0xdd, 0x79, 0xfc, 0x32, - 0xe0, 0x5d, 0x2e, 0x06, 0x3a, 0x81, 0xad, 0x9a, 0x08, 0x2b, 0xd0, 0xea, 0xc7, 0xbd, 0xe3, 0x43, - 0x97, 0xbe, 0x15, 0xe8, 0x57, 0xb0, 0x82, 0xa9, 0x3d, 0xf9, 0xc3, 0xe1, 0x49, 0x87, 0x6b, 0x95, - 0x41, 0xb9, 0x14, 0x51, 0x18, 0x5f, 0xba, 0xcc, 0x9c, 0x54, 0x3d, 0x54, 0xe9, 0x43, 0x58, 0x71, - 0x20, 0x47, 0xef, 0x84, 0x4a, 0x91, 0x32, 0x1e, 0xf5, 0xbc, 0xc7, 0x29, 0x6c, 0x9d, 0x4a, 0x7e, - 0x21, 0xe2, 0xa1, 0xca, 0xb4, 0x76, 0xde, 0xbb, 0x6a, 0x70, 0xae, 0x40, 0x4b, 0xf2, 0x64, 0x37, - 0xcd, 0xc0, 0x0a, 0xe6, 0x9e, 0x5a, 0x77, 0xe3, 0xc7, 0xf1, 0x0b, 0xfd, 0x66, 0x02, 0x27, 0xd1, - 0xa7, 0xb0, 0xf1, 0x9c, 0xc9, 0xb7, 0x99, 0x78, 0x41, 0x32, 0x7d, 0x26, 0x97, 0x8f, 0xc0, 0xb5, - 0x6e, 0x1c, 0x72, 0x17, 0x0f, 0xbf, 0x69, 0x07, 0x56, 0xf7, 0xc3, 0x30, 0x87, 0x65, 0x41, 0x96, - 0xa0, 0x19, 0x72, 0x99, 0xbc, 0xda, 0x21, 0x97, 0xe5, 0xf9, 0x1a, 0x50, 0x33, 0xa1, 0xb0, 0x71, - 0xe6, 0x03, 0xfc, 0xa6, 0x0f, 0x61, 0xad, 0x08, 0xea, 0xe6, 0x97, 0xa9, 0x85, 0xe8, 0x25, 0x83, - 0xc5, 0xd4, 0x02, 0x25, 0xfa, 0x9f, 0x3a, 0xf8, 0x1d, 0xd1, 0x8b, 0x78, 0xd6, 0xeb, 0xa5, 0x38, - 0xe7, 0x4a, 0xb3, 0xf3, 0x41, 0x91, 0x60, 0x98, 0x07, 0x58, 0x75, 0xf5, 0x19, 0x97, 0x4a, 0xc4, - 0x91, 0xcb, 0x27, 0xa3, 0x49, 0x1b, 0xa5, 0x99, 0x69, 0x14, 0xd3, 0xad, 0x3a, 0x81, 0x74, 0x4f, - 0x40, 0xaa, 0x30, 0x98, 0xfc, 0x9d, 0xe6, 0x91, 0x01, 0x50, 0x38, 0xfb, 0xe7, 0x83, 0x8c, 0xc6, - 0x78, 0x2b, 0xd1, 0x8b, 0x98, 0x1e, 0x4a, 0x8e, 0x63, 0x7f, 0x3e, 0x48, 0x15, 0xe4, 0xe7, 0xb0, - 0xdc, 0xcd, 0xbc, 0x6c, 0xb6, 0xfc, 0x37, 0x30, 0xfa, 0xf8, 0x02, 0x7d, 0x04, 0x9f, 0xda, 0x33, - 0xcb, 0xdf, 0xe8, 0x83, 0xab, 0x43, 0x6c, 0x8d, 0x29, 0x9d, 0x43, 0xff, 0x0c, 0xf7, 0x27, 0xbb, - 0xbb, 0x6a, 0xaf, 0xc3, 0xec, 0x6b, 0x11, 0xb1, 0xbe, 0x78, 0xcf, 0x93, 0xea, 0xa5, 0x0a, 0xd3, - 0xd5, 0x03, 0x4b, 0xaf, 0x5c, 0x05, 0x13, 0x91, 0x6e, 0xc2, 0x3c, 0xde, 0xf3, 0xec, 0xe0, 0xca, - 0xf2, 0xbb, 0x67, 0x40, 0x13, 0x7e, 0x83, 0x76, 0xe5, 0x73, 0xa9, 0x78, 0x68, 0x6b, 0x70, 0x9d, - 0x75, 0xbb, 0x7a, 0xd4, 0x40, 0x4e, 0xa2, 0x6d, 0xb8, 0xdd, 0xe6, 0x76, 0xb0, 0x3c, 0x89, 0x65, - 0xee, 0x4d, 0x48, 0x5d, 0xea, 0x59, 0x97, 0x8a, 0xa7, 0xe0, 0x1f, 0x75, 0xf0, 0xda, 0x5c, 0xff, - 0x68, 0x94, 0xcb, 0x30, 0x0b, 0xc9, 0xbf, 0x1f, 0x0a, 0xc9, 0xcf, 0x76, 0x4d, 0xd4, 0xf7, 0x0a, - 0xdb, 0x6a, 0x26, 0x28, 0xaa, 0xe9, 0xdf, 0xeb, 0xb0, 0x58, 0xe0, 0x65, 0xbf, 0x4c, 0x78, 0x93, - 0x7d, 0xa0, 0x36, 0xcc, 0x74, 0x9c, 0x40, 0xc9, 0xd0, 0xf6, 0xff, 0x4f, 0xc9, 0x9e, 0xc1, 0xdd, - 0xfd, 0x30, 0x2c, 0xa3, 0xd9, 0xa3, 0xca, 0x7d, 0x96, 0x4f, 0x74, 0x12, 0xda, 0x7d, 0x58, 0x2a, - 0x10, 0x7b, 0x2c, 0x9b, 0x08, 0x93, 0xc1, 0x69, 0x3e, 0x77, 0xff, 0xbd, 0x02, 0x4b, 0x1d, 0x1d, - 0x4b, 0xd6, 0x4b, 0x1a, 0x58, 0x5f, 0x91, 0x3d, 0xb8, 0xd9, 0xe6, 0xb9, 0xb7, 0x93, 0x10, 0x7c, - 0x30, 0x72, 0xc7, 0xe3, 0x13, 0x1b, 0x3d, 0xab, 0xa5, 0x35, 0xf2, 0x1b, 0x7c, 0x48, 0xb2, 0xca, - 0x83, 0x2b, 0xf3, 0xd3, 0x63, 0xd1, 0x20, 0xa4, 0x3f, 0x45, 0x2a, 0xbc, 0x7f, 0x0b, 0x4b, 0xc5, - 0xb6, 0x21, 0xb7, 0xc6, 0x8e, 0xe3, 0xf8, 0xd0, 0x2f, 0xdb, 0x3a, 0xad, 0x91, 0x97, 0xd8, 0xc0, - 0x65, 0x35, 0x24, 0xc8, 0xb6, 0x27, 0xff, 0x8e, 0xa9, 0x42, 0x3d, 0x83, 0xb5, 0xf2, 0x1f, 0x11, - 0xe4, 0x9e, 0x03, 0xad, 0xfe, 0x81, 0xe1, 0xdf, 0xae, 0x60, 0xf9, 0xb4, 0x46, 0x7e, 0x01, 0x8b, - 0x6d, 0x9e, 0x25, 0x62, 0x04, 0x8c, 0xb1, 0x9d, 0x4c, 0xfe, 0xb2, 0x4d, 0x26, 0xb3, 0x4c, 0x6b, - 0x64, 0x0f, 0xcb, 0x3b, 0xce, 0xdc, 0xb3, 0x8e, 0xab, 0x48, 0xb0, 0x8a, 0x26, 0xb4, 0x46, 0x1e, - 0xc2, 0xda, 0x18, 0xf5, 0xb3, 0x3c, 0x33, 0x25, 0x04, 0xfe, 0xec, 0x88, 0x9e, 0xd1, 0x1a, 0xe9, - 0x80, 0x57, 0x45, 0x16, 0xc9, 0xa7, 0x23, 0xc3, 0x6a, 0x2a, 0xe9, 0x2f, 0x15, 0xc9, 0x1e, 0xad, - 0x91, 0xef, 0x1c, 0x3b, 0xcb, 0xbb, 0x1d, 0xbd, 0x63, 0x5d, 0xfd, 0x91, 0xc8, 0x5f, 0xb9, 0x0d, - 0x8e, 0xf1, 0x3e, 0x7b, 0x50, 0x13, 0x39, 0x61, 0x7e, 0xe3, 0xcf, 0xe1, 0x4e, 0x85, 0x35, 0xd6, - 0xeb, 0x43, 0xe1, 0x1e, 0x81, 0x8f, 0x9f, 0xa5, 0xb7, 0xbb, 0xf4, 0x76, 0xe5, 0xdc, 0x77, 0x61, - 0x2e, 0x43, 0xf9, 0xc8, 0xda, 0x68, 0x2d, 0xc7, 0x01, 0xf3, 0x3e, 0xa7, 0x2e, 0x64, 0x29, 0x61, - 0x25, 0x3f, 0x19, 0x99, 0x4e, 0x22, 0xb4, 0x79, 0xc4, 0xa7, 0xb0, 0x90, 0xe3, 0x88, 0xc4, 0x73, - 0xdd, 0x3f, 0x46, 0x1b, 0xfd, 0x4d, 0x6c, 0xc7, 0x4a, 0x16, 0x41, 0x6b, 0xe4, 0x0b, 0x58, 0xc8, - 0x51, 0x45, 0x0b, 0x56, 0xc6, 0x1e, 0xf3, 0x49, 0x7c, 0x09, 0x0b, 0x39, 0x62, 0x68, 0xfd, 0xca, - 0xb8, 0xa2, 0x8f, 0x77, 0xc2, 0xaa, 0x68, 0x8d, 0xbc, 0x80, 0x4f, 0x2a, 0xf9, 0x21, 0xb9, 0x6f, - 0x4c, 0xa7, 0xd1, 0xc7, 0x02, 0xe0, 0x1e, 0xdc, 0x3c, 0xe1, 0x97, 0x85, 0x31, 0x39, 0x36, 0xd4, - 0x2a, 0x06, 0xdd, 0x97, 0x40, 0xec, 0x4f, 0xdd, 0xa9, 0xfe, 0x73, 0x56, 0x77, 0x74, 0x3e, 0xd0, - 0x57, 0xb4, 0x46, 0x8e, 0xe0, 0xf6, 0x09, 0xbf, 0x2c, 0x9d, 0x70, 0x65, 0xd3, 0xab, 0x6a, 0xa4, - 0xfd, 0x1e, 0x7c, 0x1b, 0xff, 0x87, 0x23, 0x15, 0x12, 0xd9, 0x83, 0xd5, 0x27, 0x8e, 0xc0, 0x7c, - 0xb8, 0xf3, 0xd7, 0xb0, 0x56, 0x4e, 0x9c, 0xed, 0xcd, 0x9a, 0x48, 0xaa, 0x8b, 0x58, 0xc7, 0xb0, - 0x98, 0xa7, 0xb8, 0xe4, 0x13, 0x7c, 0x31, 0xca, 0xb8, 0xb4, 0xef, 0x97, 0x2d, 0x59, 0x8e, 0x86, - 0xcf, 0xcf, 0xc2, 0x7e, 0x18, 0x66, 0x3a, 0x7c, 0x4a, 0x1f, 0x17, 0x53, 0x51, 0xb0, 0x3e, 0x89, - 0x0d, 0x92, 0x9f, 0xda, 0x8b, 0x3e, 0x95, 0x6e, 0xfa, 0xdb, 0xd3, 0x0d, 0x47, 0x49, 0xef, 0xc1, - 0xda, 0x21, 0x67, 0x5d, 0x2d, 0x2e, 0xc6, 0xdb, 0x69, 0x7c, 0xae, 0x14, 0x32, 0x7e, 0x04, 0xb7, - 0x53, 0xe7, 0x1f, 0xf0, 0xee, 0x16, 0xdc, 0x1f, 0xc0, 0xcc, 0x09, 0xbf, 0xc4, 0x29, 0x44, 0xdc, - 0x12, 0x0a, 0x7e, 0x56, 0xc0, 0x97, 0x87, 0x74, 0x1c, 0xb1, 0x3c, 0x95, 0x71, 0x97, 0x2b, 0x25, - 0xa2, 0x5e, 0xa9, 0x47, 0x82, 0xfc, 0x33, 0x58, 0x48, 0x3c, 0x8e, 0xa4, 0x8c, 0xe5, 0x34, 0xe3, - 0xa4, 0x17, 0xab, 0x73, 0x49, 0x8d, 0x67, 0x12, 0x92, 0x4b, 0xf0, 0x11, 0xc9, 0x12, 0xec, 0x62, - 0xe2, 0x7f, 0x82, 0x3b, 0x13, 0xf8, 0x35, 0x79, 0x90, 0x7d, 0xff, 0xab, 0x09, 0xb8, 0x4f, 0xc6, - 0x29, 0xe5, 0x88, 0xed, 0xe4, 0xe8, 0x36, 0xb9, 0xe3, 0x10, 0xcb, 0x48, 0x78, 0x31, 0xb9, 0x36, - 0x2c, 0x8f, 0x91, 0x6c, 0xb2, 0xee, 0x00, 0x3e, 0x24, 0x91, 0x6f, 0xc1, 0xab, 0xa2, 0x9e, 0xf6, - 0x31, 0x9e, 0x42, 0x4c, 0xfd, 0x95, 0x92, 0x5e, 0x51, 0xb4, 0x76, 0x70, 0xe3, 0x8f, 0x2d, 0xfc, - 0xa7, 0xfa, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x5e, 0x68, 0x89, 0xbd, 0xd8, 0x16, 0x00, 0x00, + 0x56, 0x73, 0x7b, 0x36, 0x48, 0x44, 0x73, 0xa8, 0x51, 0x7c, 0x89, 0x09, 0x36, 0x03, 0xf3, 0x49, + 0xff, 0x59, 0x87, 0x5b, 0x25, 0x21, 0xc9, 0xaf, 0xa1, 0x85, 0xa9, 0x79, 0xf5, 0xad, 0xe6, 0xf6, + 0xdc, 0x2e, 0xdd, 0x51, 0x6c, 0xa7, 0xc4, 0x6e, 0xe7, 0x39, 0x1b, 0x1c, 0xf5, 0xf9, 0x39, 0x8f, + 0x74, 0x60, 0x1d, 0xfc, 0x17, 0x00, 0xa9, 0x92, 0xac, 0xc1, 0x75, 0x1b, 0xdc, 0xdd, 0x92, 0x93, + 0xc8, 0x67, 0xd0, 0x62, 0x43, 0xfd, 0xe6, 0x3d, 0x9e, 0xea, 0xdc, 0xee, 0xad, 0x1d, 0x2c, 0x95, + 0xfc, 0x8d, 0x59, 0x0b, 0xfa, 0xdf, 0x06, 0x2c, 0x3f, 0xe6, 0xd2, 0x1c, 0x65, 0x97, 0x69, 0xde, + 0xd1, 0x4c, 0x0f, 0x95, 0x01, 0x56, 0x5c, 0x0a, 0xd6, 0x4f, 0x80, 0xad, 0x44, 0x76, 0x80, 0xa8, + 0xe1, 0x2b, 0xd5, 0x95, 0xe2, 0x15, 0x97, 0xfb, 0x83, 0x81, 0x8c, 0x2f, 0x78, 0x88, 0x51, 0x66, + 0x82, 0x92, 0x15, 0xc4, 0x41, 0x44, 0x77, 0x6d, 0x4e, 0x32, 0xf7, 0x1a, 0x77, 0xd5, 0xe0, 0x19, + 0x53, 0xfa, 0x9b, 0x41, 0xc8, 0x34, 0x0f, 0xdd, 0x95, 0x15, 0xd5, 0x64, 0x0b, 0xe6, 0x24, 0xbf, + 0x88, 0xdf, 0xf2, 0xf0, 0x90, 0x69, 0xee, 0xb5, 0xd0, 0x2a, 0xab, 0x22, 0xf7, 0x61, 0xc1, 0x89, + 0x01, 0x67, 0x2a, 0x8e, 0xbc, 0xeb, 0x68, 0x93, 0x57, 0x92, 0x5f, 0xc1, 0x6a, 0x9f, 0x29, 0x7d, + 0xf4, 0x6e, 0x20, 0xec, 0x55, 0x9e, 0xb0, 0x5e, 0x87, 0x47, 0xda, 0xbb, 0x81, 0xd6, 0xe5, 0x8b, + 0x84, 0xc2, 0xbc, 0x49, 0x28, 0xe0, 0x6a, 0x10, 0x47, 0x8a, 0x7b, 0x33, 0xf8, 0x60, 0x72, 0x3a, + 0xe2, 0xc3, 0x4c, 0x14, 0xeb, 0xfd, 0xd7, 0x9a, 0x4b, 0x6f, 0x16, 0xc1, 0x46, 0x32, 0x59, 0x87, + 0x59, 0xa1, 0x10, 0x96, 0x87, 0x1e, 0xe0, 0x31, 0xa5, 0x0a, 0xba, 0x05, 0xd7, 0x3b, 0xf6, 0x5c, + 0x2b, 0xce, 0x9b, 0xee, 0x41, 0x2b, 0x60, 0x51, 0x0f, 0x83, 0x70, 0x26, 0xfb, 0x82, 0x2b, 0xed, + 0xea, 0x72, 0x24, 0x1b, 0xe7, 0x3e, 0xd3, 0x66, 0xa5, 0x81, 0x2b, 0x4e, 0xa2, 0x1b, 0xd0, 0x7a, + 0x1c, 0x0f, 0x23, 0x4d, 0x56, 0xa0, 0xd5, 0x35, 0x1f, 0xce, 0xd3, 0x0a, 0xf4, 0x3b, 0xb8, 0x8b, + 0xcb, 0x99, 0xdb, 0x57, 0x07, 0x57, 0x27, 0xec, 0x9c, 0x8f, 0xde, 0xc4, 0x5d, 0x68, 0x49, 0x13, + 0x1e, 0x1d, 0xe7, 0x76, 0x67, 0x4d, 0x9d, 0x62, 0x3e, 0x81, 0xd5, 0x1b, 0xe4, 0xc8, 0x38, 0xb8, + 0xa7, 0x60, 0x05, 0xfa, 0xd7, 0x3a, 0xcc, 0x23, 0xb4, 0x83, 0x23, 0xbf, 0x83, 0xf9, 0x6e, 0x46, + 0x76, 0x65, 0x7f, 0xc7, 0xc0, 0x65, 0xed, 0xb2, 0xf5, 0x9e, 0x73, 0xf0, 0xbf, 0xc8, 0x95, 0x3d, + 0x81, 0x6b, 0x26, 0x90, 0x3b, 0x2b, 0xfc, 0x4e, 0xf7, 0xd8, 0xc8, 0xee, 0xf1, 0x14, 0x36, 0x30, + 0x40, 0xb6, 0x39, 0xaa, 0x83, 0xab, 0xe3, 0xd3, 0x64, 0x87, 0xa6, 0xc7, 0x0d, 0x5c, 0x1f, 0x6c, + 0x88, 0x41, 0xba, 0xe3, 0x46, 0xf9, 0x8e, 0xe9, 0xdf, 0xea, 0x70, 0x0f, 0x21, 0x8f, 0xa3, 0x8b, + 0x8f, 0x6f, 0x26, 0x3e, 0xcc, 0xbc, 0x89, 0x95, 0xc6, 0xdd, 0xd8, 0x0e, 0x38, 0x92, 0xd3, 0x54, + 0x9a, 0x15, 0xa9, 0x74, 0x80, 0x60, 0x26, 0x2f, 0x64, 0xc8, 0xe5, 0x28, 0xf4, 0x3a, 0xcc, 0xb2, + 0x2e, 0xee, 0x7e, 0x14, 0x35, 0x55, 0x4c, 0xdf, 0xdf, 0x21, 0xac, 0xb4, 0xb9, 0xee, 0x3c, 0x7e, + 0x19, 0xf0, 0x2e, 0x17, 0x03, 0x9d, 0xc0, 0x56, 0x75, 0x84, 0x15, 0x68, 0xf5, 0xe3, 0xde, 0xf1, + 0xa1, 0x4b, 0xdf, 0x0a, 0xf4, 0x2b, 0x58, 0xc1, 0xd4, 0x9e, 0xfc, 0xe1, 0xf0, 0xa4, 0xc3, 0xb5, + 0xca, 0xa0, 0x5c, 0x8a, 0x28, 0x8c, 0x2f, 0x5d, 0x66, 0x4e, 0xaa, 0x6e, 0xaa, 0xf4, 0x21, 0xac, + 0x38, 0x90, 0xa3, 0x77, 0x42, 0xa5, 0x48, 0x19, 0x8f, 0x7a, 0xde, 0xe3, 0x14, 0xb6, 0x4e, 0x25, + 0xbf, 0x10, 0xf1, 0x50, 0x65, 0x4a, 0x3b, 0xef, 0x5d, 0xd5, 0x38, 0x57, 0xa0, 0x25, 0x79, 0xb2, + 0x9b, 0x66, 0x60, 0x05, 0xf3, 0x4e, 0xad, 0xbb, 0xf1, 0xe3, 0xf8, 0x85, 0x7e, 0x33, 0x81, 0x93, + 0xe8, 0x53, 0xd8, 0x78, 0xce, 0xe4, 0xdb, 0x4c, 0xbc, 0x20, 0xe9, 0x3e, 0x93, 0x8f, 0x8f, 0xc0, + 0xb5, 0x6e, 0x1c, 0x72, 0x17, 0x0f, 0xbf, 0xe9, 0x5b, 0x58, 0xdd, 0x0f, 0xc3, 0x1c, 0x96, 0x05, + 0x59, 0x82, 0x66, 0xc8, 0x65, 0x32, 0xb5, 0x43, 0x2e, 0xcb, 0xf3, 0x35, 0xa0, 0xa6, 0x43, 0x61, + 0xe1, 0xcc, 0x07, 0xf8, 0x6d, 0x12, 0x10, 0x4a, 0x0d, 0x47, 0x8d, 0xd6, 0x49, 0xf4, 0x21, 0xac, + 0x15, 0x83, 0xb9, 0xbe, 0x66, 0xce, 0x48, 0xf4, 0x92, 0x86, 0x63, 0xce, 0x08, 0x25, 0xfa, 0x9f, + 0x3a, 0xf8, 0x1d, 0xd1, 0x8b, 0x78, 0xd6, 0xeb, 0xa5, 0x38, 0xe7, 0x4a, 0xb3, 0xf3, 0x41, 0x91, + 0x78, 0x98, 0xc1, 0xac, 0xba, 0xfa, 0x8c, 0x4b, 0x25, 0xe2, 0xc8, 0xe5, 0x99, 0xd1, 0xa4, 0x05, + 0xd4, 0xcc, 0x14, 0x90, 0xa9, 0x62, 0x9d, 0x40, 0xba, 0x8c, 0x53, 0x85, 0xc1, 0xe4, 0xef, 0x34, + 0x8f, 0x0c, 0x80, 0xc2, 0x99, 0x30, 0x1f, 0x64, 0x34, 0xc6, 0x5b, 0x89, 0x5e, 0xc4, 0xf4, 0x50, + 0x72, 0x1c, 0x07, 0xf3, 0x41, 0xaa, 0x20, 0x3f, 0x87, 0xe5, 0x6e, 0x66, 0xe2, 0xd9, 0x6b, 0xb9, + 0x81, 0xd1, 0xc7, 0x17, 0xe8, 0x23, 0xf8, 0xd4, 0xde, 0x65, 0xfe, 0xa5, 0x1f, 0x5c, 0x1d, 0x62, + 0xc9, 0x4c, 0xa9, 0x28, 0xfa, 0x67, 0xb8, 0x3f, 0xd9, 0xdd, 0x9d, 0xf6, 0x3a, 0xcc, 0xbe, 0x16, + 0x11, 0xeb, 0x8b, 0xf7, 0x3c, 0x39, 0xbd, 0x54, 0x61, 0xaa, 0x7d, 0x60, 0x69, 0x97, 0x3b, 0xc1, + 0x44, 0xa4, 0x9b, 0x30, 0x8f, 0xef, 0x3f, 0xdb, 0xd0, 0xb2, 0xbc, 0xef, 0x19, 0xd0, 0x84, 0xf7, + 0xa0, 0x5d, 0x79, 0xbf, 0x2a, 0x5e, 0xda, 0x1a, 0x5c, 0x67, 0xdd, 0xae, 0x1e, 0x15, 0x96, 0x93, + 0x68, 0x1b, 0x6e, 0xb7, 0xb9, 0x6d, 0x38, 0x4f, 0x62, 0x99, 0x9b, 0x15, 0xa9, 0x4b, 0x3d, 0xeb, + 0x52, 0x31, 0x22, 0xfe, 0x51, 0x07, 0xaf, 0xcd, 0xf5, 0x8f, 0x46, 0xc5, 0x0c, 0xe3, 0x90, 0xfc, + 0xfb, 0xa1, 0x90, 0xfc, 0x6c, 0xd7, 0x44, 0x7d, 0xaf, 0xb0, 0xac, 0x66, 0x82, 0xa2, 0x9a, 0xfe, + 0xbd, 0x0e, 0x8b, 0x05, 0xbe, 0xf6, 0xcb, 0x84, 0x4f, 0xd9, 0xc1, 0xb5, 0x61, 0xba, 0xe6, 0x04, + 0xaa, 0x86, 0xb6, 0xff, 0x7f, 0xaa, 0xf6, 0x0c, 0xee, 0xee, 0x87, 0x61, 0x19, 0xfd, 0x1e, 0x9d, + 0xdc, 0x67, 0xf9, 0x44, 0x27, 0xa1, 0xdd, 0x87, 0xa5, 0x02, 0xe1, 0xc7, 0x63, 0x13, 0x61, 0xd2, + 0x50, 0xcd, 0xe7, 0xee, 0xbf, 0x57, 0x60, 0xa9, 0xa3, 0x63, 0xc9, 0x7a, 0x49, 0x01, 0xeb, 0x2b, + 0xb2, 0x07, 0x37, 0xdb, 0x3c, 0x37, 0x53, 0x09, 0xc1, 0x41, 0x92, 0xbb, 0x1e, 0x9f, 0xd8, 0xe8, + 0x59, 0x2d, 0xad, 0x91, 0xdf, 0xe0, 0x80, 0xc9, 0x2a, 0x0f, 0xae, 0xcc, 0x4f, 0x92, 0x45, 0x83, + 0x90, 0xfe, 0x44, 0xa9, 0xf0, 0xfe, 0x2d, 0x2c, 0x15, 0xcb, 0x86, 0xdc, 0x1a, 0xbb, 0x8e, 0xe3, + 0x43, 0xbf, 0x6c, 0xeb, 0xb4, 0x46, 0x5e, 0x62, 0x01, 0x97, 0x9d, 0x21, 0x41, 0x16, 0x3e, 0xf9, + 0xf7, 0x4d, 0x15, 0xea, 0x19, 0xac, 0x95, 0xff, 0xb8, 0x20, 0xf7, 0x1c, 0x68, 0xf5, 0x0f, 0x0f, + 0xff, 0x76, 0x05, 0xfb, 0xa7, 0x35, 0xf2, 0x0b, 0x58, 0x6c, 0xf3, 0x2c, 0x41, 0x23, 0x60, 0x8c, + 0x6d, 0x67, 0xf2, 0x97, 0x6d, 0x32, 0x99, 0x65, 0x5a, 0x23, 0x7b, 0x78, 0xbc, 0xe3, 0x8c, 0x3e, + 0xeb, 0xb8, 0x8a, 0xc4, 0xab, 0x68, 0x42, 0x6b, 0xe4, 0x21, 0xac, 0x8d, 0x51, 0x42, 0xcb, 0x3f, + 0x53, 0xa2, 0xe0, 0xcf, 0x8e, 0x68, 0x1b, 0xad, 0x91, 0x0e, 0x78, 0x55, 0x24, 0x92, 0x7c, 0x3a, + 0x32, 0xac, 0xa6, 0x98, 0xfe, 0x52, 0x91, 0x04, 0xd2, 0x1a, 0xf9, 0xce, 0xb1, 0xb6, 0xbc, 0xdb, + 0xd1, 0x3b, 0xd6, 0xd5, 0x1f, 0x89, 0xfc, 0x95, 0xdb, 0xe0, 0x18, 0x1f, 0xb4, 0x17, 0x35, 0x91, + 0x2b, 0xe6, 0x37, 0xfe, 0x1c, 0xee, 0x54, 0x58, 0xe3, 0x79, 0x7d, 0x28, 0xdc, 0x23, 0xf0, 0xf1, + 0xb3, 0xf4, 0x75, 0x97, 0xbe, 0xae, 0x9c, 0xfb, 0x2e, 0xcc, 0x65, 0xa8, 0x20, 0x59, 0x1b, 0xad, + 0xe5, 0xb8, 0x61, 0xde, 0xe7, 0xd4, 0x85, 0x2c, 0x25, 0xb2, 0xe4, 0x27, 0x23, 0xd3, 0x49, 0x44, + 0x37, 0x8f, 0xf8, 0x14, 0x16, 0x72, 0xdc, 0x91, 0x78, 0xae, 0xfa, 0xc7, 0xe8, 0xa4, 0xbf, 0x89, + 0xe5, 0x58, 0xc9, 0x22, 0x68, 0x8d, 0x7c, 0x01, 0x0b, 0x39, 0x0a, 0x69, 0xc1, 0xca, 0x58, 0x65, + 0x3e, 0x89, 0x2f, 0x61, 0x21, 0x47, 0x18, 0xad, 0x5f, 0x19, 0x87, 0xf4, 0xf1, 0x4d, 0x58, 0x15, + 0xad, 0x91, 0x17, 0xf0, 0x49, 0x25, 0x6f, 0x24, 0xf7, 0x8d, 0xe9, 0x34, 0x5a, 0x59, 0x00, 0xdc, + 0x83, 0x9b, 0x27, 0xfc, 0xb2, 0xd0, 0x26, 0xc7, 0x9a, 0x5a, 0x45, 0xa3, 0xfb, 0x12, 0x88, 0xfd, + 0x09, 0x3c, 0xd5, 0x7f, 0xce, 0xea, 0x8e, 0xce, 0x07, 0xfa, 0x8a, 0xd6, 0xc8, 0x11, 0xdc, 0x3e, + 0xe1, 0x97, 0xa5, 0x1d, 0xae, 0xac, 0x7b, 0x55, 0xb5, 0xb4, 0xdf, 0x83, 0x6f, 0xe3, 0xff, 0x70, + 0xa4, 0x42, 0x22, 0x7b, 0xb0, 0xfa, 0xc4, 0x11, 0x98, 0x0f, 0x77, 0xfe, 0x1a, 0xd6, 0xca, 0x09, + 0xb5, 0x7d, 0x59, 0x13, 0xc9, 0x76, 0x11, 0xeb, 0x18, 0x16, 0xf3, 0x14, 0x97, 0x7c, 0x82, 0x13, + 0xa3, 0x8c, 0x63, 0xfb, 0x7e, 0xd9, 0x92, 0xe5, 0x68, 0x38, 0x7e, 0x16, 0xf6, 0xc3, 0x30, 0x53, + 0xe1, 0x53, 0xea, 0xb8, 0x98, 0x8a, 0x82, 0xf5, 0x49, 0x6c, 0x90, 0xfc, 0xd4, 0x3e, 0xf4, 0xa9, + 0x74, 0xd3, 0xdf, 0x9e, 0x6e, 0x38, 0x4a, 0x7a, 0x0f, 0xd6, 0x0e, 0x39, 0xeb, 0x6a, 0x71, 0x31, + 0x5e, 0x4e, 0xe3, 0x7d, 0xa5, 0x90, 0xf1, 0x23, 0xb8, 0x9d, 0x3a, 0xff, 0x80, 0xb9, 0x5b, 0x70, + 0x7f, 0x00, 0x33, 0x27, 0xfc, 0x12, 0xbb, 0x10, 0x71, 0x4b, 0x28, 0xf8, 0x59, 0x01, 0x27, 0x0f, + 0xe9, 0x38, 0x62, 0x79, 0x2a, 0xe3, 0x2e, 0x57, 0x4a, 0x44, 0xbd, 0x52, 0x8f, 0x04, 0xf9, 0x67, + 0xb0, 0x90, 0x78, 0x1c, 0x49, 0x19, 0xcb, 0x69, 0xc6, 0x49, 0x2d, 0x56, 0xe7, 0x92, 0x1a, 0xcf, + 0x24, 0x24, 0x97, 0xe0, 0x10, 0xc9, 0x12, 0xec, 0x62, 0xe2, 0x7f, 0x82, 0x3b, 0x13, 0xf8, 0x35, + 0x79, 0x90, 0x9d, 0xff, 0xd5, 0x04, 0xdc, 0x27, 0xe3, 0x94, 0x72, 0xc4, 0x76, 0x72, 0x74, 0x9b, + 0xdc, 0x71, 0x88, 0x65, 0x24, 0xbc, 0x98, 0x5c, 0x1b, 0x96, 0xc7, 0x48, 0x36, 0x59, 0x77, 0x00, + 0x1f, 0x92, 0xc8, 0xb7, 0xe0, 0x55, 0x51, 0x4f, 0x3b, 0x8c, 0xa7, 0x10, 0x53, 0x7f, 0xa5, 0xa4, + 0x56, 0x14, 0xad, 0x1d, 0xdc, 0xf8, 0x63, 0x0b, 0xff, 0xc1, 0xfe, 0x5f, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x0b, 0x21, 0xf7, 0xff, 0xf0, 0x16, 0x00, 0x00, } diff --git a/sa/proto/sa.proto b/sa/proto/sa.proto index c2e2f66ed51..956eec5759c 100644 --- a/sa/proto/sa.proto +++ b/sa/proto/sa.proto @@ -175,6 +175,10 @@ message AddCertificateRequest { // A signed OCSP response for the certificate contained in "der". // Note: The certificate status in the OCSP response is assumed to be 0 (good). optional bytes ocsp = 3; + // An optional issued time. When not present the SA defaults to using + // the current time. The orphan-finder uses this parameter to add + // certificates with the correct historic issued date + optional int64 issued = 4; } message AddCertificateResponse { diff --git a/sa/sa.go b/sa/sa.go index da20f95df50..ae227b2772f 100644 --- a/sa/sa.go +++ b/sa/sa.go @@ -887,7 +887,20 @@ func (ssa *SQLStorageAuthority) RevokeAuthorizationsByDomain(ctx context.Context // AddCertificate stores an issued certificate and returns the digest as // a string, or an error if any occurred. -func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, certDER []byte, regID int64, ocspResponse []byte) (string, error) { +func (ssa *SQLStorageAuthority) AddCertificate( + ctx context.Context, + certDER []byte, + regID int64, + ocspResponse []byte, + issued *time.Time) (string, error) { + // NOTE(@cpu): Historically `AddCertificate` was hardcoded to set the added + // `core.Certificate`'s `Issued` field to the current time. If the `issued` + // parameter is nil then default to using now as the issued time to preserve + // this historic default. + if issued == nil { + now := ssa.clk.Now() + issued = &now + } parsedCertificate, err := x509.ParseCertificate(certDER) if err != nil { return "", err @@ -900,7 +913,7 @@ func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, certDER []by Serial: serial, Digest: digest, DER: certDER, - Issued: ssa.clk.Now(), + Issued: *issued, Expires: parsedCertificate.NotAfter, } diff --git a/sa/sa_test.go b/sa/sa_test.go index ec0f5eca6b1..993debd28bb 100644 --- a/sa/sa_test.go +++ b/sa/sa_test.go @@ -495,13 +495,16 @@ func TestAddCertificate(t *testing.T) { certDER, err := ioutil.ReadFile("www.eff.org.der") test.AssertNotError(t, err, "Couldn't read example cert DER") - digest, err := sa.AddCertificate(ctx, certDER, reg.ID, nil) + digest, err := sa.AddCertificate(ctx, certDER, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add www.eff.org.der") test.AssertEquals(t, digest, "qWoItDZmR4P9eFbeYgXXP3SR4ApnkQj8x4LsB_ORKBo") retrievedCert, err := sa.GetCertificate(ctx, "000000000000000000000000000000021bd4") test.AssertNotError(t, err, "Couldn't get www.eff.org.der by full serial") test.AssertByteEquals(t, certDER, retrievedCert.DER) + // Because nil was provided as the Issued time we expect the cert was stored + // with an issued time equal to now + test.AssertEquals(t, retrievedCert.Issued, clk.Now()) certificateStatus, err := sa.GetCertificateStatus(ctx, "000000000000000000000000000000021bd4") test.AssertNotError(t, err, "Couldn't get status for www.eff.org.der") @@ -515,13 +518,18 @@ func TestAddCertificate(t *testing.T) { test.AssertNotError(t, err, "Couldn't read example cert DER") serial := "ffdd9b8a82126d96f61d378d5ba99a0474f0" - digest2, err := sa.AddCertificate(ctx, certDER2, reg.ID, nil) + // Add the certificate with a specific issued time instead of nil + issuedTime := time.Date(2018, 4, 1, 7, 0, 0, 0, time.UTC) + digest2, err := sa.AddCertificate(ctx, certDER2, reg.ID, nil, &issuedTime) test.AssertNotError(t, err, "Couldn't add test-cert.der") test.AssertEquals(t, digest2, "vrlPN5wIPME1D2PPsCy-fGnTWh8dMyyYQcXPRkjHAQI") retrievedCert2, err := sa.GetCertificate(ctx, serial) test.AssertNotError(t, err, "Couldn't get test-cert.der") test.AssertByteEquals(t, certDER2, retrievedCert2.DER) + // The cert should have been added with the specific issued time we provided + // as the issued field. + test.AssertEquals(t, retrievedCert2.Issued, issuedTime) certificateStatus2, err := sa.GetCertificateStatus(ctx, serial) test.AssertNotError(t, err, "Couldn't get status for test-cert.der") @@ -533,7 +541,7 @@ func TestAddCertificate(t *testing.T) { test.AssertNotError(t, err, "Couldn't read example cert DER") serial = "ffa0160630d618b2eb5c0510824b14274856" ocspResp := []byte{0, 0, 1} - _, err = sa.AddCertificate(ctx, certDER3, reg.ID, ocspResp) + _, err = sa.AddCertificate(ctx, certDER3, reg.ID, ocspResp, nil) test.AssertNotError(t, err, "Couldn't add test-cert2.der") certificateStatus3, err := sa.GetCertificateStatus(ctx, serial) @@ -578,7 +586,7 @@ func TestCountCertificatesByNames(t *testing.T) { // Add the test cert and query for its names. reg := satest.CreateWorkingRegistration(t, sa) - _, err = sa.AddCertificate(ctx, certDER, reg.ID, nil) + _, err = sa.AddCertificate(ctx, certDER, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert.der") // Time range including now should find the cert @@ -620,7 +628,7 @@ func TestCountCertificatesByNames(t *testing.T) { certDER2, err := ioutil.ReadFile("test-cert2.der") test.AssertNotError(t, err, "Couldn't read test-cert2.der") - _, err = sa.AddCertificate(ctx, certDER2, reg.ID, nil) + _, err = sa.AddCertificate(ctx, certDER2, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add test-cert2.der") counts, err = sa.CountCertificatesByNames(ctx, names, yesterday, now.Add(10000*time.Hour)) test.AssertNotError(t, err, "Error counting certs.") @@ -698,7 +706,7 @@ func TestMarkCertificateRevoked(t *testing.T) { // 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(ctx, certDER, reg.ID, nil) + _, err = sa.AddCertificate(ctx, certDER, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add www.eff.org.der") serial := "000000000000000000000000000000021bd4" @@ -736,7 +744,7 @@ func TestCountCertificates(t *testing.T) { // 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(ctx, certDER, reg.ID, nil) + _, err = sa.AddCertificate(ctx, certDER, reg.ID, nil, nil) test.AssertNotError(t, err, "Couldn't add www.eff.org.der") fc.Add(2 * time.Hour) @@ -1021,7 +1029,7 @@ func TestPreviousCertificateExists(t *testing.T) { certDER, err := ioutil.ReadFile("www.eff.org.der") test.AssertNotError(t, err, "reading cert DER") - _, err = sa.AddCertificate(ctx, certDER, reg.ID, nil) + _, err = sa.AddCertificate(ctx, certDER, reg.ID, nil, nil) test.AssertNotError(t, err, "calling AddCertificate") cases := []struct { diff --git a/test/config-next/orphan-finder.json b/test/config-next/orphan-finder.json index 08764946bfe..1f76b8b8a1c 100644 --- a/test/config-next/orphan-finder.json +++ b/test/config-next/orphan-finder.json @@ -1,4 +1,6 @@ { + "backdate": "1h", + "syslog": { "stdoutlevel": 7 },