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
|
||||
lease-less tokens can no longer be used (unless they are root tokens that
|
||||
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
|
||||
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.
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"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)
|
||||
}
|
||||
|
||||
derBytesOld, err := p.DeriveKey(keyContext, 1)
|
||||
derBytesOld, err := p.DeriveKey(keyContext, 1, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
derBytesOld2, err := p.DeriveKey(keyContext, 1)
|
||||
derBytesOld2, err := p.DeriveKey(keyContext, 1, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -884,12 +885,12 @@ func testDerivedKeyUpgrade(t *testing.T, keyType keysutil.KeyType) {
|
|||
t.Fatal("expected no upgrade needed")
|
||||
}
|
||||
|
||||
derBytesNew, err := p.DeriveKey(keyContext, 1)
|
||||
derBytesNew, err := p.DeriveKey(keyContext, 1, 64)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
derBytesNew2, err := p.DeriveKey(keyContext, 1)
|
||||
derBytesNew2, err := p.DeriveKey(keyContext, 1, 64)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -907,6 +908,8 @@ func TestConvergentEncryption(t *testing.T) {
|
|||
testConvergentEncryptionCommon(t, 0, keysutil.KeyType_AES256_GCM96)
|
||||
testConvergentEncryptionCommon(t, 2, keysutil.KeyType_AES256_GCM96)
|
||||
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) {
|
||||
|
@ -928,7 +931,6 @@ func testConvergentEncryptionCommon(t *testing.T, ver int, keyType keysutil.KeyT
|
|||
"convergent_encryption": true,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
p := &keysutil.Policy{
|
||||
Name: "testkey",
|
||||
Type: keyType,
|
||||
Derived: true,
|
||||
ConvergentEncryption: true,
|
||||
ConvergentVersion: ver,
|
||||
req = &logical.Request{
|
||||
Storage: storage,
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "keys/testkey",
|
||||
Data: map[string]interface{}{
|
||||
"derived": true,
|
||||
"convergent_encryption": true,
|
||||
},
|
||||
}
|
||||
|
||||
err = p.Rotate(context.Background(), storage)
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
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
|
||||
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)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
t.Fatalf("expected error, got nil, version is %d", ver)
|
||||
}
|
||||
if resp == nil {
|
||||
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")
|
||||
}
|
||||
|
||||
// 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
|
||||
// First, check without setting a plaintext at all
|
||||
req.Data = map[string]interface{}{
|
||||
|
|
|
@ -280,7 +280,7 @@ func (b *backend) pathPolicyRead(ctx context.Context, req *logical.Request, d *f
|
|||
if err != nil {
|
||||
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 {
|
||||
return nil, fmt.Errorf("failed to derive key to return public component")
|
||||
}
|
||||
|
|
|
@ -79,10 +79,6 @@ func NewEncryptedKeyStorageWrapper(config EncryptedKeyStorageConfig) (*Encrypted
|
|||
return nil, ErrPolicyConvergentEncryption
|
||||
}
|
||||
|
||||
if config.Policy.ConvergentVersion < 2 {
|
||||
return nil, ErrPolicyConvergentVersion
|
||||
}
|
||||
|
||||
if config.Prefix == "" {
|
||||
config.Prefix = DefaultPrefix
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/helper/strutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
|
@ -91,16 +91,14 @@ func TestBase58(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestEncrytedKeysStorage_BadPolicy(t *testing.T) {
|
||||
policy := &Policy{
|
||||
policy := NewPolicy(PolicyConfig{
|
||||
Name: "metadata",
|
||||
Type: KeyType_AES256_GCM96,
|
||||
Derived: false,
|
||||
KDF: Kdf_hkdf_sha256,
|
||||
ConvergentEncryption: true,
|
||||
ConvergentVersion: 2,
|
||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||
versionPrefixCache: &sync.Map{},
|
||||
}
|
||||
})
|
||||
|
||||
_, err := NewEncryptedKeyStorageWrapper(EncryptedKeyStorageConfig{
|
||||
Policy: policy,
|
||||
|
@ -110,16 +108,14 @@ func TestEncrytedKeysStorage_BadPolicy(t *testing.T) {
|
|||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
policy = &Policy{
|
||||
policy = NewPolicy(PolicyConfig{
|
||||
Name: "metadata",
|
||||
Type: KeyType_AES256_GCM96,
|
||||
Derived: true,
|
||||
KDF: Kdf_hkdf_sha256,
|
||||
ConvergentEncryption: false,
|
||||
ConvergentVersion: 2,
|
||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||
versionPrefixCache: &sync.Map{},
|
||||
}
|
||||
})
|
||||
|
||||
_, err = NewEncryptedKeyStorageWrapper(EncryptedKeyStorageConfig{
|
||||
Policy: policy,
|
||||
|
@ -129,49 +125,33 @@ func TestEncrytedKeysStorage_BadPolicy(t *testing.T) {
|
|||
t.Fatalf("Unexpected Error: %s", err)
|
||||
}
|
||||
|
||||
policy = &Policy{
|
||||
policy = NewPolicy(PolicyConfig{
|
||||
Name: "metadata",
|
||||
Type: KeyType_AES256_GCM96,
|
||||
Derived: true,
|
||||
KDF: Kdf_hkdf_sha256,
|
||||
ConvergentEncryption: true,
|
||||
ConvergentVersion: 1,
|
||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||
versionPrefixCache: &sync.Map{},
|
||||
}
|
||||
|
||||
})
|
||||
_, err = NewEncryptedKeyStorageWrapper(EncryptedKeyStorageConfig{
|
||||
Policy: policy,
|
||||
Prefix: "prefix",
|
||||
})
|
||||
if err != ErrPolicyConvergentVersion {
|
||||
if err != nil {
|
||||
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) {
|
||||
s := &logical.InmemStorage{}
|
||||
policy := &Policy{
|
||||
policy := NewPolicy(PolicyConfig{
|
||||
Name: "metadata",
|
||||
Type: KeyType_AES256_GCM96,
|
||||
Derived: true,
|
||||
KDF: Kdf_hkdf_sha256,
|
||||
ConvergentEncryption: true,
|
||||
ConvergentVersion: 2,
|
||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||
versionPrefixCache: &sync.Map{},
|
||||
}
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
|
@ -223,7 +203,7 @@ func TestEncryptedKeysStorage_List(t *testing.T) {
|
|||
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)
|
||||
}
|
||||
|
||||
|
@ -231,7 +211,7 @@ func TestEncryptedKeysStorage_List(t *testing.T) {
|
|||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -246,16 +226,14 @@ func TestEncryptedKeysStorage_List(t *testing.T) {
|
|||
|
||||
func TestEncryptedKeysStorage_CRUD(t *testing.T) {
|
||||
s := &logical.InmemStorage{}
|
||||
policy := &Policy{
|
||||
policy := NewPolicy(PolicyConfig{
|
||||
Name: "metadata",
|
||||
Type: KeyType_AES256_GCM96,
|
||||
Derived: true,
|
||||
KDF: Kdf_hkdf_sha256,
|
||||
ConvergentEncryption: true,
|
||||
ConvergentVersion: 2,
|
||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||
versionPrefixCache: &sync.Map{},
|
||||
}
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
|
@ -299,7 +277,7 @@ func TestEncryptedKeysStorage_CRUD(t *testing.T) {
|
|||
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)
|
||||
}
|
||||
|
||||
|
@ -309,7 +287,7 @@ func TestEncryptedKeysStorage_CRUD(t *testing.T) {
|
|||
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)
|
||||
}
|
||||
|
||||
|
@ -338,16 +316,14 @@ func TestEncryptedKeysStorage_CRUD(t *testing.T) {
|
|||
|
||||
func BenchmarkEncrytedKeyStorage_List(b *testing.B) {
|
||||
s := &logical.InmemStorage{}
|
||||
policy := &Policy{
|
||||
policy := NewPolicy(PolicyConfig{
|
||||
Name: "metadata",
|
||||
Type: KeyType_AES256_GCM96,
|
||||
Derived: true,
|
||||
KDF: Kdf_hkdf_sha256,
|
||||
ConvergentEncryption: true,
|
||||
ConvergentVersion: 2,
|
||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||
versionPrefixCache: &sync.Map{},
|
||||
}
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
|
@ -386,16 +362,14 @@ func BenchmarkEncrytedKeyStorage_List(b *testing.B) {
|
|||
|
||||
func BenchmarkEncrytedKeyStorage_Put(b *testing.B) {
|
||||
s := &logical.InmemStorage{}
|
||||
policy := &Policy{
|
||||
policy := NewPolicy(PolicyConfig{
|
||||
Name: "metadata",
|
||||
Type: KeyType_AES256_GCM96,
|
||||
Derived: true,
|
||||
KDF: Kdf_hkdf_sha256,
|
||||
ConvergentEncryption: true,
|
||||
ConvergentVersion: 2,
|
||||
VersionTemplate: EncryptedKeyPolicyVersionTpl,
|
||||
versionPrefixCache: &sync.Map{},
|
||||
}
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
|
@ -14,8 +14,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
shared = false
|
||||
exclusive = true
|
||||
shared = false
|
||||
exclusive = true
|
||||
currentConvergentVersion = 3
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -395,8 +396,15 @@ func (lm *LockManager) getPolicyCommon(ctx context.Context, req PolicyRequest, l
|
|||
}
|
||||
if req.Derived {
|
||||
p.KDF = Kdf_hkdf_sha256
|
||||
p.ConvergentEncryption = req.Convergent
|
||||
p.ConvergentVersion = 2
|
||||
if req.Convergent {
|
||||
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)
|
||||
|
|
|
@ -169,6 +169,10 @@ type KeyEntry struct {
|
|||
// The public key in an appropriate format for the type of 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
|
||||
// is more precise
|
||||
DeprecatedCreationTime int64 `json:"creation_time"`
|
||||
|
@ -215,8 +219,7 @@ type PolicyConfig struct {
|
|||
Type KeyType
|
||||
|
||||
// Derived keys MUST provide a context and the master underlying key is
|
||||
// never used. If convergent encryption is true, the context will be used
|
||||
// as the nonce as well.
|
||||
// never used.
|
||||
Derived bool
|
||||
KDF int
|
||||
ConvergentEncryption bool
|
||||
|
@ -242,18 +245,13 @@ type PolicyConfig struct {
|
|||
|
||||
// NewPolicy takes a policy config and returns a Policy with those settings.
|
||||
func NewPolicy(config PolicyConfig) *Policy {
|
||||
var convergentVersion int
|
||||
if config.ConvergentEncryption {
|
||||
convergentVersion = 2
|
||||
}
|
||||
|
||||
return &Policy{
|
||||
Name: config.Name,
|
||||
Type: config.Type,
|
||||
Derived: config.Derived,
|
||||
KDF: config.KDF,
|
||||
ConvergentEncryption: config.ConvergentEncryption,
|
||||
ConvergentVersion: convergentVersion,
|
||||
ConvergentVersion: -1,
|
||||
Exportable: config.Exportable,
|
||||
DeletionAllowed: config.DeletionAllowed,
|
||||
AllowPlaintextBackup: config.AllowPlaintextBackup,
|
||||
|
@ -544,7 +542,8 @@ func (p *Policy) NeedsUpgrade() bool {
|
|||
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 {
|
||||
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
|
||||
// is required, otherwise the KDF mode is used with the context to derive the
|
||||
// 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() {
|
||||
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"}
|
||||
}
|
||||
|
||||
// Fast-path non-derived keys
|
||||
if !p.Derived {
|
||||
return p.Keys[strconv.Itoa(ver)].Key, nil
|
||||
}
|
||||
|
||||
// Ensure a context is provided
|
||||
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"}
|
||||
|
@ -668,10 +667,10 @@ func (p *Policy) DeriveKey(context []byte, ver int) ([]byte, error) {
|
|||
case Kdf_hkdf_sha256:
|
||||
reader := hkdf.New(sha256.New, p.Keys[strconv.Itoa(ver)].Key, nil, context)
|
||||
derBytes := bytes.NewBuffer(nil)
|
||||
derBytes.Grow(32)
|
||||
derBytes.Grow(numBytes)
|
||||
limReader := &io.LimitedReader{
|
||||
R: reader,
|
||||
N: 32,
|
||||
N: int64(numBytes),
|
||||
}
|
||||
|
||||
switch p.Type {
|
||||
|
@ -680,8 +679,8 @@ func (p *Policy) DeriveKey(context []byte, ver int) ([]byte, error) {
|
|||
if err != nil {
|
||||
return nil, errutil.InternalError{Err: fmt.Sprintf("error reading returned derived bytes: %v", err)}
|
||||
}
|
||||
if n != 32 {
|
||||
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to read enough derived bytes, needed 32, got %d", n)}
|
||||
if n != int64(numBytes) {
|
||||
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to read enough derived bytes, needed %d, got %d", numBytes, n)}
|
||||
}
|
||||
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) {
|
||||
if !p.Type.EncryptionSupported() {
|
||||
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 {
|
||||
case KeyType_AES256_GCM96, KeyType_ChaCha20_Poly1305:
|
||||
// Derive the key that should be used
|
||||
key, err := p.DeriveKey(context, ver)
|
||||
hmacKey := context
|
||||
|
||||
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 {
|
||||
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 {
|
||||
case KeyType_AES256_GCM96:
|
||||
// Setup the cipher
|
||||
aesCipher, err := aes.NewCipher(key)
|
||||
aesCipher, err := aes.NewCipher(encKey)
|
||||
if err != nil {
|
||||
return "", errutil.InternalError{Err: err.Error()}
|
||||
}
|
||||
|
@ -754,7 +794,7 @@ func (p *Policy) Encrypt(ver int, context, nonce []byte, value string) (string,
|
|||
aead = gcm
|
||||
|
||||
case KeyType_ChaCha20_Poly1305:
|
||||
cha, err := chacha20poly1305.New(key)
|
||||
cha, err := chacha20poly1305.New(encKey)
|
||||
if err != nil {
|
||||
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 {
|
||||
switch p.ConvergentVersion {
|
||||
convergentVersion := p.convergentVersion(ver)
|
||||
switch convergentVersion {
|
||||
case 1:
|
||||
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())}
|
||||
}
|
||||
default:
|
||||
nonceHmac := hmac.New(sha256.New, context)
|
||||
case 2, 3:
|
||||
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)
|
||||
nonceSum := nonceHmac.Sum(nil)
|
||||
nonce = nonceSum[:aead.NonceSize()]
|
||||
default:
|
||||
return "", errutil.InternalError{Err: fmt.Sprintf("unhandled convergent version %d", convergentVersion)}
|
||||
}
|
||||
} else {
|
||||
// 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)
|
||||
|
||||
// Place the encrypted data after the nonce
|
||||
if !p.ConvergentEncryption || p.ConvergentVersion > 1 {
|
||||
if !p.ConvergentEncryption || p.convergentVersion(ver) > 1 {
|
||||
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"}
|
||||
}
|
||||
|
||||
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)
|
||||
if len(splitVerCiphertext) != 2 {
|
||||
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}
|
||||
}
|
||||
|
||||
convergentVersion := p.convergentVersion(ver)
|
||||
if convergentVersion == 1 && (nonce == nil || len(nonce) == 0) {
|
||||
return "", errutil.UserError{Err: "invalid convergent nonce supplied"}
|
||||
}
|
||||
|
||||
// Decode the base64
|
||||
decoded, err := base64.StdEncoding.DecodeString(splitVerCiphertext[1])
|
||||
if err != nil {
|
||||
|
@ -863,17 +910,21 @@ func (p *Policy) Decrypt(context, nonce []byte, value string) (string, error) {
|
|||
|
||||
switch p.Type {
|
||||
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 {
|
||||
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 {
|
||||
case KeyType_AES256_GCM96:
|
||||
// Setup the cipher
|
||||
aesCipher, err := aes.NewCipher(key)
|
||||
aesCipher, err := aes.NewCipher(encKey)
|
||||
if err != nil {
|
||||
return "", errutil.InternalError{Err: err.Error()}
|
||||
}
|
||||
|
@ -887,7 +938,7 @@ func (p *Policy) Decrypt(context, nonce []byte, value string) (string, error) {
|
|||
aead = gcm
|
||||
|
||||
case KeyType_ChaCha20_Poly1305:
|
||||
cha, err := chacha20poly1305.New(key)
|
||||
cha, err := chacha20poly1305.New(encKey)
|
||||
if err != nil {
|
||||
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
|
||||
var ciphertext []byte
|
||||
if p.ConvergentEncryption && p.ConvergentVersion < 2 {
|
||||
if p.ConvergentEncryption && convergentVersion == 1 {
|
||||
ciphertext = decoded
|
||||
} else {
|
||||
nonce = decoded[:aead.NonceSize()]
|
||||
|
@ -992,7 +1043,7 @@ func (p *Policy) Sign(ver int, context, input []byte, hashAlgorithm, sigAlgorith
|
|||
if p.Derived {
|
||||
// Derive the key that should be used
|
||||
var err error
|
||||
key, err = p.DeriveKey(context, ver)
|
||||
key, err = p.DeriveKey(context, ver, 32)
|
||||
if err != nil {
|
||||
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 {
|
||||
// Derive the key that should be used
|
||||
var err error
|
||||
key, err = p.DeriveKey(context, ver)
|
||||
key, err = p.DeriveKey(context, ver, 32)
|
||||
if err != nil {
|
||||
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
|
||||
|
||||
// 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
|
||||
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
|
||||
|
||||
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") %>>
|
||||
<a href="/guides/upgrading/upgrade-to-0.10.0.html">Upgrade to 0.10.0</a>
|
||||
</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>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
Loading…
Reference in New Issue