Skip to content

Commit 7dfa1ae

Browse files
authored
fix: KeyringTraceFlag requirements and data key caching (#210)
cloneMaterial incorectly required WRAPPING_KEY_GENERATED_DATA_KEY. in the case of DecryptionMaterials, this should never be the case. When I wrote the tests, I did not take this case into consideration and simply set the generate and decrypt flags on the same keyringTrace. The organization of the traces has also changed. There is now a single trace for each “operation”. This mean that a trace will exists for the set operation as well as the encrypted data key. This lets me require only valid trace flags for each operation. I simplified the cloneMaterial function to rely on the set methods `setUnencryptedDataKey` and `addEncryptedDataKey` to deny invalid fields. I also, organize the KeyringTraceFlags to determin which flags are valid. The KMS Keyring needed to be updated to make sure that the trace contains the WRAPPING_KEY_SIGNED_ENC_CTX flag. Tests are updated, and added.
1 parent 6945eef commit 7dfa1ae

9 files changed

+190
-70
lines changed

modules/cache-material/test/caching_cryptographic_materials_decorators.test.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -198,22 +198,27 @@ describe('Cryptographic Material Functions', () => {
198198

199199
const nodeSuite = new NodeAlgorithmSuite(suiteId)
200200
const udk128 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])
201-
const trace = {
201+
const encryptTrace = {
202202
keyNamespace: 'keyNamespace',
203203
keyName: 'keyName',
204-
flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY | KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY
204+
flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY
205+
}
206+
const decryptTrace = {
207+
keyNamespace: 'keyNamespace',
208+
keyName: 'keyName',
209+
flags: KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY
205210
}
206211

207212
const edk1 = new EncryptedDataKey({ providerId: 'keyNamespace', providerInfo: 'keyName', encryptedDataKey: new Uint8Array([1]) })
208213
const edk2 = new EncryptedDataKey({ providerId: 'p2', providerInfo: 'pi2', encryptedDataKey: new Uint8Array([2]) })
209214

210215
const encryptionMaterial = new NodeEncryptionMaterial(nodeSuite, {})
211-
.setUnencryptedDataKey(udk128, trace)
216+
.setUnencryptedDataKey(udk128, encryptTrace)
212217
.addEncryptedDataKey(edk1, KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY)
213218
.addEncryptedDataKey(edk2, KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY)
214219

215220
const decryptionMaterial = new NodeDecryptionMaterial(nodeSuite, {})
216-
.setUnencryptedDataKey(udk128, trace)
221+
.setUnencryptedDataKey(udk128, decryptTrace)
217222

218223
const context = {}
219224

@@ -343,17 +348,17 @@ describe('Cryptographic Material Functions', () => {
343348

344349
const nodeSuite = new NodeAlgorithmSuite(suiteId)
345350
const udk128 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])
346-
const trace = {
351+
const encryptTrace = {
347352
keyNamespace: 'keyNamespace',
348353
keyName: 'keyName',
349-
flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY | KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY
354+
flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY
350355
}
351356

352357
const edk1 = new EncryptedDataKey({ providerId: 'keyNamespace', providerInfo: 'keyName', encryptedDataKey: new Uint8Array([1]) })
353358
const edk2 = new EncryptedDataKey({ providerId: 'p2', providerInfo: 'pi2', encryptedDataKey: new Uint8Array([2]) })
354359

355360
const encryptionMaterial = new NodeEncryptionMaterial(nodeSuite, {})
356-
.setUnencryptedDataKey(udk128, trace)
361+
.setUnencryptedDataKey(udk128, encryptTrace)
357362
.addEncryptedDataKey(edk1, KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY)
358363
.addEncryptedDataKey(edk2, KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY)
359364

@@ -401,17 +406,17 @@ describe('Cryptographic Material Functions', () => {
401406

402407
const nodeSuite = new NodeAlgorithmSuite(suiteId)
403408
const udk128 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])
404-
const trace = {
409+
const encryptTrace = {
405410
keyNamespace: 'keyNamespace',
406411
keyName: 'keyName',
407-
flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY | KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY
412+
flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY
408413
}
409414

410415
const edk1 = new EncryptedDataKey({ providerId: 'keyNamespace', providerInfo: 'keyName', encryptedDataKey: new Uint8Array([1]) })
411416
const edk2 = new EncryptedDataKey({ providerId: 'p2', providerInfo: 'pi2', encryptedDataKey: new Uint8Array([2]) })
412417

413418
const encryptionMaterial = new NodeEncryptionMaterial(nodeSuite, {})
414-
.setUnencryptedDataKey(udk128, trace)
419+
.setUnencryptedDataKey(udk128, encryptTrace)
415420
.addEncryptedDataKey(edk1, KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY)
416421
.addEncryptedDataKey(edk2, KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY)
417422

modules/kms-keyring/src/kms_keyring.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ export function KmsKeyringClass<S extends SupportedAlgorithmSuites, Client exten
123123
* See cryptographic_materials as setUnencryptedDataKey will throw in this case.
124124
*/
125125
.setUnencryptedDataKey(dataKey.Plaintext, trace)
126-
.addEncryptedDataKey(kmsResponseToEncryptedDataKey(dataKey), KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY)
126+
.addEncryptedDataKey(kmsResponseToEncryptedDataKey(dataKey),
127+
KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY | KeyringTraceFlag.WRAPPING_KEY_SIGNED_ENC_CTX)
127128
} else if (generatorKeyId) {
128129
keyIds.unshift(generatorKeyId)
129130
}

modules/kms-keyring/test/kms_keyring.onencrypt.test.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,22 @@ describe('KmsKeyring: _onEncrypt', () => {
8181
expect(edkEncrypt.providerId).to.equal('aws-kms')
8282
expect(edkEncrypt.providerInfo).to.equal(encryptKmsKey)
8383

84-
expect(material.keyringTrace).to.have.lengthOf(2)
85-
const [traceGenerate, traceEncrypt] = material.keyringTrace
84+
expect(material.keyringTrace).to.have.lengthOf(3)
85+
const [traceGenerate, traceEncrypt1, traceEncrypt2] = material.keyringTrace
8686
expect(traceGenerate.keyNamespace).to.equal('aws-kms')
8787
expect(traceGenerate.keyName).to.equal(generatorKeyId)
8888
expect(traceGenerate.flags & KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY).to.equal(KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY)
8989
expect(traceGenerate.flags & KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY).to.equal(KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY)
9090
expect(traceGenerate.flags & KeyringTraceFlag.WRAPPING_KEY_SIGNED_ENC_CTX).to.equal(KeyringTraceFlag.WRAPPING_KEY_SIGNED_ENC_CTX)
91-
expect(traceEncrypt.keyNamespace).to.equal('aws-kms')
92-
expect(traceEncrypt.keyName).to.equal(encryptKmsKey)
93-
expect(traceEncrypt.flags & KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY).to.equal(KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY)
94-
expect(traceEncrypt.flags & KeyringTraceFlag.WRAPPING_KEY_SIGNED_ENC_CTX).to.equal(KeyringTraceFlag.WRAPPING_KEY_SIGNED_ENC_CTX)
91+
92+
expect(traceEncrypt1.keyNamespace).to.equal('aws-kms')
93+
expect(traceEncrypt1.keyName).to.equal(generatorKeyId)
94+
expect(traceEncrypt1.flags & KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY).to.equal(KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY)
95+
expect(traceEncrypt1.flags & KeyringTraceFlag.WRAPPING_KEY_SIGNED_ENC_CTX).to.equal(KeyringTraceFlag.WRAPPING_KEY_SIGNED_ENC_CTX)
96+
expect(traceEncrypt2.keyNamespace).to.equal('aws-kms')
97+
expect(traceEncrypt2.keyName).to.equal(encryptKmsKey)
98+
expect(traceEncrypt2.flags & KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY).to.equal(KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY)
99+
expect(traceEncrypt2.flags & KeyringTraceFlag.WRAPPING_KEY_SIGNED_ENC_CTX).to.equal(KeyringTraceFlag.WRAPPING_KEY_SIGNED_ENC_CTX)
95100
})
96101

97102
it('Check for early return (Postcondition): Discovery Keyrings do not encrypt.', async () => {

modules/material-management/src/clone_cryptographic_material.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
import {
2828
AwsEsdkKeyObject // eslint-disable-line no-unused-vars
2929
} from './types'
30-
import { KeyringTraceFlag } from './keyring_trace'
30+
import { needs } from './needs'
3131

3232
type Material = NodeEncryptionMaterial|NodeDecryptionMaterial|WebCryptoEncryptionMaterial|WebCryptoDecryptionMaterial
3333

@@ -42,36 +42,49 @@ export function cloneMaterial<M extends Material> (source: M): M {
4242
? new WebCryptoEncryptionMaterial(suite, encryptionContext)
4343
: new WebCryptoDecryptionMaterial(suite, encryptionContext))
4444

45-
/* The WRAPPING_KEY_GENERATED_DATA_KEY _should_ be the first trace,
46-
* but it is better to look for it explicitly.
45+
/* The setTrace _must_ be the first trace,
46+
* If the material is an EncryptionMaterial
47+
* then the data key *must* have been generated.
48+
* If the material is DecryptionMaterial
49+
* then the data key *must* have been decrypted.
50+
* i.e. the required flags are:
51+
* WRAPPING_KEY_GENERATED_DATA_KEY, WRAPPING_KEY_DECRYPTED_DATA_KEY
52+
* These are controlled by the material itself.
53+
* Furthermore, subsequent trace entries,
54+
* *must* be in the same order as the added encrypted data keys.
55+
* See cryptographic_materials.ts `decorateCryptographicMaterial`, `decorateWebCryptoMaterial`.
4756
*/
48-
const trace = source.keyringTrace.find(({ flags }) => flags & KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY)
57+
const [setTrace, ...traces] = source.keyringTrace.slice()
4958

5059
if (source.hasUnencryptedDataKey) {
5160
const udk = cloneUnencryptedDataKey(source.getUnencryptedDataKey())
52-
if (!trace) throw new Error('Malformed source material.')
53-
clone.setUnencryptedDataKey(udk, trace)
61+
clone.setUnencryptedDataKey(udk, setTrace)
5462
}
5563

5664
if ((<WebCryptoDecryptionMaterial>source).hasCryptoKey) {
5765
const cryptoKey = (<WebCryptoDecryptionMaterial>source).getCryptoKey()
58-
if (!trace) throw new Error('Malformed source material.')
5966
;(<WebCryptoDecryptionMaterial>clone)
60-
.setCryptoKey(cryptoKey, trace)
67+
.setCryptoKey(cryptoKey, setTrace)
6168
}
6269

6370
if (isEncryptionMaterial(source) && isEncryptionMaterial(clone)) {
64-
source.encryptedDataKeys.forEach(edk => {
71+
const encryptedDataKeys = source.encryptedDataKeys
72+
/* Precondition: For each encrypted data key, there must be a trace. */
73+
needs(encryptedDataKeys.length === traces.length, 'KeyringTrace length does not match encrypted data keys.')
74+
encryptedDataKeys.forEach((edk, i) => {
6575
const { providerInfo, providerId } = edk
66-
const trace = source.keyringTrace.find(({ keyNamespace, keyName }) => keyName === providerInfo && keyNamespace === providerId)
67-
if (!trace) throw new Error('Malformed Encrypted Data Key')
68-
clone.addEncryptedDataKey(edk, trace.flags)
76+
const { keyNamespace, keyName, flags } = traces[i]
77+
/* Precondition: The traces must be in the same order as the encrypted data keys. */
78+
needs(keyName === providerInfo && keyNamespace === providerId, 'Keyring trace does not match encrypted data key.')
79+
clone.addEncryptedDataKey(edk, flags)
6980
})
7081

7182
if (source.suite.signatureCurve && source.signatureKey) {
7283
clone.setSignatureKey(source.signatureKey)
7384
}
7485
} else if (isDecryptionMaterial(source) && isDecryptionMaterial(clone)) {
86+
/* Precondition: On Decrypt there must not be any additional traces other than the setTrace. */
87+
needs(!traces.length, 'Only 1 trace is valid on DecryptionMaterials.')
7588
if (source.suite.signatureCurve && source.verificationKey) {
7689
clone.setVerificationKey(source.verificationKey)
7790
}

modules/material-management/src/cryptographic_material.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,20 @@ export function isDecryptionMaterial (obj: any): obj is WebCryptoDecryptionMater
287287
return (obj instanceof WebCryptoDecryptionMaterial) || (obj instanceof NodeDecryptionMaterial)
288288
}
289289

290-
export function decorateCryptographicMaterial<T extends CryptographicMaterial<T>> (material: T, setFlags: KeyringTraceFlag) {
290+
export function decorateCryptographicMaterial<T extends CryptographicMaterial<T>> (material: T, setFlag: KeyringTraceFlag) {
291+
/* Precondition: setFlag must be in the set of KeyringTraceFlag.SET_FLAGS. */
292+
needs(setFlag & KeyringTraceFlag.SET_FLAGS, 'Invalid setFlag')
293+
/* When a KeyringTraceFlag is passed to setUnencryptedDataKey,
294+
* it must be valid for the type of material.
295+
* It is invalid to claim that EncryptionMaterial were decrypted.
296+
*/
297+
const deniedSetFlags = (KeyringTraceFlag.SET_FLAGS ^ setFlag) | (
298+
setFlag === KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY
299+
? KeyringTraceFlag.DECRYPT_FLAGS
300+
: setFlag === KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY
301+
? KeyringTraceFlag.ENCRYPT_FLAGS
302+
: 0)
303+
291304
let unencryptedDataKeyZeroed = false
292305
let unencryptedDataKey: AwsEsdkKeyObject | Uint8Array
293306
// This copy of the unencryptedDataKey is stored to insure that the
@@ -390,11 +403,16 @@ export function decorateCryptographicMaterial<T extends CryptographicMaterial<T>
390403
/* Precondition: Trace must be set, and the flag must indicate that the data key was generated. */
391404
needs(trace && trace.keyName && trace.keyNamespace, 'Malformed KeyringTrace')
392405
/* Precondition: On set the required KeyringTraceFlag must be set. */
393-
needs(trace.flags & setFlags, 'Required KeyringTraceFlag not set')
406+
needs(trace.flags & setFlag, 'Required KeyringTraceFlag not set')
407+
/* Precondition: Only valid flags are allowed.
408+
* An unencrypted data key can not be both generated and decrypted.
409+
*/
410+
needs(!(trace.flags & deniedSetFlags), 'Invalid KeyringTraceFlags set.')
394411
}
395412
}
396413

397414
export function decorateEncryptionMaterial<T extends EncryptionMaterial<T>> (material: T) {
415+
const deniedEncryptFlags = KeyringTraceFlag.SET_FLAGS | KeyringTraceFlag.DECRYPT_FLAGS
398416
const encryptedDataKeys: EncryptedDataKey[] = []
399417
let signatureKey: Readonly<SignatureKey>|undefined
400418

@@ -412,16 +430,18 @@ export function decorateEncryptionMaterial<T extends EncryptionMaterial<T>> (mat
412430

413431
/* Precondition: flags must indicate that the key was encrypted. */
414432
needs(flags & KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY, 'Encrypted data key flag must be set.')
415-
/* When the unencrypted data key is first set, a given wrapping key may or may not also encrypt that key.
416-
* This means that the first EDK that is added may already have a trace.
417-
* The flags for the EDK and the existing trace should be merged iif this is the first EDK
418-
* and the only existing trace corresponds to this EDK.
433+
434+
/* Precondition: flags must not include a setFlag or a decrypt flag.
435+
* The setFlag is reserved for setting the unencrypted data key
436+
* and must only occur once in the set of KeyringTrace flags.
437+
* The two setFlags in use are:
438+
* KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY
439+
* KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY
440+
*
441+
* KeyringTraceFlag.WRAPPING_KEY_VERIFIED_ENC_CTX is reserved for the decrypt path
419442
*/
420-
if (firstEdkAndTraceMatch(encryptedDataKeys, material.keyringTrace, edk)) {
421-
material.keyringTrace[0].flags |= flags
422-
} else {
423-
material.keyringTrace.push({ keyName: edk.providerInfo, keyNamespace: edk.providerId, flags })
424-
}
443+
needs(!(flags & deniedEncryptFlags), 'Invalid flag for EncryptedDataKey.')
444+
material.keyringTrace.push({ keyName: edk.providerInfo, keyNamespace: edk.providerId, flags })
425445

426446
encryptedDataKeys.push(edk)
427447
return material
@@ -463,14 +483,6 @@ export function decorateEncryptionMaterial<T extends EncryptionMaterial<T>> (mat
463483
return material
464484
}
465485

466-
/* Verify that the this is the first EDK and that it matches the 1 and only 1 trace. */
467-
function firstEdkAndTraceMatch (edks: EncryptedDataKey[], traces: KeyringTrace[], edk: EncryptedDataKey) {
468-
return edks.length === 0 &&
469-
traces.length === 1 &&
470-
edk.providerId === traces[0].keyNamespace &&
471-
edk.providerInfo === traces[0].keyName
472-
}
473-
474486
export function decorateDecryptionMaterial<T extends DecryptionMaterial<T>> (material: T) {
475487
// Verification Key
476488
let verificationKey: Readonly<VerificationKey>|undefined

modules/material-management/src/keyring_trace.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,26 @@ export enum KeyringTraceFlag {
7272
* Bit flag indicating this wrapping key verified the signature of the encryption context.
7373
*/
7474
WRAPPING_KEY_VERIFIED_ENC_CTX = (1 << 4), // eslint-disable-line no-unused-vars
75+
76+
/* KeyringTraceFlags are organized here.
77+
* The three groupings are set, encrypt, and decrypt.
78+
* An unencrypted data key is set and is required to have a SET_FLAG.
79+
* For the encrypt path, the unencrypted data key must be generated.
80+
* For the decrypt path, the unencrypted data key must be decrypted.
81+
*
82+
* A encrypted data key must be encrypted
83+
* and the encryption context may be signed.
84+
*
85+
* When an encrypted data key is decrypted,
86+
* the encryption context may be verified.
87+
*
88+
* This organization is to keep a KeyringTrace for an encrypted data key
89+
* for listing the WRAPPING_KEY_VERIFIED_ENC_CTX flag.
90+
*/
91+
92+
ENCRYPT_FLAGS = WRAPPING_KEY_ENCRYPTED_DATA_KEY | WRAPPING_KEY_SIGNED_ENC_CTX, // eslint-disable-line no-unused-vars
93+
94+
SET_FLAGS = WRAPPING_KEY_GENERATED_DATA_KEY | WRAPPING_KEY_DECRYPTED_DATA_KEY, // eslint-disable-line no-unused-vars
95+
96+
DECRYPT_FLAGS = WRAPPING_KEY_DECRYPTED_DATA_KEY | WRAPPING_KEY_VERIFIED_ENC_CTX, // eslint-disable-line no-unused-vars
7597
}

0 commit comments

Comments
 (0)