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:
parent
1e189016e2
commit
1733d2a3d6
|
@ -289,6 +289,35 @@ func testTransit_RSA(t *testing.T, keyType string) {
|
|||
if !resp.Data["valid"].(bool) {
|
||||
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) {
|
||||
|
|
|
@ -61,6 +61,8 @@ type batchResponseVerifyItem struct {
|
|||
err error
|
||||
}
|
||||
|
||||
const defaultHashAlgorithm = "sha2-256"
|
||||
|
||||
func (b *backend) pathSign() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "sign/" + framework.GenericNameRegex("name") + framework.OptionalParamRegex("urlalgorithm"),
|
||||
|
@ -83,7 +85,7 @@ derivation is enabled; currently only available with ed25519 keys.`,
|
|||
|
||||
"hash_algorithm": {
|
||||
Type: framework.TypeString,
|
||||
Default: "sha2-256",
|
||||
Default: defaultHashAlgorithm,
|
||||
Description: `Hash algorithm to use (POST body parameter). Valid values are:
|
||||
|
||||
* sha1
|
||||
|
@ -95,14 +97,17 @@ derivation is enabled; currently only available with ed25519 keys.`,
|
|||
* sha3-256
|
||||
* sha3-384
|
||||
* sha3-512
|
||||
* none
|
||||
|
||||
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": {
|
||||
Type: framework.TypeString,
|
||||
Default: "sha2-256",
|
||||
Default: defaultHashAlgorithm,
|
||||
Description: `Deprecated: use "hash_algorithm" instead.`,
|
||||
},
|
||||
|
||||
|
@ -189,7 +194,7 @@ derivation is enabled; currently only available with ed25519 keys.`,
|
|||
|
||||
"hash_algorithm": {
|
||||
Type: framework.TypeString,
|
||||
Default: "sha2-256",
|
||||
Default: defaultHashAlgorithm,
|
||||
Description: `Hash algorithm to use (POST body parameter). Valid values are:
|
||||
|
||||
* sha1
|
||||
|
@ -201,13 +206,15 @@ derivation is enabled; currently only available with ed25519 keys.`,
|
|||
* sha3-256
|
||||
* sha3-384
|
||||
* 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": {
|
||||
Type: framework.TypeString,
|
||||
Default: "sha2-256",
|
||||
Default: defaultHashAlgorithm,
|
||||
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)
|
||||
if hashAlgorithmStr == "" {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
p, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{
|
||||
Storage: req.Storage,
|
||||
|
@ -507,6 +521,9 @@ func (b *backend) pathVerifyWrite(ctx context.Context, req *logical.Request, d *
|
|||
hashAlgorithmStr = d.Get("hash_algorithm").(string)
|
||||
if hashAlgorithmStr == "" {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
p, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{
|
||||
Storage: req.Storage,
|
||||
|
|
|
@ -154,7 +154,7 @@ func testTransit_SignVerify_ECDSA(t *testing.T, bits int) {
|
|||
req.Path = "sign/foo" + postpath
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil && !errExpected {
|
||||
t.Fatal(err)
|
||||
t.Fatalf("request: %v\nerror: %v", req, err)
|
||||
}
|
||||
if resp == nil {
|
||||
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 {
|
||||
t.Log("Hash algorithm:", hashAlgorithm)
|
||||
if hashAlgorithm == "none" {
|
||||
continue
|
||||
}
|
||||
|
||||
for marshalingName := range keysutil.MarshalingTypeMap {
|
||||
t.Log("\t", "Marshaling type:", marshalingName)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
secrets/transit: Add support for PKCSv1_5_NoOID RSA signatures
|
||||
```
|
|
@ -13,8 +13,8 @@ import (
|
|||
type HashType uint32
|
||||
|
||||
const (
|
||||
_ = iota
|
||||
HashTypeSHA1 HashType = iota
|
||||
HashTypeNone HashType = iota
|
||||
HashTypeSHA1
|
||||
HashTypeSHA2224
|
||||
HashTypeSHA2256
|
||||
HashTypeSHA2384
|
||||
|
@ -35,6 +35,7 @@ const (
|
|||
|
||||
var (
|
||||
HashTypeMap = map[string]HashType{
|
||||
"none": HashTypeNone,
|
||||
"sha1": HashTypeSHA1,
|
||||
"sha2-224": HashTypeSHA2224,
|
||||
"sha2-256": HashTypeSHA2256,
|
||||
|
@ -47,6 +48,7 @@ var (
|
|||
}
|
||||
|
||||
HashFuncMap = map[HashType]func() hash.Hash{
|
||||
HashTypeNone: nil,
|
||||
HashTypeSHA1: sha1.New,
|
||||
HashTypeSHA2224: sha256.New224,
|
||||
HashTypeSHA2256: sha256.New,
|
||||
|
@ -59,6 +61,7 @@ var (
|
|||
}
|
||||
|
||||
CryptoHashMap = map[HashType]crypto.Hash{
|
||||
HashTypeNone: 0,
|
||||
HashTypeSHA1: crypto.SHA1,
|
||||
HashTypeSHA2224: crypto.SHA224,
|
||||
HashTypeSHA2256: crypto.SHA256,
|
||||
|
|
|
@ -953,6 +953,9 @@ func Test_RSA_PSS(t *testing.T) {
|
|||
// 2. For each hash algorithm...
|
||||
for hashAlgorithm, hashType := range HashTypeMap {
|
||||
t.Log(tabs[1], "Hash algorithm:", hashAlgorithm)
|
||||
if hashAlgorithm == "none" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 3. For each marshaling type...
|
||||
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
|
||||
// FIPS Go build does not support at this time the SHA3 hashes as FIPS 140_2 does
|
||||
// not accept them.
|
||||
|
|
|
@ -1125,6 +1125,7 @@ supports signing.
|
|||
- `sha3-256`
|
||||
- `sha3-384`
|
||||
- `sha3-512`
|
||||
- `none`
|
||||
|
||||
~> **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
|
||||
|
@ -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` or `batch_input` must be supplied.
|
||||
|
||||
|
@ -1293,6 +1299,7 @@ data.
|
|||
- `sha3-256`
|
||||
- `sha3-384`
|
||||
- `sha3-512`
|
||||
- `none`
|
||||
|
||||
~> **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
|
||||
|
@ -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` or `batch_input` must be supplied.
|
||||
|
||||
|
|
Loading…
Reference in New Issue