Skip to content

Commit

Permalink
Restrict ECDSA/NIST P-Curve hash function sizes for cert signing (#12872
Browse files Browse the repository at this point in the history
)

* Restrict ECDSA signatures with NIST P-Curve hashes

When using an ECDSA signature with a NIST P-Curve, we should follow
recommendations from BIS (Section 4.2) and Mozilla's root store policy
(section 5.1.2) to ensure that arbitrary selection of signature_bits
does not exceed what the curve is capable of signing.

Related: #11245

Signed-off-by: Alexander Scheel <[email protected]>

* Switch to certutil.ValidateKeyTypeSignatureLength(...)

Replaces previous calls to certutil.ValidateKeyTypeLength(...) and
certutil.ValidateSignatureLength(...) with a single call, allowing for
curve<->hash validation.

Signed-off-by: Alexander Scheel <[email protected]>

* Switch to autodetection of signature_bits

This enables detection of whether the caller manually specified a value
for signature_bits or not; when not manually specified, we can provision
a value that complies with new NIST P-Curve policy.

Signed-off-by: Alexander Scheel <[email protected]>

* Select hash function length automatically

Due to our change in behavior (to default to -1 as the value to
signature_bits to allow for automatic hash selection), switch
ValidateKeyTypeSignatureLength(...) to accept a pointer to hashBits and
provision it with valid default values.

Signed-off-by: Alexander Scheel <[email protected]>

* Prevent invalid Curve size lookups

Signed-off-by: Alexander Scheel <[email protected]>

* Switch from -1 to 0 as default SignatureBits

Signed-off-by: Alexander Scheel <[email protected]>

* Add changelog entry

Signed-off-by: Alexander Scheel <[email protected]>
  • Loading branch information
cipherboy authored Nov 12, 2021
1 parent 6725a37 commit c36f611
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 29 deletions.
1 change: 0 additions & 1 deletion builtin/logical/pki/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,6 @@ func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep {
KeyType: "rsa",
KeyBits: 2048,
RequireCN: true,
SignatureBits: 256,
}
issueVals := certutil.IssueData{}
ret := []logicaltest.TestStep{}
Expand Down
7 changes: 1 addition & 6 deletions builtin/logical/pki/ca_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,9 @@ func (b *backend) getGenerationParams(
return
}

if err := certutil.ValidateKeyTypeLength(role.KeyType, role.KeyBits); err != nil {
if err := certutil.ValidateKeyTypeSignatureLength(role.KeyType, role.KeyBits, &role.SignatureBits); err != nil {
errorResp = logical.ErrorResponse(err.Error())
}

if err := certutil.ValidateSignatureLength(role.SignatureBits); err != nil {
errorResp = logical.ErrorResponse(err.Error())
return
}

return
}
10 changes: 5 additions & 5 deletions builtin/logical/pki/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,13 +261,13 @@ the key_type.`,

fields["signature_bits"] = &framework.FieldSchema{
Type: framework.TypeInt,
Default: 256,
Default: 0,
Description: `The number of bits to use in the signature
algorithm. Defaults to 256 for SHA256.
Set to 384 for SHA384 and 512 for SHA512.
`,
algorithm; accepts 256 for SHA-2-256, 384 for SHA-2-384, and 512 for
SHA-2-512. Defaults to 0 to automatically detect based on key length
(SHA-2-256 for RSA keys, and matching the curve size for NIST P-Curves).`,
DisplayAttrs: &framework.DisplayAttributes{
Value: 256,
Value: 0,
},
}

Expand Down
13 changes: 5 additions & 8 deletions builtin/logical/pki/path_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,11 @@ the key_type.`,

"signature_bits": &framework.FieldSchema{
Type: framework.TypeInt,
Default: 256,
Default: 0,
Description: `The number of bits to use in the signature
algorithm. Defaults to 256 for SHA256.
Set to 384 for SHA384 and 512 for SHA512.`,
algorithm; accepts 256 for SHA-2-256, 384 for SHA-2-384, and 512 for
SHA-2-512. Defaults to 0 to automatically detect based on key length
(SHA-2-256 for RSA keys, and matching the curve size for NIST P-Curves).`,
},

"key_usage": &framework.FieldSchema{
Expand Down Expand Up @@ -624,11 +625,7 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data
), nil
}

if err := certutil.ValidateKeyTypeLength(entry.KeyType, entry.KeyBits); err != nil {
return logical.ErrorResponse(err.Error()), nil
}

if err := certutil.ValidateSignatureLength(entry.SignatureBits); err != nil {
if err := certutil.ValidateKeyTypeSignatureLength(entry.KeyType, entry.KeyBits, &entry.SignatureBits); err != nil {
return logical.ErrorResponse(err.Error()), nil
}

Expand Down
3 changes: 3 additions & 0 deletions changelog/12872.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
secrets/pki: Fixes around NIST P-curve signature hash length, default value for signature_bits changed to 0.
```
64 changes: 55 additions & 9 deletions sdk/helper/certutil/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ import (
cbasn1 "golang.org/x/crypto/cryptobyte/asn1"
)

// Mapping of NIST P-Curve's key length to expected signature bits.
var expectedNISTPCurveHashBits = map[int]int{
224: 256,
256: 256,
384: 384,
521: 512,
}

// GetHexFormatted returns the byte buffer formatted in hex with
// the specified separator between bytes.
func GetHexFormatted(buf []byte, sep string) string {
Expand Down Expand Up @@ -525,13 +533,55 @@ func StringToOid(in string) (asn1.ObjectIdentifier, error) {
return asn1.ObjectIdentifier(ret), nil
}

func ValidateSignatureLength(keyBits int) error {
switch keyBits {
// Validates that the combination of keyType, keyBits, and hashBits are
// valid together; replaces individual calls to ValidateSignatureLength and
// ValidateKeyTypeLength.
func ValidateKeyTypeSignatureLength(keyType string, keyBits int, hashBits *int) error {
if err := ValidateKeyTypeLength(keyType, keyBits); err != nil {
return err
}

if keyType == "ec" {
// To comply with BSI recommendations Section 4.2 and Mozilla root
// store policy section 5.1.2, enforce that NIST P-curves use a hash
// length corresponding to curve length. Note that ed25519 does not
// the "ec" key type.
expectedHashBits := expectedNISTPCurveHashBits[keyBits]

if expectedHashBits != *hashBits && *hashBits != 0 {
return fmt.Errorf("unsupported signature hash algorithm length (%d) for NIST P-%d", *hashBits, keyBits)
} else if *hashBits == 0 {
*hashBits = expectedHashBits
}
} else if keyType == "rsa" && *hashBits == 0 {
// To match previous behavior (and ignoring recommendations of hash
// size to match RSA key sizes), default to SHA-2-256.
*hashBits = 256
} /* else if keyType == "ed25519" {
// No-op; ed25519 and ed448 internally specify their own hash and
// we do not need to select one. Double hashing isn't supported in
// certificate signing.
} */

// Note that this check must come after we've selected a value for
// hashBits above, in the event it was left as the default, but we
// were allowed to update it.
if err := ValidateSignatureLength(*hashBits); err != nil || *hashBits == 0 {
return err
}

return nil
}

// Validates that the length of the hash (in bits) used in the signature
// calculation is a known, approved value.
func ValidateSignatureLength(hashBits int) error {
switch hashBits {
case 256:
case 384:
case 512:
default:
return fmt.Errorf("unsupported signature algorithm: %d", keyBits)
return fmt.Errorf("unsupported hash signature algorithm: %d", hashBits)
}
return nil
}
Expand All @@ -548,12 +598,8 @@ func ValidateKeyTypeLength(keyType string, keyBits int) error {
return fmt.Errorf("unsupported bit length for RSA key: %d", keyBits)
}
case "ec":
switch keyBits {
case 224:
case 256:
case 384:
case 521:
default:
_, present := expectedNISTPCurveHashBits[keyBits]
if !present {
return fmt.Errorf("unsupported bit length for EC key: %d", keyBits)
}
case "any", "ed25519":
Expand Down

0 comments on commit c36f611

Please sign in to comment.