Skip to content

Commit

Permalink
fix: limit default PBES2 alg's computational expense
Browse files Browse the repository at this point in the history
Also adds an option to opt-in to higher computation expense.
  • Loading branch information
panva committed Sep 1, 2022
1 parent 8c5cc34 commit 03d6d01
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 6 deletions.
4 changes: 2 additions & 2 deletions src/jwe/flattened/decrypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,9 @@ export async function flattenedDecrypt(

let cek: KeyLike | Uint8Array
try {
cek = await decryptKeyManagement(alg, key, encryptedKey, joseHeader)
cek = await decryptKeyManagement(alg, key, encryptedKey, joseHeader, options)
} catch (err) {
if (err instanceof TypeError) {
if (err instanceof TypeError || err instanceof JWEInvalid || err instanceof JOSENotSupported) {
throw err
}
// https://www.rfc-editor.org/rfc/rfc7516#section-11.5
Expand Down
8 changes: 7 additions & 1 deletion src/lib/decrypt_key_management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { decrypt as pbes2Kw } from '../runtime/pbes2kw.js'
import { decrypt as rsaEs } from '../runtime/rsaes.js'
import { decode as base64url } from '../runtime/base64url.js'

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

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

const p2cLimit = options?.maxPBES2Count || 10_000

if (joseHeader.p2c > p2cLimit)
throw new JWEInvalid(`JOSE Header "p2c" (PBES2 Count) out is of acceptable bounds`)

if (typeof joseHeader.p2s !== 'string')
throw new JWEInvalid(`JOSE Header "p2s" (PBES2 Salt) missing or invalid`)

Expand Down
7 changes: 7 additions & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,13 @@ export interface DecryptOptions extends CritOption {
* with compressed plaintext.
*/
inflateRaw?: InflateFunction

/**
* (PBES2 Key Management Algorithms only) Maximum allowed "p2c" (PBES2 Count) Header Parameter
* value. The PBKDF2 iteration count defines the algorithm's computational expense. By default
* this value is set to 10000.
*/
maxPBES2Count?: number
}

/** JWE Deflate option. */
Expand Down
18 changes: 15 additions & 3 deletions test/jwe/flattened.decrypt.test.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import test from 'ava'
import * as crypto from 'crypto'
import { root } from '../dist.mjs'
import { root, conditional } from '../dist.mjs'

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

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

await t.throwsAsync(flattenedDecrypt(jwe, t.context.secret), {
message: 'decryption operation failed',
code: 'ERR_JWE_DECRYPTION_FAILED',
message: 'Encountered unexpected JWE Encrypted Key',
code: 'ERR_JWE_INVALID',
})
}
})
Expand Down Expand Up @@ -239,3 +239,15 @@ test('decrypt empty data (CBC)', async (t) => {
const { plaintext } = await flattenedDecrypt(jwe, new Uint8Array(32))
t.is(plaintext.byteLength, 0)
})

conditional({ electron: 0 })('decrypt PBES2 p2c limit', async (t) => {
const jwe = await new FlattenedEncrypt(new Uint8Array(0))
.setProtectedHeader({ alg: 'PBES2-HS256+A128KW', enc: 'A128CBC-HS256' })
.setKeyManagementParameters({ p2c: 2049 })
.encrypt(new Uint8Array(32))

await t.throwsAsync(flattenedDecrypt(jwe, new Uint8Array(32), { maxPBES2Count: 2048 }), {
message: 'JOSE Header "p2c" (PBES2 Count) out is of acceptable bounds',
code: 'ERR_JWE_INVALID',
})
})

0 comments on commit 03d6d01

Please sign in to comment.