WFE2: allow revocation of precertificates. (#4433)

When the `features.PrecertificateRevocation` feature flag is enabled the WFE2
will allow revoking certificates for a submitted precertificate. The legacy WFE1
behaviour remains unchanged (as before (pre)certificates issued through the V1
API will be revocable with the V2 API).

Previously the WFE2 vetted the certificate from the revocation request by
looking up a final certificate by the serial number in the requested
certificate, and then doing a byte for byte comparison between the stored and
requested certificate.

Rather than adjust this logic to handle looking up and comparing stored
precertificates against requested precertificates (requiring new RPCs and an
additional round-trip) we choose to instead check the signature on the requested
certificate or precertificate and consider it valid for revocation if the
signature validates with one of the WFE2's known issuers. We trust the integrity
of our own signatures.

An integration test that performs a revocation of a precertificate (in this case
one that never had a final certificate issued due to SCT embedded errors) with
all of the available authentication mechanisms is included.

Resolves https://github.com/letsencrypt/boulder/issues/4414
This commit is contained in:
Daniel McCarney 2019-09-16 16:40:07 -04:00 committed by GitHub
parent 76afea15d8
commit 1cd9733c24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 707 additions and 231 deletions

View File

@ -94,19 +94,19 @@ type config struct {
// loadCertificateFile loads a PEM certificate from the certFile provided. It
// validates that the PEM is well-formed with no leftover bytes, and contains
// only a well-formed X509 certificate. If the cert file meets these
// requirements the PEM bytes from the file are returned, otherwise an error is
// returned. If the PEM contents of a certFile do not have a trailing newline
// one is added.
func loadCertificateFile(aiaIssuerURL, certFile string) ([]byte, error) {
// requirements the PEM bytes from the file are returned along with the parsed
// certificate, otherwise an error is returned. If the PEM contents of
// a certFile do not have a trailing newline one is added.
func loadCertificateFile(aiaIssuerURL, certFile string) ([]byte, *x509.Certificate, error) {
pemBytes, err := ioutil.ReadFile(certFile)
if err != nil {
return nil, fmt.Errorf(
return nil, nil, fmt.Errorf(
"CertificateChain entry for AIA issuer url %q has an "+
"invalid chain file: %q - error reading contents: %s",
aiaIssuerURL, certFile, err)
}
if bytes.Contains(pemBytes, []byte("\r\n")) {
return nil, fmt.Errorf(
return nil, nil, fmt.Errorf(
"CertificateChain entry for AIA issuer url %q has an "+
"invalid chain file: %q - contents had CRLF line endings",
aiaIssuerURL, certFile)
@ -114,22 +114,23 @@ func loadCertificateFile(aiaIssuerURL, certFile string) ([]byte, error) {
// Try to decode the contents as PEM
certBlock, rest := pem.Decode(pemBytes)
if certBlock == nil {
return nil, fmt.Errorf(
return nil, nil, fmt.Errorf(
"CertificateChain entry for AIA issuer url %q has an "+
"invalid chain file: %q - contents did not decode as PEM",
aiaIssuerURL, certFile)
}
// The PEM contents must be a CERTIFICATE
if certBlock.Type != "CERTIFICATE" {
return nil, fmt.Errorf(
return nil, nil, fmt.Errorf(
"CertificateChain entry for AIA issuer url %q has an "+
"invalid chain file: %q - PEM block type incorrect, found "+
"%q, expected \"CERTIFICATE\"",
aiaIssuerURL, certFile, certBlock.Type)
}
// The PEM Certificate must successfully parse
if _, err := x509.ParseCertificate(certBlock.Bytes); err != nil {
return nil, fmt.Errorf(
var cert *x509.Certificate
if cert, err = x509.ParseCertificate(certBlock.Bytes); err != nil {
return nil, nil, fmt.Errorf(
"CertificateChain entry for AIA issuer url %q has an "+
"invalid chain file: %q - certificate bytes failed to parse: %s",
aiaIssuerURL, certFile, err)
@ -137,7 +138,7 @@ func loadCertificateFile(aiaIssuerURL, certFile string) ([]byte, error) {
// If there are bytes leftover we must reject the file otherwise these
// leftover bytes will end up in a served certificate chain.
if len(rest) != 0 {
return nil, fmt.Errorf(
return nil, nil, fmt.Errorf(
"CertificateChain entry for AIA issuer url %q has an "+
"invalid chain file: %q - PEM contents had unused remainder "+
"input (%d bytes)",
@ -147,16 +148,19 @@ func loadCertificateFile(aiaIssuerURL, certFile string) ([]byte, error) {
if pemBytes[len(pemBytes)-1] != '\n' {
pemBytes = append(pemBytes, '\n')
}
return pemBytes, nil
return pemBytes, cert, nil
}
// loadCertificateChains processes the provided chainConfig of AIA Issuer URLs
// and cert filenames. For each AIA issuer URL all of its cert filenames are
// read, validated as PEM certificates, and concatenated together separated by
// newlines. The combined PEM certificate chain contents for each are returned
// in the results map, keyed by the AIA Issuer URL.
func loadCertificateChains(chainConfig map[string][]string) (map[string][]byte, error) {
// in the results map, keyed by the AIA Issuer URL. Additionally the first
// certificate in each chain is parsed and returned in a slice of issuer
// certificates.
func loadCertificateChains(chainConfig map[string][]string) (map[string][]byte, []*x509.Certificate, error) {
results := make(map[string][]byte, len(chainConfig))
var issuerCerts []*x509.Certificate
// For each AIA Issuer URL we need to read the chain cert files
for aiaIssuerURL, certFiles := range chainConfig {
@ -164,7 +168,7 @@ func loadCertificateChains(chainConfig map[string][]string) (map[string][]byte,
// There must be at least one chain file specified
if len(certFiles) == 0 {
return nil, fmt.Errorf(
return nil, nil, fmt.Errorf(
"CertificateChain entry for AIA issuer url %q has no chain "+
"file names configured",
aiaIssuerURL)
@ -172,14 +176,19 @@ func loadCertificateChains(chainConfig map[string][]string) (map[string][]byte,
// certFiles are read and appended in the order they appear in the
// configuration
for _, c := range certFiles {
for i, c := range certFiles {
// Prepend a newline before each chain entry
buffer.Write([]byte("\n"))
// Read and validate the chain file contents
pemBytes, err := loadCertificateFile(aiaIssuerURL, c)
pemBytes, cert, err := loadCertificateFile(aiaIssuerURL, c)
if err != nil {
return nil, err
return nil, nil, err
}
// Save the first certificate as a direct issuer certificate
if i == 0 {
issuerCerts = append(issuerCerts, cert)
}
// Write the PEM bytes to the result buffer for this AIAIssuer
@ -189,7 +198,8 @@ func loadCertificateChains(chainConfig map[string][]string) (map[string][]byte,
// Save the full PEM chain contents
results[aiaIssuerURL] = buffer.Bytes()
}
return results, nil
return results, issuerCerts, nil
}
func setupWFE(c config, logger blog.Logger, stats metrics.Scope, clk clock.Clock) (core.RegistrationAuthority, core.StorageAuthority, noncepb.NonceServiceClient, map[string]noncepb.NonceServiceClient) {
@ -232,7 +242,7 @@ func main() {
err := cmd.ReadConfigFile(*configFile, &c)
cmd.FailOnError(err, "Reading JSON config file into config structure")
certChains, err := loadCertificateChains(c.WFE.CertificateChains)
certChains, issuerCerts, err := loadCertificateChains(c.WFE.CertificateChains)
cmd.FailOnError(err, "Couldn't read configured CertificateChains")
err = features.Set(c.WFE.Features)
@ -248,7 +258,7 @@ func main() {
kp, err := goodkey.NewKeyPolicy("", c.WFE.BlockedKeyFile)
cmd.FailOnError(err, "Unable to create key policy")
rac, sac, rns, npm := setupWFE(c, logger, scope, clk)
wfe, err := wfe2.NewWebFrontEndImpl(scope, clk, kp, certChains, rns, npm, logger)
wfe, err := wfe2.NewWebFrontEndImpl(scope, clk, kp, certChains, issuerCerts, rns, npm, logger)
cmd.FailOnError(err, "Unable to create WFE")
wfe.RA = rac
wfe.SA = sac

View File

@ -43,23 +43,20 @@ func TestLoadCertificateChains(t *testing.T) {
test.AssertNotError(t, err, "ioutil.WriteFile failed")
testCases := []struct {
Name string
Input map[string][]string
ExpectedResult map[string][]byte
ExpectedError error
Name string
Input map[string][]string
ExpectedMap map[string][]byte
ExpectedError error
}{
{
Name: "No input",
Input: nil,
ExpectedResult: nil,
ExpectedError: nil,
Name: "No input",
Input: nil,
},
{
Name: "AIA Issuer without chain files",
Input: map[string][]string{
"http://break.the.chain.com": []string{},
},
ExpectedResult: nil,
ExpectedError: fmt.Errorf(
"CertificateChain entry for AIA issuer url \"http://break.the.chain.com\" " +
"has no chain file names configured"),
@ -69,7 +66,6 @@ func TestLoadCertificateChains(t *testing.T) {
Input: map[string][]string{
"http://where.is.my.mind": []string{"/tmp/does.not.exist.pem"},
},
ExpectedResult: nil,
ExpectedError: fmt.Errorf("CertificateChain entry for AIA issuer url \"http://where.is.my.mind\" " +
"has an invalid chain file: \"/tmp/does.not.exist.pem\" - error reading " +
"contents: open /tmp/does.not.exist.pem: no such file or directory"),
@ -79,7 +75,6 @@ func TestLoadCertificateChains(t *testing.T) {
Input: map[string][]string{
"http://windows.sad.zone": []string{crlfPEM.Name()},
},
ExpectedResult: nil,
ExpectedError: fmt.Errorf("CertificateChain entry for AIA issuer url \"http://windows.sad.zone\" "+
"has an invalid chain file: %q - contents had CRLF line endings", crlfPEM.Name()),
},
@ -88,7 +83,6 @@ func TestLoadCertificateChains(t *testing.T) {
Input: map[string][]string{
"http://ok.go": []string{invalidPEMFile.Name()},
},
ExpectedResult: nil,
ExpectedError: fmt.Errorf(
"CertificateChain entry for AIA issuer url \"http://ok.go\" has an "+
"invalid chain file: %q - contents did not decode as PEM",
@ -99,7 +93,6 @@ func TestLoadCertificateChains(t *testing.T) {
Input: map[string][]string{
"http://not-a-cert.com": []string{"../../test/test-root.key"},
},
ExpectedResult: nil,
ExpectedError: fmt.Errorf(
"CertificateChain entry for AIA issuer url \"http://not-a-cert.com\" has " +
"an invalid chain file: \"../../test/test-root.key\" - PEM block type " +
@ -110,7 +103,6 @@ func TestLoadCertificateChains(t *testing.T) {
Input: map[string][]string{
"http://tasty.leftovers.com": []string{leftoverPEMFile.Name()},
},
ExpectedResult: nil,
ExpectedError: fmt.Errorf(
"CertificateChain entry for AIA issuer url \"http://tasty.leftovers.com\" "+
"has an invalid chain file: %q - PEM contents had unused remainder input "+
@ -124,38 +116,35 @@ func TestLoadCertificateChains(t *testing.T) {
Input: map[string][]string{
"http://single-cert-chain.com": []string{"../../test/test-ca.pem"},
},
ExpectedResult: map[string][]byte{
ExpectedMap: map[string][]byte{
"http://single-cert-chain.com": []byte(fmt.Sprintf("\n%s", string(certBytesA))),
},
ExpectedError: nil,
},
{
Name: "Two PEM file chain",
Input: map[string][]string{
"http://two-cert-chain.com": []string{"../../test/test-ca.pem", "../../test/test-ca2.pem"},
},
ExpectedResult: map[string][]byte{
ExpectedMap: map[string][]byte{
"http://two-cert-chain.com": []byte(fmt.Sprintf("\n%s\n%s", string(certBytesA), string(certBytesB))),
},
ExpectedError: nil,
},
{
Name: "One PEM file chain, no trailing newline",
Input: map[string][]string{
"http://single-cert-chain.nonewline.com": []string{abruptPEM.Name()},
},
ExpectedResult: map[string][]byte{
ExpectedMap: map[string][]byte{
// NOTE(@cpu): There should be a trailing \n added by the WFE that we
// expect in the format specifier below.
"http://single-cert-chain.nonewline.com": []byte(fmt.Sprintf("\n%s\n", string(abruptPEMBytes))),
},
ExpectedError: nil,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
result, err := loadCertificateChains(tc.Input)
resultMap, issuers, err := loadCertificateChains(tc.Input)
if tc.ExpectedError == nil && err != nil {
t.Errorf("Expected nil error, got %#v\n", err)
} else if tc.ExpectedError != nil && err == nil {
@ -163,9 +152,10 @@ func TestLoadCertificateChains(t *testing.T) {
} else if tc.ExpectedError != nil {
test.AssertEquals(t, err.Error(), tc.ExpectedError.Error())
}
test.AssertEquals(t, len(result), len(tc.ExpectedResult))
for url, chain := range result {
test.Assert(t, bytes.Compare(chain, tc.ExpectedResult[url]) == 0, "Chain bytes did not match expected")
test.AssertEquals(t, len(resultMap), len(tc.ExpectedMap))
test.AssertEquals(t, len(issuers), len(tc.ExpectedMap))
for url, chain := range resultMap {
test.Assert(t, bytes.Compare(chain, tc.ExpectedMap[url]) == 0, "Chain bytes did not match expected")
}
})
}

View File

@ -118,6 +118,7 @@ type StorageGetter interface {
GetValidAuthorizations(ctx context.Context, regID int64, domains []string, now time.Time) (map[string]*Authorization, error)
GetPendingAuthorization(ctx context.Context, req *sapb.GetPendingAuthorizationRequest) (*Authorization, error)
GetCertificate(ctx context.Context, serial string) (Certificate, error)
GetPrecertificate(ctx context.Context, req *sapb.Serial) (*corepb.Certificate, error)
GetCertificateStatus(ctx context.Context, serial string) (CertificateStatus, error)
CountCertificatesByNames(ctx context.Context, domains []string, earliest, latest time.Time) (countByDomain []*sapb.CountByNames_MapElement, err error)
CountRegistrationsByIP(ctx context.Context, ip net.IP, earliest, latest time.Time) (int, error)

View File

@ -35,11 +35,12 @@ func _() {
_ = x[DeleteUnusedChallenges-24]
_ = x[V1DisableNewValidations-25]
_ = x[PrecertificateOCSP-26]
_ = x[PrecertificateRevocation-27]
}
const _FeatureFlag_name = "unusedPerformValidationRPCACME13KeyRolloverSimplifiedVAHTTPTLSSNIRevalidationAllowRenewalFirstRLSetIssuedNamesRenewalBitFasterRateLimitProbeCTLogsRevokeAtRACAAValidationMethodsCAAAccountURIHeadNonceStatusOKNewAuthorizationSchemaDisableAuthz2OrdersEarlyOrderRateLimitEnforceMultiVAMultiVAFullResultsRemoveWFE2AccountIDCheckRenewalFirstMandatoryPOSTAsGETFasterGetOrderForNamesAllowV1RegistrationParallelCheckFailedValidationDeleteUnusedChallengesV1DisableNewValidationsPrecertificateOCSP"
const _FeatureFlag_name = "unusedPerformValidationRPCACME13KeyRolloverSimplifiedVAHTTPTLSSNIRevalidationAllowRenewalFirstRLSetIssuedNamesRenewalBitFasterRateLimitProbeCTLogsRevokeAtRACAAValidationMethodsCAAAccountURIHeadNonceStatusOKNewAuthorizationSchemaDisableAuthz2OrdersEarlyOrderRateLimitEnforceMultiVAMultiVAFullResultsRemoveWFE2AccountIDCheckRenewalFirstMandatoryPOSTAsGETFasterGetOrderForNamesAllowV1RegistrationParallelCheckFailedValidationDeleteUnusedChallengesV1DisableNewValidationsPrecertificateOCSPPrecertificateRevocation"
var _FeatureFlag_index = [...]uint16{0, 6, 26, 43, 59, 77, 96, 120, 135, 146, 156, 176, 189, 206, 228, 247, 266, 280, 298, 317, 334, 352, 374, 393, 422, 444, 467, 485}
var _FeatureFlag_index = [...]uint16{0, 6, 26, 43, 59, 77, 96, 120, 135, 146, 156, 176, 189, 206, 228, 247, 266, 280, 298, 317, 334, 352, 374, 393, 422, 444, 467, 485, 509}
func (i FeatureFlag) String() string {
if i < 0 || i >= FeatureFlag(len(_FeatureFlag_index)-1) {

View File

@ -72,6 +72,9 @@ const (
// generating a precertificate. This also changes the issuance / storage flow,
// adding two new calls from CA to SA: AddSerial and AddPrecertificate.
PrecertificateOCSP
// PrecertificateRevocation allows revocation of precertificates with the
// ACMEv2 interface.
PrecertificateRevocation
)
// List of features and their default value, protected by fMu
@ -103,6 +106,7 @@ var features = map[FeatureFlag]bool{
DeleteUnusedChallenges: false,
V1DisableNewValidations: false,
PrecertificateOCSP: false,
PrecertificateRevocation: false,
}
var fMu = new(sync.RWMutex)

View File

@ -56,7 +56,7 @@ func (cac CertificateAuthorityClientWrapper) IssueCertificateForPrecertificate(c
if err != nil {
return core.Certificate{}, err
}
return pbToCert(res)
return PBToCert(res)
}
func (cac CertificateAuthorityClientWrapper) GenerateOCSP(ctx context.Context, ocspReq core.OCSPSigningRequest) ([]byte, error) {
@ -113,7 +113,7 @@ func (cas *CertificateAuthorityServerWrapper) IssueCertificateForPrecertificate(
if err != nil {
return nil, err
}
return certToPB(cert), nil
return CertToPB(cert), nil
}
func (cas *CertificateAuthorityServerWrapper) GenerateOCSP(ctx context.Context, request *caPB.GenerateOCSPRequest) (*caPB.OCSPResponse, error) {

View File

@ -426,7 +426,7 @@ func authorizationValid(authz *corepb.Authorization) bool {
return !(authz.Id == nil || authz.Identifier == nil || authz.RegistrationID == nil || authz.Status == nil || authz.Expires == nil)
}
func certToPB(cert core.Certificate) *corepb.Certificate {
func CertToPB(cert core.Certificate) *corepb.Certificate {
issued, expires := cert.Issued.UnixNano(), cert.Expires.UnixNano()
return &corepb.Certificate{
RegistrationID: &cert.RegistrationID,
@ -438,7 +438,7 @@ func certToPB(cert core.Certificate) *corepb.Certificate {
}
}
func pbToCert(pb *corepb.Certificate) (core.Certificate, error) {
func PBToCert(pb *corepb.Certificate) (core.Certificate, error) {
if pb == nil || pb.RegistrationID == nil || pb.Serial == nil || pb.Digest == nil || pb.Der == nil || pb.Issued == nil || pb.Expires == nil {
return core.Certificate{}, errIncompleteResponse
}

View File

@ -306,8 +306,8 @@ func TestCert(t *testing.T) {
Expires: now.Add(time.Hour),
}
certPB := certToPB(cert)
outCert, _ := pbToCert(certPB)
certPB := CertToPB(cert)
outCert, _ := PBToCert(certPB)
test.AssertDeepEquals(t, cert, outCert)
}

View File

@ -68,7 +68,7 @@ func (rac RegistrationAuthorityClientWrapper) NewCertificate(ctx context.Context
return core.Certificate{}, err
}
return pbToCert(response)
return PBToCert(response)
}
func (rac RegistrationAuthorityClientWrapper) UpdateRegistration(ctx context.Context, base, updates core.Registration) (core.Registration, error) {
@ -237,7 +237,7 @@ func (ras *RegistrationAuthorityServerWrapper) NewCertificate(ctx context.Contex
if err != nil {
return nil, err
}
return certToPB(cert), nil
return CertToPB(cert), nil
}
func (ras *RegistrationAuthorityServerWrapper) UpdateRegistration(ctx context.Context, request *rapb.UpdateRegistrationRequest) (*corepb.Registration, error) {

View File

@ -108,7 +108,18 @@ func (sac StorageAuthorityClientWrapper) GetCertificate(ctx context.Context, ser
return core.Certificate{}, err
}
return pbToCert(response)
return PBToCert(response)
}
func (sac StorageAuthorityClientWrapper) GetPrecertificate(ctx context.Context, serial *sapb.Serial) (*corepb.Certificate, error) {
resp, err := sac.inner.GetPrecertificate(ctx, serial)
if err != nil {
return nil, err
}
if resp == nil {
return nil, errIncompleteResponse
}
return resp, nil
}
func (sac StorageAuthorityClientWrapper) GetCertificateStatus(ctx context.Context, serial string) (core.CertificateStatus, error) {
@ -729,7 +740,14 @@ func (sas StorageAuthorityServerWrapper) GetCertificate(ctx context.Context, req
return nil, err
}
return certToPB(cert), nil
return CertToPB(cert), nil
}
func (sas StorageAuthorityServerWrapper) GetPrecertificate(ctx context.Context, request *sapb.Serial) (*corepb.Certificate, error) {
if request == nil || request.Serial == nil {
return nil, errIncompleteRequest
}
return sas.inner.GetPrecertificate(ctx, request)
}
func (sas StorageAuthorityServerWrapper) GetCertificateStatus(ctx context.Context, request *sapb.Serial) (*sapb.CertificateStatus, error) {

View File

@ -303,6 +303,11 @@ func (sa *StorageAuthority) GetCertificate(_ context.Context, serial string) (co
}
}
// GetPrecertificate is a mock
func (sa *StorageAuthority) GetPrecertificate(_ context.Context, _ *sapb.Serial) (*corepb.Certificate, error) {
return nil, nil
}
// GetCertificateStatus is a mock
func (sa *StorageAuthority) GetCertificateStatus(_ context.Context, serial string) (core.CertificateStatus, error) {
// Serial ee == 238.crt

View File

@ -42,6 +42,10 @@ func (sa *mockInvalidAuthorizationsAuthority) GetCertificate(ctx context.Context
return nil, nil
}
func (sa *mockInvalidAuthorizationsAuthority) GetPrecertificate(_ context.Context, _ *sapb.Serial, opts ...grpc.CallOption) (*core.Certificate, error) {
return nil, nil
}
func (sa *mockInvalidAuthorizationsAuthority) GetCertificateStatus(ctx context.Context, in *sapb.Serial, opts ...grpc.CallOption) (*sapb.CertificateStatus, error) {
return nil, nil
}

View File

@ -132,6 +132,25 @@ func SelectCertificate(s dbOneSelector, q string, args ...interface{}) (core.Cer
return model, err
}
const precertFields = "registrationID, serial, der, issued, expires"
// selectPrecertificate selects all fields of one precertificate object
// identified by serial.
func selectPrecertificate(s dbOneSelector, serial string) (core.Certificate, error) {
var model precertificateModel
err := s.SelectOne(
&model,
"SELECT "+precertFields+" FROM precertificates WHERE serial = ?",
serial)
return core.Certificate{
RegistrationID: model.RegistrationID,
Serial: model.Serial,
DER: model.DER,
Issued: model.Issued,
Expires: model.Expires,
}, err
}
type CertWithID struct {
ID int64
core.Certificate

View File

@ -1838,122 +1838,122 @@ func init() {
func init() { proto.RegisterFile("sa/proto/sa.proto", fileDescriptor_099fb35e782a48a6) }
var fileDescriptor_099fb35e782a48a6 = []byte{
// 1826 bytes of a gzipped FileDescriptorProto
// 1835 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x59, 0x5f, 0x73, 0xdb, 0xc6,
0x11, 0xe7, 0x1f, 0x53, 0x26, 0x57, 0xb2, 0xfe, 0x9c, 0x25, 0x0a, 0x81, 0x65, 0x9b, 0xbe, 0x3a,
0x1e, 0x65, 0x3a, 0xa3, 0xb8, 0x68, 0x27, 0xe9, 0x8c, 0x1a, 0xa7, 0x72, 0x24, 0xcb, 0x4a, 0x1d,
0x11, 0xe7, 0x1f, 0x53, 0x26, 0x57, 0xb2, 0xfe, 0x9c, 0x25, 0x1a, 0x81, 0x65, 0x9b, 0xbe, 0x3a,
0x1e, 0x65, 0x3a, 0xa3, 0xb8, 0x68, 0x26, 0xe9, 0x8c, 0x1a, 0xa7, 0x72, 0x24, 0xcb, 0x4a, 0x1d,
0x99, 0x01, 0x63, 0xa5, 0xd3, 0xf6, 0x05, 0x21, 0x2e, 0x32, 0x6a, 0x0a, 0x60, 0xee, 0x8e, 0x52,
0xa4, 0xe7, 0xce, 0xb4, 0x9f, 0xa0, 0xd3, 0xc7, 0x7e, 0x8e, 0x7e, 0x89, 0x7e, 0xa1, 0x3e, 0x74,
0x6e, 0xef, 0x00, 0x02, 0xe0, 0x81, 0x72, 0xec, 0x4e, 0xdf, 0xb0, 0x7b, 0xbb, 0x7b, 0x7b, 0x77,
0xfb, 0xe7, 0xb7, 0x24, 0xac, 0x89, 0xe0, 0xe3, 0x31, 0x4f, 0x64, 0xf2, 0xb1, 0x08, 0x76, 0xf0,
0xa4, 0xe7, 0xce, 0xb4, 0x9f, 0xa0, 0xd3, 0xc7, 0x7e, 0x86, 0x3e, 0xf6, 0x4b, 0xf4, 0x2b, 0x75,
0x6e, 0xef, 0x00, 0x02, 0xe0, 0x81, 0xf2, 0x9f, 0x4e, 0xde, 0xb0, 0x7b, 0xbb, 0x7b, 0x7b, 0x77,
0xfb, 0xe7, 0xb7, 0x24, 0xac, 0x89, 0xe0, 0xe3, 0x31, 0x4f, 0x64, 0xf2, 0xb1, 0x08, 0xb6, 0xf1,
0x83, 0x34, 0x44, 0xe0, 0x6e, 0x0c, 0x13, 0xce, 0xcc, 0x82, 0xfa, 0xd4, 0x4b, 0xb4, 0x07, 0xcb,
0x3e, 0x3b, 0x8d, 0x84, 0xe4, 0x81, 0x8c, 0x92, 0xf8, 0x68, 0x9f, 0x2c, 0x43, 0x23, 0x0a, 0x9d,
0x7a, 0xaf, 0xbe, 0xdd, 0xf4, 0x1b, 0x51, 0x48, 0xef, 0x01, 0x7c, 0x39, 0x78, 0x79, 0xfc, 0x2d,
0xfb, 0xee, 0x77, 0xec, 0x92, 0xac, 0x42, 0xf3, 0xcf, 0x17, 0x6f, 0x70, 0x79, 0xc9, 0x57, 0x9f,
0xf4, 0x01, 0xac, 0xec, 0x4d, 0xe4, 0xeb, 0x84, 0x47, 0x57, 0xb3, 0x26, 0x3a, 0x68, 0xe2, 0x5f,
0x75, 0xb8, 0x77, 0xc8, 0x64, 0x9f, 0xc5, 0x61, 0x14, 0x9f, 0x16, 0xa4, 0x7d, 0xf6, 0xc3, 0x84,
0x09, 0x49, 0x1e, 0xc1, 0x32, 0x2f, 0xf8, 0x61, 0x3c, 0x28, 0x71, 0x95, 0x5c, 0x14, 0xb2, 0x58,
0x46, 0xdf, 0x47, 0x8c, 0x7f, 0x73, 0x39, 0x66, 0x4e, 0x03, 0xb7, 0x29, 0x71, 0xc9, 0x36, 0xac,
0x4c, 0x39, 0x27, 0xc1, 0x68, 0xc2, 0x9c, 0x26, 0x0a, 0x96, 0xd9, 0xe4, 0x1e, 0xc0, 0x79, 0x30,
0x8a, 0xc2, 0x57, 0xb1, 0x8c, 0x46, 0xce, 0x0d, 0xdc, 0x35, 0xc7, 0xa1, 0x02, 0xee, 0x1e, 0x32,
0x79, 0xa2, 0x18, 0x05, 0xcf, 0xc5, 0x4f, 0x75, 0xdd, 0x81, 0x9b, 0x61, 0x72, 0x16, 0x44, 0xb1,
0x70, 0x1a, 0xbd, 0xe6, 0x76, 0xc7, 0x4f, 0x49, 0x75, 0xa9, 0x71, 0x72, 0x81, 0x0e, 0x36, 0x7d,
0xf5, 0x49, 0xff, 0x59, 0x87, 0xdb, 0x96, 0x2d, 0xc9, 0xaf, 0xa1, 0x85, 0xae, 0x39, 0xf5, 0x5e,
0x73, 0x7b, 0xd1, 0xa3, 0x3b, 0x22, 0xd8, 0xb1, 0xc8, 0xed, 0x7c, 0x15, 0x8c, 0x0f, 0x46, 0xec,
0x8c, 0xc5, 0xd2, 0xd7, 0x0a, 0xee, 0x4b, 0x80, 0x29, 0x93, 0x74, 0x61, 0x41, 0x6f, 0x6e, 0x5e,
0xc9, 0x50, 0xe4, 0x23, 0x68, 0x05, 0x13, 0xf9, 0xfa, 0x0a, 0x6f, 0x75, 0xd1, 0xbb, 0xbd, 0x83,
0xa1, 0x52, 0x7c, 0x31, 0x2d, 0x41, 0xff, 0xdd, 0x80, 0xb5, 0x2f, 0x18, 0x57, 0x57, 0x39, 0x0c,
0x24, 0x1b, 0xc8, 0x40, 0x4e, 0x84, 0x32, 0x2c, 0x18, 0x8f, 0x82, 0x51, 0x6a, 0x58, 0x53, 0xc8,
0x47, 0x09, 0xf3, 0x0c, 0x86, 0x52, 0xef, 0x94, 0x0c, 0xc5, 0xf8, 0x45, 0x20, 0xe4, 0xab, 0x71,
0x18, 0x48, 0x16, 0x9a, 0x27, 0x28, 0xb3, 0x49, 0x0f, 0x16, 0x39, 0x3b, 0x4f, 0xde, 0xb0, 0x70,
0x3f, 0x90, 0xcc, 0x69, 0xa1, 0x54, 0x9e, 0x45, 0x1e, 0xc2, 0x2d, 0x43, 0xfa, 0x2c, 0x10, 0x49,
0xec, 0x2c, 0xa0, 0x4c, 0x91, 0x49, 0x7e, 0x05, 0x1b, 0xa3, 0x40, 0xc8, 0x83, 0x1f, 0xc7, 0x91,
0x7e, 0x9a, 0xe3, 0xe0, 0x74, 0xc0, 0x62, 0xe9, 0xdc, 0x44, 0x69, 0xfb, 0x22, 0xa1, 0xb0, 0xa4,
0x1c, 0xf2, 0x99, 0x18, 0x27, 0xb1, 0x60, 0x4e, 0x1b, 0x13, 0xa0, 0xc0, 0x23, 0x2e, 0xb4, 0xe3,
0x44, 0xee, 0x7d, 0x2f, 0x19, 0x77, 0x3a, 0x68, 0x2c, 0xa3, 0xc9, 0x16, 0x74, 0x22, 0x81, 0x66,
0x59, 0xe8, 0x40, 0xaf, 0xbe, 0xdd, 0xf6, 0xa7, 0x8c, 0x2f, 0x6f, 0xb4, 0x1b, 0xab, 0x4d, 0xda,
0x83, 0x85, 0xc1, 0xf4, 0xb6, 0x2c, 0xb7, 0x48, 0x77, 0xa1, 0xe5, 0x07, 0xf1, 0x29, 0x6e, 0xc5,
0x02, 0x3e, 0x8a, 0x98, 0x90, 0x26, 0xda, 0x32, 0x5a, 0x29, 0x8f, 0x02, 0xa9, 0x56, 0x1a, 0xb8,
0x62, 0x28, 0x7a, 0x17, 0x5a, 0x5f, 0x24, 0x93, 0x58, 0x92, 0x75, 0x68, 0x0d, 0xd5, 0x87, 0xd1,
0xd4, 0x04, 0xfd, 0x3d, 0xdc, 0xc7, 0xe5, 0xdc, 0x9b, 0x8a, 0xa7, 0x97, 0xc7, 0xc1, 0x19, 0xcb,
0x22, 0xfd, 0x3e, 0xb4, 0xb8, 0xda, 0x1e, 0x15, 0x17, 0xbd, 0x8e, 0x8a, 0x3e, 0xf4, 0xc7, 0xd7,
0x7c, 0x65, 0x39, 0x56, 0x0a, 0x26, 0xc0, 0x35, 0x41, 0xff, 0x5a, 0x87, 0x25, 0x34, 0x6d, 0xcc,
0x91, 0xcf, 0x61, 0x69, 0x98, 0xa3, 0x4d, 0x30, 0xdf, 0x51, 0xe6, 0xf2, 0x72, 0xf9, 0x28, 0x2e,
0x28, 0xb8, 0x9f, 0x14, 0x82, 0x99, 0xc0, 0x0d, 0xb5, 0x91, 0xb9, 0x2b, 0xfc, 0x9e, 0x9e, 0xb1,
0x91, 0x3f, 0x63, 0x1f, 0xee, 0xe2, 0x06, 0xf9, 0x92, 0x27, 0x9e, 0x5e, 0x1e, 0xf5, 0xd3, 0x13,
0xaa, 0xca, 0x35, 0x36, 0xd5, 0xad, 0x11, 0x8d, 0xa7, 0x27, 0x6e, 0xd8, 0x4f, 0x4c, 0xff, 0x56,
0x87, 0x07, 0x68, 0xf2, 0x28, 0x3e, 0x7f, 0xff, 0x12, 0xe1, 0x42, 0xfb, 0x75, 0x22, 0x24, 0x9e,
0x46, 0xd7, 0xb5, 0x8c, 0x9e, 0xba, 0xd2, 0xac, 0x70, 0x65, 0x00, 0x04, 0x3d, 0x79, 0xc9, 0x43,
0xc6, 0xb3, 0xad, 0xb7, 0xa0, 0x13, 0x0c, 0xf1, 0xf4, 0xd9, 0xae, 0x53, 0xc6, 0xf5, 0xe7, 0x7b,
0x0e, 0xeb, 0x68, 0xf4, 0xd9, 0xd7, 0xfb, 0xc7, 0x03, 0x26, 0x33, 0xb3, 0x5d, 0x58, 0xb8, 0x88,
0xe2, 0x30, 0xb9, 0x30, 0x36, 0x0d, 0x55, 0x5d, 0xe4, 0xe8, 0x63, 0x58, 0x37, 0x46, 0x0e, 0x7e,
0x8c, 0xc4, 0xd4, 0x52, 0x4e, 0xa3, 0x5e, 0xd4, 0xe8, 0x43, 0xaf, 0xcf, 0xd9, 0x79, 0x94, 0x4c,
0x44, 0x2e, 0x28, 0x8b, 0xda, 0x55, 0x85, 0x6c, 0x1d, 0x5a, 0x9c, 0x9d, 0x1e, 0xed, 0xa7, 0xef,
0x8f, 0x84, 0xca, 0x30, 0xad, 0xae, 0xf4, 0x18, 0x7e, 0xa1, 0x5e, 0xdb, 0x37, 0x14, 0x95, 0xb0,
0xba, 0x17, 0x86, 0x3a, 0x0d, 0xd3, 0x3d, 0x32, 0x5b, 0xf5, 0x9c, 0xad, 0x5c, 0x8e, 0x36, 0x0a,
0x95, 0xce, 0x81, 0x9b, 0x43, 0xce, 0xb0, 0x92, 0xe9, 0x82, 0x9e, 0x92, 0x6a, 0x85, 0x61, 0xc2,
0x0b, 0x53, 0xe3, 0x52, 0x92, 0xbe, 0x81, 0x8d, 0xbd, 0x30, 0xcc, 0x1d, 0x32, 0xdd, 0x7a, 0x15,
0x9a, 0x21, 0xe3, 0x69, 0xbb, 0x0d, 0x19, 0xb7, 0x1f, 0x4c, 0xa5, 0x80, 0x2a, 0x45, 0xb8, 0xe3,
0x92, 0x8f, 0xdf, 0xca, 0xc1, 0x48, 0x88, 0x49, 0x56, 0x51, 0x0d, 0x45, 0x1f, 0x43, 0xb7, 0xbc,
0x99, 0x29, 0x60, 0xea, 0x32, 0xa3, 0xd3, 0xb4, 0xa6, 0xa8, 0xcb, 0x44, 0x8a, 0xf6, 0x61, 0x09,
0x83, 0x2a, 0x9f, 0x25, 0x39, 0x88, 0x40, 0x1e, 0xc3, 0xed, 0x89, 0x60, 0x27, 0x5e, 0x31, 0xf8,
0xd1, 0xc3, 0xb6, 0x6f, 0x5b, 0xa2, 0x2f, 0x80, 0xa6, 0x4d, 0x15, 0x2d, 0xdb, 0xd3, 0xa6, 0xbc,
0x4f, 0x17, 0x16, 0x82, 0xe1, 0x50, 0x66, 0x87, 0x37, 0x14, 0xbd, 0x84, 0xcd, 0x43, 0xa6, 0xe3,
0xfe, 0x59, 0xc2, 0x0b, 0x25, 0x6b, 0xaa, 0x52, 0xcf, 0xab, 0xd8, 0x2b, 0x55, 0xd5, 0x41, 0x9a,
0xd5, 0x07, 0xf9, 0x47, 0x1d, 0x9c, 0x43, 0x26, 0xff, 0x6f, 0xc8, 0x40, 0x35, 0x4c, 0xce, 0x7e,
0x98, 0x44, 0xdc, 0xf8, 0x72, 0xa5, 0x83, 0xa9, 0xed, 0x97, 0xd9, 0xf4, 0xef, 0x75, 0x58, 0x2e,
0xc1, 0x87, 0x5f, 0xa6, 0xed, 0x5d, 0x57, 0xdc, 0xbb, 0x2a, 0xdd, 0xe7, 0x20, 0x07, 0x94, 0xfd,
0xdf, 0x23, 0x87, 0x17, 0x70, 0x7f, 0x2f, 0x0c, 0x6d, 0x68, 0x30, 0xbb, 0xb9, 0x8f, 0x8a, 0x8e,
0xce, 0xb3, 0xf6, 0x10, 0x56, 0x4b, 0xf8, 0x13, 0xaf, 0x2d, 0x0a, 0xd3, 0x7a, 0xa2, 0x3e, 0x29,
0x9d, 0x91, 0xf2, 0x66, 0x90, 0xee, 0x87, 0xb0, 0x56, 0x90, 0xf1, 0x4a, 0xa6, 0x9a, 0xda, 0xd4,
0x15, 0x38, 0x3e, 0x22, 0x0a, 0x4b, 0xbe, 0xce, 0x81, 0x3f, 0x5c, 0x63, 0x12, 0x13, 0xb9, 0x9a,
0x52, 0x79, 0xab, 0xd0, 0x8d, 0x79, 0x60, 0xfc, 0x56, 0x4d, 0x80, 0xa7, 0x30, 0xe3, 0x06, 0xe6,
0x73, 0x46, 0xd3, 0xbf, 0x34, 0x60, 0xeb, 0x59, 0x14, 0x07, 0xa3, 0xe8, 0x8a, 0x59, 0x71, 0xb4,
0x25, 0x65, 0x0c, 0xee, 0x6a, 0x14, 0x70, 0x57, 0xae, 0x16, 0x35, 0x0b, 0xb5, 0x08, 0x1b, 0x86,
0x94, 0xec, 0x6c, 0x9c, 0x62, 0xb1, 0x8e, 0x3f, 0x65, 0x90, 0x7d, 0x58, 0xc3, 0x3e, 0x67, 0x36,
0x1d, 0x26, 0x3c, 0x14, 0x4e, 0x0b, 0x1f, 0xa9, 0xab, 0x1f, 0xe9, 0xa4, 0xb4, 0xec, 0xcf, 0x2a,
0x90, 0x27, 0xb0, 0x32, 0x65, 0x1e, 0x70, 0x9e, 0x70, 0xc4, 0x6a, 0x8b, 0xde, 0xba, 0xb6, 0xd1,
0xe7, 0xc9, 0x77, 0x23, 0x76, 0xb6, 0xcf, 0x64, 0x10, 0x8d, 0x84, 0x5f, 0x16, 0xf6, 0xfe, 0xb3,
0x09, 0xab, 0x03, 0x99, 0xf0, 0xe0, 0x34, 0xbd, 0x05, 0x79, 0x49, 0x76, 0x61, 0xe5, 0x90, 0x15,
0x5a, 0x3b, 0x21, 0xd8, 0xcf, 0x0a, 0xc9, 0xe6, 0x12, 0xbd, 0x45, 0x9e, 0x4b, 0x6b, 0xe4, 0x37,
0xb0, 0x5e, 0x52, 0x7e, 0x7a, 0xa9, 0xe6, 0x9d, 0x65, 0x65, 0x61, 0x3a, 0xff, 0x54, 0x68, 0x3f,
0x81, 0xd5, 0x72, 0x11, 0x20, 0xb7, 0x67, 0x92, 0xeb, 0x68, 0xdf, 0xb5, 0x05, 0x32, 0xad, 0x91,
0x6f, 0xb0, 0x80, 0xd9, 0x32, 0x82, 0x20, 0xc4, 0x9f, 0x3f, 0x3c, 0x55, 0x59, 0x3d, 0x81, 0xae,
0x7d, 0x72, 0x21, 0x0f, 0x8c, 0xd1, 0xea, 0xa9, 0xc6, 0xdd, 0xac, 0x18, 0x2d, 0x68, 0x8d, 0xfc,
0x02, 0x96, 0x0f, 0x59, 0x1e, 0x27, 0x12, 0x50, 0xc2, 0xba, 0x69, 0xba, 0x6b, 0xda, 0x99, 0xdc,
0x32, 0xad, 0x91, 0x5d, 0xbc, 0xde, 0xd9, 0x71, 0x21, 0xaf, 0xb8, 0x81, 0xf8, 0xaf, 0x2c, 0x42,
0x6b, 0x64, 0x00, 0x4e, 0x15, 0x32, 0x25, 0x3f, 0xcb, 0x40, 0x63, 0x35, 0x6e, 0x75, 0x57, 0xcb,
0xc8, 0x92, 0xd6, 0xc8, 0x73, 0xe8, 0xda, 0xa1, 0xa0, 0xbe, 0x9c, 0xb9, 0x30, 0xd1, 0xed, 0x64,
0x22, 0xb4, 0x46, 0xbe, 0x82, 0x3b, 0x15, 0xd2, 0x88, 0x89, 0x7f, 0xaa, 0xb9, 0xcf, 0xc0, 0xc5,
0x4f, 0x6b, 0x7d, 0xb4, 0x46, 0x74, 0x41, 0xdd, 0x83, 0xc5, 0x1c, 0x0a, 0x24, 0xdd, 0x6c, 0xad,
0x00, 0x0b, 0x8b, 0x3a, 0x7d, 0xb3, 0xa5, 0x15, 0xc3, 0x92, 0x0f, 0x33, 0xd1, 0x79, 0x18, 0xb7,
0x68, 0xf1, 0x13, 0xb8, 0x55, 0x80, 0x8d, 0xc4, 0xc9, 0x56, 0x4b, 0x48, 0xb2, 0xa8, 0xf7, 0x29,
0xdc, 0x2a, 0x80, 0x44, 0xad, 0x67, 0xc3, 0x8d, 0x2e, 0x86, 0x8e, 0x66, 0xd1, 0x1a, 0x79, 0x09,
0x1f, 0x54, 0x62, 0x45, 0xf2, 0x50, 0x89, 0x5e, 0x07, 0x25, 0x4b, 0x06, 0x7f, 0x0b, 0x6b, 0xe5,
0x94, 0xf6, 0xc8, 0xba, 0x25, 0xa7, 0xbd, 0xaa, 0xf4, 0x7b, 0x0e, 0x64, 0x06, 0x19, 0x78, 0x64,
0xcb, 0xa4, 0x9e, 0xfd, 0x12, 0xc9, 0x6c, 0x47, 0xa6, 0x35, 0xf2, 0x0a, 0x31, 0x86, 0x2d, 0x20,
0xbc, 0xf7, 0xa9, 0x0f, 0x4f, 0x4c, 0xe0, 0x5a, 0x23, 0xcd, 0xbb, 0x3e, 0xd4, 0xfe, 0x04, 0x5b,
0x73, 0x40, 0x9c, 0x47, 0x1e, 0xe5, 0xab, 0x4c, 0x35, 0xcc, 0xab, 0x38, 0xf4, 0xd7, 0xc6, 0x3b,
0x6b, 0xd0, 0x79, 0xef, 0x14, 0x95, 0x3e, 0x96, 0xd9, 0x13, 0x9b, 0xb9, 0xb7, 0xa8, 0x88, 0x76,
0x37, 0x77, 0x61, 0xe5, 0x98, 0x5d, 0x94, 0xba, 0xce, 0x4c, 0x8f, 0xa8, 0xe8, 0x1b, 0x9f, 0x02,
0xd1, 0x3f, 0x6f, 0x5c, 0xab, 0xbf, 0xa8, 0x79, 0x07, 0x67, 0x63, 0x79, 0x49, 0x6b, 0xe4, 0x00,
0x36, 0x8f, 0xd9, 0x85, 0xb5, 0x61, 0xd8, 0x1e, 0xbb, 0x2a, 0x02, 0x76, 0x61, 0xc3, 0x8a, 0x26,
0xec, 0x46, 0x4a, 0x3e, 0x1c, 0xc1, 0x72, 0x71, 0x8e, 0x20, 0x1f, 0xe0, 0x0d, 0xd9, 0x06, 0x19,
0xd7, 0xb5, 0x2d, 0x19, 0x50, 0xa3, 0x6a, 0xde, 0x9a, 0x42, 0x84, 0x9c, 0x0d, 0xdf, 0xce, 0x5a,
0xc9, 0x93, 0xc7, 0xd0, 0xc9, 0x86, 0x36, 0x93, 0xa3, 0xa5, 0x19, 0xae, 0xac, 0xb1, 0x0b, 0xdd,
0x7d, 0x16, 0x0c, 0x65, 0x74, 0x3e, 0x7b, 0xf9, 0xb3, 0x51, 0x5f, 0x52, 0xfe, 0x0c, 0x36, 0xa7,
0xca, 0x6f, 0xd1, 0xf4, 0x4b, 0xea, 0x8f, 0xa0, 0x7d, 0xcc, 0x2e, 0x30, 0x1f, 0x88, 0x59, 0x42,
0xc2, 0xcd, 0x13, 0x78, 0x2a, 0x32, 0x30, 0x53, 0x4d, 0x9f, 0x27, 0x43, 0x26, 0x44, 0x14, 0x9f,
0x5a, 0x35, 0x52, 0xcb, 0x3f, 0x87, 0x5b, 0xa9, 0x06, 0xe2, 0xa4, 0xeb, 0x84, 0xd3, 0xb7, 0xaf,
0xf6, 0x65, 0x2a, 0xdc, 0x4e, 0x27, 0x2c, 0x82, 0xdd, 0x34, 0x3f, 0x0f, 0x96, 0x1d, 0xff, 0x23,
0xdc, 0x99, 0x93, 0xf5, 0xef, 0x59, 0x16, 0x34, 0xd4, 0x2a, 0xcc, 0x7a, 0xe4, 0x8e, 0xb1, 0x68,
0x9b, 0x00, 0xcb, 0xce, 0x1d, 0xce, 0xd6, 0x75, 0xf1, 0x4e, 0x45, 0xf9, 0x5b, 0x70, 0xaa, 0xa6,
0x18, 0x8d, 0x4a, 0xae, 0x99, 0x71, 0x5c, 0x5b, 0x33, 0x31, 0x9d, 0x67, 0x66, 0xbe, 0xd0, 0x1e,
0x56, 0x8d, 0x1d, 0xe5, 0xd7, 0xf2, 0x81, 0x1c, 0xb3, 0x8b, 0x72, 0x89, 0x7b, 0x2b, 0xa7, 0x36,
0x66, 0x9c, 0xf2, 0xb4, 0x57, 0x47, 0xd0, 0xb5, 0x96, 0x0a, 0x8f, 0xf4, 0xb0, 0x45, 0xcf, 0x19,
0x4a, 0xca, 0xee, 0x7d, 0x0e, 0x4e, 0x45, 0xfe, 0x54, 0x75, 0xd8, 0xa2, 0x81, 0xa7, 0x37, 0xff,
0xd0, 0xc2, 0x7f, 0x2f, 0xfe, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x6f, 0x39, 0x5a, 0x2e, 0xec, 0x18,
0x00, 0x00,
0x3e, 0x3b, 0x89, 0x84, 0xe4, 0x81, 0x8c, 0x92, 0xf8, 0x70, 0x8f, 0x2c, 0x43, 0x23, 0x0a, 0x9d,
0x7a, 0xaf, 0xbe, 0xd5, 0xf4, 0x1b, 0x51, 0x48, 0xef, 0x02, 0x7c, 0x35, 0x78, 0x71, 0xf4, 0x1d,
0xfb, 0xfe, 0xf7, 0xec, 0x82, 0xac, 0x42, 0xf3, 0x2f, 0xe7, 0xaf, 0x71, 0x79, 0xc9, 0x57, 0x9f,
0xf4, 0x3e, 0xac, 0xec, 0x4e, 0xe4, 0xab, 0x84, 0x47, 0x97, 0xb3, 0x26, 0x3a, 0x68, 0xe2, 0x3f,
0x75, 0xb8, 0x7b, 0xc0, 0x64, 0x9f, 0xc5, 0x61, 0x14, 0x9f, 0x14, 0xa4, 0x7d, 0xf6, 0xe3, 0x84,
0x09, 0x49, 0x1e, 0xc2, 0x32, 0x2f, 0xf8, 0x61, 0x3c, 0x28, 0x71, 0x95, 0x5c, 0x14, 0xb2, 0x58,
0x46, 0x3f, 0x44, 0x8c, 0x7f, 0x7b, 0x31, 0x66, 0x4e, 0x03, 0xb7, 0x29, 0x71, 0xc9, 0x16, 0xac,
0x4c, 0x39, 0xc7, 0xc1, 0x68, 0xc2, 0x9c, 0x26, 0x0a, 0x96, 0xd9, 0xe4, 0x2e, 0xc0, 0x59, 0x30,
0x8a, 0xc2, 0x97, 0xb1, 0x8c, 0x46, 0xce, 0x35, 0xdc, 0x35, 0xc7, 0xa1, 0x02, 0xee, 0x1c, 0x30,
0x79, 0xac, 0x18, 0x05, 0xcf, 0xc5, 0xdb, 0xba, 0xee, 0xc0, 0xf5, 0x30, 0x39, 0x0d, 0xa2, 0x58,
0x38, 0x8d, 0x5e, 0x73, 0xab, 0xe3, 0xa7, 0xa4, 0xba, 0xd4, 0x38, 0x39, 0x47, 0x07, 0x9b, 0xbe,
0xfa, 0xa4, 0xff, 0xaa, 0xc3, 0x4d, 0xcb, 0x96, 0xe4, 0x37, 0xd0, 0x42, 0xd7, 0x9c, 0x7a, 0xaf,
0xb9, 0xb5, 0xe8, 0xd1, 0x6d, 0x11, 0x6c, 0x5b, 0xe4, 0xb6, 0xbf, 0x0e, 0xc6, 0xfb, 0x23, 0x76,
0xca, 0x62, 0xe9, 0x6b, 0x05, 0xf7, 0x05, 0xc0, 0x94, 0x49, 0xba, 0xb0, 0xa0, 0x37, 0x37, 0xaf,
0x64, 0x28, 0xf2, 0x11, 0xb4, 0x82, 0x89, 0x7c, 0x75, 0x89, 0xb7, 0xba, 0xe8, 0xdd, 0xdc, 0xc6,
0x50, 0x29, 0xbe, 0x98, 0x96, 0xa0, 0xff, 0x6d, 0xc0, 0xda, 0x97, 0x8c, 0xab, 0xab, 0x1c, 0x06,
0x92, 0x0d, 0x64, 0x20, 0x27, 0x42, 0x19, 0x16, 0x8c, 0x47, 0xc1, 0x28, 0x35, 0xac, 0x29, 0xe4,
0xa3, 0x84, 0x79, 0x06, 0x43, 0xa9, 0x77, 0x4a, 0x86, 0x62, 0xfc, 0x3c, 0x10, 0xf2, 0xe5, 0x38,
0x0c, 0x24, 0x0b, 0xcd, 0x13, 0x94, 0xd9, 0xa4, 0x07, 0x8b, 0x9c, 0x9d, 0x25, 0xaf, 0x59, 0xb8,
0x17, 0x48, 0xe6, 0xb4, 0x50, 0x2a, 0xcf, 0x22, 0x0f, 0xe0, 0x86, 0x21, 0x7d, 0x16, 0x88, 0x24,
0x76, 0x16, 0x50, 0xa6, 0xc8, 0x24, 0x9f, 0xc0, 0xc6, 0x28, 0x10, 0x72, 0xff, 0xa7, 0x71, 0xa4,
0x9f, 0xe6, 0x28, 0x38, 0x19, 0xb0, 0x58, 0x3a, 0xd7, 0x51, 0xda, 0xbe, 0x48, 0x28, 0x2c, 0x29,
0x87, 0x7c, 0x26, 0xc6, 0x49, 0x2c, 0x98, 0xd3, 0xc6, 0x04, 0x28, 0xf0, 0x88, 0x0b, 0xed, 0x38,
0x91, 0xbb, 0x3f, 0x48, 0xc6, 0x9d, 0x0e, 0x1a, 0xcb, 0x68, 0xb2, 0x09, 0x9d, 0x48, 0xa0, 0x59,
0x16, 0x3a, 0xd0, 0xab, 0x6f, 0xb5, 0xfd, 0x29, 0xe3, 0xab, 0x6b, 0xed, 0xc6, 0x6a, 0x93, 0xf6,
0x60, 0x61, 0x30, 0xbd, 0x2d, 0xcb, 0x2d, 0xd2, 0x1d, 0x68, 0xf9, 0x41, 0x7c, 0x82, 0x5b, 0xb1,
0x80, 0x8f, 0x22, 0x26, 0xa4, 0x89, 0xb6, 0x8c, 0x56, 0xca, 0xa3, 0x40, 0xaa, 0x95, 0x06, 0xae,
0x18, 0x8a, 0xde, 0x81, 0xd6, 0x97, 0xc9, 0x24, 0x96, 0x64, 0x1d, 0x5a, 0x43, 0xf5, 0x61, 0x34,
0x35, 0x41, 0xff, 0x00, 0xf7, 0x70, 0x39, 0xf7, 0xa6, 0xe2, 0xc9, 0xc5, 0x51, 0x70, 0xca, 0xb2,
0x48, 0xbf, 0x07, 0x2d, 0xae, 0xb6, 0x47, 0xc5, 0x45, 0xaf, 0xa3, 0xa2, 0x0f, 0xfd, 0xf1, 0x35,
0x5f, 0x59, 0x8e, 0x95, 0x82, 0x09, 0x70, 0x4d, 0xd0, 0xbf, 0xd5, 0x61, 0x09, 0x4d, 0x1b, 0x73,
0xe4, 0x0b, 0x58, 0x1a, 0xe6, 0x68, 0x13, 0xcc, 0xb7, 0x95, 0xb9, 0xbc, 0x5c, 0x3e, 0x8a, 0x0b,
0x0a, 0xee, 0xa7, 0x85, 0x60, 0x26, 0x70, 0x4d, 0x6d, 0x64, 0xee, 0x0a, 0xbf, 0xa7, 0x67, 0x6c,
0xe4, 0xcf, 0xd8, 0x87, 0x3b, 0xb8, 0x41, 0xbe, 0xe4, 0x89, 0x27, 0x17, 0x87, 0xfd, 0xf4, 0x84,
0xaa, 0x72, 0x8d, 0x4d, 0x75, 0x6b, 0x44, 0xe3, 0xe9, 0x89, 0x1b, 0xf6, 0x13, 0xd3, 0xbf, 0xd7,
0xe1, 0x3e, 0x9a, 0x3c, 0x8c, 0xcf, 0xde, 0xbf, 0x44, 0xb8, 0xd0, 0x7e, 0x95, 0x08, 0x89, 0xa7,
0xd1, 0x75, 0x2d, 0xa3, 0xa7, 0xae, 0x34, 0x2b, 0x5c, 0x19, 0x00, 0x41, 0x4f, 0x5e, 0xf0, 0x90,
0xf1, 0x6c, 0xeb, 0x4d, 0xe8, 0x04, 0x43, 0x3c, 0x7d, 0xb6, 0xeb, 0x94, 0x71, 0xf5, 0xf9, 0x9e,
0xc1, 0x3a, 0x1a, 0x7d, 0xfa, 0xcd, 0xde, 0xd1, 0x80, 0xc9, 0xcc, 0x6c, 0x17, 0x16, 0xce, 0xa3,
0x38, 0x4c, 0xce, 0x8d, 0x4d, 0x43, 0x55, 0x17, 0x39, 0xfa, 0x08, 0xd6, 0x8d, 0x91, 0xfd, 0x9f,
0x22, 0x31, 0xb5, 0x94, 0xd3, 0xa8, 0x17, 0x35, 0xfa, 0xd0, 0xeb, 0x73, 0x76, 0x16, 0x25, 0x13,
0x91, 0x0b, 0xca, 0xa2, 0x76, 0x55, 0x21, 0x5b, 0x87, 0x16, 0x67, 0x27, 0x87, 0x7b, 0xe9, 0xfb,
0x23, 0xa1, 0x32, 0x4c, 0xab, 0x2b, 0x3d, 0x86, 0x5f, 0xa8, 0xd7, 0xf6, 0x0d, 0x45, 0x25, 0xac,
0xee, 0x86, 0xa1, 0x4e, 0xc3, 0x74, 0x8f, 0xcc, 0x56, 0x3d, 0x67, 0x2b, 0x97, 0xa3, 0x8d, 0x42,
0xa5, 0x73, 0xe0, 0xfa, 0x90, 0x33, 0xac, 0x64, 0xba, 0xa0, 0xa7, 0xa4, 0x5a, 0x61, 0x98, 0xf0,
0xc2, 0xd4, 0xb8, 0x94, 0xa4, 0xaf, 0x61, 0x63, 0x37, 0x0c, 0x73, 0x87, 0x4c, 0xb7, 0x5e, 0x85,
0x66, 0xc8, 0x78, 0xda, 0x6e, 0x43, 0xc6, 0xed, 0x07, 0x53, 0x29, 0xa0, 0x4a, 0x11, 0xee, 0xb8,
0xe4, 0xe3, 0xb7, 0x72, 0x30, 0x12, 0x62, 0x92, 0x55, 0x54, 0x43, 0xd1, 0x47, 0xd0, 0x2d, 0x6f,
0x66, 0x0a, 0x98, 0xba, 0xcc, 0xe8, 0x24, 0xad, 0x29, 0xea, 0x32, 0x91, 0xa2, 0x7d, 0x58, 0xc2,
0xa0, 0xca, 0x67, 0x49, 0x0e, 0x22, 0x90, 0x47, 0x70, 0x73, 0x22, 0xd8, 0xb1, 0x57, 0x0c, 0x7e,
0xf4, 0xb0, 0xed, 0xdb, 0x96, 0xe8, 0x73, 0xa0, 0x69, 0x53, 0x45, 0xcb, 0xf6, 0xb4, 0x29, 0xef,
0xd3, 0x85, 0x85, 0x60, 0x38, 0x94, 0xd9, 0xe1, 0x0d, 0x45, 0x2f, 0xe0, 0xd6, 0x01, 0xd3, 0x71,
0xff, 0x34, 0xe1, 0x85, 0x92, 0x35, 0x55, 0xa9, 0xe7, 0x55, 0xec, 0x95, 0xaa, 0xea, 0x20, 0xcd,
0xea, 0x83, 0xfc, 0xb3, 0x0e, 0xce, 0x01, 0x93, 0x3f, 0x1b, 0x32, 0x50, 0x0d, 0x93, 0xb3, 0x1f,
0x27, 0x11, 0x37, 0xbe, 0x5c, 0xea, 0x60, 0x6a, 0xfb, 0x65, 0x36, 0xfd, 0x47, 0x1d, 0x96, 0x4b,
0xf0, 0xe1, 0xd7, 0x69, 0x7b, 0xd7, 0x15, 0xf7, 0x8e, 0x4a, 0xf7, 0x39, 0xc8, 0x01, 0x65, 0xff,
0xff, 0xc8, 0xe1, 0x39, 0xdc, 0xdb, 0x0d, 0x43, 0x1b, 0x1a, 0xcc, 0x6e, 0xee, 0xa3, 0xa2, 0xa3,
0xf3, 0xac, 0x3d, 0x80, 0xd5, 0x12, 0xfe, 0xc4, 0x6b, 0x8b, 0xc2, 0xb4, 0x9e, 0xa8, 0x4f, 0x4a,
0x67, 0xa4, 0xbc, 0x19, 0xa4, 0xfb, 0x21, 0xac, 0x15, 0x64, 0xbc, 0x92, 0xa9, 0xa6, 0x36, 0x75,
0x09, 0x8e, 0x8f, 0x88, 0xc2, 0x92, 0xaf, 0x73, 0xe0, 0x0f, 0xd7, 0x98, 0xc4, 0x44, 0xae, 0xa6,
0x54, 0xde, 0x2a, 0x74, 0x63, 0x1e, 0x18, 0xbf, 0x55, 0x13, 0xe0, 0x29, 0xcc, 0xb8, 0x86, 0xf9,
0x9c, 0xd1, 0xf4, 0xaf, 0x0d, 0xd8, 0x7c, 0x1a, 0xc5, 0xc1, 0x28, 0xba, 0x64, 0x56, 0x1c, 0x6d,
0x49, 0x19, 0x83, 0xbb, 0x1a, 0x05, 0xdc, 0x95, 0xab, 0x45, 0xcd, 0x42, 0x2d, 0xc2, 0x86, 0x21,
0x25, 0x3b, 0x1d, 0xa7, 0x58, 0xac, 0xe3, 0x4f, 0x19, 0x64, 0x0f, 0xd6, 0xb0, 0xcf, 0x99, 0x4d,
0x87, 0x09, 0x0f, 0x85, 0xd3, 0xc2, 0x47, 0xea, 0xea, 0x47, 0x3a, 0x2e, 0x2d, 0xfb, 0xb3, 0x0a,
0xe4, 0x31, 0xac, 0x4c, 0x99, 0xfb, 0x9c, 0x27, 0x1c, 0xb1, 0xda, 0xa2, 0xb7, 0xae, 0x6d, 0xf4,
0x79, 0xf2, 0xfd, 0x88, 0x9d, 0xee, 0x31, 0x19, 0x44, 0x23, 0xe1, 0x97, 0x85, 0xbd, 0x7f, 0x3b,
0xb0, 0x3a, 0x90, 0x09, 0x0f, 0x4e, 0xd2, 0x5b, 0x90, 0x17, 0x64, 0x07, 0x56, 0x0e, 0x58, 0xa1,
0xb5, 0x13, 0x82, 0xfd, 0xac, 0x90, 0x6c, 0x2e, 0xd1, 0x5b, 0xe4, 0xb9, 0xb4, 0x46, 0x7e, 0x0b,
0xeb, 0x25, 0xe5, 0x27, 0x17, 0x6a, 0xde, 0x59, 0x56, 0x16, 0xa6, 0xf3, 0x4f, 0x85, 0xf6, 0x63,
0x58, 0x2d, 0x17, 0x01, 0x72, 0x73, 0x26, 0xb9, 0x0e, 0xf7, 0x5c, 0x5b, 0x20, 0xd3, 0x1a, 0xf9,
0x16, 0x0b, 0x98, 0x2d, 0x23, 0x08, 0x42, 0xfc, 0xf9, 0xc3, 0x53, 0x95, 0xd5, 0x63, 0xe8, 0xda,
0x27, 0x17, 0x72, 0xdf, 0x18, 0xad, 0x9e, 0x6a, 0xdc, 0x5b, 0x15, 0xa3, 0x05, 0xad, 0x91, 0x5f,
0xc1, 0xf2, 0x01, 0xcb, 0xe3, 0x44, 0x02, 0x4a, 0x58, 0x37, 0x4d, 0x77, 0x4d, 0x3b, 0x93, 0x5b,
0xa6, 0x35, 0xf2, 0x09, 0xac, 0xa9, 0x33, 0x70, 0x36, 0x7c, 0x1b, 0xad, 0x1d, 0x7c, 0x94, 0xd9,
0x21, 0x23, 0xaf, 0xb8, 0x81, 0xa8, 0xb1, 0x2c, 0x42, 0x6b, 0x64, 0x00, 0x4e, 0x15, 0x9e, 0x25,
0xbf, 0xc8, 0xa0, 0x66, 0x35, 0xda, 0x75, 0x57, 0xcb, 0x78, 0x94, 0xd6, 0xc8, 0x33, 0xe8, 0xda,
0x01, 0xa4, 0xbe, 0xd2, 0xb9, 0xe0, 0xd2, 0xed, 0x64, 0x22, 0xb4, 0x46, 0xbe, 0x86, 0xdb, 0x15,
0xd2, 0x88, 0xa4, 0xdf, 0xd6, 0xdc, 0xe7, 0xe0, 0xe2, 0xa7, 0xb5, 0xaa, 0x5a, 0xf3, 0xa0, 0xa0,
0xee, 0xc1, 0x62, 0x0e, 0x3b, 0x92, 0x6e, 0xb6, 0x56, 0x00, 0x93, 0x45, 0x9d, 0xbe, 0xd9, 0xd2,
0x8a, 0x7c, 0xc9, 0x87, 0x99, 0xe8, 0x3c, 0x64, 0x5c, 0xb4, 0xf8, 0x29, 0xdc, 0x28, 0x80, 0x4d,
0xe2, 0x64, 0xab, 0x25, 0xfc, 0x59, 0xd4, 0xfb, 0x0c, 0x6e, 0x14, 0xa0, 0xa5, 0xd6, 0xb3, 0xa1,
0x4d, 0x17, 0x43, 0x47, 0xb3, 0x68, 0x8d, 0xbc, 0x80, 0x0f, 0x2a, 0x11, 0x26, 0x79, 0xa0, 0x44,
0xaf, 0x02, 0xa0, 0x25, 0x83, 0xbf, 0xc3, 0x38, 0x2f, 0x76, 0x11, 0xb2, 0x6e, 0xa9, 0x04, 0x5e,
0x55, 0xd2, 0x3e, 0x03, 0x32, 0x83, 0x27, 0x3c, 0xb2, 0x69, 0x12, 0xd6, 0x7e, 0x89, 0x64, 0xb6,
0x8f, 0xd3, 0x1a, 0x79, 0x89, 0xc8, 0xc4, 0x16, 0x10, 0xde, 0xfb, 0x54, 0x95, 0xc7, 0x26, 0x70,
0xad, 0x91, 0xe6, 0x5d, 0x1d, 0x6a, 0x7f, 0x86, 0xcd, 0x39, 0xd0, 0xcf, 0x23, 0x0f, 0xf3, 0xb5,
0xa9, 0x1a, 0x1c, 0x56, 0x1c, 0xfa, 0x1b, 0xe3, 0x9d, 0x35, 0xe8, 0xbc, 0x77, 0x8a, 0x4a, 0x1f,
0x8b, 0xf3, 0xb1, 0xcd, 0xdc, 0x1b, 0xd4, 0x51, 0xbb, 0x9b, 0x3b, 0xb0, 0x72, 0xc4, 0xce, 0x4b,
0xbd, 0x6a, 0xa6, 0xb3, 0x54, 0x74, 0x9b, 0xcf, 0x80, 0xe8, 0x1f, 0x45, 0xae, 0xd4, 0x5f, 0xd4,
0xbc, 0xfd, 0xd3, 0xb1, 0xbc, 0xa0, 0x35, 0xb2, 0x0f, 0xb7, 0x8e, 0xd8, 0xb9, 0xb5, 0xcd, 0xd8,
0x1e, 0xbb, 0x2a, 0x02, 0x76, 0x60, 0xc3, 0x8a, 0x41, 0xec, 0x46, 0x4a, 0x3e, 0x1c, 0xc2, 0x72,
0x71, 0xfa, 0x20, 0x1f, 0xe0, 0x0d, 0xd9, 0xc6, 0x1f, 0xd7, 0xb5, 0x2d, 0x19, 0x28, 0xa4, 0x6a,
0xde, 0x9a, 0xc2, 0x91, 0xc5, 0xa6, 0x32, 0xc7, 0x5a, 0xc9, 0x93, 0x47, 0xd0, 0xc9, 0x46, 0x3d,
0x93, 0xa3, 0xa5, 0xc9, 0xaf, 0xac, 0xb1, 0x03, 0xdd, 0x3d, 0x16, 0x0c, 0x65, 0x74, 0x36, 0x7b,
0xf9, 0xb3, 0x51, 0x5f, 0x52, 0xfe, 0x1c, 0x6e, 0x4d, 0x95, 0xdf, 0x00, 0x2a, 0x94, 0xd4, 0x1f,
0x42, 0xfb, 0x88, 0x9d, 0x63, 0x3e, 0x10, 0xb3, 0x84, 0x84, 0x9b, 0x27, 0xf0, 0x54, 0x64, 0x60,
0x66, 0xa1, 0x3e, 0x4f, 0x86, 0x4c, 0x88, 0x28, 0x3e, 0xb1, 0x6a, 0xa4, 0x96, 0x7f, 0x09, 0x37,
0x52, 0x0d, 0x44, 0x57, 0x57, 0x09, 0xa7, 0x6f, 0x5f, 0xed, 0xcb, 0x54, 0xb8, 0x9d, 0xce, 0x65,
0x04, 0xbb, 0x69, 0x7e, 0x8a, 0x2c, 0x3b, 0xfe, 0x27, 0xb8, 0x3d, 0x27, 0xeb, 0xdf, 0xb3, 0x2c,
0x68, 0x80, 0x56, 0x98, 0x10, 0xc9, 0x6d, 0x63, 0xd1, 0x36, 0x37, 0x96, 0x9d, 0x3b, 0x98, 0xad,
0xeb, 0xe2, 0x9d, 0x8a, 0xf2, 0x77, 0xe0, 0x54, 0xcd, 0x3e, 0x1a, 0x95, 0x5c, 0x31, 0x19, 0xb9,
0xb6, 0x66, 0x62, 0x3a, 0xcf, 0xcc, 0x54, 0xa2, 0x3d, 0xac, 0x1a, 0x56, 0xca, 0xaf, 0xe5, 0x03,
0x39, 0x62, 0xe7, 0xe5, 0x12, 0xf7, 0x46, 0x4e, 0x6d, 0xcc, 0x38, 0xe5, 0x69, 0xaf, 0x0e, 0xa1,
0x6b, 0x2d, 0x15, 0x1e, 0xe9, 0x61, 0x8b, 0x9e, 0x33, 0xca, 0x94, 0xdd, 0xfb, 0x02, 0x9c, 0x8a,
0xfc, 0xa9, 0xea, 0xb0, 0x45, 0x03, 0x4f, 0xae, 0xff, 0xb1, 0x85, 0xff, 0x79, 0xfc, 0x2f, 0x00,
0x00, 0xff, 0xff, 0x50, 0xdd, 0x35, 0xd0, 0x22, 0x19, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -1975,6 +1975,7 @@ type StorageAuthorityClient interface {
GetPendingAuthorization(ctx context.Context, in *GetPendingAuthorizationRequest, opts ...grpc.CallOption) (*proto1.Authorization, error)
GetValidAuthorizations(ctx context.Context, in *GetValidAuthorizationsRequest, opts ...grpc.CallOption) (*ValidAuthorizations, error)
GetCertificate(ctx context.Context, in *Serial, opts ...grpc.CallOption) (*proto1.Certificate, error)
GetPrecertificate(ctx context.Context, in *Serial, opts ...grpc.CallOption) (*proto1.Certificate, error)
GetCertificateStatus(ctx context.Context, in *Serial, opts ...grpc.CallOption) (*CertificateStatus, error)
CountCertificatesByNames(ctx context.Context, in *CountCertificatesByNamesRequest, opts ...grpc.CallOption) (*CountByNames, error)
CountRegistrationsByIP(ctx context.Context, in *CountRegistrationsByIPRequest, opts ...grpc.CallOption) (*Count, error)
@ -2081,6 +2082,15 @@ func (c *storageAuthorityClient) GetCertificate(ctx context.Context, in *Serial,
return out, nil
}
func (c *storageAuthorityClient) GetPrecertificate(ctx context.Context, in *Serial, opts ...grpc.CallOption) (*proto1.Certificate, error) {
out := new(proto1.Certificate)
err := c.cc.Invoke(ctx, "/sa.StorageAuthority/GetPrecertificate", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *storageAuthorityClient) GetCertificateStatus(ctx context.Context, in *Serial, opts ...grpc.CallOption) (*CertificateStatus, error) {
out := new(CertificateStatus)
err := c.cc.Invoke(ctx, "/sa.StorageAuthority/GetCertificateStatus", in, out, opts...)
@ -2441,6 +2451,7 @@ type StorageAuthorityServer interface {
GetPendingAuthorization(context.Context, *GetPendingAuthorizationRequest) (*proto1.Authorization, error)
GetValidAuthorizations(context.Context, *GetValidAuthorizationsRequest) (*ValidAuthorizations, error)
GetCertificate(context.Context, *Serial) (*proto1.Certificate, error)
GetPrecertificate(context.Context, *Serial) (*proto1.Certificate, error)
GetCertificateStatus(context.Context, *Serial) (*CertificateStatus, error)
CountCertificatesByNames(context.Context, *CountCertificatesByNamesRequest) (*CountByNames, error)
CountRegistrationsByIP(context.Context, *CountRegistrationsByIPRequest) (*Count, error)
@ -2507,6 +2518,9 @@ func (*UnimplementedStorageAuthorityServer) GetValidAuthorizations(ctx context.C
func (*UnimplementedStorageAuthorityServer) GetCertificate(ctx context.Context, req *Serial) (*proto1.Certificate, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetCertificate not implemented")
}
func (*UnimplementedStorageAuthorityServer) GetPrecertificate(ctx context.Context, req *Serial) (*proto1.Certificate, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetPrecertificate not implemented")
}
func (*UnimplementedStorageAuthorityServer) GetCertificateStatus(ctx context.Context, req *Serial) (*CertificateStatus, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetCertificateStatus not implemented")
}
@ -2737,6 +2751,24 @@ func _StorageAuthority_GetCertificate_Handler(srv interface{}, ctx context.Conte
return interceptor(ctx, in, info, handler)
}
func _StorageAuthority_GetPrecertificate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Serial)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StorageAuthorityServer).GetPrecertificate(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/sa.StorageAuthority/GetPrecertificate",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StorageAuthorityServer).GetPrecertificate(ctx, req.(*Serial))
}
return interceptor(ctx, in, info, handler)
}
func _StorageAuthority_GetCertificateStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Serial)
if err := dec(in); err != nil {
@ -3467,6 +3499,10 @@ var _StorageAuthority_serviceDesc = grpc.ServiceDesc{
MethodName: "GetCertificate",
Handler: _StorageAuthority_GetCertificate_Handler,
},
{
MethodName: "GetPrecertificate",
Handler: _StorageAuthority_GetPrecertificate_Handler,
},
{
MethodName: "GetCertificateStatus",
Handler: _StorageAuthority_GetCertificateStatus_Handler,

View File

@ -13,6 +13,7 @@ service StorageAuthority {
rpc GetPendingAuthorization(GetPendingAuthorizationRequest) returns (core.Authorization) {}
rpc GetValidAuthorizations(GetValidAuthorizationsRequest) returns (ValidAuthorizations) {}
rpc GetCertificate(Serial) returns (core.Certificate) {}
rpc GetPrecertificate(Serial) returns (core.Certificate) {}
rpc GetCertificateStatus(Serial) returns (CertificateStatus) {}
rpc CountCertificatesByNames(CountCertificatesByNamesRequest) returns (CountByNames) {}
rpc CountRegistrationsByIP(CountRegistrationsByIPRequest) returns (Count) {}

View File

@ -465,6 +465,25 @@ func (ssa *SQLStorageAuthority) GetCertificate(ctx context.Context, serial strin
return cert, err
}
// GetPrecertificate takes a serial number and returns the corresponding
// precertificate, or error if it does not exist.
func (ssa *SQLStorageAuthority) GetPrecertificate(ctx context.Context, reqSerial *sapb.Serial) (*corepb.Certificate, error) {
if !core.ValidSerial(*reqSerial.Serial) {
return nil,
fmt.Errorf("Invalid precertificate serial %q", *reqSerial.Serial)
}
cert, err := selectPrecertificate(ssa.dbMap.WithContext(ctx), *reqSerial.Serial)
if err == sql.ErrNoRows {
return nil,
berrors.NotFoundError("precertificate with serial %q not found", *reqSerial.Serial)
}
if err != nil {
return nil, err
}
return bgrpc.CertToPB(cert), nil
}
// GetCertificateStatus takes a hexadecimal string representing the full 128-bit serial
// number of a certificate and returns data about that certificate's current
// validity.

View File

@ -54,7 +54,8 @@
"HeadNonceStatusOK": true,
"NewAuthorizationSchema": true,
"RemoveWFE2AccountID": true,
"MandatoryPOSTAsGET": true
"MandatoryPOSTAsGET": true,
"PrecertificateRevocation": true
}
},

View File

@ -0,0 +1,93 @@
// +build integration
package integration
import (
"bytes"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
berrors "github.com/letsencrypt/boulder/errors"
)
// ctAddRejectHost adds a domain to all of the CT test server's reject-host
// lists. If this fails the test is aborted with a fatal error.
func ctAddRejectHost(domain string) error {
for _, port := range []int{4500, 4501, 4510, 4511} {
url := fmt.Sprintf("http://boulder:%d/add-reject-host", port)
body := []byte(fmt.Sprintf(`{"host": %q}`, domain))
resp, err := http.Post(url, "", bytes.NewBuffer(body))
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("adding reject host: %d", resp.StatusCode)
}
resp.Body.Close()
}
return nil
}
// ctGetRejections returns a slice of base64 encoded certificates that were
// rejected by the CT test server at the specified port or an error.
func ctGetRejections(port int) ([]string, error) {
url := fmt.Sprintf("http://boulder:%d/get-rejections", port)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf(
"getting rejections: status %d", resp.StatusCode)
}
var rejections []string
err = json.NewDecoder(resp.Body).Decode(&rejections)
if err != nil {
return nil, err
}
return rejections, nil
}
// ctFindRejection returns a parsed x509.Certificate matching the given domains
// from the base64 certificates the CT test server rejected. If no rejected
// certificate matching the provided domains is found an error is returned.
func ctFindRejection(port int, domains []string) (*x509.Certificate, error) {
// Parse each rejection cert
var cert *x509.Certificate
rejections, err := ctGetRejections(port)
if err != nil {
return nil, err
}
RejectionLoop:
for _, r := range rejections {
precertDER, err := base64.StdEncoding.DecodeString(r)
if err != nil {
return nil, err
}
c, err := x509.ParseCertificate(precertDER)
if err != nil {
return nil, err
}
// If the cert doesn't have the right number of names it won't be a match.
if len(c.DNSNames) != len(domains) {
continue
}
// If any names don't match, it isn't a match
for i, name := range c.DNSNames {
if name != domains[i] {
continue RejectionLoop
}
}
// It's a match!
cert = c
break
}
if cert == nil {
return nil, berrors.NotFoundError("no matching ct-test-srv rejection found")
}
return cert, nil
}

View File

@ -9,6 +9,7 @@ import (
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/hex"
"fmt"
"net/http"
@ -17,6 +18,10 @@ import (
"github.com/eggsampler/acme/v2"
)
var (
OIDExtensionCTPoison = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}
)
func random_domain() string {
var bytes [3]byte
rand.Read(bytes[:])
@ -80,11 +85,15 @@ type issuanceResult struct {
certs []*x509.Certificate
}
func authAndIssue(domains []string) (*issuanceResult, error) {
c, err := makeClient()
if err != nil {
return nil, err
func authAndIssue(c *client, csrKey *ecdsa.PrivateKey, domains []string) (*issuanceResult, error) {
var err error
if c == nil {
c, err = makeClient()
if err != nil {
return nil, err
}
}
var ids []acme.Identifier
for _, domain := range domains {
ids = append(ids, acme.Identifier{Type: "dns", Value: domain})
@ -116,7 +125,7 @@ func authAndIssue(domains []string) (*issuanceResult, error) {
}
}
csr, err := makeCSR(domains)
csr, err := makeCSR(csrKey, domains)
if err != nil {
return nil, err
}
@ -132,19 +141,22 @@ func authAndIssue(domains []string) (*issuanceResult, error) {
return &issuanceResult{order, certs}, nil
}
func makeCSR(domains []string) (*x509.CertificateRequest, error) {
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, fmt.Errorf("generating certificate key: %s", err)
func makeCSR(k *ecdsa.PrivateKey, domains []string) (*x509.CertificateRequest, error) {
var err error
if k == nil {
k, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, fmt.Errorf("generating certificate key: %s", err)
}
}
csrDer, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{
SignatureAlgorithm: x509.ECDSAWithSHA256,
PublicKeyAlgorithm: x509.ECDSA,
PublicKey: certKey.Public(),
PublicKey: k.Public(),
Subject: pkix.Name{CommonName: domains[0]},
DNSNames: domains,
}, certKey)
}, k)
if err != nil {
return nil, fmt.Errorf("making csr: %s", err)
}

View File

@ -3,16 +3,13 @@
package integration
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"testing"
ocsp_helper "github.com/letsencrypt/boulder/test/ocsp/helper"
"golang.org/x/crypto/ocsp"
)
func TestPrecertificateOCSP(t *testing.T) {
@ -21,21 +18,13 @@ func TestPrecertificateOCSP(t *testing.T) {
return
}
domain := random_domain()
for _, port := range []int{4500, 4501, 4510, 4511} {
url := fmt.Sprintf("http://boulder:%d/add-reject-host", port)
body := []byte(fmt.Sprintf(`{"host": "%s"}`, domain))
resp, err := http.Post(url, "", bytes.NewBuffer(body))
if err != nil {
t.Fatalf("adding reject host: %s", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("adding reject host: %d", resp.StatusCode)
}
resp.Body.Close()
err := ctAddRejectHost(domain)
if err != nil {
t.Fatalf("adding ct-test-srv reject host: %s", err)
}
os.Setenv("DIRECTORY", "http://boulder:4001/directory")
_, err := authAndIssue([]string{domain})
_, err = authAndIssue(nil, nil, []string{domain})
if err != nil {
if strings.Contains(err.Error(), "urn:ietf:params:acme:error:serverInternal") &&
strings.Contains(err.Error(), "SCT embedding") {
@ -47,26 +36,16 @@ func TestPrecertificateOCSP(t *testing.T) {
t.Fatal("expected error issuing for domain rejected by CT servers; got none")
}
resp, err := http.Get("http://boulder:4500/get-rejections")
rejections, err := ctGetRejections(4500)
if err != nil {
t.Fatalf("getting rejections: %s", err)
t.Fatalf("getting ct-test-srv rejections: %s", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("getting rejections: status %d", resp.StatusCode)
}
var rejections []string
err = json.NewDecoder(resp.Body).Decode(&rejections)
if err != nil {
t.Fatalf("parsing rejections: %s", err)
}
for _, r := range rejections {
rejectedCertBytes, err := base64.StdEncoding.DecodeString(r)
if err != nil {
t.Fatalf("decoding rejected cert: %s", err)
}
_, err = ocsp_helper.ReqDER(rejectedCertBytes)
_, err = ocsp_helper.ReqDER(rejectedCertBytes, ocsp.Good)
if err != nil {
t.Errorf("requesting OCSP for rejected precertificate: %s", err)
}

View File

@ -0,0 +1,136 @@
// +build integration
package integration
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"os"
"strings"
"testing"
"github.com/letsencrypt/boulder/test"
ocsp_helper "github.com/letsencrypt/boulder/test/ocsp/helper"
"golang.org/x/crypto/ocsp"
)
// isPrecert returns true if the provided cert has an extension with the OID
// equal to OIDExtensionCTPoison.
func isPrecert(cert *x509.Certificate) bool {
for _, ext := range cert.Extensions {
if ext.Id.Equal(OIDExtensionCTPoison) {
return true
}
}
return false
}
// TestPrecertificateRevocation tests that a precertificate without a matching
// certificate can be revoked using all of the available RFC 8555 revocation
// authentication mechansims.
func TestPrecertificateRevocation(t *testing.T) {
// This test is gated on the PrecertificateRevocation feature flag.
if !strings.Contains(os.Getenv("BOULDER_CONFIG_DIR"), "test/config-next") {
return
}
// Create a base account to use for revocation tests.
os.Setenv("DIRECTORY", "http://boulder:4001/directory")
c, err := makeClient()
test.AssertNotError(t, err, "creating acme client")
// Create a specific key for CSRs so that it is possible to test revocation
// with the cert key.
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
test.AssertNotError(t, err, "creating random cert key")
// Create a second account to test revocation with an equally authorized account
otherAccount, err := makeClient()
test.AssertNotError(t, err, "creating second acme client")
// Preauthorize a specific domain with the other account before it has been
// added to the ct-test-srv reject list.
preAuthDomain := random_domain()
_, err = authAndIssue(otherAccount, nil, []string{preAuthDomain})
test.AssertNotError(t, err, "preauthorizing second acme client")
testCases := []struct {
name string
domain string
revokeClient *client
revokeKey crypto.Signer
}{
{
name: "revocation by certificate key",
revokeKey: certKey,
},
{
name: "revocation by owner account key",
revokeKey: c.Account.PrivateKey,
},
{
name: "equivalently authorized account key",
revokeClient: otherAccount,
revokeKey: otherAccount.Account.PrivateKey,
domain: preAuthDomain,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// If the test case didn't specify a domain make one up randomly
if tc.domain == "" {
tc.domain = random_domain()
}
// If the test case didn't specify a different client to use for
// revocation use c.
if tc.revokeClient == nil {
tc.revokeClient = c
}
// Make sure the ct-test-srv will reject issuance for the domain
err := ctAddRejectHost(tc.domain)
test.AssertNotError(t, err, "adding ct-test-srv reject host")
// Issue a certificate for the name using the `c` client. It should fail
// because not enough SCTs can be collected, leaving a precert without
// a matching final cert.
_, err = authAndIssue(c, certKey, []string{tc.domain})
test.AssertError(t, err, "expected error from authAndIssue, was nil")
if !strings.Contains(err.Error(), "urn:ietf:params:acme:error:serverInternal") ||
!strings.Contains(err.Error(), "SCT embedding") {
t.Fatal(err)
}
// Find the rejected precert from the first ct-test-srv for the domains we
// attempted to issue for.
cert, err := ctFindRejection(4500, []string{tc.domain})
test.AssertNotError(t, err, "finding rejected precertificate")
// To be confident that we're testing the right thing also verify that the
// rejection is a poisoned precertificate.
if !isPrecert(cert) {
t.Fatal("precert was missing poison extension")
}
// To start with the precertificate should have a Good OCSP response.
_, err = ocsp_helper.ReqDER(cert.Raw, ocsp.Good)
test.AssertNotError(t, err, "requesting OCSP for precert")
// Revoke the precertificate using the specified key and client
err = tc.revokeClient.RevokeCertificate(
tc.revokeClient.Account,
cert,
tc.revokeKey,
ocsp.KeyCompromise)
test.AssertNotError(t, err, "revoking precert")
// Check the OCSP response for the precertificate again. It should now be
// revoked.
_, err = ocsp_helper.ReqDER(cert.Raw, ocsp.Revoked)
test.AssertNotError(t, err, "requesting OCSP for revoked precert")
})
}
}

View File

@ -96,10 +96,10 @@ func Req(fileName string) (*ocsp.Response, error) {
if err != nil {
return nil, err
}
return ReqDER(contents)
return ReqDER(contents, *expectStatus)
}
func ReqDER(der []byte) (*ocsp.Response, error) {
func ReqDER(der []byte, expectStatus int) (*ocsp.Response, error) {
cert, err := parse(der)
if err != nil {
return nil, fmt.Errorf("parsing certificate: %s", err)
@ -150,7 +150,7 @@ func ReqDER(der []byte) (*ocsp.Response, error) {
if len(respBytes) == 0 {
return nil, fmt.Errorf("empty reponse body")
}
return parseAndPrint(respBytes, cert, issuer)
return parseAndPrint(respBytes, cert, issuer, expectStatus)
}
func sendHTTPRequest(req []byte, ocspURL *url.URL) (*http.Response, error) {
@ -194,14 +194,14 @@ func getOCSPURL(cert *x509.Certificate) (*url.URL, error) {
return ocspURL, nil
}
func parseAndPrint(respBytes []byte, cert, issuer *x509.Certificate) (*ocsp.Response, error) {
func parseAndPrint(respBytes []byte, cert, issuer *x509.Certificate, expectStatus int) (*ocsp.Response, error) {
fmt.Printf("\nDecoding body: %s\n", base64.StdEncoding.EncodeToString(respBytes))
resp, err := ocsp.ParseResponseForCert(respBytes, cert, issuer)
if err != nil {
return nil, fmt.Errorf("parsing response: %s", err)
}
if resp.Status != *expectStatus {
return nil, fmt.Errorf("wrong CertStatus %d, expected %d", resp.Status, *expectStatus)
if resp.Status != expectStatus {
return nil, fmt.Errorf("wrong CertStatus %d, expected %d", resp.Status, expectStatus)
}
timeTilExpiry := time.Until(resp.NextUpdate)
tooSoonDuration := time.Duration(*tooSoon) * time.Hour

View File

@ -32,7 +32,7 @@ GRANT SELECT,INSERT,DELETE ON orderFqdnSets TO 'sa'@'localhost';
GRANT SELECT,INSERT,UPDATE ON authz2 TO 'sa'@'localhost';
GRANT SELECT,INSERT ON orderToAuthz2 TO 'sa'@'localhost';
GRANT INSERT ON serials TO 'sa'@'localhost';
GRANT INSERT ON precertificates TO 'sa'@'localhost';
GRANT SELECT,INSERT ON precertificates TO 'sa'@'localhost';
-- OCSP Responder
GRANT SELECT ON certificateStatus TO 'ocsp_resp'@'localhost';

View File

@ -83,6 +83,11 @@ type WebFrontEndImpl struct {
// sorted from leaf to root
certificateChains map[string][]byte
// issuerCertificates is a slice of known issuer certificates built with the
// first entry from each of the certificateChains. These certificates are used
// to verify the signature of certificates provided in revocation requests.
issuerCertificates []*x509.Certificate
// URL to the current subscriber agreement (should contain some version identifier)
SubscriberAgreementURL string
@ -121,6 +126,7 @@ func NewWebFrontEndImpl(
clk clock.Clock,
keyPolicy goodkey.KeyPolicy,
certificateChains map[string][]byte,
issuerCertificates []*x509.Certificate,
remoteNonceService noncepb.NonceServiceClient,
noncePrefixMap map[string]noncepb.NonceServiceClient,
logger blog.Logger,
@ -130,6 +136,7 @@ func NewWebFrontEndImpl(
clk: clk,
keyPolicy: keyPolicy,
certificateChains: certificateChains,
issuerCertificates: issuerCertificates,
stats: initStats(scope),
scope: scope,
remoteNonceService: remoteNonceService,
@ -695,22 +702,65 @@ func (wfe *WebFrontEndImpl) processRevocation(
// Compute and record the serial number of the provided certificate
serial := core.SerialToString(providedCert.SerialNumber)
logEvent.Extra["ProvidedCertificateSerial"] = serial
notFoundProb := probs.NotFound("No such certificate")
// Lookup the certificate by the serial. If the certificate wasn't found, or
// it wasn't a byte-for-byte match to the certificate requested for
// revocation, return an error
cert, err := wfe.SA.GetCertificate(ctx, serial)
if err != nil || !bytes.Equal(cert.DER, revokeRequest.CertificateDER) {
return probs.NotFound("No such certificate")
var certDER []byte
// If the PrecertificateRevocation feature flag is enabled that means we won't
// do a byte-for-byte comparison of the providedCert against the stored cert
// returned by SA.GetCertificate because a precert will always fail this
// check. Instead, perform a signature validation of the providedCert using
// the known issuer public keys. If the providedCert signature can not be
// validated with any of the known issuers return a not-found error.
if features.Enabled(features.PrecertificateRevocation) {
// If no issuerCertificates are initialized but the PrecertificateRevocation
// feature flag is enabled then return a runtime server internal error
// rather than fail open.
if len(wfe.issuerCertificates) == 0 {
return probs.ServerInternal(
"unable to verify provided certificate, empty issuerCertificates")
}
// Try to validate the signature on the provided cert using each of the
// known issuer certificates. This is O(n) but we always expect to have
// a small number of configured issuers.
var validIssuerSignature bool
for _, issuer := range wfe.issuerCertificates {
if err := providedCert.CheckSignatureFrom(issuer); err == nil {
validIssuerSignature = true
break
}
}
// If none of the issuers validate the signature on the provided cert then
// return an error.
if !validIssuerSignature {
return notFoundProb
}
// If the signature validates we can use the provided cert's DER for
// revocation safely.
certDER = providedCert.Raw
} else {
// When the precertificate revocation feature flag isn't enabled try to find
// a finalized cert in the DB matching the serial. If there is one, it needs
// to be a byte-for-byte match with the requested cert.
cert, err := wfe.SA.GetCertificate(ctx, serial)
if err != nil {
return notFoundProb
}
// If the certificate in the DB isn't a byte for byte match, return a problem
if !bytes.Equal(cert.DER, revokeRequest.CertificateDER) {
return notFoundProb
}
certDER = cert.DER
}
// Parse the certificate into memory
parsedCertificate, err := x509.ParseCertificate(cert.DER)
parsedCertificate, err := x509.ParseCertificate(certDER)
if err != nil {
// InternalServerError because cert.DER came from our own DB.
// InternalServerError because certDER came from our own DB, or was
// confirmed issued by one of our own issuers.
return probs.ServerInternal("invalid parse of stored certificate")
}
logEvent.Extra["RetrievedCertificateSerial"] = core.SerialToString(parsedCertificate.SerialNumber)
logEvent.Extra["RetrievedCertificateSerial"] = serial
logEvent.Extra["RetrievedCertificateDNSNames"] = parsedCertificate.DNSNames
if parsedCertificate.NotAfter.Before(wfe.clk.Now()) {
@ -779,21 +829,53 @@ func (wfe *WebFrontEndImpl) revokeCertByKeyID(
// certificate by checking that the account has valid authorizations for all
// of the names in the certificate or was the issuing account
authorizedToRevoke := func(parsedCertificate *x509.Certificate) *probs.ProblemDetails {
cert, err := wfe.SA.GetCertificate(ctx, core.SerialToString(parsedCertificate.SerialNumber))
if err != nil {
// Try to find a stored final certificate for the serial number
serial := core.SerialToString(parsedCertificate.SerialNumber)
cert, err := wfe.SA.GetCertificate(ctx, serial)
if berrors.Is(err, berrors.NotFound) && features.Enabled(features.PrecertificateRevocation) {
// If there was an error, it was a not found error, and the precertificate
// revocation feature is enabled, then try to find a stored precert.
pbCert, err := wfe.SA.GetPrecertificate(ctx,
&sapb.Serial{Serial: &serial})
if berrors.Is(err, berrors.NotFound) {
// If looking up a precert also returned a not found error then return
// a not found problem.
return probs.NotFound("No such certificate")
} else if err != nil {
// If there was any other error looking up the precert then return
// a server internal problem.
return probs.ServerInternal("Failed to retrieve certificate")
}
cert, err = bgrpc.PBToCert(pbCert)
if err != nil {
return probs.ServerInternal("Failed to unmarshal protobuf certificate")
}
} else if berrors.Is(err, berrors.NotFound) {
// Otherwise if the err was not nil and was a not found error but the
// precertificate revocation feature flag is not enabled, return a not
// found error.
return probs.NotFound("No such certificate")
} else if err != nil {
// Otherwise if the err was not nil and not a not found error, return
// a server internal problem.
return probs.ServerInternal("Failed to retrieve certificate")
}
// If the cert/precert is owned by the requester then return nil, it is an
// authorized revocation.
if cert.RegistrationID == acct.ID {
return nil
}
// Otherwise check if the account, while not the owner, has equivalent authorizations
valid, err := wfe.acctHoldsAuthorizations(ctx, acct.ID, parsedCertificate.DNSNames)
if err != nil {
return probs.ServerInternal("Failed to retrieve authorizations for names in certificate")
}
// If it doesn't, return an unauthorized problem.
if !valid {
return probs.Unauthorized(
"The key ID specified in the revocation request does not hold valid authorizations for all names in the certificate to be revoked")
}
// If it does, return nil. It is an an authorized revocation.
return nil
}
return wfe.processRevocation(ctx, jwsBody, acct.ID, authorizedToRevoke, request, logEvent)

View File

@ -5,6 +5,7 @@ import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
@ -352,12 +353,18 @@ func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock) {
chainPEM, err := ioutil.ReadFile("../test/test-ca2.pem")
test.AssertNotError(t, err, "Unable to read ../test/test-ca2.pem")
chainDER, _ := pem.Decode(chainPEM)
certChains := map[string][]byte{
"http://localhost:4000/acme/issuer-cert": append([]byte{'\n'}, chainPEM...),
}
issuerCert, err := x509.ParseCertificate(chainDER.Bytes)
test.AssertNotError(t, err, "Unable to parse issuer cert")
issuerCertificates := []*x509.Certificate{
issuerCert,
}
wfe, err := NewWebFrontEndImpl(stats, fc, testKeyPolicy, certChains, nil, nil, blog.NewMock())
wfe, err := NewWebFrontEndImpl(stats, fc, testKeyPolicy, certChains, issuerCertificates, nil, nil, blog.NewMock())
test.AssertNotError(t, err, "Unable to create WFE")
wfe.SubscriberAgreementURL = agreementURL
@ -2542,11 +2549,15 @@ func makeRevokeRequestJSON(reason *revocation.Reason) ([]byte, error) {
if err != nil {
return nil, err
}
return makeRevokeRequestJSONForCert(certBlock.Bytes, reason)
}
func makeRevokeRequestJSONForCert(der []byte, reason *revocation.Reason) ([]byte, error) {
revokeRequest := struct {
CertificateDER core.JSONBuffer `json:"certificate"`
Reason *revocation.Reason `json:"reason"`
}{
CertificateDER: certBlock.Bytes,
CertificateDER: der,
Reason: reason,
}
revokeRequestJSON, err := json.Marshal(revokeRequest)
@ -2591,6 +2602,60 @@ func TestRevokeCertificateCertKey(t *testing.T) {
test.AssertEquals(t, responseWriter.Body.String(), "")
}
// Valid revocation request for existing, non-revoked cert, signed with cert
// key, precertificate revocation feature flag enabled.
func TestRevokePreCertificateFeatureEnabled(t *testing.T) {
_ = features.Set(map[string]bool{"PrecertificateRevocation": true})
defer features.Reset()
wfe, fc := setupWFE(t)
wfe.SA = &mockSANoSuchRegistration{mocks.NewStorageAuthority(fc)}
responseWriter := httptest.NewRecorder()
keyPemBytes, err := ioutil.ReadFile("test/238.key")
test.AssertNotError(t, err, "Failed to load key")
key := loadKey(t, keyPemBytes)
revokeRequestJSON, err := makeRevokeRequestJSON(nil)
test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
_, _, jwsBody := signRequestEmbed(t,
key, "http://localhost/revoke-cert", string(revokeRequestJSON), wfe.nonceService)
// Revoking a certificate that was issued by a known issuer should work
wfe.RevokeCertificate(ctx, newRequestEvent(), responseWriter,
makePostRequestWithPath("revoke-cert", jwsBody))
test.AssertEquals(t, responseWriter.Code, 200)
test.AssertEquals(t, responseWriter.Body.String(), "")
// Make a self-signed junk certificate
k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
test.AssertNotError(t, err, "unexpected error making random private key")
// Use a known serial from the mocks/mocks.go GetCertificate mock. This
// shouldn't matter for the precertificate revocation feature flag flow but
// will ensure we don't get a 404 response from the feature flag being handled
// incorrectly and the GetCertificate() mock not knowing the serial.
knownSerial, err := core.StringToSerial("0000000000000000000000000000000000ee")
test.AssertNotError(t, err, "Unexpected error converting known serial to bigint")
template := &x509.Certificate{
SerialNumber: knownSerial,
}
certDER, err := x509.CreateCertificate(rand.Reader, template, template, k.Public(), k)
test.AssertNotError(t, err, "Unexpected error creating self-signed junk cert")
revokeRequestJSON, err = makeRevokeRequestJSONForCert(certDER, nil)
test.AssertNotError(t, err, "Failed to make revokeRequestJSON for certDER")
// Revoking a certificate that wasn't signed by a known issuer should fail
responseWriter = httptest.NewRecorder()
_, _, jwsBody = signRequestEmbed(t,
key, "http://localhost/revoke-cert", string(revokeRequestJSON), wfe.nonceService)
wfe.RevokeCertificate(ctx, newRequestEvent(), responseWriter,
makePostRequestWithPath("revoke-cert", jwsBody))
// It should result in a 404 response with a problem body
test.AssertEquals(t, responseWriter.Code, 404)
test.AssertEquals(t, responseWriter.Body.String(), "{\n \"type\": \"urn:ietf:params:acme:error:malformed\",\n \"detail\": \"No such certificate\",\n \"status\": 404\n}")
}
func TestRevokeCertificateReasons(t *testing.T) {
wfe, fc := setupWFE(t)
ra := wfe.RA.(*MockRegistrationAuthority)