Skip to content

Commit

Permalink
Introduce SerialPrefixHex field in CA (#7721)
Browse files Browse the repository at this point in the history
Add a new SerialPrefixHex field to the CA's config, which takes a
two-character hexadecimal string to use as the serial prefix. This
matches the way that the OCSP Responder's acceptable serial prefixes are
configured, and is easier for human operators to configure than raw
integers.

At the same time, change the type of the CA's internal serial prefix
from `int` to `byte`, using the type system to enforce its 8-bit length.

Fixes #7213
  • Loading branch information
jprenken authored Oct 4, 2024
1 parent a731497 commit beddae5
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 14 deletions.
11 changes: 6 additions & 5 deletions ca/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ type certificateAuthorityImpl struct {
issuers issuerMaps
certProfiles certProfilesMaps

prefix int // Prepended to the serial number
// The prefix is prepended to the serial number.
prefix byte
maxNames int
keyPolicy goodkey.KeyPolicy
clk clock.Clock
Expand Down Expand Up @@ -238,7 +239,7 @@ func NewCertificateAuthorityImpl(
boulderIssuers []*issuance.Issuer,
defaultCertProfileName string,
certificateProfiles map[string]*issuance.ProfileConfig,
serialPrefix int,
serialPrefix byte,
maxNames int,
keyPolicy goodkey.KeyPolicy,
logger blog.Logger,
Expand All @@ -248,8 +249,8 @@ func NewCertificateAuthorityImpl(
var ca *certificateAuthorityImpl
var err error

if serialPrefix < 1 || serialPrefix > 127 {
err = errors.New("serial prefix must be between 1 and 127")
if serialPrefix < 0x01 || serialPrefix > 0x7f {
err = errors.New("serial prefix must be between 0x01 (1) and 0x7f (127)")
return nil, err
}

Expand Down Expand Up @@ -491,7 +492,7 @@ func (ca *certificateAuthorityImpl) generateSerialNumber() (*big.Int, error) {
// We want 136 bits of random number, plus an 8-bit instance id prefix.
const randBits = 136
serialBytes := make([]byte, randBits/8+1)
serialBytes[0] = byte(ca.prefix)
serialBytes[0] = ca.prefix
_, err := rand.Read(serialBytes[1:])
if err != nil {
err = berrors.InternalServerError("failed to generate serial: %s", err)
Expand Down
8 changes: 4 additions & 4 deletions ca/ca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ type testCtx struct {
crl *crlImpl
defaultCertProfileName string
certProfiles map[string]*issuance.ProfileConfig
serialPrefix int
serialPrefix byte
maxNames int
boulderIssuers []*issuance.Issuer
keyPolicy goodkey.KeyPolicy
Expand Down Expand Up @@ -237,7 +237,7 @@ func setup(t *testing.T) *testCtx {
crl: crl,
defaultCertProfileName: "legacy",
certProfiles: certProfiles,
serialPrefix: 17,
serialPrefix: 0x11,
maxNames: 2,
boulderIssuers: boulderIssuers,
keyPolicy: keyPolicy,
Expand All @@ -257,7 +257,7 @@ func TestSerialPrefix(t *testing.T) {
nil,
"",
nil,
0,
0x00,
testCtx.maxNames,
testCtx.keyPolicy,
testCtx.logger,
Expand All @@ -271,7 +271,7 @@ func TestSerialPrefix(t *testing.T) {
nil,
"",
nil,
128,
0x80,
testCtx.maxNames,
testCtx.keyPolicy,
testCtx.logger,
Expand Down
27 changes: 23 additions & 4 deletions cmd/boulder-ca/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"flag"
"os"
"reflect"
"strconv"
"time"

"github.com/letsencrypt/boulder/ca"
Expand Down Expand Up @@ -61,15 +62,26 @@ type Config struct {
}

// How long issued certificates are valid for.
// Deprecated: Use Issuace.CertProfiles.MaxValidityPeriod instead.
// Deprecated: Use Issuance.CertProfiles.MaxValidityPeriod instead.
Expiry config.Duration

// How far back certificates should be backdated.
// Deprecated: Use Issuace.CertProfiles.MaxValidityBackdate instead.
// Deprecated: Use Issuance.CertProfiles.MaxValidityBackdate instead.
Backdate config.Duration

// What digits we should prepend to serials after randomly generating them.
SerialPrefix int `validate:"required,min=1,max=127"`
// Deprecated: Use SerialPrefixHex instead.
SerialPrefix int `validate:"required_without=SerialPrefixHex,omitempty,min=1,max=127"`

// SerialPrefixHex is the hex string to prepend to serials after randomly
// generating them. The minimum value is "01" to ensure that at least
// one bit in the prefix byte is set. The maximum value is "7f" to
// ensure that the first bit in the prefix byte is not set. The validate
// library cannot enforce mix/max values on strings, so that is done in
// NewCertificateAuthorityImpl.
//
// TODO(#7213): Replace `required_without` with `required` when SerialPrefix is removed.
SerialPrefixHex string `validate:"required_without=SerialPrefix,omitempty,hexadecimal,len=2"`

// MaxNames is the maximum number of subjectAltNames in a single cert.
// The value supplied MUST be greater than 0 and no more than 100. These
Expand Down Expand Up @@ -152,6 +164,13 @@ func main() {
c.CA.DebugAddr = *debugAddr
}

serialPrefix := byte(c.CA.SerialPrefix)
if c.CA.SerialPrefixHex != "" {
parsedSerialPrefix, err := strconv.ParseUint(c.CA.SerialPrefixHex, 16, 8)
cmd.FailOnError(err, "Couldn't convert SerialPrefixHex to int")
serialPrefix = byte(parsedSerialPrefix)
}

if c.CA.MaxNames == 0 {
cmd.Fail("Error in CA config: MaxNames must not be 0")
}
Expand Down Expand Up @@ -280,7 +299,7 @@ func main() {
issuers,
c.CA.Issuance.DefaultCertificateProfileName,
c.CA.Issuance.CertProfiles,
c.CA.SerialPrefix,
serialPrefix,
c.CA.MaxNames,
kp,
logger,
Expand Down
2 changes: 1 addition & 1 deletion test/config-next/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@
}
]
},
"serialPrefix": 127,
"serialPrefixHex": "7f",
"maxNames": 100,
"lifespanOCSP": "96h",
"goodkey": {
Expand Down

0 comments on commit beddae5

Please sign in to comment.