Skip to content
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

fix: Encryption Context changes #148

Merged
merged 15 commits into from
Jul 26, 2019
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -37,13 +37,13 @@ export function buildCryptographicMaterialsCacheKeyHelpers<S extends SupportedAl
} = serializeFactory(fromUtf8)

return {
buildEncryptionResponseCacheKey,
buildDecryptionResponseCacheKey,
buildEncryptionMaterialCacheKey,
buildDecryptionMaterialCacheKey,
encryptedDataKeysHash,
encryptionContextHash
}

async function buildEncryptionResponseCacheKey (
async function buildEncryptionMaterialCacheKey (
partition: string,
{ suite, encryptionContext }: EncryptionRequest<S>
) {
Expand All @@ -59,7 +59,7 @@ export function buildCryptographicMaterialsCacheKeyHelpers<S extends SupportedAl
return toUtf8(key)
}

async function buildDecryptionResponseCacheKey (
async function buildDecryptionMaterialCacheKey (
partition: string,
{ suite, encryptedDataKeys, encryptionContext }: DecryptionRequest<S>
) {
Expand All @@ -84,26 +84,26 @@ export function buildCryptographicMaterialsCacheKeyHelpers<S extends SupportedAl
return hashes.sort(compare)
}

function encryptionContextHash (context?: EncryptionContext) {
function encryptionContextHash (context: EncryptionContext) {
/* The AAD section is uInt16BE(length) + AAD
* see: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/message-format.html#header-aad
* However, the RAW Keyring wants _only_ the ADD.
* So, I just slice off the length.
*/
const serializedContext = serializeEncryptionContext(context || {}).slice(2)
const serializedContext = serializeEncryptionContext(context).slice(2)
return sha512(serializedContext)
}
}

export interface CryptographicMaterialsCacheKeyHelpersInterface<S extends SupportedAlgorithmSuites> {
buildEncryptionResponseCacheKey(
buildEncryptionMaterialCacheKey(
partition: string,
{ suite, encryptionContext }: EncryptionRequest<S>
): Promise<string>
buildDecryptionResponseCacheKey(
buildDecryptionMaterialCacheKey(
partition: string,
{ suite, encryptedDataKeys, encryptionContext }: DecryptionRequest<S>
): Promise<string>
encryptedDataKeysHash(encryptedDataKeys: ReadonlyArray<EncryptedDataKey>): Promise<Uint8Array[]>
encryptionContextHash(context?: EncryptionContext): Promise<Uint8Array>
encryptionContextHash(context: EncryptionContext): Promise<Uint8Array>
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
import {
GetEncryptionMaterials, // eslint-disable-line no-unused-vars
GetDecryptMaterials, // eslint-disable-line no-unused-vars
DecryptionResponse, // eslint-disable-line no-unused-vars
DecryptionMaterial, // 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
EncryptionMaterial, // eslint-disable-line no-unused-vars
MaterialsManager, // eslint-disable-line no-unused-vars
DecryptionRequest, // eslint-disable-line no-unused-vars
needs,
Expand Down Expand Up @@ -64,88 +64,88 @@ export function decorateProperties<S extends SupportedAlgorithmSuites> (
}

export function getEncryptionMaterials<S extends SupportedAlgorithmSuites> (
{ buildEncryptionResponseCacheKey }: CryptographicMaterialsCacheKeyHelpersInterface<S>
{ buildEncryptionMaterialCacheKey }: CryptographicMaterialsCacheKeyHelpersInterface<S>
): GetEncryptionMaterials<S> {
return async function getEncryptionMaterials (
this: CachingMaterialsManager<S>,
request: EncryptionRequest<S>
): Promise<EncryptionResponse<S>> {
): Promise<EncryptionMaterial<S>> {
const { suite, encryptionContext, frameLength, plaintextLength } = request
/* Check for early return (Postcondition): If I can not cache the EncryptionResponse, do not even look. */
/* Check for early return (Postcondition): If I can not cache the EncryptionMaterial, 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. */
const cacheKey = await buildEncryptionMaterialCacheKey(this._partition, { suite, encryptionContext })
const entry = this._cache.getEncryptionMaterial(cacheKey, plaintextLength)
/* Check for early return (Postcondition): If I have a valid EncryptionMaterial, return it. */
if (entry && !this._cacheEntryHasExceededLimits(entry)) {
return cloneResponse(entry.response)
} else {
this._cache.del(cacheKey)
}

const response = await this
const material = 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
/* Check for early return (Postcondition): If I can not cache the EncryptionMaterial, just return it. */
if (!material.suite.cacheSafe) return material

/* 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,
response: material,
now: Date.now(),
messagesEncrypted: 1,
bytesEncrypted: plaintextLength
}
if (!this._cacheEntryHasExceededLimits(testEntry)) {
this._cache.putEncryptionResponse(cacheKey, response, plaintextLength, this._maxAge)
this._cache.putEncryptionMaterial(cacheKey, material, plaintextLength, this._maxAge)
}

return cloneResponse(response)
return cloneResponse(material)
}
}

export function decryptMaterials<S extends SupportedAlgorithmSuites> (
{ buildDecryptionResponseCacheKey }: CryptographicMaterialsCacheKeyHelpersInterface<S>
{ buildDecryptionMaterialCacheKey }: CryptographicMaterialsCacheKeyHelpersInterface<S>
): GetDecryptMaterials<S> {
return async function decryptMaterials (
this: CachingMaterialsManager<S>,
request: DecryptionRequest<S>
): Promise<DecryptionResponse<S>> {
): Promise<DecryptionMaterial<S>> {
const { suite } = request
/* Check for early return (Postcondition): If I can not cache the DecryptionResponse, do not even look. */
/* Check for early return (Postcondition): If I can not cache the DecryptionMaterial, 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. */
const cacheKey = await buildDecryptionMaterialCacheKey(this._partition, request)
const entry = this._cache.getDecryptionMaterial(cacheKey)
/* Check for early return (Postcondition): If I have a valid DecryptionMaterial, return it. */
if (entry && !this._cacheEntryHasExceededLimits(entry)) {
return cloneResponse(entry.response)
} else {
this._cache.del(cacheKey)
}

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

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

Expand All @@ -166,14 +166,13 @@ export function cacheEntryHasExceededLimits<S extends SupportedAlgorithmSuites>
* Because when the Encryption 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
* @param material EncryptionMaterial|DecryptionMaterial
* @return EncryptionMaterial|DecryptionMaterial
*/
function cloneResponse<S extends SupportedAlgorithmSuites, R extends EncryptionResponse<S>|DecryptionResponse<S>> (
response: R
): R {
const { material } = response
return { ...response, material: cloneMaterial(material) }
function cloneResponse<S extends SupportedAlgorithmSuites, M extends EncryptionMaterial<S>|DecryptionMaterial<S>> (
material: M
): M {
return cloneMaterial(material)
}

export interface CachingMaterialsManagerInput<S extends SupportedAlgorithmSuites> extends Readonly<{
Expand Down
23 changes: 12 additions & 11 deletions modules/cache-material/src/clone_cryptographic_material.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,23 @@ import {
WebCryptoEncryptionMaterial,
WebCryptoDecryptionMaterial,
isEncryptionMaterial,
isDecryptionMaterial
isDecryptionMaterial,
NodeAlgorithmSuite

} 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
const { suite, encryptionContext } = source

if (!clone) throw new Error('Unsupported material type')
const clone = suite instanceof NodeAlgorithmSuite
? source instanceof NodeEncryptionMaterial
? new NodeEncryptionMaterial(suite, encryptionContext)
: new NodeDecryptionMaterial(suite, encryptionContext)
: source instanceof WebCryptoEncryptionMaterial
? new WebCryptoEncryptionMaterial(suite, encryptionContext)
: new WebCryptoDecryptionMaterial(suite, encryptionContext)

const udk = new Uint8Array(source.getUnencryptedDataKey())
clone.setUnencryptedDataKey(udk, source.keyringTrace[0])
Expand All @@ -61,6 +60,8 @@ export function cloneMaterial<M extends Material> (source: M): M {
if (source.suite.signatureCurve && source.verificationKey) {
clone.setVerificationKey(source.verificationKey)
}
} else {
throw new Error('Material mismatch')
}

return <M>clone
Expand Down
26 changes: 13 additions & 13 deletions modules/cache-material/src/cryptographic_materials_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,39 @@
*/

import {
EncryptionResponse, // eslint-disable-line no-unused-vars
DecryptionResponse, // eslint-disable-line no-unused-vars
EncryptionMaterial, // eslint-disable-line no-unused-vars
DecryptionMaterial, // 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(
putEncryptionMaterial(
key: string,
response: EncryptionResponse<S>,
response: EncryptionMaterial<S>,
plaintextLength: number,
maxAge?: number
): void
putDecryptionResponse(
putDecryptionMaterial(
key: string,
response: DecryptionResponse<S>,
response: DecryptionMaterial<S>,
maxAge?: number
): void
getEncryptionResponse(key: string, plaintextLength: number): EncryptionResponseEntry<S>|false
getDecryptionResponse(key: string): DecryptionResponseEntry<S>|false
getEncryptionMaterial(key: string, plaintextLength: number): EncryptionMaterialEntry<S>|false
getDecryptionMaterial(key: string): DecryptionMaterialEntry<S>|false
del(key: string): void
}

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

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

export interface DecryptionResponseEntry<S extends SupportedAlgorithmSuites> extends Entry<S> {
readonly response: DecryptionResponse<S>
export interface DecryptionMaterialEntry<S extends SupportedAlgorithmSuites> extends Entry<S> {
readonly response: DecryptionMaterial<S>
}
Loading