Skip to content

Commit 77ad031

Browse files
authored
feat: Suport Node.js crypto KeyObjects (#200)
resolves #74 KeyObjects were introduced in Node.js v11. They wrap the Buffer in an object that has even better security properties than an isolated buffer.
1 parent a5dd6c2 commit 77ad031

File tree

24 files changed

+379
-138
lines changed

24 files changed

+379
-138
lines changed

modules/cache-material/src/caching_cryptographic_materials_decorators.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import {
2424
DecryptionRequest, // eslint-disable-line no-unused-vars
2525
needs,
2626
readOnlyProperty,
27-
Keyring // eslint-disable-line no-unused-vars
27+
Keyring, // eslint-disable-line no-unused-vars
28+
cloneMaterial
2829
} from '@aws-crypto/material-management'
2930
import { Maximum } from '@aws-crypto/serialize'
3031
import {
@@ -34,7 +35,6 @@ import {
3435
import {
3536
CryptographicMaterialsCacheKeyHelpersInterface // eslint-disable-line no-unused-vars
3637
} from './build_cryptographic_materials_cache_key_helpers'
37-
import { cloneMaterial } from './clone_cryptographic_material'
3838

3939
export function decorateProperties<S extends SupportedAlgorithmSuites> (
4040
obj: CachingMaterialsManager<S>,

modules/cache-material/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,4 @@
1616
export * from './cryptographic_materials_cache'
1717
export * from './caching_cryptographic_materials_decorators'
1818
export * from './build_cryptographic_materials_cache_key_helpers'
19-
export * from './clone_cryptographic_material'
2019
export * from './get_local_cryptographic_materials_cache'

modules/kms-keyring-node/test/kms_keyring_node.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import {
2424
NodeAlgorithmSuite,
2525
AlgorithmSuiteIdentifier,
2626
EncryptedDataKey, // eslint-disable-line no-unused-vars
27-
NodeDecryptionMaterial
27+
NodeDecryptionMaterial,
28+
unwrapDataKey
2829
} from '@aws-crypto/material-management-node'
2930

3031
describe('KmsKeyringNode::constructor', () => {
@@ -61,7 +62,7 @@ describe('RawAesKeyringWebCrypto encrypt/decrypt', () => {
6162
const material = new NodeEncryptionMaterial(suite, {})
6263
const test = await keyring.onEncrypt(material)
6364
expect(test.hasValidKey()).to.equal(true)
64-
udk = test.getUnencryptedDataKey()
65+
udk = unwrapDataKey(test.getUnencryptedDataKey())
6566
expect(udk).to.have.lengthOf(suite.keyLengthBytes)
6667
expect(test.encryptedDataKeys).to.have.lengthOf(2)
6768
const [edk] = test.encryptedDataKeys
@@ -73,6 +74,6 @@ describe('RawAesKeyringWebCrypto encrypt/decrypt', () => {
7374
const material = new NodeDecryptionMaterial(suite, {})
7475
const test = await keyring.onDecrypt(material, [encryptedDataKey])
7576
expect(test.hasValidKey()).to.equal(true)
76-
expect(test.getUnencryptedDataKey()).to.deep.equal(udk)
77+
expect(unwrapDataKey(test.getUnencryptedDataKey())).to.deep.equal(udk)
7778
})
7879
})

modules/kms-keyring/src/kms_keyring.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import {
2828
KeyringTraceFlag,
2929
EncryptedDataKey, // eslint-disable-line no-unused-vars
3030
immutableClass,
31-
readOnlyProperty
31+
readOnlyProperty,
32+
unwrapDataKey
3233
} from '@aws-crypto/material-management'
3334
import { KMS_PROVIDER_ID, generateDataKey, encrypt, decrypt, kmsResponseToEncryptedDataKey } from './helpers'
3435
import { regionFromKmsKeyArn } from './region_from_kms_key_arn'
@@ -131,7 +132,7 @@ export function KmsKeyringClass<S extends SupportedAlgorithmSuites, Client exten
131132
* Furthermore *only* CMK's explicitly designated as generators can generate data keys.
132133
* See cryptographic_materials as getUnencryptedDataKey will throw in this case.
133134
*/
134-
const unencryptedDataKey = material.getUnencryptedDataKey()
135+
const unencryptedDataKey = unwrapDataKey(material.getUnencryptedDataKey())
135136

136137
const flags = KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY | KeyringTraceFlag.WRAPPING_KEY_SIGNED_ENC_CTX
137138
for (const kmsKey of keyIds) {

modules/material-management-browser/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ export {
2222
AlgorithmSuiteIdentifier, EncryptionContext, EncryptedDataKey, KeyringWebCrypto,
2323
KeyringTrace, KeyringTraceFlag, needs, MixedBackendCryptoKey, MultiKeyringWebCrypto,
2424
immutableBaseClass, immutableClass, frozenClass, readOnlyProperty, keyUsageForMaterial,
25-
isValidCryptoKey, isCryptoKey, WebCryptoMaterialsManager
25+
isValidCryptoKey, isCryptoKey, WebCryptoMaterialsManager, unwrapDataKey
2626
} from '@aws-crypto/material-management'

modules/material-management-browser/src/material_helpers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
isValidCryptoKey,
2323
keyUsageForMaterial,
2424
subtleFunctionForMaterial,
25+
unwrapDataKey,
2526
WebCryptoMaterial // eslint-disable-line no-unused-vars
2627
} from '@aws-crypto/material-management'
2728

@@ -246,7 +247,7 @@ export async function _importCryptoKey<T extends WebCryptoMaterial<T>> (
246247
) {
247248
const { suite } = material
248249
const extractable = false
249-
const udk = material.getUnencryptedDataKey()
250+
const udk = unwrapDataKey(material.getUnencryptedDataKey())
250251

251252
if (suite.kdf) {
252253
/* For several browsers, import for a key to derive with HKDF

modules/material-management-node/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@ export {
2929
AlgorithmSuiteIdentifier, EncryptionContext, EncryptedDataKey,
3030
KeyringTrace, KeyringTraceFlag, needs, KeyringNode, MultiKeyringNode,
3131
immutableBaseClass, immutableClass, frozenClass, readOnlyProperty,
32-
NodeMaterialsManager
32+
NodeMaterialsManager, unwrapDataKey, AwsEsdkKeyObject
3333
} from '@aws-crypto/material-management'

modules/material-management-node/src/material_helpers.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515

1616
import {
1717
needs, NodeEncryptionMaterial, NodeDecryptionMaterial,
18+
unwrapDataKey,
19+
wrapWithKeyObjectIfSupported,
20+
AwsEsdkKeyObject, // eslint-disable-line no-unused-vars
1821
NodeHash // eslint-disable-line no-unused-vars
1922
} from '@aws-crypto/material-management'
2023
import {
@@ -191,12 +194,13 @@ export function getCryptoStream (material: NodeEncryptionMaterial|NodeDecryption
191194
*/
192195
needs(material.hasUnencryptedDataKey, 'Unencrypted data key has been zeroed.')
193196

194-
return createCryptoStream(cipherName, derivedKey, iv)
197+
// createDecipheriv is incorrectly typed in @types/node. It should take key: CipherKey, not key: BinaryLike
198+
return createCryptoStream(cipherName, derivedKey as any, iv)
195199
}
196200
}
197201
}
198202

199-
export function nodeKdf (material: NodeEncryptionMaterial|NodeDecryptionMaterial, info?: Uint8Array): Uint8Array {
203+
export function nodeKdf (material: NodeEncryptionMaterial|NodeDecryptionMaterial, info?: Uint8Array): Uint8Array|AwsEsdkKeyObject {
200204
const dataKey = material.getUnencryptedDataKey()
201205

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

220-
return kdfIndex[<NodeHash>kdfHash](toExtract)(keyLengthBytes, infoBuff)
229+
const derivedBytes = kdfIndex[<NodeHash>kdfHash](toExtract)(keyLengthBytes, infoBuff)
230+
231+
return wrapWithKeyObjectIfSupported(derivedBytes)
221232
}

modules/material-management-node/test/material_helpers.test.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

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

33-
const test = nodeKdf(material, new Uint8Array(5))
34-
expect(test === dataKey).to.equal(true)
33+
const test = unwrapDataKey(nodeKdf(material, new Uint8Array(5)))
34+
expect(test).to.deep.equal(dataKey)
3535
})
3636

3737
it('HKDF SHA256', () => {
3838
const suite = new NodeAlgorithmSuite(AlgorithmSuiteIdentifier.ALG_AES128_GCM_IV12_TAG16_HKDF_SHA256)
3939
const material = new NodeEncryptionMaterial(suite, {})
4040
const dataKey = new Uint8Array(suite.keyLengthBytes).fill(1)
4141
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY }
42-
material.setUnencryptedDataKey(dataKey, trace)
42+
material.setUnencryptedDataKey(new Uint8Array(dataKey), trace)
4343

44-
const test = nodeKdf(material, new Uint8Array(5))
45-
expect(test === dataKey).to.equal(false)
44+
const test = unwrapDataKey(nodeKdf(material, new Uint8Array(5)))
45+
expect(test).to.not.deep.equal(dataKey)
4646
expect(test.byteLength).to.equal(suite.keyLengthBytes)
4747
})
4848

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

56-
const test = nodeKdf(material, new Uint8Array(5))
57-
expect(test === dataKey).to.equal(false)
56+
const test = unwrapDataKey(nodeKdf(material, new Uint8Array(5)))
57+
expect(test).to.not.deep.equal(dataKey)
5858
expect(test.byteLength).to.equal(suite.keyLengthBytes)
5959
})
6060

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

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

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

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

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

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

235235
const helper = getDecryptionHelper(material)
236236
expect(helper).to.haveOwnProperty('kdfGetDecipher').and.to.be.a('function')

modules/cache-material/src/clone_cryptographic_material.ts renamed to modules/material-management/src/clone_cryptographic_material.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,45 @@ import {
1919
WebCryptoEncryptionMaterial,
2020
WebCryptoDecryptionMaterial,
2121
isEncryptionMaterial,
22-
isDecryptionMaterial,
22+
isDecryptionMaterial
23+
} from './cryptographic_material'
24+
import {
2325
NodeAlgorithmSuite
24-
25-
} from '@aws-crypto/material-management'
26+
} from './node_algorithms'
27+
import {
28+
AwsEsdkKeyObject // eslint-disable-line no-unused-vars
29+
} from './types'
30+
import { KeyringTraceFlag } from './keyring_trace'
2631

2732
type Material = NodeEncryptionMaterial|NodeDecryptionMaterial|WebCryptoEncryptionMaterial|WebCryptoDecryptionMaterial
2833

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

32-
const clone = suite instanceof NodeAlgorithmSuite
37+
const clone = <M>(suite instanceof NodeAlgorithmSuite
3338
? source instanceof NodeEncryptionMaterial
3439
? new NodeEncryptionMaterial(suite, encryptionContext)
3540
: new NodeDecryptionMaterial(suite, encryptionContext)
3641
: source instanceof WebCryptoEncryptionMaterial
3742
? new WebCryptoEncryptionMaterial(suite, encryptionContext)
38-
: new WebCryptoDecryptionMaterial(suite, encryptionContext)
43+
: new WebCryptoDecryptionMaterial(suite, encryptionContext))
44+
45+
/* The WRAPPING_KEY_GENERATED_DATA_KEY _should_ be the first trace,
46+
* but it is better to look for it explicitly.
47+
*/
48+
const trace = source.keyringTrace.find(({ flags }) => flags & KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY)
3949

4050
if (source.hasUnencryptedDataKey) {
41-
const udk = new Uint8Array(source.getUnencryptedDataKey())
42-
clone.setUnencryptedDataKey(udk, source.keyringTrace[0])
51+
const udk = cloneUnencryptedDataKey(source.getUnencryptedDataKey())
52+
if (!trace) throw new Error('Malformed source material.')
53+
clone.setUnencryptedDataKey(udk, trace)
4354
}
4455

4556
if ((<WebCryptoDecryptionMaterial>source).hasCryptoKey) {
4657
const cryptoKey = (<WebCryptoDecryptionMaterial>source).getCryptoKey()
58+
if (!trace) throw new Error('Malformed source material.')
4759
;(<WebCryptoDecryptionMaterial>clone)
48-
.setCryptoKey(cryptoKey, source.keyringTrace[0])
60+
.setCryptoKey(cryptoKey, trace)
4961
}
5062

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

70-
return <M>clone
82+
return clone
83+
}
84+
85+
function cloneUnencryptedDataKey (dataKey: AwsEsdkKeyObject| Uint8Array) {
86+
if (dataKey instanceof Uint8Array) {
87+
return new Uint8Array(dataKey)
88+
}
89+
return dataKey
7190
}

0 commit comments

Comments
 (0)