Skip to content

Commit 03d6d01

Browse files
committed
fix: limit default PBES2 alg's computational expense
Also adds an option to opt-in to higher computation expense.
1 parent 8c5cc34 commit 03d6d01

File tree

4 files changed

+31
-6
lines changed

4 files changed

+31
-6
lines changed

src/jwe/flattened/decrypt.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,9 @@ export async function flattenedDecrypt(
188188

189189
let cek: KeyLike | Uint8Array
190190
try {
191-
cek = await decryptKeyManagement(alg, key, encryptedKey, joseHeader)
191+
cek = await decryptKeyManagement(alg, key, encryptedKey, joseHeader, options)
192192
} catch (err) {
193-
if (err instanceof TypeError) {
193+
if (err instanceof TypeError || err instanceof JWEInvalid || err instanceof JOSENotSupported) {
194194
throw err
195195
}
196196
// https://www.rfc-editor.org/rfc/rfc7516#section-11.5

src/lib/decrypt_key_management.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { decrypt as pbes2Kw } from '../runtime/pbes2kw.js'
44
import { decrypt as rsaEs } from '../runtime/rsaes.js'
55
import { decode as base64url } from '../runtime/base64url.js'
66

7-
import type { JWEHeaderParameters, KeyLike, JWK } from '../types.d'
7+
import type { DecryptOptions, JWEHeaderParameters, KeyLike, JWK } from '../types.d'
88
import { JOSENotSupported, JWEInvalid } from '../util/errors.js'
99
import { bitLength as cekLength } from '../lib/cek.js'
1010
import { importJWK } from '../key/import.js'
@@ -17,6 +17,7 @@ async function decryptKeyManagement(
1717
key: KeyLike | Uint8Array,
1818
encryptedKey: Uint8Array | undefined,
1919
joseHeader: JWEHeaderParameters,
20+
options?: DecryptOptions,
2021
): Promise<KeyLike | Uint8Array> {
2122
checkKeyType(alg, key, 'decrypt')
2223

@@ -96,6 +97,11 @@ async function decryptKeyManagement(
9697
if (typeof joseHeader.p2c !== 'number')
9798
throw new JWEInvalid(`JOSE Header "p2c" (PBES2 Count) missing or invalid`)
9899

100+
const p2cLimit = options?.maxPBES2Count || 10_000
101+
102+
if (joseHeader.p2c > p2cLimit)
103+
throw new JWEInvalid(`JOSE Header "p2c" (PBES2 Count) out is of acceptable bounds`)
104+
99105
if (typeof joseHeader.p2s !== 'string')
100106
throw new JWEInvalid(`JOSE Header "p2s" (PBES2 Salt) missing or invalid`)
101107

src/types.d.ts

+7
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,13 @@ export interface DecryptOptions extends CritOption {
401401
* with compressed plaintext.
402402
*/
403403
inflateRaw?: InflateFunction
404+
405+
/**
406+
* (PBES2 Key Management Algorithms only) Maximum allowed "p2c" (PBES2 Count) Header Parameter
407+
* value. The PBKDF2 iteration count defines the algorithm's computational expense. By default
408+
* this value is set to 10000.
409+
*/
410+
maxPBES2Count?: number
404411
}
405412

406413
/** JWE Deflate option. */

test/jwe/flattened.decrypt.test.mjs

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import test from 'ava'
22
import * as crypto from 'crypto'
3-
import { root } from '../dist.mjs'
3+
import { root, conditional } from '../dist.mjs'
44

55
const { FlattenedEncrypt, flattenedDecrypt, base64url } = await import(root)
66

@@ -177,8 +177,8 @@ test('JWE format validation', async (t) => {
177177
jwe.encrypted_key = 'foo'
178178

179179
await t.throwsAsync(flattenedDecrypt(jwe, t.context.secret), {
180-
message: 'decryption operation failed',
181-
code: 'ERR_JWE_DECRYPTION_FAILED',
180+
message: 'Encountered unexpected JWE Encrypted Key',
181+
code: 'ERR_JWE_INVALID',
182182
})
183183
}
184184
})
@@ -239,3 +239,15 @@ test('decrypt empty data (CBC)', async (t) => {
239239
const { plaintext } = await flattenedDecrypt(jwe, new Uint8Array(32))
240240
t.is(plaintext.byteLength, 0)
241241
})
242+
243+
conditional({ electron: 0 })('decrypt PBES2 p2c limit', async (t) => {
244+
const jwe = await new FlattenedEncrypt(new Uint8Array(0))
245+
.setProtectedHeader({ alg: 'PBES2-HS256+A128KW', enc: 'A128CBC-HS256' })
246+
.setKeyManagementParameters({ p2c: 2049 })
247+
.encrypt(new Uint8Array(32))
248+
249+
await t.throwsAsync(flattenedDecrypt(jwe, new Uint8Array(32), { maxPBES2Count: 2048 }), {
250+
message: 'JOSE Header "p2c" (PBES2 Count) out is of acceptable bounds',
251+
code: 'ERR_JWE_INVALID',
252+
})
253+
})

0 commit comments

Comments
 (0)