Implement unpredictable issuance from similar intermediates (#7418)

Replace the CA's "useForRSA" and "useForECDSA" config keys with a single
"active" boolean. When the CA starts up, all active RSA issuers will be
used to issue precerts with RSA pubkeys, and all ECDSA issuers will be
used to issue precerts with ECDSA pubkeys (if the ECDSAForAll flag is
true; otherwise just those that are on the allow-list). All "inactive"
issuers can still issue OCSP responses, CRLs, and (notably) final
certificates.

Instead of using the "useForRSA" and "useForECDSA" flags, plus implicit
config ordering, to determine which issuer to use to handle a given
issuance, simply use the issuer's public key algorithm to determine
which issuances it should be handling. All implicit ordering
considerations are removed, because the "active" certificates now just
form a pool that is sampled from randomly.

To facilitate this, update some unit and integration tests to be more
flexible and try multiple potential issuing intermediates, particularly
when constructing OCSP requests.

For this change to be safe to deploy with no user-visible behavior
changes, the CA configs must contain:
- Exactly one RSA-keyed intermediate with "useForRSALeaves" set to true;
and
- Exactly one ECDSA-keyed intermediate with "useForECDSALeaves" set to
true.

If the configs contain more than one intermediate meeting one of the
bullets above, then randomized issuance will begin immediately.

Fixes https://github.com/letsencrypt/boulder/issues/7291
Fixes https://github.com/letsencrypt/boulder/issues/7290
This commit is contained in:
Aaron Gable 2024-04-18 10:00:38 -07:00 committed by GitHub
parent 13172ac3f1
commit 94d14689bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 355 additions and 183 deletions

View File

@ -14,6 +14,7 @@ import (
"errors"
"fmt"
"math/big"
mrand "math/rand"
"strings"
"time"
@ -46,11 +47,11 @@ const (
)
// Two maps of keys to Issuers. Lookup by PublicKeyAlgorithm is useful for
// determining which issuer to use to sign a given (pre)cert, based on its
// PublicKeyAlgorithm. Lookup by NameID is useful for looking up the appropriate
// determining the set of issuers which can sign a given (pre)cert, based on its
// PublicKeyAlgorithm. Lookup by NameID is useful for looking up a specific
// issuer based on the issuer of a given (pre)certificate.
type issuerMaps struct {
byAlg map[x509.PublicKeyAlgorithm]*issuance.Issuer
byAlg map[x509.PublicKeyAlgorithm][]*issuance.Issuer
byNameID map[issuance.NameID]*issuance.Issuer
}
@ -99,24 +100,29 @@ type certificateAuthorityImpl struct {
lintErrorCount prometheus.Counter
}
// makeIssuerMaps processes a list of issuers into a set of maps, mapping
// nearly-unique identifiers of those issuers to the issuers themselves. Note
// that, if two issuers have the same nearly-unique ID, the *latter* one in
// the input list "wins".
func makeIssuerMaps(issuers []*issuance.Issuer) issuerMaps {
issuersByAlg := make(map[x509.PublicKeyAlgorithm]*issuance.Issuer, 2)
// makeIssuerMaps processes a list of issuers into a set of maps for easy
// lookup either by key algorithm (useful for picking an issuer for a precert)
// or by unique ID (useful for final certs, OCSP, and CRLs). If two issuers with
// the same unique ID are encountered, an error is returned.
func makeIssuerMaps(issuers []*issuance.Issuer) (issuerMaps, error) {
issuersByAlg := make(map[x509.PublicKeyAlgorithm][]*issuance.Issuer, 2)
issuersByNameID := make(map[issuance.NameID]*issuance.Issuer, len(issuers))
for _, issuer := range issuers {
for _, alg := range issuer.Algs() {
// TODO(#5259): Enforce that there is only one issuer for each algorithm,
// instead of taking the first issuer for each algorithm type.
if issuersByAlg[alg] == nil {
issuersByAlg[alg] = issuer
}
if _, found := issuersByNameID[issuer.NameID()]; found {
return issuerMaps{}, fmt.Errorf("two issuers with same NameID %d (%s) configured", issuer.NameID(), issuer.Name())
}
issuersByNameID[issuer.NameID()] = issuer
if issuer.IsActive() {
issuersByAlg[issuer.KeyType()] = append(issuersByAlg[issuer.KeyType()], issuer)
}
}
return issuerMaps{issuersByAlg, issuersByNameID}
if i, ok := issuersByAlg[x509.ECDSA]; !ok || len(i) == 0 {
return issuerMaps{}, errors.New("no ECDSA issuers configured")
}
if i, ok := issuersByAlg[x509.RSA]; !ok || len(i) == 0 {
return issuerMaps{}, errors.New("no RSA issuers configured")
}
return issuerMaps{issuersByAlg, issuersByNameID}, nil
}
// makeCertificateProfilesMap processes a list of certificate issuance profile
@ -237,7 +243,10 @@ func NewCertificateAuthorityImpl(
return nil, err
}
issuers := makeIssuerMaps(boulderIssuers)
issuers, err := makeIssuerMaps(boulderIssuers)
if err != nil {
return nil, err
}
lintErrorCount := prometheus.NewCounter(
prometheus.CounterOpts{
@ -393,27 +402,27 @@ func (ca *certificateAuthorityImpl) IssueCertificateForPrecertificate(ctx contex
}
names := strings.Join(issuanceReq.DNSNames, ", ")
ca.log.AuditInfof("Signing cert: serial=[%s] regID=[%d] names=[%s] certProfileName=[%s] certProfileHash=[%x] precert=[%s]",
serialHex, req.RegistrationID, names, certProfile.name, certProfile.hash, hex.EncodeToString(precert.Raw))
ca.log.AuditInfof("Signing cert: issuer=[%s] serial=[%s] regID=[%d] names=[%s] certProfileName=[%s] certProfileHash=[%x] precert=[%s]",
issuer.Name(), serialHex, req.RegistrationID, names, certProfile.name, certProfile.hash, hex.EncodeToString(precert.Raw))
_, issuanceToken, err := issuer.Prepare(certProfile.profile, issuanceReq)
if err != nil {
ca.log.AuditErrf("Preparing cert failed: serial=[%s] regID=[%d] names=[%s] certProfileName=[%s] certProfileHash=[%x] err=[%v]",
serialHex, req.RegistrationID, names, certProfile.name, certProfile.hash, err)
ca.log.AuditErrf("Preparing cert failed: issuer=[%s] serial=[%s] regID=[%d] names=[%s] certProfileName=[%s] certProfileHash=[%x] err=[%v]",
issuer.Name(), serialHex, req.RegistrationID, names, certProfile.name, certProfile.hash, err)
return nil, berrors.InternalServerError("failed to prepare certificate signing: %s", err)
}
certDER, err := issuer.Issue(issuanceToken)
if err != nil {
ca.noteSignError(err)
ca.log.AuditErrf("Signing cert failed: serial=[%s] regID=[%d] names=[%s] certProfileName=[%s] certProfileHash=[%x] err=[%v]",
serialHex, req.RegistrationID, names, certProfile.name, certProfile.hash, err)
ca.log.AuditErrf("Signing cert failed: issuer=[%s] serial=[%s] regID=[%d] names=[%s] certProfileName=[%s] certProfileHash=[%x] err=[%v]",
issuer.Name(), serialHex, req.RegistrationID, names, certProfile.name, certProfile.hash, err)
return nil, berrors.InternalServerError("failed to sign certificate: %s", err)
}
ca.signatureCount.With(prometheus.Labels{"purpose": string(certType), "issuer": issuer.Name()}).Inc()
ca.log.AuditInfof("Signing cert success: serial=[%s] regID=[%d] names=[%s] certificate=[%s] certProfileName=[%s] certProfileHash=[%x]",
serialHex, req.RegistrationID, names, hex.EncodeToString(certDER), certProfile.name, certProfile.hash)
ca.log.AuditInfof("Signing cert success: issuer=[%s] serial=[%s] regID=[%d] names=[%s] certificate=[%s] certProfileName=[%s] certProfileHash=[%x]",
issuer.Name(), serialHex, req.RegistrationID, names, hex.EncodeToString(certDER), certProfile.name, certProfile.hash)
_, err = ca.sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
Der: certDER,
@ -421,8 +430,8 @@ func (ca *certificateAuthorityImpl) IssueCertificateForPrecertificate(ctx contex
Issued: timestamppb.New(ca.clk.Now()),
})
if err != nil {
ca.log.AuditErrf("Failed RPC to store at SA: serial=[%s] cert=[%s] issuerID=[%d] regID=[%d] orderID=[%d] certProfileName=[%s] certProfileHash=[%x] err=[%v]",
serialHex, hex.EncodeToString(certDER), issuer.NameID(), req.RegistrationID, req.OrderID, certProfile.name, certProfile.hash, err)
ca.log.AuditErrf("Failed RPC to store at SA: issuer=[%s] serial=[%s] cert=[%s] regID=[%d] orderID=[%d] certProfileName=[%s] certProfileHash=[%x] err=[%v]",
issuer.Name(), serialHex, hex.EncodeToString(certDER), req.RegistrationID, req.OrderID, certProfile.name, certProfile.hash, err)
return nil, err
}
@ -514,17 +523,19 @@ func (ca *certificateAuthorityImpl) issuePrecertificateInner(ctx context.Context
return nil, nil, err
}
// Use the issuer which corresponds to the algorithm of the public key
// contained in the CSR, unless we have an allowlist of registration IDs
// for ECDSA, in which case switch all not-allowed accounts to RSA issuance.
// Select which pool of issuers to use, based on the to-be-issued cert's key
// type and whether we're using the ECDSA Allow List.
alg := csr.PublicKeyAlgorithm
if alg == x509.ECDSA && !features.Get().ECDSAForAll && ca.ecdsaAllowList != nil && !ca.ecdsaAllowList.permitted(issueReq.RegistrationID) {
alg = x509.RSA
}
issuer, ok := ca.issuers.byAlg[alg]
if !ok {
return nil, nil, berrors.InternalServerError("no issuer found for public key algorithm %s", csr.PublicKeyAlgorithm)
// Select a random issuer from among the active issuers of this key type.
issuerPool, ok := ca.issuers.byAlg[alg]
if !ok || len(issuerPool) == 0 {
return nil, nil, berrors.InternalServerError("no issuers found for public key algorithm %s", csr.PublicKeyAlgorithm)
}
issuer := issuerPool[mrand.Intn(len(issuerPool))]
if issuer.Cert.NotAfter.Before(validity.NotAfter) {
err = berrors.InternalServerError("cannot issue a certificate that expires after the issuer certificate")
@ -557,8 +568,8 @@ func (ca *certificateAuthorityImpl) issuePrecertificateInner(ctx context.Context
lintCertBytes, issuanceToken, err := issuer.Prepare(certProfile.profile, req)
if err != nil {
ca.log.AuditErrf("Preparing precert failed: serial=[%s] regID=[%d] names=[%s] certProfileName=[%s] certProfileHash=[%x] err=[%v]",
serialHex, issueReq.RegistrationID, strings.Join(csr.DNSNames, ", "), certProfile.name, certProfile.hash, err)
ca.log.AuditErrf("Preparing precert failed: issuer=[%s] serial=[%s] regID=[%d] names=[%s] certProfileName=[%s] certProfileHash=[%x] err=[%v]",
issuer.Name(), serialHex, issueReq.RegistrationID, strings.Join(csr.DNSNames, ", "), certProfile.name, certProfile.hash, err)
if errors.Is(err, linter.ErrLinting) {
ca.lintErrorCount.Inc()
}
@ -579,14 +590,14 @@ func (ca *certificateAuthorityImpl) issuePrecertificateInner(ctx context.Context
certDER, err := issuer.Issue(issuanceToken)
if err != nil {
ca.noteSignError(err)
ca.log.AuditErrf("Signing precert failed: serial=[%s] regID=[%d] names=[%s] certProfileName=[%s] certProfileHash=[%x] err=[%v]",
serialHex, issueReq.RegistrationID, strings.Join(csr.DNSNames, ", "), certProfile.name, certProfile.hash, err)
ca.log.AuditErrf("Signing precert failed: issuer=[%s] serial=[%s] regID=[%d] names=[%s] certProfileName=[%s] certProfileHash=[%x] err=[%v]",
issuer.Name(), serialHex, issueReq.RegistrationID, strings.Join(csr.DNSNames, ", "), certProfile.name, certProfile.hash, err)
return nil, nil, berrors.InternalServerError("failed to sign precertificate: %s", err)
}
ca.signatureCount.With(prometheus.Labels{"purpose": string(precertType), "issuer": issuer.Name()}).Inc()
ca.log.AuditInfof("Signing precert success: serial=[%s] regID=[%d] names=[%s] precertificate=[%s] certProfileName=[%s] certProfileHash=[%x]",
serialHex, issueReq.RegistrationID, strings.Join(csr.DNSNames, ", "), hex.EncodeToString(certDER), certProfile.name, certProfile.hash)
ca.log.AuditInfof("Signing precert success: issuer=[%s] serial=[%s] regID=[%d] names=[%s] precertificate=[%s] certProfileName=[%s] certProfileHash=[%x]",
issuer.Name(), serialHex, issueReq.RegistrationID, strings.Join(csr.DNSNames, ", "), hex.EncodeToString(certDER), certProfile.name, certProfile.hash)
return certDER, certProfile.hash[:], nil
}

View File

@ -96,12 +96,6 @@ var (
const arbitraryRegID int64 = 1001
// Useful key and certificate files.
const rsaIntKey = "../test/hierarchy/int-r3.key.pem"
const rsaIntCert = "../test/hierarchy/int-r3.cert.pem"
const ecdsaIntKey = "../test/hierarchy/int-e1.key.pem"
const ecdsaIntCert = "../test/hierarchy/int-e1.cert.pem"
func mustRead(path string) []byte {
return must.Do(os.ReadFile(path))
}
@ -192,28 +186,20 @@ func setup(t *testing.T) *testCtx {
}
test.AssertEquals(t, len(certProfiles), 2)
ecdsaOnlyIssuer, err := issuance.LoadIssuer(issuance.IssuerConfig{
UseForRSALeaves: false,
UseForECDSALeaves: true,
IssuerURL: "http://not-example.com/issuer-url",
OCSPURL: "http://not-example.com/ocsp",
CRLURLBase: "http://not-example.com/crl/",
Location: issuance.IssuerLoc{File: ecdsaIntKey, CertFile: ecdsaIntCert},
}, fc)
test.AssertNotError(t, err, "Couldn't load test issuer")
ecdsaAndRSAIssuer, err := issuance.LoadIssuer(issuance.IssuerConfig{
UseForRSALeaves: true,
UseForECDSALeaves: true,
IssuerURL: "http://not-example.com/issuer-url",
OCSPURL: "http://not-example.com/ocsp",
CRLURLBase: "http://not-example.com/crl/",
Location: issuance.IssuerLoc{File: rsaIntKey, CertFile: rsaIntCert},
}, fc)
test.AssertNotError(t, err, "Couldn't load test issuer")
// Must list ECDSA-only issuer first, so it is the default for ECDSA.
boulderIssuers := []*issuance.Issuer{ecdsaOnlyIssuer, ecdsaAndRSAIssuer}
boulderIssuers := make([]*issuance.Issuer, 4)
for i, name := range []string{"int-r3", "int-r4", "int-e1", "int-e2"} {
boulderIssuers[i], err = issuance.LoadIssuer(issuance.IssuerConfig{
Active: true,
IssuerURL: fmt.Sprintf("http://not-example.com/i/%s", name),
OCSPURL: "http://not-example.com/o",
CRLURLBase: fmt.Sprintf("http://not-example.com/c/%s/", name),
Location: issuance.IssuerLoc{
File: fmt.Sprintf("../test/hierarchy/%s.key.pem", name),
CertFile: fmt.Sprintf("../test/hierarchy/%s.cert.pem", name),
},
}, fc)
test.AssertNotError(t, err, "Couldn't load test issuer")
}
keyPolicy := goodkey.KeyPolicy{
AllowRSA: true,
@ -496,25 +482,114 @@ func TestMultipleIssuers(t *testing.T) {
_, ok := ca.certProfiles.profileByName[selectedProfile]
test.Assert(t, ok, "Certificate profile was expected to exist")
// Test that an RSA CSR gets issuance from the RSA issuer.
// Test that an RSA CSR gets issuance from an RSA issuer.
issuedCert, err := ca.IssuePrecertificate(ctx, &capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID, CertProfileName: selectedProfile})
test.AssertNotError(t, err, "Failed to issue certificate")
cert, err := x509.ParseCertificate(issuedCert.DER)
test.AssertNotError(t, err, "Certificate failed to parse")
err = cert.CheckSignatureFrom(testCtx.boulderIssuers[1].Cert.Certificate)
test.AssertNotError(t, err, "Certificate failed signature validation")
validated := false
for _, issuer := range ca.issuers.byAlg[x509.RSA] {
err = cert.CheckSignatureFrom(issuer.Cert.Certificate)
if err == nil {
validated = true
break
}
}
test.Assert(t, validated, "Certificate failed signature validation")
test.AssertMetricWithLabelsEquals(t, ca.signatureCount, prometheus.Labels{"purpose": "precertificate", "status": "success"}, 1)
// Test that an ECDSA CSR gets issuance from the ECDSA issuer.
// Test that an ECDSA CSR gets issuance from an ECDSA issuer.
issuedCert, err = ca.IssuePrecertificate(ctx, &capb.IssueCertificateRequest{Csr: ECDSACSR, RegistrationID: arbitraryRegID, CertProfileName: selectedProfile})
test.AssertNotError(t, err, "Failed to issue certificate")
cert, err = x509.ParseCertificate(issuedCert.DER)
test.AssertNotError(t, err, "Certificate failed to parse")
err = cert.CheckSignatureFrom(testCtx.boulderIssuers[0].Cert.Certificate)
test.AssertNotError(t, err, "Certificate failed signature validation")
validated = false
for _, issuer := range ca.issuers.byAlg[x509.ECDSA] {
err = cert.CheckSignatureFrom(issuer.Cert.Certificate)
if err == nil {
validated = true
break
}
}
test.Assert(t, validated, "Certificate failed signature validation")
test.AssertMetricWithLabelsEquals(t, ca.signatureCount, prometheus.Labels{"purpose": "precertificate", "status": "success"}, 2)
}
func TestUnpredictableIssuance(t *testing.T) {
testCtx := setup(t)
sa := &mockSA{}
// Load our own set of issuer configs, specifically with:
// - 3 issuers,
// - 2 of which are active
boulderIssuers := make([]*issuance.Issuer, 3)
var err error
for i, name := range []string{"int-e1", "int-e2", "int-r3"} {
boulderIssuers[i], err = issuance.LoadIssuer(issuance.IssuerConfig{
Active: i != 0, // Make one of the ECDSA issuers inactive.
IssuerURL: fmt.Sprintf("http://not-example.com/i/%s", name),
OCSPURL: "http://not-example.com/o",
CRLURLBase: fmt.Sprintf("http://not-example.com/c/%s/", name),
Location: issuance.IssuerLoc{
File: fmt.Sprintf("../test/hierarchy/%s.key.pem", name),
CertFile: fmt.Sprintf("../test/hierarchy/%s.cert.pem", name),
},
}, testCtx.fc)
test.AssertNotError(t, err, "Couldn't load test issuer")
}
ca, err := NewCertificateAuthorityImpl(
sa,
testCtx.pa,
boulderIssuers,
testCtx.defaultCertProfileName,
testCtx.ignoredCertProfileLints,
testCtx.certProfiles,
nil,
testCtx.certExpiry,
testCtx.certBackdate,
testCtx.serialPrefix,
testCtx.maxNames,
testCtx.keyPolicy,
testCtx.logger,
testCtx.stats,
testCtx.signatureCount,
testCtx.signErrorCount,
testCtx.fc)
test.AssertNotError(t, err, "Failed to remake CA")
// Then, modify the resulting issuer maps so that the RSA issuer appears to
// be an ECDSA issuer. This would be easier if we had three ECDSA issuers to
// use here, but that doesn't exist in //test/hierarchy (yet).
ca.issuers.byAlg[x509.ECDSA] = append(ca.issuers.byAlg[x509.ECDSA], ca.issuers.byAlg[x509.RSA]...)
ca.issuers.byAlg[x509.RSA] = []*issuance.Issuer{}
// Issue the same (ECDSA-keyed) certificate 20 times. None of the issuances
// should come from the inactive issuer (int-e1). At least one issuance should
// come from each of the two active issuers (int-e2 and int-r3). With 20
// trials, the probability that all 20 issuances come from the same issuer is
// 0.5 ^ 20 = 9.5e-7 ~= 1e-6 = 1 in a million, so we do not consider this test
// to be flaky.
req := &capb.IssueCertificateRequest{Csr: ECDSACSR, RegistrationID: arbitraryRegID}
seenE2 := false
seenR3 := false
for i := 0; i < 20; i++ {
result, err := ca.IssuePrecertificate(ctx, req)
test.AssertNotError(t, err, "Failed to issue test certificate")
cert, err := x509.ParseCertificate(result.DER)
test.AssertNotError(t, err, "Failed to parse test certificate")
if strings.Contains(cert.Issuer.CommonName, "E1") {
t.Fatal("Issued certificate from inactive issuer")
} else if strings.Contains(cert.Issuer.CommonName, "E2") {
seenE2 = true
} else if strings.Contains(cert.Issuer.CommonName, "R3") {
seenR3 = true
}
}
test.Assert(t, seenE2, "Expected at least one issuance from active issuer")
test.Assert(t, seenR3, "Expected at least one issuance from active issuer")
}
func TestProfiles(t *testing.T) {
t.Parallel()
ctx := setup(t)
@ -695,7 +770,7 @@ func TestECDSAAllowList(t *testing.T) {
test.AssertNotError(t, err, "Failed to issue certificate")
cert, err := x509.ParseCertificate(result.DER)
test.AssertNotError(t, err, "Certificate failed to parse")
test.AssertByteEquals(t, cert.RawIssuer, ca.issuers.byAlg[x509.ECDSA].Cert.RawSubject)
test.AssertEquals(t, cert.SignatureAlgorithm, x509.ECDSAWithSHA384)
// With allowlist not containing arbitraryRegID, issuance should fall back to RSA issuer.
regIDMap = makeRegIDsMap([]int64{2002})
@ -704,7 +779,7 @@ func TestECDSAAllowList(t *testing.T) {
test.AssertNotError(t, err, "Failed to issue certificate")
cert, err = x509.ParseCertificate(result.DER)
test.AssertNotError(t, err, "Certificate failed to parse")
test.AssertByteEquals(t, cert.RawIssuer, ca.issuers.byAlg[x509.RSA].Cert.RawSubject)
test.AssertEquals(t, cert.SignatureAlgorithm, x509.SHA256WithRSA)
// With empty allowlist but ECDSAForAll enabled, issuance should come from ECDSA issuer.
ca, _ = issueCertificateSubTestSetup(t, nil)
@ -714,7 +789,7 @@ func TestECDSAAllowList(t *testing.T) {
test.AssertNotError(t, err, "Failed to issue certificate")
cert, err = x509.ParseCertificate(result.DER)
test.AssertNotError(t, err, "Certificate failed to parse")
test.AssertByteEquals(t, cert.RawIssuer, ca.issuers.byAlg[x509.ECDSA].Cert.RawSubject)
test.AssertEquals(t, cert.SignatureAlgorithm, x509.ECDSAWithSHA384)
}
func TestInvalidCSRs(t *testing.T) {

View File

@ -11,6 +11,7 @@ import (
capb "github.com/letsencrypt/boulder/ca/proto"
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/issuance"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/metrics"
"github.com/letsencrypt/boulder/test"
@ -54,41 +55,43 @@ func TestOCSP(t *testing.T) {
test.AssertNotError(t, err, "Failed to create CA")
ocspi := testCtx.ocsp
// Issue a certificate from the RSA issuer, then check OCSP comes from that same issuer.
rsaIssuerID := ca.issuers.byAlg[x509.RSA].NameID()
// Issue a certificate from an RSA issuer, request OCSP from the same issuer,
// and make sure it works.
rsaCertPB, err := ca.IssuePrecertificate(ctx, &capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID})
test.AssertNotError(t, err, "Failed to issue certificate")
rsaCert, err := x509.ParseCertificate(rsaCertPB.DER)
test.AssertNotError(t, err, "Failed to parse rsaCert")
rsaIssuerID := issuance.IssuerNameID(rsaCert)
rsaOCSPPB, err := ocspi.GenerateOCSP(ctx, &capb.GenerateOCSPRequest{
Serial: core.SerialToString(rsaCert.SerialNumber),
IssuerID: int64(rsaIssuerID),
Status: string(core.OCSPStatusGood),
})
test.AssertNotError(t, err, "Failed to generate OCSP")
rsaOCSP, err := ocsp.ParseResponse(rsaOCSPPB.Response, testCtx.boulderIssuers[1].Cert.Certificate)
rsaOCSP, err := ocsp.ParseResponse(rsaOCSPPB.Response, ca.issuers.byNameID[rsaIssuerID].Cert.Certificate)
test.AssertNotError(t, err, "Failed to parse / validate OCSP for rsaCert")
test.AssertEquals(t, rsaOCSP.Status, 0)
test.AssertEquals(t, rsaOCSP.RevocationReason, 0)
test.AssertEquals(t, rsaOCSP.SerialNumber.Cmp(rsaCert.SerialNumber), 0)
// Check that a different issuer cannot validate the OCSP response
_, err = ocsp.ParseResponse(rsaOCSPPB.Response, testCtx.boulderIssuers[0].Cert.Certificate)
_, err = ocsp.ParseResponse(rsaOCSPPB.Response, ca.issuers.byAlg[x509.ECDSA][0].Cert.Certificate)
test.AssertError(t, err, "Parsed / validated OCSP for rsaCert, but should not have")
// Issue a certificate from an ECDSA issuer, then check OCSP comes from that same issuer.
ecdsaIssuerID := ca.issuers.byAlg[x509.ECDSA].NameID()
// Issue a certificate from an ECDSA issuer, request OCSP from the same issuer,
// and make sure it works.
ecdsaCertPB, err := ca.IssuePrecertificate(ctx, &capb.IssueCertificateRequest{Csr: ECDSACSR, RegistrationID: arbitraryRegID})
test.AssertNotError(t, err, "Failed to issue certificate")
ecdsaCert, err := x509.ParseCertificate(ecdsaCertPB.DER)
test.AssertNotError(t, err, "Failed to parse ecdsaCert")
ecdsaIssuerID := issuance.IssuerNameID(ecdsaCert)
ecdsaOCSPPB, err := ocspi.GenerateOCSP(ctx, &capb.GenerateOCSPRequest{
Serial: core.SerialToString(ecdsaCert.SerialNumber),
IssuerID: int64(ecdsaIssuerID),
Status: string(core.OCSPStatusGood),
})
test.AssertNotError(t, err, "Failed to generate OCSP")
ecdsaOCSP, err := ocsp.ParseResponse(ecdsaOCSPPB.Response, testCtx.boulderIssuers[0].Cert.Certificate)
ecdsaOCSP, err := ocsp.ParseResponse(ecdsaOCSPPB.Response, ca.issuers.byNameID[ecdsaIssuerID].Cert.Certificate)
test.AssertNotError(t, err, "Failed to parse / validate OCSP for ecdsaCert")
test.AssertEquals(t, ecdsaOCSP.Status, 0)
test.AssertEquals(t, ecdsaOCSP.RevocationReason, 0)

View File

@ -83,18 +83,15 @@ func NewProfile(profileConfig ProfileConfig, skipLints []string) (*Profile, erro
// request doesn't match the signing profile an error is returned.
func (i *Issuer) requestValid(clk clock.Clock, prof *Profile, req *IssuanceRequest) error {
switch req.PublicKey.(type) {
case *rsa.PublicKey:
if !i.useForRSALeaves {
return errors.New("cannot sign RSA public keys")
}
case *ecdsa.PublicKey:
if !i.useForECDSALeaves {
return errors.New("cannot sign ECDSA public keys")
}
case *rsa.PublicKey, *ecdsa.PublicKey:
default:
return errors.New("unsupported public key type")
}
if len(req.precertDER) == 0 && !i.active {
return errors.New("inactive issuer cannot issue precert")
}
if len(req.SubjectKeyId) != 20 {
return errors.New("unexpected subject key ID length")
}

View File

@ -53,23 +53,23 @@ func TestRequestValid(t *testing.T) {
expectedError: "unsupported public key type",
},
{
name: "cannot sign rsa",
name: "inactive (rsa)",
issuer: &Issuer{},
profile: &Profile{},
request: &IssuanceRequest{PublicKey: &rsa.PublicKey{}},
expectedError: "cannot sign RSA public keys",
expectedError: "inactive issuer cannot issue precert",
},
{
name: "cannot sign ecdsa",
name: "inactive (ecdsa)",
issuer: &Issuer{},
profile: &Profile{},
request: &IssuanceRequest{PublicKey: &ecdsa.PublicKey{}},
expectedError: "cannot sign ECDSA public keys",
expectedError: "inactive issuer cannot issue precert",
},
{
name: "skid too short",
issuer: &Issuer{
useForECDSALeaves: true,
active: true,
},
profile: &Profile{},
request: &IssuanceRequest{
@ -81,7 +81,7 @@ func TestRequestValid(t *testing.T) {
{
name: "must staple not allowed",
issuer: &Issuer{
useForECDSALeaves: true,
active: true,
},
profile: &Profile{},
request: &IssuanceRequest{
@ -94,7 +94,7 @@ func TestRequestValid(t *testing.T) {
{
name: "ct poison not allowed",
issuer: &Issuer{
useForECDSALeaves: true,
active: true,
},
profile: &Profile{},
request: &IssuanceRequest{
@ -107,7 +107,7 @@ func TestRequestValid(t *testing.T) {
{
name: "sct list not allowed",
issuer: &Issuer{
useForECDSALeaves: true,
active: true,
},
profile: &Profile{},
request: &IssuanceRequest{
@ -120,7 +120,7 @@ func TestRequestValid(t *testing.T) {
{
name: "sct list and ct poison not allowed",
issuer: &Issuer{
useForECDSALeaves: true,
active: true,
},
profile: &Profile{
allowCTPoison: true,
@ -137,7 +137,7 @@ func TestRequestValid(t *testing.T) {
{
name: "common name not allowed",
issuer: &Issuer{
useForECDSALeaves: true,
active: true,
},
profile: &Profile{},
request: &IssuanceRequest{
@ -150,7 +150,7 @@ func TestRequestValid(t *testing.T) {
{
name: "negative validity",
issuer: &Issuer{
useForECDSALeaves: true,
active: true,
},
profile: &Profile{},
request: &IssuanceRequest{
@ -164,7 +164,7 @@ func TestRequestValid(t *testing.T) {
{
name: "validity larger than max",
issuer: &Issuer{
useForECDSALeaves: true,
active: true,
},
profile: &Profile{
maxValidity: time.Minute,
@ -180,7 +180,7 @@ func TestRequestValid(t *testing.T) {
{
name: "validity larger than max due to inclusivity",
issuer: &Issuer{
useForECDSALeaves: true,
active: true,
},
profile: &Profile{
maxValidity: time.Hour,
@ -196,7 +196,7 @@ func TestRequestValid(t *testing.T) {
{
name: "validity backdated more than max",
issuer: &Issuer{
useForECDSALeaves: true,
active: true,
},
profile: &Profile{
maxValidity: time.Hour * 2,
@ -213,7 +213,7 @@ func TestRequestValid(t *testing.T) {
{
name: "validity is forward dated",
issuer: &Issuer{
useForECDSALeaves: true,
active: true,
},
profile: &Profile{
maxValidity: time.Hour * 2,
@ -230,7 +230,7 @@ func TestRequestValid(t *testing.T) {
{
name: "serial too short",
issuer: &Issuer{
useForECDSALeaves: true,
active: true,
},
profile: &Profile{
maxValidity: time.Hour * 2,
@ -247,7 +247,7 @@ func TestRequestValid(t *testing.T) {
{
name: "serial too long",
issuer: &Issuer{
useForECDSALeaves: true,
active: true,
},
profile: &Profile{
maxValidity: time.Hour * 2,
@ -264,7 +264,7 @@ func TestRequestValid(t *testing.T) {
{
name: "good",
issuer: &Issuer{
useForECDSALeaves: true,
active: true,
},
profile: &Profile{
maxValidity: time.Hour * 2,

View File

@ -152,7 +152,28 @@ func LoadChain(certFiles []string) ([]*Certificate, error) {
// IssuerConfig describes the constraints on and URLs used by a single issuer.
type IssuerConfig struct {
UseForRSALeaves bool
// Active determines if the issuer can be used to sign precertificates. All
// issuers, regardless of this field, can be used to sign final certificates
// (for which an issuance token is presented), OCSP responses, and CRLs.
// All Active issuers of a given key type (RSA or ECDSA) are part of a pool
// and each precertificate will be issued randomly from a selected pool.
// The selection of which pool depends on the precertificate's key algorithm,
// the ECDSAForAll feature flag, and the ECDSAAllowListFilename config field.
Active bool
// UseForRSALeaves is a synonym for Active. Note that, despite the name,
// setting this field to true cannot add an issuer to a pool different than
// its key type. An active issuer will always be part of a pool based on its
// key type.
//
// Deprecated: use Active instead.
UseForRSALeaves bool
// UseForECDSALeaves is a synonym for Active. Note that, despite the name,
// setting this field to true cannot add an issuer to a pool different than
// its key type. An active issuer will always be part of a pool based on its
// key type.
//
// Deprecated: use Active instead.
UseForECDSALeaves bool
IssuerURL string `validate:"required,url"`
@ -188,9 +209,9 @@ type Issuer struct {
Signer crypto.Signer
Linter *linter.Linter
sigAlg x509.SignatureAlgorithm
useForRSALeaves bool
useForECDSALeaves bool
keyAlg x509.PublicKeyAlgorithm
sigAlg x509.SignatureAlgorithm
active bool
// Used to set the Authority Information Access caIssuers URL in issued
// certificates.
@ -208,11 +229,14 @@ type Issuer struct {
// newIssuer constructs a new Issuer from the in-memory certificate and signer.
// It exists as a helper for LoadIssuer to make testing simpler.
func newIssuer(config IssuerConfig, cert *Certificate, signer crypto.Signer, clk clock.Clock) (*Issuer, error) {
var keyAlg x509.PublicKeyAlgorithm
var sigAlg x509.SignatureAlgorithm
switch k := cert.PublicKey.(type) {
case *rsa.PublicKey:
keyAlg = x509.RSA
sigAlg = x509.SHA256WithRSA
case *ecdsa.PublicKey:
keyAlg = x509.ECDSA
switch k.Curve {
case elliptic.P256():
sigAlg = x509.ECDSAWithSHA256
@ -258,32 +282,31 @@ func newIssuer(config IssuerConfig, cert *Certificate, signer crypto.Signer, clk
}
i := &Issuer{
Cert: cert,
Signer: signer,
Linter: lintSigner,
sigAlg: sigAlg,
useForRSALeaves: config.UseForRSALeaves,
useForECDSALeaves: config.UseForECDSALeaves,
issuerURL: config.IssuerURL,
ocspURL: config.OCSPURL,
crlURLBase: config.CRLURLBase,
clk: clk,
Cert: cert,
Signer: signer,
Linter: lintSigner,
keyAlg: keyAlg,
sigAlg: sigAlg,
active: config.Active || config.UseForRSALeaves || config.UseForECDSALeaves,
issuerURL: config.IssuerURL,
ocspURL: config.OCSPURL,
crlURLBase: config.CRLURLBase,
clk: clk,
}
return i, nil
}
// Algs provides the list of leaf certificate public key algorithms for which
// this issuer is willing to issue. This is not necessarily the same as the
// public key algorithm or signature algorithm in this issuer's own cert.
func (i *Issuer) Algs() []x509.PublicKeyAlgorithm {
var algs []x509.PublicKeyAlgorithm
if i.useForRSALeaves {
algs = append(algs, x509.RSA)
}
if i.useForECDSALeaves {
algs = append(algs, x509.ECDSA)
}
return algs
// KeyType returns either x509.RSA or x509.ECDSA, depending on whether the
// issuer has an RSA or ECDSA keypair. This is useful for determining which
// issuance requests should be routed to this issuer.
func (i *Issuer) KeyType() x509.PublicKeyAlgorithm {
return i.keyAlg
}
// IsActive is true if the issuer is willing to issue precertificates, and false
// if the issuer is only willing to issue final certificates, OCSP, and CRLs.
func (i *Issuer) IsActive() bool {
return i.active
}
// Name provides the Common Name specified in the issuer's certificate.

View File

@ -35,11 +35,10 @@ func defaultProfileConfig() ProfileConfig {
func defaultIssuerConfig() IssuerConfig {
return IssuerConfig{
UseForECDSALeaves: true,
UseForRSALeaves: true,
IssuerURL: "http://issuer-url.example.org",
OCSPURL: "http://ocsp-url.example.org",
CRLURLBase: "http://crl-url.example.org/",
Active: true,
IssuerURL: "http://issuer-url.example.org",
OCSPURL: "http://ocsp-url.example.org",
CRLURLBase: "http://crl-url.example.org/",
}
}

View File

@ -64,8 +64,7 @@
},
"issuers": [
{
"useForRSALeaves": false,
"useForECDSALeaves": true,
"active": true,
"issuerURL": "http://127.0.0.1:4502/int ecdsa a",
"ocspURL": "http://127.0.0.1:4002/",
"crlURLBase": "http://127.0.0.1:4501/ecdsa-a/",
@ -76,8 +75,29 @@
}
},
{
"useForRSALeaves": true,
"useForECDSALeaves": true,
"active": true,
"issuerURL": "http://127.0.0.1:4502/int ecdsa b",
"ocspURL": "http://127.0.0.1:4002/",
"crlURLBase": "http://127.0.0.1:4501/ecdsa-b/",
"location": {
"configFile": "/hierarchy/int-ecdsa-b.pkcs11.json",
"certFile": "/hierarchy/int-ecdsa-b.cert.pem",
"numSessions": 2
}
},
{
"active": false,
"issuerURL": "http://127.0.0.1:4502/int ecdsa c",
"ocspURL": "http://127.0.0.1:4002/",
"crlURLBase": "http://127.0.0.1:4501/ecdsa-c/",
"location": {
"configFile": "/hierarchy/int-ecdsa-c.pkcs11.json",
"certFile": "/hierarchy/int-ecdsa-c.cert.pem",
"numSessions": 2
}
},
{
"active": true,
"issuerURL": "http://127.0.0.1:4502/int rsa a",
"ocspURL": "http://127.0.0.1:4002/",
"crlURLBase": "http://127.0.0.1:4501/rsa-a/",
@ -88,8 +108,7 @@
}
},
{
"useForRSALeaves": false,
"useForECDSALeaves": false,
"active": true,
"issuerURL": "http://127.0.0.1:4502/int rsa b",
"ocspURL": "http://127.0.0.1:4002/",
"crlURLBase": "http://127.0.0.1:4501/rsa-b/",
@ -98,6 +117,17 @@
"certFile": "/hierarchy/int-rsa-b.cert.pem",
"numSessions": 2
}
},
{
"active": false,
"issuerURL": "http://127.0.0.1:4502/int rsa c",
"ocspURL": "http://127.0.0.1:4002/",
"crlURLBase": "http://127.0.0.1:4501/rsa-c/",
"location": {
"configFile": "/hierarchy/int-rsa-c.pkcs11.json",
"certFile": "/hierarchy/int-rsa-c.cert.pem",
"numSessions": 2
}
}
],
"ignoredLints": [

View File

@ -23,7 +23,10 @@
"issuerCerts": [
"/hierarchy/int-rsa-a.cert.pem",
"/hierarchy/int-rsa-b.cert.pem",
"/hierarchy/int-ecdsa-a.cert.pem"
"/hierarchy/int-rsa-c.cert.pem",
"/hierarchy/int-ecdsa-a.cert.pem",
"/hierarchy/int-ecdsa-b.cert.pem",
"/hierarchy/int-ecdsa-c.cert.pem"
],
"s3Endpoint": "http://localhost:4501",
"s3Bucket": "lets-encrypt-crls",

View File

@ -38,7 +38,10 @@
"issuerCerts": [
"/hierarchy/int-rsa-a.cert.pem",
"/hierarchy/int-rsa-b.cert.pem",
"/hierarchy/int-ecdsa-a.cert.pem"
"/hierarchy/int-rsa-c.cert.pem",
"/hierarchy/int-ecdsa-a.cert.pem",
"/hierarchy/int-ecdsa-b.cert.pem",
"/hierarchy/int-ecdsa-c.cert.pem"
],
"numShards": 10,
"shardWidth": "240h",

View File

@ -46,7 +46,10 @@
"issuerCerts": [
"/hierarchy/int-rsa-a.cert.pem",
"/hierarchy/int-rsa-b.cert.pem",
"/hierarchy/int-ecdsa-a.cert.pem"
"/hierarchy/int-rsa-c.cert.pem",
"/hierarchy/int-ecdsa-a.cert.pem",
"/hierarchy/int-ecdsa-b.cert.pem",
"/hierarchy/int-ecdsa-c.cert.pem"
],
"liveSigningPeriod": "60h",
"timeout": "4.9s",

View File

@ -16,7 +16,10 @@
"issuerCerts": [
"/hierarchy/int-rsa-a.cert.pem",
"/hierarchy/int-rsa-b.cert.pem",
"/hierarchy/int-ecdsa-a.cert.pem"
"/hierarchy/int-rsa-c.cert.pem",
"/hierarchy/int-ecdsa-a.cert.pem",
"/hierarchy/int-ecdsa-b.cert.pem",
"/hierarchy/int-ecdsa-c.cert.pem"
],
"tls": {
"caCertFile": "test/grpc-creds/minica.pem",

View File

@ -1,17 +1,17 @@
import base64
import os
import urllib
import time
import re
import random
import json
import requests
import socket
import tempfile
import shutil
import atexit
import base64
import errno
import glob
import os
import random
import re
import requests
import shutil
import socket
import subprocess
import tempfile
import time
import urllib
import challtestsrv
@ -96,7 +96,21 @@ def ocsp_verify(cert_file, issuer_file, ocsp_response):
raise(Exception("OCSP verify failure"))
return output
def verify_ocsp(cert_file, issuer_file, url, status="revoked", reason=None):
def verify_ocsp(cert_file, issuer_glob, url, status="revoked", reason=None):
# Try to verify the OCSP response using every issuer identified by the glob.
# If one works, great. If none work, re-raise the exception produced by the
# last attempt
lastException = None
for issuer_file in glob.glob(issuer_glob):
try:
output = try_verify_ocsp(cert_file, issuer_file, url, status, reason)
return output
except Exception as e:
lastException = e
continue
raise(lastException)
def try_verify_ocsp(cert_file, issuer_file, url, status="revoked", reason=None):
ocsp_request = make_ocsp_req(cert_file, issuer_file)
responses = fetch_ocsp(ocsp_request, url)

View File

@ -7,6 +7,7 @@ import (
"crypto/elliptic"
"crypto/rand"
"os"
"strings"
"testing"
"github.com/letsencrypt/boulder/test"
@ -36,10 +37,10 @@ func TestSubordinateCAChainsServedByWFE(t *testing.T) {
seenECDSACrossSignedIntermediate := false
for _, certUrl := range chains.certs {
for _, cert := range certUrl {
if cert.Subject.String() == "CN=int ecdsa a,O=good guys,C=US" && cert.Issuer.String() == "CN=root ecdsa,O=good guys,C=US" {
if strings.Contains(cert.Subject.CommonName, "int ecdsa") && cert.Issuer.CommonName == "root ecdsa" {
seenECDSAIntermediate = true
}
if cert.Subject.String() == "CN=int ecdsa a,O=good guys,C=US" && cert.Issuer.String() == "CN=root rsa,O=good guys,C=US" {
if strings.Contains(cert.Subject.CommonName, "int ecdsa") && cert.Issuer.CommonName == "root rsa" {
seenECDSACrossSignedIntermediate = true
}
}

View File

@ -679,7 +679,7 @@ def test_revoke_by_account_unspecified():
reset_akamai_purges()
client.revoke(josepy.ComparableX509(cert), 0)
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-a.cert.pem", "http://localhost:4002", "revoked")
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-*.cert.pem", "http://localhost:4002", "revoked")
verify_akamai_purge()
def test_revoke_by_account_with_reason():
@ -693,7 +693,7 @@ def test_revoke_by_account_with_reason():
# Requesting revocation for keyCompromise should work, but not block the
# key.
client.revoke(josepy.ComparableX509(cert), 1)
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-a.cert.pem", "http://localhost:4002", "revoked", "keyCompromise")
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-*.cert.pem", "http://localhost:4002", "revoked", "keyCompromise")
verify_akamai_purge()
@ -712,7 +712,7 @@ def test_revoke_by_authz():
# Even though we requested reason 1 ("keyCompromise"), the result should be
# 5 ("cessationOfOperation") due to the authorization method.
client.revoke(josepy.ComparableX509(cert), 1)
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-a.cert.pem", "http://localhost:4002", "revoked", "cessationOfOperation")
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-*.cert.pem", "http://localhost:4002", "revoked", "cessationOfOperation")
verify_akamai_purge()
@ -755,7 +755,7 @@ def test_revoke_by_privkey():
# Even though we requested reason 0 ("unspecified"), the result should be
# 1 ("keyCompromise") due to the authorization method.
revoke_client.revoke(josepy.ComparableX509(cert), 0)
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-a.cert.pem", "http://localhost:4002", "revoked", "keyCompromise")
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-*.cert.pem", "http://localhost:4002", "revoked", "keyCompromise")
verify_akamai_purge()
@ -797,7 +797,7 @@ def test_double_revocation():
# First revoke for any reason.
sub_client.revoke(josepy.ComparableX509(cert), 0)
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-a.cert.pem", "http://localhost:4002", "revoked")
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-*.cert.pem", "http://localhost:4002", "revoked")
verify_akamai_purge()
# Re-revocation for anything other than keyCompromise should fail.
@ -812,7 +812,7 @@ def test_double_revocation():
# via the cert key to demonstrate said compromise.
reset_akamai_purges()
cert_client.revoke(josepy.ComparableX509(cert), 1)
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-a.cert.pem", "http://localhost:4002", "revoked", "keyCompromise")
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-*.cert.pem", "http://localhost:4002", "revoked", "keyCompromise")
verify_akamai_purge()
# A subsequent attempt should fail, because the cert is already revoked
@ -1229,7 +1229,7 @@ def test_auth_deactivation_v2():
def test_ocsp():
cert_file = temppath('test_ocsp.pem')
chisel2.auth_and_issue([random_domain()], cert_output=cert_file.name)
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-a.cert.pem", "http://localhost:4002", "good")
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-*.cert.pem", "http://localhost:4002", "good")
def test_ct_submission():
hostname = random_domain()
@ -1301,15 +1301,22 @@ ocsp_exp_unauth_setup_data = {}
def ocsp_exp_unauth_setup():
client = chisel2.make_client(None)
cert_file = temppath('ocsp_exp_unauth_setup.pem')
order = chisel2.auth_and_issue([random_domain()], client=client, cert_output=cert_file.name)
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, order.fullchain_pem)
chisel2.auth_and_issue([random_domain()], client=client, cert_output=cert_file.name)
# Since our servers are pretending to be in the past, but the openssl cli
# isn't, we'll get an expired OCSP response. Just check that it exists;
# don't do the full verification (which would fail).
check_ocsp_basic_oid(cert_file.name, "/hierarchy/int-rsa-a.cert.pem", "http://localhost:4002")
global ocsp_exp_unauth_setup_data
ocsp_exp_unauth_setup_data['cert_file'] = cert_file.name
lastException = None
for issuer_file in glob.glob("/hierarchy/int-rsa-*.cert.pem"):
try:
check_ocsp_basic_oid(cert_file.name, issuer_file, "http://localhost:4002")
global ocsp_exp_unauth_setup_data
ocsp_exp_unauth_setup_data['cert_file'] = cert_file.name
return
except Exception as e:
lastException = e
continue
raise(lastException)
def test_ocsp_exp_unauth():
tries = 0
@ -1319,7 +1326,7 @@ def test_ocsp_exp_unauth():
last_error = ""
while tries < 5:
try:
verify_ocsp(cert_file, "/hierarchy/int-rsa-a.cert.pem", "http://localhost:4002", "XXX")
verify_ocsp(cert_file, "/hierarchy/int-rsa-*.cert.pem", "http://localhost:4002", "XXX")
raise(Exception("Unexpected return from verify_ocsp"))
except subprocess.CalledProcessError as cpe:
last_error = cpe.output
@ -1590,7 +1597,7 @@ def test_admin_revoker_cert():
"-reason", "keyCompromise"])
# Wait for OCSP response to indicate revocation took place
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-a.cert.pem", "http://localhost:4002", "revoked", "keyCompromise")
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-*.cert.pem", "http://localhost:4002", "revoked", "keyCompromise")
verify_akamai_purge()
def test_admin_revoker_batched():
@ -1615,7 +1622,7 @@ def test_admin_revoker_batched():
"-parallelism", "2"])
for cert_file in cert_files:
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-a.cert.pem", "http://localhost:4002", "revoked", "unspecified")
verify_ocsp(cert_file.name, "/hierarchy/int-rsa-*.cert.pem", "http://localhost:4002", "revoked", "unspecified")
def test_sct_embedding():
order = chisel2.auth_and_issue([random_domain()])
@ -1661,9 +1668,9 @@ def test_auth_deactivation():
if resp.body.status is not messages.STATUS_DEACTIVATED:
raise Exception("unexpected authorization status")
def get_ocsp_response_and_reason(cert_file, issuer_file, url):
def get_ocsp_response_and_reason(cert_file, issuer_glob, url):
"""Returns the ocsp response output and revocation reason."""
output = verify_ocsp(cert_file, issuer_file, url, None)
output = verify_ocsp(cert_file, issuer_glob, url, None)
m = re.search('Reason: (\w+)', output)
reason = m.group(1) if m is not None else ""
return output, reason
@ -1687,7 +1694,7 @@ def ocsp_resigning_setup():
client.revoke(josepy.ComparableX509(cert), 5)
ocsp_response, reason = get_ocsp_response_and_reason(
cert_file.name, "/hierarchy/int-rsa-a.cert.pem", "http://localhost:4002")
cert_file.name, "/hierarchy/int-rsa-*.cert.pem", "http://localhost:4002")
global ocsp_resigning_setup_data
ocsp_resigning_setup_data = {
'cert_file': cert_file.name,
@ -1703,7 +1710,7 @@ def test_ocsp_resigning():
tries = 0
while tries < 5:
resp, reason = get_ocsp_response_and_reason(
ocsp_resigning_setup_data['cert_file'], "/hierarchy/int-rsa-a.cert.pem", "http://localhost:4002")
ocsp_resigning_setup_data['cert_file'], "/hierarchy/int-rsa-*.cert.pem", "http://localhost:4002")
if resp != ocsp_resigning_setup_data['response']:
break
tries += 1