Merge pull request #1696 from hashicorp/transit-convergent-specify-nonce
Require nonce specification for more flexibility
This commit is contained in:
commit
0a67bcb5bd
|
@ -29,9 +29,10 @@ FEATURES:
|
||||||
deprecates App-ID. [GH-1426]
|
deprecates App-ID. [GH-1426]
|
||||||
* **Convergent Encryption in `Transit`**: The `transit` backend now supports a
|
* **Convergent Encryption in `Transit`**: The `transit` backend now supports a
|
||||||
convergent encryption mode where the same plaintext will produce the same
|
convergent encryption mode where the same plaintext will produce the same
|
||||||
ciphertext. Although very useful in some situations, this has security
|
ciphertext. Although very useful in some situations, this has potential
|
||||||
implications, which are mostly mitigated by requiring the use of key
|
security implications, which are mostly mitigated by requiring the use of
|
||||||
derivation when convergent encryption is enabled. See [the `transit`
|
key derivation when convergent encryption is enabled. See [the `transit`
|
||||||
|
backend
|
||||||
documentation](https://www.vaultproject.io/docs/secrets/transit/index.html)
|
documentation](https://www.vaultproject.io/docs/secrets/transit/index.html)
|
||||||
for more details. [GH-1537]
|
for more details. [GH-1537]
|
||||||
* **Improved LDAP Group Filters**: The `ldap` auth backend now uses templates
|
* **Improved LDAP Group Filters**: The `ldap` auth backend now uses templates
|
||||||
|
|
|
@ -56,19 +56,6 @@ func TestBackend_upsert(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackend_upsert_convergent(t *testing.T) {
|
|
||||||
decryptData := make(map[string]interface{})
|
|
||||||
logicaltest.Test(t, logicaltest.TestCase{
|
|
||||||
Factory: Factory,
|
|
||||||
Steps: []logicaltest.TestStep{
|
|
||||||
testAccStepReadPolicy(t, "test", true, false),
|
|
||||||
testAccStepEncryptUpsertConvergent(t, "test", testPlaintext, decryptData),
|
|
||||||
testAccStepReadPolicy(t, "test", false, false),
|
|
||||||
testAccStepDecrypt(t, "test", testPlaintext, decryptData),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBackend_datakey(t *testing.T) {
|
func TestBackend_datakey(t *testing.T) {
|
||||||
dataKeyInfo := make(map[string]interface{})
|
dataKeyInfo := make(map[string]interface{})
|
||||||
logicaltest.Test(t, logicaltest.TestCase{
|
logicaltest.Test(t, logicaltest.TestCase{
|
||||||
|
@ -323,30 +310,6 @@ func testAccStepEncryptUpsert(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccStepEncryptUpsertConvergent(
|
|
||||||
t *testing.T, name, plaintext string, decryptData map[string]interface{}) logicaltest.TestStep {
|
|
||||||
return logicaltest.TestStep{
|
|
||||||
Operation: logical.CreateOperation,
|
|
||||||
Path: "encrypt/" + name,
|
|
||||||
Data: map[string]interface{}{
|
|
||||||
"plaintext": base64.StdEncoding.EncodeToString([]byte(plaintext)),
|
|
||||||
},
|
|
||||||
Check: func(resp *logical.Response) error {
|
|
||||||
var d struct {
|
|
||||||
Ciphertext string `mapstructure:"ciphertext"`
|
|
||||||
}
|
|
||||||
if err := mapstructure.Decode(resp.Data, &d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if d.Ciphertext == "" {
|
|
||||||
return fmt.Errorf("missing ciphertext")
|
|
||||||
}
|
|
||||||
decryptData["ciphertext"] = d.Ciphertext
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAccStepEncryptContext(
|
func testAccStepEncryptContext(
|
||||||
t *testing.T, name, plaintext, context string, decryptData map[string]interface{}) logicaltest.TestStep {
|
t *testing.T, name, plaintext, context string, decryptData map[string]interface{}) logicaltest.TestStep {
|
||||||
return logicaltest.TestStep{
|
return logicaltest.TestStep{
|
||||||
|
@ -633,7 +596,8 @@ func TestConvergentEncryption(t *testing.T) {
|
||||||
req.Path = "encrypt/testkey"
|
req.Path = "encrypt/testkey"
|
||||||
req.Data = map[string]interface{}{
|
req.Data = map[string]interface{}{
|
||||||
"plaintext": "emlwIHphcA==", // "zip zap"
|
"plaintext": "emlwIHphcA==", // "zip zap"
|
||||||
"context": "Zm9vIGJhcg==", // "foo bar"
|
"nonce": "Zm9vIGJhcg==", // "foo bar"
|
||||||
|
"context": "pWZ6t/im3AORd0lVYE0zBdKpX6Bl3/SvFtoVTPWbdkzjG788XmMAnOlxandSdd7S",
|
||||||
}
|
}
|
||||||
resp, err = b.HandleRequest(req)
|
resp, err = b.HandleRequest(req)
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
|
@ -643,10 +607,21 @@ func TestConvergentEncryption(t *testing.T) {
|
||||||
t.Fatalf("expected error response, got %#v", *resp)
|
t.Fatalf("expected error response, got %#v", *resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure we fail if we do not provide a nonce
|
||||||
|
req.Data = map[string]interface{}{
|
||||||
|
"plaintext": "emlwIHphcA==", // "zip zap"
|
||||||
|
"context": "pWZ6t/im3AORd0lVYE0zBdKpX6Bl3/SvFtoVTPWbdkzjG788XmMAnOlxandSdd7S",
|
||||||
|
}
|
||||||
|
resp, err = b.HandleRequest(req)
|
||||||
|
if err == nil && (resp == nil || !resp.IsError()) {
|
||||||
|
t.Fatal("expected error response")
|
||||||
|
}
|
||||||
|
|
||||||
// Now test encrypting the same value twice
|
// Now test encrypting the same value twice
|
||||||
req.Data = map[string]interface{}{
|
req.Data = map[string]interface{}{
|
||||||
"plaintext": "emlwIHphcA==", // "zip zap"
|
"plaintext": "emlwIHphcA==", // "zip zap"
|
||||||
"context": "b25ldHdvdGhyZWVl", // "onetwothreee"
|
"nonce": "b25ldHdvdGhyZWVl", // "onetwothreee"
|
||||||
|
"context": "pWZ6t/im3AORd0lVYE0zBdKpX6Bl3/SvFtoVTPWbdkzjG788XmMAnOlxandSdd7S",
|
||||||
}
|
}
|
||||||
resp, err = b.HandleRequest(req)
|
resp, err = b.HandleRequest(req)
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
|
@ -670,10 +645,11 @@ func TestConvergentEncryption(t *testing.T) {
|
||||||
t.Fatalf("expected the same ciphertext but got %s and %s", ciphertext1, ciphertext2)
|
t.Fatalf("expected the same ciphertext but got %s and %s", ciphertext1, ciphertext2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// For sanity, also check a different value
|
// For sanity, also check a different nonce value...
|
||||||
req.Data = map[string]interface{}{
|
req.Data = map[string]interface{}{
|
||||||
"plaintext": "emlwIHphcA==", // "zip zap"
|
"plaintext": "emlwIHphcA==", // "zip zap"
|
||||||
"context": "dHdvdGhyZWVmb3Vy", // "twothreefour"
|
"nonce": "dHdvdGhyZWVmb3Vy", // "twothreefour"
|
||||||
|
"context": "pWZ6t/im3AORd0lVYE0zBdKpX6Bl3/SvFtoVTPWbdkzjG788XmMAnOlxandSdd7S",
|
||||||
}
|
}
|
||||||
resp, err = b.HandleRequest(req)
|
resp, err = b.HandleRequest(req)
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
|
@ -694,12 +670,45 @@ func TestConvergentEncryption(t *testing.T) {
|
||||||
ciphertext4 := resp.Data["ciphertext"].(string)
|
ciphertext4 := resp.Data["ciphertext"].(string)
|
||||||
|
|
||||||
if ciphertext3 != ciphertext4 {
|
if ciphertext3 != ciphertext4 {
|
||||||
t.Fatalf("expected the same ciphertext but got %s and %s", ciphertext1, ciphertext2)
|
t.Fatalf("expected the same ciphertext but got %s and %s", ciphertext3, ciphertext4)
|
||||||
}
|
}
|
||||||
if ciphertext1 == ciphertext3 {
|
if ciphertext1 == ciphertext3 {
|
||||||
t.Fatalf("expected different ciphertexts")
|
t.Fatalf("expected different ciphertexts")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ...and a different context value
|
||||||
|
req.Data = map[string]interface{}{
|
||||||
|
"plaintext": "emlwIHphcA==", // "zip zap"
|
||||||
|
"nonce": "dHdvdGhyZWVmb3Vy", // "twothreefour"
|
||||||
|
"context": "qV4h9iQyvn+raODOer4JNAsOhkXBwdT4HZ677Ql4KLqXSU+Jk4C/fXBWbv6xkSYT",
|
||||||
|
}
|
||||||
|
resp, err = b.HandleRequest(req)
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatal("expected non-nil response")
|
||||||
|
}
|
||||||
|
if resp.IsError() {
|
||||||
|
t.Fatalf("got error response: %#v", *resp)
|
||||||
|
}
|
||||||
|
ciphertext5 := resp.Data["ciphertext"].(string)
|
||||||
|
|
||||||
|
resp, err = b.HandleRequest(req)
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatal("expected non-nil response")
|
||||||
|
}
|
||||||
|
if resp.IsError() {
|
||||||
|
t.Fatalf("got error response: %#v", *resp)
|
||||||
|
}
|
||||||
|
ciphertext6 := resp.Data["ciphertext"].(string)
|
||||||
|
|
||||||
|
if ciphertext5 != ciphertext6 {
|
||||||
|
t.Fatalf("expected the same ciphertext but got %s and %s", ciphertext5, ciphertext6)
|
||||||
|
}
|
||||||
|
if ciphertext1 == ciphertext5 {
|
||||||
|
t.Fatalf("expected different ciphertexts")
|
||||||
|
}
|
||||||
|
if ciphertext3 == ciphertext5 {
|
||||||
|
t.Fatalf("expected different ciphertexts")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicyFuzzing(t *testing.T) {
|
func TestPolicyFuzzing(t *testing.T) {
|
||||||
|
|
|
@ -132,7 +132,7 @@ func (lm *lockManager) GetPolicyExclusive(storage logical.Storage, name string)
|
||||||
// Get the policy with a read lock; if it returns that an exclusive lock is
|
// Get the policy with a read lock; if it returns that an exclusive lock is
|
||||||
// needed, retry. If successful, call one more time to get a read lock and
|
// needed, retry. If successful, call one more time to get a read lock and
|
||||||
// return the value.
|
// return the value.
|
||||||
func (lm *lockManager) GetPolicyUpsert(storage logical.Storage, name string, derived bool, convergent bool) (*Policy, *sync.RWMutex, bool, error) {
|
func (lm *lockManager) GetPolicyUpsert(storage logical.Storage, name string, derived, convergent bool) (*Policy, *sync.RWMutex, bool, error) {
|
||||||
p, lock, _, err := lm.getPolicyCommon(storage, name, true, derived, convergent, shared)
|
p, lock, _, err := lm.getPolicyCommon(storage, name, true, derived, convergent, shared)
|
||||||
if err == nil ||
|
if err == nil ||
|
||||||
(err != nil && err != errNeedExclusiveLock) {
|
(err != nil && err != errNeedExclusiveLock) {
|
||||||
|
|
|
@ -30,6 +30,11 @@ ciphertext; "wrapped" will return the ciphertext only.`,
|
||||||
Description: "Context for key derivation. Required for derived keys.",
|
Description: "Context for key derivation. Required for derived keys.",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"nonce": &framework.FieldSchema{
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "Nonce for when convergent encryption is used",
|
||||||
|
},
|
||||||
|
|
||||||
"bits": &framework.FieldSchema{
|
"bits": &framework.FieldSchema{
|
||||||
Type: framework.TypeInt,
|
Type: framework.TypeInt,
|
||||||
Description: `Number of bits for the key; currently 128, 256,
|
Description: `Number of bits for the key; currently 128, 256,
|
||||||
|
@ -61,17 +66,28 @@ func (b *backend) pathDatakeyWrite(
|
||||||
return logical.ErrorResponse("Invalid path, must be 'plaintext' or 'wrapped'"), logical.ErrInvalidRequest
|
return logical.ErrorResponse("Invalid path, must be 'plaintext' or 'wrapped'"), logical.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
// Decode the context if any
|
// Decode the context if any
|
||||||
contextRaw := d.Get("context").(string)
|
contextRaw := d.Get("context").(string)
|
||||||
var context []byte
|
var context []byte
|
||||||
if len(contextRaw) != 0 {
|
if len(contextRaw) != 0 {
|
||||||
var err error
|
|
||||||
context, err = base64.StdEncoding.DecodeString(contextRaw)
|
context, err = base64.StdEncoding.DecodeString(contextRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return logical.ErrorResponse("failed to decode context as base64"), logical.ErrInvalidRequest
|
return logical.ErrorResponse("failed to decode context as base64"), logical.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode the nonce if any
|
||||||
|
nonceRaw := d.Get("nonce").(string)
|
||||||
|
var nonce []byte
|
||||||
|
if len(nonceRaw) != 0 {
|
||||||
|
nonce, err = base64.StdEncoding.DecodeString(nonceRaw)
|
||||||
|
if err != nil {
|
||||||
|
return logical.ErrorResponse("failed to decode nonce as base64"), logical.ErrInvalidRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get the policy
|
// Get the policy
|
||||||
p, lock, err := b.lm.GetPolicyShared(req.Storage, name)
|
p, lock, err := b.lm.GetPolicyShared(req.Storage, name)
|
||||||
if lock != nil {
|
if lock != nil {
|
||||||
|
@ -100,7 +116,7 @@ func (b *backend) pathDatakeyWrite(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ciphertext, err := p.Encrypt(context, base64.StdEncoding.EncodeToString(newKey))
|
ciphertext, err := p.Encrypt(context, nonce, base64.StdEncoding.EncodeToString(newKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case errutil.UserError:
|
case errutil.UserError:
|
||||||
|
|
|
@ -27,6 +27,11 @@ func (b *backend) pathDecrypt() *framework.Path {
|
||||||
Type: framework.TypeString,
|
Type: framework.TypeString,
|
||||||
Description: "Context for key derivation. Required for derived keys.",
|
Description: "Context for key derivation. Required for derived keys.",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"nonce": &framework.FieldSchema{
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "Nonce for when convergent encryption is used",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||||
|
@ -46,17 +51,28 @@ func (b *backend) pathDecryptWrite(
|
||||||
return logical.ErrorResponse("missing ciphertext to decrypt"), logical.ErrInvalidRequest
|
return logical.ErrorResponse("missing ciphertext to decrypt"), logical.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
// Decode the context if any
|
// Decode the context if any
|
||||||
contextRaw := d.Get("context").(string)
|
contextRaw := d.Get("context").(string)
|
||||||
var context []byte
|
var context []byte
|
||||||
if len(contextRaw) != 0 {
|
if len(contextRaw) != 0 {
|
||||||
var err error
|
|
||||||
context, err = base64.StdEncoding.DecodeString(contextRaw)
|
context, err = base64.StdEncoding.DecodeString(contextRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return logical.ErrorResponse("failed to decode context as base64"), logical.ErrInvalidRequest
|
return logical.ErrorResponse("failed to decode context as base64"), logical.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode the nonce if any
|
||||||
|
nonceRaw := d.Get("nonce").(string)
|
||||||
|
var nonce []byte
|
||||||
|
if len(nonceRaw) != 0 {
|
||||||
|
nonce, err = base64.StdEncoding.DecodeString(nonceRaw)
|
||||||
|
if err != nil {
|
||||||
|
return logical.ErrorResponse("failed to decode nonce as base64"), logical.ErrInvalidRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get the policy
|
// Get the policy
|
||||||
p, lock, err := b.lm.GetPolicyShared(req.Storage, name)
|
p, lock, err := b.lm.GetPolicyShared(req.Storage, name)
|
||||||
if lock != nil {
|
if lock != nil {
|
||||||
|
@ -69,7 +85,7 @@ func (b *backend) pathDecryptWrite(
|
||||||
return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest
|
return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
plaintext, err := p.Decrypt(context, ciphertext)
|
plaintext, err := p.Decrypt(context, nonce, ciphertext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case errutil.UserError:
|
case errutil.UserError:
|
||||||
|
|
|
@ -28,6 +28,11 @@ func (b *backend) pathEncrypt() *framework.Path {
|
||||||
Type: framework.TypeString,
|
Type: framework.TypeString,
|
||||||
Description: "Context for key derivation. Required for derived keys.",
|
Description: "Context for key derivation. Required for derived keys.",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"nonce": &framework.FieldSchema{
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "Nonce for when convergent encryption is used",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||||
|
@ -63,10 +68,11 @@ func (b *backend) pathEncryptWrite(
|
||||||
return logical.ErrorResponse("missing plaintext to encrypt"), logical.ErrInvalidRequest
|
return logical.ErrorResponse("missing plaintext to encrypt"), logical.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
// Decode the context if any
|
// Decode the context if any
|
||||||
contextRaw := d.Get("context").(string)
|
contextRaw := d.Get("context").(string)
|
||||||
var context []byte
|
var context []byte
|
||||||
var err error
|
|
||||||
if len(contextRaw) != 0 {
|
if len(contextRaw) != 0 {
|
||||||
context, err = base64.StdEncoding.DecodeString(contextRaw)
|
context, err = base64.StdEncoding.DecodeString(contextRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -74,6 +80,16 @@ func (b *backend) pathEncryptWrite(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode the nonce if any
|
||||||
|
nonceRaw := d.Get("nonce").(string)
|
||||||
|
var nonce []byte
|
||||||
|
if len(nonceRaw) != 0 {
|
||||||
|
nonce, err = base64.StdEncoding.DecodeString(nonceRaw)
|
||||||
|
if err != nil {
|
||||||
|
return logical.ErrorResponse("failed to decode nonce as base64"), logical.ErrInvalidRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get the policy
|
// Get the policy
|
||||||
var p *Policy
|
var p *Policy
|
||||||
var lock *sync.RWMutex
|
var lock *sync.RWMutex
|
||||||
|
@ -93,7 +109,7 @@ func (b *backend) pathEncryptWrite(
|
||||||
return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest
|
return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
ciphertext, err := p.Encrypt(context, value)
|
ciphertext, err := p.Encrypt(context, nonce, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case errutil.UserError:
|
case errutil.UserError:
|
||||||
|
|
|
@ -18,23 +18,25 @@ func (b *backend) pathKeys() *framework.Path {
|
||||||
},
|
},
|
||||||
|
|
||||||
"derived": &framework.FieldSchema{
|
"derived": &framework.FieldSchema{
|
||||||
Type: framework.TypeBool,
|
Type: framework.TypeBool,
|
||||||
Description: "Enables key derivation mode. This allows for per-transaction unique keys",
|
Description: `Enables key derivation mode. This
|
||||||
|
allows for per-transaction unique keys.`,
|
||||||
},
|
},
|
||||||
|
|
||||||
"convergent_encryption": &framework.FieldSchema{
|
"convergent_encryption": &framework.FieldSchema{
|
||||||
Type: framework.TypeBool,
|
Type: framework.TypeBool,
|
||||||
Description: `Whether to use convergent encryption.
|
Description: `Whether to support convergent encryption.
|
||||||
This is only supported when using a key with
|
This is only supported when using a key with
|
||||||
key derivation enabled and will require all
|
key derivation enabled and will require all
|
||||||
context values to be 96 bits (12 bytes) when
|
requests to carry both a context and 96-bit
|
||||||
base64-decoded. This mode ensures that when
|
(12-byte) nonce. The given nonce will be used
|
||||||
the same context is supplied, the same
|
in place of a randomly generated nonce. As a
|
||||||
ciphertext is emitted from the encryption
|
result, when the same context and nonce are
|
||||||
function. It is *very important* when using
|
supplied, the same ciphertext is generated. It
|
||||||
this mode that you ensure that all contexts
|
is *very important* when using this mode that
|
||||||
are *globally unique*. Failing to do so will
|
you ensure that all nonces are unique for a
|
||||||
severely impact the security of the key.`,
|
given context. Failing to do so will severely
|
||||||
|
impact the ciphertext's security.`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,11 @@ func (b *backend) pathRewrap() *framework.Path {
|
||||||
Type: framework.TypeString,
|
Type: framework.TypeString,
|
||||||
Description: "Context for key derivation. Required for derived keys.",
|
Description: "Context for key derivation. Required for derived keys.",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"nonce": &framework.FieldSchema{
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "Nonce for when convergent encryption is used",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||||
|
@ -47,17 +52,28 @@ func (b *backend) pathRewrapWrite(
|
||||||
return logical.ErrorResponse("missing ciphertext to decrypt"), logical.ErrInvalidRequest
|
return logical.ErrorResponse("missing ciphertext to decrypt"), logical.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
// Decode the context if any
|
// Decode the context if any
|
||||||
contextRaw := d.Get("context").(string)
|
contextRaw := d.Get("context").(string)
|
||||||
var context []byte
|
var context []byte
|
||||||
if len(contextRaw) != 0 {
|
if len(contextRaw) != 0 {
|
||||||
var err error
|
|
||||||
context, err = base64.StdEncoding.DecodeString(contextRaw)
|
context, err = base64.StdEncoding.DecodeString(contextRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return logical.ErrorResponse("failed to decode context as base64"), logical.ErrInvalidRequest
|
return logical.ErrorResponse("failed to decode context as base64"), logical.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode the nonce if any
|
||||||
|
nonceRaw := d.Get("nonce").(string)
|
||||||
|
var nonce []byte
|
||||||
|
if len(nonceRaw) != 0 {
|
||||||
|
nonce, err = base64.StdEncoding.DecodeString(nonceRaw)
|
||||||
|
if err != nil {
|
||||||
|
return logical.ErrorResponse("failed to decode nonce as base64"), logical.ErrInvalidRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get the policy
|
// Get the policy
|
||||||
p, lock, err := b.lm.GetPolicyShared(req.Storage, name)
|
p, lock, err := b.lm.GetPolicyShared(req.Storage, name)
|
||||||
if lock != nil {
|
if lock != nil {
|
||||||
|
@ -71,7 +87,7 @@ func (b *backend) pathRewrapWrite(
|
||||||
return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest
|
return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
plaintext, err := p.Decrypt(context, value)
|
plaintext, err := p.Decrypt(context, nonce, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case errutil.UserError:
|
case errutil.UserError:
|
||||||
|
@ -87,7 +103,7 @@ func (b *backend) pathRewrapWrite(
|
||||||
return nil, fmt.Errorf("empty plaintext returned during rewrap")
|
return nil, fmt.Errorf("empty plaintext returned during rewrap")
|
||||||
}
|
}
|
||||||
|
|
||||||
ciphertext, err := p.Encrypt(context, plaintext)
|
ciphertext, err := p.Encrypt(context, nonce, plaintext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case errutil.UserError:
|
case errutil.UserError:
|
||||||
|
|
|
@ -307,10 +307,6 @@ func (p *Policy) DeriveKey(context []byte, ver int) ([]byte, error) {
|
||||||
return nil, errutil.InternalError{Err: "unable to access the key; no key versions found"}
|
return nil, errutil.InternalError{Err: "unable to access the key; no key versions found"}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.LatestVersion == 0 {
|
|
||||||
return nil, errutil.InternalError{Err: "unable to access the key; no key versions found"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ver <= 0 || ver > p.LatestVersion {
|
if ver <= 0 || ver > p.LatestVersion {
|
||||||
return nil, errutil.UserError{Err: "invalid key version"}
|
return nil, errutil.UserError{Err: "invalid key version"}
|
||||||
}
|
}
|
||||||
|
@ -335,7 +331,7 @@ func (p *Policy) DeriveKey(context []byte, ver int) ([]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Policy) Encrypt(context []byte, value string) (string, error) {
|
func (p *Policy) Encrypt(context, nonce []byte, value string) (string, error) {
|
||||||
// Decode the plaintext value
|
// Decode the plaintext value
|
||||||
plaintext, err := base64.StdEncoding.DecodeString(value)
|
plaintext, err := base64.StdEncoding.DecodeString(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -367,15 +363,12 @@ func (p *Policy) Encrypt(context []byte, value string) (string, error) {
|
||||||
return "", errutil.InternalError{Err: err.Error()}
|
return "", errutil.InternalError{Err: err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.ConvergentEncryption && len(context) != gcm.NonceSize() {
|
|
||||||
return "", errutil.UserError{Err: fmt.Sprintf("base64-decoded context must be %d bytes long when using convergent encryption with this key", gcm.NonceSize())}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute random nonce
|
|
||||||
var nonce []byte
|
|
||||||
if p.ConvergentEncryption {
|
if p.ConvergentEncryption {
|
||||||
nonce = context
|
if len(nonce) != gcm.NonceSize() {
|
||||||
|
return "", errutil.UserError{Err: fmt.Sprintf("base64-decoded nonce must be %d bytes long when using convergent encryption with this key", gcm.NonceSize())}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Compute random nonce
|
||||||
nonce = make([]byte, gcm.NonceSize())
|
nonce = make([]byte, gcm.NonceSize())
|
||||||
_, err = rand.Read(nonce)
|
_, err = rand.Read(nonce)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -387,7 +380,10 @@ func (p *Policy) Encrypt(context []byte, value string) (string, error) {
|
||||||
out := gcm.Seal(nil, nonce, plaintext, nil)
|
out := gcm.Seal(nil, nonce, plaintext, nil)
|
||||||
|
|
||||||
// Place the encrypted data after the nonce
|
// Place the encrypted data after the nonce
|
||||||
full := append(nonce, out...)
|
full := out
|
||||||
|
if !p.ConvergentEncryption {
|
||||||
|
full = append(nonce, out...)
|
||||||
|
}
|
||||||
|
|
||||||
// Convert to base64
|
// Convert to base64
|
||||||
encoded := base64.StdEncoding.EncodeToString(full)
|
encoded := base64.StdEncoding.EncodeToString(full)
|
||||||
|
@ -398,12 +394,16 @@ func (p *Policy) Encrypt(context []byte, value string) (string, error) {
|
||||||
return encoded, nil
|
return encoded, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Policy) Decrypt(context []byte, value string) (string, error) {
|
func (p *Policy) Decrypt(context, nonce []byte, value string) (string, error) {
|
||||||
// Verify the prefix
|
// Verify the prefix
|
||||||
if !strings.HasPrefix(value, "vault:v") {
|
if !strings.HasPrefix(value, "vault:v") {
|
||||||
return "", errutil.UserError{Err: "invalid ciphertext: no prefix"}
|
return "", errutil.UserError{Err: "invalid ciphertext: no prefix"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.ConvergentEncryption && (nonce == nil || len(nonce) == 0) {
|
||||||
|
return "", errutil.UserError{Err: "invalid convergent nonce supplied"}
|
||||||
|
}
|
||||||
|
|
||||||
splitVerCiphertext := strings.SplitN(strings.TrimPrefix(value, "vault:v"), ":", 2)
|
splitVerCiphertext := strings.SplitN(strings.TrimPrefix(value, "vault:v"), ":", 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"}
|
||||||
|
@ -460,8 +460,13 @@ func (p *Policy) Decrypt(context []byte, value string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the nonce and ciphertext
|
// Extract the nonce and ciphertext
|
||||||
nonce := decoded[:gcm.NonceSize()]
|
var ciphertext []byte
|
||||||
ciphertext := decoded[gcm.NonceSize():]
|
if p.ConvergentEncryption {
|
||||||
|
ciphertext = decoded
|
||||||
|
} else {
|
||||||
|
nonce = decoded[:gcm.NonceSize()]
|
||||||
|
ciphertext = decoded[gcm.NonceSize():]
|
||||||
|
}
|
||||||
|
|
||||||
// Verify and Decrypt
|
// Verify and Decrypt
|
||||||
plain, err := gcm.Open(nil, nonce, ciphertext, nil)
|
plain, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||||
|
|
|
@ -33,7 +33,7 @@ data's attack surface.
|
||||||
Key derivation is supported, which allows the same key to be used for multiple
|
Key derivation is supported, which allows the same key to be used for multiple
|
||||||
purposes by deriving a new key based on a user-supplied context value. In this
|
purposes by deriving a new key based on a user-supplied context value. In this
|
||||||
mode, convergent encryption can optionally be supported, which allows the same
|
mode, convergent encryption can optionally be supported, which allows the same
|
||||||
context and plaintext to produce the same ciphertext.
|
input values to produce the same ciphertext.
|
||||||
|
|
||||||
The backend also supports key rotation, which allows a new version of the named
|
The backend also supports key rotation, which allows a new version of the named
|
||||||
key to be generated. All data encrypted with the key will use the newest
|
key to be generated. All data encrypted with the key will use the newest
|
||||||
|
@ -156,12 +156,16 @@ only encrypt or decrypt using the named keys they need access to.
|
||||||
<span class="param-flags">optional</span>
|
<span class="param-flags">optional</span>
|
||||||
If set, the key will support convergent encryption, where the same
|
If set, the key will support convergent encryption, where the same
|
||||||
plaintext creates the same ciphertext. This requires _derived_ to be
|
plaintext creates the same ciphertext. This requires _derived_ to be
|
||||||
set to `true`. When enabled, the context value must be exactly 12 bytes
|
set to `true`. When enabled, each
|
||||||
(96 bits) and will both be used to derive the key and as the nonce for
|
encryption(/decryption/rewrap/datakey) operation will require a `nonce`
|
||||||
the encryption operation. Note that while this is useful for particular
|
value to be specified. Note that while this is useful for particular
|
||||||
situations, it also has security implications. In particular, you must
|
situations, all nonce values used with a given context value **must be
|
||||||
ensure that you do **not** use the same context value for more than one
|
unique** or it will compromise the security of your key. A common way
|
||||||
plaintext value. Defaults to false.
|
to use this will be to generate a unique identifier for the given data
|
||||||
|
(for instance, a SHA-512 sum), then separate the bytes so that twelve
|
||||||
|
bytes are used as the nonce and the remaining as the context, ensuring
|
||||||
|
that all bits of unique identity are used as a part of the encryption
|
||||||
|
operation. Defaults to false.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</dd>
|
</dd>
|
||||||
|
@ -347,6 +351,15 @@ only encrypt or decrypt using the named keys they need access to.
|
||||||
The key derivation context, provided as base64 encoded.
|
The key derivation context, provided as base64 encoded.
|
||||||
Must be provided if derivation is enabled.
|
Must be provided if derivation is enabled.
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="param">nonce</span>
|
||||||
|
<span class="param-flags">optional</span>
|
||||||
|
The nonce value, provided as base64 encoded. Must be provided if
|
||||||
|
convergent encryption is enabled for this key. The value must be
|
||||||
|
exactly 96 bits (12 bytes) long and the user must ensure that for any
|
||||||
|
given context (and thus, any given encryption key) this nonce value is
|
||||||
|
**never reused**.
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
|
@ -393,6 +406,12 @@ only encrypt or decrypt using the named keys they need access to.
|
||||||
The key derivation context, provided as base64 encoded.
|
The key derivation context, provided as base64 encoded.
|
||||||
Must be provided if derivation is enabled.
|
Must be provided if derivation is enabled.
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="param">nonce</span>
|
||||||
|
<span class="param-flags">optional</span>
|
||||||
|
The nonce value used during encryption, provided as base64 encoded.
|
||||||
|
Must be provided if convergent encryption is enabled for this key.
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
|
@ -441,6 +460,12 @@ only encrypt or decrypt using the named keys they need access to.
|
||||||
The key derivation context, provided as base64 encoded.
|
The key derivation context, provided as base64 encoded.
|
||||||
Must be provided if derivation is enabled.
|
Must be provided if derivation is enabled.
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="param">nonce</span>
|
||||||
|
<span class="param-flags">optional</span>
|
||||||
|
The nonce value used during encryption, provided as base64 encoded.
|
||||||
|
Must be provided if convergent encryption is enabled for this key.
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
|
@ -493,6 +518,15 @@ only encrypt or decrypt using the named keys they need access to.
|
||||||
The key derivation context, provided as base64 encoded.
|
The key derivation context, provided as base64 encoded.
|
||||||
Must be provided if derivation is enabled.
|
Must be provided if derivation is enabled.
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="param">nonce</span>
|
||||||
|
<span class="param-flags">optional</span>
|
||||||
|
The nonce value, provided as base64 encoded. Must be provided if
|
||||||
|
convergent encryption is enabled for this key. The value must be
|
||||||
|
exactly 96 bits (12 bytes) long and the user must ensure that for any
|
||||||
|
given context (and thus, any given encryption key) this nonce value is
|
||||||
|
**never reused**.
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="param">bits</span>
|
<span class="param">bits</span>
|
||||||
<span class="param-flags">optional</span>
|
<span class="param-flags">optional</span>
|
||||||
|
|
Loading…
Reference in a new issue