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/build_cryptographic_materials_cache_key_helpers.ts b/modules/cache-material/src/build_cryptographic_materials_cache_key_helpers.ts new file mode 100644 index 000000000..be6ef25cc --- /dev/null +++ b/modules/cache-material/src/build_cryptographic_materials_cache_key_helpers.ts @@ -0,0 +1,104 @@ +/* + * 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, // 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 ( + fromUtf8: (input: string) => Uint8Array, + sha512Hex: (...data: ((Uint8Array|string))[]) => Promise +): CryptographicMaterialsCacheKeyHelpersInterface { + 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 CryptographicMaterialsCacheKeyHelpersInterface { + 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/caching_cryptographic_materials_decorators.ts b/modules/cache-material/src/caching_cryptographic_materials_decorators.ts new file mode 100644 index 000000000..3a6e3f88d --- /dev/null +++ b/modules/cache-material/src/caching_cryptographic_materials_decorators.ts @@ -0,0 +1,202 @@ +/* + * 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, // 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 // eslint-disable-line no-unused-vars +} from '@aws-crypto/material-management' +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 ( + obj: CachingMaterialsManager, + input: CachingMaterialsManagerDecorateInput +) { + const { cache, backingMaterialsManager, maxAge, maxBytesEncrypted, maxMessagesEncrypted } = input + + /* 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) + readOnlyProperty(obj, '_maxAge', maxAge) + readOnlyProperty(obj, '_maxBytesEncrypted', maxBytesEncrypted || Maximum.BYTES_PER_KEY) + readOnlyProperty(obj, '_maxMessagesEncrypted', maxMessagesEncrypted || Maximum.MESSAGES_PER_KEY) +} + +export function getEncryptionMaterials ( + { buildEncryptionResponseCacheKey }: CryptographicMaterialsCacheKeyHelpersInterface +): 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) || typeof plaintextLength !== 'number' || plaintextLength < 0) { + 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 cloneResponse(entry.response) + } else { + this._cache.del(cacheKey) + } + + 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 + + /* 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 cloneResponse(response) + } +} + +export function decryptMaterials ( + { buildDecryptionResponseCacheKey }: CryptographicMaterialsCacheKeyHelpersInterface +): 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 DecryptionResponse, return it. */ + if (entry && !this._cacheEntryHasExceededLimits(entry)) { + return cloneResponse(entry.response) + } else { + this._cache.del(cacheKey) + } + + const response = await this + ._backingMaterialsManager + .decryptMaterials(request) + + this._cache.putDecryptionResponse(cacheKey, response, this._maxAge) + return cloneResponse(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 + } +} + +/** + * 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 { + const { material } = response + return { ...response, material: cloneMaterial(material) } +} + +export interface CachingMaterialsManagerInput extends Readonly<{ + cache: CryptographicMaterialsCache + 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: CryptographicMaterialsCache + 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/clone_cryptographic_material.ts b/modules/cache-material/src/clone_cryptographic_material.ts new file mode 100644 index 000000000..e3dc46a9e --- /dev/null +++ b/modules/cache-material/src/clone_cryptographic_material.ts @@ -0,0 +1,64 @@ +/* + * 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: 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 + + if (!clone) throw new Error('Unsupported material type') + + 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 +} diff --git a/modules/cache-material/src/cryptographic_materials_cache.ts b/modules/cache-material/src/cryptographic_materials_cache.ts new file mode 100644 index 000000000..37d8b66d0 --- /dev/null +++ b/modules/cache-material/src/cryptographic_materials_cache.ts @@ -0,0 +1,52 @@ +/* + * 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 { + 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 interface CryptographicMaterialsCache { + 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/get_local_cryptographic_materials_cache.ts b/modules/cache-material/src/get_local_cryptographic_materials_cache.ts new file mode 100644 index 000000000..650201e14 --- /dev/null +++ b/modules/cache-material/src/get_local_cryptographic_materials_cache.ts @@ -0,0 +1,153 @@ +/* + * 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, // eslint-disable-line no-unused-vars + DecryptionResponse, // eslint-disable-line no-unused-vars + SupportedAlgorithmSuites, // eslint-disable-line no-unused-vars + needs, + isEncryptionMaterial, + isDecryptionMaterial +} from '@aws-crypto/material-management' + +import { + 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 './cryptographic_materials_cache' + +export function getLocalCryptographicMaterialsCache ( + maxSize: number, + proactiveFrequency: number = 1000 * 60 +): CryptographicMaterialsCache { + 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() + } + }) + + /* It is not a guarantee that the last item in the LRU will be the Oldest Item. + * But such degenerative cases are not my concern. + * The LRU will not return things that are too old, + * so all this is just to try and proactively dispose material. + * + * To be clear, as an example say I add 9 items at T=0. + * If the MaxAge is 60 minutes, and at T=59 I add a 10th item. + * Then get each of the other 9 items. + * Now, at T=60, `mayEvictTail` will check the age of the tail + * and not evict it because the item has not aged out. + * If there is no get activity, + * it will take until T=120 before I again begin evicting items. + */ + ;(function proactivelyTryAndEvictTail () { + const timeout = setTimeout(() => { + mayEvictTail() + proactivelyTryAndEvictTail() + }, proactiveFrequency) + /* In Node.js the event loop will _only_ exit if there are no outstanding events. + * This means that if I did nothing the event loop would *always* be blocked. + * This is unfortunate and very bad for things like Lambda. + * So, I tell Node.js to not wait for this timer. + * See: https://nodejs.org/api/timers.html#timers_timeout_unref + */ + // @ts-ignore + timeout.unref && timeout.unref() + })() + + 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 not have an 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 not have a DecryptionMaterial, return false. */ + if (!entry) return false + /* Postcondition: Only return DecryptionMaterial. */ + needs(isDecryptionMaterial(entry.response.material), '') + + return >entry + }, + del (key: string) { + cache.del(key) + } + } + + function mayEvictTail () { + // @ts-ignore + 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`. + * This value is a lru-cache Entry and has a `key`. + */ + const { key } = tail.value + // Peek will evict, but not update the "recently used"-ness of the key. + cache.peek(key) + } +} diff --git a/modules/cache-material/src/index.ts b/modules/cache-material/src/index.ts new file mode 100644 index 000000000..cc048d2a9 --- /dev/null +++ b/modules/cache-material/src/index.ts @@ -0,0 +1,19 @@ +/* + * 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 './cryptographic_materials_cache' +export * from './caching_cryptographic_materials_decorators' +export * from './build_cryptographic_materials_cache_key_helpers' +export * from './clone_cryptographic_material' 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..b19a33fb1 --- /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, // eslint-disable-line no-unused-vars + decorateProperties, + getEncryptionMaterials, + decryptMaterials, + cacheEntryHasExceededLimits, + buildCryptographicMaterialsCacheKeyHelpers, + CachingMaterialsManagerInput, // eslint-disable-line no-unused-vars + CryptographicMaterialsCache // eslint-disable-line no-unused-vars +} from '@aws-crypto/cache-material' +import { + MaterialsManager, // eslint-disable-line no-unused-vars + NodeCryptographicMaterialsManager, + NodeAlgorithmSuite, // eslint-disable-line no-unused-vars + 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 cacheKeyHelpers = buildCryptographicMaterialsCacheKeyHelpers(fromUtf8, sha512Hex) + +export class NodeCachingMaterialsManager implements CachingMaterialsManager { + readonly _cache!: CryptographicMaterialsCache + 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(cacheKeyHelpers) + decryptMaterials = decryptMaterials(cacheKeyHelpers) + _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/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 11ebe25b3..bc17ac832 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 = 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 6c77ff1e4..cf4f0fa5f 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),