Skip to content

Commit

Permalink
Progress before bed
Browse files Browse the repository at this point in the history
  • Loading branch information
pgporada committed Feb 17, 2024
1 parent 800b3c5 commit e625842
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 38 deletions.
73 changes: 58 additions & 15 deletions ca/ca.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package ca

import (
"bytes"
"context"
"crypto"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/gob"
"encoding/hex"
"errors"
"fmt"
Expand Down Expand Up @@ -54,7 +56,10 @@ type issuerMaps struct {

// certProfilesMap allows looking up the human-readable name of a certificate
// profile to retrieve the actual profile.
type certProfilesMap map[string]*issuance.Profile
type certProfilesMaps struct {
byHash map[[32]byte]*issuance.Profile
byName map[string]*issuance.Profile
}

// certificateAuthorityImpl represents a CA that signs certificates.
// It can sign OCSP responses as well, but only via delegation to an ocspImpl.
Expand All @@ -63,7 +68,7 @@ type certificateAuthorityImpl struct {
sa sapb.StorageAuthorityCertificateClient
pa core.PolicyAuthority
issuers issuerMaps
certProfiles certProfilesMap
certProfiles certProfilesMaps

// This is temporary, and will be used for testing and slow roll-out
// of ECDSA issuance, but will then be removed.
Expand Down Expand Up @@ -100,24 +105,48 @@ func makeIssuerMaps(issuers []*issuance.Issuer) issuerMaps {
return issuerMaps{issuersByAlg, issuersByNameID}
}

// makeCertificateProfilesMap aggregates multiple certificate issuance profiles
// and maps the human-readable name to the profile. It returns the map or an
// error if a duplicate profile name is found.
func makeCertificateProfilesMap(origProfile *issuance.Profile, certProfiles []*issuance.Profile) (certProfilesMap, error) {
// makeCertificateProfilesMap processes a list of certificate issuance profiles
// into a set of maps, mapping a human-readable name to the profile and a unique
// hash over the profile to the profile itself. It returns the maps or an error
// if a duplicate name or hash is found.
//
// The unique hash is useful in the case of
// - RA instructs CA1 to issue a precertificate
// - CA1 returns the precertificate DER bytes to the RA
// - RA instructs CA2 to issue a final certificate, but CA2 does not contain a
// profile corresponding to that hash and an issuance is prevented.
func makeCertificateProfilesMap(origProfile *issuance.Profile, certProfiles []*issuance.Profile) (certProfilesMaps, error) {
var allProfiles []*issuance.Profile
allProfiles = append(allProfiles, origProfile)
allProfiles = append(allProfiles, certProfiles...)

profilesByName := make(map[string]*issuance.Profile, len(allProfiles))
profilesByHash := make(map[[32]byte]*issuance.Profile, len(allProfiles))

var encodedProfile bytes.Buffer
for _, profile := range allProfiles {
if profilesByName[profile.Name] == nil {
profilesByName[profile.Name] = profile
} else {
return nil, fmt.Errorf("duplicate certificate profile name %+v", profile)
return certProfilesMaps{}, fmt.Errorf("duplicate certificate profile name %+v", profile)
}

// We encode each profile into a byte slice then create a unique hash over those bytes.
enc := gob.NewEncoder(&encodedProfile)
err := enc.Encode(allProfiles)
if err != nil {
return certProfilesMaps{}, err
}
hash := sha256.Sum256(encodedProfile.Bytes())

if profilesByHash[hash] == nil {
profilesByHash[hash] = profile
} else {
return certProfilesMaps{}, fmt.Errorf("duplicate certificate profile hash %+v", profile)
}
}

return profilesByName, nil
return certProfilesMaps{profilesByHash, profilesByName}, nil
}

// NewCertificateAuthorityImpl creates a CA instance that can sign certificates
Expand Down Expand Up @@ -199,12 +228,26 @@ func NewCertificateAuthorityImpl(
return ca, nil
}

// certificateProfileExists checks if the given certificate profile exists in
// the CA's list of configured certificate profiles or returns an error.
func (ca *certificateAuthorityImpl) certificateProfileExists(profile string) error {
_, ok := ca.certProfiles[profile]
// certificateProfileNameExists checks if the given certificate profile name
// exists in the CA's list of configured certificate profiles or returns an
// error. The name is guaranteed to be unique, but the profile values may differ
// between CA nodes and should not be solely relied upon.
func (ca *certificateAuthorityImpl) certificateProfileNameExists(profile string) error {
_, ok := ca.certProfiles.byName[profile]
if !ok {
return fmt.Errorf("the CA is not capable of using a profile named %s", profile)
}

return nil
}

// certificateProfileHashExists checks if the given certificate profile hash
// exists in the CA's list of configured certificate profiles or returns an
// error. The hash over the whole profile is guaranteed to be unique.
func (ca *certificateAuthorityImpl) certificateProfileHashExists(hash [32]byte) error {
_, ok := ca.certProfiles.byHash[hash]
if !ok {
return fmt.Errorf("the CA is not capable of using profile %s", profile)
return fmt.Errorf("the CA is not capable of using a profile with hash %d", hash)
}

return nil
Expand Down Expand Up @@ -336,7 +379,7 @@ func (ca *certificateAuthorityImpl) IssueCertificateForPrecertificate(ctx contex
ca.log.AuditInfof("Signing cert: serial=[%s] regID=[%d] names=[%s] precert=[%s]",
serialHex, req.RegistrationID, names, hex.EncodeToString(precert.Raw))

_, issuanceToken, err := issuer.Prepare(ca.certProfiles["defaultCertificateProfileName"], issuanceReq)
_, issuanceToken, err := issuer.Prepare(ca.certProfiles.byName["defaultCertificateProfileName"], issuanceReq)
if err != nil {
ca.log.AuditErrf("Preparing cert failed: serial=[%s] regID=[%d] names=[%s] err=[%v]",
serialHex, req.RegistrationID, names, err)
Expand Down Expand Up @@ -491,7 +534,7 @@ func (ca *certificateAuthorityImpl) issuePrecertificateInner(ctx context.Context
NotAfter: validity.NotAfter,
}

lintCertBytes, issuanceToken, err := issuer.Prepare(ca.certProfiles["defaultCertificateProfileName"], req)
lintCertBytes, issuanceToken, err := issuer.Prepare(ca.certProfiles.byName["defaultCertificateProfileName"], 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)
Expand Down
46 changes: 24 additions & 22 deletions ca/ca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,35 +522,35 @@ func TestProfiles(t *testing.T) {
// existence of both profile and certProfiles without a duplicate name (the
// happy path).
testCases := []struct {
name string
profile *issuance.Profile
certProfiles []*issuance.Profile
expectErrSubstr string
expectProfileName string
name string
profile *issuance.Profile
certProfiles []*issuance.Profile
expectedErrSubstr string
expectedProfileName string
}{
{
name: "both empty",
profile: nil,
certProfiles: nil,
expectErrSubstr: "at least one certificate profile",
name: "both empty",
profile: nil,
certProfiles: nil,
expectedErrSubstr: "at least one certificate profile",
},
{
name: "no profile",
profile: nil,
certProfiles: ctx.certProfiles,
expectErrSubstr: "at least one certificate profile",
name: "no profile",
profile: nil,
certProfiles: ctx.certProfiles,
expectedErrSubstr: "at least one certificate profile",
},
{
name: "no certProfiles",
profile: ctx.profile,
certProfiles: nil,
expectProfileName: "defaultCertificateProfileName",
expectedErrSubstr: "defaultCertificateProfileName",
},
{
name: "both exist, but have a duplicate profile name",
profile: ctx.profile,
certProfiles: duplicateProfiles,
expectErrSubstr: "duplicate certificate profile name",
name: "both exist, but have a duplicate profile name",
profile: ctx.profile,
certProfiles: duplicateProfiles,
expectedErrSubstr: "duplicate certificate profile name",
},
}

Expand All @@ -573,15 +573,17 @@ func TestProfiles(t *testing.T) {
ctx.signatureCount,
ctx.signErrorCount,
ctx.fc)
if tc.expectErrSubstr != "" {
if !strings.Contains(err.Error(), tc.expectErrSubstr) {
t.Errorf("expected error to contain %q, got %q", tc.expectErrSubstr, err.Error())
if tc.expectedErrSubstr != "" {
if !strings.Contains(err.Error(), tc.expectedErrSubstr) {
t.Errorf("expected error to contain %q, got %q", tc.expectedErrSubstr, err.Error())
}
test.AssertError(t, err, "No profile found during CA construction.")
} else {
test.AssertNotError(t, err, "Profiles should exist, but were not found")
err = tCA.certificateProfileExists(tc.expectProfileName)
err = tCA.certificateProfileNameExists(tc.expectedProfileName)
test.AssertNotError(t, err, "Profile name not found in map")
err = tCA.certificateProfileHashExists(tc.expectedProfileHash)
test.AssertNotError(t, err, "Profile hash not found in map")
}
})
}
Expand Down
8 changes: 7 additions & 1 deletion issuance/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ type Profile struct {
lints lint.Registry
}

const defaultCertProfileName = "boulderDefaultCertificateProfile"

// NewProfile synthesizes the profile config and issuer config into a single
// object, and checks various aspects for correctness.
func NewProfile(profileConfig ProfileConfig, skipLints []string) (*Profile, error) {
Expand All @@ -68,8 +70,12 @@ func NewProfile(profileConfig ProfileConfig, skipLints []string) (*Profile, erro
return nil, fmt.Errorf("creating lint registry: %w", err)
}

if profileConfig.Name == "" {
profileConfig.Name = defaultCertProfileName
}

sp := &Profile{
Name: "defaultCertificateProfileName",
Name: profileConfig.Name,
allowMustStaple: profileConfig.AllowMustStaple,
allowCTPoison: profileConfig.AllowCTPoison,
allowSCTList: profileConfig.AllowSCTList,
Expand Down

0 comments on commit e625842

Please sign in to comment.