Introduce SerialPrefixHex field in CA (#7721)

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
This commit is contained in:
James Renken 2024-10-04 10:50:57 -07:00 committed by GitHub
parent a731497958
commit beddae5970
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 34 additions and 14 deletions

View File

@ -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
@ -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,
@ -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
}
@ -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)

View File

@ -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
@ -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,
@ -257,7 +257,7 @@ func TestSerialPrefix(t *testing.T) {
nil,
"",
nil,
0,
0x00,
testCtx.maxNames,
testCtx.keyPolicy,
testCtx.logger,
@ -271,7 +271,7 @@ func TestSerialPrefix(t *testing.T) {
nil,
"",
nil,
128,
0x80,
testCtx.maxNames,
testCtx.keyPolicy,
testCtx.logger,

View File

@ -5,6 +5,7 @@ import (
"flag"
"os"
"reflect"
"strconv"
"time"
"github.com/letsencrypt/boulder/ca"
@ -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
@ -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")
}
@ -280,7 +299,7 @@ func main() {
issuers,
c.CA.Issuance.DefaultCertificateProfileName,
c.CA.Issuance.CertProfiles,
c.CA.SerialPrefix,
serialPrefix,
c.CA.MaxNames,
kp,
logger,

View File

@ -142,7 +142,7 @@
}
]
},
"serialPrefix": 127,
"serialPrefixHex": "7f",
"maxNames": 100,
"lifespanOCSP": "96h",
"goodkey": {