Add support for PKCSv1_5_NoOID signatures (#17636)

* Add support for PKCSv1_5_NoOID signatures

This assumes a pre-hashed input has been provided to Vault, but we do
not write the hash's OID into the signature stream. This allows us to
generate the alternative PKCSv1_5_NoOID signature type rather than the
existing PKCSv1_5_DERnull signature type we presently use.

These are specified in RFC 3447 Section 9.2.

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add changelog

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Exclude new none type from PSS based tests

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add tests for PKCS#1v1.5 signatures

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
This commit is contained in:
Alexander Scheel 2022-10-27 08:26:20 -04:00 committed by GitHub
parent 1e189016e2
commit 1733d2a3d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 169 additions and 9 deletions

View File

@ -289,6 +289,35 @@ func testTransit_RSA(t *testing.T, keyType string) {
if !resp.Data["valid"].(bool) { if !resp.Data["valid"].(bool) {
t.Fatalf("failed to verify the RSA signature") t.Fatalf("failed to verify the RSA signature")
} }
// Take a random hash and sign it using PKCSv1_5_NoOID.
hash := "P8m2iUWdc4+MiKOkiqnjNUIBa3pAUuABqqU2/KdIE8s="
signReq.Data = map[string]interface{}{
"input": hash,
"hash_algorithm": "none",
"signature_algorithm": "pkcs1v15",
"prehashed": true,
}
resp, err = b.HandleRequest(context.Background(), signReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
}
signature = resp.Data["signature"].(string)
verifyReq.Data = map[string]interface{}{
"input": hash,
"signature": signature,
"hash_algorithm": "none",
"signature_algorithm": "pkcs1v15",
"prehashed": true,
}
resp, err = b.HandleRequest(context.Background(), verifyReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
}
if !resp.Data["valid"].(bool) {
t.Fatalf("failed to verify the RSA signature")
}
} }
func TestBackend_basic(t *testing.T) { func TestBackend_basic(t *testing.T) {

View File

@ -61,6 +61,8 @@ type batchResponseVerifyItem struct {
err error err error
} }
const defaultHashAlgorithm = "sha2-256"
func (b *backend) pathSign() *framework.Path { func (b *backend) pathSign() *framework.Path {
return &framework.Path{ return &framework.Path{
Pattern: "sign/" + framework.GenericNameRegex("name") + framework.OptionalParamRegex("urlalgorithm"), Pattern: "sign/" + framework.GenericNameRegex("name") + framework.OptionalParamRegex("urlalgorithm"),
@ -83,7 +85,7 @@ derivation is enabled; currently only available with ed25519 keys.`,
"hash_algorithm": { "hash_algorithm": {
Type: framework.TypeString, Type: framework.TypeString,
Default: "sha2-256", Default: defaultHashAlgorithm,
Description: `Hash algorithm to use (POST body parameter). Valid values are: Description: `Hash algorithm to use (POST body parameter). Valid values are:
* sha1 * sha1
@ -95,14 +97,17 @@ derivation is enabled; currently only available with ed25519 keys.`,
* sha3-256 * sha3-256
* sha3-384 * sha3-384
* sha3-512 * sha3-512
* none
Defaults to "sha2-256". Not valid for all key types, Defaults to "sha2-256". Not valid for all key types,
including ed25519.`, including ed25519. Using none requires setting prehashed=true and
signature_algorithm=pkcs1v15, yielding a PKCSv1_5_NoOID instead of
the usual PKCSv1_5_DERnull signature.`,
}, },
"algorithm": { "algorithm": {
Type: framework.TypeString, Type: framework.TypeString,
Default: "sha2-256", Default: defaultHashAlgorithm,
Description: `Deprecated: use "hash_algorithm" instead.`, Description: `Deprecated: use "hash_algorithm" instead.`,
}, },
@ -189,7 +194,7 @@ derivation is enabled; currently only available with ed25519 keys.`,
"hash_algorithm": { "hash_algorithm": {
Type: framework.TypeString, Type: framework.TypeString,
Default: "sha2-256", Default: defaultHashAlgorithm,
Description: `Hash algorithm to use (POST body parameter). Valid values are: Description: `Hash algorithm to use (POST body parameter). Valid values are:
* sha1 * sha1
@ -201,13 +206,15 @@ derivation is enabled; currently only available with ed25519 keys.`,
* sha3-256 * sha3-256
* sha3-384 * sha3-384
* sha3-512 * sha3-512
* none
Defaults to "sha2-256". Not valid for all key types.`, Defaults to "sha2-256". Not valid for all key types. See note about
none on signing path.`,
}, },
"algorithm": { "algorithm": {
Type: framework.TypeString, Type: framework.TypeString,
Default: "sha2-256", Default: defaultHashAlgorithm,
Description: `Deprecated: use "hash_algorithm" instead.`, Description: `Deprecated: use "hash_algorithm" instead.`,
}, },
@ -280,6 +287,9 @@ func (b *backend) pathSignWrite(ctx context.Context, req *logical.Request, d *fr
hashAlgorithmStr = d.Get("hash_algorithm").(string) hashAlgorithmStr = d.Get("hash_algorithm").(string)
if hashAlgorithmStr == "" { if hashAlgorithmStr == "" {
hashAlgorithmStr = d.Get("algorithm").(string) hashAlgorithmStr = d.Get("algorithm").(string)
if hashAlgorithmStr == "" {
hashAlgorithmStr = defaultHashAlgorithm
}
} }
} }
@ -301,6 +311,10 @@ func (b *backend) pathSignWrite(ctx context.Context, req *logical.Request, d *fr
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
} }
if hashAlgorithm == keysutil.HashTypeNone && (!prehashed || sigAlgorithm != "pkcs1v15") {
return logical.ErrorResponse("hash_algorithm=none requires both prehashed=true and signature_algorithm=pkcs1v15"), logical.ErrInvalidRequest
}
// Get the policy // Get the policy
p, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{ p, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{
Storage: req.Storage, Storage: req.Storage,
@ -507,6 +521,9 @@ func (b *backend) pathVerifyWrite(ctx context.Context, req *logical.Request, d *
hashAlgorithmStr = d.Get("hash_algorithm").(string) hashAlgorithmStr = d.Get("hash_algorithm").(string)
if hashAlgorithmStr == "" { if hashAlgorithmStr == "" {
hashAlgorithmStr = d.Get("algorithm").(string) hashAlgorithmStr = d.Get("algorithm").(string)
if hashAlgorithmStr == "" {
hashAlgorithmStr = defaultHashAlgorithm
}
} }
} }
@ -528,6 +545,10 @@ func (b *backend) pathVerifyWrite(ctx context.Context, req *logical.Request, d *
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
} }
if hashAlgorithm == keysutil.HashTypeNone && (!prehashed || sigAlgorithm != "pkcs1v15") {
return logical.ErrorResponse("hash_algorithm=none requires both prehashed=true and signature_algorithm=pkcs1v15"), logical.ErrInvalidRequest
}
// Get the policy // Get the policy
p, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{ p, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{
Storage: req.Storage, Storage: req.Storage,

View File

@ -154,7 +154,7 @@ func testTransit_SignVerify_ECDSA(t *testing.T, bits int) {
req.Path = "sign/foo" + postpath req.Path = "sign/foo" + postpath
resp, err := b.HandleRequest(context.Background(), req) resp, err := b.HandleRequest(context.Background(), req)
if err != nil && !errExpected { if err != nil && !errExpected {
t.Fatal(err) t.Fatalf("request: %v\nerror: %v", req, err)
} }
if resp == nil { if resp == nil {
t.Fatal("expected non-nil response") t.Fatal("expected non-nil response")
@ -950,6 +950,9 @@ func testTransit_SignVerify_RSA_PSS(t *testing.T, bits int) {
for hashAlgorithm := range keysutil.HashTypeMap { for hashAlgorithm := range keysutil.HashTypeMap {
t.Log("Hash algorithm:", hashAlgorithm) t.Log("Hash algorithm:", hashAlgorithm)
if hashAlgorithm == "none" {
continue
}
for marshalingName := range keysutil.MarshalingTypeMap { for marshalingName := range keysutil.MarshalingTypeMap {
t.Log("\t", "Marshaling type:", marshalingName) t.Log("\t", "Marshaling type:", marshalingName)

3
changelog/17636.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
secrets/transit: Add support for PKCSv1_5_NoOID RSA signatures
```

View File

@ -13,8 +13,8 @@ import (
type HashType uint32 type HashType uint32
const ( const (
_ = iota HashTypeNone HashType = iota
HashTypeSHA1 HashType = iota HashTypeSHA1
HashTypeSHA2224 HashTypeSHA2224
HashTypeSHA2256 HashTypeSHA2256
HashTypeSHA2384 HashTypeSHA2384
@ -35,6 +35,7 @@ const (
var ( var (
HashTypeMap = map[string]HashType{ HashTypeMap = map[string]HashType{
"none": HashTypeNone,
"sha1": HashTypeSHA1, "sha1": HashTypeSHA1,
"sha2-224": HashTypeSHA2224, "sha2-224": HashTypeSHA2224,
"sha2-256": HashTypeSHA2256, "sha2-256": HashTypeSHA2256,
@ -47,6 +48,7 @@ var (
} }
HashFuncMap = map[HashType]func() hash.Hash{ HashFuncMap = map[HashType]func() hash.Hash{
HashTypeNone: nil,
HashTypeSHA1: sha1.New, HashTypeSHA1: sha1.New,
HashTypeSHA2224: sha256.New224, HashTypeSHA2224: sha256.New224,
HashTypeSHA2256: sha256.New, HashTypeSHA2256: sha256.New,
@ -59,6 +61,7 @@ var (
} }
CryptoHashMap = map[HashType]crypto.Hash{ CryptoHashMap = map[HashType]crypto.Hash{
HashTypeNone: 0,
HashTypeSHA1: crypto.SHA1, HashTypeSHA1: crypto.SHA1,
HashTypeSHA2224: crypto.SHA224, HashTypeSHA2224: crypto.SHA224,
HashTypeSHA2256: crypto.SHA256, HashTypeSHA2256: crypto.SHA256,

View File

@ -953,6 +953,9 @@ func Test_RSA_PSS(t *testing.T) {
// 2. For each hash algorithm... // 2. For each hash algorithm...
for hashAlgorithm, hashType := range HashTypeMap { for hashAlgorithm, hashType := range HashTypeMap {
t.Log(tabs[1], "Hash algorithm:", hashAlgorithm) t.Log(tabs[1], "Hash algorithm:", hashAlgorithm)
if hashAlgorithm == "none" {
continue
}
// 3. For each marshaling type... // 3. For each marshaling type...
for marshalingName, marshalingType := range MarshalingTypeMap { for marshalingName, marshalingType := range MarshalingTypeMap {
@ -964,6 +967,92 @@ func Test_RSA_PSS(t *testing.T) {
} }
} }
func Test_RSA_PKCS1(t *testing.T) {
t.Log("Testing RSA PKCS#1v1.5")
ctx := context.Background()
storage := &logical.InmemStorage{}
// https://crypto.stackexchange.com/a/1222
input := []byte("Sphinx of black quartz, judge my vow")
sigAlgorithm := "pkcs1v15"
tabs := make(map[int]string)
for i := 1; i <= 6; i++ {
tabs[i] = strings.Repeat("\t", i)
}
test_RSA_PKCS1 := func(t *testing.T, p *Policy, rsaKey *rsa.PrivateKey, hashType HashType,
marshalingType MarshalingType,
) {
unsaltedOptions := SigningOptions{
HashAlgorithm: hashType,
Marshaling: marshalingType,
SigAlgorithm: sigAlgorithm,
}
cryptoHash := CryptoHashMap[hashType]
// PKCS#1v1.5 NoOID uses a direct input and assumes it is pre-hashed.
if hashType != 0 {
hash := cryptoHash.New()
hash.Write(input)
input = hash.Sum(nil)
}
// 1. Make a signature with the given key size and hash algorithm.
t.Log(tabs[3], "Make an automatic signature")
sig, err := p.Sign(0, nil, input, hashType, sigAlgorithm, marshalingType)
if err != nil {
// A bit of a hack but FIPS go does not support some hash types
if isUnsupportedGoHashType(hashType, err) {
t.Skip(tabs[4], "skipping test as FIPS Go does not support hash type")
return
}
t.Fatal(tabs[4], "❌ Failed to automatically sign:", err)
}
// 1.1 Verify this signature using the *inferred* salt length.
autoVerify(4, t, p, input, sig, unsaltedOptions)
}
rsaKeyTypes := []KeyType{KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096}
testKeys, err := generateTestKeys()
if err != nil {
t.Fatalf("error generating test keys: %s", err)
}
// 1. For each standard RSA key size 2048, 3072, and 4096...
for _, rsaKeyType := range rsaKeyTypes {
t.Log("Key size: ", rsaKeyType)
p := &Policy{
Name: fmt.Sprint(rsaKeyType), // NOTE: crucial to create a new key per key size
Type: rsaKeyType,
}
rsaKeyBytes := testKeys[rsaKeyType]
err := p.Import(ctx, storage, rsaKeyBytes, rand.Reader)
if err != nil {
t.Fatal(tabs[1], "❌ Failed to import key:", err)
}
rsaKeyAny, err := x509.ParsePKCS8PrivateKey(rsaKeyBytes)
if err != nil {
t.Fatalf("error parsing test keys: %s", err)
}
rsaKey := rsaKeyAny.(*rsa.PrivateKey)
// 2. For each hash algorithm...
for hashAlgorithm, hashType := range HashTypeMap {
t.Log(tabs[1], "Hash algorithm:", hashAlgorithm)
// 3. For each marshaling type...
for marshalingName, marshalingType := range MarshalingTypeMap {
t.Log(tabs[2], "Marshaling type:", marshalingName)
testName := fmt.Sprintf("%s-%s-%s", rsaKeyType, hashAlgorithm, marshalingName)
t.Run(testName, func(t *testing.T) { test_RSA_PKCS1(t, p, rsaKey, hashType, marshalingType) })
}
}
}
}
// Normal Go builds support all the hash functions for RSA_PSS signatures but the // Normal Go builds support all the hash functions for RSA_PSS signatures but the
// FIPS Go build does not support at this time the SHA3 hashes as FIPS 140_2 does // FIPS Go build does not support at this time the SHA3 hashes as FIPS 140_2 does
// not accept them. // not accept them.

View File

@ -1125,6 +1125,7 @@ supports signing.
- `sha3-256` - `sha3-256`
- `sha3-384` - `sha3-384`
- `sha3-512` - `sha3-512`
- `none`
~> **Note**: In FIPS 140-2 mode, the following algorithms are not certified ~> **Note**: In FIPS 140-2 mode, the following algorithms are not certified
and thus should not be used: `sha3-224`, `sha3-256`, `sha3-384`, and and thus should not be used: `sha3-224`, `sha3-256`, `sha3-384`, and
@ -1140,6 +1141,11 @@ supports signing.
} }
``` ```
~> **Note**: using `hash_algorithm=none` requires setting `prehashed=true`
and `signature_algorithm=pkcs1v15`. This generates a `PKCSv1_5_NoOID`
signature rather than the `PKCSv1_5_DERnull` signature type usually
created. See [RFC 3447 Section 9.2](https://www.rfc-editor.org/rfc/rfc3447#section-9.2).
- `input` `(string: "")`  Specifies the **base64 encoded** input data. One of - `input` `(string: "")`  Specifies the **base64 encoded** input data. One of
`input` or `batch_input` must be supplied. `input` or `batch_input` must be supplied.
@ -1293,6 +1299,7 @@ data.
- `sha3-256` - `sha3-256`
- `sha3-384` - `sha3-384`
- `sha3-512` - `sha3-512`
- `none`
~> **Note**: In FIPS 140-2 mode, the following algorithms are not certified ~> **Note**: In FIPS 140-2 mode, the following algorithms are not certified
and thus should not be used: `sha3-224`, `sha3-256`, `sha3-384`, and and thus should not be used: `sha3-224`, `sha3-256`, `sha3-384`, and
@ -1309,6 +1316,11 @@ data.
} }
``` ```
~> **Note**: using `hash_algorithm=none` requires setting `prehashed=true`
and `signature_algorithm=pkcs1v15`. This verifies a `PKCSv1_5_NoOID`
signature rather than the `PKCSv1_5_DERnull` signature type usually
verified. See [RFC 3447 Section 9.2](https://www.rfc-editor.org/rfc/rfc3447#section-9.2).
- `input` `(string: "")`  Specifies the **base64 encoded** input data. One of - `input` `(string: "")`  Specifies the **base64 encoded** input data. One of
`input` or `batch_input` must be supplied. `input` or `batch_input` must be supplied.