Skip to content

Commit 80dfd38

Browse files
authored
Merge branch 'master' into zero_byte_operations
2 parents 50549a3 + 81b4562 commit 80dfd38

File tree

5 files changed

+143
-68
lines changed

5 files changed

+143
-68
lines changed

modules/integration-node/src/decrypt_materials_manager_node.ts

+9-10
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import {
2020
RawAesKeyringNode,
2121
WrappingSuiteIdentifier, // eslint-disable-line no-unused-vars
2222
RawAesWrappingSuiteIdentifier,
23-
RawRsaKeyringNode
23+
RawRsaKeyringNode,
24+
oaepHashSupported
2425
} from '@aws-crypto/client-node'
2526
import {
2627
RsaKeyInfo, // eslint-disable-line no-unused-vars
@@ -82,18 +83,16 @@ export function rsaKeyring (keyInfo: RsaKeyInfo, key: RSAKey) {
8283
const rsaKey = key.type === 'private'
8384
? { privateKey: key.material }
8485
: { publicKey: key.material }
85-
const padding = rsaPadding(keyInfo)
86-
return new RawRsaKeyringNode({ keyName, keyNamespace, rsaKey, padding })
86+
const { padding, oaepHash } = rsaPadding(keyInfo)
87+
return new RawRsaKeyringNode({ keyName, keyNamespace, rsaKey, padding, oaepHash })
8788
}
8889

8990
export function rsaPadding (keyInfo: RsaKeyInfo) {
90-
const paddingAlgorithm = keyInfo['padding-algorithm']
91-
const paddingHash = keyInfo['padding-hash']
92-
93-
if (paddingAlgorithm === 'pkcs1') return constants.RSA_PKCS1_PADDING
94-
needs(paddingHash === 'sha1', 'Not supported at this time.')
95-
96-
return constants.RSA_PKCS1_OAEP_PADDING
91+
if (keyInfo['padding-algorithm'] === 'pkcs1') return { padding: constants.RSA_PKCS1_PADDING }
92+
const padding = constants.RSA_PKCS1_OAEP_PADDING
93+
const oaepHash = keyInfo['padding-hash']
94+
needs(oaepHashSupported || oaepHash === 'sha1', 'Not supported at this time.')
95+
return { padding, oaepHash }
9796
}
9897

9998
export class NotSupported extends Error {

modules/raw-rsa-keyring-node/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
*/
1515

1616
export * from './raw_rsa_keyring_node'
17+
export * from './oaep_hash_supported'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use
5+
* this file except in compliance with the License. A copy of the License is
6+
* located at
7+
*
8+
* http://aws.amazon.com/apache2.0/
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12+
* implied. See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
/* oaepHash support was added in Node.js v12.9.1 (https://github.com/nodejs/node/pull/28335)
17+
* However, the integration tests need to be able to verify functionality on other versions.
18+
* There are no constants to sniff,
19+
* and looking at the version would not catch back-ports.
20+
* So I simply try the function.
21+
* However there is a rub as the test might seem backwards.
22+
* Sending an invalid hash to the version that supports oaepHash will throw an error.
23+
* But sending an invalid hash to a version that does not support oaepHash will be ignored.
24+
*/
25+
26+
import {
27+
needs
28+
} from '@aws-crypto/material-management-node'
29+
30+
import {
31+
constants,
32+
publicEncrypt
33+
} from 'crypto'
34+
35+
export const oaepHashSupported = (function () {
36+
const key = '-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAs7RoNYEPAIws89VV+kra\nrVv/4wbdmUAaAKWgWuxZi5na9GJSmnhCkqyLRm7wPbQY4LCoa5/IMUxkHLsYDPdu\nudY0Qm0GcoxOlvJKHYo4RjF7HyiS34D6dvyO4Gd3aq0mZHoxSGCxW/7hf03wEMzc\niVJXWHXhaI0lD6nrzIEgLrE4L+3V2LeAQjvZsTKd+bYMqeZOL2syiVVIAU8POwAG\nGVBroJoveFm/SUp6lCiN0M2kTeyQA2ax3QTtZSAa8nwrI7U52XOzVmdMicJsy2Pg\nuW98te3MuODdK24yNkHIkYameP/Umf/SJshUJQd5a/TUp3XE+HhOWAumx22tIDlC\nvZS11cuk2fp0WeHUnXaC19N5qWKfvHEKSugzty/z3lGP7ItFhrF2X1qJHeAAsL11\nkjo6Lc48KsE1vKvbnW4VLyB3wdNiVvmUNO29tPXwaR0Q5Gbr3jk3nUzdkEHouHWQ\n41lubOHCCBN3V13mh/MgtNhESHjfmmOnh54ErD9saA1d7CjTf8g2wqmjEqvGSW6N\nq7zhcWR2tp1olflS7oHzul4/I3hnkfL6Kb2xAWWaQKvg3mtsY2OPlzFEP0tR5UcH\nPfp5CeS1Xzg7hN6vRICW6m4l3u2HJFld2akDMm1vnSz8RCbPW7jp7YBxUkWJmypM\ntG7Yv2aGZXGbUtM8o1cZarECAwEAAQ==\n-----END PUBLIC KEY-----'
37+
38+
const oaepHash = 'i_am_not_valid'
39+
try {
40+
// @ts-ignore
41+
publicEncrypt({ key, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash }, Buffer.from([1, 2, 3, 4]))
42+
/* See note above,
43+
* only versions that support oaepHash will respond.
44+
* So the only way I can get here is if the option was ignored.
45+
*/
46+
return false
47+
} catch (ex) {
48+
needs(ex.code === 'ERR_OSSL_EVP_INVALID_DIGEST', 'Unexpected error testing oaepHash.')
49+
return true
50+
}
51+
})()

modules/raw-rsa-keyring-node/src/raw_rsa_keyring_node.ts

+18-9
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ import {
3232
constants,
3333
publicEncrypt,
3434
privateDecrypt,
35-
randomBytes
35+
randomBytes,
36+
RsaPublicKey, // eslint-disable-line no-unused-vars
37+
RsaPrivateKey // eslint-disable-line no-unused-vars
3638
} from 'crypto'
3739

3840
import {
@@ -42,6 +44,8 @@ import {
4244
UnwrapKey // eslint-disable-line no-unused-vars
4345
} from '@aws-crypto/raw-keyring'
4446

47+
import { oaepHashSupported } from './oaep_hash_supported'
48+
4549
/* Interface question:
4650
* When creating a keyring being able to define
4751
* if the keyring can be used for encrypt/decrypt/both
@@ -57,18 +61,17 @@ interface RsaKey {
5761
privateKey?: string | Buffer | AwsEsdkKeyObject
5862
}
5963

64+
export type OaepHash = 'sha1'|'sha256'|'sha384'|'sha512'|undefined
65+
const supportedOaepHash: OaepHash[] = ['sha1', 'sha256', 'sha384', 'sha512', undefined]
66+
6067
export type RawRsaKeyringNodeInput = {
6168
keyNamespace: string
6269
keyName: string
6370
rsaKey: RsaKey
6471
padding?: number
72+
oaepHash?: OaepHash
6573
}
6674

67-
/* Node supports RSA_OAEP_SHA1_MFG1 by default.
68-
* It does not support RSA_OAEP_SHA256_MFG1 at this time.
69-
* Passing RSA_PKCS1_OAEP_PADDING implies RSA_OAEP_SHA1_MFG1.
70-
*/
71-
7275
export class RawRsaKeyringNode extends KeyringNode {
7376
public keyNamespace!: string
7477
public keyName!: string
@@ -78,19 +81,25 @@ export class RawRsaKeyringNode extends KeyringNode {
7881
constructor (input: RawRsaKeyringNodeInput) {
7982
super()
8083

81-
const { rsaKey, keyName, keyNamespace, padding = constants.RSA_PKCS1_OAEP_PADDING } = input
84+
const { rsaKey, keyName, keyNamespace, padding = constants.RSA_PKCS1_OAEP_PADDING, oaepHash } = input
8285
const { publicKey, privateKey } = rsaKey
8386
/* Precondition: RsaKeyringNode needs either a public or a private key to operate. */
8487
needs(publicKey || privateKey, 'No Key provided.')
8588
/* Precondition: RsaKeyringNode needs identifying information for encrypt and decrypt. */
8689
needs(keyName && keyNamespace, 'Identifying information must be defined.')
90+
/* Precondition: The AWS ESDK only supports specific hash values for OAEP padding. */
91+
needs(padding === constants.RSA_PKCS1_OAEP_PADDING
92+
? oaepHashSupported
93+
? supportedOaepHash.includes(oaepHash)
94+
: !oaepHash || oaepHash === 'sha1'
95+
: !oaepHash, 'Unsupported oaepHash')
8796

8897
const _wrapKey = async (material: NodeEncryptionMaterial) => {
8998
/* Precondition: Public key must be defined to support encrypt. */
9099
if (!publicKey) throw new Error('No public key defined in constructor. Encrypt disabled.')
91100
const { buffer, byteOffset, byteLength } = unwrapDataKey(material.getUnencryptedDataKey())
92101
const encryptedDataKey = publicEncrypt(
93-
{ key: publicKey, padding },
102+
{ key: publicKey, padding, oaepHash } as RsaPublicKey,
94103
Buffer.from(buffer, byteOffset, byteLength))
95104
const providerInfo = this.keyName
96105
const providerId = this.keyNamespace
@@ -112,7 +121,7 @@ export class RawRsaKeyringNode extends KeyringNode {
112121
const { buffer, byteOffset, byteLength } = edk.encryptedDataKey
113122
const encryptedDataKey = Buffer.from(buffer, byteOffset, byteLength)
114123
const unencryptedDataKey = privateDecrypt(
115-
{ key: privateKey, padding },
124+
{ key: privateKey, padding, oaepHash } as RsaPrivateKey,
116125
encryptedDataKey)
117126
return material.setUnencryptedDataKey(unencryptedDataKey, trace)
118127
}

modules/raw-rsa-keyring-node/test/raw_rsa_keyring_node.test.ts

+64-49
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import * as chai from 'chai'
1919
import chaiAsPromised from 'chai-as-promised'
2020
import 'mocha'
2121
import {
22-
RawRsaKeyringNode
22+
RawRsaKeyringNode,
23+
OaepHash // eslint-disable-line no-unused-vars
2324
} from '../src/index'
2425
import {
2526
KeyringNode,
@@ -30,6 +31,7 @@ import {
3031
NodeDecryptionMaterial,
3132
unwrapDataKey
3233
} from '@aws-crypto/material-management-node'
34+
import { oaepHashSupported } from '../src/oaep_hash_supported'
3335

3436
chai.use(chaiAsPromised)
3537
const { expect } = chai
@@ -108,6 +110,16 @@ describe('RawRsaKeyringNode::constructor', () => {
108110
})).to.throw()
109111
})
110112

113+
it('Precondition: The AWS ESDK only supports specific hash values for OAEP padding.', () => {
114+
expect(() => new RawRsaKeyringNode({
115+
keyName,
116+
keyNamespace,
117+
// @ts-ignore Valid hash, but not supported by the ESDK
118+
oaepHash: 'rmd160',
119+
rsaKey: { privateKey: privatePem }
120+
})).to.throw('Unsupported oaepHash')
121+
})
122+
111123
it('Precondition: RsaKeyringNode needs identifying information for encrypt and decrypt.', () => {
112124
// @ts-ignore Typescript is trying to save us.
113125
expect(() => new RawRsaKeyringNode({
@@ -126,58 +138,61 @@ describe('RawRsaKeyringNode::constructor', () => {
126138
})
127139
})
128140

129-
describe('RawRsaKeyringNode encrypt/decrypt', () => {
130-
const keyNamespace = 'keyNamespace'
131-
const keyName = 'keyName'
132-
const keyring = new RawRsaKeyringNode({
133-
rsaKey: { privateKey: privatePem, publicKey: publicPem },
134-
keyName,
135-
keyNamespace
136-
})
137-
let encryptedDataKey: EncryptedDataKey
138-
139-
it('can encrypt and create unencrypted data key', async () => {
140-
const suite = new NodeAlgorithmSuite(AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256)
141-
const material = new NodeEncryptionMaterial(suite, {})
142-
const test = await keyring.onEncrypt(material)
143-
expect(test.hasValidKey()).to.equal(true)
144-
const udk = unwrapDataKey(test.getUnencryptedDataKey())
145-
expect(udk).to.have.lengthOf(suite.keyLengthBytes)
146-
expect(test.encryptedDataKeys).to.have.lengthOf(1)
147-
const [edk] = test.encryptedDataKeys
148-
expect(edk.providerId).to.equal(keyNamespace)
149-
encryptedDataKey = edk
150-
})
151-
152-
it('can decrypt an EncryptedDataKey', async () => {
153-
const suite = new NodeAlgorithmSuite(AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256)
154-
const material = new NodeDecryptionMaterial(suite, {})
155-
const test = await keyring.onDecrypt(material, [encryptedDataKey])
156-
expect(test.hasValidKey()).to.equal(true)
157-
})
158-
159-
it('Precondition: Public key must be defined to support encrypt.', async () => {
141+
const oaepHashOptions: OaepHash[] = [undefined, 'sha1', 'sha256', 'sha384', 'sha512']
142+
oaepHashOptions
143+
.filter(oaepHash => oaepHashSupported || [undefined, 'sha1'].includes(oaepHash))
144+
.forEach(oaepHash => describe(`RawRsaKeyringNode encrypt/decrypt for oaepHash=${oaepHash || 'undefined'}`, () => {
145+
const keyNamespace = 'keyNamespace'
146+
const keyName = 'keyName'
160147
const keyring = new RawRsaKeyringNode({
161-
rsaKey: { privateKey: privatePem },
148+
rsaKey: { privateKey: privatePem, publicKey: publicPem },
162149
keyName,
163-
keyNamespace
150+
keyNamespace,
151+
oaepHash
152+
})
153+
let encryptedDataKey: EncryptedDataKey
154+
155+
it('can encrypt and create unencrypted data key', async () => {
156+
const suite = new NodeAlgorithmSuite(AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256)
157+
const material = new NodeEncryptionMaterial(suite, {})
158+
const test = await keyring.onEncrypt(material)
159+
expect(test.hasValidKey()).to.equal(true)
160+
const udk = unwrapDataKey(test.getUnencryptedDataKey())
161+
expect(udk).to.have.lengthOf(suite.keyLengthBytes)
162+
expect(test.encryptedDataKeys).to.have.lengthOf(1)
163+
const [edk] = test.encryptedDataKeys
164+
expect(edk.providerId).to.equal(keyNamespace)
165+
encryptedDataKey = edk
164166
})
165167

166-
const suite = new NodeAlgorithmSuite(AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256)
167-
const material = new NodeEncryptionMaterial(suite, {})
168-
expect(keyring.onEncrypt(material)).to.rejectedWith(Error)
169-
})
168+
it('can decrypt an EncryptedDataKey', async () => {
169+
const suite = new NodeAlgorithmSuite(AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256)
170+
const material = new NodeDecryptionMaterial(suite, {})
171+
const test = await keyring.onDecrypt(material, [encryptedDataKey])
172+
expect(test.hasValidKey()).to.equal(true)
173+
})
170174

171-
it('Precondition: Private key must be defined to support decrypt.', async () => {
172-
const keyring = new RawRsaKeyringNode({
173-
rsaKey: { publicKey: publicPem },
174-
keyName,
175-
keyNamespace
175+
it('Precondition: Public key must be defined to support encrypt.', async () => {
176+
const keyring = new RawRsaKeyringNode({
177+
rsaKey: { privateKey: privatePem },
178+
keyName,
179+
keyNamespace
180+
})
181+
182+
const suite = new NodeAlgorithmSuite(AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256)
183+
const material = new NodeEncryptionMaterial(suite, {})
184+
return expect(keyring.onEncrypt(material)).to.rejectedWith(Error)
176185
})
177186

178-
const suite = new NodeAlgorithmSuite(AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256)
179-
const material = new NodeDecryptionMaterial(suite, {})
180-
await keyring.onDecrypt(material, [encryptedDataKey])
181-
expect(keyring.onDecrypt(material, [encryptedDataKey])).to.rejectedWith(Error)
182-
})
183-
})
187+
it('Precondition: Private key must be defined to support decrypt.', async () => {
188+
const keyring = new RawRsaKeyringNode({
189+
rsaKey: { publicKey: publicPem },
190+
keyName,
191+
keyNamespace
192+
})
193+
194+
const suite = new NodeAlgorithmSuite(AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256)
195+
const material = new NodeDecryptionMaterial(suite, {})
196+
return expect(keyring._unwrapKey(material, encryptedDataKey)).to.rejectedWith(Error)
197+
})
198+
}))

0 commit comments

Comments
 (0)