From 3ea2ec7c06f952a807c138e3e9c60970986e447b Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Wed, 25 Oct 2017 10:57:53 -0400 Subject: [PATCH 01/18] encrypt/decrypt/sign/verify RSA --- builtin/logical/transit/path_keys.go | 24 ++- helper/keysutil/lock_manager.go | 8 +- helper/keysutil/policy.go | 220 ++++++++++++++++----------- 3 files changed, 163 insertions(+), 89 deletions(-) diff --git a/builtin/logical/transit/path_keys.go b/builtin/logical/transit/path_keys.go index ad9a9188c254..5dc971a65b74 100644 --- a/builtin/logical/transit/path_keys.go +++ b/builtin/logical/transit/path_keys.go @@ -2,7 +2,9 @@ package transit import ( "crypto/elliptic" + "crypto/x509" "encoding/base64" + "encoding/pem" "fmt" "strconv" "time" @@ -131,6 +133,8 @@ func (b *backend) pathPolicyWrite( polReq.KeyType = keysutil.KeyType_ECDSA_P256 case "ed25519": polReq.KeyType = keysutil.KeyType_ED25519 + case "rsa": + polReq.KeyType = keysutil.KeyType_RSA default: return logical.ErrorResponse(fmt.Sprintf("unknown key type %v", keyType)), logical.ErrInvalidRequest } @@ -225,7 +229,7 @@ func (b *backend) pathPolicyRead( } resp.Data["keys"] = retKeys - case keysutil.KeyType_ECDSA_P256, keysutil.KeyType_ED25519: + case keysutil.KeyType_ECDSA_P256, keysutil.KeyType_ED25519, keysutil.KeyType_RSA: retKeys := map[string]map[string]interface{}{} for k, v := range p.Keys { key := asymKey{ @@ -253,6 +257,24 @@ func (b *backend) pathPolicyRead( } } key.Name = "ed25519" + case keysutil.KeyType_RSA: + key.Name = "rsa" + + // Encode the RSA public key in PEM format to return over the + // API + derBytes, err := x509.MarshalPKIXPublicKey(v.RSAKey.Public()) + if err != nil { + return nil, fmt.Errorf("error marshaling RSA public key: %v", err) + } + pemBlock := &pem.Block{ + Type: "PUBLIC KEY", + Bytes: derBytes, + } + pemBytes := pem.EncodeToMemory(pemBlock) + if pemBytes == nil || len(pemBytes) == 0 { + return nil, fmt.Errorf("failed to PEM-encode RSA public key") + } + key.PublicKey = string(pemBytes) } retKeys[strconv.Itoa(k)] = structs.New(key).Map() diff --git a/helper/keysutil/lock_manager.go b/helper/keysutil/lock_manager.go index 75881997340e..a96c106f7c69 100644 --- a/helper/keysutil/lock_manager.go +++ b/helper/keysutil/lock_manager.go @@ -256,7 +256,13 @@ func (lm *LockManager) getPolicyCommon(req PolicyRequest, lockType bool) (*Polic case KeyType_ED25519: if req.Convergent { lm.UnlockPolicy(lock, lockType) - return nil, nil, false, fmt.Errorf("convergent encryption not not supported for keys of type %v", req.KeyType) + return nil, nil, false, fmt.Errorf("convergent encryption not supported for keys of type %v", req.KeyType) + } + + case KeyType_RSA: + if req.Derived || req.Convergent { + lm.UnlockPolicy(lock, lockType) + return nil, nil, false, fmt.Errorf("key derivation and convergent encryption not supported for keys of type %v", req.KeyType) } default: diff --git a/helper/keysutil/policy.go b/helper/keysutil/policy.go index 5e14334f6a93..139b0d0be8ae 100644 --- a/helper/keysutil/policy.go +++ b/helper/keysutil/policy.go @@ -9,6 +9,7 @@ import ( "crypto/elliptic" "crypto/hmac" "crypto/rand" + "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/asn1" @@ -44,6 +45,7 @@ const ( KeyType_AES256_GCM96 = iota KeyType_ECDSA_P256 KeyType_ED25519 + KeyType_RSA ) const ErrTooOld = "ciphertext or signature version is disallowed by policy (too old)" @@ -61,7 +63,7 @@ type KeyType int func (kt KeyType) EncryptionSupported() bool { switch kt { - case KeyType_AES256_GCM96: + case KeyType_AES256_GCM96, KeyType_RSA: return true } return false @@ -69,7 +71,7 @@ func (kt KeyType) EncryptionSupported() bool { func (kt KeyType) DecryptionSupported() bool { switch kt { - case KeyType_AES256_GCM96: + case KeyType_AES256_GCM96, KeyType_RSA: return true } return false @@ -77,7 +79,7 @@ func (kt KeyType) DecryptionSupported() bool { func (kt KeyType) SigningSupported() bool { switch kt { - case KeyType_ECDSA_P256, KeyType_ED25519: + case KeyType_ECDSA_P256, KeyType_ED25519, KeyType_RSA: return true } return false @@ -107,6 +109,8 @@ func (kt KeyType) String() string { return "ecdsa-p256" case KeyType_ED25519: return "ed25519" + case KeyType_RSA: + return "rsa" } return "[unknown]" @@ -127,6 +131,8 @@ type KeyEntry struct { EC_Y *big.Int `json:"ec_y"` EC_D *big.Int `json:"ec_d"` + RSAKey *rsa.PrivateKey `json:"rsa_key"` + // The public key in an appropriate format for the type of key FormattedPublicKey string `json:"public_key"` @@ -519,13 +525,6 @@ func (p *Policy) Encrypt(ver int, context, nonce []byte, value string) (string, return "", errutil.UserError{Err: fmt.Sprintf("message encryption not supported for key type %v", p.Type)} } - // Guard against a potentially invalid key type - switch p.Type { - case KeyType_AES256_GCM96: - default: - return "", errutil.InternalError{Err: fmt.Sprintf("unsupported key type %v", p.Type)} - } - // Decode the plaintext value plaintext, err := base64.StdEncoding.DecodeString(value) if err != nil { @@ -543,62 +542,69 @@ func (p *Policy) Encrypt(ver int, context, nonce []byte, value string) (string, return "", errutil.UserError{Err: "requested version for encryption is less than the minimum encryption key version"} } - // Derive the key that should be used - key, err := p.DeriveKey(context, ver) - if err != nil { - return "", err - } + var ciphertext []byte - // Guard against a potentially invalid key type switch p.Type { case KeyType_AES256_GCM96: - default: - return "", errutil.InternalError{Err: fmt.Sprintf("unsupported key type %v", p.Type)} - } - - // Setup the cipher - aesCipher, err := aes.NewCipher(key) - if err != nil { - return "", errutil.InternalError{Err: err.Error()} - } + // Derive the key that should be used + key, err := p.DeriveKey(context, ver) + if err != nil { + return "", err + } - // Setup the GCM AEAD - gcm, err := cipher.NewGCM(aesCipher) - if err != nil { - return "", errutil.InternalError{Err: err.Error()} - } + // Setup the cipher + aesCipher, err := aes.NewCipher(key) + if err != nil { + return "", errutil.InternalError{Err: err.Error()} + } - if p.ConvergentEncryption { - switch p.ConvergentVersion { - case 1: - if len(nonce) != gcm.NonceSize() { - return "", errutil.UserError{Err: fmt.Sprintf("base64-decoded nonce must be %d bytes long when using convergent encryption with this key", gcm.NonceSize())} - } - default: - nonceHmac := hmac.New(sha256.New, context) - nonceHmac.Write(plaintext) - nonceSum := nonceHmac.Sum(nil) - nonce = nonceSum[:gcm.NonceSize()] - } - } else { - // Compute random nonce - nonce, err = uuid.GenerateRandomBytes(gcm.NonceSize()) + // Setup the GCM AEAD + gcm, err := cipher.NewGCM(aesCipher) if err != nil { return "", errutil.InternalError{Err: err.Error()} } - } - // Encrypt and tag with GCM - out := gcm.Seal(nil, nonce, plaintext, nil) + if p.ConvergentEncryption { + switch p.ConvergentVersion { + case 1: + if len(nonce) != gcm.NonceSize() { + return "", errutil.UserError{Err: fmt.Sprintf("base64-decoded nonce must be %d bytes long when using convergent encryption with this key", gcm.NonceSize())} + } + default: + nonceHmac := hmac.New(sha256.New, context) + nonceHmac.Write(plaintext) + nonceSum := nonceHmac.Sum(nil) + nonce = nonceSum[:gcm.NonceSize()] + } + } else { + // Compute random nonce + nonce, err = uuid.GenerateRandomBytes(gcm.NonceSize()) + if err != nil { + return "", errutil.InternalError{Err: err.Error()} + } + } + + // Encrypt and tag with GCM + out := gcm.Seal(nil, nonce, plaintext, nil) + + // Place the encrypted data after the nonce + if !p.ConvergentEncryption || p.ConvergentVersion > 1 { + ciphertext = append(nonce, out...) + } - // Place the encrypted data after the nonce - full := out - if !p.ConvergentEncryption || p.ConvergentVersion > 1 { - full = append(nonce, out...) + case KeyType_RSA: + key := p.Keys[ver].RSAKey + ciphertext, err = rsa.EncryptOAEP(sha256.New(), rand.Reader, &key.PublicKey, plaintext, nil) + if err != nil { + return "", errutil.InternalError{Err: fmt.Sprintf("RSA: Error from encryption: %s\n", err)} + } + + default: + return "", errutil.InternalError{Err: fmt.Sprintf("unsupported key type %v", p.Type)} } // Convert to base64 - encoded := base64.StdEncoding.EncodeToString(full) + encoded := base64.StdEncoding.EncodeToString(ciphertext) // Prepend some information encoded = "vault:v" + strconv.Itoa(ver) + ":" + encoded @@ -644,50 +650,57 @@ func (p *Policy) Decrypt(context, nonce []byte, value string) (string, error) { return "", errutil.UserError{Err: ErrTooOld} } - // Derive the key that should be used - key, err := p.DeriveKey(context, ver) + // Decode the base64 + decoded, err := base64.StdEncoding.DecodeString(splitVerCiphertext[1]) if err != nil { - return "", err + return "", errutil.UserError{Err: "invalid ciphertext: could not decode base64"} } - // Guard against a potentially invalid key type + var plain []byte + switch p.Type { case KeyType_AES256_GCM96: - default: - return "", errutil.InternalError{Err: fmt.Sprintf("unsupported key type %v", p.Type)} - } + key, err := p.DeriveKey(context, ver) + if err != nil { + return "", err + } - // Decode the base64 - decoded, err := base64.StdEncoding.DecodeString(splitVerCiphertext[1]) - if err != nil { - return "", errutil.UserError{Err: "invalid ciphertext: could not decode base64"} - } + // Setup the cipher + aesCipher, err := aes.NewCipher(key) + if err != nil { + return "", errutil.InternalError{Err: err.Error()} + } - // Setup the cipher - aesCipher, err := aes.NewCipher(key) - if err != nil { - return "", errutil.InternalError{Err: err.Error()} - } + // Setup the GCM AEAD + gcm, err := cipher.NewGCM(aesCipher) + if err != nil { + return "", errutil.InternalError{Err: err.Error()} + } - // Setup the GCM AEAD - gcm, err := cipher.NewGCM(aesCipher) - if err != nil { - return "", errutil.InternalError{Err: err.Error()} - } + // Extract the nonce and ciphertext + var ciphertext []byte + if p.ConvergentEncryption && p.ConvergentVersion < 2 { + ciphertext = decoded + } else { + nonce = decoded[:gcm.NonceSize()] + ciphertext = decoded[gcm.NonceSize():] + } - // Extract the nonce and ciphertext - var ciphertext []byte - if p.ConvergentEncryption && p.ConvergentVersion < 2 { - ciphertext = decoded - } else { - nonce = decoded[:gcm.NonceSize()] - ciphertext = decoded[gcm.NonceSize():] - } + // Verify and Decrypt + plain, err = gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return "", errutil.UserError{Err: "invalid ciphertext: unable to decrypt"} + } - // Verify and Decrypt - plain, err := gcm.Open(nil, nonce, ciphertext, nil) - if err != nil { - return "", errutil.UserError{Err: "invalid ciphertext: unable to decrypt"} + case KeyType_RSA: + key := p.Keys[ver].RSAKey + plain, err = rsa.DecryptOAEP(sha256.New(), rand.Reader, key, decoded, nil) + if err != nil { + return "", errutil.InternalError{Err: fmt.Sprintf("error while decrypting RSA ciphertext: %v", err)} + } + + default: + return "", errutil.InternalError{Err: fmt.Sprintf("unsupported key type %v", p.Type)} } return base64.StdEncoding.EncodeToString(plain), nil @@ -773,6 +786,20 @@ func (p *Policy) Sign(ver int, context, input []byte) (*SigningResult, error) { return nil, err } + case KeyType_RSA: + key := p.Keys[ver].RSAKey + + hash := crypto.SHA256.New() + hash.Write(input) + inputHash := hash.Sum(nil) + + sig, err = rsa.SignPSS(rand.Reader, key, crypto.SHA256, inputHash, &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthEqualsHash, + }) + if err != nil { + return nil, err + } + default: return nil, fmt.Errorf("unsupported key type %v", p.Type) } @@ -857,6 +884,19 @@ func (p *Policy) VerifySignature(context, input []byte, sig string) (bool, error return ed25519.Verify(key.Public().(ed25519.PublicKey), input, sigBytes), nil + case KeyType_RSA: + key := p.Keys[ver].RSAKey + + hash := crypto.SHA256.New() + hash.Write(input) + inputHash := hash.Sum(nil) + + err = rsa.VerifyPSS(&key.PublicKey, crypto.SHA256, inputHash, sigBytes, &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthEqualsHash, + }) + + return err == nil, nil + default: return false, errutil.InternalError{Err: fmt.Sprintf("unsupported key type %v", p.Type)} } @@ -923,6 +963,12 @@ func (p *Policy) Rotate(storage logical.Storage) error { } entry.Key = pri entry.FormattedPublicKey = base64.StdEncoding.EncodeToString(pub) + + case KeyType_RSA: + entry.RSAKey, err = rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return err + } } p.Keys[p.LatestVersion] = entry From 27594aa0e40586f6b03760eff6ce77dc64fb5cb6 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Wed, 25 Oct 2017 14:17:48 -0400 Subject: [PATCH 02/18] update path-help and doc --- builtin/logical/transit/path_keys.go | 8 +++++--- website/source/api/secret/transit/index.html.md | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/builtin/logical/transit/path_keys.go b/builtin/logical/transit/path_keys.go index 5dc971a65b74..61b1562fde45 100644 --- a/builtin/logical/transit/path_keys.go +++ b/builtin/logical/transit/path_keys.go @@ -42,9 +42,11 @@ func (b *backend) pathKeys() *framework.Path { "type": &framework.FieldSchema{ Type: framework.TypeString, Default: "aes256-gcm96", - Description: `The type of key to create. Currently, -"aes256-gcm96" (symmetric) and "ecdsa-p256" (asymmetric), and -'ed25519' (asymmetric) are supported. Defaults to "aes256-gcm96".`, + Description: ` +The type of key to create. Currently, "aes256-gcm96" (symmetric), "ecdsa-p256" +(asymmetric), 'ed25519' (asymmetric), 'rsa' (asymmetric) are supported. +Defaults to "aes256-gcm96". +`, }, "derived": &framework.FieldSchema{ diff --git a/website/source/api/secret/transit/index.html.md b/website/source/api/secret/transit/index.html.md index 5520b2493fa1..d9c95e438449 100644 --- a/website/source/api/secret/transit/index.html.md +++ b/website/source/api/secret/transit/index.html.md @@ -52,6 +52,7 @@ values set here cannot be changed after key creation. (symmetric, supports derivation) - `ecdsa-p256` – ECDSA using the P-256 elliptic curve (asymmetric) - `ed25519` – ED25519 (asymmetric, supports derivation) + - `rsa` - RSA with bit size of 2048 (asymmetric) ### Sample Payload From 0e105a3eb95d47b1ff1de994b02c6cb76b76fa0e Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Wed, 25 Oct 2017 17:53:04 -0400 Subject: [PATCH 03/18] Fix the bug which was breaking convergent encryption --- helper/keysutil/policy.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helper/keysutil/policy.go b/helper/keysutil/policy.go index 139b0d0be8ae..40e92f29a184 100644 --- a/helper/keysutil/policy.go +++ b/helper/keysutil/policy.go @@ -585,11 +585,11 @@ func (p *Policy) Encrypt(ver int, context, nonce []byte, value string) (string, } // Encrypt and tag with GCM - out := gcm.Seal(nil, nonce, plaintext, nil) + ciphertext = gcm.Seal(nil, nonce, plaintext, nil) // Place the encrypted data after the nonce if !p.ConvergentEncryption || p.ConvergentVersion > 1 { - ciphertext = append(nonce, out...) + ciphertext = append(nonce, ciphertext...) } case KeyType_RSA: From e284bb804c683d5dd3dc57aacc6301340b2de351 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Wed, 25 Oct 2017 21:21:51 -0400 Subject: [PATCH 04/18] support both 2048 and 4096 --- builtin/logical/transit/path_keys.go | 19 +++++++++------ helper/keysutil/lock_manager.go | 2 +- helper/keysutil/policy.go | 36 +++++++++++++++++----------- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/builtin/logical/transit/path_keys.go b/builtin/logical/transit/path_keys.go index 61b1562fde45..42ce0b9b581a 100644 --- a/builtin/logical/transit/path_keys.go +++ b/builtin/logical/transit/path_keys.go @@ -44,8 +44,8 @@ func (b *backend) pathKeys() *framework.Path { Default: "aes256-gcm96", Description: ` The type of key to create. Currently, "aes256-gcm96" (symmetric), "ecdsa-p256" -(asymmetric), 'ed25519' (asymmetric), 'rsa' (asymmetric) are supported. -Defaults to "aes256-gcm96". +(asymmetric), 'ed25519' (asymmetric), 'rsa-2048' (asymmetric), 'rsa-4096' +(asymmetric) are supported. Defaults to "aes256-gcm96". `, }, @@ -135,8 +135,10 @@ func (b *backend) pathPolicyWrite( polReq.KeyType = keysutil.KeyType_ECDSA_P256 case "ed25519": polReq.KeyType = keysutil.KeyType_ED25519 - case "rsa": - polReq.KeyType = keysutil.KeyType_RSA + case "rsa-2048": + polReq.KeyType = keysutil.KeyType_RSA2048 + case "rsa-4096": + polReq.KeyType = keysutil.KeyType_RSA4096 default: return logical.ErrorResponse(fmt.Sprintf("unknown key type %v", keyType)), logical.ErrInvalidRequest } @@ -231,7 +233,7 @@ func (b *backend) pathPolicyRead( } resp.Data["keys"] = retKeys - case keysutil.KeyType_ECDSA_P256, keysutil.KeyType_ED25519, keysutil.KeyType_RSA: + case keysutil.KeyType_ECDSA_P256, keysutil.KeyType_ED25519, keysutil.KeyType_RSA2048, keysutil.KeyType_RSA4096: retKeys := map[string]map[string]interface{}{} for k, v := range p.Keys { key := asymKey{ @@ -259,8 +261,11 @@ func (b *backend) pathPolicyRead( } } key.Name = "ed25519" - case keysutil.KeyType_RSA: - key.Name = "rsa" + case keysutil.KeyType_RSA2048, keysutil.KeyType_RSA4096: + key.Name = "rsa-2048" + if p.Type == keysutil.KeyType_RSA4096 { + key.Name = "rsa-4096" + } // Encode the RSA public key in PEM format to return over the // API diff --git a/helper/keysutil/lock_manager.go b/helper/keysutil/lock_manager.go index a96c106f7c69..fc28ea0eee1b 100644 --- a/helper/keysutil/lock_manager.go +++ b/helper/keysutil/lock_manager.go @@ -259,7 +259,7 @@ func (lm *LockManager) getPolicyCommon(req PolicyRequest, lockType bool) (*Polic return nil, nil, false, fmt.Errorf("convergent encryption not supported for keys of type %v", req.KeyType) } - case KeyType_RSA: + case KeyType_RSA2048, KeyType_RSA4096: if req.Derived || req.Convergent { lm.UnlockPolicy(lock, lockType) return nil, nil, false, fmt.Errorf("key derivation and convergent encryption not supported for keys of type %v", req.KeyType) diff --git a/helper/keysutil/policy.go b/helper/keysutil/policy.go index 40e92f29a184..2ee18344d8ff 100644 --- a/helper/keysutil/policy.go +++ b/helper/keysutil/policy.go @@ -45,7 +45,8 @@ const ( KeyType_AES256_GCM96 = iota KeyType_ECDSA_P256 KeyType_ED25519 - KeyType_RSA + KeyType_RSA2048 + KeyType_RSA4096 ) const ErrTooOld = "ciphertext or signature version is disallowed by policy (too old)" @@ -63,7 +64,7 @@ type KeyType int func (kt KeyType) EncryptionSupported() bool { switch kt { - case KeyType_AES256_GCM96, KeyType_RSA: + case KeyType_AES256_GCM96, KeyType_RSA2048, KeyType_RSA4096: return true } return false @@ -71,7 +72,7 @@ func (kt KeyType) EncryptionSupported() bool { func (kt KeyType) DecryptionSupported() bool { switch kt { - case KeyType_AES256_GCM96, KeyType_RSA: + case KeyType_AES256_GCM96, KeyType_RSA2048, KeyType_RSA4096: return true } return false @@ -79,7 +80,7 @@ func (kt KeyType) DecryptionSupported() bool { func (kt KeyType) SigningSupported() bool { switch kt { - case KeyType_ECDSA_P256, KeyType_ED25519, KeyType_RSA: + case KeyType_ECDSA_P256, KeyType_ED25519, KeyType_RSA2048, KeyType_RSA4096: return true } return false @@ -109,8 +110,10 @@ func (kt KeyType) String() string { return "ecdsa-p256" case KeyType_ED25519: return "ed25519" - case KeyType_RSA: - return "rsa" + case KeyType_RSA2048: + return "rsa-2048" + case KeyType_RSA4096: + return "rsa-4096" } return "[unknown]" @@ -592,11 +595,11 @@ func (p *Policy) Encrypt(ver int, context, nonce []byte, value string) (string, ciphertext = append(nonce, ciphertext...) } - case KeyType_RSA: + case KeyType_RSA2048, KeyType_RSA4096: key := p.Keys[ver].RSAKey ciphertext, err = rsa.EncryptOAEP(sha256.New(), rand.Reader, &key.PublicKey, plaintext, nil) if err != nil { - return "", errutil.InternalError{Err: fmt.Sprintf("RSA: Error from encryption: %s\n", err)} + return "", errutil.InternalError{Err: fmt.Sprintf("failed to RSA encrypt the plaintext: %v", err)} } default: @@ -692,11 +695,11 @@ func (p *Policy) Decrypt(context, nonce []byte, value string) (string, error) { return "", errutil.UserError{Err: "invalid ciphertext: unable to decrypt"} } - case KeyType_RSA: + case KeyType_RSA2048, KeyType_RSA4096: key := p.Keys[ver].RSAKey plain, err = rsa.DecryptOAEP(sha256.New(), rand.Reader, key, decoded, nil) if err != nil { - return "", errutil.InternalError{Err: fmt.Sprintf("error while decrypting RSA ciphertext: %v", err)} + return "", errutil.InternalError{Err: fmt.Sprintf("failed to RSA decrypt the ciphertext: %v", err)} } default: @@ -786,7 +789,7 @@ func (p *Policy) Sign(ver int, context, input []byte) (*SigningResult, error) { return nil, err } - case KeyType_RSA: + case KeyType_RSA2048, KeyType_RSA4096: key := p.Keys[ver].RSAKey hash := crypto.SHA256.New() @@ -884,7 +887,7 @@ func (p *Policy) VerifySignature(context, input []byte, sig string) (bool, error return ed25519.Verify(key.Public().(ed25519.PublicKey), input, sigBytes), nil - case KeyType_RSA: + case KeyType_RSA2048, KeyType_RSA4096: key := p.Keys[ver].RSAKey hash := crypto.SHA256.New() @@ -964,8 +967,13 @@ func (p *Policy) Rotate(storage logical.Storage) error { entry.Key = pri entry.FormattedPublicKey = base64.StdEncoding.EncodeToString(pub) - case KeyType_RSA: - entry.RSAKey, err = rsa.GenerateKey(rand.Reader, 2048) + case KeyType_RSA2048, KeyType_RSA4096: + bitSize := 2048 + if p.Type == KeyType_RSA4096 { + bitSize = 4096 + } + + entry.RSAKey, err = rsa.GenerateKey(rand.Reader, bitSize) if err != nil { return err } From ce1962ca302136b1d8151caaa4110d9fde6202fc Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Wed, 25 Oct 2017 21:23:23 -0400 Subject: [PATCH 05/18] update doc to contain both 2048 and 4096 --- website/source/api/secret/transit/index.html.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/source/api/secret/transit/index.html.md b/website/source/api/secret/transit/index.html.md index d9c95e438449..570f1946416f 100644 --- a/website/source/api/secret/transit/index.html.md +++ b/website/source/api/secret/transit/index.html.md @@ -52,7 +52,8 @@ values set here cannot be changed after key creation. (symmetric, supports derivation) - `ecdsa-p256` – ECDSA using the P-256 elliptic curve (asymmetric) - `ed25519` – ED25519 (asymmetric, supports derivation) - - `rsa` - RSA with bit size of 2048 (asymmetric) + - `rsa-2048` - RSA with bit size of 2048 (asymmetric) + - `rsa-4096` - RSA with bit size of 4096 (asymmetric) ### Sample Payload From 9ea51967fcae8cda51e4e0e06c7555ef61a6d64a Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Wed, 25 Oct 2017 22:11:39 -0400 Subject: [PATCH 06/18] Add test for encrypt, decrypt and rotate on RSA keys --- builtin/logical/transit/backend_test.go | 109 +++++++++++++++++++ builtin/logical/transit/path_encrypt_test.go | 2 +- 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/builtin/logical/transit/backend_test.go b/builtin/logical/transit/backend_test.go index a9c27bcef624..e4fcd3d92f23 100644 --- a/builtin/logical/transit/backend_test.go +++ b/builtin/logical/transit/backend_test.go @@ -38,6 +38,115 @@ func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) { return b, config.StorageView } +func TestTransit_RSA(t *testing.T) { + fmt.Printf("now running rsa-2048\n") + testTransit_RSA(t, "rsa-2048") + + fmt.Printf("now running rsa-4096\n") + testTransit_RSA(t, "rsa-4096") +} + +func testTransit_RSA(t *testing.T, keyType string) { + var resp *logical.Response + var err error + b, storage := createBackendWithStorage(t) + + keyReq := &logical.Request{ + Path: "keys/rsa", + Operation: logical.UpdateOperation, + Data: map[string]interface{}{ + "type": keyType, + }, + Storage: storage, + } + + resp, err = b.HandleRequest(keyReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } + + plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA==" // "the quick brown fox" + + encryptReq := &logical.Request{ + Path: "encrypt/rsa", + Operation: logical.UpdateOperation, + Storage: storage, + Data: map[string]interface{}{ + "plaintext": plaintext, + }, + } + + resp, err = b.HandleRequest(encryptReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } + + ciphertext1 := resp.Data["ciphertext"].(string) + + decryptReq := &logical.Request{ + Path: "decrypt/rsa", + Operation: logical.UpdateOperation, + Storage: storage, + Data: map[string]interface{}{ + "ciphertext": ciphertext1, + }, + } + + resp, err = b.HandleRequest(decryptReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } + + decryptedPlaintext := resp.Data["plaintext"] + + if plaintext != decryptedPlaintext { + t.Fatalf("bad: plaintext; expected: %q\nactual: %q", plaintext, decryptedPlaintext) + } + + // Rotate the key + rotateReq := &logical.Request{ + Path: "keys/rsa/rotate", + Operation: logical.UpdateOperation, + Storage: storage, + } + resp, err = b.HandleRequest(rotateReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } + + // Encrypt again + resp, err = b.HandleRequest(encryptReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } + ciphertext2 := resp.Data["ciphertext"].(string) + + if ciphertext1 == ciphertext2 { + t.Fatalf("expected different ciphertexts") + } + + // See if the older ciphertext can still be decrypted + resp, err = b.HandleRequest(decryptReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } + if resp.Data["plaintext"].(string) != plaintext { + t.Fatal("failed to decrypt old ciphertext after rotating the key") + } + + // Decrypt the new ciphertext + decryptReq.Data = map[string]interface{}{ + "ciphertext": ciphertext2, + } + resp, err = b.HandleRequest(decryptReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } + if resp.Data["plaintext"].(string) != plaintext { + t.Fatal("failed to decrypt ciphertext after rotating the key") + } +} + func TestBackend_basic(t *testing.T) { decryptData := make(map[string]interface{}) logicaltest.Test(t, logicaltest.TestCase{ diff --git a/builtin/logical/transit/path_encrypt_test.go b/builtin/logical/transit/path_encrypt_test.go index 6ab20db27130..d5866004535e 100644 --- a/builtin/logical/transit/path_encrypt_test.go +++ b/builtin/logical/transit/path_encrypt_test.go @@ -26,7 +26,7 @@ func TestTransit_BatchEncryptionCase1(t *testing.T) { t.Fatalf("err:%v resp:%#v", err, resp) } - plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA==" + plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA==" // "the quick brown fox" encData := map[string]interface{}{ "plaintext": plaintext, From 4acd786b82c49abcd998fd49b5f8b1646bf8c807 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Wed, 25 Oct 2017 22:33:41 -0400 Subject: [PATCH 07/18] Support exporting RSA keys --- builtin/logical/transit/backend_test.go | 3 --- builtin/logical/transit/path_export.go | 10 ++++++++++ builtin/logical/transit/path_keys.go | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/builtin/logical/transit/backend_test.go b/builtin/logical/transit/backend_test.go index e4fcd3d92f23..57519fee7f58 100644 --- a/builtin/logical/transit/backend_test.go +++ b/builtin/logical/transit/backend_test.go @@ -39,10 +39,7 @@ func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) { } func TestTransit_RSA(t *testing.T) { - fmt.Printf("now running rsa-2048\n") testTransit_RSA(t, "rsa-2048") - - fmt.Printf("now running rsa-4096\n") testTransit_RSA(t, "rsa-4096") } diff --git a/builtin/logical/transit/path_export.go b/builtin/logical/transit/path_export.go index a18db91b0f59..29bfc773ab33 100644 --- a/builtin/logical/transit/path_export.go +++ b/builtin/logical/transit/path_export.go @@ -152,6 +152,16 @@ func getExportKey(policy *keysutil.Policy, key *keysutil.KeyEntry, exportType st switch policy.Type { case keysutil.KeyType_AES256_GCM96: return strings.TrimSpace(base64.StdEncoding.EncodeToString(key.Key)), nil + + case keysutil.KeyType_RSA2048, keysutil.KeyType_RSA4096: + derBytes := x509.MarshalPKCS1PrivateKey(key.RSAKey) + pemBlock := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: derBytes, + } + pemBytes := pem.EncodeToMemory(pemBlock) + return string(pemBytes), nil + } case exportTypeSigningKey: diff --git a/builtin/logical/transit/path_keys.go b/builtin/logical/transit/path_keys.go index 42ce0b9b581a..58ad9e175642 100644 --- a/builtin/logical/transit/path_keys.go +++ b/builtin/logical/transit/path_keys.go @@ -274,7 +274,7 @@ func (b *backend) pathPolicyRead( return nil, fmt.Errorf("error marshaling RSA public key: %v", err) } pemBlock := &pem.Block{ - Type: "PUBLIC KEY", + Type: "RSA PUBLIC KEY", Bytes: derBytes, } pemBytes := pem.EncodeToMemory(pemBlock) From ea8e327960e0818e3cd22bb3b2cb9eb1c62b358b Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Wed, 25 Oct 2017 22:51:54 -0400 Subject: [PATCH 08/18] Add sign and verify test steps --- builtin/logical/transit/backend_test.go | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/builtin/logical/transit/backend_test.go b/builtin/logical/transit/backend_test.go index 57519fee7f58..3e1cfedd35b9 100644 --- a/builtin/logical/transit/backend_test.go +++ b/builtin/logical/transit/backend_test.go @@ -142,6 +142,38 @@ func testTransit_RSA(t *testing.T, keyType string) { if resp.Data["plaintext"].(string) != plaintext { t.Fatal("failed to decrypt ciphertext after rotating the key") } + + signReq := &logical.Request{ + Path: "sign/rsa", + Operation: logical.UpdateOperation, + Storage: storage, + Data: map[string]interface{}{ + "input": plaintext, + }, + } + resp, err = b.HandleRequest(signReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } + signature := resp.Data["signature"].(string) + + verifyReq := &logical.Request{ + Path: "verify/rsa", + Operation: logical.UpdateOperation, + Storage: storage, + Data: map[string]interface{}{ + "input": plaintext, + "signature": signature, + }, + } + + resp, err = b.HandleRequest(verifyReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } + if !resp.Data["valid"].(bool) { + t.Fatal("failed to verify the RSA signature") + } } func TestBackend_basic(t *testing.T) { From fd6582451fd35c61db423d7e005d2027df1115ca Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Thu, 26 Oct 2017 19:19:52 -0400 Subject: [PATCH 09/18] Remove 'RSA' from PEM header --- builtin/logical/transit/path_export.go | 2 +- builtin/logical/transit/path_keys.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/logical/transit/path_export.go b/builtin/logical/transit/path_export.go index 29bfc773ab33..83a43e857d8b 100644 --- a/builtin/logical/transit/path_export.go +++ b/builtin/logical/transit/path_export.go @@ -156,7 +156,7 @@ func getExportKey(policy *keysutil.Policy, key *keysutil.KeyEntry, exportType st case keysutil.KeyType_RSA2048, keysutil.KeyType_RSA4096: derBytes := x509.MarshalPKCS1PrivateKey(key.RSAKey) pemBlock := &pem.Block{ - Type: "RSA PRIVATE KEY", + Type: "PRIVATE KEY", Bytes: derBytes, } pemBytes := pem.EncodeToMemory(pemBlock) diff --git a/builtin/logical/transit/path_keys.go b/builtin/logical/transit/path_keys.go index 58ad9e175642..42ce0b9b581a 100644 --- a/builtin/logical/transit/path_keys.go +++ b/builtin/logical/transit/path_keys.go @@ -274,7 +274,7 @@ func (b *backend) pathPolicyRead( return nil, fmt.Errorf("error marshaling RSA public key: %v", err) } pemBlock := &pem.Block{ - Type: "RSA PUBLIC KEY", + Type: "PUBLIC KEY", Bytes: derBytes, } pemBytes := pem.EncodeToMemory(pemBlock) From 8f5b19285db9995cf1a92bb6ea6a131722bdc7d6 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Thu, 26 Oct 2017 19:41:54 -0400 Subject: [PATCH 10/18] use the default salt length --- helper/keysutil/policy.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/helper/keysutil/policy.go b/helper/keysutil/policy.go index 2ee18344d8ff..0e9730ad724f 100644 --- a/helper/keysutil/policy.go +++ b/helper/keysutil/policy.go @@ -796,9 +796,7 @@ func (p *Policy) Sign(ver int, context, input []byte) (*SigningResult, error) { hash.Write(input) inputHash := hash.Sum(nil) - sig, err = rsa.SignPSS(rand.Reader, key, crypto.SHA256, inputHash, &rsa.PSSOptions{ - SaltLength: rsa.PSSSaltLengthEqualsHash, - }) + sig, err = rsa.SignPSS(rand.Reader, key, crypto.SHA256, inputHash, nil) if err != nil { return nil, err } @@ -894,9 +892,7 @@ func (p *Policy) VerifySignature(context, input []byte, sig string) (bool, error hash.Write(input) inputHash := hash.Sum(nil) - err = rsa.VerifyPSS(&key.PublicKey, crypto.SHA256, inputHash, sigBytes, &rsa.PSSOptions{ - SaltLength: rsa.PSSSaltLengthEqualsHash, - }) + err = rsa.VerifyPSS(&key.PublicKey, crypto.SHA256, inputHash, sigBytes, nil) return err == nil, nil From a42b5b5cabccd82a25e239967e9567dda688ae65 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 27 Oct 2017 08:56:45 -0400 Subject: [PATCH 11/18] Add 'RSA' to PEM header since openssl is expecting that --- builtin/logical/transit/path_export.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/logical/transit/path_export.go b/builtin/logical/transit/path_export.go index 83a43e857d8b..29bfc773ab33 100644 --- a/builtin/logical/transit/path_export.go +++ b/builtin/logical/transit/path_export.go @@ -156,7 +156,7 @@ func getExportKey(policy *keysutil.Policy, key *keysutil.KeyEntry, exportType st case keysutil.KeyType_RSA2048, keysutil.KeyType_RSA4096: derBytes := x509.MarshalPKCS1PrivateKey(key.RSAKey) pemBlock := &pem.Block{ - Type: "PRIVATE KEY", + Type: "RSA PRIVATE KEY", Bytes: derBytes, } pemBytes := pem.EncodeToMemory(pemBlock) From 7bb366b92b98ed4da2d682dcf89503b891f5ce6f Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 27 Oct 2017 10:43:35 -0400 Subject: [PATCH 12/18] export rsa keys as signing-key as well --- builtin/logical/transit/path_export.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/builtin/logical/transit/path_export.go b/builtin/logical/transit/path_export.go index 29bfc773ab33..7221b743cbc3 100644 --- a/builtin/logical/transit/path_export.go +++ b/builtin/logical/transit/path_export.go @@ -3,6 +3,7 @@ package transit import ( "crypto/ecdsa" "crypto/elliptic" + "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/pem" @@ -154,14 +155,7 @@ func getExportKey(policy *keysutil.Policy, key *keysutil.KeyEntry, exportType st return strings.TrimSpace(base64.StdEncoding.EncodeToString(key.Key)), nil case keysutil.KeyType_RSA2048, keysutil.KeyType_RSA4096: - derBytes := x509.MarshalPKCS1PrivateKey(key.RSAKey) - pemBlock := &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: derBytes, - } - pemBytes := pem.EncodeToMemory(pemBlock) - return string(pemBytes), nil - + return encodeRSAPrivateKey(key.RSAKey), nil } case exportTypeSigningKey: @@ -175,12 +169,25 @@ func getExportKey(policy *keysutil.Policy, key *keysutil.KeyEntry, exportType st case keysutil.KeyType_ED25519: return strings.TrimSpace(base64.StdEncoding.EncodeToString(key.Key)), nil + + case keysutil.KeyType_RSA2048, keysutil.KeyType_RSA4096: + return encodeRSAPrivateKey(key.RSAKey), nil } } return "", fmt.Errorf("unknown key type %v", policy.Type) } +func encodeRSAPrivateKey(key *rsa.PrivateKey) string { + derBytes := x509.MarshalPKCS1PrivateKey(key) + pemBlock := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: derBytes, + } + pemBytes := pem.EncodeToMemory(pemBlock) + return string(pemBytes) +} + func keyEntryToECPrivateKey(k *keysutil.KeyEntry, curve elliptic.Curve) (string, error) { if k == nil { return "", errors.New("nil KeyEntry provided") From c0921f80f3f3394c8a0601d59844f77cbe170642 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 27 Oct 2017 11:51:24 -0400 Subject: [PATCH 13/18] Comment the reasoning behind the PEM headers --- builtin/logical/transit/path_export.go | 3 +++ builtin/logical/transit/path_keys.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/builtin/logical/transit/path_export.go b/builtin/logical/transit/path_export.go index 7221b743cbc3..707c4ff2570a 100644 --- a/builtin/logical/transit/path_export.go +++ b/builtin/logical/transit/path_export.go @@ -179,6 +179,9 @@ func getExportKey(policy *keysutil.Policy, key *keysutil.KeyEntry, exportType st } func encodeRSAPrivateKey(key *rsa.PrivateKey) string { + // As per "https://wiki.openssl.org/index.php/Manual:Rsa(1)", the header + // should be 'RSA PRIVATE KEY'. 'openssl' also expects the header to be + // this. derBytes := x509.MarshalPKCS1PrivateKey(key) pemBlock := &pem.Block{ Type: "RSA PRIVATE KEY", diff --git a/builtin/logical/transit/path_keys.go b/builtin/logical/transit/path_keys.go index 42ce0b9b581a..34f4a973b261 100644 --- a/builtin/logical/transit/path_keys.go +++ b/builtin/logical/transit/path_keys.go @@ -273,6 +273,9 @@ func (b *backend) pathPolicyRead( if err != nil { return nil, fmt.Errorf("error marshaling RSA public key: %v", err) } + // Not making the header 'RSA PUBLIC KEY' since the format is + // of generic public key and not specifically of RSA. If its + // 'RSA PUBLIC KEY', 'openssl' complains. pemBlock := &pem.Block{ Type: "PUBLIC KEY", Bytes: derBytes, From 0d5ce93f9f2ec2250f82e18d920fb23ddd4a47c7 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 3 Nov 2017 04:11:05 -0400 Subject: [PATCH 14/18] remove comment --- builtin/logical/transit/path_keys.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/builtin/logical/transit/path_keys.go b/builtin/logical/transit/path_keys.go index 34f4a973b261..42ce0b9b581a 100644 --- a/builtin/logical/transit/path_keys.go +++ b/builtin/logical/transit/path_keys.go @@ -273,9 +273,6 @@ func (b *backend) pathPolicyRead( if err != nil { return nil, fmt.Errorf("error marshaling RSA public key: %v", err) } - // Not making the header 'RSA PUBLIC KEY' since the format is - // of generic public key and not specifically of RSA. If its - // 'RSA PUBLIC KEY', 'openssl' complains. pemBlock := &pem.Block{ Type: "PUBLIC KEY", Bytes: derBytes, From 174f6d78d8c5ca9c702790da92288de81cff6608 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 3 Nov 2017 04:15:34 -0400 Subject: [PATCH 15/18] update comment --- builtin/logical/transit/path_export.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/builtin/logical/transit/path_export.go b/builtin/logical/transit/path_export.go index 707c4ff2570a..a218c22d8231 100644 --- a/builtin/logical/transit/path_export.go +++ b/builtin/logical/transit/path_export.go @@ -179,9 +179,8 @@ func getExportKey(policy *keysutil.Policy, key *keysutil.KeyEntry, exportType st } func encodeRSAPrivateKey(key *rsa.PrivateKey) string { - // As per "https://wiki.openssl.org/index.php/Manual:Rsa(1)", the header - // should be 'RSA PRIVATE KEY'. 'openssl' also expects the header to be - // this. + // When encoding PKCS1, the PEM header should be `RSA PRIVATE KEY`. When Go + // has PKCS8 encoding support, we may want to change this. derBytes := x509.MarshalPKCS1PrivateKey(key) pemBlock := &pem.Block{ Type: "RSA PRIVATE KEY", From f245daf52467fae0d08688876094a51ab1ef316c Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 3 Nov 2017 05:56:39 -0400 Subject: [PATCH 16/18] Parameterize hashing for RSA signing and verification --- builtin/logical/transit/path_sign_verify.go | 22 ++++++++--- helper/keysutil/policy.go | 42 +++++++++++++++------ 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/builtin/logical/transit/path_sign_verify.go b/builtin/logical/transit/path_sign_verify.go index 074f7ff22236..6bdc96d3c6f7 100644 --- a/builtin/logical/transit/path_sign_verify.go +++ b/builtin/logical/transit/path_sign_verify.go @@ -37,7 +37,6 @@ derivation is enabled; currently only available with ed25519 keys.`, Default: "sha2-256", Description: `Hash algorithm to use (POST body parameter). Valid values are: -* none * sha2-224 * sha2-256 * sha2-384 @@ -58,6 +57,11 @@ including ed25519.`, Must be 0 (for latest) or a value greater than or equal to the min_encryption_version configured on the key.`, }, + + "prehashed": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `Set to 'true' when the input is already hashed. If the key type is 'rsa-2048' or 'rsa-4096', then the algorithm used to hash the input should be indicated by the 'algorithm' parameter.`, + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -109,7 +113,6 @@ derivation is enabled; currently only available with ed25519 keys.`, Default: "sha2-256", Description: `Hash algorithm to use (POST body parameter). Valid values are: -* none * sha2-224 * sha2-256 * sha2-384 @@ -117,6 +120,11 @@ derivation is enabled; currently only available with ed25519 keys.`, Defaults to "sha2-256". Not valid for all key types.`, }, + + "prehashed": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `Set to 'true' when the input is already hashed. If the key type is 'rsa-2048' or 'rsa-4096', then the algorithm used to hash the input should be indicated by the 'algorithm' parameter.`, + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -137,6 +145,7 @@ func (b *backend) pathSignWrite( if algorithm == "" { algorithm = d.Get("algorithm").(string) } + prehashed := d.Get("prehashed").(bool) input, err := base64.StdEncoding.DecodeString(inputB64) if err != nil { @@ -168,7 +177,7 @@ func (b *backend) pathSignWrite( } } - if p.Type.HashSignatureInput() && algorithm != "none" { + if p.Type.HashSignatureInput() && !prehashed { var hf hash.Hash switch algorithm { case "sha2-224": @@ -186,7 +195,7 @@ func (b *backend) pathSignWrite( input = hf.Sum(nil) } - sig, err := p.Sign(ver, context, input) + sig, err := p.Sign(ver, context, input, algorithm) if err != nil { return nil, err } @@ -230,6 +239,7 @@ func (b *backend) pathVerifyWrite( if algorithm == "" { algorithm = d.Get("algorithm").(string) } + prehashed := d.Get("prehashed").(bool) input, err := base64.StdEncoding.DecodeString(inputB64) if err != nil { @@ -261,7 +271,7 @@ func (b *backend) pathVerifyWrite( } } - if p.Type.HashSignatureInput() && algorithm != "none" { + if p.Type.HashSignatureInput() && !prehashed { var hf hash.Hash switch algorithm { case "sha2-224": @@ -279,7 +289,7 @@ func (b *backend) pathVerifyWrite( input = hf.Sum(nil) } - valid, err := p.VerifySignature(context, input, sig) + valid, err := p.VerifySignature(context, input, sig, algorithm) if err != nil { switch err.(type) { case errutil.UserError: diff --git a/helper/keysutil/policy.go b/helper/keysutil/policy.go index 0e9730ad724f..b0169a2a2c9f 100644 --- a/helper/keysutil/policy.go +++ b/helper/keysutil/policy.go @@ -88,7 +88,7 @@ func (kt KeyType) SigningSupported() bool { func (kt KeyType) HashSignatureInput() bool { switch kt { - case KeyType_ECDSA_P256: + case KeyType_ECDSA_P256, KeyType_RSA2048, KeyType_RSA4096: return true } return false @@ -724,7 +724,7 @@ func (p *Policy) HMACKey(version int) ([]byte, error) { return p.Keys[version].HMACKey, nil } -func (p *Policy) Sign(ver int, context, input []byte) (*SigningResult, error) { +func (p *Policy) Sign(ver int, context, input []byte, algorithm string) (*SigningResult, error) { if !p.Type.SigningSupported() { return nil, fmt.Errorf("message signing not supported for key type %v", p.Type) } @@ -792,11 +792,21 @@ func (p *Policy) Sign(ver int, context, input []byte) (*SigningResult, error) { case KeyType_RSA2048, KeyType_RSA4096: key := p.Keys[ver].RSAKey - hash := crypto.SHA256.New() - hash.Write(input) - inputHash := hash.Sum(nil) + var algo crypto.Hash + switch algorithm { + case "sha2-224": + algo = crypto.SHA224 + case "sha2-256": + algo = crypto.SHA256 + case "sha2-384": + algo = crypto.SHA384 + case "sha2-512": + algo = crypto.SHA512 + default: + return nil, errutil.InternalError{Err: fmt.Sprintf("unsupported algorithm %s", algorithm)} + } - sig, err = rsa.SignPSS(rand.Reader, key, crypto.SHA256, inputHash, nil) + sig, err = rsa.SignPSS(rand.Reader, key, algo, input, nil) if err != nil { return nil, err } @@ -816,7 +826,7 @@ func (p *Policy) Sign(ver int, context, input []byte) (*SigningResult, error) { return res, nil } -func (p *Policy) VerifySignature(context, input []byte, sig string) (bool, error) { +func (p *Policy) VerifySignature(context, input []byte, sig, algorithm string) (bool, error) { if !p.Type.SigningSupported() { return false, errutil.UserError{Err: fmt.Sprintf("message verification not supported for key type %v", p.Type)} } @@ -888,11 +898,21 @@ func (p *Policy) VerifySignature(context, input []byte, sig string) (bool, error case KeyType_RSA2048, KeyType_RSA4096: key := p.Keys[ver].RSAKey - hash := crypto.SHA256.New() - hash.Write(input) - inputHash := hash.Sum(nil) + var algo crypto.Hash + switch algorithm { + case "sha2-224": + algo = crypto.SHA224 + case "sha2-256": + algo = crypto.SHA256 + case "sha2-384": + algo = crypto.SHA384 + case "sha2-512": + algo = crypto.SHA512 + default: + return false, errutil.InternalError{Err: fmt.Sprintf("unsupported algorithm %s", algorithm)} + } - err = rsa.VerifyPSS(&key.PublicKey, crypto.SHA256, inputHash, sigBytes, nil) + err = rsa.VerifyPSS(&key.PublicKey, algo, input, sigBytes, nil) return err == nil, nil From d5f001fd5d4edba6d1bfb2d6900c85173be10f7e Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 3 Nov 2017 06:07:26 -0400 Subject: [PATCH 17/18] Added test steps to check hash algo choice for RSA sign/verify --- builtin/logical/transit/backend_test.go | 49 ++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/builtin/logical/transit/backend_test.go b/builtin/logical/transit/backend_test.go index 3e1cfedd35b9..050ce18ab7d6 100644 --- a/builtin/logical/transit/backend_test.go +++ b/builtin/logical/transit/backend_test.go @@ -172,7 +172,54 @@ func testTransit_RSA(t *testing.T, keyType string) { t.Fatalf("bad: err: %v\nresp: %#v", err, resp) } if !resp.Data["valid"].(bool) { - t.Fatal("failed to verify the RSA signature") + t.Fatalf("failed to verify the RSA signature") + } + + signReq.Data = map[string]interface{}{ + "input": plaintext, + "algorithm": "invalid", + } + resp, err = b.HandleRequest(signReq) + if err != nil { + t.Fatal(err) + } + if resp == nil || !resp.IsError() { + t.Fatal("expected an error response") + } + + signReq.Data = map[string]interface{}{ + "input": plaintext, + "algorithm": "sha2-512", + } + resp, err = b.HandleRequest(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": plaintext, + "signature": signature, + } + resp, err = b.HandleRequest(verifyReq) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: err: %v\nresp: %#v", err, resp) + } + if resp.Data["valid"].(bool) { + t.Fatalf("expected validation to fail") + } + + verifyReq.Data = map[string]interface{}{ + "input": plaintext, + "signature": signature, + "algorithm": "sha2-512", + } + resp, err = b.HandleRequest(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") } } From 3f0fd6899b4babb969152466ac525e486293d774 Mon Sep 17 00:00:00 2001 From: vishalnayak Date: Fri, 3 Nov 2017 10:45:07 -0400 Subject: [PATCH 18/18] fix test by using 'prehashed' --- builtin/logical/transit/path_sign_verify_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin/logical/transit/path_sign_verify_test.go b/builtin/logical/transit/path_sign_verify_test.go index 1ab994f1da3c..f23f0a8edb8c 100644 --- a/builtin/logical/transit/path_sign_verify_test.go +++ b/builtin/logical/transit/path_sign_verify_test.go @@ -164,9 +164,10 @@ func TestTransit_SignVerify_P256(t *testing.T) { sig = signRequest(req, false, "") verifyRequest(req, false, "", sig) - req.Data["algorithm"] = "none" + req.Data["prehashed"] = true sig = signRequest(req, false, "") verifyRequest(req, false, "", sig) + delete(req.Data, "prehashed") // Test 512 and save sig for later to ensure we can't validate once min // decryption version is set