From 24f306e7f33485daaba1e250dfc97b5f621079ad Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 26 Apr 2023 15:39:03 +0200 Subject: [PATCH] fix(types): headers and payloads may only be JSON values and primitives --- docs/modules/types.md | 4 +++ docs/types/types.JsonArray.md | 9 ++++++ docs/types/types.JsonObject.md | 9 ++++++ docs/types/types.JsonPrimitive.md | 9 ++++++ docs/types/types.JsonValue.md | 9 ++++++ src/jwe/flattened/encrypt.ts | 3 +- src/lib/jwt_claims_set.ts | 3 +- src/lib/validate_crit.ts | 3 +- src/runtime/interfaces.d.ts | 6 ++-- src/runtime/node/key_to_jwk.ts | 8 +++--- src/types.d.ts | 13 ++++++--- test/types/index.test-d.ts | 46 ++++++++++++++++++++++++++++++- 12 files changed, 108 insertions(+), 14 deletions(-) create mode 100644 docs/types/types.JsonArray.md create mode 100644 docs/types/types.JsonObject.md create mode 100644 docs/types/types.JsonPrimitive.md create mode 100644 docs/types/types.JsonValue.md diff --git a/docs/modules/types.md b/docs/modules/types.md index 63bc6b0ad4..fe0790e6c1 100644 --- a/docs/modules/types.md +++ b/docs/modules/types.md @@ -10,6 +10,10 @@ Support from the community to continue maintaining and improving this module is ### Type Aliases +- [JsonArray](../types/types.JsonArray.md) +- [JsonObject](../types/types.JsonObject.md) +- [JsonPrimitive](../types/types.JsonPrimitive.md) +- [JsonValue](../types/types.JsonValue.md) - [KeyLike](../types/types.KeyLike.md) ### Interfaces diff --git a/docs/types/types.JsonArray.md b/docs/types/types.JsonArray.md new file mode 100644 index 0000000000..365ddfb32e --- /dev/null +++ b/docs/types/types.JsonArray.md @@ -0,0 +1,9 @@ +# Type alias: JsonArray + +## [💗 Help the project](https://github.com/sponsors/panva) + +Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by [becoming a sponsor](https://github.com/sponsors/panva). + +--- + +Ƭ **JsonArray**: [`JsonValue`](types.JsonValue.md)[] diff --git a/docs/types/types.JsonObject.md b/docs/types/types.JsonObject.md new file mode 100644 index 0000000000..a7c5643cb1 --- /dev/null +++ b/docs/types/types.JsonObject.md @@ -0,0 +1,9 @@ +# Type alias: JsonObject + +## [💗 Help the project](https://github.com/sponsors/panva) + +Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by [becoming a sponsor](https://github.com/sponsors/panva). + +--- + +Ƭ **JsonObject**: { [Key in string]?: JsonValue } diff --git a/docs/types/types.JsonPrimitive.md b/docs/types/types.JsonPrimitive.md new file mode 100644 index 0000000000..28129d930b --- /dev/null +++ b/docs/types/types.JsonPrimitive.md @@ -0,0 +1,9 @@ +# Type alias: JsonPrimitive + +## [💗 Help the project](https://github.com/sponsors/panva) + +Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by [becoming a sponsor](https://github.com/sponsors/panva). + +--- + +Ƭ **JsonPrimitive**: `string` \| `number` \| `boolean` \| ``null`` diff --git a/docs/types/types.JsonValue.md b/docs/types/types.JsonValue.md new file mode 100644 index 0000000000..cf70332084 --- /dev/null +++ b/docs/types/types.JsonValue.md @@ -0,0 +1,9 @@ +# Type alias: JsonValue + +## [💗 Help the project](https://github.com/sponsors/panva) + +Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by [becoming a sponsor](https://github.com/sponsors/panva). + +--- + +Ƭ **JsonValue**: [`JsonPrimitive`](types.JsonPrimitive.md) \| [`JsonObject`](types.JsonObject.md) \| [`JsonArray`](types.JsonArray.md) diff --git a/src/jwe/flattened/encrypt.ts b/src/jwe/flattened/encrypt.ts index 7e8b155c2f..457fa2406b 100644 --- a/src/jwe/flattened/encrypt.ts +++ b/src/jwe/flattened/encrypt.ts @@ -3,6 +3,7 @@ import encrypt from '../../runtime/encrypt.js' import { deflate } from '../../runtime/zlib.js' import type { + JsonValue, KeyLike, FlattenedJWE, JWEHeaderParameters, @@ -225,7 +226,7 @@ export class FlattenedEncrypt { let cek: KeyLike | Uint8Array { - let parameters: { [propName: string]: unknown } | undefined + let parameters: { [parameter: string]: JsonValue | undefined } | undefined ;({ cek, encryptedKey, parameters } = await encryptKeyManagement( alg, enc, diff --git a/src/lib/jwt_claims_set.ts b/src/lib/jwt_claims_set.ts index 205d37b6d4..9f0da0259d 100644 --- a/src/lib/jwt_claims_set.ts +++ b/src/lib/jwt_claims_set.ts @@ -1,4 +1,5 @@ import type { + JsonValue, JWTPayload, JWTClaimVerificationOptions, JWEHeaderParameters, @@ -40,7 +41,7 @@ export default ( throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', 'typ', 'check_failed') } - let payload!: { [propName: string]: unknown } + let payload!: { [propName: string]: JsonValue | undefined } try { payload = JSON.parse(decoder.decode(encodedPayload)) } catch { diff --git a/src/lib/validate_crit.ts b/src/lib/validate_crit.ts index 1467af2f7f..63cf1a1899 100644 --- a/src/lib/validate_crit.ts +++ b/src/lib/validate_crit.ts @@ -1,9 +1,10 @@ +import type { JsonValue } from '../types.d' import { JOSENotSupported, JWEInvalid, JWSInvalid } from '../util/errors.js' interface CritCheckHeader { b64?: boolean crit?: string[] - [propName: string]: unknown + [propName: string]: JsonValue | undefined } function validateCrit( diff --git a/src/runtime/interfaces.d.ts b/src/runtime/interfaces.d.ts index a5462ae650..6cbbe0fd81 100644 --- a/src/runtime/interfaces.d.ts +++ b/src/runtime/interfaces.d.ts @@ -1,4 +1,4 @@ -import type { JWK, KeyLike } from '../types.d' +import type { JWK, KeyLike, JsonValue } from '../types.d' import type { PEMImportOptions } from '../key/import.js' type AsyncOrSync = Promise | T @@ -57,7 +57,9 @@ export interface DecryptFunction { ): AsyncOrSync } export interface FetchFunction { - (url: URL, timeout: number, options?: any): Promise<{ [propName: string]: unknown }> + (url: URL, timeout: number, options?: any): Promise<{ + [parameter: string]: JsonValue | undefined + }> } export interface DigestFunction { (digest: 'sha256' | 'sha384' | 'sha512', data: Uint8Array): AsyncOrSync diff --git a/src/runtime/node/key_to_jwk.ts b/src/runtime/node/key_to_jwk.ts index c0c9e28f04..1feeb4896b 100644 --- a/src/runtime/node/key_to_jwk.ts +++ b/src/runtime/node/key_to_jwk.ts @@ -36,7 +36,7 @@ const keyToJWK: JWKExportFunction = (key: unknown): JWK => { ) { throw new JOSENotSupported('Unsupported key asymmetricKeyType') } - return keyObject.export({ format: 'jwk' }) + return keyObject.export({ format: 'jwk' }) } switch (keyObject.type) { @@ -112,7 +112,7 @@ const keyToJWK: JWKExportFunction = (key: unknown): JWK => { if (der.length < 100) { offset += correction } - return { + return { ...keyToJWK(createPublicKey(keyObject)), d: base64url(der.subarray(offset, offset + len / 2)), } @@ -130,7 +130,7 @@ const keyToJWK: JWKExportFunction = (key: unknown): JWK => { } const der = keyObject.export({ type: 'pkcs8', format: 'der' }) - return { + return { ...keyToJWK(createPublicKey(keyObject)), d: base64url(der.subarray(-32)), } @@ -148,7 +148,7 @@ const keyToJWK: JWKExportFunction = (key: unknown): JWK => { } const der = keyObject.export({ type: 'pkcs8', format: 'der' }) - return { + return { ...keyToJWK(createPublicKey(keyObject)), d: base64url(der.subarray(crv === 'Ed448' ? -57 : -56)), } diff --git a/src/types.d.ts b/src/types.d.ts index ea08fe55d8..e74d0c49d2 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -91,6 +91,11 @@ */ export type KeyLike = { type: string } +type JsonObject = { [Key in string]?: JsonValue } +type JsonArray = JsonValue[] +type JsonPrimitive = string | number | boolean | null +type JsonValue = JsonPrimitive | JsonObject | JsonArray + /** * JSON Web Key ({@link https://www.rfc-editor.org/rfc/rfc7517 JWK}). "RSA", "EC", "OKP", and "oct" * key types are supported. @@ -134,7 +139,7 @@ export interface JWK { /** JWK "x5u" (X.509 URL) Parameter. */ x5u?: string - [propName: string]: unknown + [propName: string]: JsonValue | undefined } /** @@ -258,7 +263,7 @@ export interface JWSHeaderParameters extends JoseHeaderParameters { crit?: string[] /** Any other JWS Header member. */ - [propName: string]: unknown + [propName: string]: JsonValue | undefined } /** Recognized JWE Key Management-related Header Parameters. */ @@ -366,7 +371,7 @@ export interface JWEHeaderParameters extends JoseHeaderParameters { zip?: string /** Any other JWE Header member. */ - [propName: string]: unknown + [propName: string]: JsonValue | undefined } /** Shared Interface with a "crit" property for all sign, verify, encrypt and decrypt operations. */ @@ -540,7 +545,7 @@ export interface JWTPayload { iat?: number /** Any other JWT Claim Set member. */ - [propName: string]: unknown + [propName: string]: JsonValue | undefined } /** diff --git a/test/types/index.test-d.ts b/test/types/index.test-d.ts index a2c8741780..6f0e05e222 100644 --- a/test/types/index.test-d.ts +++ b/test/types/index.test-d.ts @@ -1,5 +1,5 @@ import type { KeyObject } from 'crypto' -import { expectType } from 'tsd' +import { expectError, expectType } from 'tsd' import * as lib from '../../dist/types' @@ -208,3 +208,47 @@ expectType(await lib.createRemoteJWKSet(new URL(''))()) expectType(await lib.EmbeddedJWK()) expectType(await lib.EmbeddedJWK()) expectType(await lib.EmbeddedJWK()) + +{ + const result = await lib.jwtVerify('', new Uint8Array()) + switch (typeof result.payload.unknown) { + case 'bigint': + case 'function': + case 'symbol': + expectType(result.payload.unknown) + } + + switch (typeof result.protectedHeader.unknown) { + case 'bigint': + case 'function': + case 'symbol': + expectType(result.protectedHeader.unknown) + } +} + +{ + const result = await lib.decodeJwt('') + switch (typeof result.unknown) { + case 'bigint': + case 'function': + case 'symbol': + expectType(result.unknown) + } +} + +{ + const result = await lib.decodeProtectedHeader('') + switch (typeof result.unknown) { + case 'bigint': + case 'function': + case 'symbol': + expectType(result.unknown) + } +} + +expectError(new lib.SignJWT({ foo() {} })) +expectError(new lib.SignJWT({}).setProtectedHeader({ foo() {} })) +expectError(new lib.SignJWT({ foo: Symbol() })) +expectError(new lib.SignJWT({}).setProtectedHeader({ foo: Symbol() })) +expectError(new lib.SignJWT({ foo: 0n })) +expectError(new lib.SignJWT({}).setProtectedHeader({ foo: 0n }))