Remove OCSP and MustStaple support from issuance (#8181)
Remove the ability for the issuance package to include the AIA OCSP URI and the Must Staple (more properly known as the tlsRequest) extension in certificates. Deprecate the "OmitOCSP" and "AllowMustStaple" profile config keys, as they no longer have any effect. Similarly deprecate the "OCSPURL" issuer config key, as it is no longer included in certificates. Update the tests to always include to CRLDP extension instead, and remove some OCSP- or Stapling-specific test cases. Fixes https://github.com/letsencrypt/boulder/issues/8179
This commit is contained in:
parent
caa29b2937
commit
c9e2f98b5d
19
ca/ca.go
19
ca/ca.go
|
@ -549,16 +549,15 @@ func (ca *certificateAuthorityImpl) issuePrecertificateInner(ctx context.Context
|
|||
}
|
||||
|
||||
req := &issuance.IssuanceRequest{
|
||||
PublicKey: issuance.MarshalablePublicKey{PublicKey: csr.PublicKey},
|
||||
SubjectKeyId: subjectKeyId,
|
||||
Serial: serialBigInt.Bytes(),
|
||||
DNSNames: dnsNames,
|
||||
IPAddresses: ipAddresses,
|
||||
CommonName: csrlib.NamesFromCSR(csr).CN,
|
||||
IncludeCTPoison: true,
|
||||
IncludeMustStaple: issuance.ContainsMustStaple(csr.Extensions),
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
PublicKey: issuance.MarshalablePublicKey{PublicKey: csr.PublicKey},
|
||||
SubjectKeyId: subjectKeyId,
|
||||
Serial: serialBigInt.Bytes(),
|
||||
DNSNames: dnsNames,
|
||||
IPAddresses: ipAddresses,
|
||||
CommonName: csrlib.NamesFromCSR(csr).CN,
|
||||
IncludeCTPoison: true,
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
}
|
||||
|
||||
lintCertBytes, issuanceToken, err := issuer.Prepare(certProfile.profile, req)
|
||||
|
|
|
@ -154,20 +154,20 @@ func setup(t *testing.T) *testCtx {
|
|||
|
||||
certProfiles := make(map[string]*issuance.ProfileConfig, 0)
|
||||
certProfiles["legacy"] = &issuance.ProfileConfig{
|
||||
AllowMustStaple: true,
|
||||
MaxValidityPeriod: config.Duration{Duration: time.Hour * 24 * 90},
|
||||
MaxValidityBackdate: config.Duration{Duration: time.Hour},
|
||||
IgnoredLints: []string{"w_subject_common_name_included"},
|
||||
IncludeCRLDistributionPoints: true,
|
||||
MaxValidityPeriod: config.Duration{Duration: time.Hour * 24 * 90},
|
||||
MaxValidityBackdate: config.Duration{Duration: time.Hour},
|
||||
IgnoredLints: []string{"w_subject_common_name_included"},
|
||||
}
|
||||
certProfiles["modern"] = &issuance.ProfileConfig{
|
||||
AllowMustStaple: true,
|
||||
OmitCommonName: true,
|
||||
OmitKeyEncipherment: true,
|
||||
OmitClientAuth: true,
|
||||
OmitSKID: true,
|
||||
MaxValidityPeriod: config.Duration{Duration: time.Hour * 24 * 6},
|
||||
MaxValidityBackdate: config.Duration{Duration: time.Hour},
|
||||
IgnoredLints: []string{"w_ext_subject_key_identifier_missing_sub_cert"},
|
||||
OmitCommonName: true,
|
||||
OmitKeyEncipherment: true,
|
||||
OmitClientAuth: true,
|
||||
OmitSKID: true,
|
||||
IncludeCRLDistributionPoints: true,
|
||||
MaxValidityPeriod: config.Duration{Duration: time.Hour * 24 * 6},
|
||||
MaxValidityBackdate: config.Duration{Duration: time.Hour},
|
||||
IgnoredLints: []string{"w_ext_subject_key_identifier_missing_sub_cert"},
|
||||
}
|
||||
test.AssertEquals(t, len(certProfiles), 2)
|
||||
|
||||
|
@ -178,6 +178,7 @@ func setup(t *testing.T) *testCtx {
|
|||
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),
|
||||
CRLShards: 10,
|
||||
Location: issuance.IssuerLoc{
|
||||
File: fmt.Sprintf("../test/hierarchy/%s.key.pem", name),
|
||||
CertFile: fmt.Sprintf("../test/hierarchy/%s.cert.pem", name),
|
||||
|
@ -314,7 +315,6 @@ func TestIssuePrecertificate(t *testing.T) {
|
|||
{"IssuePrecertificate", CNandSANCSR, issueCertificateSubTestIssuePrecertificate},
|
||||
{"ProfileSelectionRSA", CNandSANCSR, issueCertificateSubTestProfileSelectionRSA},
|
||||
{"ProfileSelectionECDSA", ECDSACSR, issueCertificateSubTestProfileSelectionECDSA},
|
||||
{"MustStaple", MustStapleCSR, issueCertificateSubTestMustStaple},
|
||||
{"UnknownExtension", UnsupportedExtensionCSR, issueCertificateSubTestUnknownExtension},
|
||||
{"CTPoisonExtension", CTPoisonExtensionCSR, issueCertificateSubTestCTPoisonExtension},
|
||||
{"CTPoisonExtensionEmpty", CTPoisonExtensionEmptyCSR, issueCertificateSubTestCTPoisonExtension},
|
||||
|
@ -492,6 +492,7 @@ func TestUnpredictableIssuance(t *testing.T) {
|
|||
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),
|
||||
CRLShards: 10,
|
||||
Location: issuance.IssuerLoc{
|
||||
File: fmt.Sprintf("../test/hierarchy/%s.key.pem", name),
|
||||
CertFile: fmt.Sprintf("../test/hierarchy/%s.cert.pem", name),
|
||||
|
@ -573,6 +574,13 @@ func TestMakeCertificateProfilesMap(t *testing.T) {
|
|||
profileConfigs: map[string]*issuance.ProfileConfig{
|
||||
"empty": {},
|
||||
},
|
||||
expectedErrSubstr: "at least one revocation mechanism must be included",
|
||||
},
|
||||
{
|
||||
name: "minimal profile config",
|
||||
profileConfigs: map[string]*issuance.ProfileConfig{
|
||||
"empty": {IncludeCRLDistributionPoints: true},
|
||||
},
|
||||
expectedProfiles: []string{"empty"},
|
||||
},
|
||||
{
|
||||
|
@ -733,30 +741,12 @@ func issueCertificateSubTestProfileSelectionECDSA(t *testing.T, i *TestCertifica
|
|||
test.AssertEquals(t, i.cert.KeyUsage, expectedKeyUsage)
|
||||
}
|
||||
|
||||
func countMustStaple(t *testing.T, cert *x509.Certificate) (count int) {
|
||||
oidTLSFeature := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}
|
||||
mustStapleFeatureValue := []byte{0x30, 0x03, 0x02, 0x01, 0x05}
|
||||
for _, ext := range cert.Extensions {
|
||||
if ext.Id.Equal(oidTLSFeature) {
|
||||
test.Assert(t, !ext.Critical, "Extension was marked critical")
|
||||
test.AssertByteEquals(t, ext.Value, mustStapleFeatureValue)
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func issueCertificateSubTestMustStaple(t *testing.T, i *TestCertificateIssuance) {
|
||||
test.AssertMetricWithLabelsEquals(t, i.ca.metrics.signatureCount, prometheus.Labels{"purpose": "precertificate"}, 1)
|
||||
test.AssertEquals(t, countMustStaple(t, i.cert), 1)
|
||||
}
|
||||
|
||||
func issueCertificateSubTestUnknownExtension(t *testing.T, i *TestCertificateIssuance) {
|
||||
test.AssertMetricWithLabelsEquals(t, i.ca.metrics.signatureCount, prometheus.Labels{"purpose": "precertificate"}, 1)
|
||||
|
||||
// NOTE: The hard-coded value here will have to change over time as Boulder
|
||||
// adds or removes (unrequested/default) extensions in certificates.
|
||||
expectedExtensionCount := 9
|
||||
expectedExtensionCount := 10
|
||||
test.AssertEquals(t, len(i.cert.Extensions), expectedExtensionCount)
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,9 @@ import (
|
|||
type ProfileConfig struct {
|
||||
// AllowMustStaple, when false, causes all IssuanceRequests which specify the
|
||||
// OCSP Must Staple extension to be rejected.
|
||||
//
|
||||
// Deprecated: This has no effect, Must Staple is always omitted.
|
||||
// TODO(#8177): Remove this.
|
||||
AllowMustStaple bool
|
||||
|
||||
// OmitCommonName causes the CN field to be excluded from the resulting
|
||||
|
@ -50,6 +53,9 @@ type ProfileConfig struct {
|
|||
// Information Access extension. This cannot be true unless
|
||||
// IncludeCRLDistributionPoints is also true, to ensure that every
|
||||
// certificate has at least one revocation mechanism included.
|
||||
//
|
||||
// Deprecated: This has no effect; OCSP is always omitted.
|
||||
// TODO(#8177): Remove this.
|
||||
OmitOCSP bool
|
||||
// IncludeCRLDistributionPoints causes the CRLDistributionPoints extension to
|
||||
// be added to all certificates issued by this profile.
|
||||
|
@ -73,12 +79,10 @@ type PolicyConfig struct {
|
|||
|
||||
// Profile is the validated structure created by reading in ProfileConfigs and IssuerConfigs
|
||||
type Profile struct {
|
||||
allowMustStaple bool
|
||||
omitCommonName bool
|
||||
omitKeyEncipherment bool
|
||||
omitClientAuth bool
|
||||
omitSKID bool
|
||||
omitOCSP bool
|
||||
|
||||
includeCRLDistributionPoints bool
|
||||
|
||||
|
@ -105,7 +109,8 @@ func NewProfile(profileConfig *ProfileConfig) (*Profile, error) {
|
|||
// Although the Baseline Requirements say that revocation information may be
|
||||
// omitted entirely *for short-lived certs*, the Microsoft root program still
|
||||
// requires that at least one revocation mechanism be included in all certs.
|
||||
if profileConfig.OmitOCSP && !profileConfig.IncludeCRLDistributionPoints {
|
||||
// TODO(#7673): Remove this restriction.
|
||||
if !profileConfig.IncludeCRLDistributionPoints {
|
||||
return nil, fmt.Errorf("at least one revocation mechanism must be included")
|
||||
}
|
||||
|
||||
|
@ -118,12 +123,10 @@ func NewProfile(profileConfig *ProfileConfig) (*Profile, error) {
|
|||
}
|
||||
|
||||
sp := &Profile{
|
||||
allowMustStaple: profileConfig.AllowMustStaple,
|
||||
omitCommonName: profileConfig.OmitCommonName,
|
||||
omitKeyEncipherment: profileConfig.OmitKeyEncipherment,
|
||||
omitClientAuth: profileConfig.OmitClientAuth,
|
||||
omitSKID: profileConfig.OmitSKID,
|
||||
omitOCSP: profileConfig.OmitOCSP,
|
||||
includeCRLDistributionPoints: profileConfig.IncludeCRLDistributionPoints,
|
||||
maxBackdate: profileConfig.MaxValidityBackdate.Duration,
|
||||
maxValidity: profileConfig.MaxValidityPeriod.Duration,
|
||||
|
@ -163,10 +166,6 @@ func (i *Issuer) requestValid(clk clock.Clock, prof *Profile, req *IssuanceReque
|
|||
return errors.New("unexpected subject key ID length")
|
||||
}
|
||||
|
||||
if !prof.allowMustStaple && req.IncludeMustStaple {
|
||||
return errors.New("must-staple extension cannot be included")
|
||||
}
|
||||
|
||||
if req.IncludeCTPoison && req.sctList != nil {
|
||||
return errors.New("cannot include both ct poison and sct list extensions")
|
||||
}
|
||||
|
@ -253,16 +252,6 @@ func generateSCTListExt(scts []ct.SignedCertificateTimestamp) (pkix.Extension, e
|
|||
}, nil
|
||||
}
|
||||
|
||||
var mustStapleExt = pkix.Extension{
|
||||
// RFC 7633: id-pe-tlsfeature OBJECT IDENTIFIER ::= { id-pe 24 }
|
||||
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24},
|
||||
// ASN.1 encoding of:
|
||||
// SEQUENCE
|
||||
// INTEGER 5
|
||||
// where "5" is the status_request feature (RFC 6066)
|
||||
Value: []byte{0x30, 0x03, 0x02, 0x01, 0x05},
|
||||
}
|
||||
|
||||
// MarshalablePublicKey is a wrapper for crypto.PublicKey with a custom JSON
|
||||
// marshaller that encodes the public key as a DER-encoded SubjectPublicKeyInfo.
|
||||
type MarshalablePublicKey struct {
|
||||
|
@ -301,8 +290,7 @@ type IssuanceRequest struct {
|
|||
DNSNames []string
|
||||
IPAddresses []net.IP
|
||||
|
||||
IncludeMustStaple bool
|
||||
IncludeCTPoison bool
|
||||
IncludeCTPoison bool
|
||||
|
||||
// sctList is a list of SCTs to include in a final certificate.
|
||||
// If it is non-empty, PrecertDER must also be non-empty.
|
||||
|
@ -393,10 +381,6 @@ func (i *Issuer) Prepare(prof *Profile, req *IssuanceRequest) ([]byte, *issuance
|
|||
return nil, nil, errors.New("invalid request contains neither sctList nor precertDER")
|
||||
}
|
||||
|
||||
if !prof.omitOCSP {
|
||||
template.OCSPServer = []string{i.ocspURL}
|
||||
}
|
||||
|
||||
// If explicit CRL sharding is enabled, pick a shard based on the serial number
|
||||
// modulus the number of shards. This gives us random distribution that is
|
||||
// nonetheless consistent between precert and cert.
|
||||
|
@ -410,10 +394,6 @@ func (i *Issuer) Prepare(prof *Profile, req *IssuanceRequest) ([]byte, *issuance
|
|||
template.CRLDistributionPoints = []string{url}
|
||||
}
|
||||
|
||||
if req.IncludeMustStaple {
|
||||
template.ExtraExtensions = append(template.ExtraExtensions, mustStapleExt)
|
||||
}
|
||||
|
||||
// check that the tbsCertificate is properly formed by signing it
|
||||
// with a throwaway key and then linting it using zlint
|
||||
lintCertBytes, err := i.Linter.Check(template, req.PublicKey.PublicKey, prof.lints)
|
||||
|
@ -454,18 +434,6 @@ func (i *Issuer) Issue(token *issuanceToken) ([]byte, error) {
|
|||
return x509.CreateCertificate(rand.Reader, template, i.Cert.Certificate, token.pubKey.PublicKey, i.Signer)
|
||||
}
|
||||
|
||||
// ContainsMustStaple returns true if the provided set of extensions includes
|
||||
// an entry whose OID and value both match the expected values for the OCSP
|
||||
// Must-Staple (a.k.a. id-pe-tlsFeature) extension.
|
||||
func ContainsMustStaple(extensions []pkix.Extension) bool {
|
||||
for _, ext := range extensions {
|
||||
if ext.Id.Equal(mustStapleExt.Id) && bytes.Equal(ext.Value, mustStapleExt.Value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// containsCTPoison returns true if the provided set of extensions includes
|
||||
// an entry whose OID and value both match the expected values for the CT
|
||||
// Poison extension.
|
||||
|
@ -486,16 +454,15 @@ func RequestFromPrecert(precert *x509.Certificate, scts []ct.SignedCertificateTi
|
|||
return nil, errors.New("provided certificate doesn't contain the CT poison extension")
|
||||
}
|
||||
return &IssuanceRequest{
|
||||
PublicKey: MarshalablePublicKey{precert.PublicKey},
|
||||
SubjectKeyId: precert.SubjectKeyId,
|
||||
Serial: precert.SerialNumber.Bytes(),
|
||||
NotBefore: precert.NotBefore,
|
||||
NotAfter: precert.NotAfter,
|
||||
CommonName: precert.Subject.CommonName,
|
||||
DNSNames: precert.DNSNames,
|
||||
IPAddresses: precert.IPAddresses,
|
||||
IncludeMustStaple: ContainsMustStaple(precert.Extensions),
|
||||
sctList: scts,
|
||||
precertDER: precert.Raw,
|
||||
PublicKey: MarshalablePublicKey{precert.PublicKey},
|
||||
SubjectKeyId: precert.SubjectKeyId,
|
||||
Serial: precert.SerialNumber.Bytes(),
|
||||
NotBefore: precert.NotBefore,
|
||||
NotAfter: precert.NotAfter,
|
||||
CommonName: precert.Subject.CommonName,
|
||||
DNSNames: precert.DNSNames,
|
||||
IPAddresses: precert.IPAddresses,
|
||||
sctList: scts,
|
||||
precertDER: precert.Raw,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -127,19 +127,6 @@ func TestRequestValid(t *testing.T) {
|
|||
},
|
||||
expectedError: "unexpected subject key ID length",
|
||||
},
|
||||
{
|
||||
name: "must staple not allowed",
|
||||
issuer: &Issuer{
|
||||
active: true,
|
||||
},
|
||||
profile: &Profile{},
|
||||
request: &IssuanceRequest{
|
||||
PublicKey: MarshalablePublicKey{&ecdsa.PublicKey{}},
|
||||
SubjectKeyId: goodSKID,
|
||||
IncludeMustStaple: true,
|
||||
},
|
||||
expectedError: "must-staple extension cannot be included",
|
||||
},
|
||||
{
|
||||
name: "both sct list and ct poison provided",
|
||||
issuer: &Issuer{
|
||||
|
@ -322,7 +309,6 @@ func TestRequestValid(t *testing.T) {
|
|||
|
||||
func TestGenerateTemplate(t *testing.T) {
|
||||
issuer := &Issuer{
|
||||
ocspURL: "http://ocsp",
|
||||
issuerURL: "http://issuer",
|
||||
crlURLBase: "http://crl/",
|
||||
sigAlg: x509.SHA256WithRSA,
|
||||
|
@ -401,10 +387,10 @@ func TestIssue(t *testing.T) {
|
|||
test.AssertDeepEquals(t, cert.IPAddresses, []net.IP{net.ParseIP("128.101.101.101").To4(), net.ParseIP("3fff:aaa:a:c0ff:ee:a:bad:deed")})
|
||||
test.AssertByteEquals(t, cert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
test.AssertDeepEquals(t, cert.PublicKey, pk.Public())
|
||||
test.AssertEquals(t, len(cert.Extensions), 9) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies, Poison
|
||||
test.AssertEquals(t, len(cert.Extensions), 10) // Constraints, KU, EKU, SKID, AKID, AIA, CRLDP, SAN, Policies, Poison
|
||||
test.AssertEquals(t, cert.KeyUsage, tc.ku)
|
||||
if len(cert.CRLDistributionPoints) > 0 {
|
||||
t.Errorf("want CRLDistributionPoints=[], got %v", cert.CRLDistributionPoints)
|
||||
if len(cert.CRLDistributionPoints) != 1 || !strings.HasPrefix(cert.CRLDistributionPoints[0], "http://crl-url.example.org/") {
|
||||
t.Errorf("want CRLDistributionPoints=[http://crl-url.example.org/x.crl], got %v", cert.CRLDistributionPoints)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -656,8 +642,8 @@ func TestIssueCTPoison(t *testing.T) {
|
|||
test.AssertNotError(t, err, "signature validation failed")
|
||||
test.AssertByteEquals(t, cert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
test.AssertDeepEquals(t, cert.PublicKey, pk.Public())
|
||||
test.AssertEquals(t, len(cert.Extensions), 9) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies, CT Poison
|
||||
test.AssertDeepEquals(t, cert.Extensions[8], ctPoisonExt)
|
||||
test.AssertEquals(t, len(cert.Extensions), 10) // Constraints, KU, EKU, SKID, AKID, AIA, CRLDP, SAN, Policies, Poison
|
||||
test.AssertDeepEquals(t, cert.Extensions[9], ctPoisonExt)
|
||||
}
|
||||
|
||||
func mustDecodeB64(b string) []byte {
|
||||
|
@ -728,8 +714,8 @@ func TestIssueSCTList(t *testing.T) {
|
|||
test.AssertNotError(t, err, "signature validation failed")
|
||||
test.AssertByteEquals(t, finalCert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
test.AssertDeepEquals(t, finalCert.PublicKey, pk.Public())
|
||||
test.AssertEquals(t, len(finalCert.Extensions), 9) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies, SCT list
|
||||
test.AssertDeepEquals(t, finalCert.Extensions[8], pkix.Extension{
|
||||
test.AssertEquals(t, len(finalCert.Extensions), 10) // Constraints, KU, EKU, SKID, AKID, AIA, CRLDP, SAN, Policies, Poison
|
||||
test.AssertDeepEquals(t, finalCert.Extensions[9], pkix.Extension{
|
||||
Id: sctListOID,
|
||||
Value: []byte{
|
||||
4, 100, 0, 98, 0, 47, 0, 56, 152, 140, 148, 208, 53, 152, 195, 147, 45,
|
||||
|
@ -742,37 +728,6 @@ func TestIssueSCTList(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestIssueMustStaple(t *testing.T) {
|
||||
fc := clock.NewFake()
|
||||
fc.Set(time.Now())
|
||||
|
||||
signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
|
||||
test.AssertNotError(t, err, "NewIssuer failed")
|
||||
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
test.AssertNotError(t, err, "failed to generate test key")
|
||||
_, issuanceToken, err := signer.Prepare(defaultProfile(), &IssuanceRequest{
|
||||
PublicKey: MarshalablePublicKey{pk.Public()},
|
||||
SubjectKeyId: goodSKID,
|
||||
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||
DNSNames: []string{"example.com"},
|
||||
IncludeMustStaple: true,
|
||||
NotBefore: fc.Now(),
|
||||
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
||||
IncludeCTPoison: true,
|
||||
})
|
||||
test.AssertNotError(t, err, "Prepare failed")
|
||||
certBytes, err := signer.Issue(issuanceToken)
|
||||
test.AssertNotError(t, err, "Issue failed")
|
||||
cert, err := x509.ParseCertificate(certBytes)
|
||||
test.AssertNotError(t, err, "failed to parse certificate")
|
||||
err = cert.CheckSignatureFrom(issuerCert.Certificate)
|
||||
test.AssertNotError(t, err, "signature validation failed")
|
||||
test.AssertByteEquals(t, cert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
test.AssertDeepEquals(t, cert.PublicKey, pk.Public())
|
||||
test.AssertEquals(t, len(cert.Extensions), 10) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies, Must-Staple, Poison
|
||||
test.AssertDeepEquals(t, cert.Extensions[9], mustStapleExt)
|
||||
}
|
||||
|
||||
func TestIssueBadLint(t *testing.T) {
|
||||
fc := clock.NewFake()
|
||||
fc.Set(time.Now())
|
||||
|
@ -966,17 +921,9 @@ func TestNewProfile(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
config: ProfileConfig{
|
||||
MaxValidityBackdate: config.Duration{Duration: 1 * time.Hour},
|
||||
MaxValidityPeriod: config.Duration{Duration: 90 * 24 * time.Hour},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "crl but no ocsp",
|
||||
config: ProfileConfig{
|
||||
MaxValidityBackdate: config.Duration{Duration: 1 * time.Hour},
|
||||
MaxValidityPeriod: config.Duration{Duration: 90 * 24 * time.Hour},
|
||||
OmitOCSP: false,
|
||||
IncludeCRLDistributionPoints: true,
|
||||
},
|
||||
},
|
||||
|
@ -1001,7 +948,6 @@ func TestNewProfile(t *testing.T) {
|
|||
config: ProfileConfig{
|
||||
MaxValidityBackdate: config.Duration{Duration: 1 * time.Hour},
|
||||
MaxValidityPeriod: config.Duration{Duration: 90 * 24 * time.Hour},
|
||||
OmitOCSP: true,
|
||||
IncludeCRLDistributionPoints: false,
|
||||
},
|
||||
wantErr: "revocation mechanism must be included",
|
||||
|
|
|
@ -161,9 +161,11 @@ type IssuerConfig struct {
|
|||
Active bool
|
||||
|
||||
IssuerURL string `validate:"required,url"`
|
||||
OCSPURL string `validate:"required,url"`
|
||||
CRLURLBase string `validate:"required,url,startswith=http://,endswith=/"`
|
||||
|
||||
// TODO(#8177): Remove this.
|
||||
OCSPURL string `validate:"omitempty,url"`
|
||||
|
||||
// Number of CRL shards.
|
||||
// This must be nonzero if adding CRLDistributionPoints to certificates
|
||||
// (that is, if profile.IncludeCRLDistributionPoints is true).
|
||||
|
@ -205,9 +207,6 @@ type Issuer struct {
|
|||
// Used to set the Authority Information Access caIssuers URL in issued
|
||||
// certificates.
|
||||
issuerURL string
|
||||
// Used to set the Authority Information Access ocsp URL in issued
|
||||
// certificates.
|
||||
ocspURL string
|
||||
// Used to set the Issuing Distribution Point extension in issued CRLs
|
||||
// and the CRL Distribution Point extension in issued certs.
|
||||
crlURLBase string
|
||||
|
@ -243,9 +242,6 @@ func newIssuer(config IssuerConfig, cert *Certificate, signer crypto.Signer, clk
|
|||
if config.IssuerURL == "" {
|
||||
return nil, errors.New("Issuer URL is required")
|
||||
}
|
||||
if config.OCSPURL == "" {
|
||||
return nil, errors.New("OCSP URL is required")
|
||||
}
|
||||
if config.CRLURLBase == "" {
|
||||
return nil, errors.New("CRL URL base is required")
|
||||
}
|
||||
|
@ -281,7 +277,6 @@ func newIssuer(config IssuerConfig, cert *Certificate, signer crypto.Signer, clk
|
|||
sigAlg: sigAlg,
|
||||
active: config.Active,
|
||||
issuerURL: config.IssuerURL,
|
||||
ocspURL: config.OCSPURL,
|
||||
crlURLBase: config.CRLURLBase,
|
||||
crlShards: config.CRLShards,
|
||||
clk: clk,
|
||||
|
|
|
@ -24,9 +24,10 @@ import (
|
|||
|
||||
func defaultProfileConfig() *ProfileConfig {
|
||||
return &ProfileConfig{
|
||||
AllowMustStaple: true,
|
||||
MaxValidityPeriod: config.Duration{Duration: time.Hour},
|
||||
MaxValidityBackdate: config.Duration{Duration: time.Hour},
|
||||
AllowMustStaple: true,
|
||||
IncludeCRLDistributionPoints: true,
|
||||
MaxValidityPeriod: config.Duration{Duration: time.Hour},
|
||||
MaxValidityBackdate: config.Duration{Duration: time.Hour},
|
||||
IgnoredLints: []string{
|
||||
// Ignore the two SCT lints because these tests don't get SCTs.
|
||||
"w_ct_sct_policy_count_unsatisfied",
|
||||
|
@ -42,8 +43,8 @@ func defaultIssuerConfig() IssuerConfig {
|
|||
return IssuerConfig{
|
||||
Active: true,
|
||||
IssuerURL: "http://issuer-url.example.org",
|
||||
OCSPURL: "http://ocsp-url.example.org",
|
||||
CRLURLBase: "http://crl-url.example.org/",
|
||||
CRLShards: 10,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
25
ra/ra.go
25
ra/ra.go
|
@ -1,9 +1,12 @@
|
|||
package ra
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -1045,6 +1048,26 @@ func (ra *RegistrationAuthorityImpl) FinalizeOrder(ctx context.Context, req *rap
|
|||
}
|
||||
}
|
||||
|
||||
// containsMustStaple returns true if the provided set of extensions includes
|
||||
// an entry whose OID and value both match the expected values for the OCSP
|
||||
// Must-Staple (a.k.a. id-pe-tlsFeature) extension.
|
||||
func containsMustStaple(extensions []pkix.Extension) bool {
|
||||
// RFC 7633: id-pe-tlsfeature OBJECT IDENTIFIER ::= { id-pe 24 }
|
||||
var mustStapleExtId = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}
|
||||
// ASN.1 encoding of:
|
||||
// SEQUENCE
|
||||
// INTEGER 5
|
||||
// where "5" is the status_request feature (RFC 6066)
|
||||
var mustStapleExtValue = []byte{0x30, 0x03, 0x02, 0x01, 0x05}
|
||||
|
||||
for _, ext := range extensions {
|
||||
if ext.Id.Equal(mustStapleExtId) && bytes.Equal(ext.Value, mustStapleExtValue) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// validateFinalizeRequest checks that a FinalizeOrder request is fully correct
|
||||
// and ready for issuance.
|
||||
func (ra *RegistrationAuthorityImpl) validateFinalizeRequest(
|
||||
|
@ -1085,7 +1108,7 @@ func (ra *RegistrationAuthorityImpl) validateFinalizeRequest(
|
|||
return nil, berrors.BadCSRError("unable to parse CSR: %s", err.Error())
|
||||
}
|
||||
|
||||
if issuance.ContainsMustStaple(csr.Extensions) {
|
||||
if containsMustStaple(csr.Extensions) {
|
||||
ra.mustStapleRequestsCounter.WithLabelValues("denied").Inc()
|
||||
return nil, berrors.UnauthorizedError(
|
||||
"OCSP must-staple extension is no longer available: see https://letsencrypt.org/2024/12/05/ending-ocsp",
|
||||
|
|
|
@ -54,8 +54,6 @@
|
|||
"issuance": {
|
||||
"certProfiles": {
|
||||
"legacy": {
|
||||
"allowMustStaple": true,
|
||||
"omitOCSP": true,
|
||||
"includeCRLDistributionPoints": true,
|
||||
"maxValidityPeriod": "7776000s",
|
||||
"maxValidityBackdate": "1h5m",
|
||||
|
@ -66,12 +64,10 @@
|
|||
]
|
||||
},
|
||||
"modern": {
|
||||
"allowMustStaple": true,
|
||||
"omitCommonName": true,
|
||||
"omitKeyEncipherment": true,
|
||||
"omitClientAuth": true,
|
||||
"omitSKID": true,
|
||||
"omitOCSP": true,
|
||||
"includeCRLDistributionPoints": true,
|
||||
"maxValidityPeriod": "583200s",
|
||||
"maxValidityBackdate": "1h5m",
|
||||
|
@ -81,12 +77,10 @@
|
|||
]
|
||||
},
|
||||
"shortlived": {
|
||||
"allowMustStaple": true,
|
||||
"omitCommonName": true,
|
||||
"omitKeyEncipherment": true,
|
||||
"omitClientAuth": true,
|
||||
"omitSKID": true,
|
||||
"omitOCSP": true,
|
||||
"includeCRLDistributionPoints": true,
|
||||
"maxValidityPeriod": "160h",
|
||||
"maxValidityBackdate": "1h5m",
|
||||
|
@ -106,7 +100,6 @@
|
|||
"active": true,
|
||||
"crlShards": 10,
|
||||
"issuerURL": "http://ca.example.org:4502/int-ecdsa-a",
|
||||
"ocspURL": "http://ca.example.org:4002/",
|
||||
"crlURLBase": "http://ca.example.org:4501/lets-encrypt-crls/43104258997432926/",
|
||||
"location": {
|
||||
"configFile": "test/certs/webpki/int-ecdsa-a.pkcs11.json",
|
||||
|
@ -118,7 +111,6 @@
|
|||
"active": true,
|
||||
"crlShards": 10,
|
||||
"issuerURL": "http://ca.example.org:4502/int-ecdsa-b",
|
||||
"ocspURL": "http://ca.example.org:4002/",
|
||||
"crlURLBase": "http://ca.example.org:4501/lets-encrypt-crls/17302365692836921/",
|
||||
"location": {
|
||||
"configFile": "test/certs/webpki/int-ecdsa-b.pkcs11.json",
|
||||
|
@ -130,7 +122,6 @@
|
|||
"active": false,
|
||||
"crlShards": 10,
|
||||
"issuerURL": "http://ca.example.org:4502/int-ecdsa-c",
|
||||
"ocspURL": "http://ca.example.org:4002/",
|
||||
"crlURLBase": "http://ca.example.org:4501/lets-encrypt-crls/56560759852043581/",
|
||||
"location": {
|
||||
"configFile": "test/certs/webpki/int-ecdsa-c.pkcs11.json",
|
||||
|
@ -142,7 +133,6 @@
|
|||
"active": true,
|
||||
"crlShards": 10,
|
||||
"issuerURL": "http://ca.example.org:4502/int-rsa-a",
|
||||
"ocspURL": "http://ca.example.org:4002/",
|
||||
"crlURLBase": "http://ca.example.org:4501/lets-encrypt-crls/29947985078257530/",
|
||||
"location": {
|
||||
"configFile": "test/certs/webpki/int-rsa-a.pkcs11.json",
|
||||
|
@ -154,7 +144,6 @@
|
|||
"active": true,
|
||||
"crlShards": 10,
|
||||
"issuerURL": "http://ca.example.org:4502/int-rsa-b",
|
||||
"ocspURL": "http://ca.example.org:4002/",
|
||||
"crlURLBase": "http://ca.example.org:4501/lets-encrypt-crls/6762885421992935/",
|
||||
"location": {
|
||||
"configFile": "test/certs/webpki/int-rsa-b.pkcs11.json",
|
||||
|
@ -166,7 +155,6 @@
|
|||
"active": false,
|
||||
"crlShards": 10,
|
||||
"issuerURL": "http://ca.example.org:4502/int-rsa-c",
|
||||
"ocspURL": "http://ca.example.org:4002/",
|
||||
"crlURLBase": "http://ca.example.org:4501/lets-encrypt-crls/56183656833365902/",
|
||||
"location": {
|
||||
"configFile": "test/certs/webpki/int-rsa-c.pkcs11.json",
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
"certProfiles": {
|
||||
"legacy": {
|
||||
"allowMustStaple": true,
|
||||
"omitOCSP": true,
|
||||
"includeCRLDistributionPoints": true,
|
||||
"maxValidityPeriod": "7776000s",
|
||||
"maxValidityBackdate": "1h5m",
|
||||
|
@ -71,6 +72,7 @@
|
|||
"omitKeyEncipherment": true,
|
||||
"omitClientAuth": true,
|
||||
"omitSKID": true,
|
||||
"omitOCSP": true,
|
||||
"includeCRLDistributionPoints": true,
|
||||
"maxValidityPeriod": "583200s",
|
||||
"maxValidityBackdate": "1h5m",
|
||||
|
@ -85,6 +87,7 @@
|
|||
"omitKeyEncipherment": true,
|
||||
"omitClientAuth": true,
|
||||
"omitSKID": true,
|
||||
"omitOCSP": true,
|
||||
"includeCRLDistributionPoints": true,
|
||||
"maxValidityPeriod": "160h",
|
||||
"maxValidityBackdate": "1h5m",
|
||||
|
|
Loading…
Reference in New Issue