Transit convergent v3

This commit is contained in:
Jeff Mitchell 2018-06-05 18:51:35 -04:00 committed by Jeff Mitchell
parent 11e2fd2fce
commit 4b7d2bed01
10 changed files with 332 additions and 108 deletions

View File

@ -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.

View File

@ -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{}{

View File

@ -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")
} }

View File

@ -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
} }

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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>