Skip to content

feat: Suport Node.js crypto KeyObjects #200

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 5 commits into from
Aug 31, 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 @@ -24,7 +24,8 @@ import {
DecryptionRequest, // eslint-disable-line no-unused-vars
needs,
readOnlyProperty,
Keyring // eslint-disable-line no-unused-vars
Keyring, // eslint-disable-line no-unused-vars
cloneMaterial
} from '@aws-crypto/material-management'
import { Maximum } from '@aws-crypto/serialize'
import {
Expand All @@ -34,7 +35,6 @@ import {
import {
CryptographicMaterialsCacheKeyHelpersInterface // eslint-disable-line no-unused-vars
} from './build_cryptographic_materials_cache_key_helpers'
import { cloneMaterial } from './clone_cryptographic_material'

export function decorateProperties<S extends SupportedAlgorithmSuites> (
obj: CachingMaterialsManager<S>,
Expand Down
1 change: 0 additions & 1 deletion modules/cache-material/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,4 @@
export * from './cryptographic_materials_cache'
export * from './caching_cryptographic_materials_decorators'
export * from './build_cryptographic_materials_cache_key_helpers'
export * from './clone_cryptographic_material'
export * from './get_local_cryptographic_materials_cache'
7 changes: 4 additions & 3 deletions modules/kms-keyring-node/test/kms_keyring_node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import {
NodeAlgorithmSuite,
AlgorithmSuiteIdentifier,
EncryptedDataKey, // eslint-disable-line no-unused-vars
NodeDecryptionMaterial
NodeDecryptionMaterial,
unwrapDataKey
} from '@aws-crypto/material-management-node'

describe('KmsKeyringNode::constructor', () => {
Expand Down Expand Up @@ -61,7 +62,7 @@ describe('RawAesKeyringWebCrypto encrypt/decrypt', () => {
const material = new NodeEncryptionMaterial(suite, {})
const test = await keyring.onEncrypt(material)
expect(test.hasValidKey()).to.equal(true)
udk = test.getUnencryptedDataKey()
udk = unwrapDataKey(test.getUnencryptedDataKey())
expect(udk).to.have.lengthOf(suite.keyLengthBytes)
expect(test.encryptedDataKeys).to.have.lengthOf(2)
const [edk] = test.encryptedDataKeys
Expand All @@ -73,6 +74,6 @@ describe('RawAesKeyringWebCrypto encrypt/decrypt', () => {
const material = new NodeDecryptionMaterial(suite, {})
const test = await keyring.onDecrypt(material, [encryptedDataKey])
expect(test.hasValidKey()).to.equal(true)
expect(test.getUnencryptedDataKey()).to.deep.equal(udk)
expect(unwrapDataKey(test.getUnencryptedDataKey())).to.deep.equal(udk)
})
})
5 changes: 3 additions & 2 deletions modules/kms-keyring/src/kms_keyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
KeyringTraceFlag,
EncryptedDataKey, // eslint-disable-line no-unused-vars
immutableClass,
readOnlyProperty
readOnlyProperty,
unwrapDataKey
} from '@aws-crypto/material-management'
import { KMS_PROVIDER_ID, generateDataKey, encrypt, decrypt, kmsResponseToEncryptedDataKey } from './helpers'
import { regionFromKmsKeyArn } from './region_from_kms_key_arn'
Expand Down Expand Up @@ -131,7 +132,7 @@ export function KmsKeyringClass<S extends SupportedAlgorithmSuites, Client exten
* Furthermore *only* CMK's explicitly designated as generators can generate data keys.
* See cryptographic_materials as getUnencryptedDataKey will throw in this case.
*/
const unencryptedDataKey = material.getUnencryptedDataKey()
const unencryptedDataKey = unwrapDataKey(material.getUnencryptedDataKey())

const flags = KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY | KeyringTraceFlag.WRAPPING_KEY_SIGNED_ENC_CTX
for (const kmsKey of keyIds) {
Expand Down
2 changes: 1 addition & 1 deletion modules/material-management-browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ export {
AlgorithmSuiteIdentifier, EncryptionContext, EncryptedDataKey, KeyringWebCrypto,
KeyringTrace, KeyringTraceFlag, needs, MixedBackendCryptoKey, MultiKeyringWebCrypto,
immutableBaseClass, immutableClass, frozenClass, readOnlyProperty, keyUsageForMaterial,
isValidCryptoKey, isCryptoKey, WebCryptoMaterialsManager
isValidCryptoKey, isCryptoKey, WebCryptoMaterialsManager, unwrapDataKey
} from '@aws-crypto/material-management'
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
isValidCryptoKey,
keyUsageForMaterial,
subtleFunctionForMaterial,
unwrapDataKey,
WebCryptoMaterial // eslint-disable-line no-unused-vars
} from '@aws-crypto/material-management'

Expand Down Expand Up @@ -246,7 +247,7 @@ export async function _importCryptoKey<T extends WebCryptoMaterial<T>> (
) {
const { suite } = material
const extractable = false
const udk = material.getUnencryptedDataKey()
const udk = unwrapDataKey(material.getUnencryptedDataKey())

if (suite.kdf) {
/* For several browsers, import for a key to derive with HKDF
Expand Down
2 changes: 1 addition & 1 deletion modules/material-management-node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ export {
AlgorithmSuiteIdentifier, EncryptionContext, EncryptedDataKey,
KeyringTrace, KeyringTraceFlag, needs, KeyringNode, MultiKeyringNode,
immutableBaseClass, immutableClass, frozenClass, readOnlyProperty,
NodeMaterialsManager
NodeMaterialsManager, unwrapDataKey, AwsEsdkKeyObject
} from '@aws-crypto/material-management'
19 changes: 15 additions & 4 deletions modules/material-management-node/src/material_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

import {
needs, NodeEncryptionMaterial, NodeDecryptionMaterial,
unwrapDataKey,
wrapWithKeyObjectIfSupported,
AwsEsdkKeyObject, // eslint-disable-line no-unused-vars
NodeHash // eslint-disable-line no-unused-vars
} from '@aws-crypto/material-management'
import {
Expand Down Expand Up @@ -191,12 +194,13 @@ export function getCryptoStream (material: NodeEncryptionMaterial|NodeDecryption
*/
needs(material.hasUnencryptedDataKey, 'Unencrypted data key has been zeroed.')

return createCryptoStream(cipherName, derivedKey, iv)
// createDecipheriv is incorrectly typed in @types/node. It should take key: CipherKey, not key: BinaryLike
return createCryptoStream(cipherName, derivedKey as any, iv)
}
}
}

export function nodeKdf (material: NodeEncryptionMaterial|NodeDecryptionMaterial, info?: Uint8Array): Uint8Array {
export function nodeKdf (material: NodeEncryptionMaterial|NodeDecryptionMaterial, info?: Uint8Array): Uint8Array|AwsEsdkKeyObject {
const dataKey = material.getUnencryptedDataKey()

const { kdf, kdfHash, keyLengthBytes } = material.suite
Expand All @@ -212,10 +216,17 @@ export function nodeKdf (material: NodeEncryptionMaterial|NodeDecryptionMaterial
info instanceof Uint8Array,
'Invalid HKDF values.'
)
/* The unwrap is done once we *know* that a KDF is required.
* If we unwrapped before everything will work,
* but we may be creating new copies of the unencrypted data key (export).
*/
const { buffer: dkBuffer, byteOffset: dkByteOffset, byteLength: dkByteLength } = unwrapDataKey(dataKey)
// info and kdfHash are now defined
const toExtract = Buffer.from(dataKey.buffer, dataKey.byteOffset, dataKey.byteLength)
const toExtract = Buffer.from(dkBuffer, dkByteOffset, dkByteLength)
const { buffer, byteOffset, byteLength } = <Uint8Array> info
const infoBuff = Buffer.from(buffer, byteOffset, byteLength)

return kdfIndex[<NodeHash>kdfHash](toExtract)(keyLengthBytes, infoBuff)
const derivedBytes = kdfIndex[<NodeHash>kdfHash](toExtract)(keyLengthBytes, infoBuff)

return wrapWithKeyObjectIfSupported(derivedBytes)
}
32 changes: 16 additions & 16 deletions modules/material-management-node/test/material_helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import { expect } from 'chai'
import 'mocha'
import { NodeDecryptionMaterial, NodeEncryptionMaterial, NodeAlgorithmSuite, AlgorithmSuiteIdentifier, KeyringTraceFlag, SignatureKey, VerificationKey } from '@aws-crypto/material-management'
import { NodeDecryptionMaterial, NodeEncryptionMaterial, NodeAlgorithmSuite, AlgorithmSuiteIdentifier, KeyringTraceFlag, SignatureKey, VerificationKey, unwrapDataKey } from '@aws-crypto/material-management'
import { nodeKdf, getCryptoStream, getEncryptHelper, getDecryptionHelper } from '../src/material_helpers'
// @ts-ignore
import { Decipheriv, Cipheriv, createECDH } from 'crypto'
Expand All @@ -28,21 +28,21 @@ describe('nodeKdf', () => {
const material = new NodeEncryptionMaterial(suite, {})
const dataKey = new Uint8Array(suite.keyLengthBytes).fill(1)
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY }
material.setUnencryptedDataKey(dataKey, trace)
material.setUnencryptedDataKey(new Uint8Array(dataKey), trace)

const test = nodeKdf(material, new Uint8Array(5))
expect(test === dataKey).to.equal(true)
const test = unwrapDataKey(nodeKdf(material, new Uint8Array(5)))
expect(test).to.deep.equal(dataKey)
})

it('HKDF SHA256', () => {
const suite = new NodeAlgorithmSuite(AlgorithmSuiteIdentifier.ALG_AES128_GCM_IV12_TAG16_HKDF_SHA256)
const material = new NodeEncryptionMaterial(suite, {})
const dataKey = new Uint8Array(suite.keyLengthBytes).fill(1)
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY }
material.setUnencryptedDataKey(dataKey, trace)
material.setUnencryptedDataKey(new Uint8Array(dataKey), trace)

const test = nodeKdf(material, new Uint8Array(5))
expect(test === dataKey).to.equal(false)
const test = unwrapDataKey(nodeKdf(material, new Uint8Array(5)))
expect(test).to.not.deep.equal(dataKey)
expect(test.byteLength).to.equal(suite.keyLengthBytes)
})

Expand All @@ -51,10 +51,10 @@ describe('nodeKdf', () => {
const material = new NodeEncryptionMaterial(suite, {})
const dataKey = new Uint8Array(suite.keyLengthBytes).fill(1)
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY }
material.setUnencryptedDataKey(dataKey, trace)
material.setUnencryptedDataKey(new Uint8Array(dataKey), trace)

const test = nodeKdf(material, new Uint8Array(5))
expect(test === dataKey).to.equal(false)
const test = unwrapDataKey(nodeKdf(material, new Uint8Array(5)))
expect(test).to.not.deep.equal(dataKey)
expect(test.byteLength).to.equal(suite.keyLengthBytes)
})

Expand Down Expand Up @@ -91,7 +91,7 @@ describe('getCryptoStream', () => {
const material = new NodeEncryptionMaterial(suite, {})
const dataKey = new Uint8Array(suite.keyLengthBytes).fill(1)
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY }
material.setUnencryptedDataKey(dataKey, trace)
material.setUnencryptedDataKey(new Uint8Array(dataKey), trace)

const test = getCryptoStream(material)()
const iv = new Uint8Array(12)
Expand All @@ -104,7 +104,7 @@ describe('getCryptoStream', () => {
const material = new NodeDecryptionMaterial(suite, {})
const dataKey = new Uint8Array(suite.keyLengthBytes).fill(1)
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY }
material.setUnencryptedDataKey(dataKey, trace)
material.setUnencryptedDataKey(new Uint8Array(dataKey), trace)

const test = getCryptoStream(material)()
const iv = new Uint8Array(12)
Expand All @@ -122,7 +122,7 @@ describe('getCryptoStream', () => {
const material = new NodeEncryptionMaterial(suite, {})
const dataKey = new Uint8Array(suite.keyLengthBytes).fill(1)
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY }
material.setUnencryptedDataKey(dataKey, trace)
material.setUnencryptedDataKey(new Uint8Array(dataKey), trace)

const test = getCryptoStream(material)()
const iv = new Uint8Array(1)
Expand All @@ -134,7 +134,7 @@ describe('getCryptoStream', () => {
const material = new NodeEncryptionMaterial(suite, {})
const dataKey = new Uint8Array(suite.keyLengthBytes).fill(1)
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY }
material.setUnencryptedDataKey(dataKey, trace)
material.setUnencryptedDataKey(new Uint8Array(dataKey), trace)

const test = getCryptoStream(material)()
material.zeroUnencryptedDataKey()
Expand All @@ -149,7 +149,7 @@ describe('getEncryptHelper', () => {
const material = new NodeEncryptionMaterial(suite, {})
const dataKey = new Uint8Array(suite.keyLengthBytes).fill(1)
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY }
material.setUnencryptedDataKey(dataKey, trace)
material.setUnencryptedDataKey(new Uint8Array(dataKey), trace)

const helper = getEncryptHelper(material)
expect(helper).to.haveOwnProperty('kdfGetCipher').and.to.be.a('function')
Expand Down Expand Up @@ -230,7 +230,7 @@ describe('getDecryptionHelper', () => {
const material = new NodeDecryptionMaterial(suite, {})
const dataKey = new Uint8Array(suite.keyLengthBytes).fill(1)
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY }
material.setUnencryptedDataKey(dataKey, trace)
material.setUnencryptedDataKey(new Uint8Array(dataKey), trace)

const helper = getDecryptionHelper(material)
expect(helper).to.haveOwnProperty('kdfGetDecipher').and.to.be.a('function')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,45 @@ import {
WebCryptoEncryptionMaterial,
WebCryptoDecryptionMaterial,
isEncryptionMaterial,
isDecryptionMaterial,
isDecryptionMaterial
} from './cryptographic_material'
import {
NodeAlgorithmSuite

} from '@aws-crypto/material-management'
} from './node_algorithms'
import {
AwsEsdkKeyObject // eslint-disable-line no-unused-vars
} from './types'
import { KeyringTraceFlag } from './keyring_trace'

type Material = NodeEncryptionMaterial|NodeDecryptionMaterial|WebCryptoEncryptionMaterial|WebCryptoDecryptionMaterial

export function cloneMaterial<M extends Material> (source: M): M {
const { suite, encryptionContext } = source

const clone = suite instanceof NodeAlgorithmSuite
const clone = <M>(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)
: new WebCryptoDecryptionMaterial(suite, encryptionContext))

/* The WRAPPING_KEY_GENERATED_DATA_KEY _should_ be the first trace,
* but it is better to look for it explicitly.
*/
const trace = source.keyringTrace.find(({ flags }) => flags & KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY)

if (source.hasUnencryptedDataKey) {
const udk = new Uint8Array(source.getUnencryptedDataKey())
clone.setUnencryptedDataKey(udk, source.keyringTrace[0])
const udk = cloneUnencryptedDataKey(source.getUnencryptedDataKey())
if (!trace) throw new Error('Malformed source material.')
clone.setUnencryptedDataKey(udk, trace)
}

if ((<WebCryptoDecryptionMaterial>source).hasCryptoKey) {
const cryptoKey = (<WebCryptoDecryptionMaterial>source).getCryptoKey()
if (!trace) throw new Error('Malformed source material.')
;(<WebCryptoDecryptionMaterial>clone)
.setCryptoKey(cryptoKey, source.keyringTrace[0])
.setCryptoKey(cryptoKey, trace)
}

if (isEncryptionMaterial(source) && isEncryptionMaterial(clone)) {
Expand All @@ -67,5 +79,12 @@ export function cloneMaterial<M extends Material> (source: M): M {
throw new Error('Material mismatch')
}

return <M>clone
return clone
}

function cloneUnencryptedDataKey (dataKey: AwsEsdkKeyObject| Uint8Array) {
if (dataKey instanceof Uint8Array) {
return new Uint8Array(dataKey)
}
return dataKey
}
Loading