Remove context-as-nonce, add docs, and properly support datakey

This commit is contained in:
Jeff Mitchell 2016-08-07 15:53:40 -04:00
parent b5858e2237
commit 84cd3c20b3
11 changed files with 100 additions and 207 deletions

View File

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

View File

@ -230,7 +230,6 @@ func testAccStepReadPolicy(t *testing.T, name string, expectNone, derived bool)
KDFMode string `mapstructure:"kdf_mode"` KDFMode string `mapstructure:"kdf_mode"`
DeletionAllowed bool `mapstructure:"deletion_allowed"` DeletionAllowed bool `mapstructure:"deletion_allowed"`
ConvergentEncryption bool `mapstructure:"convergent_encryption"` ConvergentEncryption bool `mapstructure:"convergent_encryption"`
ContextAsNonce bool `mapstructure:"context_as_nonce"`
} }
if err := mapstructure.Decode(resp.Data, &d); err != nil { if err := mapstructure.Decode(resp.Data, &d); err != nil {
return err return err
@ -608,6 +607,16 @@ 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"
@ -702,126 +711,6 @@ func TestConvergentEncryption(t *testing.T) {
} }
} }
func TestConvergentEncryptionContextAsNonce(t *testing.T) {
var b *backend
sysView := logical.TestSystemView()
storage := &logical.InmemStorage{}
b = Backend(&logical.BackendConfig{
StorageView: storage,
System: sysView,
})
req := &logical.Request{
Storage: storage,
Operation: logical.UpdateOperation,
Path: "keys/testkeynonderived",
Data: map[string]interface{}{
"derived": false,
"convergent_encryption": true,
"context_as_nonce": true,
},
}
resp, err := b.HandleRequest(req)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
if !resp.IsError() {
t.Fatalf("bad: expected error response, got %#v", *resp)
}
req.Path = "keys/testkey"
req.Data = map[string]interface{}{
"derived": true,
"convergent_encryption": true,
"context_as_nonce": true,
}
resp, err = b.HandleRequest(req)
if err != nil {
t.Fatal(err)
}
if resp != nil {
t.Fatalf("bad: got resp %#v", *resp)
}
// First, test using an invalid length of nonce
req.Path = "encrypt/testkey"
req.Data = map[string]interface{}{
"plaintext": "emlwIHphcA==", // "zip zap"
"context": "Zm9vIGJhcg==", // "foo bar"
}
resp, err = b.HandleRequest(req)
if resp == nil {
t.Fatal("expected non-nil response")
}
if !resp.IsError() {
t.Fatalf("expected error response, got %#v", *resp)
}
// Now test encrypting the same value twice
req.Data = map[string]interface{}{
"plaintext": "emlwIHphcA==", // "zip zap"
"context": "b25ldHdvdGhyZWVl", // "onetwothreee"
}
resp, err = b.HandleRequest(req)
if resp == nil {
t.Fatal("expected non-nil response")
}
if resp.IsError() {
t.Fatalf("got error response: %#v", *resp)
}
ciphertext1 := 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)
}
ciphertext2 := resp.Data["ciphertext"].(string)
if ciphertext1 != ciphertext2 {
t.Fatalf("expected the same ciphertext but got %s and %s", ciphertext1, ciphertext2)
}
// For sanity, also check a different value
req.Data = map[string]interface{}{
"plaintext": "emlwIHphcA==", // "zip zap"
"context": "dHdvdGhyZWVmb3Vy", // "twothreefour"
}
resp, err = b.HandleRequest(req)
if resp == nil {
t.Fatal("expected non-nil response")
}
if resp.IsError() {
t.Fatalf("got error response: %#v", *resp)
}
ciphertext3 := 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)
}
ciphertext4 := resp.Data["ciphertext"].(string)
if ciphertext3 != ciphertext4 {
t.Fatalf("expected the same ciphertext but got %s and %s", ciphertext3, ciphertext4)
}
if ciphertext1 == ciphertext3 {
t.Fatalf("expected different ciphertexts")
}
}
func TestPolicyFuzzing(t *testing.T) { func TestPolicyFuzzing(t *testing.T) {
var be *backend var be *backend
sysView := logical.TestSystemView() sysView := logical.TestSystemView()

View File

@ -105,42 +105,42 @@ func (lm *lockManager) UnlockPolicy(lock *sync.RWMutex, lockType bool) {
// is needed (for instance, for an upgrade/migration), give up the read lock, // is needed (for instance, for an upgrade/migration), give up the read lock,
// call again with an exclusive lock, then swap back out for a read lock. // call again with an exclusive lock, then swap back out for a read lock.
func (lm *lockManager) GetPolicyShared(storage logical.Storage, name string) (*Policy, *sync.RWMutex, error) { func (lm *lockManager) GetPolicyShared(storage logical.Storage, name string) (*Policy, *sync.RWMutex, error) {
p, lock, _, err := lm.getPolicyCommon(storage, name, false, false, false, false, shared) p, lock, _, err := lm.getPolicyCommon(storage, name, false, false, false, shared)
if err == nil || if err == nil ||
(err != nil && err != errNeedExclusiveLock) { (err != nil && err != errNeedExclusiveLock) {
return p, lock, err return p, lock, err
} }
// Try again while asking for an exlusive lock // Try again while asking for an exlusive lock
p, lock, _, err = lm.getPolicyCommon(storage, name, false, false, false, false, exclusive) p, lock, _, err = lm.getPolicyCommon(storage, name, false, false, false, exclusive)
if err != nil || p == nil || lock == nil { if err != nil || p == nil || lock == nil {
return p, lock, err return p, lock, err
} }
lock.Unlock() lock.Unlock()
p, lock, _, err = lm.getPolicyCommon(storage, name, false, false, false, false, shared) p, lock, _, err = lm.getPolicyCommon(storage, name, false, false, false, shared)
return p, lock, err return p, lock, err
} }
// Get the policy with an exclusive lock // Get the policy with an exclusive lock
func (lm *lockManager) GetPolicyExclusive(storage logical.Storage, name string) (*Policy, *sync.RWMutex, error) { func (lm *lockManager) GetPolicyExclusive(storage logical.Storage, name string) (*Policy, *sync.RWMutex, error) {
p, lock, _, err := lm.getPolicyCommon(storage, name, false, false, false, false, exclusive) p, lock, _, err := lm.getPolicyCommon(storage, name, false, false, false, exclusive)
return p, lock, err return p, lock, err
} }
// 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, convergent, contextAsNonce 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, contextAsNonce, 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) {
return p, lock, false, err return p, lock, false, err
} }
// Try again while asking for an exlusive lock // Try again while asking for an exlusive lock
p, lock, upserted, err := lm.getPolicyCommon(storage, name, true, derived, convergent, contextAsNonce, exclusive) p, lock, upserted, err := lm.getPolicyCommon(storage, name, true, derived, convergent, exclusive)
if err != nil || p == nil || lock == nil { if err != nil || p == nil || lock == nil {
return p, lock, upserted, err return p, lock, upserted, err
} }
@ -148,14 +148,14 @@ func (lm *lockManager) GetPolicyUpsert(storage logical.Storage, name string, der
lock.Unlock() lock.Unlock()
// Now get a shared lock for the return, but preserve the value of upsert // Now get a shared lock for the return, but preserve the value of upsert
p, lock, _, err = lm.getPolicyCommon(storage, name, true, derived, convergent, contextAsNonce, shared) p, lock, _, err = lm.getPolicyCommon(storage, name, true, derived, convergent, shared)
return p, lock, upserted, err return p, lock, upserted, err
} }
// When the function returns, a lock will be held on the policy if err == nil. // When the function returns, a lock will be held on the policy if err == nil.
// It is the caller's responsibility to unlock. // It is the caller's responsibility to unlock.
func (lm *lockManager) getPolicyCommon(storage logical.Storage, name string, upsert, derived, convergent, contextAsNonce, lockType bool) (*Policy, *sync.RWMutex, bool, error) { func (lm *lockManager) getPolicyCommon(storage logical.Storage, name string, upsert, derived, convergent, lockType bool) (*Policy, *sync.RWMutex, bool, error) {
lock := lm.policyLock(name, lockType) lock := lm.policyLock(name, lockType)
var p *Policy var p *Policy
@ -204,8 +204,6 @@ func (lm *lockManager) getPolicyCommon(storage logical.Storage, name string, ups
if derived { if derived {
p.KDFMode = kdfMode p.KDFMode = kdfMode
p.ConvergentEncryption = convergent p.ConvergentEncryption = convergent
p.ContextAsNonce = new(bool)
*p.ContextAsNonce = contextAsNonce
} }
err = p.rotate(storage) err = p.rotate(storage)

View File

@ -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, nil, 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:

View File

@ -30,7 +30,7 @@ func (b *backend) pathDecrypt() *framework.Path {
"nonce": &framework.FieldSchema{ "nonce": &framework.FieldSchema{
Type: framework.TypeString, Type: framework.TypeString,
Description: "Nonce for when convergent encryption is used and the context is not used as the nonce", Description: "Nonce for when convergent encryption is used",
}, },
}, },

View File

@ -31,7 +31,7 @@ func (b *backend) pathEncrypt() *framework.Path {
"nonce": &framework.FieldSchema{ "nonce": &framework.FieldSchema{
Type: framework.TypeString, Type: framework.TypeString,
Description: "Nonce for when convergent encryption is used and the context is not used as the nonce", Description: "Nonce for when convergent encryption is used",
}, },
}, },
@ -95,7 +95,7 @@ func (b *backend) pathEncryptWrite(
var lock *sync.RWMutex var lock *sync.RWMutex
var upserted bool var upserted bool
if req.Operation == logical.CreateOperation { if req.Operation == logical.CreateOperation {
p, lock, upserted, err = b.lm.GetPolicyUpsert(req.Storage, name, len(context) != 0, false, false) p, lock, upserted, err = b.lm.GetPolicyUpsert(req.Storage, name, len(context) != 0, false)
} else { } else {
p, lock, err = b.lm.GetPolicyShared(req.Storage, name) p, lock, err = b.lm.GetPolicyShared(req.Storage, name)
} }

View File

@ -29,31 +29,14 @@ allows for per-transaction unique keys.`,
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
requests to carry both a context and 96-bit requests to carry both a context and 96-bit
(12-byte) nonce, unless the "context_as_nonce" (12-byte) nonce. The given nonce will be used
feature is also enabled. The given nonce will in place of a randomly generated nonce. As a
be used in place of a randomly generated nonce. result, when the same context and nonce are
As a result, when the same context and nonce supplied, the same ciphertext is generated. It
(or context, if "context_as_nonce" is enabled) is *very important* when using this mode that
are supplied, the same ciphertext is emitted you ensure that all nonces are unique for a
from the encryption function. It is *very given context. Failing to do so will severely
important* when using this mode that you ensure impact the ciphertext's security.`,
that all nonces are unique for a given context,
or, when using "context_as_nonce", that all
contexts are unique for a given key. Failing to
do so will severely impact the ciphertext's
security.`,
},
"context_as_nonce": &framework.FieldSchema{
Type: framework.TypeBool,
Description: `Whether to use the context value as the
nonce in the convergent encryption operation
mode. If set true, the user will have to
supply a 96-bit (12-byte) context value.
It is *very important* when using this
mode that you ensure that all contexts are
*globally unique*. Failing to do so will
severely impact the security of the key.`,
}, },
}, },
@ -73,13 +56,12 @@ func (b *backend) pathPolicyWrite(
name := d.Get("name").(string) name := d.Get("name").(string)
derived := d.Get("derived").(bool) derived := d.Get("derived").(bool)
convergent := d.Get("convergent_encryption").(bool) convergent := d.Get("convergent_encryption").(bool)
contextAsNonce := d.Get("context_as_nonce").(bool)
if !derived && convergent { if !derived && convergent {
return logical.ErrorResponse("convergent encryption requires derivation to be enabled"), nil return logical.ErrorResponse("convergent encryption requires derivation to be enabled"), nil
} }
p, lock, upserted, err := b.lm.GetPolicyUpsert(req.Storage, name, derived, convergent, contextAsNonce) p, lock, upserted, err := b.lm.GetPolicyUpsert(req.Storage, name, derived, convergent)
if lock != nil { if lock != nil {
defer lock.RUnlock() defer lock.RUnlock()
} }
@ -127,9 +109,6 @@ func (b *backend) pathPolicyRead(
if p.Derived { if p.Derived {
resp.Data["kdf_mode"] = p.KDFMode resp.Data["kdf_mode"] = p.KDFMode
resp.Data["convergent_encryption"] = p.ConvergentEncryption resp.Data["convergent_encryption"] = p.ConvergentEncryption
if p.ContextAsNonce != nil {
resp.Data["context_as_nonce"] = *p.ContextAsNonce
}
} }
retKeys := map[string]int64{} retKeys := map[string]int64{}

View File

@ -30,7 +30,7 @@ func (b *backend) pathRewrap() *framework.Path {
"nonce": &framework.FieldSchema{ "nonce": &framework.FieldSchema{
Type: framework.TypeString, Type: framework.TypeString,
Description: "Nonce for when convergent encryption is used and the context is not used as the nonce", Description: "Nonce for when convergent encryption is used",
}, },
}, },

View File

@ -72,7 +72,6 @@ type Policy struct {
Derived bool `json:"derived"` Derived bool `json:"derived"`
KDFMode string `json:"kdf_mode"` KDFMode string `json:"kdf_mode"`
ConvergentEncryption bool `json:"convergent_encryption"` ConvergentEncryption bool `json:"convergent_encryption"`
ContextAsNonce *bool `json:"context_as_nonce"`
// The minimum version of the key allowed to be used // The minimum version of the key allowed to be used
// for decryption // for decryption
@ -260,10 +259,6 @@ func (p *Policy) needsUpgrade() bool {
return true return true
} }
if p.ConvergentEncryption && p.ContextAsNonce == nil {
return true
}
return false return false
} }
@ -293,14 +288,6 @@ func (p *Policy) upgrade(storage logical.Storage) error {
persistNeeded = true persistNeeded = true
} }
// Originally the context-as-nonce mode was the only mode, so keep that
// behavior if convergent encryption is already in use
if p.ConvergentEncryption && p.ContextAsNonce == nil {
p.ContextAsNonce = new(bool)
*p.ContextAsNonce = true
persistNeeded = true
}
if persistNeeded { if persistNeeded {
err := p.Persist(storage) err := p.Persist(storage)
if err != nil { if err != nil {
@ -377,17 +364,9 @@ func (p *Policy) Encrypt(context, nonce []byte, value string) (string, error) {
} }
if p.ConvergentEncryption { if p.ConvergentEncryption {
if len(nonce) != gcm.NonceSize() {
if *p.ContextAsNonce {
if len(context) != gcm.NonceSize() {
return "", errutil.UserError{Err: fmt.Sprintf("base64-decoded context must be %d bytes long when using convergent encryption with context-as-nonce with this key", gcm.NonceSize())}
}
nonce = context
} else 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())} 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 // Compute random nonce
nonce = make([]byte, gcm.NonceSize()) nonce = make([]byte, gcm.NonceSize())
@ -421,7 +400,7 @@ 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.ContextAsNonce && (nonce == nil || len(nonce) == 0) { if p.ConvergentEncryption && (nonce == nil || len(nonce) == 0) {
return "", errutil.UserError{Err: "invalid convergent nonce supplied"} return "", errutil.UserError{Err: "invalid convergent nonce supplied"}
} }
@ -483,9 +462,6 @@ 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 { if p.ConvergentEncryption {
if *p.ContextAsNonce {
nonce = context
}
ciphertext = decoded ciphertext = decoded
} else { } else {
nonce = decoded[:gcm.NonceSize()] nonce = decoded[:gcm.NonceSize()]

View File

@ -22,7 +22,7 @@ func Test_KeyUpgrade(t *testing.T) {
func testKeyUpgradeCommon(t *testing.T, lm *lockManager) { func testKeyUpgradeCommon(t *testing.T, lm *lockManager) {
storage := &logical.InmemStorage{} storage := &logical.InmemStorage{}
p, lock, upserted, err := lm.GetPolicyUpsert(storage, "test", false, false, false) p, lock, upserted, err := lm.GetPolicyUpsert(storage, "test", false, false)
if lock != nil { if lock != nil {
defer lock.RUnlock() defer lock.RUnlock()
} }
@ -68,7 +68,7 @@ func testArchivingUpgradeCommon(t *testing.T, lm *lockManager) {
storage := &logical.InmemStorage{} storage := &logical.InmemStorage{}
p, lock, _, err := lm.GetPolicyUpsert(storage, "test", false, false, false) p, lock, _, err := lm.GetPolicyUpsert(storage, "test", false, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -198,7 +198,7 @@ func testArchivingCommon(t *testing.T, lm *lockManager) {
storage := &logical.InmemStorage{} storage := &logical.InmemStorage{}
p, lock, _, err := lm.GetPolicyUpsert(storage, "test", false, false, false) p, lock, _, err := lm.GetPolicyUpsert(storage, "test", false, false)
if lock != nil { if lock != nil {
defer lock.RUnlock() defer lock.RUnlock()
} }

View File

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