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) {
|
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) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue