issuance: split linting and issuing (#6788)
It's useful to be able to get a copy of the linting certificate out of the process, so we can store it in the database for use by processes that want a certificate-shaped object (for instance, scanning possibly-issued public keys for a newly discovered weakness in key generation). To do this, while still ensuring that it's impossible to issue a certificate without linting it, return an IssuanceToken from the linting process. The IssuanceToken has a private field containing the template that was used for linting. To issue a final certificate, the IssuanceToken has to be redeemed, and only the template stored in it can be used.
This commit is contained in:
parent
d6cd589795
commit
b4bdd035ad
20
ca/ca.go
20
ca/ca.go
|
@ -319,12 +319,19 @@ func (ca *certificateAuthorityImpl) IssueCertificateForPrecertificate(ctx contex
|
||||||
ca.log.AuditInfof("Signing cert: serial=[%s] regID=[%d] names=[%s] precert=[%s]",
|
ca.log.AuditInfof("Signing cert: serial=[%s] regID=[%d] names=[%s] precert=[%s]",
|
||||||
serialHex, req.RegistrationID, names, hex.EncodeToString(precert.Raw))
|
serialHex, req.RegistrationID, names, hex.EncodeToString(precert.Raw))
|
||||||
|
|
||||||
certDER, err := issuer.Issue(issuanceReq)
|
_, issuanceToken, err := issuer.Prepare(issuanceReq)
|
||||||
|
if err != nil {
|
||||||
|
ca.log.AuditErrf("Preparing cert failed: serial=[%s] regID=[%d] names=[%s] err=[%v]",
|
||||||
|
serialHex, req.RegistrationID, names, err)
|
||||||
|
return nil, berrors.InternalServerError("failed to prepare certificate signing: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certDER, err := issuer.Issue(issuanceToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ca.noteSignError(err)
|
ca.noteSignError(err)
|
||||||
ca.log.AuditErrf("Signing cert failed: serial=[%s] regID=[%d] names=[%s] err=[%v]",
|
ca.log.AuditErrf("Signing cert failed: serial=[%s] regID=[%d] names=[%s] err=[%v]",
|
||||||
serialHex, req.RegistrationID, names, err)
|
serialHex, req.RegistrationID, names, err)
|
||||||
return nil, berrors.InternalServerError("failed to sign precertificate: %s", err)
|
return nil, berrors.InternalServerError("failed to sign certificate: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ca.signatureCount.With(prometheus.Labels{"purpose": string(certType), "issuer": issuer.Name()}).Inc()
|
ca.signatureCount.With(prometheus.Labels{"purpose": string(certType), "issuer": issuer.Name()}).Inc()
|
||||||
|
@ -448,7 +455,14 @@ func (ca *certificateAuthorityImpl) issuePrecertificateInner(ctx context.Context
|
||||||
NotAfter: validity.NotAfter,
|
NotAfter: validity.NotAfter,
|
||||||
}
|
}
|
||||||
|
|
||||||
certDER, err := issuer.Issue(req)
|
_, issuanceToken, err := issuer.Prepare(req)
|
||||||
|
if err != nil {
|
||||||
|
ca.log.AuditErrf("Preparing precert failed: serial=[%s] regID=[%d] names=[%s] err=[%v]",
|
||||||
|
serialHex, issueReq.RegistrationID, strings.Join(csr.DNSNames, ", "), err)
|
||||||
|
return nil, nil, nil, berrors.InternalServerError("failed to prepare precertificate signing: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certDER, err := issuer.Issue(issuanceToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ca.noteSignError(err)
|
ca.noteSignError(err)
|
||||||
ca.log.AuditErrf("Signing precert failed: serial=[%s] regID=[%d] names=[%s] err=[%v]",
|
ca.log.AuditErrf("Signing precert failed: serial=[%s] regID=[%d] names=[%s] err=[%v]",
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
ct "github.com/google/certificate-transparency-go"
|
ct "github.com/google/certificate-transparency-go"
|
||||||
|
@ -598,16 +599,30 @@ type IssuanceRequest struct {
|
||||||
SCTList []ct.SignedCertificateTimestamp
|
SCTList []ct.SignedCertificateTimestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue generates a certificate from the provided issuance request and
|
// An issuanceToken represents an assertion that Issuer.Lint has generated
|
||||||
// signs it. Before signing the certificate with the issuer's private
|
// a linting certificate for a given input and run the linter over it with no
|
||||||
// key, it is signed using a throwaway key so that it can be linted using
|
// errors. The token may be redeemed (at most once) to sign a certificate or
|
||||||
// zlint. If the linting fails, an error is returned and the certificate
|
// precertificate with the same Issuer's private key, containing the same
|
||||||
// is not signed using the issuer's key.
|
// contents that were linted.
|
||||||
func (i *Issuer) Issue(req *IssuanceRequest) ([]byte, error) {
|
type issuanceToken struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
template *x509.Certificate
|
||||||
|
pubKey any
|
||||||
|
// A pointer to the issuer that created this token. This token may only
|
||||||
|
// be redeemed by the same issuer.
|
||||||
|
issuer *Issuer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare applies this Issuer's profile to create a template certificate. It
|
||||||
|
// then generates a linting certificate from that template and runs the linter
|
||||||
|
// over it. If successful, returns both the linting certificate (which can be
|
||||||
|
// stored) and an issuanceToken. The issuanceToken can be used to sign a
|
||||||
|
// matching certificate with this Issuer's private key.
|
||||||
|
func (i *Issuer) Prepare(req *IssuanceRequest) ([]byte, *issuanceToken, error) {
|
||||||
// check request is valid according to the issuance profile
|
// check request is valid according to the issuance profile
|
||||||
err := i.Profile.requestValid(i.Clk, req)
|
err := i.Profile.requestValid(i.Clk, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate template from the issuance profile
|
// generate template from the issuance profile
|
||||||
|
@ -623,7 +638,7 @@ func (i *Issuer) Issue(req *IssuanceRequest) ([]byte, error) {
|
||||||
template.AuthorityKeyId = i.Cert.SubjectKeyId
|
template.AuthorityKeyId = i.Cert.SubjectKeyId
|
||||||
skid, err := generateSKID(req.PublicKey)
|
skid, err := generateSKID(req.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
template.SubjectKeyId = skid
|
template.SubjectKeyId = skid
|
||||||
switch req.PublicKey.(type) {
|
switch req.PublicKey.(type) {
|
||||||
|
@ -638,7 +653,7 @@ func (i *Issuer) Issue(req *IssuanceRequest) ([]byte, error) {
|
||||||
} else if req.SCTList != nil {
|
} else if req.SCTList != nil {
|
||||||
sctListExt, err := generateSCTListExt(req.SCTList)
|
sctListExt, err := generateSCTListExt(req.SCTList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
template.ExtraExtensions = append(template.ExtraExtensions, sctListExt)
|
template.ExtraExtensions = append(template.ExtraExtensions, sctListExt)
|
||||||
}
|
}
|
||||||
|
@ -649,12 +664,35 @@ func (i *Issuer) Issue(req *IssuanceRequest) ([]byte, error) {
|
||||||
|
|
||||||
// check that the tbsCertificate is properly formed by signing it
|
// check that the tbsCertificate is properly formed by signing it
|
||||||
// with a throwaway key and then linting it using zlint
|
// with a throwaway key and then linting it using zlint
|
||||||
err = i.Linter.Check(template, req.PublicKey)
|
lintCertBytes, err := i.Linter.Check(template, req.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("tbsCertificate linting failed: %w", err)
|
return nil, nil, fmt.Errorf("tbsCertificate linting failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return x509.CreateCertificate(rand.Reader, template, i.Cert.Certificate, req.PublicKey, i.Signer)
|
token := &issuanceToken{sync.Mutex{}, template, req.PublicKey, i}
|
||||||
|
return lintCertBytes, token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue performs a real issuance using an issuanceToken resulting from a
|
||||||
|
// previous call to Prepare(). Call this at most once per token. Calls after
|
||||||
|
// the first will receive an error.
|
||||||
|
func (i *Issuer) Issue(token *issuanceToken) ([]byte, error) {
|
||||||
|
if token == nil {
|
||||||
|
return nil, errors.New("nil issuanceToken")
|
||||||
|
}
|
||||||
|
token.mu.Lock()
|
||||||
|
defer token.mu.Unlock()
|
||||||
|
if token.template == nil {
|
||||||
|
return nil, errors.New("issuance token already redeemed")
|
||||||
|
}
|
||||||
|
template := token.template
|
||||||
|
token.template = nil
|
||||||
|
|
||||||
|
if token.issuer != i {
|
||||||
|
return nil, errors.New("tried to redeem issuance token with the wrong issuer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return x509.CreateCertificate(rand.Reader, template, i.Cert.Certificate, token.pubKey, i.Signer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ContainsMustStaple(extensions []pkix.Extension) bool {
|
func ContainsMustStaple(extensions []pkix.Extension) bool {
|
||||||
|
|
|
@ -552,7 +552,7 @@ func TestIssue(t *testing.T) {
|
||||||
test.AssertNotError(t, err, "NewIssuer failed")
|
test.AssertNotError(t, err, "NewIssuer failed")
|
||||||
pk, err := tc.generateFunc()
|
pk, err := tc.generateFunc()
|
||||||
test.AssertNotError(t, err, "failed to generate test key")
|
test.AssertNotError(t, err, "failed to generate test key")
|
||||||
certBytes, err := signer.Issue(&IssuanceRequest{
|
lintCertBytes, issuanceToken, err := signer.Prepare(&IssuanceRequest{
|
||||||
PublicKey: pk.Public(),
|
PublicKey: pk.Public(),
|
||||||
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||||
CommonName: "example.com",
|
CommonName: "example.com",
|
||||||
|
@ -560,6 +560,10 @@ func TestIssue(t *testing.T) {
|
||||||
NotBefore: fc.Now(),
|
NotBefore: fc.Now(),
|
||||||
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
||||||
})
|
})
|
||||||
|
test.AssertNotError(t, err, "Prepare failed")
|
||||||
|
_, err = x509.ParseCertificate(lintCertBytes)
|
||||||
|
test.AssertNotError(t, err, "failed to parse certificate")
|
||||||
|
certBytes, err := signer.Issue(issuanceToken)
|
||||||
test.AssertNotError(t, err, "Issue failed")
|
test.AssertNotError(t, err, "Issue failed")
|
||||||
cert, err := x509.ParseCertificate(certBytes)
|
cert, err := x509.ParseCertificate(certBytes)
|
||||||
test.AssertNotError(t, err, "failed to parse certificate")
|
test.AssertNotError(t, err, "failed to parse certificate")
|
||||||
|
@ -591,14 +595,16 @@ func TestIssueRSA(t *testing.T) {
|
||||||
test.AssertNotError(t, err, "NewIssuer failed")
|
test.AssertNotError(t, err, "NewIssuer failed")
|
||||||
pk, err := rsa.GenerateKey(rand.Reader, 2048)
|
pk, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
test.AssertNotError(t, err, "failed to generate test key")
|
test.AssertNotError(t, err, "failed to generate test key")
|
||||||
certBytes, err := signer.Issue(&IssuanceRequest{
|
_, issuanceToken, err := signer.Prepare(&IssuanceRequest{
|
||||||
PublicKey: pk.Public(),
|
PublicKey: pk.Public(),
|
||||||
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||||
DNSNames: []string{"example.com"},
|
DNSNames: []string{"example.com"},
|
||||||
NotBefore: fc.Now(),
|
NotBefore: fc.Now(),
|
||||||
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
||||||
})
|
})
|
||||||
test.AssertNotError(t, err, "Issue failed")
|
test.AssertNotError(t, err, "failed to parse lint certificate")
|
||||||
|
certBytes, err := signer.Issue(issuanceToken)
|
||||||
|
test.AssertNotError(t, err, "failed to parse certificate")
|
||||||
cert, err := x509.ParseCertificate(certBytes)
|
cert, err := x509.ParseCertificate(certBytes)
|
||||||
test.AssertNotError(t, err, "failed to parse certificate")
|
test.AssertNotError(t, err, "failed to parse certificate")
|
||||||
err = cert.CheckSignatureFrom(issuerCert.Certificate)
|
err = cert.CheckSignatureFrom(issuerCert.Certificate)
|
||||||
|
@ -635,18 +641,22 @@ func TestIssueCommonName(t *testing.T) {
|
||||||
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
||||||
}
|
}
|
||||||
|
|
||||||
certBytes, err := signer.Issue(ir)
|
_, issuanceToken, err := signer.Prepare(ir)
|
||||||
|
test.AssertNotError(t, err, "Prepare failed")
|
||||||
|
certBytes, err := signer.Issue(issuanceToken)
|
||||||
test.AssertNotError(t, err, "Issue failed")
|
test.AssertNotError(t, err, "Issue failed")
|
||||||
cert, err := x509.ParseCertificate(certBytes)
|
cert, err := x509.ParseCertificate(certBytes)
|
||||||
test.AssertNotError(t, err, "failed to parse certificate")
|
test.AssertNotError(t, err, "failed to parse certificate")
|
||||||
test.AssertEquals(t, cert.Subject.CommonName, "example.com")
|
test.AssertEquals(t, cert.Subject.CommonName, "example.com")
|
||||||
|
|
||||||
signer.Profile.allowCommonName = false
|
signer.Profile.allowCommonName = false
|
||||||
_, err = signer.Issue(ir)
|
_, _, err = signer.Prepare(ir)
|
||||||
test.AssertError(t, err, "Issue should have failed")
|
test.AssertError(t, err, "Prepare should have failed")
|
||||||
|
|
||||||
ir.CommonName = ""
|
ir.CommonName = ""
|
||||||
certBytes, err = signer.Issue(ir)
|
_, issuanceToken, err = signer.Prepare(ir)
|
||||||
|
test.AssertNotError(t, err, "Prepare failed")
|
||||||
|
certBytes, err = signer.Issue(issuanceToken)
|
||||||
test.AssertNotError(t, err, "Issue failed")
|
test.AssertNotError(t, err, "Issue failed")
|
||||||
cert, err = x509.ParseCertificate(certBytes)
|
cert, err = x509.ParseCertificate(certBytes)
|
||||||
test.AssertNotError(t, err, "failed to parse certificate")
|
test.AssertNotError(t, err, "failed to parse certificate")
|
||||||
|
@ -670,7 +680,7 @@ func TestIssueCTPoison(t *testing.T) {
|
||||||
test.AssertNotError(t, err, "NewIssuer failed")
|
test.AssertNotError(t, err, "NewIssuer failed")
|
||||||
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
test.AssertNotError(t, err, "failed to generate test key")
|
test.AssertNotError(t, err, "failed to generate test key")
|
||||||
certBytes, err := signer.Issue(&IssuanceRequest{
|
_, issuanceToken, err := signer.Prepare(&IssuanceRequest{
|
||||||
PublicKey: pk.Public(),
|
PublicKey: pk.Public(),
|
||||||
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||||
DNSNames: []string{"example.com"},
|
DNSNames: []string{"example.com"},
|
||||||
|
@ -678,6 +688,8 @@ func TestIssueCTPoison(t *testing.T) {
|
||||||
NotBefore: fc.Now(),
|
NotBefore: fc.Now(),
|
||||||
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
||||||
})
|
})
|
||||||
|
test.AssertNotError(t, err, "Prepare failed")
|
||||||
|
certBytes, err := signer.Issue(issuanceToken)
|
||||||
test.AssertNotError(t, err, "Issue failed")
|
test.AssertNotError(t, err, "Issue failed")
|
||||||
cert, err := x509.ParseCertificate(certBytes)
|
cert, err := x509.ParseCertificate(certBytes)
|
||||||
test.AssertNotError(t, err, "failed to parse certificate")
|
test.AssertNotError(t, err, "failed to parse certificate")
|
||||||
|
@ -708,7 +720,7 @@ func TestIssueSCTList(t *testing.T) {
|
||||||
test.AssertNotError(t, err, "failed to decode ct log ID")
|
test.AssertNotError(t, err, "failed to decode ct log ID")
|
||||||
logID2, err := base64.StdEncoding.DecodeString("UtToynGEyMkkXDMQei8Ll54oMwWHI0IieDEKs12/Td4=")
|
logID2, err := base64.StdEncoding.DecodeString("UtToynGEyMkkXDMQei8Ll54oMwWHI0IieDEKs12/Td4=")
|
||||||
test.AssertNotError(t, err, "failed to decode ct log ID")
|
test.AssertNotError(t, err, "failed to decode ct log ID")
|
||||||
certBytes, err := signer.Issue(&IssuanceRequest{
|
_, issuanceToken, err := signer.Prepare(&IssuanceRequest{
|
||||||
PublicKey: pk.Public(),
|
PublicKey: pk.Public(),
|
||||||
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||||
DNSNames: []string{"example.com"},
|
DNSNames: []string{"example.com"},
|
||||||
|
@ -725,6 +737,8 @@ func TestIssueSCTList(t *testing.T) {
|
||||||
NotBefore: fc.Now(),
|
NotBefore: fc.Now(),
|
||||||
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
||||||
})
|
})
|
||||||
|
test.AssertNotError(t, err, "Prepare failed")
|
||||||
|
certBytes, err := signer.Issue(issuanceToken)
|
||||||
test.AssertNotError(t, err, "Issue failed")
|
test.AssertNotError(t, err, "Issue failed")
|
||||||
cert, err := x509.ParseCertificate(certBytes)
|
cert, err := x509.ParseCertificate(certBytes)
|
||||||
test.AssertNotError(t, err, "failed to parse certificate")
|
test.AssertNotError(t, err, "failed to parse certificate")
|
||||||
|
@ -762,7 +776,7 @@ func TestIssueMustStaple(t *testing.T) {
|
||||||
test.AssertNotError(t, err, "NewIssuer failed")
|
test.AssertNotError(t, err, "NewIssuer failed")
|
||||||
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
test.AssertNotError(t, err, "failed to generate test key")
|
test.AssertNotError(t, err, "failed to generate test key")
|
||||||
certBytes, err := signer.Issue(&IssuanceRequest{
|
_, issuanceToken, err := signer.Prepare(&IssuanceRequest{
|
||||||
PublicKey: pk.Public(),
|
PublicKey: pk.Public(),
|
||||||
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||||
DNSNames: []string{"example.com"},
|
DNSNames: []string{"example.com"},
|
||||||
|
@ -770,6 +784,8 @@ func TestIssueMustStaple(t *testing.T) {
|
||||||
NotBefore: fc.Now(),
|
NotBefore: fc.Now(),
|
||||||
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
||||||
})
|
})
|
||||||
|
test.AssertNotError(t, err, "Prepare failed")
|
||||||
|
certBytes, err := signer.Issue(issuanceToken)
|
||||||
test.AssertNotError(t, err, "Issue failed")
|
test.AssertNotError(t, err, "Issue failed")
|
||||||
cert, err := x509.ParseCertificate(certBytes)
|
cert, err := x509.ParseCertificate(certBytes)
|
||||||
test.AssertNotError(t, err, "failed to parse certificate")
|
test.AssertNotError(t, err, "failed to parse certificate")
|
||||||
|
@ -790,14 +806,14 @@ func TestIssueBadLint(t *testing.T) {
|
||||||
test.AssertNotError(t, err, "NewIssuer failed")
|
test.AssertNotError(t, err, "NewIssuer failed")
|
||||||
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
test.AssertNotError(t, err, "failed to generate test key")
|
test.AssertNotError(t, err, "failed to generate test key")
|
||||||
_, err = signer.Issue(&IssuanceRequest{
|
_, _, err = signer.Prepare(&IssuanceRequest{
|
||||||
PublicKey: pk.Public(),
|
PublicKey: pk.Public(),
|
||||||
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||||
DNSNames: []string{"example.com"},
|
DNSNames: []string{"example.com"},
|
||||||
NotBefore: fc.Now(),
|
NotBefore: fc.Now(),
|
||||||
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
||||||
})
|
})
|
||||||
test.AssertError(t, err, "Issue didn't fail")
|
test.AssertError(t, err, "Prepare didn't fail")
|
||||||
test.AssertContains(t, err.Error(), "tbsCertificate linting failed: failed lints")
|
test.AssertContains(t, err.Error(), "tbsCertificate linting failed: failed lints")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -856,3 +872,50 @@ func TestLoadChain_InvalidSig(t *testing.T) {
|
||||||
test.Assert(t, strings.Contains(err.Error(), "signature from \"CN=happy hacker fake CA\""),
|
test.Assert(t, strings.Contains(err.Error(), "signature from \"CN=happy hacker fake CA\""),
|
||||||
fmt.Sprintf("Expected error to mention subject, got: %s", err))
|
fmt.Sprintf("Expected error to mention subject, got: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssuanceToken(t *testing.T) {
|
||||||
|
fc := clock.NewFake()
|
||||||
|
linter, err := linter.New(issuerCert.Certificate, issuerSigner, []string{})
|
||||||
|
test.AssertNotError(t, err, "failed to create linter")
|
||||||
|
signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
|
||||||
|
test.AssertNotError(t, err, "NewIssuer failed")
|
||||||
|
|
||||||
|
_, err = signer.Issue(&issuanceToken{})
|
||||||
|
test.AssertError(t, err, "expected issuance with a zero token to fail")
|
||||||
|
|
||||||
|
_, err = signer.Issue(nil)
|
||||||
|
test.AssertError(t, err, "expected issuance with a nil token to fail")
|
||||||
|
|
||||||
|
pk, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
test.AssertNotError(t, err, "failed to generate test key")
|
||||||
|
_, issuanceToken, err := signer.Prepare(&IssuanceRequest{
|
||||||
|
PublicKey: pk.Public(),
|
||||||
|
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||||
|
DNSNames: []string{"example.com"},
|
||||||
|
NotBefore: fc.Now(),
|
||||||
|
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
||||||
|
})
|
||||||
|
test.AssertNotError(t, err, "expected Prepare to succeed")
|
||||||
|
_, err = signer.Issue(issuanceToken)
|
||||||
|
test.AssertNotError(t, err, "expected first issuance to succeed")
|
||||||
|
|
||||||
|
_, err = signer.Issue(issuanceToken)
|
||||||
|
test.AssertError(t, err, "expected second issuance with the same issuance token to fail")
|
||||||
|
test.AssertContains(t, err.Error(), "issuance token already redeemed")
|
||||||
|
|
||||||
|
_, issuanceToken, err = signer.Prepare(&IssuanceRequest{
|
||||||
|
PublicKey: pk.Public(),
|
||||||
|
Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||||
|
DNSNames: []string{"example.com"},
|
||||||
|
NotBefore: fc.Now(),
|
||||||
|
NotAfter: fc.Now().Add(time.Hour - time.Second),
|
||||||
|
})
|
||||||
|
test.AssertNotError(t, err, "expected Prepare to succeed")
|
||||||
|
|
||||||
|
signer2, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
|
||||||
|
test.AssertNotError(t, err, "NewIssuer failed")
|
||||||
|
|
||||||
|
_, err = signer2.Issue(issuanceToken)
|
||||||
|
test.AssertError(t, err, "expected redeeming an issuance token with the wrong issuer to fail")
|
||||||
|
test.AssertContains(t, err.Error(), "wrong issuer")
|
||||||
|
}
|
||||||
|
|
|
@ -33,7 +33,8 @@ func Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey, realIssuer *x5
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return linter.Check(tbs, subjectPubKey)
|
_, err = linter.Check(tbs, subjectPubKey)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Linter is capable of linting a to-be-signed (TBS) certificate. It does so by
|
// Linter is capable of linting a to-be-signed (TBS) certificate. It does so by
|
||||||
|
@ -69,14 +70,19 @@ func New(realIssuer *x509.Certificate, realSigner crypto.Signer, skipLints []str
|
||||||
|
|
||||||
// Check signs the given TBS certificate using the Linter's fake issuer cert and
|
// Check signs the given TBS certificate using the Linter's fake issuer cert and
|
||||||
// private key, then runs the resulting certificate through all non-filtered
|
// private key, then runs the resulting certificate through all non-filtered
|
||||||
// lints. It returns an error if any lint fails.
|
// lints. It returns an error if any lint fails. On success it also returns the
|
||||||
func (l Linter) Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey) error {
|
// DER bytes of the linting certificate.
|
||||||
cert, err := makeLintCert(tbs, subjectPubKey, l.issuer, l.signer)
|
func (l Linter) Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey) ([]byte, error) {
|
||||||
|
lintCertBytes, cert, err := makeLintCert(tbs, subjectPubKey, l.issuer, l.signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
lintRes := zlint.LintCertificateEx(cert, l.registry)
|
lintRes := zlint.LintCertificateEx(cert, l.registry)
|
||||||
return ProcessResultSet(lintRes)
|
err = ProcessResultSet(lintRes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return lintCertBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckCRL signs the given RevocationList template using the Linter's fake
|
// CheckCRL signs the given RevocationList template using the Linter's fake
|
||||||
|
@ -178,16 +184,16 @@ func makeRegistry(skipLints []string) (lint.Registry, error) {
|
||||||
return reg, nil
|
return reg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeLintCert(tbs *x509.Certificate, subjectPubKey crypto.PublicKey, issuer *x509.Certificate, signer crypto.Signer) (*zlintx509.Certificate, error) {
|
func makeLintCert(tbs *x509.Certificate, subjectPubKey crypto.PublicKey, issuer *x509.Certificate, signer crypto.Signer) ([]byte, *zlintx509.Certificate, error) {
|
||||||
lintCertBytes, err := x509.CreateCertificate(rand.Reader, tbs, issuer, subjectPubKey, signer)
|
lintCertBytes, err := x509.CreateCertificate(rand.Reader, tbs, issuer, subjectPubKey, signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create lint certificate: %w", err)
|
return nil, nil, fmt.Errorf("failed to create lint certificate: %w", err)
|
||||||
}
|
}
|
||||||
lintCert, err := zlintx509.ParseCertificate(lintCertBytes)
|
lintCert, err := zlintx509.ParseCertificate(lintCertBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse lint certificate: %w", err)
|
return nil, nil, fmt.Errorf("failed to parse lint certificate: %w", err)
|
||||||
}
|
}
|
||||||
return lintCert, nil
|
return lintCertBytes, lintCert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProcessResultSet(lintRes *zlint.ResultSet) error {
|
func ProcessResultSet(lintRes *zlint.ResultSet) error {
|
||||||
|
|
Loading…
Reference in New Issue