Add support for a dedicated HMAC type in Transit. (#16668)
* Get import correct * limits, docs * changelog * unit tests * And fix import for hmac unit test * typo * Update website/content/api-docs/secret/transit.mdx Co-authored-by: Matt Schultz <975680+schultz-is@users.noreply.github.com> * Update builtin/logical/transit/path_keys.go Co-authored-by: Matt Schultz <975680+schultz-is@users.noreply.github.com> * Validate key sizes a bit more carefully * Update sdk/helper/keysutil/policy.go Co-authored-by: Matt Schultz <975680+schultz-is@users.noreply.github.com> Co-authored-by: Matt Schultz <975680+schultz-is@users.noreply.github.com>
This commit is contained in:
parent
95b5449e8a
commit
606edb66d6
|
@ -14,71 +14,167 @@ import (
|
|||
func TestTransit_HMAC(t *testing.T) {
|
||||
b, storage := createBackendWithSysView(t)
|
||||
|
||||
// First create a key
|
||||
req := &logical.Request{
|
||||
Storage: storage,
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "keys/foo",
|
||||
}
|
||||
_, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
cases := []struct {
|
||||
name string
|
||||
typ string
|
||||
}{
|
||||
{
|
||||
name: "foo",
|
||||
typ: "",
|
||||
},
|
||||
{
|
||||
name: "dedicated",
|
||||
typ: "hmac",
|
||||
},
|
||||
}
|
||||
|
||||
// Now, change the key value to something we control
|
||||
p, _, err := b.GetPolicy(context.Background(), keysutil.PolicyRequest{
|
||||
Storage: storage,
|
||||
Name: "foo",
|
||||
}, b.GetRandomReader())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// We don't care as we're the only one using this
|
||||
latestVersion := strconv.Itoa(p.LatestVersion)
|
||||
keyEntry := p.Keys[latestVersion]
|
||||
keyEntry.HMACKey = []byte("01234567890123456789012345678901")
|
||||
p.Keys[latestVersion] = keyEntry
|
||||
if err = p.Persist(context.Background(), storage); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req.Path = "hmac/foo"
|
||||
req.Data = map[string]interface{}{
|
||||
"input": "dGhlIHF1aWNrIGJyb3duIGZveA==",
|
||||
}
|
||||
|
||||
doRequest := func(req *logical.Request, errExpected bool, expected string) {
|
||||
path := req.Path
|
||||
defer func() { req.Path = path }()
|
||||
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil && !errExpected {
|
||||
panic(fmt.Sprintf("%v", err))
|
||||
for _, c := range cases {
|
||||
req := &logical.Request{
|
||||
Storage: storage,
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "keys/" + c.name,
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
_, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if errExpected {
|
||||
if !resp.IsError() {
|
||||
|
||||
// Now, change the key value to something we control
|
||||
p, _, err := b.GetPolicy(context.Background(), keysutil.PolicyRequest{
|
||||
Storage: storage,
|
||||
Name: c.name,
|
||||
}, b.GetRandomReader())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// We don't care as we're the only one using this
|
||||
latestVersion := strconv.Itoa(p.LatestVersion)
|
||||
keyEntry := p.Keys[latestVersion]
|
||||
keyEntry.HMACKey = []byte("01234567890123456789012345678901")
|
||||
keyEntry.Key = []byte("01234567890123456789012345678901")
|
||||
p.Keys[latestVersion] = keyEntry
|
||||
if err = p.Persist(context.Background(), storage); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req.Path = "hmac/" + c.name
|
||||
req.Data = map[string]interface{}{
|
||||
"input": "dGhlIHF1aWNrIGJyb3duIGZveA==",
|
||||
}
|
||||
|
||||
doRequest := func(req *logical.Request, errExpected bool, expected string) {
|
||||
path := req.Path
|
||||
defer func() { req.Path = path }()
|
||||
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil && !errExpected {
|
||||
panic(fmt.Sprintf("%v", err))
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
if errExpected {
|
||||
if !resp.IsError() {
|
||||
t.Fatalf("bad: got error response: %#v", *resp)
|
||||
}
|
||||
return
|
||||
}
|
||||
if resp.IsError() {
|
||||
t.Fatalf("bad: got error response: %#v", *resp)
|
||||
}
|
||||
return
|
||||
}
|
||||
if resp.IsError() {
|
||||
t.Fatalf("bad: got error response: %#v", *resp)
|
||||
}
|
||||
value, ok := resp.Data["hmac"]
|
||||
if !ok {
|
||||
t.Fatalf("no hmac key found in returned data, got resp data %#v", resp.Data)
|
||||
}
|
||||
if value.(string) != expected {
|
||||
panic(fmt.Sprintf("mismatched hashes; expected %s, got resp data %#v", expected, resp.Data))
|
||||
value, ok := resp.Data["hmac"]
|
||||
if !ok {
|
||||
t.Fatalf("no hmac key found in returned data, got resp data %#v", resp.Data)
|
||||
}
|
||||
if value.(string) != expected {
|
||||
panic(fmt.Sprintf("mismatched hashes; expected %s, got resp data %#v", expected, resp.Data))
|
||||
}
|
||||
|
||||
// Now verify
|
||||
req.Path = strings.ReplaceAll(req.Path, "hmac", "verify")
|
||||
req.Data["hmac"] = value.(string)
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v: %v", err, resp)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
if resp.Data["valid"].(bool) == false {
|
||||
panic(fmt.Sprintf("error validating hmac;\nreq:\n%#v\nresp:\n%#v", *req, *resp))
|
||||
}
|
||||
}
|
||||
|
||||
// Now verify
|
||||
req.Path = strings.ReplaceAll(req.Path, "hmac", "verify")
|
||||
req.Data["hmac"] = value.(string)
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
// Comparisons are against values generated via openssl
|
||||
|
||||
// Test defaults -- sha2-256
|
||||
doRequest(req, false, "vault:v1:UcBvm5VskkukzZHlPgm3p5P/Yr/PV6xpuOGZISya3A4=")
|
||||
|
||||
// Test algorithm selection in the path
|
||||
req.Path = "hmac/" + c.name + "/sha2-224"
|
||||
doRequest(req, false, "vault:v1:3p+ZWVquYDvu2dSTCa65Y3fgoMfIAc6fNaBbtg==")
|
||||
|
||||
// Reset and test algorithm selection in the data
|
||||
req.Path = "hmac/" + c.name
|
||||
req.Data["algorithm"] = "sha2-224"
|
||||
doRequest(req, false, "vault:v1:3p+ZWVquYDvu2dSTCa65Y3fgoMfIAc6fNaBbtg==")
|
||||
|
||||
req.Data["algorithm"] = "sha2-384"
|
||||
doRequest(req, false, "vault:v1:jDB9YXdPjpmr29b1JCIEJO93IydlKVfD9mA2EO9OmJtJQg3QAV5tcRRRb7IQGW9p")
|
||||
|
||||
req.Data["algorithm"] = "sha2-512"
|
||||
doRequest(req, false, "vault:v1:PSXLXvkvKF4CpU65e2bK1tGBZQpcpCEM32fq2iUoiTyQQCfBcGJJItQ+60tMwWXAPQrC290AzTrNJucGrr4GFA==")
|
||||
|
||||
// Test returning as base64
|
||||
req.Data["format"] = "base64"
|
||||
doRequest(req, false, "vault:v1:PSXLXvkvKF4CpU65e2bK1tGBZQpcpCEM32fq2iUoiTyQQCfBcGJJItQ+60tMwWXAPQrC290AzTrNJucGrr4GFA==")
|
||||
|
||||
// Test SHA3
|
||||
req.Path = "hmac/" + c.name
|
||||
req.Data["algorithm"] = "sha3-224"
|
||||
doRequest(req, false, "vault:v1:TGipmKH8LR/BkMolYpDYy0BJCIhTtGPDhV2VkQ==")
|
||||
|
||||
req.Data["algorithm"] = "sha3-256"
|
||||
doRequest(req, false, "vault:v1:+px9V/7QYLfdK808zPESC2T/L33uFf4Blzsn9Jy838o=")
|
||||
|
||||
req.Data["algorithm"] = "sha3-384"
|
||||
doRequest(req, false, "vault:v1:YGoRwN4UdTRYZeOER86jsQOB8piWenzLDzJ2wJQK/Jq59rAsY8lh7SCdqqCyFg70")
|
||||
|
||||
req.Data["algorithm"] = "sha3-512"
|
||||
doRequest(req, false, "vault:v1:GrNA8sU88naMPEQ7UZGj9EJl7YJhl03AFHfxcEURFrtvnobdea9ZlZHePpxAx/oCaC7R2HkrAO+Tu3uXPIl3lg==")
|
||||
|
||||
// Test returning SHA3 as base64
|
||||
req.Data["format"] = "base64"
|
||||
doRequest(req, false, "vault:v1:GrNA8sU88naMPEQ7UZGj9EJl7YJhl03AFHfxcEURFrtvnobdea9ZlZHePpxAx/oCaC7R2HkrAO+Tu3uXPIl3lg==")
|
||||
|
||||
req.Data["algorithm"] = "foobar"
|
||||
doRequest(req, true, "")
|
||||
|
||||
req.Data["algorithm"] = "sha2-256"
|
||||
req.Data["input"] = "foobar"
|
||||
doRequest(req, true, "")
|
||||
req.Data["input"] = "dGhlIHF1aWNrIGJyb3duIGZveA=="
|
||||
|
||||
// Rotate
|
||||
err = p.Rotate(context.Background(), storage, b.GetRandomReader())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keyEntry = p.Keys["2"]
|
||||
// Set to another value we control
|
||||
keyEntry.HMACKey = []byte("12345678901234567890123456789012")
|
||||
p.Keys["2"] = keyEntry
|
||||
if err = p.Persist(context.Background(), storage); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
doRequest(req, false, "vault:v2:Dt+mO/B93kuWUbGMMobwUNX5Wodr6dL3JH4DMfpQ0kw=")
|
||||
|
||||
// Verify a previous version
|
||||
req.Path = "verify/" + c.name
|
||||
|
||||
req.Data["hmac"] = "vault:v1:UcBvm5VskkukzZHlPgm3p5P/Yr/PV6xpuOGZISya3A4="
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v: %v", err, resp)
|
||||
}
|
||||
|
@ -86,116 +182,36 @@ func TestTransit_HMAC(t *testing.T) {
|
|||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
if resp.Data["valid"].(bool) == false {
|
||||
panic(fmt.Sprintf("error validating hmac;\nreq:\n%#v\nresp:\n%#v", *req, *resp))
|
||||
t.Fatalf("error validating hmac\nreq\n%#v\nresp\n%#v", *req, *resp)
|
||||
}
|
||||
}
|
||||
|
||||
// Comparisons are against values generated via openssl
|
||||
// Try a bad value
|
||||
req.Data["hmac"] = "vault:v1:UcBvm4VskkukzZHlPgm3p5P/Yr/PV6xpuOGZISya3A4="
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v: %v", err, resp)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
if resp.Data["valid"].(bool) {
|
||||
t.Fatalf("expected error validating hmac")
|
||||
}
|
||||
|
||||
// Test defaults -- sha2-256
|
||||
doRequest(req, false, "vault:v1:UcBvm5VskkukzZHlPgm3p5P/Yr/PV6xpuOGZISya3A4=")
|
||||
// Set min decryption version, attempt to verify
|
||||
p.MinDecryptionVersion = 2
|
||||
if err = p.Persist(context.Background(), storage); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test algorithm selection in the path
|
||||
req.Path = "hmac/foo/sha2-224"
|
||||
doRequest(req, false, "vault:v1:3p+ZWVquYDvu2dSTCa65Y3fgoMfIAc6fNaBbtg==")
|
||||
|
||||
// Reset and test algorithm selection in the data
|
||||
req.Path = "hmac/foo"
|
||||
req.Data["algorithm"] = "sha2-224"
|
||||
doRequest(req, false, "vault:v1:3p+ZWVquYDvu2dSTCa65Y3fgoMfIAc6fNaBbtg==")
|
||||
|
||||
req.Data["algorithm"] = "sha2-384"
|
||||
doRequest(req, false, "vault:v1:jDB9YXdPjpmr29b1JCIEJO93IydlKVfD9mA2EO9OmJtJQg3QAV5tcRRRb7IQGW9p")
|
||||
|
||||
req.Data["algorithm"] = "sha2-512"
|
||||
doRequest(req, false, "vault:v1:PSXLXvkvKF4CpU65e2bK1tGBZQpcpCEM32fq2iUoiTyQQCfBcGJJItQ+60tMwWXAPQrC290AzTrNJucGrr4GFA==")
|
||||
|
||||
// Test returning as base64
|
||||
req.Data["format"] = "base64"
|
||||
doRequest(req, false, "vault:v1:PSXLXvkvKF4CpU65e2bK1tGBZQpcpCEM32fq2iUoiTyQQCfBcGJJItQ+60tMwWXAPQrC290AzTrNJucGrr4GFA==")
|
||||
|
||||
// Test SHA3
|
||||
req.Path = "hmac/foo"
|
||||
req.Data["algorithm"] = "sha3-224"
|
||||
doRequest(req, false, "vault:v1:TGipmKH8LR/BkMolYpDYy0BJCIhTtGPDhV2VkQ==")
|
||||
|
||||
req.Data["algorithm"] = "sha3-256"
|
||||
doRequest(req, false, "vault:v1:+px9V/7QYLfdK808zPESC2T/L33uFf4Blzsn9Jy838o=")
|
||||
|
||||
req.Data["algorithm"] = "sha3-384"
|
||||
doRequest(req, false, "vault:v1:YGoRwN4UdTRYZeOER86jsQOB8piWenzLDzJ2wJQK/Jq59rAsY8lh7SCdqqCyFg70")
|
||||
|
||||
req.Data["algorithm"] = "sha3-512"
|
||||
doRequest(req, false, "vault:v1:GrNA8sU88naMPEQ7UZGj9EJl7YJhl03AFHfxcEURFrtvnobdea9ZlZHePpxAx/oCaC7R2HkrAO+Tu3uXPIl3lg==")
|
||||
|
||||
// Test returning SHA3 as base64
|
||||
req.Data["format"] = "base64"
|
||||
doRequest(req, false, "vault:v1:GrNA8sU88naMPEQ7UZGj9EJl7YJhl03AFHfxcEURFrtvnobdea9ZlZHePpxAx/oCaC7R2HkrAO+Tu3uXPIl3lg==")
|
||||
|
||||
req.Data["algorithm"] = "foobar"
|
||||
doRequest(req, true, "")
|
||||
|
||||
req.Data["algorithm"] = "sha2-256"
|
||||
req.Data["input"] = "foobar"
|
||||
doRequest(req, true, "")
|
||||
req.Data["input"] = "dGhlIHF1aWNrIGJyb3duIGZveA=="
|
||||
|
||||
// Rotate
|
||||
err = p.Rotate(context.Background(), storage, b.GetRandomReader())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keyEntry = p.Keys["2"]
|
||||
// Set to another value we control
|
||||
keyEntry.HMACKey = []byte("12345678901234567890123456789012")
|
||||
p.Keys["2"] = keyEntry
|
||||
if err = p.Persist(context.Background(), storage); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
doRequest(req, false, "vault:v2:Dt+mO/B93kuWUbGMMobwUNX5Wodr6dL3JH4DMfpQ0kw=")
|
||||
|
||||
// Verify a previous version
|
||||
req.Path = "verify/foo"
|
||||
|
||||
req.Data["hmac"] = "vault:v1:UcBvm5VskkukzZHlPgm3p5P/Yr/PV6xpuOGZISya3A4="
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v: %v", err, resp)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
if resp.Data["valid"].(bool) == false {
|
||||
t.Fatalf("error validating hmac\nreq\n%#v\nresp\n%#v", *req, *resp)
|
||||
}
|
||||
|
||||
// Try a bad value
|
||||
req.Data["hmac"] = "vault:v1:UcBvm4VskkukzZHlPgm3p5P/Yr/PV6xpuOGZISya3A4="
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("%v: %v", err, resp)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
if resp.Data["valid"].(bool) {
|
||||
t.Fatalf("expected error validating hmac")
|
||||
}
|
||||
|
||||
// Set min decryption version, attempt to verify
|
||||
p.MinDecryptionVersion = 2
|
||||
if err = p.Persist(context.Background(), storage); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req.Data["hmac"] = "vault:v1:UcBvm5VskkukzZHlPgm3p5P/Yr/PV6xpuOGZISya3A4="
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
if err == nil {
|
||||
t.Fatalf("expected an error, got response %#v", resp)
|
||||
}
|
||||
if err != logical.ErrInvalidRequest {
|
||||
t.Fatalf("expected invalid request error, got %v", err)
|
||||
req.Data["hmac"] = "vault:v1:UcBvm5VskkukzZHlPgm3p5P/Yr/PV6xpuOGZISya3A4="
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
if err == nil {
|
||||
t.Fatalf("expected an error, got response %#v", resp)
|
||||
}
|
||||
if err != logical.ErrInvalidRequest {
|
||||
t.Fatalf("expected invalid request error, got %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -177,6 +177,8 @@ func (b *backend) pathImportWrite(ctx context.Context, req *logical.Request, d *
|
|||
polReq.KeyType = keysutil.KeyType_RSA3072
|
||||
case "rsa-4096":
|
||||
polReq.KeyType = keysutil.KeyType_RSA4096
|
||||
case "hmac":
|
||||
polReq.KeyType = keysutil.KeyType_HMAC
|
||||
default:
|
||||
return logical.ErrorResponse(fmt.Sprintf("unknown key type: %v", keyType)), logical.ErrInvalidRequest
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ var keyTypes = []string{
|
|||
"rsa-2048",
|
||||
"rsa-3072",
|
||||
"rsa-4096",
|
||||
"hmac",
|
||||
}
|
||||
|
||||
var hashFns = []string{
|
||||
|
@ -543,7 +544,7 @@ func wrapTargetKeyForImport(t *testing.T, wrappingKey *rsa.PublicKey, targetKey
|
|||
var ok bool
|
||||
var err error
|
||||
switch targetKeyType {
|
||||
case "aes128-gcm96", "aes256-gcm96", "chacha20-poly1305":
|
||||
case "aes128-gcm96", "aes256-gcm96", "chacha20-poly1305", "hmac":
|
||||
preppedTargetKey, ok = targetKey.([]byte)
|
||||
if !ok {
|
||||
t.Fatal("failed to wrap target key for import: symmetric key not provided in byte format")
|
||||
|
@ -600,7 +601,7 @@ func generateKey(keyType string) (interface{}, error) {
|
|||
switch keyType {
|
||||
case "aes128-gcm96":
|
||||
return uuid.GenerateRandomBytes(16)
|
||||
case "aes256-gcm96":
|
||||
case "aes256-gcm96", "hmac":
|
||||
return uuid.GenerateRandomBytes(32)
|
||||
case "chacha20-poly1305":
|
||||
return uuid.GenerateRandomBytes(32)
|
||||
|
|
|
@ -103,6 +103,11 @@ being automatically rotated. A value of 0
|
|||
(default) disables automatic rotation for the
|
||||
key.`,
|
||||
},
|
||||
"key_size": {
|
||||
Type: framework.TypeInt,
|
||||
Default: 0,
|
||||
Description: fmt.Sprintf("The key size in bytes for the algorithm. Only applies to HMAC and must be no fewer than %d bytes and no more than %d", keysutil.HmacMinKeySize, keysutil.HmacMaxKeySize),
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
|
@ -130,6 +135,7 @@ func (b *backend) pathPolicyWrite(ctx context.Context, req *logical.Request, d *
|
|||
derived := d.Get("derived").(bool)
|
||||
convergent := d.Get("convergent_encryption").(bool)
|
||||
keyType := d.Get("type").(string)
|
||||
keySize := d.Get("key_size").(int)
|
||||
exportable := d.Get("exportable").(bool)
|
||||
allowPlaintextBackup := d.Get("allow_plaintext_backup").(bool)
|
||||
autoRotatePeriod := time.Second * time.Duration(d.Get("auto_rotate_period").(int))
|
||||
|
@ -152,6 +158,7 @@ func (b *backend) pathPolicyWrite(ctx context.Context, req *logical.Request, d *
|
|||
AllowPlaintextBackup: allowPlaintextBackup,
|
||||
AutoRotatePeriod: autoRotatePeriod,
|
||||
}
|
||||
|
||||
switch keyType {
|
||||
case "aes128-gcm96":
|
||||
polReq.KeyType = keysutil.KeyType_AES128_GCM96
|
||||
|
@ -173,9 +180,20 @@ func (b *backend) pathPolicyWrite(ctx context.Context, req *logical.Request, d *
|
|||
polReq.KeyType = keysutil.KeyType_RSA3072
|
||||
case "rsa-4096":
|
||||
polReq.KeyType = keysutil.KeyType_RSA4096
|
||||
case "hmac":
|
||||
polReq.KeyType = keysutil.KeyType_HMAC
|
||||
default:
|
||||
return logical.ErrorResponse(fmt.Sprintf("unknown key type %v", keyType)), logical.ErrInvalidRequest
|
||||
}
|
||||
if keySize != 0 {
|
||||
if polReq.KeyType != keysutil.KeyType_HMAC {
|
||||
return logical.ErrorResponse(fmt.Sprintf("key_size is not valid for algorithm %v", polReq.KeyType)), logical.ErrInvalidRequest
|
||||
}
|
||||
if keySize < keysutil.HmacMinKeySize || keySize > keysutil.HmacMaxKeySize {
|
||||
return logical.ErrorResponse(fmt.Sprintf("invalid key_size %d", keySize)), logical.ErrInvalidRequest
|
||||
}
|
||||
polReq.KeySize = keySize
|
||||
}
|
||||
|
||||
p, upserted, err := b.GetPolicy(ctx, polReq, b.GetRandomReader())
|
||||
if err != nil {
|
||||
|
@ -242,6 +260,9 @@ func (b *backend) pathPolicyRead(ctx context.Context, req *logical.Request, d *f
|
|||
"imported_key": p.Imported,
|
||||
},
|
||||
}
|
||||
if p.KeySize != 0 {
|
||||
resp.Data["key_size"] = p.KeySize
|
||||
}
|
||||
|
||||
if p.Imported {
|
||||
resp.Data["imported_key_allow_rotation"] = p.AllowImportedKeyRotation
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
secrets/transit: Add a dedicated HMAC key type, which can be used with key import.
|
||||
```
|
|
@ -36,6 +36,9 @@ type PolicyRequest struct {
|
|||
// The key type
|
||||
KeyType KeyType
|
||||
|
||||
// The key size for variable key size algorithms
|
||||
KeySize int
|
||||
|
||||
// Whether it should be derived
|
||||
Derived bool
|
||||
|
||||
|
@ -373,6 +376,11 @@ func (lm *LockManager) GetPolicy(ctx context.Context, req PolicyRequest, rand io
|
|||
cleanup()
|
||||
return nil, false, fmt.Errorf("key derivation and convergent encryption not supported for keys of type %v", req.KeyType)
|
||||
}
|
||||
case KeyType_HMAC:
|
||||
if req.Derived || req.Convergent {
|
||||
cleanup()
|
||||
return nil, false, fmt.Errorf("key derivation and convergent encryption not supported for keys of type %v", req.KeyType)
|
||||
}
|
||||
|
||||
default:
|
||||
cleanup()
|
||||
|
@ -387,6 +395,7 @@ func (lm *LockManager) GetPolicy(ctx context.Context, req PolicyRequest, rand io
|
|||
Exportable: req.Exportable,
|
||||
AllowPlaintextBackup: req.AllowPlaintextBackup,
|
||||
AutoRotatePeriod: req.AutoRotatePeriod,
|
||||
KeySize: req.KeySize,
|
||||
}
|
||||
|
||||
if req.Derived {
|
||||
|
|
|
@ -45,6 +45,9 @@ import (
|
|||
const (
|
||||
Kdf_hmac_sha256_counter = iota // built-in helper
|
||||
Kdf_hkdf_sha256 // golang.org/x/crypto/hkdf
|
||||
|
||||
HmacMinKeySize = 256 / 8
|
||||
HmacMaxKeySize = 4096 / 8
|
||||
)
|
||||
|
||||
// Or this one...we need the default of zero to be the original AES256-GCM96
|
||||
|
@ -59,6 +62,7 @@ const (
|
|||
KeyType_ECDSA_P521
|
||||
KeyType_AES128_GCM96
|
||||
KeyType_RSA3072
|
||||
KeyType_HMAC
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -160,6 +164,8 @@ func (kt KeyType) String() string {
|
|||
return "rsa-3072"
|
||||
case KeyType_RSA4096:
|
||||
return "rsa-4096"
|
||||
case KeyType_HMAC:
|
||||
return "hmac"
|
||||
}
|
||||
|
||||
return "[unknown]"
|
||||
|
@ -318,9 +324,10 @@ type Policy struct {
|
|||
// served after a delete.
|
||||
deleted uint32
|
||||
|
||||
Name string `json:"name"`
|
||||
Key []byte `json:"key,omitempty"` // DEPRECATED
|
||||
Keys keyEntryMap `json:"keys"`
|
||||
Name string `json:"name"`
|
||||
Key []byte `json:"key,omitempty"` // DEPRECATED
|
||||
KeySize int `json:"key_size,omitempty"` // For algorithms with variable key sizes
|
||||
Keys keyEntryMap `json:"keys"`
|
||||
|
||||
// Derived keys MUST provide a context and the master underlying key is
|
||||
// never used. If convergent encryption is true, the context will be used
|
||||
|
@ -1025,10 +1032,13 @@ func (p *Policy) HMACKey(version int) ([]byte, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.Type == KeyType_HMAC {
|
||||
return keyEntry.Key, nil
|
||||
}
|
||||
if keyEntry.HMACKey == nil {
|
||||
return nil, fmt.Errorf("no HMAC key exists for that key version")
|
||||
}
|
||||
|
||||
return keyEntry.HMACKey, nil
|
||||
}
|
||||
|
||||
|
@ -1386,19 +1396,25 @@ func (p *Policy) Import(ctx context.Context, storage logical.Storage, key []byte
|
|||
DeprecatedCreationTime: now.Unix(),
|
||||
}
|
||||
|
||||
hmacKey, err := uuid.GenerateRandomBytesWithReader(32, randReader)
|
||||
if err != nil {
|
||||
return err
|
||||
if p.Type != KeyType_HMAC {
|
||||
hmacKey, err := uuid.GenerateRandomBytesWithReader(32, randReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entry.HMACKey = hmacKey
|
||||
}
|
||||
entry.HMACKey = hmacKey
|
||||
|
||||
if (p.Type == KeyType_AES128_GCM96 && len(key) != 16) ||
|
||||
((p.Type == KeyType_AES256_GCM96 || p.Type == KeyType_ChaCha20_Poly1305) && len(key) != 32) {
|
||||
((p.Type == KeyType_AES256_GCM96 || p.Type == KeyType_ChaCha20_Poly1305) && len(key) != 32) ||
|
||||
(p.Type == KeyType_HMAC && (len(key) < HmacMinKeySize || len(key) > HmacMaxKeySize)) {
|
||||
return fmt.Errorf("invalid key size %d bytes for key type %s", len(key), p.Type)
|
||||
}
|
||||
|
||||
if p.Type == KeyType_AES128_GCM96 || p.Type == KeyType_AES256_GCM96 || p.Type == KeyType_ChaCha20_Poly1305 {
|
||||
if p.Type == KeyType_AES128_GCM96 || p.Type == KeyType_AES256_GCM96 || p.Type == KeyType_ChaCha20_Poly1305 || p.Type == KeyType_HMAC {
|
||||
entry.Key = key
|
||||
if p.Type == KeyType_HMAC {
|
||||
p.KeySize = len(key)
|
||||
}
|
||||
} else {
|
||||
parsedPrivateKey, err := x509.ParsePKCS8PrivateKey(key)
|
||||
if err != nil {
|
||||
|
@ -1549,11 +1565,16 @@ func (p *Policy) RotateInMemory(randReader io.Reader) (retErr error) {
|
|||
entry.HMACKey = hmacKey
|
||||
|
||||
switch p.Type {
|
||||
case KeyType_AES128_GCM96, KeyType_AES256_GCM96, KeyType_ChaCha20_Poly1305:
|
||||
case KeyType_AES128_GCM96, KeyType_AES256_GCM96, KeyType_ChaCha20_Poly1305, KeyType_HMAC:
|
||||
// Default to 256 bit key
|
||||
numBytes := 32
|
||||
if p.Type == KeyType_AES128_GCM96 {
|
||||
numBytes = 16
|
||||
} else if p.Type == KeyType_HMAC {
|
||||
numBytes := p.KeySize
|
||||
if numBytes < HmacMinKeySize || numBytes > HmacMaxKeySize {
|
||||
return fmt.Errorf("invalid key size for HMAC key, must be between %d and %d bytes", HmacMinKeySize, HmacMaxKeySize)
|
||||
}
|
||||
}
|
||||
newKey, err := uuid.GenerateRandomBytesWithReader(numBytes, randReader)
|
||||
if err != nil {
|
||||
|
|
|
@ -63,10 +63,19 @@ values set here cannot be changed after key creation.
|
|||
- `rsa-2048` - RSA with bit size of 2048 (asymmetric)
|
||||
- `rsa-3072` - RSA with bit size of 3072 (asymmetric)
|
||||
- `rsa-4096` - RSA with bit size of 4096 (asymmetric)
|
||||
- `hmac` - HMAC (HMAC generation, verification)
|
||||
|
||||
~> **Note**: In FIPS 140-2 mode, the following algorithms are not certified
|
||||
and thus should not be used: `chacha20-poly1305` and `ed25519`.
|
||||
|
||||
~> **Note**: All key types support HMAC through the use of a second randomly
|
||||
generated key created key creation time or rotation. The HMAC key type only
|
||||
supports HMAC, and behaves identically to other algorithms with
|
||||
respect to the HMAC operations but supports key import. By default,
|
||||
the HMAC key type uses a 256-bit key.
|
||||
- `key_size` `(int: "0", optional)` - The key size in bytes for algorithms
|
||||
that allow variable key sizes. Currently only applicable to HMAC, where
|
||||
it must be between 16 and 512 bytes.
|
||||
- `auto_rotate_period` `(duration: "0", optional)` – The period at which
|
||||
this key should be rotated automatically. Setting this to "0" (the default)
|
||||
will disable automatic key rotation. This value cannot be shorter than one
|
||||
|
@ -431,6 +440,9 @@ ciphertext to be encrypted with the latest version of the key, use the `rewrap`
|
|||
endpoint. This is only supported with keys that support encryption and
|
||||
decryption operations.
|
||||
|
||||
For algorithms with a configurable key size, the rotated key will use the same
|
||||
key size as the previous version.
|
||||
|
||||
~> **Note**: For imported keys, rotation is only supported if the
|
||||
`allow_rotation` field was set to `true` on import. Once an imported key is
|
||||
rotated within Vault, it will not support further import operations.
|
||||
|
|
|
@ -85,10 +85,17 @@ types also generate separate HMAC keys):
|
|||
signature verification
|
||||
- `rsa-4096`: 4096-bit RSA key; supports encryption, decryption, signing, and
|
||||
signature verification
|
||||
- `hmac`: HMAC; supporting HMAC generation and verification.
|
||||
|
||||
~> **Note**: In FIPS 140-2 mode, the following algorithms are not certified
|
||||
and thus should not be used: `chacha20-poly1305` and `ed25519`.
|
||||
|
||||
~> **Note**: All key types support HMAC operations through the use of a second randomly
|
||||
generated key created key creation time or rotation. The HMAC key type only
|
||||
supports HMAC, and behaves identically to other algorithms with
|
||||
respect to the HMAC operations but supports key import. By default,
|
||||
the HMAC key type uses a 256-bit key.
|
||||
|
||||
## Convergent Encryption
|
||||
|
||||
Convergent encryption is a mode where the same set of plaintext+context always
|
||||
|
|
Loading…
Reference in New Issue