Skip to content

Commit 28d7cf8

Browse files
committed
feat: add RSA-OAEP-256 support (when a node version supports it)
resolves #29
1 parent d157d23 commit 28d7cf8

File tree

6 files changed

+125
-38
lines changed

6 files changed

+125
-38
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ Legend:
6262
| AES || A128KW, A192KW, A256KW |
6363
| AES GCM || A128GCMKW, A192GCMKW, A256GCMKW |
6464
| Direct Key Agreement || dir |
65-
| RSAES OAEP || RSA-OAEP |
65+
| RSAES OAEP || RSA-OAEP, RSA-OAEP-256 |
6666
| RSAES-PKCS1-v1_5 || RSA1_5 |
6767
| PBES2 || PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW |
6868
| ECDH-ES || ECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW |
@@ -79,10 +79,13 @@ Legend:
7979
| Logout Token - [OpenID Connect Back-Channel Logout 1.0][spec-oidc-logout_token] |||
8080
| JARM - [JWT Secured Authorization Response Mode for OAuth 2.0][draft-jarm] |||
8181

82+
Notes
83+
- RSA-OAEP-256 is only supported when Node.js >= 12.9.0 runtime is detected
84+
- See [#electron-support](#electron-support) for electron exceptions
85+
8286
---
8387

8488
Pending Node.js Support 🤞:
85-
- RSA-OAEP-256 - see [nodejs/node#28335](https://github.com/nodejs/node/pull/28335)
8689
- ECDH-ES with X25519 and X448 - see [nodejs/node#26626](https://github.com/nodejs/node/pull/26626)
8790

8891
Won't implement:

docs/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,12 @@ privateKey.algorithms()
227227
// 'PS512',
228228
// 'RS512',
229229
// 'RSA-OAEP',
230+
// 'RSA-OAEP-256',
230231
// 'RSA1_5' }
231232
privateKey.algorithms('wrapKey')
232233
// Set {
233234
// 'RSA-OAEP',
235+
// 'RSA-OAEP-256',
234236
// 'RSA1_5' }
235237

236238
const publicKey = generateSync('RSA', 2048, { use: 'enc' }, false)
@@ -241,6 +243,7 @@ publicKey.algorithms('unwrapKey')
241243
publicKey.algorithms('wrapKey')
242244
// Set {
243245
// 'RSA-OAEP',
246+
// 'RSA-OAEP-256',
244247
// 'RSA1_5' }
245248
```
246249
</details>

lib/help/node_support.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const [major, minor] = process.version.substr(1).split('.').map(x => parseInt(x, 10))
2+
3+
module.exports = {
4+
oaepHash: major > 12 || (major === 12 && minor >= 9)
5+
}

lib/jwa/rsaes.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,42 @@ const { KEYOBJECT } = require('../help/consts')
55

66
const resolvePadding = (alg) => {
77
switch (alg) {
8+
case 'RSA-OAEP-256':
89
case 'RSA-OAEP':
910
return constants.RSA_PKCS1_OAEP_PADDING
1011
case 'RSA1_5':
1112
return constants.RSA_PKCS1_PADDING
1213
}
1314
}
1415

15-
const wrapKey = (padding, { [KEYOBJECT]: keyObject }, payload) => {
16-
return { wrapped: publicEncrypt({ key: keyObject, padding }, payload) }
16+
const resolveOaepHash = (alg) => {
17+
switch (alg) {
18+
case 'RSA-OAEP-256':
19+
return 'sha256'
20+
case 'RSA-OAEP':
21+
return 'sha1'
22+
default:
23+
return undefined
24+
}
25+
}
26+
27+
const wrapKey = (padding, oaepHash, { [KEYOBJECT]: keyObject }, payload) => {
28+
return { wrapped: publicEncrypt({ key: keyObject, oaepHash, padding }, payload) }
1729
}
1830

19-
const unwrapKey = (padding, { [KEYOBJECT]: keyObject }, payload) => {
20-
return privateDecrypt({ key: keyObject, padding }, payload)
31+
const unwrapKey = (padding, oaepHash, { [KEYOBJECT]: keyObject }, payload) => {
32+
return privateDecrypt({ key: keyObject, oaepHash, padding }, payload)
2133
}
2234

2335
module.exports = (JWA) => {
24-
['RSA1_5', 'RSA-OAEP'].forEach((jwaAlg) => {
36+
['RSA1_5', 'RSA-OAEP', 'RSA-OAEP-256'].forEach((jwaAlg) => {
2537
const padding = resolvePadding(jwaAlg)
38+
const oaepHash = resolveOaepHash(jwaAlg)
2639

2740
assert(!JWA.keyManagementEncrypt.has(jwaAlg), `keyManagementEncrypt alg ${jwaAlg} already registered`)
2841
assert(!JWA.keyManagementDecrypt.has(jwaAlg), `keyManagementDecrypt alg ${jwaAlg} already registered`)
2942

30-
JWA.keyManagementEncrypt.set(jwaAlg, wrapKey.bind(undefined, padding))
31-
JWA.keyManagementDecrypt.set(jwaAlg, unwrapKey.bind(undefined, padding))
43+
JWA.keyManagementEncrypt.set(jwaAlg, wrapKey.bind(undefined, padding, oaepHash))
44+
JWA.keyManagementDecrypt.set(jwaAlg, unwrapKey.bind(undefined, padding, oaepHash))
3245
})
3346
}

lib/jwk/key/rsa.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const {
55
THUMBPRINT_MATERIAL, JWK_MEMBERS, PUBLIC_MEMBERS,
66
PRIVATE_MEMBERS, KEY_MANAGEMENT_DECRYPT, KEY_MANAGEMENT_ENCRYPT
77
} = require('../../help/consts')
8+
const { oaepHash } = require('../../help/node_support')
89

910
const Key = require('./base')
1011

@@ -13,6 +14,10 @@ const generateKeyPair = promisify(async)
1314
const SIG_ALGS = ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512']
1415
const WRAP_ALGS = ['RSA-OAEP', 'RSA1_5']
1516

17+
if (oaepHash) {
18+
WRAP_ALGS.splice(1, 0, 'RSA-OAEP-256')
19+
}
20+
1621
const RSA_PUBLIC = new Set(['e', 'n'])
1722
Object.freeze(RSA_PUBLIC)
1823
const RSA_PRIVATE = new Set([...RSA_PUBLIC, 'd', 'p', 'q', 'dp', 'dq', 'qi'])
@@ -36,11 +41,14 @@ const sigAlgsAvailableFor = (length) => {
3641
}
3742

3843
const wrapAlgsAvailableFor = (length) => {
39-
if (length >= 592) {
40-
return new Set(WRAP_ALGS)
44+
switch (true) {
45+
case length >= 784:
46+
return new Set(WRAP_ALGS)
47+
case length >= 592:
48+
return new Set(['RSA-OAEP', 'RSA1_5'])
49+
default:
50+
return new Set(['RSA1_5'])
4151
}
42-
43-
return new Set(['RSA1_5'])
4452
}
4553

4654
// RSA Key Type

test/jwk/rsa.test.js

Lines changed: 80 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const { createPrivateKey, createPublicKey } = require('crypto')
33
const { hasProperty, hasNoProperties, hasProperties } = require('../macros')
44
const fixtures = require('../fixtures')
55

6+
const { oaepHash } = require('../../lib/help/node_support')
67
const { generateSync } = require('../../lib/jwk/generate')
78
const RSAKey = require('../../lib/jwk/key/rsa')
89

@@ -31,11 +32,19 @@ test(`RSA key .algorithms invalid operation`, t => {
3132
test(`RSA Private key`, hasProperty, key, 'type', 'private')
3233
test(`RSA Private key`, hasProperty, key, 'use', undefined)
3334

34-
test('RSA Private key algorithms (no operation)', t => {
35-
const result = key.algorithms()
36-
t.is(result.constructor, Set)
37-
t.deepEqual([...result], ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512', 'RSA-OAEP', 'RSA1_5'])
38-
})
35+
if (oaepHash) {
36+
test('RSA Private key algorithms (no operation)', t => {
37+
const result = key.algorithms()
38+
t.is(result.constructor, Set)
39+
t.deepEqual([...result], ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512', 'RSA-OAEP', 'RSA-OAEP-256', 'RSA1_5'])
40+
})
41+
} else {
42+
test('RSA Private key algorithms (no operation)', t => {
43+
const result = key.algorithms()
44+
t.is(result.constructor, Set)
45+
t.deepEqual([...result], ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512', 'RSA-OAEP', 'RSA1_5'])
46+
})
47+
}
3948

4049
test('RSA Private key algorithms (no operation, w/ alg)', t => {
4150
const key = new RSAKey(keyObject, { alg: 'RS256' })
@@ -116,11 +125,19 @@ test(`RSA key .algorithms invalid operation`, t => {
116125
t.deepEqual([...result], [])
117126
})
118127

119-
test('RSA Private key .algorithms("wrapKey")', t => {
120-
const result = key.algorithms('wrapKey')
121-
t.is(result.constructor, Set)
122-
t.deepEqual([...result], ['RSA-OAEP', 'RSA1_5'])
123-
})
128+
if (oaepHash) {
129+
test('RSA Private key .algorithms("wrapKey")', t => {
130+
const result = key.algorithms('wrapKey')
131+
t.is(result.constructor, Set)
132+
t.deepEqual([...result], ['RSA-OAEP', 'RSA-OAEP-256', 'RSA1_5'])
133+
})
134+
} else {
135+
test('RSA Private key .algorithms("wrapKey")', t => {
136+
const result = key.algorithms('wrapKey')
137+
t.is(result.constructor, Set)
138+
t.deepEqual([...result], ['RSA-OAEP', 'RSA1_5'])
139+
})
140+
}
124141

125142
test('RSA Private key .algorithms("wrapKey") when use is sig', t => {
126143
const sigKey = new RSAKey(keyObject, { use: 'sig' })
@@ -129,11 +146,19 @@ test(`RSA key .algorithms invalid operation`, t => {
129146
t.deepEqual([...result], [])
130147
})
131148

132-
test('RSA Private key .algorithms("unwrapKey")', t => {
133-
const result = key.algorithms('unwrapKey')
134-
t.is(result.constructor, Set)
135-
t.deepEqual([...result], ['RSA-OAEP', 'RSA1_5'])
136-
})
149+
if (oaepHash) {
150+
test('RSA Private key .algorithms("unwrapKey")', t => {
151+
const result = key.algorithms('unwrapKey')
152+
t.is(result.constructor, Set)
153+
t.deepEqual([...result], ['RSA-OAEP', 'RSA-OAEP-256', 'RSA1_5'])
154+
})
155+
} else {
156+
test('RSA Private key .algorithms("unwrapKey")', t => {
157+
const result = key.algorithms('unwrapKey')
158+
t.is(result.constructor, Set)
159+
t.deepEqual([...result], ['RSA-OAEP', 'RSA1_5'])
160+
})
161+
}
137162

138163
test('RSA Private key .algorithms("unwrapKey") when use is sig', t => {
139164
const sigKey = new RSAKey(keyObject, { use: 'sig' })
@@ -163,11 +188,19 @@ test(`RSA key .algorithms invalid operation`, t => {
163188
test(`RSA Public key`, hasProperty, key, 'type', 'public')
164189
test(`RSA Public key`, hasProperty, key, 'use', undefined)
165190

166-
test('RSA EC Public key algorithms (no operation)', t => {
167-
const result = key.algorithms()
168-
t.is(result.constructor, Set)
169-
t.deepEqual([...result], ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512', 'RSA-OAEP', 'RSA1_5'])
170-
})
191+
if (oaepHash) {
192+
test('RSA EC Public key algorithms (no operation)', t => {
193+
const result = key.algorithms()
194+
t.is(result.constructor, Set)
195+
t.deepEqual([...result], ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512', 'RSA-OAEP', 'RSA-OAEP-256', 'RSA1_5'])
196+
})
197+
} else {
198+
test('RSA EC Public key algorithms (no operation)', t => {
199+
const result = key.algorithms()
200+
t.is(result.constructor, Set)
201+
t.deepEqual([...result], ['PS256', 'RS256', 'PS384', 'RS384', 'PS512', 'RS512', 'RSA-OAEP', 'RSA1_5'])
202+
})
203+
}
171204

172205
test('RSA EC Public key algorithms (no operation, w/ alg)', t => {
173206
const key = new RSAKey(keyObject, { alg: 'RS256' })
@@ -248,11 +281,19 @@ test(`RSA key .algorithms invalid operation`, t => {
248281
t.deepEqual([...result], [])
249282
})
250283

251-
test('RSA Public key .algorithms("wrapKey")', t => {
252-
const result = key.algorithms('wrapKey')
253-
t.is(result.constructor, Set)
254-
t.deepEqual([...result], ['RSA-OAEP', 'RSA1_5'])
255-
})
284+
if (oaepHash) {
285+
test('RSA Public key .algorithms("wrapKey")', t => {
286+
const result = key.algorithms('wrapKey')
287+
t.is(result.constructor, Set)
288+
t.deepEqual([...result], ['RSA-OAEP', 'RSA-OAEP-256', 'RSA1_5'])
289+
})
290+
} else {
291+
test('RSA Public key .algorithms("wrapKey")', t => {
292+
const result = key.algorithms('wrapKey')
293+
t.is(result.constructor, Set)
294+
t.deepEqual([...result], ['RSA-OAEP', 'RSA1_5'])
295+
})
296+
}
256297

257298
test('RSA Public key .algorithms("wrapKey") when use is sig', t => {
258299
const sigKey = new RSAKey(keyObject, { use: 'sig' })
@@ -295,6 +336,13 @@ test(`RSA key .algorithms invalid operation`, t => {
295336
t.true(k.algorithms().has('RS512'))
296337
})
297338

339+
if (oaepHash) {
340+
test('RSA key >= 784 bits can do RSA-OAEP-256', t => {
341+
const k = generateSync('RSA', 784)
342+
t.true(k.algorithms().has('RSA-OAEP-256'))
343+
})
344+
}
345+
298346
test('RSA key >= 784 bits can do PS384', t => {
299347
const k = generateSync('RSA', 784)
300348
t.true(k.algorithms().has('PS384'))
@@ -322,6 +370,13 @@ test(`RSA key .algorithms invalid operation`, t => {
322370
t.true(k.algorithms().has('PS384'))
323371
})
324372

373+
if (oaepHash) {
374+
test('RSA key >= 896 bits can do RSA-OAEP-256', t => {
375+
const k = generateSync('RSA', 896)
376+
t.true(k.algorithms().has('RSA-OAEP-256'))
377+
})
378+
}
379+
325380
test('RSA key >= 1152 bits can do PS512', t => {
326381
const k = generateSync('RSA', 1152)
327382
t.true(k.algorithms().has('PS512'))

0 commit comments

Comments
 (0)