diff --git a/modules/cache-material/src/caching_cryptographic_materials_decorators.ts b/modules/cache-material/src/caching_cryptographic_materials_decorators.ts index b5d0549b2..16f9b817b 100644 --- a/modules/cache-material/src/caching_cryptographic_materials_decorators.ts +++ b/modules/cache-material/src/caching_cryptographic_materials_decorators.ts @@ -110,9 +110,15 @@ export function getEncryptionMaterials ( } if (!this._cacheEntryHasExceededLimits(testEntry)) { this._cache.putEncryptionMaterial(cacheKey, material, plaintextLength, this._maxAge) + return cloneResponse(material) + } else { + /* Postcondition: If the material has exceeded limits it MUST NOT be cloned. + * If it is cloned, and the clone is returned, + * then there exist a copy of the unencrypted data key. + * It is true that this data would be caught by GC, it is better to just not rely on that. + */ + return material } - - return cloneResponse(material) } } diff --git a/modules/cache-material/test/caching_cryptographic_materials_decorators.test.ts b/modules/cache-material/test/caching_cryptographic_materials_decorators.test.ts index 9457b87d4..20c1df81f 100644 --- a/modules/cache-material/test/caching_cryptographic_materials_decorators.test.ts +++ b/modules/cache-material/test/caching_cryptographic_materials_decorators.test.ts @@ -393,6 +393,69 @@ describe('Cryptographic Material Functions', () => { expect(assertCount).to.equal(1) expect(test === encryptionMaterial).to.equal(true) }) + + it('Postcondition: If the material has exceeded limits it MUST NOT be cloned.', async () => { + let assertCount = 0 + + const suiteId = AlgorithmSuiteIdentifier.ALG_AES128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256 + + const nodeSuite = new NodeAlgorithmSuite(suiteId) + const udk128 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]) + const trace = { + keyNamespace: 'keyNamespace', + keyName: 'keyName', + flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY | KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY + } + + const edk1 = new EncryptedDataKey({ providerId: 'keyNamespace', providerInfo: 'keyName', encryptedDataKey: new Uint8Array([1]) }) + const edk2 = new EncryptedDataKey({ providerId: 'p2', providerInfo: 'pi2', encryptedDataKey: new Uint8Array([2]) }) + + const encryptionMaterial = new NodeEncryptionMaterial(nodeSuite, {}) + .setUnencryptedDataKey(udk128, trace) + .addEncryptedDataKey(edk1, KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY) + .addEncryptedDataKey(edk2, KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY) + + const testCMM = { + _partition, + _maxAge, + _maxBytesEncrypted, + _maxMessagesEncrypted, + _cache: { + getEncryptionMaterial () { + assertCount += 1 + return false + }, + del () {} + }, + _backingMaterialsManager: { + getEncryptionMaterials () { + assertCount += 1 + return encryptionMaterial + } + }, + _cacheEntryHasExceededLimits: () => { + // This is the test. + // If the entry is cashable, + // but has exceeded limit... + assertCount += 1 + return true + }, + getEncryptionMaterials: getEncryptionMaterials(cacheKeyHelpers), + decryptMaterials: () => { + throw new Error('this should never happen') + } + } as any + + const test = await testCMM.getEncryptionMaterials({ + suite: nodeSuite, + encryptionContext: context, + frameLength: 10, + plaintextLength: 10 + }) + + expect(assertCount).to.equal(3) + expect(test === encryptionMaterial).to.equal(true) + }) }) describe('decryptionMaterial', () => {