Transit convergent v3
This commit is contained in:
parent
11e2fd2fce
commit
4b7d2bed01
|
@ -12,6 +12,12 @@ SECURITY:
|
||||||
future issues. In addition, the logic we have put in place ensures that such
|
future issues. In addition, the logic we have put in place ensures that such
|
||||||
lease-less tokens can no longer be used (unless they are root tokens that
|
lease-less tokens can no longer be used (unless they are root tokens that
|
||||||
never had an expiration to begin with).
|
never had an expiration to begin with).
|
||||||
|
* Convergent Encryption: The version 2 algorithm used in `transit`'s
|
||||||
|
convergent encryption feature is susceptible to offline chosen plaintext
|
||||||
|
attacks. As a result, we are introducing a version 3 algorithm that
|
||||||
|
mitigates this. If you are currently using convergent encryption, we
|
||||||
|
recommend upgrading, rotating your encryption key (the new key version will
|
||||||
|
use the new algorithm), and rewrapping your data.
|
||||||
* AppRole case-sensitive role name secret-id leaking: When using a mixed-case
|
* AppRole case-sensitive role name secret-id leaking: When using a mixed-case
|
||||||
role name via AppRole, deleting a secret-id via accessor or other operations
|
role name via AppRole, deleting a secret-id via accessor or other operations
|
||||||
could end up leaving the secret-id behind and valid but without an accessor.
|
could end up leaving the secret-id behind and valid but without an accessor.
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -865,12 +866,12 @@ func testDerivedKeyUpgrade(t *testing.T, keyType keysutil.KeyType) {
|
||||||
t.Fatalf("bad KDF value by default; counter val is %d, KDF val is %d, policy is %#v", keysutil.Kdf_hmac_sha256_counter, p.KDF, *p)
|
t.Fatalf("bad KDF value by default; counter val is %d, KDF val is %d, policy is %#v", keysutil.Kdf_hmac_sha256_counter, p.KDF, *p)
|
||||||
}
|
}
|
||||||
|
|
||||||
derBytesOld, err := p.DeriveKey(keyContext, 1)
|
derBytesOld, err := p.DeriveKey(keyContext, 1, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
derBytesOld2, err := p.DeriveKey(keyContext, 1)
|
derBytesOld2, err := p.DeriveKey(keyContext, 1, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -884,12 +885,12 @@ func testDerivedKeyUpgrade(t *testing.T, keyType keysutil.KeyType) {
|
||||||
t.Fatal("expected no upgrade needed")
|
t.Fatal("expected no upgrade needed")
|
||||||
}
|
}
|
||||||
|
|
||||||
derBytesNew, err := p.DeriveKey(keyContext, 1)
|
derBytesNew, err := p.DeriveKey(keyContext, 1, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
derBytesNew2, err := p.DeriveKey(keyContext, 1)
|
derBytesNew2, err := p.DeriveKey(keyContext, 1, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -907,6 +908,8 @@ func TestConvergentEncryption(t *testing.T) {
|
||||||
testConvergentEncryptionCommon(t, 0, keysutil.KeyType_AES256_GCM96)
|
testConvergentEncryptionCommon(t, 0, keysutil.KeyType_AES256_GCM96)
|
||||||
testConvergentEncryptionCommon(t, 2, keysutil.KeyType_AES256_GCM96)
|
testConvergentEncryptionCommon(t, 2, keysutil.KeyType_AES256_GCM96)
|
||||||
testConvergentEncryptionCommon(t, 2, keysutil.KeyType_ChaCha20_Poly1305)
|
testConvergentEncryptionCommon(t, 2, keysutil.KeyType_ChaCha20_Poly1305)
|
||||||
|
testConvergentEncryptionCommon(t, 3, keysutil.KeyType_AES256_GCM96)
|
||||||
|
testConvergentEncryptionCommon(t, 3, keysutil.KeyType_ChaCha20_Poly1305)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testConvergentEncryptionCommon(t *testing.T, ver int, keyType keysutil.KeyType) {
|
func testConvergentEncryptionCommon(t *testing.T, ver int, keyType keysutil.KeyType) {
|
||||||
|
@ -928,7 +931,6 @@ func testConvergentEncryptionCommon(t *testing.T, ver int, keyType keysutil.KeyT
|
||||||
"convergent_encryption": true,
|
"convergent_encryption": true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := b.HandleRequest(context.Background(), req)
|
resp, err := b.HandleRequest(context.Background(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -940,18 +942,69 @@ func testConvergentEncryptionCommon(t *testing.T, ver int, keyType keysutil.KeyT
|
||||||
t.Fatalf("bad: expected error response, got %#v", *resp)
|
t.Fatalf("bad: expected error response, got %#v", *resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := &keysutil.Policy{
|
req = &logical.Request{
|
||||||
Name: "testkey",
|
Storage: storage,
|
||||||
Type: keyType,
|
Operation: logical.UpdateOperation,
|
||||||
Derived: true,
|
Path: "keys/testkey",
|
||||||
ConvergentEncryption: true,
|
Data: map[string]interface{}{
|
||||||
ConvergentVersion: ver,
|
"derived": true,
|
||||||
|
"convergent_encryption": true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
resp, err = b.HandleRequest(context.Background(), req)
|
||||||
err = p.Rotate(context.Background(), storage)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if resp != nil {
|
||||||
|
t.Fatal("expected nil response")
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := keysutil.LoadPolicy(context.Background(), storage, path.Join("policy", "testkey"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if p == nil {
|
||||||
|
t.Fatal("got nil policy")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ver > 2 {
|
||||||
|
p.ConvergentVersion = -1
|
||||||
|
} else {
|
||||||
|
p.ConvergentVersion = ver
|
||||||
|
}
|
||||||
|
err = p.Persist(context.Background(), storage)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b.invalidate(context.Background(), "policy/testkey")
|
||||||
|
|
||||||
|
if ver < 3 {
|
||||||
|
// There will be an embedded key version of 3, so specifically clear it
|
||||||
|
key := p.Keys[strconv.Itoa(p.LatestVersion)]
|
||||||
|
key.ConvergentVersion = 0
|
||||||
|
p.Keys[strconv.Itoa(p.LatestVersion)] = key
|
||||||
|
err = p.Persist(context.Background(), storage)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b.invalidate(context.Background(), "policy/testkey")
|
||||||
|
|
||||||
|
// Verify it
|
||||||
|
p, err = keysutil.LoadPolicy(context.Background(), storage, path.Join(p.StoragePrefix, "policy", "testkey"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if p == nil {
|
||||||
|
t.Fatal("got nil policy")
|
||||||
|
}
|
||||||
|
if p.ConvergentVersion != ver {
|
||||||
|
t.Fatalf("bad convergent version %d", p.ConvergentVersion)
|
||||||
|
}
|
||||||
|
key = p.Keys[strconv.Itoa(p.LatestVersion)]
|
||||||
|
if key.ConvergentVersion != 0 {
|
||||||
|
t.Fatalf("bad convergent key version %d", key.ConvergentVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// First, test using an invalid length of nonce -- this is only used for v1 convergent
|
// First, test using an invalid length of nonce -- this is only used for v1 convergent
|
||||||
req.Path = "encrypt/testkey"
|
req.Path = "encrypt/testkey"
|
||||||
|
@ -963,7 +1016,7 @@ func testConvergentEncryptionCommon(t *testing.T, ver int, keyType keysutil.KeyT
|
||||||
}
|
}
|
||||||
resp, err = b.HandleRequest(context.Background(), req)
|
resp, err = b.HandleRequest(context.Background(), req)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error, got nil")
|
t.Fatalf("expected error, got nil, version is %d", ver)
|
||||||
}
|
}
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
t.Fatal("expected non-nil response")
|
t.Fatal("expected non-nil response")
|
||||||
|
@ -1100,6 +1153,80 @@ func testConvergentEncryptionCommon(t *testing.T, ver int, keyType keysutil.KeyT
|
||||||
t.Fatalf("expected different ciphertexts")
|
t.Fatalf("expected different ciphertexts")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If running version 2, check upgrade handling
|
||||||
|
if ver == 2 {
|
||||||
|
curr, err := keysutil.LoadPolicy(context.Background(), storage, path.Join(p.StoragePrefix, "policy", "testkey"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if curr == nil {
|
||||||
|
t.Fatal("got nil policy")
|
||||||
|
}
|
||||||
|
if curr.ConvergentVersion != 2 {
|
||||||
|
t.Fatalf("bad convergent version %d", curr.ConvergentVersion)
|
||||||
|
}
|
||||||
|
key := curr.Keys[strconv.Itoa(curr.LatestVersion)]
|
||||||
|
if key.ConvergentVersion != 0 {
|
||||||
|
t.Fatalf("bad convergent key version %d", key.ConvergentVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
curr.ConvergentVersion = 3
|
||||||
|
err = curr.Persist(context.Background(), storage)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b.invalidate(context.Background(), "policy/testkey")
|
||||||
|
|
||||||
|
// Different algorithm, should be different value
|
||||||
|
resp, err = b.HandleRequest(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatal("expected non-nil response")
|
||||||
|
}
|
||||||
|
if resp.IsError() {
|
||||||
|
t.Fatalf("got error response: %#v", *resp)
|
||||||
|
}
|
||||||
|
ciphertext7 := resp.Data["ciphertext"].(string)
|
||||||
|
|
||||||
|
// Now do it via key-specified version
|
||||||
|
if len(curr.Keys) != 1 {
|
||||||
|
t.Fatalf("unexpected length of keys %d", len(curr.Keys))
|
||||||
|
}
|
||||||
|
key = curr.Keys[strconv.Itoa(curr.LatestVersion)]
|
||||||
|
key.ConvergentVersion = 3
|
||||||
|
curr.Keys[strconv.Itoa(curr.LatestVersion)] = key
|
||||||
|
curr.ConvergentVersion = 2
|
||||||
|
err = curr.Persist(context.Background(), storage)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b.invalidate(context.Background(), "policy/testkey")
|
||||||
|
|
||||||
|
resp, err = b.HandleRequest(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatal("expected non-nil response")
|
||||||
|
}
|
||||||
|
if resp.IsError() {
|
||||||
|
t.Fatalf("got error response: %#v", *resp)
|
||||||
|
}
|
||||||
|
ciphertext8 := resp.Data["ciphertext"].(string)
|
||||||
|
|
||||||
|
if ciphertext7 != ciphertext8 {
|
||||||
|
t.Fatalf("expected the same ciphertext but got %s and %s", ciphertext7, ciphertext8)
|
||||||
|
}
|
||||||
|
if ciphertext6 == ciphertext7 {
|
||||||
|
t.Fatalf("expected different ciphertexts")
|
||||||
|
}
|
||||||
|
if ciphertext3 == ciphertext7 {
|
||||||
|
t.Fatalf("expected different ciphertexts")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Finally, check operations on empty values
|
// Finally, check operations on empty values
|
||||||
// First, check without setting a plaintext at all
|
// First, check without setting a plaintext at all
|
||||||
req.Data = map[string]interface{}{
|
req.Data = map[string]interface{}{
|
||||||
|
|
|
@ -280,7 +280,7 @@ func (b *backend) pathPolicyRead(ctx context.Context, req *logical.Request, d *f
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errwrap.Wrapf(fmt.Sprintf("invalid version %q: {{err}}", k), err)
|
return nil, errwrap.Wrapf(fmt.Sprintf("invalid version %q: {{err}}", k), err)
|
||||||
}
|
}
|
||||||
derived, err := p.DeriveKey(context, ver)
|
derived, err := p.DeriveKey(context, ver, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to derive key to return public component")
|
return nil, fmt.Errorf("failed to derive key to return public component")
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,10 +79,6 @@ func NewEncryptedKeyStorageWrapper(config EncryptedKeyStorageConfig) (*Encrypted
|
||||||
return nil, ErrPolicyConvergentEncryption
|
return nil, ErrPolicyConvergentEncryption
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Policy.ConvergentVersion < 2 {
|
|
||||||
return nil, ErrPolicyConvergentVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Prefix == "" {
|
if config.Prefix == "" {
|
||||||
config.Prefix = DefaultPrefix
|
config.Prefix = DefaultPrefix
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/helper/strutil"
|
||||||
"github.com/hashicorp/vault/logical"
|
"github.com/hashicorp/vault/logical"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -91,16 +91,14 @@ func TestBase58(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncrytedKeysStorage_BadPolicy(t *testing.T) {
|
func TestEncrytedKeysStorage_BadPolicy(t *testing.T) {
|
||||||
policy := &Policy{
|
policy := NewPolicy(PolicyConfig{
|
||||||
Name: "metadata",
|
Name: "metadata",
|
||||||
Type: KeyType_AES256_GCM96,
|
Type: KeyType_AES256_GCM96,
|
||||||
Derived: false,
|
Derived: false,
|
||||||
KDF: Kdf_hkdf_sha256,
|
KDF: Kdf_hkdf_sha256,
|
||||||
ConvergentEncryption: true,
|
ConvergentEncryption: true,
|
||||||
ConvergentVersion: 2,
|
|
||||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||||
versionPrefixCache: &sync.Map{},
|
})
|
||||||
}
|
|
||||||
|
|
||||||
_, err := NewEncryptedKeyStorageWrapper(EncryptedKeyStorageConfig{
|
_, err := NewEncryptedKeyStorageWrapper(EncryptedKeyStorageConfig{
|
||||||
Policy: policy,
|
Policy: policy,
|
||||||
|
@ -110,16 +108,14 @@ func TestEncrytedKeysStorage_BadPolicy(t *testing.T) {
|
||||||
t.Fatalf("Unexpected Error: %s", err)
|
t.Fatalf("Unexpected Error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
policy = &Policy{
|
policy = NewPolicy(PolicyConfig{
|
||||||
Name: "metadata",
|
Name: "metadata",
|
||||||
Type: KeyType_AES256_GCM96,
|
Type: KeyType_AES256_GCM96,
|
||||||
Derived: true,
|
Derived: true,
|
||||||
KDF: Kdf_hkdf_sha256,
|
KDF: Kdf_hkdf_sha256,
|
||||||
ConvergentEncryption: false,
|
ConvergentEncryption: false,
|
||||||
ConvergentVersion: 2,
|
|
||||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||||
versionPrefixCache: &sync.Map{},
|
})
|
||||||
}
|
|
||||||
|
|
||||||
_, err = NewEncryptedKeyStorageWrapper(EncryptedKeyStorageConfig{
|
_, err = NewEncryptedKeyStorageWrapper(EncryptedKeyStorageConfig{
|
||||||
Policy: policy,
|
Policy: policy,
|
||||||
|
@ -129,49 +125,33 @@ func TestEncrytedKeysStorage_BadPolicy(t *testing.T) {
|
||||||
t.Fatalf("Unexpected Error: %s", err)
|
t.Fatalf("Unexpected Error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
policy = &Policy{
|
policy = NewPolicy(PolicyConfig{
|
||||||
Name: "metadata",
|
Name: "metadata",
|
||||||
Type: KeyType_AES256_GCM96,
|
Type: KeyType_AES256_GCM96,
|
||||||
Derived: true,
|
Derived: true,
|
||||||
KDF: Kdf_hkdf_sha256,
|
KDF: Kdf_hkdf_sha256,
|
||||||
ConvergentEncryption: true,
|
ConvergentEncryption: true,
|
||||||
ConvergentVersion: 1,
|
|
||||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||||
versionPrefixCache: &sync.Map{},
|
})
|
||||||
}
|
|
||||||
|
|
||||||
_, err = NewEncryptedKeyStorageWrapper(EncryptedKeyStorageConfig{
|
_, err = NewEncryptedKeyStorageWrapper(EncryptedKeyStorageConfig{
|
||||||
Policy: policy,
|
Policy: policy,
|
||||||
Prefix: "prefix",
|
Prefix: "prefix",
|
||||||
})
|
})
|
||||||
if err != ErrPolicyConvergentVersion {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected Error: %s", err)
|
t.Fatalf("Unexpected Error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
policy = &Policy{
|
|
||||||
Name: "metadata",
|
|
||||||
Type: KeyType_AES256_GCM96,
|
|
||||||
Derived: true,
|
|
||||||
KDF: Kdf_hkdf_sha256,
|
|
||||||
ConvergentEncryption: true,
|
|
||||||
ConvergentVersion: 2,
|
|
||||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
|
||||||
versionPrefixCache: &sync.Map{},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncryptedKeysStorage_List(t *testing.T) {
|
func TestEncryptedKeysStorage_List(t *testing.T) {
|
||||||
s := &logical.InmemStorage{}
|
s := &logical.InmemStorage{}
|
||||||
policy := &Policy{
|
policy := NewPolicy(PolicyConfig{
|
||||||
Name: "metadata",
|
Name: "metadata",
|
||||||
Type: KeyType_AES256_GCM96,
|
Type: KeyType_AES256_GCM96,
|
||||||
Derived: true,
|
Derived: true,
|
||||||
KDF: Kdf_hkdf_sha256,
|
KDF: Kdf_hkdf_sha256,
|
||||||
ConvergentEncryption: true,
|
ConvergentEncryption: true,
|
||||||
ConvergentVersion: 2,
|
|
||||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||||
versionPrefixCache: &sync.Map{},
|
})
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
@ -223,7 +203,7 @@ func TestEncryptedKeysStorage_List(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(keys) != 2 || keys[0] != "foo1/" || keys[1] != "foo" {
|
if len(keys) != 2 || !strutil.StrListContains(keys, "foo1/") || !strutil.StrListContains(keys, "foo") {
|
||||||
t.Fatalf("bad keys: %#v", keys)
|
t.Fatalf("bad keys: %#v", keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,7 +211,7 @@ func TestEncryptedKeysStorage_List(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if len(keys) != 2 || keys[0] != "test" || keys[1] != "test/" {
|
if len(keys) != 2 || !strutil.StrListContains(keys, "test") || !strutil.StrListContains(keys, "test/") {
|
||||||
t.Fatalf("bad keys: %#v", keys)
|
t.Fatalf("bad keys: %#v", keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,16 +226,14 @@ func TestEncryptedKeysStorage_List(t *testing.T) {
|
||||||
|
|
||||||
func TestEncryptedKeysStorage_CRUD(t *testing.T) {
|
func TestEncryptedKeysStorage_CRUD(t *testing.T) {
|
||||||
s := &logical.InmemStorage{}
|
s := &logical.InmemStorage{}
|
||||||
policy := &Policy{
|
policy := NewPolicy(PolicyConfig{
|
||||||
Name: "metadata",
|
Name: "metadata",
|
||||||
Type: KeyType_AES256_GCM96,
|
Type: KeyType_AES256_GCM96,
|
||||||
Derived: true,
|
Derived: true,
|
||||||
KDF: Kdf_hkdf_sha256,
|
KDF: Kdf_hkdf_sha256,
|
||||||
ConvergentEncryption: true,
|
ConvergentEncryption: true,
|
||||||
ConvergentVersion: 2,
|
|
||||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||||
versionPrefixCache: &sync.Map{},
|
})
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
@ -299,7 +277,7 @@ func TestEncryptedKeysStorage_CRUD(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(keys) != 2 || keys[0] != "foo1/" || keys[1] != "foo" {
|
if len(keys) != 2 || !strutil.StrListContains(keys, "foo1/") || !strutil.StrListContains(keys, "foo") {
|
||||||
t.Fatalf("bad keys: %#v", keys)
|
t.Fatalf("bad keys: %#v", keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,7 +287,7 @@ func TestEncryptedKeysStorage_CRUD(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(keys) != 2 || keys[0] != "foo1/" || keys[1] != "foo" {
|
if len(keys) != 2 || !strutil.StrListContains(keys, "foo1/") || !strutil.StrListContains(keys, "foo") {
|
||||||
t.Fatalf("bad keys: %#v", keys)
|
t.Fatalf("bad keys: %#v", keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,16 +316,14 @@ func TestEncryptedKeysStorage_CRUD(t *testing.T) {
|
||||||
|
|
||||||
func BenchmarkEncrytedKeyStorage_List(b *testing.B) {
|
func BenchmarkEncrytedKeyStorage_List(b *testing.B) {
|
||||||
s := &logical.InmemStorage{}
|
s := &logical.InmemStorage{}
|
||||||
policy := &Policy{
|
policy := NewPolicy(PolicyConfig{
|
||||||
Name: "metadata",
|
Name: "metadata",
|
||||||
Type: KeyType_AES256_GCM96,
|
Type: KeyType_AES256_GCM96,
|
||||||
Derived: true,
|
Derived: true,
|
||||||
KDF: Kdf_hkdf_sha256,
|
KDF: Kdf_hkdf_sha256,
|
||||||
ConvergentEncryption: true,
|
ConvergentEncryption: true,
|
||||||
ConvergentVersion: 2,
|
|
||||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||||
versionPrefixCache: &sync.Map{},
|
})
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
@ -386,16 +362,14 @@ func BenchmarkEncrytedKeyStorage_List(b *testing.B) {
|
||||||
|
|
||||||
func BenchmarkEncrytedKeyStorage_Put(b *testing.B) {
|
func BenchmarkEncrytedKeyStorage_Put(b *testing.B) {
|
||||||
s := &logical.InmemStorage{}
|
s := &logical.InmemStorage{}
|
||||||
policy := &Policy{
|
policy := NewPolicy(PolicyConfig{
|
||||||
Name: "metadata",
|
Name: "metadata",
|
||||||
Type: KeyType_AES256_GCM96,
|
Type: KeyType_AES256_GCM96,
|
||||||
Derived: true,
|
Derived: true,
|
||||||
KDF: Kdf_hkdf_sha256,
|
KDF: Kdf_hkdf_sha256,
|
||||||
ConvergentEncryption: true,
|
ConvergentEncryption: true,
|
||||||
ConvergentVersion: 2,
|
|
||||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||||
versionPrefixCache: &sync.Map{},
|
})
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
shared = false
|
shared = false
|
||||||
exclusive = true
|
exclusive = true
|
||||||
|
currentConvergentVersion = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -395,8 +396,15 @@ func (lm *LockManager) getPolicyCommon(ctx context.Context, req PolicyRequest, l
|
||||||
}
|
}
|
||||||
if req.Derived {
|
if req.Derived {
|
||||||
p.KDF = Kdf_hkdf_sha256
|
p.KDF = Kdf_hkdf_sha256
|
||||||
p.ConvergentEncryption = req.Convergent
|
if req.Convergent {
|
||||||
p.ConvergentVersion = 2
|
p.ConvergentEncryption = true
|
||||||
|
// As of version 3 we store the version within each key, so we
|
||||||
|
// set to -1 to indicate that the value in the policy has no
|
||||||
|
// meaning. We still, for backwards compatibility, fall back to
|
||||||
|
// this value if the key doesn't have one, which means it will
|
||||||
|
// only be -1 in the case where every key version is >= 3
|
||||||
|
p.ConvergentVersion = -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.Rotate(ctx, req.Storage)
|
err = p.Rotate(ctx, req.Storage)
|
||||||
|
|
|
@ -169,6 +169,10 @@ type KeyEntry struct {
|
||||||
// The public key in an appropriate format for the type of key
|
// The public key in an appropriate format for the type of key
|
||||||
FormattedPublicKey string `json:"public_key"`
|
FormattedPublicKey string `json:"public_key"`
|
||||||
|
|
||||||
|
// If convergent is enabled, the version (falling back to what's in the
|
||||||
|
// policy)
|
||||||
|
ConvergentVersion int `json:"convergent_version"`
|
||||||
|
|
||||||
// This is deprecated (but still filled) in favor of the value above which
|
// This is deprecated (but still filled) in favor of the value above which
|
||||||
// is more precise
|
// is more precise
|
||||||
DeprecatedCreationTime int64 `json:"creation_time"`
|
DeprecatedCreationTime int64 `json:"creation_time"`
|
||||||
|
@ -215,8 +219,7 @@ type PolicyConfig struct {
|
||||||
Type KeyType
|
Type KeyType
|
||||||
|
|
||||||
// Derived keys MUST provide a context and the master underlying key is
|
// Derived keys MUST provide a context and the master underlying key is
|
||||||
// never used. If convergent encryption is true, the context will be used
|
// never used.
|
||||||
// as the nonce as well.
|
|
||||||
Derived bool
|
Derived bool
|
||||||
KDF int
|
KDF int
|
||||||
ConvergentEncryption bool
|
ConvergentEncryption bool
|
||||||
|
@ -242,18 +245,13 @@ type PolicyConfig struct {
|
||||||
|
|
||||||
// NewPolicy takes a policy config and returns a Policy with those settings.
|
// NewPolicy takes a policy config and returns a Policy with those settings.
|
||||||
func NewPolicy(config PolicyConfig) *Policy {
|
func NewPolicy(config PolicyConfig) *Policy {
|
||||||
var convergentVersion int
|
|
||||||
if config.ConvergentEncryption {
|
|
||||||
convergentVersion = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Policy{
|
return &Policy{
|
||||||
Name: config.Name,
|
Name: config.Name,
|
||||||
Type: config.Type,
|
Type: config.Type,
|
||||||
Derived: config.Derived,
|
Derived: config.Derived,
|
||||||
KDF: config.KDF,
|
KDF: config.KDF,
|
||||||
ConvergentEncryption: config.ConvergentEncryption,
|
ConvergentEncryption: config.ConvergentEncryption,
|
||||||
ConvergentVersion: convergentVersion,
|
ConvergentVersion: -1,
|
||||||
Exportable: config.Exportable,
|
Exportable: config.Exportable,
|
||||||
DeletionAllowed: config.DeletionAllowed,
|
DeletionAllowed: config.DeletionAllowed,
|
||||||
AllowPlaintextBackup: config.AllowPlaintextBackup,
|
AllowPlaintextBackup: config.AllowPlaintextBackup,
|
||||||
|
@ -544,7 +542,8 @@ func (p *Policy) NeedsUpgrade() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to write the version
|
// Need to write the version if zero; for version 3 on we set this to -1 to
|
||||||
|
// ignore it since we store this information in each key entry
|
||||||
if p.ConvergentEncryption && p.ConvergentVersion == 0 {
|
if p.ConvergentEncryption && p.ConvergentVersion == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -636,7 +635,12 @@ func (p *Policy) Upgrade(ctx context.Context, storage logical.Storage) (retErr e
|
||||||
// on the policy. If derivation is disabled the raw key is used and no context
|
// on the policy. If derivation is disabled the raw key is used and no context
|
||||||
// is required, otherwise the KDF mode is used with the context to derive the
|
// is required, otherwise the KDF mode is used with the context to derive the
|
||||||
// proper key.
|
// proper key.
|
||||||
func (p *Policy) DeriveKey(context []byte, ver int) ([]byte, error) {
|
func (p *Policy) DeriveKey(context []byte, ver, numBytes int) ([]byte, error) {
|
||||||
|
// Fast-path non-derived keys
|
||||||
|
if !p.Derived {
|
||||||
|
return p.Keys[strconv.Itoa(ver)].Key, nil
|
||||||
|
}
|
||||||
|
|
||||||
if !p.Type.DerivationSupported() {
|
if !p.Type.DerivationSupported() {
|
||||||
return nil, errutil.UserError{Err: fmt.Sprintf("derivation not supported for key type %v", p.Type)}
|
return nil, errutil.UserError{Err: fmt.Sprintf("derivation not supported for key type %v", p.Type)}
|
||||||
}
|
}
|
||||||
|
@ -649,11 +653,6 @@ func (p *Policy) DeriveKey(context []byte, ver int) ([]byte, error) {
|
||||||
return nil, errutil.UserError{Err: "invalid key version"}
|
return nil, errutil.UserError{Err: "invalid key version"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast-path non-derived keys
|
|
||||||
if !p.Derived {
|
|
||||||
return p.Keys[strconv.Itoa(ver)].Key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure a context is provided
|
// Ensure a context is provided
|
||||||
if len(context) == 0 {
|
if len(context) == 0 {
|
||||||
return nil, errutil.UserError{Err: "missing 'context' for key derivation; the key was created using a derived key, which means additional, per-request information must be included in order to perform operations with the key"}
|
return nil, errutil.UserError{Err: "missing 'context' for key derivation; the key was created using a derived key, which means additional, per-request information must be included in order to perform operations with the key"}
|
||||||
|
@ -668,10 +667,10 @@ func (p *Policy) DeriveKey(context []byte, ver int) ([]byte, error) {
|
||||||
case Kdf_hkdf_sha256:
|
case Kdf_hkdf_sha256:
|
||||||
reader := hkdf.New(sha256.New, p.Keys[strconv.Itoa(ver)].Key, nil, context)
|
reader := hkdf.New(sha256.New, p.Keys[strconv.Itoa(ver)].Key, nil, context)
|
||||||
derBytes := bytes.NewBuffer(nil)
|
derBytes := bytes.NewBuffer(nil)
|
||||||
derBytes.Grow(32)
|
derBytes.Grow(numBytes)
|
||||||
limReader := &io.LimitedReader{
|
limReader := &io.LimitedReader{
|
||||||
R: reader,
|
R: reader,
|
||||||
N: 32,
|
N: int64(numBytes),
|
||||||
}
|
}
|
||||||
|
|
||||||
switch p.Type {
|
switch p.Type {
|
||||||
|
@ -680,8 +679,8 @@ func (p *Policy) DeriveKey(context []byte, ver int) ([]byte, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errutil.InternalError{Err: fmt.Sprintf("error reading returned derived bytes: %v", err)}
|
return nil, errutil.InternalError{Err: fmt.Sprintf("error reading returned derived bytes: %v", err)}
|
||||||
}
|
}
|
||||||
if n != 32 {
|
if n != int64(numBytes) {
|
||||||
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to read enough derived bytes, needed 32, got %d", n)}
|
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to read enough derived bytes, needed %d, got %d", numBytes, n)}
|
||||||
}
|
}
|
||||||
return derBytes.Bytes(), nil
|
return derBytes.Bytes(), nil
|
||||||
|
|
||||||
|
@ -703,6 +702,24 @@ func (p *Policy) DeriveKey(context []byte, ver int) ([]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Policy) convergentVersion(ver int) int {
|
||||||
|
if !p.ConvergentEncryption {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
convergentVersion := p.ConvergentVersion
|
||||||
|
if convergentVersion == 0 {
|
||||||
|
// For some reason, not upgraded yet
|
||||||
|
convergentVersion = 1
|
||||||
|
}
|
||||||
|
currKey := p.Keys[strconv.Itoa(ver)]
|
||||||
|
if currKey.ConvergentVersion != 0 {
|
||||||
|
convergentVersion = currKey.ConvergentVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
return convergentVersion
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Policy) Encrypt(ver int, context, nonce []byte, value string) (string, error) {
|
func (p *Policy) Encrypt(ver int, context, nonce []byte, value string) (string, error) {
|
||||||
if !p.Type.EncryptionSupported() {
|
if !p.Type.EncryptionSupported() {
|
||||||
return "", errutil.UserError{Err: fmt.Sprintf("message encryption not supported for key type %v", p.Type)}
|
return "", errutil.UserError{Err: fmt.Sprintf("message encryption not supported for key type %v", p.Type)}
|
||||||
|
@ -729,18 +746,41 @@ func (p *Policy) Encrypt(ver int, context, nonce []byte, value string) (string,
|
||||||
|
|
||||||
switch p.Type {
|
switch p.Type {
|
||||||
case KeyType_AES256_GCM96, KeyType_ChaCha20_Poly1305:
|
case KeyType_AES256_GCM96, KeyType_ChaCha20_Poly1305:
|
||||||
// Derive the key that should be used
|
hmacKey := context
|
||||||
key, err := p.DeriveKey(context, ver)
|
|
||||||
|
var aead cipher.AEAD
|
||||||
|
var encKey []byte
|
||||||
|
var deriveHMAC bool
|
||||||
|
|
||||||
|
numBytes := 32
|
||||||
|
if p.convergentVersion(ver) > 2 {
|
||||||
|
deriveHMAC = true
|
||||||
|
numBytes = 64
|
||||||
|
}
|
||||||
|
key, err := p.DeriveKey(context, ver, numBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var aead cipher.AEAD
|
if len(key) < numBytes {
|
||||||
|
return "", errutil.InternalError{Err: "could not derive key, length too small"}
|
||||||
|
}
|
||||||
|
|
||||||
|
encKey = key[:32]
|
||||||
|
if len(encKey) != 32 {
|
||||||
|
return "", errutil.InternalError{Err: "could not derive enc key, length not correct"}
|
||||||
|
}
|
||||||
|
if deriveHMAC {
|
||||||
|
hmacKey = key[32:]
|
||||||
|
if len(hmacKey) != 32 {
|
||||||
|
return "", errutil.InternalError{Err: "could not derive hmac key, length not correct"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch p.Type {
|
switch p.Type {
|
||||||
case KeyType_AES256_GCM96:
|
case KeyType_AES256_GCM96:
|
||||||
// Setup the cipher
|
// Setup the cipher
|
||||||
aesCipher, err := aes.NewCipher(key)
|
aesCipher, err := aes.NewCipher(encKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errutil.InternalError{Err: err.Error()}
|
return "", errutil.InternalError{Err: err.Error()}
|
||||||
}
|
}
|
||||||
|
@ -754,7 +794,7 @@ func (p *Policy) Encrypt(ver int, context, nonce []byte, value string) (string,
|
||||||
aead = gcm
|
aead = gcm
|
||||||
|
|
||||||
case KeyType_ChaCha20_Poly1305:
|
case KeyType_ChaCha20_Poly1305:
|
||||||
cha, err := chacha20poly1305.New(key)
|
cha, err := chacha20poly1305.New(encKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errutil.InternalError{Err: err.Error()}
|
return "", errutil.InternalError{Err: err.Error()}
|
||||||
}
|
}
|
||||||
|
@ -763,16 +803,22 @@ func (p *Policy) Encrypt(ver int, context, nonce []byte, value string) (string,
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.ConvergentEncryption {
|
if p.ConvergentEncryption {
|
||||||
switch p.ConvergentVersion {
|
convergentVersion := p.convergentVersion(ver)
|
||||||
|
switch convergentVersion {
|
||||||
case 1:
|
case 1:
|
||||||
if len(nonce) != aead.NonceSize() {
|
if len(nonce) != aead.NonceSize() {
|
||||||
return "", errutil.UserError{Err: fmt.Sprintf("base64-decoded nonce must be %d bytes long when using convergent encryption with this key", aead.NonceSize())}
|
return "", errutil.UserError{Err: fmt.Sprintf("base64-decoded nonce must be %d bytes long when using convergent encryption with this key", aead.NonceSize())}
|
||||||
}
|
}
|
||||||
default:
|
case 2, 3:
|
||||||
nonceHmac := hmac.New(sha256.New, context)
|
if len(hmacKey) == 0 {
|
||||||
|
return "", errutil.InternalError{Err: fmt.Sprintf("invalid hmac key length of zero")}
|
||||||
|
}
|
||||||
|
nonceHmac := hmac.New(sha256.New, hmacKey)
|
||||||
nonceHmac.Write(plaintext)
|
nonceHmac.Write(plaintext)
|
||||||
nonceSum := nonceHmac.Sum(nil)
|
nonceSum := nonceHmac.Sum(nil)
|
||||||
nonce = nonceSum[:aead.NonceSize()]
|
nonce = nonceSum[:aead.NonceSize()]
|
||||||
|
default:
|
||||||
|
return "", errutil.InternalError{Err: fmt.Sprintf("unhandled convergent version %d", convergentVersion)}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Compute random nonce
|
// Compute random nonce
|
||||||
|
@ -786,7 +832,7 @@ func (p *Policy) Encrypt(ver int, context, nonce []byte, value string) (string,
|
||||||
ciphertext = aead.Seal(nil, nonce, plaintext, nil)
|
ciphertext = aead.Seal(nil, nonce, plaintext, nil)
|
||||||
|
|
||||||
// Place the encrypted data after the nonce
|
// Place the encrypted data after the nonce
|
||||||
if !p.ConvergentEncryption || p.ConvergentVersion > 1 {
|
if !p.ConvergentEncryption || p.convergentVersion(ver) > 1 {
|
||||||
ciphertext = append(nonce, ciphertext...)
|
ciphertext = append(nonce, ciphertext...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -825,10 +871,6 @@ func (p *Policy) Decrypt(context, nonce []byte, value string) (string, error) {
|
||||||
return "", errutil.UserError{Err: "invalid ciphertext: no prefix"}
|
return "", errutil.UserError{Err: "invalid ciphertext: no prefix"}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.ConvergentEncryption && p.ConvergentVersion == 1 && (nonce == nil || len(nonce) == 0) {
|
|
||||||
return "", errutil.UserError{Err: "invalid convergent nonce supplied"}
|
|
||||||
}
|
|
||||||
|
|
||||||
splitVerCiphertext := strings.SplitN(strings.TrimPrefix(value, tplParts[0]), tplParts[1], 2)
|
splitVerCiphertext := strings.SplitN(strings.TrimPrefix(value, tplParts[0]), tplParts[1], 2)
|
||||||
if len(splitVerCiphertext) != 2 {
|
if len(splitVerCiphertext) != 2 {
|
||||||
return "", errutil.UserError{Err: "invalid ciphertext: wrong number of fields"}
|
return "", errutil.UserError{Err: "invalid ciphertext: wrong number of fields"}
|
||||||
|
@ -853,6 +895,11 @@ func (p *Policy) Decrypt(context, nonce []byte, value string) (string, error) {
|
||||||
return "", errutil.UserError{Err: ErrTooOld}
|
return "", errutil.UserError{Err: ErrTooOld}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convergentVersion := p.convergentVersion(ver)
|
||||||
|
if convergentVersion == 1 && (nonce == nil || len(nonce) == 0) {
|
||||||
|
return "", errutil.UserError{Err: "invalid convergent nonce supplied"}
|
||||||
|
}
|
||||||
|
|
||||||
// Decode the base64
|
// Decode the base64
|
||||||
decoded, err := base64.StdEncoding.DecodeString(splitVerCiphertext[1])
|
decoded, err := base64.StdEncoding.DecodeString(splitVerCiphertext[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -863,17 +910,21 @@ func (p *Policy) Decrypt(context, nonce []byte, value string) (string, error) {
|
||||||
|
|
||||||
switch p.Type {
|
switch p.Type {
|
||||||
case KeyType_AES256_GCM96, KeyType_ChaCha20_Poly1305:
|
case KeyType_AES256_GCM96, KeyType_ChaCha20_Poly1305:
|
||||||
key, err := p.DeriveKey(context, ver)
|
var aead cipher.AEAD
|
||||||
|
|
||||||
|
encKey, err := p.DeriveKey(context, ver, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var aead cipher.AEAD
|
if len(encKey) != 32 {
|
||||||
|
return "", errutil.InternalError{Err: "could not derive enc key, length not correct"}
|
||||||
|
}
|
||||||
|
|
||||||
switch p.Type {
|
switch p.Type {
|
||||||
case KeyType_AES256_GCM96:
|
case KeyType_AES256_GCM96:
|
||||||
// Setup the cipher
|
// Setup the cipher
|
||||||
aesCipher, err := aes.NewCipher(key)
|
aesCipher, err := aes.NewCipher(encKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errutil.InternalError{Err: err.Error()}
|
return "", errutil.InternalError{Err: err.Error()}
|
||||||
}
|
}
|
||||||
|
@ -887,7 +938,7 @@ func (p *Policy) Decrypt(context, nonce []byte, value string) (string, error) {
|
||||||
aead = gcm
|
aead = gcm
|
||||||
|
|
||||||
case KeyType_ChaCha20_Poly1305:
|
case KeyType_ChaCha20_Poly1305:
|
||||||
cha, err := chacha20poly1305.New(key)
|
cha, err := chacha20poly1305.New(encKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errutil.InternalError{Err: err.Error()}
|
return "", errutil.InternalError{Err: err.Error()}
|
||||||
}
|
}
|
||||||
|
@ -901,7 +952,7 @@ func (p *Policy) Decrypt(context, nonce []byte, value string) (string, error) {
|
||||||
|
|
||||||
// Extract the nonce and ciphertext
|
// Extract the nonce and ciphertext
|
||||||
var ciphertext []byte
|
var ciphertext []byte
|
||||||
if p.ConvergentEncryption && p.ConvergentVersion < 2 {
|
if p.ConvergentEncryption && convergentVersion == 1 {
|
||||||
ciphertext = decoded
|
ciphertext = decoded
|
||||||
} else {
|
} else {
|
||||||
nonce = decoded[:aead.NonceSize()]
|
nonce = decoded[:aead.NonceSize()]
|
||||||
|
@ -992,7 +1043,7 @@ func (p *Policy) Sign(ver int, context, input []byte, hashAlgorithm, sigAlgorith
|
||||||
if p.Derived {
|
if p.Derived {
|
||||||
// Derive the key that should be used
|
// Derive the key that should be used
|
||||||
var err error
|
var err error
|
||||||
key, err = p.DeriveKey(context, ver)
|
key, err = p.DeriveKey(context, ver, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errutil.InternalError{Err: fmt.Sprintf("error deriving key: %v", err)}
|
return nil, errutil.InternalError{Err: fmt.Sprintf("error deriving key: %v", err)}
|
||||||
}
|
}
|
||||||
|
@ -1122,7 +1173,7 @@ func (p *Policy) VerifySignature(context, input []byte, sig, hashAlgorithm strin
|
||||||
if p.Derived {
|
if p.Derived {
|
||||||
// Derive the key that should be used
|
// Derive the key that should be used
|
||||||
var err error
|
var err error
|
||||||
key, err = p.DeriveKey(context, ver)
|
key, err = p.DeriveKey(context, ver, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errutil.InternalError{Err: fmt.Sprintf("error deriving key: %v", err)}
|
return false, errutil.InternalError{Err: fmt.Sprintf("error deriving key: %v", err)}
|
||||||
}
|
}
|
||||||
|
@ -1260,6 +1311,12 @@ func (p *Policy) Rotate(ctx context.Context, storage logical.Storage) (retErr er
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.ConvergentEncryption {
|
||||||
|
if p.ConvergentVersion == -1 || p.ConvergentVersion > 1 {
|
||||||
|
entry.ConvergentVersion = currentConvergentVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p.Keys[strconv.Itoa(p.LatestVersion)] = entry
|
p.Keys[strconv.Itoa(p.LatestVersion)] = entry
|
||||||
|
|
||||||
// This ensures that with new key creations min decryption version is set
|
// This ensures that with new key creations min decryption version is set
|
||||||
|
|
|
@ -66,6 +66,34 @@ types also generate separate HMAC keys):
|
||||||
* `rsa-4096`: 4096-bit RSA key; supports encryption, decryption, signing, and
|
* `rsa-4096`: 4096-bit RSA key; supports encryption, decryption, signing, and
|
||||||
signature verification
|
signature verification
|
||||||
|
|
||||||
|
## Convergent Encryption
|
||||||
|
|
||||||
|
Convergent encryption is a mode where the same set of plaintext+context always
|
||||||
|
result in the same ciphertext. It does this by deriving a key using a key
|
||||||
|
derivation function but also by deterministically deriving a nonce. Because
|
||||||
|
these properties differ for any combination of plaintext and ciphertext over a
|
||||||
|
keyspace the size of 2^256, the risk of nonce reuse is near zero.
|
||||||
|
|
||||||
|
This has many practical uses. A key usage mode is to allow values to be stored
|
||||||
|
encrypted in a database, but with limited lookup/query support, so that rows
|
||||||
|
with the same value for a specific field can be returned from a query.
|
||||||
|
|
||||||
|
To accommodate for any needed upgrades to the algorithm, different versions of
|
||||||
|
convergent encryption have historically been supported:
|
||||||
|
|
||||||
|
* Version 1 required the client to provide their own nonce, which is highly
|
||||||
|
flexible but if done incorrectly can be dangerous. This was only in Vault
|
||||||
|
0.6.1, and keys using this version cannot be upgraded.
|
||||||
|
* Version 2 used an algorithmic approach to deriving the parameters. However,
|
||||||
|
the algorithm used was susceptible to offline plaintext-confirmation attacks,
|
||||||
|
which could allow attackers to brute force decryption if the plaintext size
|
||||||
|
was small. Keys using version 2 can be upgraded by simply performing a rotate
|
||||||
|
operation to a new key version; existing values can then be rewrapped against
|
||||||
|
the new key version and will use the version 3 algorithm.
|
||||||
|
* Version 3 uses a different algorithm designed to be resistant to offline
|
||||||
|
plaintext-confirmation attacks. It is similar to AES-SIV in that it uses a
|
||||||
|
PRF to generate the nonce from the plaintext.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
Most secrets engines must be configured in advance before they can perform their
|
Most secrets engines must be configured in advance before they can perform their
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
layout: "guides"
|
||||||
|
page_title: "Upgrading to Vault 0.10.2 - Guides"
|
||||||
|
sidebar_current: "guides-upgrading-to-0.10.2"
|
||||||
|
description: |-
|
||||||
|
This page contains the list of deprecations and important or breaking changes
|
||||||
|
for Vault 0.10.2. Please read it carefully.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
This page contains the list of deprecations and important or breaking changes
|
||||||
|
for Vault 0.10.2 compared to 0.10.1. Please read it carefully.
|
||||||
|
|
||||||
|
### Convergent Encryption version 3
|
||||||
|
|
||||||
|
If you are using `transit`'s convergent encryption feature, which prior to this
|
||||||
|
release was at version 2, we recommend rotating your encryption key (the new
|
||||||
|
key will use version 3) and rewrapping your data to mitigate the chance of
|
||||||
|
offline plaintext-confirmation attacks.
|
||||||
|
|
||||||
|
### PKI duration return types
|
||||||
|
|
||||||
|
The PKI backend now returns durations (e.g. when reading a role) as an integer
|
||||||
|
number of seconds instead of a Go-style string.
|
|
@ -144,6 +144,9 @@
|
||||||
<li<%= sidebar_current("guides-upgrading-to-0.10.0") %>>
|
<li<%= sidebar_current("guides-upgrading-to-0.10.0") %>>
|
||||||
<a href="/guides/upgrading/upgrade-to-0.10.0.html">Upgrade to 0.10.0</a>
|
<a href="/guides/upgrading/upgrade-to-0.10.0.html">Upgrade to 0.10.0</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li<%= sidebar_current("guides-upgrading-to-0.10.2") %>>
|
||||||
|
<a href="/guides/upgrading/upgrade-to-0.10.2.html">Upgrade to 0.10.2</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
Loading…
Reference in New Issue