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
This commit is contained in:
Daniel McCarney 2018-04-19 13:25:12 -04:00 committed by Roland Bracewell Shoemaker
parent cb548e32f0
commit f8f9a158c7
14 changed files with 309 additions and 164 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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")
}

View File

@ -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

View File

@ -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:

View File

@ -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)
// Set an example backdate duration (this is normally read from config)
backdateDuration = time.Hour
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)
testCertDER := "3082045b30820343a003020102021300ffa0160630d618b2eb5c0510824b14274856300d06092a864886f70d01010b0500301f311d301b06035504030c146861707079206861636b65722066616b65204341301e170d3135313030333035323130305a170d3136303130313035323130305a3018311630140603550403130d6578616d706c652e636f2e626e30820122300d06092a864886f70d01010105000382010f003082010a02820101009ea3f1d21fade5596e36a6a77095a94758e4b72466b7444ada4f7c4cf6fde9b1d470b93b65c1fdd896917f248ccae49b57c80dc21c64b010699432130d059d2d8392346e8a179c7c947835549c64a7a5680c518faf0a5cbea48e684fca6304775c8fa9239c34f1d5cb2d063b098bd1c17183c7521efc884641b2f0b41402ac87c7076848d4347cef59dd5a9c174ad25467db933c95ef48c578ba762f527b21666a198fb5e1fe2d8299b4dceb1791e96ad075e3ecb057c776d764fad8f0829d43c32ddf985a3a36fade6966cec89468721a1ec47ab38eac8da4514060ded51d283a787b7c69971bda01f49f76baa41b1f9b4348aa4279e0fa55645d6616441f0d0203010001a382019530820191300e0603551d0f0101ff0404030205a0301d0603551d250416301406082b0601050507030106082b06010505070302300c0603551d130101ff04023000301d0603551d0e04160414369d0c100452b9eb3ffe7ae852e9e839a3ae5adb301f0603551d23041830168014fb784f12f96015832c9f177f3419b32e36ea4189306a06082b06010505070101045e305c302606082b06010505073001861a687474703a2f2f6c6f63616c686f73743a343030322f6f637370303206082b060105050730028626687474703a2f2f6c6f63616c686f73743a343030302f61636d652f6973737565722d6365727430180603551d110411300f820d6578616d706c652e636f2e626e30270603551d1f0420301e301ca01aa0188616687474703a2f2f6578616d706c652e636f6d2f63726c30630603551d20045c305a300a060667810c0102013000304c06032a03043045302206082b060105050702011616687474703a2f2f6578616d706c652e636f6d2f637073301f06082b0601050507020230130c11446f20576861742054686f752057696c74300d06092a864886f70d01010b05000382010100bbb4b994971cafa2e56e2258db46d88bfb361d8bfcd75521c03174e471eaa9f3ff2e719059bb57cc064079496d8550577c127baa84a18e792ddd36bf4f7b874b6d40d1d14288c15d38e4d6be25eb7805b1c3756b3735702eb4585d1886bc8af2c14086d3ce506e55184913c83aaaa8dfe6160bd035e42cda6d97697ed3ee3124c9bf9620a9fe6602191c1b746533c1d4a30023bbe902cb4aa661901177ed924eb836c94cc062dd0ce439c4ece9ee1dfe0499a42cbbcb2ea7243c59f4df4fdd7058229bacf9a640632dbd776b21633137b2df1c41f0765a66f448777aeec7ed4c0cdeb9d8a2356ff813820a287e11d52efde1aa543b4ef2ee992a7a9d5ccf7da4"
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)
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) {

View File

@ -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

View File

@ -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: &regID,
Ocsp: ocspResponse,
Der: der,
RegID: &regID,
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
}

View File

@ -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
}

View File

@ -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,
}

View File

@ -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 {

View File

@ -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,
}

View File

@ -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 {

View File

@ -1,4 +1,6 @@
{
"backdate": "1h",
"syslog": {
"stdoutlevel": 7
},