From adf8ef38951cb53558e8cb4f41934750a62eae97 Mon Sep 17 00:00:00 2001 From: seebees Date: Mon, 8 Apr 2019 14:20:58 -0700 Subject: [PATCH 01/12] feat: cacheing material management * Add abstract methods in cache-material * Add implementation in caching-materials-manager-node * update material-management to export more granular interface * update serialize with max values and `serializeEncryptedDataKey` --- modules/cache-material/LICENSE | 202 ++++++++++++++++++ modules/cache-material/NOTICE | 2 + modules/cache-material/package.json | 54 +++++ modules/cache-material/src/cache.ts | 136 ++++++++++++ .../cache-material/src/cache_decorators.ts | 159 ++++++++++++++ modules/cache-material/src/cache_key.ts | 98 +++++++++ modules/cache-material/src/index.ts | 18 ++ modules/cache-material/tsconfig.json | 13 ++ modules/cache-material/tsconfig.module.json | 12 ++ .../caching-materials-manager-node/LICENSE | 202 ++++++++++++++++++ modules/caching-materials-manager-node/NOTICE | 2 + .../package.json | 53 +++++ .../src/caching_materials_manager_node.ts | 61 ++++++ .../src/index.ts | 16 ++ .../tsconfig.json | 13 ++ .../tsconfig.module.json | 12 ++ modules/material-management-node/src/index.ts | 3 +- modules/material-management/src/index.ts | 2 +- .../src/materials_manager.ts | 17 +- modules/serialize/src/identifiers.ts | 17 ++ modules/serialize/src/serialize_factory.ts | 24 ++- 21 files changed, 1099 insertions(+), 17 deletions(-) create mode 100644 modules/cache-material/LICENSE create mode 100644 modules/cache-material/NOTICE create mode 100644 modules/cache-material/package.json create mode 100644 modules/cache-material/src/cache.ts create mode 100644 modules/cache-material/src/cache_decorators.ts create mode 100644 modules/cache-material/src/cache_key.ts create mode 100644 modules/cache-material/src/index.ts create mode 100644 modules/cache-material/tsconfig.json create mode 100644 modules/cache-material/tsconfig.module.json create mode 100644 modules/caching-materials-manager-node/LICENSE create mode 100644 modules/caching-materials-manager-node/NOTICE create mode 100644 modules/caching-materials-manager-node/package.json create mode 100644 modules/caching-materials-manager-node/src/caching_materials_manager_node.ts create mode 100644 modules/caching-materials-manager-node/src/index.ts create mode 100644 modules/caching-materials-manager-node/tsconfig.json create mode 100644 modules/caching-materials-manager-node/tsconfig.module.json diff --git a/modules/cache-material/LICENSE b/modules/cache-material/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/modules/cache-material/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/modules/cache-material/NOTICE b/modules/cache-material/NOTICE new file mode 100644 index 000000000..88f7bea1e --- /dev/null +++ b/modules/cache-material/NOTICE @@ -0,0 +1,2 @@ +AWS Encryption SDK for Javascript +Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/modules/cache-material/package.json b/modules/cache-material/package.json new file mode 100644 index 000000000..8367379bc --- /dev/null +++ b/modules/cache-material/package.json @@ -0,0 +1,54 @@ +{ + "name": "@aws-crypto/cache-material", + "private": true, + "version": "0.0.1", + "scripts": { + "prepublishOnly": "npm run build", + "build": "tsc -b tsconfig.json && tsc -b tsconfig.module.json", + "lint": "standard src/*.ts test/**/*.ts", + "mocha": "mocha --require ts-node/register test/**/*test.ts", + "test": "npm run lint && npm run coverage", + "coverage": "nyc -e .ts npm run mocha" + }, + "author": { + "name": "AWS Crypto Tools Team", + "email": "aws-crypto-tools-team@amazon.com", + "url": "https://github.com/awslabs/aws-encryption-sdk-javascript" + }, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/material-management": "^0.1.0", + "@aws-crypto/serialize": "^0.0.1", + "@types/lru-cache": "^5.1.0", + "lru-cache": "^5.1.1", + "tslib": "^1.9.3" + }, + "devDependencies": { + "@types/chai": "^4.1.4", + "@types/mocha": "^5.2.5", + "@types/node": "^11.11.4", + "@types/chai-as-promised": "^7.1.0", + "@typescript-eslint/eslint-plugin": "^1.4.2", + "@typescript-eslint/parser": "^1.4.2", + "chai": "^4.1.2", + "chai-as-promised": "^7.1.1", + "mocha": "^5.2.0", + "nyc": "^12.0.2", + "standard": "^12.0.1", + "ts-node": "^7.0.1", + "typescript": "^3.2.0" + }, + "sideEffects": false, + "main": "./build/main/index.js", + "module": "./build/module/index.js", + "types": "./build/main/index.d.ts", + "files": [ + "./build/**/*" + ], + "standard": { + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ] + } +} diff --git a/modules/cache-material/src/cache.ts b/modules/cache-material/src/cache.ts new file mode 100644 index 000000000..744e7c66e --- /dev/null +++ b/modules/cache-material/src/cache.ts @@ -0,0 +1,136 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use + * this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import LRU from 'lru-cache' +import { + EncryptionResponse, + DecryptionResponse, + SupportedAlgorithmSuites, + needs, + isEncryptionMaterial, + isDecryptionMaterial +} from '@aws-crypto/material-management' + +export function getCache( + maxSize: number +): Cache { + const cache = new LRU>({ + max: maxSize, + dispose(_key, value) { + /* Zero out the unencrypted dataKey, when the material is removed from the cache. */ + value.response.material.zeroUnencryptedDataKey() + } + }) + + return { + putEncryptionResponse( + key: string, + response: EncryptionResponse, + plaintextLength: number, + maxAge?: number + ) { + /* Precondition: plaintextLength can not be negative */ + needs(plaintextLength > 0, '') + /* Precondition: Only cache EncryptionMaterial. */ + needs(isEncryptionMaterial(response.material), '') + /* Precondition: Only cache EncryptionMaterial that is cacheSafe. */ + needs(response.material.suite.cacheSafe, '') + const entry = Object.seal({ + response: Object.freeze(response), + bytesEncrypted: plaintextLength, + messagesEncrypted: 1, + now: Date.now() + }) + + cache.set(key, entry, maxAge) + }, + putDecryptionResponse( + key: string, + response: DecryptionResponse, + maxAge?: number + ) { + /* Precondition: Only cache DecryptionMaterial. */ + needs(isDecryptionMaterial(response.material), '') + /* Precondition: Only cache DecryptionMaterial that is cacheSafe. */ + needs(response.material.suite.cacheSafe, '') + const entry = Object.seal({ + response: Object.freeze(response), + bytesEncrypted: 0, + messagesEncrypted: 0, + now: Date.now() + }) + + cache.set(key, entry, maxAge) + }, + getEncryptionResponse(key: string, plaintextLength: number) { + /* Precondition: plaintextLength can not be negative */ + needs(plaintextLength > 0, '') + const entry = cache.get(key) + /* Check for early return (Postcondition): If this key does have EncryptionMaterial, return false. */ + if (!entry) return false + /* Postcondition: Only return EncryptionMaterial. */ + needs(isEncryptionMaterial(entry.response.material), '') + + entry.bytesEncrypted += plaintextLength + entry.messagesEncrypted += 1 + + return >entry + }, + getDecryptionResponse(key: string){ + const entry = cache.get(key) + /* Check for early return (Postcondition): If this key does have DecryptionMaterial, return false. */ + if (!entry) return false + /* Postcondition: Only return DecryptionMaterial. */ + needs(isDecryptionMaterial(entry.response.material), '') + + return >entry + }, + del(key: string) { + cache.del(key) + } + } +} + +export interface Cache { + putEncryptionResponse( + key: string, + response: EncryptionResponse, + plaintextLength: number, + maxAge?: number + ): void + putDecryptionResponse( + key: string, + response: DecryptionResponse, + maxAge?: number + ): void + getEncryptionResponse(key: string, plaintextLength: number): EncryptionResponseEntry|false + getDecryptionResponse(key: string): DecryptionResponseEntry|false + del(key: string): void +} + +export interface Entry { + readonly response: EncryptionResponse|DecryptionResponse + bytesEncrypted: number + messagesEncrypted: number + readonly now: number +} + +export interface EncryptionResponseEntry extends Entry { + readonly response: EncryptionResponse +} + +export interface DecryptionResponseEntry extends Entry { + readonly response: DecryptionResponse +} diff --git a/modules/cache-material/src/cache_decorators.ts b/modules/cache-material/src/cache_decorators.ts new file mode 100644 index 000000000..39465db7f --- /dev/null +++ b/modules/cache-material/src/cache_decorators.ts @@ -0,0 +1,159 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use + * this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + GetEncryptionMaterials, + GetDecryptMaterials, + DecryptionResponse, + SupportedAlgorithmSuites, + EncryptionRequest, + EncryptionResponse, + MaterialsManager, + DecryptionRequest, + needs, + readOnlyProperty, + Keyring +} from '@aws-crypto/material-management' +import {Maximum} from '@aws-crypto/serialize' +import {Cache, Entry} from './cache' +import {Build} from './cache_key' + +export function decorateProperties( + obj: CachingMaterialsManager, + input: CachingMaterialsManagerDecorateInput +) { + const {cache, backingMaterialsManager, maxAge, maxBytesEncrypted, maxMessagesEncrypted} = input + + needs(cache, '') + needs(backingMaterialsManager, '') + needs(!maxAge || maxAge > 0, '') + needs(!maxBytesEncrypted || (maxBytesEncrypted > 0 && Maximum.BYTES_PER_KEY > maxBytesEncrypted), '') + needs(!maxMessagesEncrypted || (maxMessagesEncrypted > 0 && Maximum.MESSAGES_PER_KEY > maxMessagesEncrypted), '') + + readOnlyProperty(obj, '_cache', cache) + readOnlyProperty(obj, '_backingMaterialsManager', backingMaterialsManager) + readOnlyProperty(obj, '_maxAge', maxAge) + readOnlyProperty(obj, '_maxBytesEncrypted', maxBytesEncrypted || Maximum.BYTES_PER_KEY) + readOnlyProperty(obj, '_maxMessagesEncrypted', maxMessagesEncrypted || Maximum.MESSAGES_PER_KEY) +} + +export function getEncryptionMaterials( + {buildEncryptionResponseCacheKey}: Build +): GetEncryptionMaterials { + return async function getEncryptionMaterials( + this: CachingMaterialsManager, + request: EncryptionRequest + ): Promise> { + const {suite, encryptionContext, frameLength, plaintextLength} = request + /* Check for early return (Postcondition): If I can not cache the EncryptionResponse, do not even look. */ + if ((suite && !suite.cacheSafe) || !plaintextLength) { + return this + ._backingMaterialsManager + .getEncryptionMaterials(request) + } + + const cacheKey = await buildEncryptionResponseCacheKey(this._partition, {suite, encryptionContext}) + const entry = this._cache.getEncryptionResponse(cacheKey, plaintextLength) + /* Check for early return (Postcondition): If I have a valid EncryptionResponse, return it. */ + if (entry && !this._cacheEntryHasExceededLimits(entry)) { + return entry.response + } + + const response = await this + ._backingMaterialsManager + /* Strip any information about the plaintext from the backing request, + * because the resulting response may be used to encrypt multiple plaintexts. + */ + .getEncryptionMaterials({suite, encryptionContext, frameLength}) + + /* Check for early return (Postcondition): If I can not cache the EncryptionResponse, just return it. */ + if (!response.material.suite.cacheSafe) return response + + this._cache.putEncryptionResponse(cacheKey, response, plaintextLength, this._maxAge) + return response + } +} + +export function decryptMaterials( + {buildDecryptionResponseCacheKey}: Build +): GetDecryptMaterials { + return async function decryptMaterials( + this: CachingMaterialsManager, + request: DecryptionRequest + ): Promise> { + + const {suite} = request + /* Check for early return (Postcondition): If I can not cache the DecryptionResponse, do not even look. */ + if (!suite.cacheSafe) { + return this + ._backingMaterialsManager + .decryptMaterials(request) + } + + const cacheKey = await buildDecryptionResponseCacheKey(this._partition, request) + const entry = this._cache.getDecryptionResponse(cacheKey) + /* Check for early return (Postcondition): If I have a valid EncryptionResponse, return it. */ + if (entry && !this._cacheEntryHasExceededLimits(entry)) { + return entry.response + } + + const response = await this + ._backingMaterialsManager + .decryptMaterials(request) + + this._cache.putDecryptionResponse(cacheKey, response, this._maxAge) + return response + } +} + +export function cacheEntryHasExceededLimits(): CacheEntryHasExceededLimits { + return function cacheEntryHasExceededLimits( + this: CachingMaterialsManager, + {now, messagesEncrypted, bytesEncrypted}: Entry + ): boolean { + const age = Date.now() - now + return (!this._maxAge || age > this._maxAge) || + messagesEncrypted > this._maxMessagesEncrypted || + bytesEncrypted > this._maxBytesEncrypted + } +} + +export interface CachingMaterialsManagerInput extends Readonly<{ + cache: Cache + backingMaterials: MaterialsManager|Keyring + partition?: string + maxBytesEncrypted?: number + maxMessagesEncrypted?: number + maxAge?: number +}>{} + +export interface CachingMaterialsManagerDecorateInput extends CachingMaterialsManagerInput { + backingMaterialsManager: MaterialsManager +} + +export interface CachingMaterialsManager extends MaterialsManager { + readonly _partition: string + readonly _cache: Cache + readonly _backingMaterialsManager: MaterialsManager + readonly _maxBytesEncrypted: number + readonly _maxMessagesEncrypted: number + readonly _maxAge?: number + + _cacheEntryHasExceededLimits: CacheEntryHasExceededLimits +} + +export interface CacheEntryHasExceededLimits { + (entry: Entry): boolean +} diff --git a/modules/cache-material/src/cache_key.ts b/modules/cache-material/src/cache_key.ts new file mode 100644 index 000000000..e049132aa --- /dev/null +++ b/modules/cache-material/src/cache_key.ts @@ -0,0 +1,98 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use + * this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {SupportedAlgorithmSuites, DecryptionRequest, EncryptionRequest, EncryptedDataKey, EncryptionContext} from '@aws-crypto/material-management' +import { serializeFactory, uInt16BE } from '@aws-crypto/serialize' + +// 512 bits of 0 for padding between hashes in decryption materials cache ID generation. +const BIT_PAD_512 = Buffer.alloc(64) + +export function build( + fromUtf8: (input: string) => Uint8Array, + sha512Hex: (...data: ((Uint8Array|string))[]) => Promise +): Build { + const { + serializeEncryptionContext, + encodeEncryptionContext, + serializeEncryptedDataKey + } = serializeFactory(fromUtf8) + + return { + buildEncryptionResponseCacheKey, + buildDecryptionResponseCacheKey, + encryptedDataKeysHash, + encryptionContextHash + } + + async function buildEncryptionResponseCacheKey( + partition: string, + {suite, encryptionContext}: EncryptionRequest + ) { + const algorithmInfo = suite + ? [Buffer.from([1]), uInt16BE(suite.id)] + : [Buffer.alloc(0)] + + return sha512Hex( + await sha512Hex(partition), + ...algorithmInfo, + await encryptionContextHash(encryptionContext) + ) + } + + async function buildDecryptionResponseCacheKey( + partition: string, + {suite, encryptedDataKeys, encryptionContext}: DecryptionRequest + ) { + const { id } = suite + + return sha512Hex( + await sha512Hex(partition), + uInt16BE(id), + ...(await encryptedDataKeysHash(encryptedDataKeys)), + BIT_PAD_512, + await encryptionContextHash(encryptionContext) + ) + } + + async function encryptedDataKeysHash(encryptedDataKeys: ReadonlyArray) { + const hashes = await Promise.all( + encryptedDataKeys + .map(serializeEncryptedDataKey) + .map(edk => sha512Hex(edk)) + ) + return hashes + // is this sort valid? locally, it should be fine + .sort((a, b) => a.localeCompare(b)) + } + + function encryptionContextHash(context?: EncryptionContext) { + const encodedContext = encodeEncryptionContext(context || {}) + const serializedContext = serializeEncryptionContext(encodedContext) + return sha512Hex(serializedContext) + } +} + +export interface Build { + buildEncryptionResponseCacheKey( + partition: string, + {suite, encryptionContext}: EncryptionRequest + ): Promise + buildDecryptionResponseCacheKey( + partition: string, + {suite, encryptedDataKeys, encryptionContext}: DecryptionRequest + ): Promise + encryptedDataKeysHash(encryptedDataKeys: ReadonlyArray): Promise + encryptionContextHash(context?: EncryptionContext): Promise +} diff --git a/modules/cache-material/src/index.ts b/modules/cache-material/src/index.ts new file mode 100644 index 000000000..d887ef9d8 --- /dev/null +++ b/modules/cache-material/src/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use + * this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './cache' +export * from './cache_decorators' +export * from './cache_key' diff --git a/modules/cache-material/tsconfig.json b/modules/cache-material/tsconfig.json new file mode 100644 index 000000000..378049926 --- /dev/null +++ b/modules/cache-material/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.settings.json", + "compilerOptions": { + "outDir": "build/main", + "rootDir": "./src" + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules/**"], + "references": [ + { "path": "../material-management" }, + { "path": "../serialize" }, + ] +} \ No newline at end of file diff --git a/modules/cache-material/tsconfig.module.json b/modules/cache-material/tsconfig.module.json new file mode 100644 index 000000000..50bf04db4 --- /dev/null +++ b/modules/cache-material/tsconfig.module.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig", + "compilerOptions": { + "target": "esnext", + "outDir": "build/module", + "module": "esnext", + "allowSyntheticDefaultImports": true + }, + "exclude": [ + "node_modules/**" + ] +} \ No newline at end of file diff --git a/modules/caching-materials-manager-node/LICENSE b/modules/caching-materials-manager-node/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/modules/caching-materials-manager-node/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/modules/caching-materials-manager-node/NOTICE b/modules/caching-materials-manager-node/NOTICE new file mode 100644 index 000000000..88f7bea1e --- /dev/null +++ b/modules/caching-materials-manager-node/NOTICE @@ -0,0 +1,2 @@ +AWS Encryption SDK for Javascript +Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/modules/caching-materials-manager-node/package.json b/modules/caching-materials-manager-node/package.json new file mode 100644 index 000000000..a69754f37 --- /dev/null +++ b/modules/caching-materials-manager-node/package.json @@ -0,0 +1,53 @@ +{ + "name": "@aws-crypto/caching-materials-manager-node", + "private": true, + "version": "0.0.1", + "scripts": { + "prepublishOnly": "npm run build", + "build": "tsc -b tsconfig.json && tsc -b tsconfig.module.json", + "lint": "standard src/*.ts test/**/*.ts", + "mocha": "mocha --require ts-node/register test/**/*test.ts", + "test": "npm run lint && npm run coverage", + "coverage": "nyc -e .ts npm run mocha" + }, + "author": { + "name": "AWS Crypto Tools Team", + "email": "aws-crypto-tools-team@amazon.com", + "url": "https://github.com/awslabs/aws-encryption-sdk-javascript" + }, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/material-management-node": "^0.0.1", + "@aws-crypto/cache-material": "^0.0.1", + "@aws-crypto/serialize": "^0.0.1", + "tslib": "^1.9.3" + }, + "devDependencies": { + "@types/chai": "^4.1.4", + "@types/mocha": "^5.2.5", + "@types/node": "^11.11.4", + "@types/chai-as-promised": "^7.1.0", + "@typescript-eslint/eslint-plugin": "^1.4.2", + "@typescript-eslint/parser": "^1.4.2", + "chai": "^4.1.2", + "chai-as-promised": "^7.1.1", + "mocha": "^5.2.0", + "nyc": "^12.0.2", + "standard": "^12.0.1", + "ts-node": "^7.0.1", + "typescript": "^3.2.0" + }, + "sideEffects": false, + "main": "./build/main/index.js", + "module": "./build/module/index.js", + "types": "./build/main/index.d.ts", + "files": [ + "./build/**/*" + ], + "standard": { + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ] + } +} diff --git a/modules/caching-materials-manager-node/src/caching_materials_manager_node.ts b/modules/caching-materials-manager-node/src/caching_materials_manager_node.ts new file mode 100644 index 000000000..130037961 --- /dev/null +++ b/modules/caching-materials-manager-node/src/caching_materials_manager_node.ts @@ -0,0 +1,61 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use + * this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + CachingMaterialsManager, + decorateProperties, + getEncryptionMaterials, + decryptMaterials, + cacheEntryHasExceededLimits, + build, + CachingMaterialsManagerInput, + Cache +} from '@aws-crypto/cache-material' +import { + MaterialsManager, + NodeCryptographicMaterialsManager, + NodeAlgorithmSuite, + KeyringNode, +} from '@aws-crypto/material-management-node' + +import {createHash} from 'crypto' + +const fromUtf8 = (input: string) => Buffer.from(input, 'utf8') +const sha512Hex = async (...data: (Uint8Array|string)[]) => data + .reduce((hash, item) => hash.update(item), createHash('sha512')) + .digest('hex') + +const buildCacheKey = build(fromUtf8, sha512Hex) + +export class NodeCachingMaterialsManager implements CachingMaterialsManager { + readonly _cache!: Cache + readonly _backingMaterialsManager!: MaterialsManager + readonly _partition!: string + readonly _maxBytesEncrypted!: number + readonly _maxMessagesEncrypted!: number + readonly _maxAge?: number + + constructor(input: CachingMaterialsManagerInput) { + const backingMaterialsManager = input.backingMaterials instanceof KeyringNode + ? new NodeCryptographicMaterialsManager(input.backingMaterials) + : input.backingMaterials + + decorateProperties(this, {backingMaterialsManager, ...input}) + } + + getEncryptionMaterials = getEncryptionMaterials(buildCacheKey) + decryptMaterials = decryptMaterials(buildCacheKey) + _cacheEntryHasExceededLimits = cacheEntryHasExceededLimits() +} diff --git a/modules/caching-materials-manager-node/src/index.ts b/modules/caching-materials-manager-node/src/index.ts new file mode 100644 index 000000000..40ad42123 --- /dev/null +++ b/modules/caching-materials-manager-node/src/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use + * this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './caching_materials_manager_node' diff --git a/modules/caching-materials-manager-node/tsconfig.json b/modules/caching-materials-manager-node/tsconfig.json new file mode 100644 index 000000000..06ab91136 --- /dev/null +++ b/modules/caching-materials-manager-node/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.settings.json", + "compilerOptions": { + "outDir": "build/main", + "rootDir": "./src" + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules/**"], + "references": [ + { "path": "../material-management-node" }, + { "path": "../cache-material" }, + ] +} \ No newline at end of file diff --git a/modules/caching-materials-manager-node/tsconfig.module.json b/modules/caching-materials-manager-node/tsconfig.module.json new file mode 100644 index 000000000..50bf04db4 --- /dev/null +++ b/modules/caching-materials-manager-node/tsconfig.module.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig", + "compilerOptions": { + "target": "esnext", + "outDir": "build/module", + "module": "esnext", + "allowSyntheticDefaultImports": true + }, + "exclude": [ + "node_modules/**" + ] +} \ No newline at end of file diff --git a/modules/material-management-node/src/index.ts b/modules/material-management-node/src/index.ts index 71feee68d..c3976fd8c 100644 --- a/modules/material-management-node/src/index.ts +++ b/modules/material-management-node/src/index.ts @@ -24,5 +24,6 @@ export { NodeDecryptionMaterial, NodeEncryptionMaterial, NodeAlgorithmSuite, AlgorithmSuiteIdentifier, EncryptionContext, EncryptedDataKey, KeyringTrace, KeyringTraceFlag, needs, KeyringNode, MultiKeyringNode, - immutableBaseClass, immutableClass, frozenClass, readOnlyProperty + immutableBaseClass, immutableClass, frozenClass, readOnlyProperty, + MaterialsManager, GetEncryptionMaterials, GetDecryptMaterials } from '@aws-crypto/material-management' diff --git a/modules/material-management/src/index.ts b/modules/material-management/src/index.ts index 942da83a3..89447cfb0 100644 --- a/modules/material-management/src/index.ts +++ b/modules/material-management/src/index.ts @@ -25,7 +25,7 @@ export { NodeAlgorithmSuite } from './node_algorithms' export { Keyring, KeyringNode, KeyringWebCrypto } from './keyring' export { KeyringTrace, KeyringTraceFlag } from './keyring_trace' export { MultiKeyringNode, MultiKeyringWebCrypto } from './multi_keyring' -export { NodeMaterialsManager, WebCryptoMaterialsManager } from './materials_manager' +export * from './materials_manager' export { NodeEncryptionMaterial, NodeDecryptionMaterial } from './cryptographic_material' export { isValidCryptoKey, isCryptoKey, keyUsageForMaterial, subtleFunctionForMaterial } from './cryptographic_material' diff --git a/modules/material-management/src/materials_manager.ts b/modules/material-management/src/materials_manager.ts index 08d8ade52..fc022873f 100644 --- a/modules/material-management/src/materials_manager.ts +++ b/modules/material-management/src/materials_manager.ts @@ -13,7 +13,7 @@ * limitations under the License. */ -import { Keyring, EncryptionRequest, DecryptionRequest } from '.' // eslint-disable-line no-unused-vars +import { EncryptionRequest, DecryptionRequest } from '.' // eslint-disable-line no-unused-vars import { EncryptionResponse, DecryptionResponse, SupportedAlgorithmSuites } from './types' // eslint-disable-line no-unused-vars import { NodeAlgorithmSuite } from './node_algorithms' // eslint-disable-line no-unused-vars import { WebCryptoAlgorithmSuite } from './web_crypto_algorithms' // eslint-disable-line no-unused-vars @@ -25,10 +25,17 @@ import { WebCryptoAlgorithmSuite } from './web_crypto_algorithms' // eslint-disa * need to use it and you should not do so. */ -interface MaterialsManager { - readonly keyring: Keyring - getEncryptionMaterials(request: EncryptionRequest): Promise> - decryptMaterials(request: DecryptionRequest): Promise> +export interface GetEncryptionMaterials { + (request: EncryptionRequest): Promise> +} + +export interface GetDecryptMaterials { + (request: DecryptionRequest): Promise> +} + +export interface MaterialsManager { + getEncryptionMaterials: GetEncryptionMaterials + decryptMaterials: GetDecryptMaterials } export interface NodeMaterialsManager extends MaterialsManager {} diff --git a/modules/serialize/src/identifiers.ts b/modules/serialize/src/identifiers.ts index 11ebe25b3..a624d9679 100644 --- a/modules/serialize/src/identifiers.ts +++ b/modules/serialize/src/identifiers.ts @@ -58,3 +58,20 @@ export enum SequenceIdentifier { SEQUENCE_NUMBER_END = 0xFFFFFFFF // eslint-disable-line no-unused-vars } Object.freeze(SequenceIdentifier) + +export enum Maximum { + //Maximum number of messages which are allowed to be encrypted under a single cached data key + MESSAGES_PER_KEY = 4294967296, // 2 ** 32 + //Maximum number of bytes which are allowed to be encrypted under a single cached data key + BYTES_PER_KEY = 9223372036854775807, // 2 ** 63 - 1 + //Maximum number of frames allowed in one message as defined in specification + FRAME_COUNT = 4294967295, // 2 ** 32 - 1 + //Maximum bytes allowed in a single frame as defined in specification + FRAME_SIZE = 2147483647, // 2 ** 31 - 1 + //Maximum bytes allowed in a non-framed message ciphertext as defined in specification + GCM_CONTENT_SIZE = 68719476704, // 2 ** 36 - 32 + NON_FRAMED_SIZE = 68719476704, // 2 ** 36 - 32 + //Maximum number of AAD bytes allowed as defined in specification + AAD_BYTE_SIZE = 65535, // 2 ** 16 - 1 +} +Object.freeze(Maximum) diff --git a/modules/serialize/src/serialize_factory.ts b/modules/serialize/src/serialize_factory.ts index 6c77ff1e4..9244dd2fe 100644 --- a/modules/serialize/src/serialize_factory.ts +++ b/modules/serialize/src/serialize_factory.ts @@ -38,6 +38,7 @@ export function serializeFactory (fromUtf8: (input: any) => Uint8Array) { encodeEncryptionContext, serializeEncryptionContext, serializeEncryptedDataKeys, + serializeEncryptedDataKey, serializeMessageHeader } @@ -94,16 +95,7 @@ export function serializeFactory (fromUtf8: (input: any) => Uint8Array) { function serializeEncryptedDataKeys (encryptedDataKeys: ReadonlyArray) { const encryptedKeyInfo = encryptedDataKeys - .map(({ providerId, providerInfo, encryptedDataKey, rawInfo }) => { - const providerIdBytes = fromUtf8(providerId) - // The providerInfo is technically a binary field, so I prefer rawInfo - const providerInfoBytes = rawInfo || fromUtf8(providerInfo) - return concatBuffers( - uInt16BE(providerIdBytes.byteLength), providerIdBytes, - uInt16BE(providerInfoBytes.byteLength), providerInfoBytes, - uInt16BE(encryptedDataKey.byteLength), encryptedDataKey - ) - }) + .map(serializeEncryptedDataKey) return concatBuffers( uInt16BE(encryptedDataKeys.length), @@ -111,6 +103,18 @@ export function serializeFactory (fromUtf8: (input: any) => Uint8Array) { ) } + function serializeEncryptedDataKey(edk: EncryptedDataKey) { + const { providerId, providerInfo, encryptedDataKey, rawInfo } = edk + const providerIdBytes = fromUtf8(providerId) + // The providerInfo is technically a binary field, so I prefer rawInfo + const providerInfoBytes = rawInfo || fromUtf8(providerInfo) + return concatBuffers( + uInt16BE(providerIdBytes.byteLength), providerIdBytes, + uInt16BE(providerInfoBytes.byteLength), providerInfoBytes, + uInt16BE(encryptedDataKey.byteLength), encryptedDataKey + ) + } + function serializeMessageHeader (messageHeader: MessageHeader) { return concatBuffers( uInt8(messageHeader.version), From afd22ec0ba13fd675f2eda0e24aab03af3753b66 Mon Sep 17 00:00:00 2001 From: seebees Date: Tue, 9 Apr 2019 13:44:55 -0700 Subject: [PATCH 02/12] cache names --- modules/cache-material/src/cache.ts | 90 +----------------- modules/cache-material/src/local_cache.ts | 111 ++++++++++++++++++++++ 2 files changed, 114 insertions(+), 87 deletions(-) create mode 100644 modules/cache-material/src/local_cache.ts diff --git a/modules/cache-material/src/cache.ts b/modules/cache-material/src/cache.ts index 744e7c66e..2c7f30491 100644 --- a/modules/cache-material/src/cache.ts +++ b/modules/cache-material/src/cache.ts @@ -13,96 +13,12 @@ * limitations under the License. */ -import LRU from 'lru-cache' import { - EncryptionResponse, - DecryptionResponse, - SupportedAlgorithmSuites, - needs, - isEncryptionMaterial, - isDecryptionMaterial + EncryptionResponse, // eslint-disable-line no-unused-vars + DecryptionResponse, // eslint-disable-line no-unused-vars + SupportedAlgorithmSuites // eslint-disable-line no-unused-vars } from '@aws-crypto/material-management' -export function getCache( - maxSize: number -): Cache { - const cache = new LRU>({ - max: maxSize, - dispose(_key, value) { - /* Zero out the unencrypted dataKey, when the material is removed from the cache. */ - value.response.material.zeroUnencryptedDataKey() - } - }) - - return { - putEncryptionResponse( - key: string, - response: EncryptionResponse, - plaintextLength: number, - maxAge?: number - ) { - /* Precondition: plaintextLength can not be negative */ - needs(plaintextLength > 0, '') - /* Precondition: Only cache EncryptionMaterial. */ - needs(isEncryptionMaterial(response.material), '') - /* Precondition: Only cache EncryptionMaterial that is cacheSafe. */ - needs(response.material.suite.cacheSafe, '') - const entry = Object.seal({ - response: Object.freeze(response), - bytesEncrypted: plaintextLength, - messagesEncrypted: 1, - now: Date.now() - }) - - cache.set(key, entry, maxAge) - }, - putDecryptionResponse( - key: string, - response: DecryptionResponse, - maxAge?: number - ) { - /* Precondition: Only cache DecryptionMaterial. */ - needs(isDecryptionMaterial(response.material), '') - /* Precondition: Only cache DecryptionMaterial that is cacheSafe. */ - needs(response.material.suite.cacheSafe, '') - const entry = Object.seal({ - response: Object.freeze(response), - bytesEncrypted: 0, - messagesEncrypted: 0, - now: Date.now() - }) - - cache.set(key, entry, maxAge) - }, - getEncryptionResponse(key: string, plaintextLength: number) { - /* Precondition: plaintextLength can not be negative */ - needs(plaintextLength > 0, '') - const entry = cache.get(key) - /* Check for early return (Postcondition): If this key does have EncryptionMaterial, return false. */ - if (!entry) return false - /* Postcondition: Only return EncryptionMaterial. */ - needs(isEncryptionMaterial(entry.response.material), '') - - entry.bytesEncrypted += plaintextLength - entry.messagesEncrypted += 1 - - return >entry - }, - getDecryptionResponse(key: string){ - const entry = cache.get(key) - /* Check for early return (Postcondition): If this key does have DecryptionMaterial, return false. */ - if (!entry) return false - /* Postcondition: Only return DecryptionMaterial. */ - needs(isDecryptionMaterial(entry.response.material), '') - - return >entry - }, - del(key: string) { - cache.del(key) - } - } -} - export interface Cache { putEncryptionResponse( key: string, diff --git a/modules/cache-material/src/local_cache.ts b/modules/cache-material/src/local_cache.ts new file mode 100644 index 000000000..cd70ac219 --- /dev/null +++ b/modules/cache-material/src/local_cache.ts @@ -0,0 +1,111 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use + * this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +import LRU from 'lru-cache' +import { + EncryptionResponse, + DecryptionResponse, + SupportedAlgorithmSuites, + needs, + isEncryptionMaterial, + isDecryptionMaterial +} from '@aws-crypto/material-management' + +import { + Cache, // eslint-disable-line no-unused-vars + Entry, // eslint-disable-line no-unused-vars + EncryptionResponseEntry, // eslint-disable-line no-unused-vars + DecryptionResponseEntry // eslint-disable-line no-unused-vars +} from './cache' + +export function getLocalCache( + maxSize: number +): Cache { + const cache = new LRU>({ + max: maxSize, + dispose(_key, value) { + /* Zero out the unencrypted dataKey, when the material is removed from the cache. */ + value.response.material.zeroUnencryptedDataKey() + } + }) + + return { + putEncryptionResponse( + key: string, + response: EncryptionResponse, + plaintextLength: number, + maxAge?: number + ) { + /* Precondition: plaintextLength can not be negative */ + needs(plaintextLength > 0, '') + /* Precondition: Only cache EncryptionMaterial. */ + needs(isEncryptionMaterial(response.material), '') + /* Precondition: Only cache EncryptionMaterial that is cacheSafe. */ + needs(response.material.suite.cacheSafe, '') + const entry = Object.seal({ + response: Object.freeze(response), + bytesEncrypted: plaintextLength, + messagesEncrypted: 1, + now: Date.now() + }) + + cache.set(key, entry, maxAge) + }, + putDecryptionResponse( + key: string, + response: DecryptionResponse, + maxAge?: number + ) { + /* Precondition: Only cache DecryptionMaterial. */ + needs(isDecryptionMaterial(response.material), '') + /* Precondition: Only cache DecryptionMaterial that is cacheSafe. */ + needs(response.material.suite.cacheSafe, '') + const entry = Object.seal({ + response: Object.freeze(response), + bytesEncrypted: 0, + messagesEncrypted: 0, + now: Date.now() + }) + + cache.set(key, entry, maxAge) + }, + getEncryptionResponse(key: string, plaintextLength: number) { + /* Precondition: plaintextLength can not be negative */ + needs(plaintextLength > 0, '') + const entry = cache.get(key) + /* Check for early return (Postcondition): If this key does have EncryptionMaterial, return false. */ + if (!entry) return false + /* Postcondition: Only return EncryptionMaterial. */ + needs(isEncryptionMaterial(entry.response.material), '') + + entry.bytesEncrypted += plaintextLength + entry.messagesEncrypted += 1 + + return >entry + }, + getDecryptionResponse(key: string){ + const entry = cache.get(key) + /* Check for early return (Postcondition): If this key does have DecryptionMaterial, return false. */ + if (!entry) return false + /* Postcondition: Only return DecryptionMaterial. */ + needs(isDecryptionMaterial(entry.response.material), '') + + return >entry + }, + del(key: string) { + cache.del(key) + } + } +} From 1cbec63024d6c76fac9bb82d0b7d44ae3c31e83c Mon Sep 17 00:00:00 2001 From: seebees Date: Tue, 9 Apr 2019 13:57:52 -0700 Subject: [PATCH 03/12] comment --- modules/cache-material/src/cache_decorators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cache-material/src/cache_decorators.ts b/modules/cache-material/src/cache_decorators.ts index 39465db7f..5ad5260fd 100644 --- a/modules/cache-material/src/cache_decorators.ts +++ b/modules/cache-material/src/cache_decorators.ts @@ -104,7 +104,7 @@ export function decryptMaterials( const cacheKey = await buildDecryptionResponseCacheKey(this._partition, request) const entry = this._cache.getDecryptionResponse(cacheKey) - /* Check for early return (Postcondition): If I have a valid EncryptionResponse, return it. */ + /* Check for early return (Postcondition): If I have a valid DecryptionResponse, return it. */ if (entry && !this._cacheEntryHasExceededLimits(entry)) { return entry.response } From 3c9fc1478465aed4d6440210bebe2005e061128a Mon Sep 17 00:00:00 2001 From: seebees Date: Tue, 9 Apr 2019 14:12:07 -0700 Subject: [PATCH 04/12] =?UTF-8?q?remove=20on=20exceed=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cache-material/src/cache_decorators.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/modules/cache-material/src/cache_decorators.ts b/modules/cache-material/src/cache_decorators.ts index 5ad5260fd..60fb61d29 100644 --- a/modules/cache-material/src/cache_decorators.ts +++ b/modules/cache-material/src/cache_decorators.ts @@ -69,6 +69,8 @@ export function getEncryptionMaterials( /* Check for early return (Postcondition): If I have a valid EncryptionResponse, return it. */ if (entry && !this._cacheEntryHasExceededLimits(entry)) { return entry.response + } else { + this._cache.del(cacheKey) } const response = await this @@ -81,7 +83,21 @@ export function getEncryptionMaterials( /* Check for early return (Postcondition): If I can not cache the EncryptionResponse, just return it. */ if (!response.material.suite.cacheSafe) return response - this._cache.putEncryptionResponse(cacheKey, response, plaintextLength, this._maxAge) + /* It is possible for an entry to exceed limits immediately. + * The simplest case is to need to encrypt more than then maxBytesEncrypted. + * In this case, I return the response to encrypt the data, + * but do not put a know invalid item into the cache. + */ + const testEntry = { + response, + now: Date.now(), + messagesEncrypted: 1, + bytesEncrypted: plaintextLength + } + if (!this._cacheEntryHasExceededLimits(testEntry)) { + this._cache.putEncryptionResponse(cacheKey, response, plaintextLength, this._maxAge) + } + return response } } @@ -107,6 +123,8 @@ export function decryptMaterials( /* Check for early return (Postcondition): If I have a valid DecryptionResponse, return it. */ if (entry && !this._cacheEntryHasExceededLimits(entry)) { return entry.response + } else { + this._cache.del(cacheKey) } const response = await this From 8eafd0f33caa58070c4430e4b95453dd0fe99c28 Mon Sep 17 00:00:00 2001 From: seebees Date: Wed, 10 Apr 2019 13:45:57 -0700 Subject: [PATCH 05/12] upate --- modules/cache-material/src/local_cache.ts | 4 ++-- modules/serialize/src/identifiers.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/cache-material/src/local_cache.ts b/modules/cache-material/src/local_cache.ts index cd70ac219..d2d7d5a2b 100644 --- a/modules/cache-material/src/local_cache.ts +++ b/modules/cache-material/src/local_cache.ts @@ -85,7 +85,7 @@ export function getLocalCache( /* Precondition: plaintextLength can not be negative */ needs(plaintextLength > 0, '') const entry = cache.get(key) - /* Check for early return (Postcondition): If this key does have EncryptionMaterial, return false. */ + /* Check for early return (Postcondition): If this key does not have an EncryptionMaterial, return false. */ if (!entry) return false /* Postcondition: Only return EncryptionMaterial. */ needs(isEncryptionMaterial(entry.response.material), '') @@ -97,7 +97,7 @@ export function getLocalCache( }, getDecryptionResponse(key: string){ const entry = cache.get(key) - /* Check for early return (Postcondition): If this key does have DecryptionMaterial, return false. */ + /* Check for early return (Postcondition): If this key does not have a DecryptionMaterial, return false. */ if (!entry) return false /* Postcondition: Only return DecryptionMaterial. */ needs(isDecryptionMaterial(entry.response.material), '') diff --git a/modules/serialize/src/identifiers.ts b/modules/serialize/src/identifiers.ts index a624d9679..104f5cadf 100644 --- a/modules/serialize/src/identifiers.ts +++ b/modules/serialize/src/identifiers.ts @@ -67,7 +67,7 @@ export enum Maximum { //Maximum number of frames allowed in one message as defined in specification FRAME_COUNT = 4294967295, // 2 ** 32 - 1 //Maximum bytes allowed in a single frame as defined in specification - FRAME_SIZE = 2147483647, // 2 ** 31 - 1 + FRAME_SIZE = 4294967295, // 2 ** 32 - 1 //Maximum bytes allowed in a non-framed message ciphertext as defined in specification GCM_CONTENT_SIZE = 68719476704, // 2 ** 36 - 32 NON_FRAMED_SIZE = 68719476704, // 2 ** 36 - 32 From 5dfbf9966edde8eb662c79a499701f1f6921cadd Mon Sep 17 00:00:00 2001 From: seebees Date: Tue, 16 Apr 2019 08:17:16 -0700 Subject: [PATCH 06/12] renames --- ...ptographic_materials_cache_key_helpers.ts} | 6 +- ....ts => caching_cryptographic_materials.ts} | 12 ++-- .../src/clone_cryptographic_material.ts | 68 +++++++++++++++++++ ...he.ts => cryptographic_materials_cache.ts} | 2 +- ...et_local_cryptographic_materials_cache.ts} | 8 +-- modules/cache-material/src/index.ts | 7 +- .../src/caching_materials_manager_node.ts | 12 ++-- 7 files changed, 92 insertions(+), 23 deletions(-) rename modules/cache-material/src/{cache_key.ts => build_cryptographic_materials_cache_key_helpers.ts} (92%) rename modules/cache-material/src/{cache_decorators.ts => caching_cryptographic_materials.ts} (93%) create mode 100644 modules/cache-material/src/clone_cryptographic_material.ts rename modules/cache-material/src/{cache.ts => cryptographic_materials_cache.ts} (95%) rename modules/cache-material/src/{local_cache.ts => get_local_cryptographic_materials_cache.ts} (93%) diff --git a/modules/cache-material/src/cache_key.ts b/modules/cache-material/src/build_cryptographic_materials_cache_key_helpers.ts similarity index 92% rename from modules/cache-material/src/cache_key.ts rename to modules/cache-material/src/build_cryptographic_materials_cache_key_helpers.ts index e049132aa..df9fa8c78 100644 --- a/modules/cache-material/src/cache_key.ts +++ b/modules/cache-material/src/build_cryptographic_materials_cache_key_helpers.ts @@ -19,10 +19,10 @@ import { serializeFactory, uInt16BE } from '@aws-crypto/serialize' // 512 bits of 0 for padding between hashes in decryption materials cache ID generation. const BIT_PAD_512 = Buffer.alloc(64) -export function build( +export function buildCryptographicMaterialsCacheKeyHelpers( fromUtf8: (input: string) => Uint8Array, sha512Hex: (...data: ((Uint8Array|string))[]) => Promise -): Build { +): CryptographicMaterialsCacheKeyHelpersInterface { const { serializeEncryptionContext, encodeEncryptionContext, @@ -84,7 +84,7 @@ export function build( } } -export interface Build { +export interface CryptographicMaterialsCacheKeyHelpersInterface { buildEncryptionResponseCacheKey( partition: string, {suite, encryptionContext}: EncryptionRequest diff --git a/modules/cache-material/src/cache_decorators.ts b/modules/cache-material/src/caching_cryptographic_materials.ts similarity index 93% rename from modules/cache-material/src/cache_decorators.ts rename to modules/cache-material/src/caching_cryptographic_materials.ts index 60fb61d29..b9c7f4379 100644 --- a/modules/cache-material/src/cache_decorators.ts +++ b/modules/cache-material/src/caching_cryptographic_materials.ts @@ -27,8 +27,8 @@ import { Keyring } from '@aws-crypto/material-management' import {Maximum} from '@aws-crypto/serialize' -import {Cache, Entry} from './cache' -import {Build} from './cache_key' +import {CryptographicMaterialsCache, Entry} from './cryptographic_materials_cache' +import {CryptographicMaterialsCacheKeyHelpersInterface} from './build_cryptographic_materials_cache_key_helpers' export function decorateProperties( obj: CachingMaterialsManager, @@ -50,7 +50,7 @@ export function decorateProperties( } export function getEncryptionMaterials( - {buildEncryptionResponseCacheKey}: Build + {buildEncryptionResponseCacheKey}: CryptographicMaterialsCacheKeyHelpersInterface ): GetEncryptionMaterials { return async function getEncryptionMaterials( this: CachingMaterialsManager, @@ -103,7 +103,7 @@ export function getEncryptionMaterials( } export function decryptMaterials( - {buildDecryptionResponseCacheKey}: Build + {buildDecryptionResponseCacheKey}: CryptographicMaterialsCacheKeyHelpersInterface ): GetDecryptMaterials { return async function decryptMaterials( this: CachingMaterialsManager, @@ -149,7 +149,7 @@ export function cacheEntryHasExceededLimits( } export interface CachingMaterialsManagerInput extends Readonly<{ - cache: Cache + cache: CryptographicMaterialsCache backingMaterials: MaterialsManager|Keyring partition?: string maxBytesEncrypted?: number @@ -163,7 +163,7 @@ export interface CachingMaterialsManagerDecorateInput extends MaterialsManager { readonly _partition: string - readonly _cache: Cache + readonly _cache: CryptographicMaterialsCache readonly _backingMaterialsManager: MaterialsManager readonly _maxBytesEncrypted: number readonly _maxMessagesEncrypted: number diff --git a/modules/cache-material/src/clone_cryptographic_material.ts b/modules/cache-material/src/clone_cryptographic_material.ts new file mode 100644 index 000000000..9d1af2416 --- /dev/null +++ b/modules/cache-material/src/clone_cryptographic_material.ts @@ -0,0 +1,68 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use + * this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import { + NodeEncryptionMaterial, + NodeDecryptionMaterial, + WebCryptoEncryptionMaterial, + WebCryptoDecryptionMaterial, + isEncryptionMaterial, + isDecryptionMaterial, + +} from '@aws-crypto/material-management' + +type Material = NodeEncryptionMaterial|NodeDecryptionMaterial|WebCryptoEncryptionMaterial|WebCryptoDecryptionMaterial + +export function cloneMaterial(source: Material) { + + const clone = source instanceof NodeEncryptionMaterial + ? new NodeEncryptionMaterial(source.suite) + : source instanceof NodeDecryptionMaterial + ? new NodeDecryptionMaterial(source.suite) + : source instanceof WebCryptoEncryptionMaterial + ? new WebCryptoEncryptionMaterial(source.suite) + : source instanceof WebCryptoDecryptionMaterial + ? new WebCryptoDecryptionMaterial(source.suite) + : false + + if (!clone) throw new Error('') + + const udk = new Uint8Array(source.getUnencryptedDataKey()) + clone.setUnencryptedDataKey(udk, clone.keyringTrace[0]) + if ((source).hasCryptoKey) { + const cryptoKey = (source).getCryptoKey() + ;(clone) + .setCryptoKey(cryptoKey, clone.keyringTrace[0]) + } + + if (isEncryptionMaterial(source) && isEncryptionMaterial(clone)) { + source.encryptedDataKeys.forEach((edk, i) => { + clone.addEncryptedDataKey(edk, clone.keyringTrace[i].flags) + }) + + if (source.suite.signatureCurve && source.signatureKey) { + clone.setSignatureKey(source.signatureKey) + } + } else if (isDecryptionMaterial(source) && isDecryptionMaterial(clone)) { + if (source.suite.signatureCurve && source.verificationKey) { + clone.setVerificationKey(source.verificationKey) + } + } + + + + return clone +} \ No newline at end of file diff --git a/modules/cache-material/src/cache.ts b/modules/cache-material/src/cryptographic_materials_cache.ts similarity index 95% rename from modules/cache-material/src/cache.ts rename to modules/cache-material/src/cryptographic_materials_cache.ts index 2c7f30491..37d8b66d0 100644 --- a/modules/cache-material/src/cache.ts +++ b/modules/cache-material/src/cryptographic_materials_cache.ts @@ -19,7 +19,7 @@ import { SupportedAlgorithmSuites // eslint-disable-line no-unused-vars } from '@aws-crypto/material-management' -export interface Cache { +export interface CryptographicMaterialsCache { putEncryptionResponse( key: string, response: EncryptionResponse, diff --git a/modules/cache-material/src/local_cache.ts b/modules/cache-material/src/get_local_cryptographic_materials_cache.ts similarity index 93% rename from modules/cache-material/src/local_cache.ts rename to modules/cache-material/src/get_local_cryptographic_materials_cache.ts index d2d7d5a2b..fc419b9ac 100644 --- a/modules/cache-material/src/local_cache.ts +++ b/modules/cache-material/src/get_local_cryptographic_materials_cache.ts @@ -24,15 +24,15 @@ import { } from '@aws-crypto/material-management' import { - Cache, // eslint-disable-line no-unused-vars + CryptographicMaterialsCache, // eslint-disable-line no-unused-vars Entry, // eslint-disable-line no-unused-vars EncryptionResponseEntry, // eslint-disable-line no-unused-vars DecryptionResponseEntry // eslint-disable-line no-unused-vars -} from './cache' +} from './cryptographic_materials_cache' -export function getLocalCache( +export function getLocalCryptographicMaterialsCache( maxSize: number -): Cache { +): CryptographicMaterialsCache { const cache = new LRU>({ max: maxSize, dispose(_key, value) { diff --git a/modules/cache-material/src/index.ts b/modules/cache-material/src/index.ts index d887ef9d8..8bec2cfba 100644 --- a/modules/cache-material/src/index.ts +++ b/modules/cache-material/src/index.ts @@ -13,6 +13,7 @@ * limitations under the License. */ -export * from './cache' -export * from './cache_decorators' -export * from './cache_key' +export * from './cryptographic_materials_cache' +export * from './caching_cryptographic_materials' +export * from './build_cryptographic_materials_cache_key_helpers' +export * from './clone_cryptographic_material' diff --git a/modules/caching-materials-manager-node/src/caching_materials_manager_node.ts b/modules/caching-materials-manager-node/src/caching_materials_manager_node.ts index 130037961..e6c208468 100644 --- a/modules/caching-materials-manager-node/src/caching_materials_manager_node.ts +++ b/modules/caching-materials-manager-node/src/caching_materials_manager_node.ts @@ -19,9 +19,9 @@ import { getEncryptionMaterials, decryptMaterials, cacheEntryHasExceededLimits, - build, + buildCryptographicMaterialsCacheKeyHelpers, CachingMaterialsManagerInput, - Cache + CryptographicMaterialsCache } from '@aws-crypto/cache-material' import { MaterialsManager, @@ -37,10 +37,10 @@ const sha512Hex = async (...data: (Uint8Array|string)[]) => data .reduce((hash, item) => hash.update(item), createHash('sha512')) .digest('hex') -const buildCacheKey = build(fromUtf8, sha512Hex) +const cacheKeyHelpers = buildCryptographicMaterialsCacheKeyHelpers(fromUtf8, sha512Hex) export class NodeCachingMaterialsManager implements CachingMaterialsManager { - readonly _cache!: Cache + readonly _cache!: CryptographicMaterialsCache readonly _backingMaterialsManager!: MaterialsManager readonly _partition!: string readonly _maxBytesEncrypted!: number @@ -55,7 +55,7 @@ export class NodeCachingMaterialsManager implements CachingMaterialsManager(buildCacheKey) - decryptMaterials = decryptMaterials(buildCacheKey) + getEncryptionMaterials = getEncryptionMaterials(cacheKeyHelpers) + decryptMaterials = decryptMaterials(cacheKeyHelpers) _cacheEntryHasExceededLimits = cacheEntryHasExceededLimits() } From 81c7de05ea8ede83d0333cdb1bb15816ddbd9966 Mon Sep 17 00:00:00 2001 From: seebees Date: Tue, 16 Apr 2019 09:58:48 -0700 Subject: [PATCH 07/12] clone and zero bytes --- ...aching_cryptographic_materials_decorators.ts} | 16 ++++++++++++---- .../src/clone_cryptographic_material.ts | 6 ++---- .../get_local_cryptographic_materials_cache.ts | 4 ++-- modules/cache-material/src/index.ts | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) rename modules/cache-material/src/{caching_cryptographic_materials.ts => caching_cryptographic_materials_decorators.ts} (93%) diff --git a/modules/cache-material/src/caching_cryptographic_materials.ts b/modules/cache-material/src/caching_cryptographic_materials_decorators.ts similarity index 93% rename from modules/cache-material/src/caching_cryptographic_materials.ts rename to modules/cache-material/src/caching_cryptographic_materials_decorators.ts index b9c7f4379..1b0f167be 100644 --- a/modules/cache-material/src/caching_cryptographic_materials.ts +++ b/modules/cache-material/src/caching_cryptographic_materials_decorators.ts @@ -29,6 +29,7 @@ import { import {Maximum} from '@aws-crypto/serialize' import {CryptographicMaterialsCache, Entry} from './cryptographic_materials_cache' import {CryptographicMaterialsCacheKeyHelpersInterface} from './build_cryptographic_materials_cache_key_helpers' +import {cloneMaterial} from './clone_cryptographic_material' export function decorateProperties( obj: CachingMaterialsManager, @@ -68,7 +69,7 @@ export function getEncryptionMaterials( const entry = this._cache.getEncryptionResponse(cacheKey, plaintextLength) /* Check for early return (Postcondition): If I have a valid EncryptionResponse, return it. */ if (entry && !this._cacheEntryHasExceededLimits(entry)) { - return entry.response + return cloneResponse(entry.response) } else { this._cache.del(cacheKey) } @@ -98,7 +99,7 @@ export function getEncryptionMaterials( this._cache.putEncryptionResponse(cacheKey, response, plaintextLength, this._maxAge) } - return response + return cloneResponse(response) } } @@ -122,7 +123,7 @@ export function decryptMaterials( const entry = this._cache.getDecryptionResponse(cacheKey) /* Check for early return (Postcondition): If I have a valid DecryptionResponse, return it. */ if (entry && !this._cacheEntryHasExceededLimits(entry)) { - return entry.response + return cloneResponse(entry.response) } else { this._cache.del(cacheKey) } @@ -132,7 +133,7 @@ export function decryptMaterials( .decryptMaterials(request) this._cache.putDecryptionResponse(cacheKey, response, this._maxAge) - return response + return cloneResponse(response) } } @@ -148,6 +149,13 @@ export function cacheEntryHasExceededLimits( } } +function cloneResponse|DecryptionResponse>( + response: R +): R { + const {material} = response + return {...response, material: cloneMaterial(material)} +} + export interface CachingMaterialsManagerInput extends Readonly<{ cache: CryptographicMaterialsCache backingMaterials: MaterialsManager|Keyring diff --git a/modules/cache-material/src/clone_cryptographic_material.ts b/modules/cache-material/src/clone_cryptographic_material.ts index 9d1af2416..afd6d4540 100644 --- a/modules/cache-material/src/clone_cryptographic_material.ts +++ b/modules/cache-material/src/clone_cryptographic_material.ts @@ -26,7 +26,7 @@ import { type Material = NodeEncryptionMaterial|NodeDecryptionMaterial|WebCryptoEncryptionMaterial|WebCryptoDecryptionMaterial -export function cloneMaterial(source: Material) { +export function cloneMaterial(source: M): M { const clone = source instanceof NodeEncryptionMaterial ? new NodeEncryptionMaterial(source.suite) @@ -62,7 +62,5 @@ export function cloneMaterial(source: Material) { } } - - - return clone + return clone } \ No newline at end of file diff --git a/modules/cache-material/src/get_local_cryptographic_materials_cache.ts b/modules/cache-material/src/get_local_cryptographic_materials_cache.ts index fc419b9ac..885832eac 100644 --- a/modules/cache-material/src/get_local_cryptographic_materials_cache.ts +++ b/modules/cache-material/src/get_local_cryptographic_materials_cache.ts @@ -49,7 +49,7 @@ export function getLocalCryptographicMaterialsCache 0, '') + needs(plaintextLength >= 0, '') /* Precondition: Only cache EncryptionMaterial. */ needs(isEncryptionMaterial(response.material), '') /* Precondition: Only cache EncryptionMaterial that is cacheSafe. */ @@ -83,7 +83,7 @@ export function getLocalCryptographicMaterialsCache 0, '') + needs(plaintextLength >= 0, '') const entry = cache.get(key) /* Check for early return (Postcondition): If this key does not have an EncryptionMaterial, return false. */ if (!entry) return false diff --git a/modules/cache-material/src/index.ts b/modules/cache-material/src/index.ts index 8bec2cfba..cc048d2a9 100644 --- a/modules/cache-material/src/index.ts +++ b/modules/cache-material/src/index.ts @@ -14,6 +14,6 @@ */ export * from './cryptographic_materials_cache' -export * from './caching_cryptographic_materials' +export * from './caching_cryptographic_materials_decorators' export * from './build_cryptographic_materials_cache_key_helpers' export * from './clone_cryptographic_material' From 94f05088a2058d31f78ec700b823f3dc852640d7 Mon Sep 17 00:00:00 2001 From: seebees Date: Tue, 16 Apr 2019 18:39:36 -0700 Subject: [PATCH 08/12] udpates --- ...hing_cryptographic_materials_decorators.ts | 10 ++++- .../src/clone_cryptographic_material.ts | 2 +- ...get_local_cryptographic_materials_cache.ts | 38 ++++++++++++++++++- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/modules/cache-material/src/caching_cryptographic_materials_decorators.ts b/modules/cache-material/src/caching_cryptographic_materials_decorators.ts index 1b0f167be..99d496cac 100644 --- a/modules/cache-material/src/caching_cryptographic_materials_decorators.ts +++ b/modules/cache-material/src/caching_cryptographic_materials_decorators.ts @@ -59,7 +59,7 @@ export function getEncryptionMaterials( ): Promise> { const {suite, encryptionContext, frameLength, plaintextLength} = request /* Check for early return (Postcondition): If I can not cache the EncryptionResponse, do not even look. */ - if ((suite && !suite.cacheSafe) || !plaintextLength) { + if ((suite && !suite.cacheSafe) || typeof plaintextLength !== 'number') { return this ._backingMaterialsManager .getEncryptionMaterials(request) @@ -149,6 +149,14 @@ export function cacheEntryHasExceededLimits( } } +/** + * I need to clone the underlying material. + * Because when the SDK is done with material, it will zero it out. + * Plucking off the material and cloning just that and then returning the rest of the response + * can just be handled in one place. + * @param response EncryptionResponse|DecryptionResponse + * @return EncryptionResponse|DecryptionResponse + */ function cloneResponse|DecryptionResponse>( response: R ): R { diff --git a/modules/cache-material/src/clone_cryptographic_material.ts b/modules/cache-material/src/clone_cryptographic_material.ts index afd6d4540..6896f5b19 100644 --- a/modules/cache-material/src/clone_cryptographic_material.ts +++ b/modules/cache-material/src/clone_cryptographic_material.ts @@ -38,7 +38,7 @@ export function cloneMaterial(source: M): M { ? new WebCryptoDecryptionMaterial(source.suite) : false - if (!clone) throw new Error('') + if (!clone) throw new Error('Unsupported material type') const udk = new Uint8Array(source.getUnencryptedDataKey()) clone.setUnencryptedDataKey(udk, clone.keyringTrace[0]) diff --git a/modules/cache-material/src/get_local_cryptographic_materials_cache.ts b/modules/cache-material/src/get_local_cryptographic_materials_cache.ts index 885832eac..7b5fca47a 100644 --- a/modules/cache-material/src/get_local_cryptographic_materials_cache.ts +++ b/modules/cache-material/src/get_local_cryptographic_materials_cache.ts @@ -31,7 +31,8 @@ import { } from './cryptographic_materials_cache' export function getLocalCryptographicMaterialsCache( - maxSize: number + maxSize: number, + proactiveFrequency: number = 1000*60 ): CryptographicMaterialsCache { const cache = new LRU>({ max: maxSize, @@ -41,6 +42,28 @@ export function getLocalCryptographicMaterialsCache { + mayEvictTail() + proactivelyTryAndEvictTail() + }, proactiveFrequency) + // @ts-ignore + timeout.unref && timeout.unref() + })() + return { putEncryptionResponse( key: string, @@ -108,4 +131,17 @@ export function getLocalCryptographicMaterialsCache Date: Tue, 16 Apr 2019 18:40:57 -0700 Subject: [PATCH 09/12] done --- .../src/caching_cryptographic_materials_decorators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cache-material/src/caching_cryptographic_materials_decorators.ts b/modules/cache-material/src/caching_cryptographic_materials_decorators.ts index 99d496cac..80bbb3aea 100644 --- a/modules/cache-material/src/caching_cryptographic_materials_decorators.ts +++ b/modules/cache-material/src/caching_cryptographic_materials_decorators.ts @@ -59,7 +59,7 @@ export function getEncryptionMaterials( ): Promise> { const {suite, encryptionContext, frameLength, plaintextLength} = request /* Check for early return (Postcondition): If I can not cache the EncryptionResponse, do not even look. */ - if ((suite && !suite.cacheSafe) || typeof plaintextLength !== 'number') { + if ((suite && !suite.cacheSafe) || typeof plaintextLength !== 'number' || 0 > plaintextLength) { return this ._backingMaterialsManager .getEncryptionMaterials(request) From 771b00d4d14777b24ccb2f25f9393a154d036548 Mon Sep 17 00:00:00 2001 From: seebees Date: Tue, 16 Apr 2019 18:46:30 -0700 Subject: [PATCH 10/12] lint --- ...yptographic_materials_cache_key_helpers.ts | 42 +++--- ...hing_cryptographic_materials_decorators.ts | 72 +++++----- .../src/clone_cryptographic_material.ts | 26 ++-- ...get_local_cryptographic_materials_cache.ts | 36 ++--- .../src/caching_materials_manager_node.ts | 20 +-- .../src/raw_aes_encrypted_data_keys.ts | 125 +++++++++--------- modules/raw-keyring/src/raw_aes_material.ts | 6 +- modules/serialize/src/identifiers.ts | 26 ++-- modules/serialize/src/serialize_factory.ts | 2 +- 9 files changed, 181 insertions(+), 174 deletions(-) diff --git a/modules/cache-material/src/build_cryptographic_materials_cache_key_helpers.ts b/modules/cache-material/src/build_cryptographic_materials_cache_key_helpers.ts index df9fa8c78..be6ef25cc 100644 --- a/modules/cache-material/src/build_cryptographic_materials_cache_key_helpers.ts +++ b/modules/cache-material/src/build_cryptographic_materials_cache_key_helpers.ts @@ -13,13 +13,19 @@ * limitations under the License. */ -import {SupportedAlgorithmSuites, DecryptionRequest, EncryptionRequest, EncryptedDataKey, EncryptionContext} from '@aws-crypto/material-management' +import { + SupportedAlgorithmSuites, // eslint-disable-line no-unused-vars + DecryptionRequest, // eslint-disable-line no-unused-vars + EncryptionRequest, // eslint-disable-line no-unused-vars + EncryptedDataKey, // eslint-disable-line no-unused-vars + EncryptionContext // eslint-disable-line no-unused-vars +} from '@aws-crypto/material-management' import { serializeFactory, uInt16BE } from '@aws-crypto/serialize' // 512 bits of 0 for padding between hashes in decryption materials cache ID generation. const BIT_PAD_512 = Buffer.alloc(64) -export function buildCryptographicMaterialsCacheKeyHelpers( +export function buildCryptographicMaterialsCacheKeyHelpers ( fromUtf8: (input: string) => Uint8Array, sha512Hex: (...data: ((Uint8Array|string))[]) => Promise ): CryptographicMaterialsCacheKeyHelpersInterface { @@ -35,28 +41,28 @@ export function buildCryptographicMaterialsCacheKeyHelpers + { suite, encryptionContext }: EncryptionRequest ) { const algorithmInfo = suite ? [Buffer.from([1]), uInt16BE(suite.id)] - : [Buffer.alloc(0)] - + : [Buffer.alloc(0)] + return sha512Hex( await sha512Hex(partition), ...algorithmInfo, await encryptionContextHash(encryptionContext) ) } - - async function buildDecryptionResponseCacheKey( + + async function buildDecryptionResponseCacheKey ( partition: string, - {suite, encryptedDataKeys, encryptionContext}: DecryptionRequest + { suite, encryptedDataKeys, encryptionContext }: DecryptionRequest ) { const { id } = suite - + return sha512Hex( await sha512Hex(partition), uInt16BE(id), @@ -65,19 +71,19 @@ export function buildCryptographicMaterialsCacheKeyHelpers) { + + async function encryptedDataKeysHash (encryptedDataKeys: ReadonlyArray) { const hashes = await Promise.all( encryptedDataKeys .map(serializeEncryptedDataKey) .map(edk => sha512Hex(edk)) - ) + ) return hashes // is this sort valid? locally, it should be fine .sort((a, b) => a.localeCompare(b)) } - - function encryptionContextHash(context?: EncryptionContext) { + + function encryptionContextHash (context?: EncryptionContext) { const encodedContext = encodeEncryptionContext(context || {}) const serializedContext = serializeEncryptionContext(encodedContext) return sha512Hex(serializedContext) @@ -87,11 +93,11 @@ export function buildCryptographicMaterialsCacheKeyHelpers { buildEncryptionResponseCacheKey( partition: string, - {suite, encryptionContext}: EncryptionRequest + { suite, encryptionContext }: EncryptionRequest ): Promise buildDecryptionResponseCacheKey( partition: string, - {suite, encryptedDataKeys, encryptionContext}: DecryptionRequest + { suite, encryptedDataKeys, encryptionContext }: DecryptionRequest ): Promise encryptedDataKeysHash(encryptedDataKeys: ReadonlyArray): Promise encryptionContextHash(context?: EncryptionContext): Promise diff --git a/modules/cache-material/src/caching_cryptographic_materials_decorators.ts b/modules/cache-material/src/caching_cryptographic_materials_decorators.ts index 80bbb3aea..2ebc3f54d 100644 --- a/modules/cache-material/src/caching_cryptographic_materials_decorators.ts +++ b/modules/cache-material/src/caching_cryptographic_materials_decorators.ts @@ -14,28 +14,33 @@ */ import { - GetEncryptionMaterials, - GetDecryptMaterials, - DecryptionResponse, - SupportedAlgorithmSuites, - EncryptionRequest, - EncryptionResponse, - MaterialsManager, - DecryptionRequest, + GetEncryptionMaterials, // eslint-disable-line no-unused-vars + GetDecryptMaterials, // eslint-disable-line no-unused-vars + DecryptionResponse, // eslint-disable-line no-unused-vars + SupportedAlgorithmSuites, // eslint-disable-line no-unused-vars + EncryptionRequest, // eslint-disable-line no-unused-vars + EncryptionResponse, // eslint-disable-line no-unused-vars + MaterialsManager, // eslint-disable-line no-unused-vars + DecryptionRequest, // eslint-disable-line no-unused-vars needs, readOnlyProperty, - Keyring + Keyring // eslint-disable-line no-unused-vars } from '@aws-crypto/material-management' -import {Maximum} from '@aws-crypto/serialize' -import {CryptographicMaterialsCache, Entry} from './cryptographic_materials_cache' -import {CryptographicMaterialsCacheKeyHelpersInterface} from './build_cryptographic_materials_cache_key_helpers' -import {cloneMaterial} from './clone_cryptographic_material' +import { Maximum } from '@aws-crypto/serialize' +import { + CryptographicMaterialsCache, // eslint-disable-line no-unused-vars + Entry // eslint-disable-line no-unused-vars +} from './cryptographic_materials_cache' +import { + CryptographicMaterialsCacheKeyHelpersInterface // eslint-disable-line no-unused-vars +} from './build_cryptographic_materials_cache_key_helpers' +import { cloneMaterial } from './clone_cryptographic_material' -export function decorateProperties( +export function decorateProperties ( obj: CachingMaterialsManager, input: CachingMaterialsManagerDecorateInput ) { - const {cache, backingMaterialsManager, maxAge, maxBytesEncrypted, maxMessagesEncrypted} = input + const { cache, backingMaterialsManager, maxAge, maxBytesEncrypted, maxMessagesEncrypted } = input needs(cache, '') needs(backingMaterialsManager, '') @@ -50,22 +55,22 @@ export function decorateProperties( readOnlyProperty(obj, '_maxMessagesEncrypted', maxMessagesEncrypted || Maximum.MESSAGES_PER_KEY) } -export function getEncryptionMaterials( - {buildEncryptionResponseCacheKey}: CryptographicMaterialsCacheKeyHelpersInterface +export function getEncryptionMaterials ( + { buildEncryptionResponseCacheKey }: CryptographicMaterialsCacheKeyHelpersInterface ): GetEncryptionMaterials { - return async function getEncryptionMaterials( + return async function getEncryptionMaterials ( this: CachingMaterialsManager, request: EncryptionRequest ): Promise> { - const {suite, encryptionContext, frameLength, plaintextLength} = request + const { suite, encryptionContext, frameLength, plaintextLength } = request /* Check for early return (Postcondition): If I can not cache the EncryptionResponse, do not even look. */ - if ((suite && !suite.cacheSafe) || typeof plaintextLength !== 'number' || 0 > plaintextLength) { + if ((suite && !suite.cacheSafe) || typeof plaintextLength !== 'number' || plaintextLength < 0) { return this ._backingMaterialsManager .getEncryptionMaterials(request) } - const cacheKey = await buildEncryptionResponseCacheKey(this._partition, {suite, encryptionContext}) + const cacheKey = await buildEncryptionResponseCacheKey(this._partition, { suite, encryptionContext }) const entry = this._cache.getEncryptionResponse(cacheKey, plaintextLength) /* Check for early return (Postcondition): If I have a valid EncryptionResponse, return it. */ if (entry && !this._cacheEntryHasExceededLimits(entry)) { @@ -79,7 +84,7 @@ export function getEncryptionMaterials( /* Strip any information about the plaintext from the backing request, * because the resulting response may be used to encrypt multiple plaintexts. */ - .getEncryptionMaterials({suite, encryptionContext, frameLength}) + .getEncryptionMaterials({ suite, encryptionContext, frameLength }) /* Check for early return (Postcondition): If I can not cache the EncryptionResponse, just return it. */ if (!response.material.suite.cacheSafe) return response @@ -98,20 +103,19 @@ export function getEncryptionMaterials( if (!this._cacheEntryHasExceededLimits(testEntry)) { this._cache.putEncryptionResponse(cacheKey, response, plaintextLength, this._maxAge) } - + return cloneResponse(response) } } -export function decryptMaterials( - {buildDecryptionResponseCacheKey}: CryptographicMaterialsCacheKeyHelpersInterface +export function decryptMaterials ( + { buildDecryptionResponseCacheKey }: CryptographicMaterialsCacheKeyHelpersInterface ): GetDecryptMaterials { - return async function decryptMaterials( + return async function decryptMaterials ( this: CachingMaterialsManager, request: DecryptionRequest ): Promise> { - - const {suite} = request + const { suite } = request /* Check for early return (Postcondition): If I can not cache the DecryptionResponse, do not even look. */ if (!suite.cacheSafe) { return this @@ -137,10 +141,10 @@ export function decryptMaterials( } } -export function cacheEntryHasExceededLimits(): CacheEntryHasExceededLimits { - return function cacheEntryHasExceededLimits( +export function cacheEntryHasExceededLimits (): CacheEntryHasExceededLimits { + return function cacheEntryHasExceededLimits ( this: CachingMaterialsManager, - {now, messagesEncrypted, bytesEncrypted}: Entry + { now, messagesEncrypted, bytesEncrypted }: Entry ): boolean { const age = Date.now() - now return (!this._maxAge || age > this._maxAge) || @@ -157,11 +161,11 @@ export function cacheEntryHasExceededLimits( * @param response EncryptionResponse|DecryptionResponse * @return EncryptionResponse|DecryptionResponse */ -function cloneResponse|DecryptionResponse>( +function cloneResponse|DecryptionResponse> ( response: R ): R { - const {material} = response - return {...response, material: cloneMaterial(material)} + const { material } = response + return { ...response, material: cloneMaterial(material) } } export interface CachingMaterialsManagerInput extends Readonly<{ diff --git a/modules/cache-material/src/clone_cryptographic_material.ts b/modules/cache-material/src/clone_cryptographic_material.ts index 6896f5b19..e3dc46a9e 100644 --- a/modules/cache-material/src/clone_cryptographic_material.ts +++ b/modules/cache-material/src/clone_cryptographic_material.ts @@ -13,30 +13,28 @@ * limitations under the License. */ - import { NodeEncryptionMaterial, NodeDecryptionMaterial, WebCryptoEncryptionMaterial, WebCryptoDecryptionMaterial, isEncryptionMaterial, - isDecryptionMaterial, - + isDecryptionMaterial + } from '@aws-crypto/material-management' type Material = NodeEncryptionMaterial|NodeDecryptionMaterial|WebCryptoEncryptionMaterial|WebCryptoDecryptionMaterial -export function cloneMaterial(source: M): M { - +export function cloneMaterial (source: M): M { const clone = source instanceof NodeEncryptionMaterial ? new NodeEncryptionMaterial(source.suite) : source instanceof NodeDecryptionMaterial - ? new NodeDecryptionMaterial(source.suite) - : source instanceof WebCryptoEncryptionMaterial - ? new WebCryptoEncryptionMaterial(source.suite) - : source instanceof WebCryptoDecryptionMaterial - ? new WebCryptoDecryptionMaterial(source.suite) - : false + ? new NodeDecryptionMaterial(source.suite) + : source instanceof WebCryptoEncryptionMaterial + ? new WebCryptoEncryptionMaterial(source.suite) + : source instanceof WebCryptoDecryptionMaterial + ? new WebCryptoDecryptionMaterial(source.suite) + : false if (!clone) throw new Error('Unsupported material type') @@ -52,15 +50,15 @@ export function cloneMaterial(source: M): M { source.encryptedDataKeys.forEach((edk, i) => { clone.addEncryptedDataKey(edk, clone.keyringTrace[i].flags) }) - + if (source.suite.signatureCurve && source.signatureKey) { clone.setSignatureKey(source.signatureKey) } - } else if (isDecryptionMaterial(source) && isDecryptionMaterial(clone)) { + } else if (isDecryptionMaterial(source) && isDecryptionMaterial(clone)) { if (source.suite.signatureCurve && source.verificationKey) { clone.setVerificationKey(source.verificationKey) } } return clone -} \ No newline at end of file +} diff --git a/modules/cache-material/src/get_local_cryptographic_materials_cache.ts b/modules/cache-material/src/get_local_cryptographic_materials_cache.ts index 7b5fca47a..c0e590e70 100644 --- a/modules/cache-material/src/get_local_cryptographic_materials_cache.ts +++ b/modules/cache-material/src/get_local_cryptographic_materials_cache.ts @@ -15,9 +15,9 @@ import LRU from 'lru-cache' import { - EncryptionResponse, - DecryptionResponse, - SupportedAlgorithmSuites, + EncryptionResponse, // eslint-disable-line no-unused-vars + DecryptionResponse, // eslint-disable-line no-unused-vars + SupportedAlgorithmSuites, // eslint-disable-line no-unused-vars needs, isEncryptionMaterial, isDecryptionMaterial @@ -30,13 +30,13 @@ import { DecryptionResponseEntry // eslint-disable-line no-unused-vars } from './cryptographic_materials_cache' -export function getLocalCryptographicMaterialsCache( +export function getLocalCryptographicMaterialsCache ( maxSize: number, - proactiveFrequency: number = 1000*60 + proactiveFrequency: number = 1000 * 60 ): CryptographicMaterialsCache { const cache = new LRU>({ max: maxSize, - dispose(_key, value) { + dispose (_key, value) { /* Zero out the unencrypted dataKey, when the material is removed from the cache. */ value.response.material.zeroUnencryptedDataKey() } @@ -46,7 +46,7 @@ export function getLocalCryptographicMaterialsCache { mayEvictTail() proactivelyTryAndEvictTail() @@ -65,7 +65,7 @@ export function getLocalCryptographicMaterialsCache, plaintextLength: number, @@ -76,7 +76,7 @@ export function getLocalCryptographicMaterialsCache, maxAge?: number ) { - /* Precondition: Only cache DecryptionMaterial. */ + /* Precondition: Only cache DecryptionMaterial. */ needs(isDecryptionMaterial(response.material), '') - /* Precondition: Only cache DecryptionMaterial that is cacheSafe. */ + /* Precondition: Only cache DecryptionMaterial that is cacheSafe. */ needs(response.material.suite.cacheSafe, '') const entry = Object.seal({ response: Object.freeze(response), @@ -104,7 +104,7 @@ export function getLocalCryptographicMaterialsCache= 0, '') const entry = cache.get(key) @@ -118,7 +118,7 @@ export function getLocalCryptographicMaterialsCache>entry }, - getDecryptionResponse(key: string){ + getDecryptionResponse (key: string) { const entry = cache.get(key) /* Check for early return (Postcondition): If this key does not have a DecryptionMaterial, return false. */ if (!entry) return false @@ -127,14 +127,14 @@ export function getLocalCryptographicMaterialsCache>entry }, - del(key: string) { + del (key: string) { cache.del(key) } } - function mayEvictTail() { + function mayEvictTail () { // @ts-ignore - const {tail} = cache.dumpLru() + const { tail } = cache.dumpLru() /* Check for early return (Postcondition): If there is no tail, then the cache is empty. */ if (!tail) return /* The underlying Yallist tail Node has a `value`. diff --git a/modules/caching-materials-manager-node/src/caching_materials_manager_node.ts b/modules/caching-materials-manager-node/src/caching_materials_manager_node.ts index e6c208468..b19a33fb1 100644 --- a/modules/caching-materials-manager-node/src/caching_materials_manager_node.ts +++ b/modules/caching-materials-manager-node/src/caching_materials_manager_node.ts @@ -14,23 +14,23 @@ */ import { - CachingMaterialsManager, + CachingMaterialsManager, // eslint-disable-line no-unused-vars decorateProperties, getEncryptionMaterials, decryptMaterials, cacheEntryHasExceededLimits, buildCryptographicMaterialsCacheKeyHelpers, - CachingMaterialsManagerInput, - CryptographicMaterialsCache + CachingMaterialsManagerInput, // eslint-disable-line no-unused-vars + CryptographicMaterialsCache // eslint-disable-line no-unused-vars } from '@aws-crypto/cache-material' import { - MaterialsManager, + MaterialsManager, // eslint-disable-line no-unused-vars NodeCryptographicMaterialsManager, - NodeAlgorithmSuite, - KeyringNode, + NodeAlgorithmSuite, // eslint-disable-line no-unused-vars + KeyringNode } from '@aws-crypto/material-management-node' -import {createHash} from 'crypto' +import { createHash } from 'crypto' const fromUtf8 = (input: string) => Buffer.from(input, 'utf8') const sha512Hex = async (...data: (Uint8Array|string)[]) => data @@ -47,12 +47,12 @@ export class NodeCachingMaterialsManager implements CachingMaterialsManager) { + constructor (input: CachingMaterialsManagerInput) { const backingMaterialsManager = input.backingMaterials instanceof KeyringNode ? new NodeCryptographicMaterialsManager(input.backingMaterials) : input.backingMaterials - - decorateProperties(this, {backingMaterialsManager, ...input}) + + decorateProperties(this, { backingMaterialsManager, ...input }) } getEncryptionMaterials = getEncryptionMaterials(cacheKeyHelpers) diff --git a/modules/raw-keyring/src/raw_aes_encrypted_data_keys.ts b/modules/raw-keyring/src/raw_aes_encrypted_data_keys.ts index 22c120c1c..67ef0831f 100644 --- a/modules/raw-keyring/src/raw_aes_encrypted_data_keys.ts +++ b/modules/raw-keyring/src/raw_aes_encrypted_data_keys.ts @@ -26,73 +26,72 @@ * The AAD (encryption context) is the same as the message. */ - import { concatBuffers, uInt32BE } from '@aws-crypto/serialize' - import { - AlgorithmSuite, // eslint-disable-line no-unused-vars - EncryptedDataKey, // eslint-disable-line no-unused-vars - needs - } from '@aws-crypto/material-management' - - export function rawAesEncryptedDataKeyFactory ( - toUtf8: (input: Uint8Array) => string, - fromUtf8: (input: any) => Uint8Array - ) { - return { rawAesEncryptedDataKey } - - function rawAesEncryptedDataKey ( - keyNamespace: string, - keyName: string, - iv: Uint8Array, - ciphertext: Uint8Array, - authTag: Uint8Array - ): EncryptedDataKey { - const ivLength = iv.byteLength - const authTagBitLength = authTag.byteLength * 8 - const encryptedDataKey = concatBuffers(ciphertext, authTag) - const providerId = keyNamespace - const rawInfo = concatBuffers(fromUtf8(keyName), uInt32BE(authTagBitLength), uInt32BE(ivLength), iv) - const providerInfo = toUtf8(rawInfo) - return new EncryptedDataKey({ encryptedDataKey, providerId, providerInfo, rawInfo }) - } - } - - export function rawAesEncryptedPartsFactory (fromUtf8: (input: any) => Uint8Array) { - return { rawAesEncryptedParts } - - function rawAesEncryptedParts (suite: AlgorithmSuite, keyName: string, { encryptedDataKey, rawInfo }: EncryptedDataKey) { - if (!(rawInfo instanceof Uint8Array)) throw new Error('Malformed Encrypted Data Key.') - // see above for format, slice off the "string part" - rawInfo = rawInfo.slice(fromUtf8(keyName).byteLength) - /* Uint8Array is a view on top of the underlying ArrayBuffer. +import { concatBuffers, uInt32BE } from '@aws-crypto/serialize' +import { + AlgorithmSuite, // eslint-disable-line no-unused-vars + EncryptedDataKey, // eslint-disable-line no-unused-vars + needs +} from '@aws-crypto/material-management' + +export function rawAesEncryptedDataKeyFactory ( + toUtf8: (input: Uint8Array) => string, + fromUtf8: (input: any) => Uint8Array +) { + return { rawAesEncryptedDataKey } + + function rawAesEncryptedDataKey ( + keyNamespace: string, + keyName: string, + iv: Uint8Array, + ciphertext: Uint8Array, + authTag: Uint8Array + ): EncryptedDataKey { + const ivLength = iv.byteLength + const authTagBitLength = authTag.byteLength * 8 + const encryptedDataKey = concatBuffers(ciphertext, authTag) + const providerId = keyNamespace + const rawInfo = concatBuffers(fromUtf8(keyName), uInt32BE(authTagBitLength), uInt32BE(ivLength), iv) + const providerInfo = toUtf8(rawInfo) + return new EncryptedDataKey({ encryptedDataKey, providerId, providerInfo, rawInfo }) + } +} + +export function rawAesEncryptedPartsFactory (fromUtf8: (input: any) => Uint8Array) { + return { rawAesEncryptedParts } + + function rawAesEncryptedParts (suite: AlgorithmSuite, keyName: string, { encryptedDataKey, rawInfo }: EncryptedDataKey) { + if (!(rawInfo instanceof Uint8Array)) throw new Error('Malformed Encrypted Data Key.') + // see above for format, slice off the "string part" + rawInfo = rawInfo.slice(fromUtf8(keyName).byteLength) + /* Uint8Array is a view on top of the underlying ArrayBuffer. * This means that raw underlying memory stored in the ArrayBuffer * may be larger than the Uint8Array. This is especially true of * the Node.js Buffer object. The offset and length *must* be * passed to the DataView otherwise I will get unexpected results. */ - const dataView = new DataView( - rawInfo.buffer, - rawInfo.byteOffset, - rawInfo.byteLength - ) - /* See above: + const dataView = new DataView( + rawInfo.buffer, + rawInfo.byteOffset, + rawInfo.byteLength + ) + /* See above: * uInt32BE(authTagBitLength),uInt32BE(ivLength), iv */ - const tagLengthBits = dataView.getUint32(0, false) // big endian - const ivLength = dataView.getUint32(4, false) // big endian - /* Precondition: The ivLength must match the algorith suite specification. */ - needs(ivLength === suite.ivLength, 'Malformed providerInfo') - /* Precondition: The tagLength must match the algorith suite specification. */ - needs(tagLengthBits === suite.tagLength, 'Malformed providerInfo') - /* Precondition: The byteLength of rawInfo should match the encoded length. */ - needs(rawInfo.byteLength === 4 + 4 + ivLength, 'Malformed providerInfo') - const tagLength = tagLengthBits / 8 - /* Precondition: The encryptedDataKey byteLength must match the algorith suite specification and encoded length. */ - needs(encryptedDataKey.byteLength === tagLength + suite.keyLengthBytes, 'Malformed providerInfo') - const iv = rawInfo.slice(-ivLength) - const authTag = encryptedDataKey.slice(-tagLength) - const ciphertext = encryptedDataKey.slice(0, -tagLength) - - return { authTag, ciphertext, iv } - } - } - \ No newline at end of file + const tagLengthBits = dataView.getUint32(0, false) // big endian + const ivLength = dataView.getUint32(4, false) // big endian + /* Precondition: The ivLength must match the algorith suite specification. */ + needs(ivLength === suite.ivLength, 'Malformed providerInfo') + /* Precondition: The tagLength must match the algorith suite specification. */ + needs(tagLengthBits === suite.tagLength, 'Malformed providerInfo') + /* Precondition: The byteLength of rawInfo should match the encoded length. */ + needs(rawInfo.byteLength === 4 + 4 + ivLength, 'Malformed providerInfo') + const tagLength = tagLengthBits / 8 + /* Precondition: The encryptedDataKey byteLength must match the algorith suite specification and encoded length. */ + needs(encryptedDataKey.byteLength === tagLength + suite.keyLengthBytes, 'Malformed providerInfo') + const iv = rawInfo.slice(-ivLength) + const authTag = encryptedDataKey.slice(-tagLength) + const ciphertext = encryptedDataKey.slice(0, -tagLength) + + return { authTag, ciphertext, iv } + } +} diff --git a/modules/raw-keyring/src/raw_aes_material.ts b/modules/raw-keyring/src/raw_aes_material.ts index 985d59a74..d5b0fd91e 100644 --- a/modules/raw-keyring/src/raw_aes_material.ts +++ b/modules/raw-keyring/src/raw_aes_material.ts @@ -13,7 +13,7 @@ * limitations under the License. */ -/* Here I am reusing the Material implementation and interface from material-management. +/* Here I am reusing the Material implementation and interface from material-management. * This is because there are many security guarantees that this implementations offer * that map to the current implementation of raw AES keyrings. * The KeyringTrace is an unfortunate case because there is no mapping. @@ -55,7 +55,7 @@ export class NodeRawAesMaterial implements /* NodeRawAesMaterial need to set a flag, this is an abuse of TraceFlags * because the material is not generated. * but CryptographicMaterial force a flag to be set. - */ + */ const setFlags = KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY decorateCryptographicMaterial(this, setFlags) Object.setPrototypeOf(this, NodeRawAesMaterial.prototype) @@ -87,7 +87,7 @@ export class WebCryptoRawAesMaterial implements /* WebCryptoRawAesMaterial need to set a flag, this is an abuse of TraceFlags * because the material is not generated. * but CryptographicMaterial force a flag to be set. - */ + */ const setFlag = KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY decorateCryptographicMaterial(this, setFlag) decorateWebCryptoMaterial(this, 0) diff --git a/modules/serialize/src/identifiers.ts b/modules/serialize/src/identifiers.ts index 104f5cadf..bc17ac832 100644 --- a/modules/serialize/src/identifiers.ts +++ b/modules/serialize/src/identifiers.ts @@ -60,18 +60,18 @@ export enum SequenceIdentifier { Object.freeze(SequenceIdentifier) export enum Maximum { - //Maximum number of messages which are allowed to be encrypted under a single cached data key - MESSAGES_PER_KEY = 4294967296, // 2 ** 32 - //Maximum number of bytes which are allowed to be encrypted under a single cached data key - BYTES_PER_KEY = 9223372036854775807, // 2 ** 63 - 1 - //Maximum number of frames allowed in one message as defined in specification - FRAME_COUNT = 4294967295, // 2 ** 32 - 1 - //Maximum bytes allowed in a single frame as defined in specification - FRAME_SIZE = 4294967295, // 2 ** 32 - 1 - //Maximum bytes allowed in a non-framed message ciphertext as defined in specification - GCM_CONTENT_SIZE = 68719476704, // 2 ** 36 - 32 - NON_FRAMED_SIZE = 68719476704, // 2 ** 36 - 32 - //Maximum number of AAD bytes allowed as defined in specification - AAD_BYTE_SIZE = 65535, // 2 ** 16 - 1 + // Maximum number of messages which are allowed to be encrypted under a single cached data key + MESSAGES_PER_KEY = 2 ** 32, // eslint-disable-line no-unused-vars + // Maximum number of bytes which are allowed to be encrypted under a single cached data key + BYTES_PER_KEY = 2 ** 63 - 1, // eslint-disable-line no-unused-vars + // Maximum number of frames allowed in one message as defined in specification + FRAME_COUNT = 2 ** 32 - 1, // eslint-disable-line no-unused-vars + // Maximum bytes allowed in a single frame as defined in specification + FRAME_SIZE = 2 ** 32 - 1, // eslint-disable-line no-unused-vars + // Maximum bytes allowed in a non-framed message ciphertext as defined in specification + GCM_CONTENT_SIZE = 2 ** 32 - 1, // eslint-disable-line no-unused-vars + NON_FRAMED_SIZE = 2 ** 32 - 1, // eslint-disable-line no-unused-vars + // Maximum number of AAD bytes allowed as defined in specification + AAD_BYTE_SIZE = 2 ** 16 - 1, // eslint-disable-line no-unused-vars } Object.freeze(Maximum) diff --git a/modules/serialize/src/serialize_factory.ts b/modules/serialize/src/serialize_factory.ts index 9244dd2fe..cf4f0fa5f 100644 --- a/modules/serialize/src/serialize_factory.ts +++ b/modules/serialize/src/serialize_factory.ts @@ -103,7 +103,7 @@ export function serializeFactory (fromUtf8: (input: any) => Uint8Array) { ) } - function serializeEncryptedDataKey(edk: EncryptedDataKey) { + function serializeEncryptedDataKey (edk: EncryptedDataKey) { const { providerId, providerInfo, encryptedDataKey, rawInfo } = edk const providerIdBytes = fromUtf8(providerId) // The providerInfo is technically a binary field, so I prefer rawInfo From 71be8ebe9e417cc0df5ea2757f099b1bb48075f2 Mon Sep 17 00:00:00 2001 From: seebees Date: Wed, 17 Apr 2019 13:49:54 -0700 Subject: [PATCH 11/12] comment --- .../src/get_local_cryptographic_materials_cache.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/cache-material/src/get_local_cryptographic_materials_cache.ts b/modules/cache-material/src/get_local_cryptographic_materials_cache.ts index c0e590e70..650201e14 100644 --- a/modules/cache-material/src/get_local_cryptographic_materials_cache.ts +++ b/modules/cache-material/src/get_local_cryptographic_materials_cache.ts @@ -60,6 +60,12 @@ export function getLocalCryptographicMaterialsCache Date: Wed, 17 Apr 2019 13:55:53 -0700 Subject: [PATCH 12/12] comments and conditions --- ...hing_cryptographic_materials_decorators.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/modules/cache-material/src/caching_cryptographic_materials_decorators.ts b/modules/cache-material/src/caching_cryptographic_materials_decorators.ts index 2ebc3f54d..3a6e3f88d 100644 --- a/modules/cache-material/src/caching_cryptographic_materials_decorators.ts +++ b/modules/cache-material/src/caching_cryptographic_materials_decorators.ts @@ -42,11 +42,16 @@ export function decorateProperties ( ) { const { cache, backingMaterialsManager, maxAge, maxBytesEncrypted, maxMessagesEncrypted } = input - needs(cache, '') - needs(backingMaterialsManager, '') - needs(!maxAge || maxAge > 0, '') - needs(!maxBytesEncrypted || (maxBytesEncrypted > 0 && Maximum.BYTES_PER_KEY > maxBytesEncrypted), '') - needs(!maxMessagesEncrypted || (maxMessagesEncrypted > 0 && Maximum.MESSAGES_PER_KEY > maxMessagesEncrypted), '') + /* Precondition: A caching material manager needs a cache. */ + needs(cache, 'You must provide a cache.') + /* Precondition: A caching material manager needs a way to get material. */ + needs(backingMaterialsManager, 'You must provide a backing material source.') + /* Precondition: You *can not* cache something forever. */ + needs(maxAge > 0, 'You must configure a maxAge') + /* Precondition: maxBytesEncrypted must be inside bounds. i.e. positive and not more than the maximum. */ + needs(!maxBytesEncrypted || (maxBytesEncrypted > 0 && Maximum.BYTES_PER_KEY >= maxBytesEncrypted), 'maxBytesEncrypted is outside of bounds.') + /* Precondition: maxMessagesEncrypted must be inside bounds. i.e. positive and not more than the maximum. */ + needs(!maxMessagesEncrypted || (maxMessagesEncrypted > 0 && Maximum.MESSAGES_PER_KEY >= maxMessagesEncrypted), 'maxMessagesEncrypted is outside of bounds.') readOnlyProperty(obj, '_cache', cache) readOnlyProperty(obj, '_backingMaterialsManager', backingMaterialsManager) @@ -174,7 +179,7 @@ export interface CachingMaterialsManagerInput{} export interface CachingMaterialsManagerDecorateInput extends CachingMaterialsManagerInput { @@ -187,7 +192,7 @@ export interface CachingMaterialsManager ext readonly _backingMaterialsManager: MaterialsManager readonly _maxBytesEncrypted: number readonly _maxMessagesEncrypted: number - readonly _maxAge?: number + readonly _maxAge: number _cacheEntryHasExceededLimits: CacheEntryHasExceededLimits }