Use RFC 7093 truncated SHA256 hash for Subject Key Identifier (#7179)

- 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](90a98dc7c1/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.
This commit is contained in:
Phil Porada 2023-12-06 13:44:17 -05:00 committed by GitHub
parent c45bfb8aed
commit 3366be50f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 63 additions and 7 deletions

View File

@ -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

View File

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

View File

@ -28,11 +28,12 @@ func _() {
_ = x[AsyncFinalize-17]
_ = x[AllowNoCommonName-18]
_ = x[CAAAfterValidation-19]
_ = x[SHA256SubjectKeyIdentifier-20]
}
const _FeatureFlag_name = "unusedStoreRevokerInfoROCSPStage6ROCSPStage7StoreLintingCertificateInsteadOfPrecertificateCAAValidationMethodsCAAAccountURILeaseCRLShardsEnforceMultiVAMultiVAFullResultsECDSAForAllServeRenewalInfoAllowUnrecognizedFeaturesExpirationMailerUsesJoinCertCheckerChecksValidationsCertCheckerRequiresValidationsCertCheckerRequiresCorrespondenceAsyncFinalizeAllowNoCommonNameCAAAfterValidation"
const _FeatureFlag_name = "unusedStoreRevokerInfoROCSPStage6ROCSPStage7StoreLintingCertificateInsteadOfPrecertificateCAAValidationMethodsCAAAccountURILeaseCRLShardsEnforceMultiVAMultiVAFullResultsECDSAForAllServeRenewalInfoAllowUnrecognizedFeaturesExpirationMailerUsesJoinCertCheckerChecksValidationsCertCheckerRequiresValidationsCertCheckerRequiresCorrespondenceAsyncFinalizeAllowNoCommonNameCAAAfterValidationSHA256SubjectKeyIdentifier"
var _FeatureFlag_index = [...]uint16{0, 6, 22, 33, 44, 90, 110, 123, 137, 151, 169, 180, 196, 221, 245, 273, 303, 336, 349, 366, 384}
var _FeatureFlag_index = [...]uint16{0, 6, 22, 33, 44, 90, 110, 123, 137, 151, 169, 180, 196, 221, 245, 273, 303, 336, 349, 366, 384, 410}
func (i FeatureFlag) String() string {
if i < 0 || i >= FeatureFlag(len(_FeatureFlag_index)-1) {

View File

@ -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
@ -104,6 +109,7 @@ var features = map[FeatureFlag]bool{
AllowNoCommonName: false,
LeaseCRLShards: false,
CAAAfterValidation: false,
SHA256SubjectKeyIdentifier: false,
StoreLintingCertificateInsteadOfPrecertificate: false,
}

View File

@ -7,6 +7,7 @@ import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
@ -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"
)
@ -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

View File

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

View File

@ -113,7 +113,8 @@
"ctLogListFile": "test/ct-test-srv/log_list.json",
"features": {
"ECDSAForAll": true,
"AllowNoCommonName": true
"AllowNoCommonName": true,
"SHA256SubjectKeyIdentifier": true
}
},
"pa": {

View File

@ -113,7 +113,8 @@
"ctLogListFile": "test/ct-test-srv/log_list.json",
"features": {
"ECDSAForAll": true,
"AllowNoCommonName": true
"AllowNoCommonName": true,
"SHA256SubjectKeyIdentifier": true
}
},
"pa": {