Skip to content

Commit

Permalink
Use RFC 7093 truncated SHA256 hash for Subject Key Identifier (#7179)
Browse files Browse the repository at this point in the history
- Adds a feature flag to gate rollout for SHA256 Subject Key Identifiers
for end-entity certificates.
- The ceremony tool will now use the RFC 7093 section 2 option 1 method
for generating Subject Key Identifiers for future root CA, intermediate
CA, and cross-sign ceremonies.

- - - -

[RFC 7093 section 2 option
1](https://datatracker.ietf.org/doc/html/rfc7093#section-2) provides a
method for generating a truncated SHA256 hash for the Subject Key
Identifier field in accordance with Baseline Requirement [section
7.1.2.11.4 Subject Key
Identifier](https://github.com/cabforum/servercert/blob/90a98dc7c1131eaab01af411968aa7330d315b9b/docs/BR.md#712114-subject-key-identifier).

> [RFC5280] specifies two examples for generating key identifiers from
>    public keys.  Four additional mechanisms are as follows:
> 
>    1) The keyIdentifier is composed of the leftmost 160-bits of the
>       SHA-256 hash of the value of the BIT STRING subjectPublicKey
>       (excluding the tag, length, and number of unused bits).

The related [RFC 5280 section
4.2.1.2](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.2)
states:
>   For CA certificates, subject key identifiers SHOULD be derived from
>   the public key or a method that generates unique values.  Two common
>   methods for generating key identifiers from the public key are:
>   ...
>   Other methods of generating unique numbers are also acceptable.
  • Loading branch information
pgporada authored Dec 6, 2023
1 parent c45bfb8 commit 3366be5
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 7 deletions.
7 changes: 6 additions & 1 deletion cmd/ceremony/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,13 @@ func generateSKID(pk []byte) ([]byte, error) {
if _, err := asn1.Unmarshal(pk, &pkixPublicKey); err != nil {
return nil, err
}

// RFC 7093 Section 2 Additional Methods for Generating Key Identifiers: The
// keyIdentifier [may be] composed of the leftmost 160-bits of the SHA-256
// hash of the value of the BIT STRING subjectPublicKey (excluding the tag,
// length, and number of unused bits).
skid := sha256.Sum256(pkixPublicKey.BitString.Bytes)
return skid[:], nil
return skid[0:20:20], nil
}

// makeTemplate generates the certificate template for use in x509.CreateCertificate
Expand Down
7 changes: 7 additions & 0 deletions cmd/ceremony/cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,3 +579,10 @@ func TestLoadCert(t *testing.T) {
_, err = loadCert("../../test/test-root.pubkey.pem")
test.AssertError(t, err, "should have failed when trying to parse a public key")
}

func TestGenerateSKID(t *testing.T) {
sha256skid, err := generateSKID(samplePubkey())
test.AssertNotError(t, err, "Error generating SKID")
test.AssertEquals(t, len(sha256skid), 20)
test.AssertEquals(t, cap(sha256skid), 20)
}
5 changes: 3 additions & 2 deletions features/featureflag_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions features/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ const (
// successful validations slower by serializing the DCV and CAA work, but
// makes unsuccessful validations easier by not doing CAA work at all.
CAAAfterValidation

// SHA256SubjectKeyIdentifier enables the generation and use of an RFC 7093
// compliant truncated SHA256 Subject Key Identifier in end-entity
// certificates.
SHA256SubjectKeyIdentifier
)

// List of features and their default value, protected by fMu
Expand All @@ -104,6 +109,7 @@ var features = map[FeatureFlag]bool{
AllowNoCommonName: false,
LeaseCRLShards: false,
CAAAfterValidation: false,
SHA256SubjectKeyIdentifier: false,

StoreLintingCertificateInsteadOfPrecertificate: false,
}
Expand Down
16 changes: 14 additions & 2 deletions issuance/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
Expand All @@ -21,6 +22,7 @@ import (
ctx509 "github.com/google/certificate-transparency-go/x509"
"github.com/jmhodges/clock"

"github.com/letsencrypt/boulder/features"
"github.com/letsencrypt/boulder/precert"
)

Expand Down Expand Up @@ -211,8 +213,18 @@ func generateSKID(pk crypto.PublicKey) ([]byte, error) {
if _, err := asn1.Unmarshal(pkBytes, &pkixPublicKey); err != nil {
return nil, err
}
skid := sha1.Sum(pkixPublicKey.BitString.Bytes)
return skid[:], nil

if features.Enabled(features.SHA256SubjectKeyIdentifier) {
// RFC 7093 Section 2 Additional Methods for Generating Key Identifiers:
// The keyIdentifier [may be] composed of the leftmost 160-bits of the
// SHA-256 hash of the value of the BIT STRING subjectPublicKey
// (excluding the tag, length, and number of unused bits).
skid := sha256.Sum256(pkixPublicKey.BitString.Bytes)
return skid[0:20:20], nil
} else {
skid := sha1.Sum(pkixPublicKey.BitString.Bytes)
return skid[:], nil
}
}

// IssuanceRequest describes a certificate issuance request
Expand Down
23 changes: 23 additions & 0 deletions issuance/cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/jmhodges/clock"

"github.com/letsencrypt/boulder/ctpolicy/loglist"
"github.com/letsencrypt/boulder/features"
"github.com/letsencrypt/boulder/linter"
"github.com/letsencrypt/boulder/test"
)
Expand Down Expand Up @@ -764,3 +765,25 @@ func TestMismatchedProfiles(t *testing.T) {
test.AssertError(t, err, "preparing final cert issuance")
test.AssertContains(t, err.Error(), "precert does not correspond to linted final cert")
}

func TestGenerateSKID(t *testing.T) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
test.AssertNotError(t, err, "Error generating key")

_ = features.Set(map[string]bool{"SHA256SubjectKeyIdentifier": true})
defer features.Reset()
// RFC 7093 section 2 method 1 allows us to use 160 of the leftmost bits for
// the Subject Key Identifier. This is the same amount of bits as the
// related SHA1 hash.
sha256skid, err := generateSKID(key.Public())
test.AssertNotError(t, err, "Error generating SKID")
test.AssertEquals(t, len(sha256skid), 20)
test.AssertEquals(t, cap(sha256skid), 20)
features.Reset()

_ = features.Set(map[string]bool{"SHA256SubjectKeyIdentifier": false})
sha1skid, err := generateSKID(key.Public())
test.AssertNotError(t, err, "Error generating SKID")
test.AssertEquals(t, len(sha1skid), 20)
test.AssertEquals(t, cap(sha1skid), 20)
}
3 changes: 2 additions & 1 deletion test/config-next/ca-a.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@
"ctLogListFile": "test/ct-test-srv/log_list.json",
"features": {
"ECDSAForAll": true,
"AllowNoCommonName": true
"AllowNoCommonName": true,
"SHA256SubjectKeyIdentifier": true
}
},
"pa": {
Expand Down
3 changes: 2 additions & 1 deletion test/config-next/ca-b.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@
"ctLogListFile": "test/ct-test-srv/log_list.json",
"features": {
"ECDSAForAll": true,
"AllowNoCommonName": true
"AllowNoCommonName": true,
"SHA256SubjectKeyIdentifier": true
}
},
"pa": {
Expand Down

0 comments on commit 3366be5

Please sign in to comment.