x509: generate serial number for nil template SerialNumber #279

This commit is contained in:
Sun Yimin 2024-11-25 08:41:30 +08:00 committed by GitHub
parent e2c430a0ff
commit 9fd122e614
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 65 additions and 12 deletions

View File

@ -25,6 +25,7 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/ed25519" "crypto/ed25519"
"crypto/elliptic" "crypto/elliptic"
cryptorand "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha1" "crypto/sha1"
"crypto/x509" "crypto/x509"
@ -1422,6 +1423,10 @@ var emptyASN1Subject = []byte{0x30, 0}
// //
// If SubjectKeyId from template is empty and the template is a CA, SubjectKeyId // If SubjectKeyId from template is empty and the template is a CA, SubjectKeyId
// will be generated from the hash of the public key. // will be generated from the hash of the public key.
//
// If template.SerialNumber is nil, a serial number will be generated which
// conforms to RFC 5280, Section 4.1.2.2 using entropy from rand.
//
func CreateCertificate(rand io.Reader, template, parent, pub, priv any) ([]byte, error) { func CreateCertificate(rand io.Reader, template, parent, pub, priv any) ([]byte, error) {
realTemplate, err := toCertificate(template) realTemplate, err := toCertificate(template)
if err != nil { if err != nil {
@ -1438,8 +1443,27 @@ func CreateCertificate(rand io.Reader, template, parent, pub, priv any) ([]byte,
return nil, errors.New("x509: certificate private key does not implement crypto.Signer") return nil, errors.New("x509: certificate private key does not implement crypto.Signer")
} }
if realTemplate.SerialNumber == nil { serialNumber := realTemplate.SerialNumber
return nil, errors.New("x509: no SerialNumber given") if serialNumber == nil {
// Generate a serial number following RFC 5280 Section 4.1.2.2 if one is not provided.
// Requirements:
// - serial number must be positive
// - at most 20 octets when encoded
maxSerial := big.NewInt(1).Lsh(big.NewInt(1), 20*8)
for {
var err error
serialNumber, err = cryptorand.Int(rand, maxSerial)
if err != nil {
return nil, err
}
// If the serial is exactly 20 octets, check if the high bit of the first byte is set.
// If so, generate a new serial, since it will be padded with a leading 0 byte during
// encoding so that the serial is not interpreted as a negative integer, making it
// 21 octets.
if serialBytes := serialNumber.Bytes(); len(serialBytes) > 0 && (len(serialBytes) < 20 || serialBytes[0]&0x80 == 0) {
break
}
}
} }
// RFC 5280 Section 4.1.2.2: serial number must positive // RFC 5280 Section 4.1.2.2: serial number must positive
@ -1447,7 +1471,7 @@ func CreateCertificate(rand io.Reader, template, parent, pub, priv any) ([]byte,
// We _should_ also restrict serials to <= 20 octets, but it turns out a lot of people // We _should_ also restrict serials to <= 20 octets, but it turns out a lot of people
// get this wrong, in part because the encoding can itself alter the length of the // get this wrong, in part because the encoding can itself alter the length of the
// serial. For now we accept these non-conformant serials. // serial. For now we accept these non-conformant serials.
if realTemplate.SerialNumber.Sign() == -1 { if serialNumber.Sign() == -1 {
return nil, errors.New("x509: serial number must be positive") return nil, errors.New("x509: serial number must be positive")
} }
@ -1513,7 +1537,7 @@ func CreateCertificate(rand io.Reader, template, parent, pub, priv any) ([]byte,
encodedPublicKey := asn1.BitString{BitLength: len(publicKeyBytes) * 8, Bytes: publicKeyBytes} encodedPublicKey := asn1.BitString{BitLength: len(publicKeyBytes) * 8, Bytes: publicKeyBytes}
c := tbsCertificate{ c := tbsCertificate{
Version: 2, Version: 2,
SerialNumber: realTemplate.SerialNumber, SerialNumber: serialNumber,
SignatureAlgorithm: algorithmIdentifier, SignatureAlgorithm: algorithmIdentifier,
Issuer: asn1.RawValue{FullBytes: asn1Issuer}, Issuer: asn1.RawValue{FullBytes: asn1Issuer},
Validity: validity{realTemplate.NotBefore.UTC(), realTemplate.NotAfter.UTC()}, Validity: validity{realTemplate.NotBefore.UTC(), realTemplate.NotAfter.UTC()},

View File

@ -1626,16 +1626,18 @@ func TestInsecureAlgorithmErrorString(t *testing.T) {
*/ */
// These CSR was generated with OpenSSL: // These CSR was generated with OpenSSL:
// openssl req -out CSR.csr -new -sha256 -nodes -keyout privateKey.key -config openssl.cnf //
// openssl req -out CSR.csr -new -sha256 -nodes -keyout privateKey.key -config openssl.cnf
// //
// With openssl.cnf containing the following sections: // With openssl.cnf containing the following sections:
// [ v3_req ] //
// basicConstraints = CA:FALSE // [ v3_req ]
// keyUsage = nonRepudiation, digitalSignature, keyEncipherment // basicConstraints = CA:FALSE
// subjectAltName = email:gopher@golang.org,DNS:test.example.com // keyUsage = nonRepudiation, digitalSignature, keyEncipherment
// [ req_attributes ] // subjectAltName = email:gopher@golang.org,DNS:test.example.com
// challengePassword = ignored challenge // [ req_attributes ]
// unstructuredName = ignored unstructured name // challengePassword = ignored challenge
// unstructuredName = ignored unstructured name
var csrBase64Array = [...]string{ var csrBase64Array = [...]string{
// Just [ v3_req ] // Just [ v3_req ]
"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",
@ -2126,6 +2128,33 @@ func TestAdditionFieldsInGeneralSubtree(t *testing.T) {
} }
} }
func TestEmptySerialNumber(t *testing.T) {
template := Certificate{
DNSNames: []string{"example.com"},
}
for i := 0; i < 100; i++ {
derBytes, err := CreateCertificate(rand.Reader, &template, &template, &testPrivateKey.PublicKey, testPrivateKey)
if err != nil {
t.Fatalf("failed to create certificate: %s", err)
}
cert, err := ParseCertificate(derBytes)
if err != nil {
t.Fatalf("failed to parse certificate: %s", err)
}
if sign := cert.SerialNumber.Sign(); sign != 1 {
t.Fatalf("generated a non positive serial, sign: %d", sign)
}
b, err := asn1.Marshal(cert.SerialNumber)
if err != nil {
t.Fatalf("failed to marshal generated serial number: %s", err)
}
// subtract 2 for tag and length
if l := len(b) - 2; l > 20 {
t.Fatalf("generated serial number larger than 20 octets when encoded: %d", l)
}
}
}
func TestEmptySubject(t *testing.T) { func TestEmptySubject(t *testing.T) {
template := x509.Certificate{ template := x509.Certificate{
SerialNumber: big.NewInt(1), SerialNumber: big.NewInt(1),