From 53019cd1fa3a4dc265d4868b9c626d4d6c832e86 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 11 Feb 2024 20:45:24 +0100 Subject: [PATCH] fix(types): iv and tag is optional in JSON serializations --- docs/interfaces/types.FlattenedJWE.md | 40 +++++++++++++-------------- docs/interfaces/types.GeneralJWE.md | 40 +++++++++++++-------------- src/jwe/compact/decrypt.ts | 6 ++-- src/jwe/flattened/decrypt.ts | 32 +++++++++++---------- src/runtime/browser/decrypt.ts | 13 +++++++-- src/runtime/interfaces.d.ts | 4 +-- src/runtime/node/decrypt.ts | 13 +++++++-- src/types.d.ts | 4 +-- test/jwe/flattened.decrypt.test.mjs | 20 ++++++++++---- 9 files changed, 99 insertions(+), 73 deletions(-) diff --git a/docs/interfaces/types.FlattenedJWE.md b/docs/interfaces/types.FlattenedJWE.md index 8d257c97bc..413e7150ef 100644 --- a/docs/interfaces/types.FlattenedJWE.md +++ b/docs/interfaces/types.FlattenedJWE.md @@ -13,12 +13,12 @@ Flattened JWE definition. ### Properties - [ciphertext](types.FlattenedJWE.md#ciphertext) -- [iv](types.FlattenedJWE.md#iv) -- [tag](types.FlattenedJWE.md#tag) - [aad](types.FlattenedJWE.md#aad) - [encrypted\_key](types.FlattenedJWE.md#encrypted_key) - [header](types.FlattenedJWE.md#header) +- [iv](types.FlattenedJWE.md#iv) - [protected](types.FlattenedJWE.md#protected) +- [tag](types.FlattenedJWE.md#tag) - [unprotected](types.FlattenedJWE.md#unprotected) ## Properties @@ -31,24 +31,6 @@ The "ciphertext" member MUST be present and contain the value BASE64URL(JWE Ciph ___ -### iv - -• **iv**: `string` - -The "iv" member MUST be present and contain the value BASE64URL(JWE Initialization Vector) when -the JWE Initialization Vector value is non-empty; otherwise, it MUST be absent. - -___ - -### tag - -• **tag**: `string` - -The "tag" member MUST be present and contain the value BASE64URL(JWE Authentication Tag) when -the JWE Authentication Tag value is non-empty; otherwise, it MUST be absent. - -___ - ### aad • `Optional` **aad**: `string` @@ -79,6 +61,15 @@ Parameter values are not integrity protected. ___ +### iv + +• `Optional` **iv**: `string` + +The "iv" member MUST be present and contain the value BASE64URL(JWE Initialization Vector) when +the JWE Initialization Vector value is non-empty; otherwise, it MUST be absent. + +___ + ### protected • `Optional` **protected**: `string` @@ -89,6 +80,15 @@ Header Parameter values are integrity protected. ___ +### tag + +• `Optional` **tag**: `string` + +The "tag" member MUST be present and contain the value BASE64URL(JWE Authentication Tag) when +the JWE Authentication Tag value is non-empty; otherwise, it MUST be absent. + +___ + ### unprotected • `Optional` **unprotected**: [`JWEHeaderParameters`](types.JWEHeaderParameters.md) diff --git a/docs/interfaces/types.GeneralJWE.md b/docs/interfaces/types.GeneralJWE.md index ee0ea6307a..2a79ac5ecd 100644 --- a/docs/interfaces/types.GeneralJWE.md +++ b/docs/interfaces/types.GeneralJWE.md @@ -11,11 +11,11 @@ Support from the community to continue maintaining and improving this module is ### Properties - [ciphertext](types.GeneralJWE.md#ciphertext) -- [iv](types.GeneralJWE.md#iv) - [recipients](types.GeneralJWE.md#recipients) -- [tag](types.GeneralJWE.md#tag) - [aad](types.GeneralJWE.md#aad) +- [iv](types.GeneralJWE.md#iv) - [protected](types.GeneralJWE.md#protected) +- [tag](types.GeneralJWE.md#tag) - [unprotected](types.GeneralJWE.md#unprotected) ## Properties @@ -28,30 +28,12 @@ The "ciphertext" member MUST be present and contain the value BASE64URL(JWE Ciph ___ -### iv - -• **iv**: `string` - -The "iv" member MUST be present and contain the value BASE64URL(JWE Initialization Vector) when -the JWE Initialization Vector value is non-empty; otherwise, it MUST be absent. - -___ - ### recipients • **recipients**: [`Pick`]( https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys )\<[`FlattenedJWE`](types.FlattenedJWE.md), ``"header"`` \| ``"encrypted_key"``\>[] ___ -### tag - -• **tag**: `string` - -The "tag" member MUST be present and contain the value BASE64URL(JWE Authentication Tag) when -the JWE Authentication Tag value is non-empty; otherwise, it MUST be absent. - -___ - ### aad • `Optional` **aad**: `string` @@ -62,6 +44,15 @@ base64url-encoded value to be integrity protected but not encrypted. ___ +### iv + +• `Optional` **iv**: `string` + +The "iv" member MUST be present and contain the value BASE64URL(JWE Initialization Vector) when +the JWE Initialization Vector value is non-empty; otherwise, it MUST be absent. + +___ + ### protected • `Optional` **protected**: `string` @@ -72,6 +63,15 @@ Header Parameter values are integrity protected. ___ +### tag + +• `Optional` **tag**: `string` + +The "tag" member MUST be present and contain the value BASE64URL(JWE Authentication Tag) when +the JWE Authentication Tag value is non-empty; otherwise, it MUST be absent. + +___ + ### unprotected • `Optional` **unprotected**: [`JWEHeaderParameters`](types.JWEHeaderParameters.md) diff --git a/src/jwe/compact/decrypt.ts b/src/jwe/compact/decrypt.ts index e9f689c5d1..98b67fea26 100644 --- a/src/jwe/compact/decrypt.ts +++ b/src/jwe/compact/decrypt.ts @@ -82,9 +82,9 @@ export async function compactDecrypt( const decrypted = await flattenedDecrypt( { ciphertext, - iv: (iv || undefined), - protected: protectedHeader || undefined, - tag: (tag || undefined), + iv: iv || undefined, + protected: protectedHeader, + tag: tag || undefined, encrypted_key: encryptedKey || undefined, }, [1]>key, diff --git a/src/jwe/flattened/decrypt.ts b/src/jwe/flattened/decrypt.ts index e0b3604222..3b8d92027d 100644 --- a/src/jwe/flattened/decrypt.ts +++ b/src/jwe/flattened/decrypt.ts @@ -85,16 +85,16 @@ export async function flattenedDecrypt( throw new JWEInvalid('JOSE Header missing') } - if (typeof jwe.iv !== 'string') { - throw new JWEInvalid('JWE Initialization Vector missing or incorrect type') + if (jwe.iv !== undefined && typeof jwe.iv !== 'string') { + throw new JWEInvalid('JWE Initialization Vector incorrect type') } if (typeof jwe.ciphertext !== 'string') { throw new JWEInvalid('JWE Ciphertext missing or incorrect type') } - if (typeof jwe.tag !== 'string') { - throw new JWEInvalid('JWE Authentication Tag missing or incorrect type') + if (jwe.tag !== undefined && typeof jwe.tag !== 'string') { + throw new JWEInvalid('JWE Authentication Tag incorrect type') } if (jwe.protected !== undefined && typeof jwe.protected !== 'string') { @@ -205,17 +205,21 @@ export async function flattenedDecrypt( cek = generateCek(enc) } - let iv: Uint8Array - let tag: Uint8Array - try { - iv = base64url(jwe.iv) - } catch { - throw new JWEInvalid('Failed to base64url decode the iv') + let iv: Uint8Array | undefined + let tag: Uint8Array | undefined + if (jwe.iv !== undefined) { + try { + iv = base64url(jwe.iv) + } catch { + throw new JWEInvalid('Failed to base64url decode the iv') + } } - try { - tag = base64url(jwe.tag) - } catch { - throw new JWEInvalid('Failed to base64url decode the tag') + if (jwe.tag !== undefined) { + try { + tag = base64url(jwe.tag) + } catch { + throw new JWEInvalid('Failed to base64url decode the tag') + } } const protectedHeader: Uint8Array = encoder.encode(jwe.protected ?? '') diff --git a/src/runtime/browser/decrypt.ts b/src/runtime/browser/decrypt.ts index e2c0a06f42..98a88cc742 100644 --- a/src/runtime/browser/decrypt.ts +++ b/src/runtime/browser/decrypt.ts @@ -4,7 +4,7 @@ import type { DecryptFunction } from '../interfaces.d' import checkIvLength from '../../lib/check_iv_length.js' import checkCekLength from './check_cek_length.js' import timingSafeEqual from './timing_safe_equal.js' -import { JOSENotSupported, JWEDecryptionFailed } from '../../util/errors.js' +import { JOSENotSupported, JWEDecryptionFailed, JWEInvalid } from '../../util/errors.js' import crypto, { isCryptoKey } from './webcrypto.js' import { checkEncCryptoKey } from '../../lib/crypto_key.js' import invalidKeyInput from '../../lib/invalid_key_input.js' @@ -108,14 +108,21 @@ const decrypt: DecryptFunction = async ( enc: string, cek: unknown, ciphertext: Uint8Array, - iv: Uint8Array, - tag: Uint8Array, + iv: Uint8Array | undefined, + tag: Uint8Array | undefined, aad: Uint8Array, ) => { if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) { throw new TypeError(invalidKeyInput(cek, ...types, 'Uint8Array')) } + if (!iv) { + throw new JWEInvalid('JWE Initialization Vector missing') + } + if (!tag) { + throw new JWEInvalid('JWE Authentication Tag missing') + } + checkIvLength(enc, iv) switch (enc) { diff --git a/src/runtime/interfaces.d.ts b/src/runtime/interfaces.d.ts index 8b66e404f0..af2ed154f8 100644 --- a/src/runtime/interfaces.d.ts +++ b/src/runtime/interfaces.d.ts @@ -63,8 +63,8 @@ export interface DecryptFunction { enc: string, cek: unknown, ciphertext: Uint8Array, - iv: Uint8Array, - tag: Uint8Array, + iv: Uint8Array | undefined, + tag: Uint8Array | undefined, additionalData: Uint8Array, ): AsyncOrSync } diff --git a/src/runtime/node/decrypt.ts b/src/runtime/node/decrypt.ts index 99d5a5f4e7..9600104e72 100644 --- a/src/runtime/node/decrypt.ts +++ b/src/runtime/node/decrypt.ts @@ -5,7 +5,7 @@ import type { DecryptFunction } from '../interfaces.d' import checkIvLength from '../../lib/check_iv_length.js' import checkCekLength from './check_cek_length.js' import { concat } from '../../lib/buffer_utils.js' -import { JOSENotSupported, JWEDecryptionFailed } from '../../util/errors.js' +import { JOSENotSupported, JWEDecryptionFailed, JWEInvalid } from '../../util/errors.js' import timingSafeEqual from './timing_safe_equal.js' import cbcTag from './cbc_tag.js' import { isCryptoKey } from './webcrypto.js' @@ -97,8 +97,8 @@ const decrypt: DecryptFunction = ( enc: string, cek: unknown, ciphertext: Uint8Array, - iv: Uint8Array, - tag: Uint8Array, + iv: Uint8Array | undefined, + tag: Uint8Array | undefined, aad: Uint8Array, ) => { let key: KeyObject | Uint8Array @@ -111,6 +111,13 @@ const decrypt: DecryptFunction = ( throw new TypeError(invalidKeyInput(cek, ...types, 'Uint8Array')) } + if (!iv) { + throw new JWEInvalid('JWE Initialization Vector missing') + } + if (!tag) { + throw new JWEInvalid('JWE Authentication Tag missing') + } + checkCekLength(enc, key) checkIvLength(enc, iv) diff --git a/src/types.d.ts b/src/types.d.ts index 7de641fcfc..0d9c832075 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -334,7 +334,7 @@ export interface FlattenedJWE { * The "iv" member MUST be present and contain the value BASE64URL(JWE Initialization Vector) when * the JWE Initialization Vector value is non-empty; otherwise, it MUST be absent. */ - iv: string + iv?: string /** * The "protected" member MUST be present and contain the value BASE64URL(UTF8(JWE Protected @@ -347,7 +347,7 @@ export interface FlattenedJWE { * The "tag" member MUST be present and contain the value BASE64URL(JWE Authentication Tag) when * the JWE Authentication Tag value is non-empty; otherwise, it MUST be absent. */ - tag: string + tag?: string /** * The "unprotected" member MUST be present and contain the value JWE Shared Unprotected Header diff --git a/test/jwe/flattened.decrypt.test.mjs b/test/jwe/flattened.decrypt.test.mjs index 960905b5fb..d2ffe7b08f 100644 --- a/test/jwe/flattened.decrypt.test.mjs +++ b/test/jwe/flattened.decrypt.test.mjs @@ -40,16 +40,20 @@ test('JWE format validation', async (t) => { { const jwe = { ...fullJwe } - jwe.iv = undefined const assertion = { - message: 'JWE Initialization Vector missing or incorrect type', + message: 'JWE Initialization Vector incorrect type', code: 'ERR_JWE_INVALID', } + jwe.iv = 12 await t.throwsAsync(flattenedDecrypt(jwe, t.context.secret), assertion) jwe.iv = null - await t.throwsAsync(flattenedDecrypt(jwe, t.context.secret), assertion) + jwe.iv = undefined + await t.throwsAsync(flattenedDecrypt(jwe, t.context.secret), { + message: 'JWE Initialization Vector missing', + code: 'ERR_JWE_INVALID', + }) } { @@ -68,16 +72,20 @@ test('JWE format validation', async (t) => { { const jwe = { ...fullJwe } - jwe.tag = undefined const assertion = { - message: 'JWE Authentication Tag missing or incorrect type', + message: 'JWE Authentication Tag incorrect type', code: 'ERR_JWE_INVALID', } + jwe.tag = 12 await t.throwsAsync(flattenedDecrypt(jwe, t.context.secret), assertion) jwe.tag = null - await t.throwsAsync(flattenedDecrypt(jwe, t.context.secret), assertion) + jwe.tag = undefined + await t.throwsAsync(flattenedDecrypt(jwe, t.context.secret), { + message: 'JWE Authentication Tag missing', + code: 'ERR_JWE_INVALID', + }) } {