Skip to content

feat: cacheing material management #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Apr 18, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<S extends SupportedAlgorithmSuites>(
export function buildCryptographicMaterialsCacheKeyHelpers<S extends SupportedAlgorithmSuites>(
fromUtf8: (input: string) => Uint8Array,
sha512Hex: (...data: ((Uint8Array|string))[]) => Promise<string>
): Build<S> {
): CryptographicMaterialsCacheKeyHelpersInterface<S> {
const {
serializeEncryptionContext,
encodeEncryptionContext,
Expand Down Expand Up @@ -84,7 +84,7 @@ export function build<S extends SupportedAlgorithmSuites>(
}
}

export interface Build<S extends SupportedAlgorithmSuites> {
export interface CryptographicMaterialsCacheKeyHelpersInterface<S extends SupportedAlgorithmSuites> {
buildEncryptionResponseCacheKey(
partition: string,
{suite, encryptionContext}: EncryptionRequest<S>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ 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'
import {cloneMaterial} from './clone_cryptographic_material'

export function decorateProperties<S extends SupportedAlgorithmSuites>(
obj: CachingMaterialsManager<S>,
Expand All @@ -50,7 +51,7 @@ export function decorateProperties<S extends SupportedAlgorithmSuites>(
}

export function getEncryptionMaterials<S extends SupportedAlgorithmSuites>(
{buildEncryptionResponseCacheKey}: Build<S>
{buildEncryptionResponseCacheKey}: CryptographicMaterialsCacheKeyHelpersInterface<S>
): GetEncryptionMaterials<S> {
return async function getEncryptionMaterials(
this: CachingMaterialsManager<S>,
Expand All @@ -68,7 +69,9 @@ export function getEncryptionMaterials<S extends SupportedAlgorithmSuites>(
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)
}

const response = await this
Expand All @@ -81,13 +84,27 @@ export function getEncryptionMaterials<S extends SupportedAlgorithmSuites>(
/* 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
/* 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<S extends SupportedAlgorithmSuites>(
{buildDecryptionResponseCacheKey}: Build<S>
{buildDecryptionResponseCacheKey}: CryptographicMaterialsCacheKeyHelpersInterface<S>
): GetDecryptMaterials<S> {
return async function decryptMaterials(
this: CachingMaterialsManager<S>,
Expand All @@ -104,17 +121,19 @@ export function decryptMaterials<S extends SupportedAlgorithmSuites>(

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
return cloneResponse(entry.response)
} else {
this._cache.del(cacheKey)
}

const response = await this
._backingMaterialsManager
.decryptMaterials(request)

this._cache.putDecryptionResponse(cacheKey, response, this._maxAge)
return response
return cloneResponse(response)
}
}

Expand All @@ -130,8 +149,15 @@ export function cacheEntryHasExceededLimits<S extends SupportedAlgorithmSuites>(
}
}

function cloneResponse<S extends SupportedAlgorithmSuites, R extends EncryptionResponse<S>|DecryptionResponse<S>>(
response: R
): R {
const {material} = response
return {...response, material: cloneMaterial(material)}
}

export interface CachingMaterialsManagerInput<S extends SupportedAlgorithmSuites> extends Readonly<{
cache: Cache<S>
cache: CryptographicMaterialsCache<S>
backingMaterials: MaterialsManager<S>|Keyring<S>
partition?: string
maxBytesEncrypted?: number
Expand All @@ -145,7 +171,7 @@ export interface CachingMaterialsManagerDecorateInput<S extends SupportedAlgorit

export interface CachingMaterialsManager<S extends SupportedAlgorithmSuites> extends MaterialsManager<S> {
readonly _partition: string
readonly _cache: Cache<S>
readonly _cache: CryptographicMaterialsCache<S>
readonly _backingMaterialsManager: MaterialsManager<S>
readonly _maxBytesEncrypted: number
readonly _maxMessagesEncrypted: number
Expand Down
66 changes: 66 additions & 0 deletions modules/cache-material/src/clone_cryptographic_material.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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<M extends Material>(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('')

const udk = new Uint8Array(source.getUnencryptedDataKey())
clone.setUnencryptedDataKey(udk, clone.keyringTrace[0])
if ((<WebCryptoDecryptionMaterial>source).hasCryptoKey) {
const cryptoKey = (<WebCryptoDecryptionMaterial>source).getCryptoKey()
;(<WebCryptoDecryptionMaterial>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 <M>clone
}
52 changes: 52 additions & 0 deletions modules/cache-material/src/cryptographic_materials_cache.ts
Original file line number Diff line number Diff line change
@@ -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<S extends SupportedAlgorithmSuites> {
putEncryptionResponse(
key: string,
response: EncryptionResponse<S>,
plaintextLength: number,
maxAge?: number
): void
putDecryptionResponse(
key: string,
response: DecryptionResponse<S>,
maxAge?: number
): void
getEncryptionResponse(key: string, plaintextLength: number): EncryptionResponseEntry<S>|false
getDecryptionResponse(key: string): DecryptionResponseEntry<S>|false
del(key: string): void
}

export interface Entry<S extends SupportedAlgorithmSuites> {
readonly response: EncryptionResponse<S>|DecryptionResponse<S>
bytesEncrypted: number
messagesEncrypted: number
readonly now: number
}

export interface EncryptionResponseEntry<S extends SupportedAlgorithmSuites> extends Entry<S> {
readonly response: EncryptionResponse<S>
}

export interface DecryptionResponseEntry<S extends SupportedAlgorithmSuites> extends Entry<S> {
readonly response: DecryptionResponse<S>
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,16 @@ import {
isDecryptionMaterial
} from '@aws-crypto/material-management'

export function getCache<S extends SupportedAlgorithmSuites>(
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<S extends SupportedAlgorithmSuites>(
maxSize: number
): Cache<S> {
): CryptographicMaterialsCache<S> {
const cache = new LRU<string, Entry<S>>({
max: maxSize,
dispose(_key, value) {
Expand All @@ -42,7 +49,7 @@ export function getCache<S extends SupportedAlgorithmSuites>(
maxAge?: number
) {
/* Precondition: plaintextLength can not be negative */
needs(plaintextLength > 0, '')
needs(plaintextLength >= 0, '')
/* Precondition: Only cache EncryptionMaterial. */
needs(isEncryptionMaterial(response.material), '')
/* Precondition: Only cache EncryptionMaterial that is cacheSafe. */
Expand Down Expand Up @@ -76,9 +83,9 @@ export function getCache<S extends SupportedAlgorithmSuites>(
},
getEncryptionResponse(key: string, plaintextLength: number) {
/* Precondition: plaintextLength can not be negative */
needs(plaintextLength > 0, '')
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), '')
Expand All @@ -90,7 +97,7 @@ export function getCache<S extends SupportedAlgorithmSuites>(
},
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), '')
Expand All @@ -102,35 +109,3 @@ export function getCache<S extends SupportedAlgorithmSuites>(
}
}
}

export interface Cache<S extends SupportedAlgorithmSuites> {
putEncryptionResponse(
key: string,
response: EncryptionResponse<S>,
plaintextLength: number,
maxAge?: number
): void
putDecryptionResponse(
key: string,
response: DecryptionResponse<S>,
maxAge?: number
): void
getEncryptionResponse(key: string, plaintextLength: number): EncryptionResponseEntry<S>|false
getDecryptionResponse(key: string): DecryptionResponseEntry<S>|false
del(key: string): void
}

export interface Entry<S extends SupportedAlgorithmSuites> {
readonly response: EncryptionResponse<S>|DecryptionResponse<S>
bytesEncrypted: number
messagesEncrypted: number
readonly now: number
}

export interface EncryptionResponseEntry<S extends SupportedAlgorithmSuites> extends Entry<S> {
readonly response: EncryptionResponse<S>
}

export interface DecryptionResponseEntry<S extends SupportedAlgorithmSuites> extends Entry<S> {
readonly response: DecryptionResponse<S>
}
7 changes: 4 additions & 3 deletions modules/cache-material/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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_decorators'
export * from './build_cryptographic_materials_cache_key_helpers'
export * from './clone_cryptographic_material'
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import {
getEncryptionMaterials,
decryptMaterials,
cacheEntryHasExceededLimits,
build,
buildCryptographicMaterialsCacheKeyHelpers,
CachingMaterialsManagerInput,
Cache
CryptographicMaterialsCache
} from '@aws-crypto/cache-material'
import {
MaterialsManager,
Expand All @@ -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<NodeAlgorithmSuite> {
readonly _cache!: Cache<NodeAlgorithmSuite>
readonly _cache!: CryptographicMaterialsCache<NodeAlgorithmSuite>
readonly _backingMaterialsManager!: MaterialsManager<NodeAlgorithmSuite>
readonly _partition!: string
readonly _maxBytesEncrypted!: number
Expand All @@ -55,7 +55,7 @@ export class NodeCachingMaterialsManager implements CachingMaterialsManager<Node
decorateProperties(this, {backingMaterialsManager, ...input})
}

getEncryptionMaterials = getEncryptionMaterials<NodeAlgorithmSuite>(buildCacheKey)
decryptMaterials = decryptMaterials<NodeAlgorithmSuite>(buildCacheKey)
getEncryptionMaterials = getEncryptionMaterials<NodeAlgorithmSuite>(cacheKeyHelpers)
decryptMaterials = decryptMaterials<NodeAlgorithmSuite>(cacheKeyHelpers)
_cacheEntryHasExceededLimits = cacheEntryHasExceededLimits<NodeAlgorithmSuite>()
}
2 changes: 1 addition & 1 deletion modules/serialize/src/identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down