Add support for the must-staple extension
This commit is contained in:
parent
6b5101c58a
commit
02a98e8097
|
@ -9,6 +9,7 @@ import (
|
|||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
|
@ -47,6 +48,19 @@ var badSignatureAlgorithms = map[x509.SignatureAlgorithm]bool{
|
|||
x509.ECDSAWithSHA1: true,
|
||||
}
|
||||
|
||||
// OID and fixed value for the "must staple" variant of the TLS Feature
|
||||
// extension:
|
||||
//
|
||||
// Features ::= SEQUENCE OF INTEGER [RFC7633]
|
||||
// enum { ... status_request(5) ...} ExtensionType; [RFC6066]
|
||||
//
|
||||
// DER Encoding:
|
||||
// 30 03 - SEQUENCE (3 octets)
|
||||
// |-- 02 01 - INTEGER (1 octet)
|
||||
// | |-- 05 - 5
|
||||
var oidTLSFeature = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}
|
||||
var mustStapleFeatureValue = "0303020105"
|
||||
|
||||
// Metrics for CA statistics
|
||||
const (
|
||||
// Increments when CA observes an HSM fault
|
||||
|
@ -298,6 +312,26 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
|
|||
}
|
||||
}
|
||||
|
||||
// Process requested extensions. For now, the only extension we support is
|
||||
// TLS Feature [RFC7633], i.e., "Must Staple", and for that we ignore the
|
||||
// value provided by the client and just overwrite it with the specific value
|
||||
// that only requires stapling.
|
||||
//
|
||||
// Other requested extensions are silently ignored.
|
||||
//
|
||||
// XXX(rlb): It might be good to add a generic mechanism for extensions, but
|
||||
// on the other hand, we probably *don't* want to copy arbitrary client-
|
||||
// provided data into extensions.
|
||||
extensions := core.ExtensionsFromCSR(&csr)
|
||||
requestedExtensions := []signer.Extension{}
|
||||
if _, present := extensions[oidTLSFeature.String()]; present {
|
||||
requestedExtensions = append(requestedExtensions, signer.Extension{
|
||||
ID: cfsslConfig.OID(oidTLSFeature),
|
||||
Critical: false,
|
||||
Value: mustStapleFeatureValue,
|
||||
})
|
||||
}
|
||||
|
||||
notAfter := ca.clk.Now().Add(ca.validityPeriod)
|
||||
|
||||
if ca.notAfter.Before(notAfter) {
|
||||
|
@ -336,7 +370,8 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
|
|||
Subject: &signer.Subject{
|
||||
CN: commonName,
|
||||
},
|
||||
Serial: serialBigInt,
|
||||
Serial: serialBigInt,
|
||||
Extensions: requestedExtensions,
|
||||
}
|
||||
|
||||
certPEM, err := ca.signer.Sign(req)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
|
@ -92,6 +93,24 @@ var (
|
|||
// * DNSNames = moreCAPs.com, morecaps.com, evenMOREcaps.com, Capitalizedletters.COM
|
||||
CapitalizedCSR = mustRead("./testdata/capitalized_cn_and_san.der.csr")
|
||||
|
||||
// CSR generated by Go:
|
||||
// * Random public key
|
||||
// * CN = not-example.com
|
||||
// * Includes an extensionRequest attribute for a well-formed TLS Feature extension
|
||||
MustStapleCSR = mustRead("./testdata/must_staple.der.csr")
|
||||
|
||||
// CSR generated by Go:
|
||||
// * Random public key
|
||||
// * CN = not-example.com
|
||||
// * Includes an extensionRequest attribute for an empty TLS Feature extension
|
||||
TLSFeatureUnknownCSR = mustRead("./testdata/tls_feature_unknown.der.csr")
|
||||
|
||||
// CSR generated by Go:
|
||||
// * Random public key
|
||||
// * CN = not-example.com
|
||||
// * Includes an extensionRequest attribute for the CT Poison extension (not supported)
|
||||
UnsupportedExtensionCSR = mustRead("./testdata/unsupported_extension.der.csr")
|
||||
|
||||
log = mocks.UseMockLog()
|
||||
)
|
||||
|
||||
|
@ -191,6 +210,9 @@ func setup(t *testing.T) *testCtx {
|
|||
PublicKey: true,
|
||||
SignatureAlgorithm: true,
|
||||
},
|
||||
AllowedExtensions: []cfsslConfig.OID{
|
||||
cfsslConfig.OID(oidTLSFeature),
|
||||
},
|
||||
},
|
||||
},
|
||||
Default: &cfsslConfig.SigningProfile{
|
||||
|
@ -499,3 +521,60 @@ func TestHSMFaultTimeout(t *testing.T) {
|
|||
test.AssertEquals(t, ctx.stats.Counters[metricHSMFaultObserved], int64(2))
|
||||
test.AssertEquals(t, ctx.stats.Counters[metricHSMFaultRejected], int64(4))
|
||||
}
|
||||
|
||||
func TestExtensions(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ctx.caConfig.MaxNames = 3
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
||||
// A TLS feature extension should put a must-staple extension into the cert
|
||||
csr, _ := x509.ParseCertificateRequest(MustStapleCSR)
|
||||
cert, err := ca.IssueCertificate(*csr, ctx.reg.ID)
|
||||
test.AssertNotError(t, err, "Failed to gracefully handle a CSR with must_staple")
|
||||
parsedCert, err := x509.ParseCertificate(cert.DER)
|
||||
test.AssertNotError(t, err, "Error parsing certificate produced by CA")
|
||||
|
||||
// We already expect 8 extensions: extKeyUsage, basicConstraints,
|
||||
// subjectKeyIdentifier, authorityKeyIdentifier, subjectAlternativeName,
|
||||
// crlDistributionPoints, authorityInfoAccess, certificatePolicies
|
||||
test.AssertEquals(t, len(parsedCert.Extensions), 9)
|
||||
foundMustStaple := false
|
||||
for _, ext := range parsedCert.Extensions {
|
||||
if ext.Id.Equal(oidTLSFeature) {
|
||||
foundMustStaple = true
|
||||
test.Assert(t, !ext.Critical, "Extension was marked critical")
|
||||
test.AssertEquals(t, hex.EncodeToString(ext.Value), mustStapleFeatureValue)
|
||||
}
|
||||
}
|
||||
test.Assert(t, foundMustStaple, "TLS Feature extension not found")
|
||||
|
||||
// ... even if it doesn't ask for stapling
|
||||
csr, _ = x509.ParseCertificateRequest(TLSFeatureUnknownCSR)
|
||||
cert, err = ca.IssueCertificate(*csr, ctx.reg.ID)
|
||||
test.AssertNotError(t, err, "Failed to gracefully handle a CSR with an empty TLS feature extension")
|
||||
parsedCert, err = x509.ParseCertificate(cert.DER)
|
||||
test.AssertNotError(t, err, "Error parsing certificate produced by CA")
|
||||
|
||||
test.AssertEquals(t, len(parsedCert.Extensions), 9)
|
||||
foundMustStaple = false
|
||||
for _, ext := range parsedCert.Extensions {
|
||||
if ext.Id.Equal(oidTLSFeature) {
|
||||
foundMustStaple = true
|
||||
test.Assert(t, !ext.Critical, "Extension was marked critical")
|
||||
test.AssertEquals(t, hex.EncodeToString(ext.Value), mustStapleFeatureValue)
|
||||
}
|
||||
}
|
||||
test.Assert(t, foundMustStaple, "TLS Feature extension not found")
|
||||
|
||||
// Unsupported extensions should be ignored
|
||||
csr, _ = x509.ParseCertificateRequest(UnsupportedExtensionCSR)
|
||||
cert, err = ca.IssueCertificate(*csr, ctx.reg.ID)
|
||||
test.AssertNotError(t, err, "Failed to gracefully handle a CSR with an unsupported extension")
|
||||
parsedCert, err = x509.ParseCertificate(cert.DER)
|
||||
test.AssertNotError(t, err, "Error parsing certificate produced by CA")
|
||||
test.AssertEquals(t, len(parsedCert.Extensions), 8)
|
||||
}
|
||||
|
|
30
core/util.go
30
core/util.go
|
@ -431,6 +431,36 @@ func VerifyCSR(csr *x509.CertificateRequest) error {
|
|||
return errors.New("Unsupported CSR signing algorithm")
|
||||
}
|
||||
|
||||
var (
|
||||
oidExtensionRequest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 14}
|
||||
oidSubjectAltName = asn1.ObjectIdentifier{2, 5, 29, 17}
|
||||
)
|
||||
|
||||
// ExtensionsFromCSR extracts the set of requested extensions from a CSR
|
||||
func ExtensionsFromCSR(csr *x509.CertificateRequest) map[string][]byte {
|
||||
extensionMap := map[string][]byte{}
|
||||
for _, attr := range csr.Attributes {
|
||||
if !attr.Type.Equal(oidExtensionRequest) || len(attr.Value) != 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ext := range attr.Value[0] {
|
||||
// SubjectAltName is already handled by ParseCertificateRequest
|
||||
if ext.Type.Equal(oidSubjectAltName) {
|
||||
continue
|
||||
}
|
||||
|
||||
extValue, ok := ext.Value.([]byte)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
extensionMap[ext.Type.String()] = extValue
|
||||
}
|
||||
}
|
||||
return extensionMap
|
||||
}
|
||||
|
||||
// SerialToString converts a certificate serial number (big.Int) to a String
|
||||
// consistently.
|
||||
func SerialToString(serial *big.Int) string {
|
||||
|
|
Loading…
Reference in New Issue