refactor: restructure src/lib and src/runtime now that runtime is fixed

BREAKING CHANGE: createRemoteJWKSet now uses fetch, because of that its Node.js only options.agent property has been removed and new fetch-related options were added
This commit is contained in:
Filip Skokan
2025-02-22 15:16:04 +01:00
parent af6f5722e1
commit eebee55a01
151 changed files with 2569 additions and 3223 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ echo $(electron -i <<< 'process.exit(0)' 2> /dev/null | grep "Using" | awk '{$1=
electron -i <<< 'process.exit(parseInt(process.versions.node, 10))' &> /dev/null
NODE_VERSION=$?
export NODE_OPTIONS='--no-warnings --enable-source-maps'
export NODE_OPTIONS='--no-warnings --enable-source-maps --import=tsx/esm'
if [[ $NODE_VERSION -eq 18 ]]; then
export NODE_OPTIONS+=' --experimental-global-webcrypto'
+4 -88
View File
@@ -13,30 +13,6 @@ jobs:
build:
uses: ./.github/workflows/build.yml
typings:
needs:
- build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup node
id: node
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'npm'
- run: npm clean-install
- name: Load cached dist
uses: actions/cache@v4
id: dist
with:
path: dist
key: ${{ needs.build.outputs.cache-key }}
fail-on-cache-miss: true
- name: Check TypeScript type definitions
run: npm run test:types
node-versions:
uses: panva/.github/.github/workflows/node-versions.yml@main
with:
@@ -45,7 +21,6 @@ jobs:
node:
needs:
- node-versions
- build
strategy:
fail-fast: false
matrix:
@@ -66,19 +41,10 @@ jobs:
cache: 'npm'
check-latest: true
- run: npm clean-install
- name: Load cached dist
uses: actions/cache@v4
id: dist
with:
path: dist
key: ${{ needs.build.outputs.cache-key }}
fail-on-cache-miss: true
- name: Run Test Suite
run: npm run ${{ matrix.suite }}
deno:
needs:
- build
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -86,27 +52,20 @@ jobs:
- uses: denoland/setup-deno@v1
with:
deno-version: latest
- name: Load cached dist
uses: actions/cache@v4
id: dist
with:
path: dist
key: ${{ needs.build.outputs.cache-key }}
fail-on-cache-miss: true
- name: Test Deno Definitions
run: deno check dist/deno/index.ts
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'npm'
- run: npm clean-install
- name: Test Deno Definitions
run: |
npm run build:deno
deno check dist/deno/index.ts
- name: Test Deno
run: npm run tap:deno
bun:
needs:
- build
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -114,13 +73,6 @@ jobs:
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Load cached dist
uses: actions/cache@v4
id: dist
with:
path: dist
key: ${{ needs.build.outputs.cache-key }}
fail-on-cache-miss: true
- name: Setup node
uses: actions/setup-node@v4
with:
@@ -131,8 +83,6 @@ jobs:
run: npm run tap:bun
workerd:
needs:
- build
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -144,19 +94,10 @@ jobs:
cache: 'npm'
- run: npm clean-install
- run: npm install --global workerd
- name: Load cached dist
uses: actions/cache@v4
id: dist
with:
path: dist
key: ${{ needs.build.outputs.cache-key }}
fail-on-cache-miss: true
- name: Run Test Suite
run: npm run tap:workerd
edge-runtime:
needs:
- build
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -167,19 +108,10 @@ jobs:
node-version: lts/*
cache: 'npm'
- run: npm clean-install
- name: Load cached dist
uses: actions/cache@v4
id: dist
with:
path: dist
key: ${{ needs.build.outputs.cache-key }}
fail-on-cache-miss: true
- name: Run Test Suite
run: npm run tap:edge-runtime
browsers:
needs:
- build
runs-on: ${{ matrix.runs-on || 'ubuntu-latest' }}
strategy:
fail-fast: false
@@ -206,21 +138,12 @@ jobs:
run: |
sudo sqlite3 "$HOME/Library/Application Support/com.apple.TCC/TCC.db" "INSERT OR REPLACE INTO access VALUES('kTCCServiceAppleEvents','com.devexpress.testcafe-browser-tools',0,2,3,1,X'fade0c0000000068000000010000000700000007000000080000001443fa4ca5141baeda21aeca1f50894673b440d4690000000800000014f8afcf6e69791b283e55bd0b03e39e422745770e0000000800000014bf4fc1aed64c871a49fc6bc9dd3878ce5d4d17c6',NULL,0,'com.apple.Safari',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e53616661726900000003',NULL,1687952810,NULL,NULL,'UNUSED',1687952810);"
sudo sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db" "INSERT OR REPLACE INTO access VALUES('kTCCServiceScreenCapture','com.devexpress.testcafe-browser-tools',0,2,3,1,X'fade0c0000000068000000010000000700000007000000080000001443fa4ca5141baeda21aeca1f50894673b440d4690000000800000014f8afcf6e69791b283e55bd0b03e39e422745770e0000000800000014bf4fc1aed64c871a49fc6bc9dd3878ce5d4d17c6',NULL,0,'UNUSED',NULL,0,1687952810,NULL,NULL,'UNUSED',1687952810);"
- name: Load cached dist
uses: actions/cache@v4
id: dist
with:
path: dist
key: ${{ needs.build.outputs.cache-key }}
fail-on-cache-miss: true
- name: Run Test Suite
run: npm run tap:browsers
env:
BROWSER: ${{ matrix.browser }}
electron:
needs:
- build
runs-on: macos-latest
steps:
- name: Checkout
@@ -231,13 +154,6 @@ jobs:
node-version: lts/*
cache: 'npm'
- run: npm clean-install
- name: Load cached dist
uses: actions/cache@v4
id: dist
with:
path: dist
key: ${{ needs.build.outputs.cache-key }}
fail-on-cache-miss: true
- name: Install Electron
run: npm install --global electron
- name: Run Test Suite
+2
View File
@@ -124,6 +124,8 @@ tap/*.js
tap/*.js.map
tap/run-*.mjs
tap/run-*.mjs.map
tap/run-*.cjs
tap/run-*.cjs.map
*.bak
*.bun
tap/.workerd.capnp
+1 -1
View File
@@ -2,7 +2,7 @@ echo "Using Node.js $(node --version)"
node -e 'process.exit(parseInt(process.versions.node, 10))' &> /dev/null
NODE_VERSION=$?
export NODE_OPTIONS='--no-warnings --enable-source-maps'
export NODE_OPTIONS='--no-warnings --enable-source-maps --import=tsx/esm'
if [[ $NODE_VERSION -eq 18 ]]; then
export NODE_OPTIONS+=' --experimental-global-webcrypto'
-21
View File
@@ -1,21 +0,0 @@
const files = [
"./test/**/*.test.mjs"
]
const environmentVariables = {}
if ('CITGM' in process.env) {
files.push("!**/remote.test.mjs")
}
if ('electron' in process.versions) {
files.push("!**/rsa-pss.test.mjs")
}
module.exports = {
require: [
"./test/.require.mjs"
],
files,
environmentVariables,
};
+15
View File
@@ -0,0 +1,15 @@
const files = ['test/**/*.test.ts']
if ('CITGM' in process.env) {
files.push("!**/remote.test.ts")
}
export default {
extensions: {
ts: 'module',
mjs: true,
},
files,
workerThreads: false,
nodeArguments: ['--enable-source-maps'],
}
+1152 -1170
View File
File diff suppressed because it is too large Load Diff
+10 -12
View File
@@ -206,19 +206,17 @@
],
"scripts": {
"build": "tsc",
"build-all": "run-s clear build:*",
"build:webapi": "npm run build -- -p ./tsconfig/webapi.json",
"build:webapi-bundle": "esbuild --bundle dist/webapi/index.js --format=esm --target=es2020 --outfile=dist/webapi/index.bundle.js",
"build:webapi-bundle-min": "esbuild --minify --bundle dist/webapi/index.js --format=esm --target=es2020 --outfile=dist/webapi/index.bundle.min.js",
"build:webapi-umd": "rollup dist/webapi/index.bundle.js --format umd --name jose -o dist/webapi/index.umd.js && rollup dist/webapi/index.bundle.min.js --compact --format umd --name jose -o dist/webapi/index.umd.min.js",
"build:deno": "mkdir -p dist/deno && cp -R src/. dist/deno && find dist/deno -name '*.ts' -type f -print0 | xargs -0 sed -i.bak -e \"s/\\.js'/.ts'/g\" && npm run-script sedcleanup",
"build:types": "npm run-script build -- -p ./tsconfig/types.json && cd src && find . -name '*.d.ts' -maxdepth 2 -type f -exec rsync -R \"{}\" ../dist/types \\; && cd .. && run-s -s types:find | xargs -0 sed -i.bak -e \"s/\\.js'/'/g\" -e \"s/\\.d'/'/g\" && npm run-script sedcleanup",
"build-all": "run-s clear build build:*",
"build:bundle": "esbuild --bundle dist/webapi/index.js --format=esm --target=es2020 --outfile=dist/webapi/index.bundle.js",
"build:bundle-min": "esbuild --minify --bundle dist/webapi/index.js --format=esm --target=es2020 --outfile=dist/webapi/index.bundle.min.js",
"build:umd": "rollup dist/webapi/index.bundle.js --format umd --name jose -o dist/webapi/index.umd.js && rollup dist/webapi/index.bundle.min.js --compact --format umd --name jose -o dist/webapi/index.umd.min.js",
"build:deno": "mkdir -p dist/deno && cp -R src/. dist/deno && find dist/deno -name '*.ts' -type f -print0 | xargs -0 sed -i.bak -e \"s/\\.js'/.ts'/g\" && npm run sedcleanup",
"build:types": "npm run build -- -p ./tsconfig/types.json && cd src && find . -name '*.d.ts' -maxdepth 2 -type f -exec rsync -R \"{}\" ../dist/types \\; && cd .. && run-s -s types:find | xargs -0 sed -i.bak -e \"s/\\.js'/'/g\" -e \"s/\\.d.ts'/'/g\" && npm run sedcleanup",
"clear": "rm -Rf dist",
"sedcleanup": "find . -name '*.bak' -type f -print0 | xargs -0 rm -f",
"docs:generate": "typedoc",
"types:find": "find dist/types -name '*.d.ts' -type f -print0",
"test": "ava",
"test:types": "tsd -f test/types",
"test": "bash -c 'source .node_flags.sh && ava'",
"format": "prettier --log-level silent --write ./test ./tap ./src ./tools ./cookbook ./tsconfig",
"tap:browsers": "./tap/.browsers.sh",
"tap:bun": "./tap/.bun.sh",
@@ -235,7 +233,6 @@
"edge-runtime": "^4.0.1",
"esbuild": "^0.25.0",
"glob": "^11.0.1",
"nock": "^13.5.6",
"npm-run-all2": "^7.0.2",
"patch-package": "^8.0.0",
"prettier": "^3.5.1",
@@ -244,10 +241,11 @@
"rollup": "^4.34.8",
"tar": "^7.4.3",
"timekeeper": "^2.3.1",
"tsd": "^0.31.2",
"tsx": "^4.19.2",
"typedoc": "^0.27.2",
"typedoc-plugin-markdown": "^4.3.1",
"typedoc-plugin-mdn-links": "^4.0.3",
"typescript": "^5.7.3"
"typescript": "^5.7.3",
"undici": "^6.21.1"
}
}
+45 -41
View File
@@ -36,7 +36,13 @@ export { EmbeddedJWK } from './jwk/embedded.js'
export { createLocalJWKSet } from './jwks/local.js'
export { createRemoteJWKSet, jwksCache } from './jwks/remote.js'
export type { RemoteJWKSetOptions, JWKSCacheInput, ExportedJWKSCache } from './jwks/remote.js'
export type {
RemoteJWKSetOptions,
JWKSCacheInput,
ExportedJWKSCache,
customFetch,
FetchImplementation,
} from './jwks/remote.js'
export { UnsecuredJWT } from './jwt/unsecured.js'
export type { UnsecuredResult } from './jwt/unsecured.js'
@@ -60,50 +66,48 @@ export type { GenerateSecretOptions } from './key/generate_secret.js'
export * as base64url from './util/base64url.js'
export type {
CryptoKey,
KeyObject,
KeyLike,
JWK,
JWKParameters,
JWK_OKP_Public,
JWK_OKP_Private,
JWK_EC_Public,
JWK_EC_Private,
JWK_RSA_Public,
JWK_RSA_Private,
JWK_oct,
FlattenedJWSInput,
GeneralJWSInput,
FlattenedJWS,
GeneralJWS,
JoseHeaderParameters,
JWSHeaderParameters,
JWEKeyManagementHeaderParameters,
FlattenedJWE,
GeneralJWE,
JWEHeaderParameters,
CritOption,
DecryptOptions,
EncryptOptions,
JWTClaimVerificationOptions,
VerifyOptions,
SignOptions,
JWTPayload,
FlattenedDecryptResult,
GeneralDecryptResult,
CompactDecryptResult,
FlattenedVerifyResult,
GeneralVerifyResult,
CompactVerifyResult,
JWTVerifyResult,
JWTDecryptResult,
ResolvedKey,
CompactJWEHeaderParameters,
CompactJWSHeaderParameters,
JWTHeaderParameters,
JSONWebKeySet,
CryptoRuntime,
CompactVerifyResult,
CritOption,
CryptoKey,
DecryptOptions,
EncryptOptions,
FlattenedDecryptResult,
FlattenedJWE,
FlattenedJWS,
FlattenedJWSInput,
FlattenedVerifyResult,
GeneralDecryptResult,
GeneralJWE,
GeneralJWS,
GeneralJWSInput,
GeneralVerifyResult,
GetKeyFunction,
JoseHeaderParameters,
JSONWebKeySet,
JWEHeaderParameters,
JWEKeyManagementHeaderParameters,
JWK_EC_Private,
JWK_EC_Public,
JWK_oct,
JWK_OKP_Private,
JWK_OKP_Public,
JWK_RSA_Private,
JWK_RSA_Public,
JWK,
JWKParameters,
JWSHeaderParameters,
JWTClaimVerificationOptions,
JWTDecryptResult,
JWTHeaderParameters,
JWTPayload,
JWTVerifyResult,
KeyObject,
ResolvedKey,
SignOptions,
VerifyOptions,
} from './types.d.ts'
export const cryptoRuntime = 'WebCryptoAPI'
+9 -19
View File
@@ -1,24 +1,14 @@
import { flattenedDecrypt } from '../flattened/decrypt.js'
import { JWEInvalid } from '../../util/errors.js'
import { decoder } from '../../lib/buffer_utils.js'
import type {
CryptoKey,
DecryptOptions,
CompactJWEHeaderParameters,
GetKeyFunction,
FlattenedJWE,
CompactDecryptResult,
ResolvedKey,
KeyObject,
JWK,
} from '../../types.d.ts'
import type * as types from '../../types.d.ts'
/**
* Interface for Compact JWE Decryption dynamic key resolution. No token components have been
* verified at the time of this function call.
*/
export interface CompactDecryptGetKey
extends GetKeyFunction<CompactJWEHeaderParameters, FlattenedJWE> {}
extends types.GetKeyFunction<types.CompactJWEHeaderParameters, types.FlattenedJWE> {}
/**
* Decrypts a Compact JWE.
@@ -45,9 +35,9 @@ export interface CompactDecryptGetKey
*/
export async function compactDecrypt(
jwe: string | Uint8Array,
key: CryptoKey | KeyObject | JWK | Uint8Array,
options?: DecryptOptions,
): Promise<CompactDecryptResult>
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.DecryptOptions,
): Promise<types.CompactDecryptResult>
/**
* @param jwe Compact JWE.
* @param getKey Function resolving Private Key or Secret to decrypt the JWE with. See
@@ -57,12 +47,12 @@ export async function compactDecrypt(
export async function compactDecrypt(
jwe: string | Uint8Array,
getKey: CompactDecryptGetKey,
options?: DecryptOptions,
): Promise<CompactDecryptResult & ResolvedKey>
options?: types.DecryptOptions,
): Promise<types.CompactDecryptResult & types.ResolvedKey>
export async function compactDecrypt(
jwe: string | Uint8Array,
key: CryptoKey | KeyObject | JWK | Uint8Array | CompactDecryptGetKey,
options?: DecryptOptions,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array | CompactDecryptGetKey,
options?: types.DecryptOptions,
) {
if (jwe instanceof Uint8Array) {
jwe = decoder.decode(jwe)
+5 -12
View File
@@ -1,12 +1,5 @@
import type * as types from '../../types.d.ts'
import { FlattenedEncrypt } from '../flattened/encrypt.js'
import type {
KeyObject,
CryptoKey,
JWEKeyManagementHeaderParameters,
CompactJWEHeaderParameters,
EncryptOptions,
JWK,
} from '../../types.d.ts'
/**
* The CompactEncrypt class is used to build and encrypt Compact JWE strings.
@@ -67,7 +60,7 @@ export class CompactEncrypt {
*
* @param protectedHeader JWE Protected Header object.
*/
setProtectedHeader(protectedHeader: CompactJWEHeaderParameters): this {
setProtectedHeader(protectedHeader: types.CompactJWEHeaderParameters): this {
this._flattened.setProtectedHeader(protectedHeader)
return this
}
@@ -79,7 +72,7 @@ export class CompactEncrypt {
*
* @param parameters JWE Key Management parameters.
*/
setKeyManagementParameters(parameters: JWEKeyManagementHeaderParameters): this {
setKeyManagementParameters(parameters: types.JWEKeyManagementHeaderParameters): this {
this._flattened.setKeyManagementParameters(parameters)
return this
}
@@ -92,8 +85,8 @@ export class CompactEncrypt {
* @param options JWE Encryption options.
*/
async encrypt(
key: CryptoKey | KeyObject | JWK | Uint8Array,
options?: EncryptOptions,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.EncryptOptions,
): Promise<string> {
const jwe = await this._flattened.encrypt(key, options)
+19 -30
View File
@@ -1,26 +1,15 @@
import { decode as base64url } from '../../runtime/base64url.js'
import decrypt from '../../runtime/decrypt.js'
import type * as types from '../../types.d.ts'
import { decode as base64url } from '../../lib/base64url.js'
import decrypt from '../../lib/decrypt.js'
import { JOSEAlgNotAllowed, JOSENotSupported, JWEInvalid } from '../../util/errors.js'
import isDisjoint from '../../lib/is_disjoint.js'
import isObject from '../../lib/is_object.js'
import decryptKeyManagement from '../../lib/decrypt_key_management.js'
import type {
FlattenedDecryptResult,
CryptoKey,
FlattenedJWE,
JWEHeaderParameters,
DecryptOptions,
GetKeyFunction,
ResolvedKey,
KeyObject,
JWK,
} from '../../types.d.ts'
import { encoder, decoder, concat } from '../../lib/buffer_utils.js'
import generateCek from '../../lib/cek.js'
import validateCrit from '../../lib/validate_crit.js'
import validateAlgorithms from '../../lib/validate_algorithms.js'
import normalizeKey from '../../runtime/normalize_key.js'
import normalizeKey from '../../lib/normalize_key.js'
import checkKeyType from '../../lib/check_key_type.js'
/**
@@ -28,7 +17,7 @@ import checkKeyType from '../../lib/check_key_type.js'
* verified at the time of this function call.
*/
export interface FlattenedDecryptGetKey
extends GetKeyFunction<JWEHeaderParameters | undefined, FlattenedJWE> {}
extends types.GetKeyFunction<types.JWEHeaderParameters | undefined, types.FlattenedJWE> {}
/**
* Decrypts a Flattened JWE.
@@ -64,10 +53,10 @@ export interface FlattenedDecryptGetKey
* @param options JWE Decryption options.
*/
export function flattenedDecrypt(
jwe: FlattenedJWE,
key: CryptoKey | KeyObject | JWK | Uint8Array,
options?: DecryptOptions,
): Promise<FlattenedDecryptResult>
jwe: types.FlattenedJWE,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.DecryptOptions,
): Promise<types.FlattenedDecryptResult>
/**
* @param jwe Flattened JWE.
* @param getKey Function resolving Private Key or Secret to decrypt the JWE with. See
@@ -75,14 +64,14 @@ export function flattenedDecrypt(
* @param options JWE Decryption options.
*/
export function flattenedDecrypt(
jwe: FlattenedJWE,
jwe: types.FlattenedJWE,
getKey: FlattenedDecryptGetKey,
options?: DecryptOptions,
): Promise<FlattenedDecryptResult & ResolvedKey>
options?: types.DecryptOptions,
): Promise<types.FlattenedDecryptResult & types.ResolvedKey>
export async function flattenedDecrypt(
jwe: FlattenedJWE,
key: CryptoKey | KeyObject | JWK | Uint8Array | FlattenedDecryptGetKey,
options?: DecryptOptions,
jwe: types.FlattenedJWE,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array | FlattenedDecryptGetKey,
options?: types.DecryptOptions,
) {
if (!isObject(jwe)) {
throw new JWEInvalid('Flattened JWE must be an object')
@@ -124,7 +113,7 @@ export async function flattenedDecrypt(
throw new JWEInvalid('JWE Per-Recipient Unprotected Header incorrect type')
}
let parsedProt!: JWEHeaderParameters
let parsedProt!: types.JWEHeaderParameters
if (jwe.protected) {
try {
const protectedHeader = base64url(jwe.protected)
@@ -139,7 +128,7 @@ export async function flattenedDecrypt(
)
}
const joseHeader: JWEHeaderParameters = {
const joseHeader: types.JWEHeaderParameters = {
...parsedProt,
...jwe.header,
...jwe.unprotected,
@@ -197,7 +186,7 @@ export async function flattenedDecrypt(
checkKeyType(alg === 'dir' ? enc : alg, key, 'decrypt')
const k = await normalizeKey(key, alg)
let cek: CryptoKey | Uint8Array
let cek: types.CryptoKey | Uint8Array
try {
cek = await decryptKeyManagement(alg, k, encryptedKey, joseHeader, options)
} catch (err) {
@@ -248,7 +237,7 @@ export async function flattenedDecrypt(
}
const plaintext = await decrypt(enc, cek, ciphertext, iv, tag, additionalData)
const result: FlattenedDecryptResult = { plaintext }
const result: types.FlattenedDecryptResult = { plaintext }
if (jwe.protected !== undefined) {
result.protectedHeader = parsedProt
+18 -27
View File
@@ -1,22 +1,13 @@
import { encode as base64url } from '../../runtime/base64url.js'
import { encode as base64url } from '../../lib/base64url.js'
import { unprotected } from '../../lib/private_symbols.js'
import encrypt from '../../runtime/encrypt.js'
import type {
CryptoKey,
FlattenedJWE,
JWEHeaderParameters,
JWEKeyManagementHeaderParameters,
EncryptOptions,
KeyObject,
JWK,
} from '../../types.d.ts'
import encrypt from '../../lib/encrypt.js'
import type * as types from '../../types.d.ts'
import encryptKeyManagement from '../../lib/encrypt_key_management.js'
import { JOSENotSupported, JWEInvalid } from '../../util/errors.js'
import isDisjoint from '../../lib/is_disjoint.js'
import { encoder, decoder, concat } from '../../lib/buffer_utils.js'
import validateCrit from '../../lib/validate_crit.js'
import normalizeKey from '../../runtime/normalize_key.js'
import normalizeKey from '../../lib/normalize_key.js'
import checkKeyType from '../../lib/check_key_type.js'
/**
@@ -41,11 +32,11 @@ import checkKeyType from '../../lib/check_key_type.js'
export class FlattenedEncrypt {
private _plaintext: Uint8Array
private _protectedHeader!: JWEHeaderParameters | undefined
private _protectedHeader!: types.JWEHeaderParameters | undefined
private _sharedUnprotectedHeader!: JWEHeaderParameters | undefined
private _sharedUnprotectedHeader!: types.JWEHeaderParameters | undefined
private _unprotectedHeader!: JWEHeaderParameters | undefined
private _unprotectedHeader!: types.JWEHeaderParameters | undefined
private _aad!: Uint8Array | undefined
@@ -53,7 +44,7 @@ export class FlattenedEncrypt {
private _iv!: Uint8Array | undefined
private _keyManagementParameters!: JWEKeyManagementHeaderParameters
private _keyManagementParameters!: types.JWEKeyManagementHeaderParameters
/** @param plaintext Binary representation of the plaintext to encrypt. */
constructor(plaintext: Uint8Array) {
@@ -71,7 +62,7 @@ export class FlattenedEncrypt {
*
* @param parameters JWE Key Management parameters.
*/
setKeyManagementParameters(parameters: JWEKeyManagementHeaderParameters): this {
setKeyManagementParameters(parameters: types.JWEKeyManagementHeaderParameters): this {
if (this._keyManagementParameters) {
throw new TypeError('setKeyManagementParameters can only be called once')
}
@@ -84,7 +75,7 @@ export class FlattenedEncrypt {
*
* @param protectedHeader JWE Protected Header.
*/
setProtectedHeader(protectedHeader: JWEHeaderParameters): this {
setProtectedHeader(protectedHeader: types.JWEHeaderParameters): this {
if (this._protectedHeader) {
throw new TypeError('setProtectedHeader can only be called once')
}
@@ -97,7 +88,7 @@ export class FlattenedEncrypt {
*
* @param sharedUnprotectedHeader JWE Shared Unprotected Header.
*/
setSharedUnprotectedHeader(sharedUnprotectedHeader: JWEHeaderParameters): this {
setSharedUnprotectedHeader(sharedUnprotectedHeader: types.JWEHeaderParameters): this {
if (this._sharedUnprotectedHeader) {
throw new TypeError('setSharedUnprotectedHeader can only be called once')
}
@@ -110,7 +101,7 @@ export class FlattenedEncrypt {
*
* @param unprotectedHeader JWE Per-Recipient Unprotected Header.
*/
setUnprotectedHeader(unprotectedHeader: JWEHeaderParameters): this {
setUnprotectedHeader(unprotectedHeader: types.JWEHeaderParameters): this {
if (this._unprotectedHeader) {
throw new TypeError('setUnprotectedHeader can only be called once')
}
@@ -170,9 +161,9 @@ export class FlattenedEncrypt {
* @param options JWE Encryption options.
*/
async encrypt(
key: CryptoKey | KeyObject | JWK | Uint8Array,
options?: EncryptOptions,
): Promise<FlattenedJWE> {
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.EncryptOptions,
): Promise<types.FlattenedJWE> {
if (!this._protectedHeader && !this._unprotectedHeader && !this._sharedUnprotectedHeader) {
throw new JWEInvalid(
'either setProtectedHeader, setUnprotectedHeader, or sharedUnprotectedHeader must be called before #encrypt()',
@@ -187,7 +178,7 @@ export class FlattenedEncrypt {
)
}
const joseHeader: JWEHeaderParameters = {
const joseHeader: types.JWEHeaderParameters = {
...this._protectedHeader,
...this._unprotectedHeader,
...this._sharedUnprotectedHeader,
@@ -221,7 +212,7 @@ export class FlattenedEncrypt {
checkKeyType(alg === 'dir' ? enc : alg, key, 'encrypt')
let cek: CryptoKey | Uint8Array
let cek: types.CryptoKey | Uint8Array
{
let parameters: { [propName: string]: unknown } | undefined
const k = await normalizeKey(key, alg)
@@ -272,7 +263,7 @@ export class FlattenedEncrypt {
additionalData,
)
const jwe: FlattenedJWE = {
const jwe: types.FlattenedJWE = {
ciphertext: base64url(ciphertext),
}
+13 -23
View File
@@ -1,24 +1,14 @@
import { flattenedDecrypt } from '../flattened/decrypt.js'
import { JWEDecryptionFailed, JWEInvalid } from '../../util/errors.js'
import type {
CryptoKey,
DecryptOptions,
JWEHeaderParameters,
GetKeyFunction,
FlattenedJWE,
GeneralJWE,
GeneralDecryptResult,
ResolvedKey,
KeyObject,
JWK,
} from '../../types.d.ts'
import type * as types from '../../types.d.ts'
import isObject from '../../lib/is_object.js'
/**
* Interface for General JWE Decryption dynamic key resolution. No token components have been
* verified at the time of this function call.
*/
export interface GeneralDecryptGetKey extends GetKeyFunction<JWEHeaderParameters, FlattenedJWE> {}
export interface GeneralDecryptGetKey
extends types.GetKeyFunction<types.JWEHeaderParameters, types.FlattenedJWE> {}
/**
* Decrypts a General JWE.
@@ -58,10 +48,10 @@ export interface GeneralDecryptGetKey extends GetKeyFunction<JWEHeaderParameters
* @param options JWE Decryption options.
*/
export function generalDecrypt(
jwe: GeneralJWE,
key: CryptoKey | KeyObject | JWK | Uint8Array,
options?: DecryptOptions,
): Promise<GeneralDecryptResult>
jwe: types.GeneralJWE,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.DecryptOptions,
): Promise<types.GeneralDecryptResult>
/**
* @param jwe General JWE.
* @param getKey Function resolving Private Key or Secret to decrypt the JWE with. See
@@ -69,14 +59,14 @@ export function generalDecrypt(
* @param options JWE Decryption options.
*/
export function generalDecrypt(
jwe: GeneralJWE,
jwe: types.GeneralJWE,
getKey: GeneralDecryptGetKey,
options?: DecryptOptions,
): Promise<GeneralDecryptResult & ResolvedKey>
options?: types.DecryptOptions,
): Promise<types.GeneralDecryptResult & types.ResolvedKey>
export async function generalDecrypt(
jwe: GeneralJWE,
key: CryptoKey | KeyObject | JWK | Uint8Array | GeneralDecryptGetKey,
options?: DecryptOptions,
jwe: types.GeneralJWE,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array | GeneralDecryptGetKey,
options?: types.DecryptOptions,
) {
if (!isObject(jwe)) {
throw new JWEInvalid('General JWE must be an object')
+24 -29
View File
@@ -1,21 +1,13 @@
import type * as types from '../../types.d.ts'
import { FlattenedEncrypt } from '../flattened/encrypt.js'
import { unprotected } from '../../lib/private_symbols.js'
import { JOSENotSupported, JWEInvalid } from '../../util/errors.js'
import generateCek from '../../lib/cek.js'
import isDisjoint from '../../lib/is_disjoint.js'
import encryptKeyManagement from '../../lib/encrypt_key_management.js'
import { encode as base64url } from '../../runtime/base64url.js'
import { encode as base64url } from '../../lib/base64url.js'
import validateCrit from '../../lib/validate_crit.js'
import type {
CryptoKey,
GeneralJWE,
JWEHeaderParameters,
CritOption,
KeyObject,
JWK,
} from '../../types.d.ts'
import normalizeKey from '../../runtime/normalize_key.js'
import normalizeKey from '../../lib/normalize_key.js'
import checkKeyType from '../../lib/check_key_type.js'
export interface Recipient {
@@ -24,13 +16,13 @@ export interface Recipient {
*
* @param unprotectedHeader JWE Per-Recipient Unprotected Header.
*/
setUnprotectedHeader(unprotectedHeader: JWEHeaderParameters): Recipient
setUnprotectedHeader(unprotectedHeader: types.JWEHeaderParameters): Recipient
/** A shorthand for calling addRecipient() on the enclosing GeneralEncrypt instance */
addRecipient(...args: Parameters<GeneralEncrypt['addRecipient']>): Recipient
/** A shorthand for calling encrypt() on the enclosing GeneralEncrypt instance */
encrypt(...args: Parameters<GeneralEncrypt['encrypt']>): Promise<GeneralJWE>
encrypt(...args: Parameters<GeneralEncrypt['encrypt']>): Promise<types.GeneralJWE>
/** Returns the enclosing GeneralEncrypt */
done(): GeneralEncrypt
@@ -38,21 +30,21 @@ export interface Recipient {
class IndividualRecipient implements Recipient {
private parent: GeneralEncrypt
unprotectedHeader?: JWEHeaderParameters
key: CryptoKey | KeyObject | JWK | Uint8Array
options: CritOption
unprotectedHeader?: types.JWEHeaderParameters
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array
options: types.CritOption
constructor(
enc: GeneralEncrypt,
key: CryptoKey | KeyObject | JWK | Uint8Array,
options: CritOption,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options: types.CritOption,
) {
this.parent = enc
this.key = key
this.options = options
}
setUnprotectedHeader(unprotectedHeader: JWEHeaderParameters) {
setUnprotectedHeader(unprotectedHeader: types.JWEHeaderParameters) {
if (this.unprotectedHeader) {
throw new TypeError('setUnprotectedHeader can only be called once')
}
@@ -100,9 +92,9 @@ export class GeneralEncrypt {
private _recipients: IndividualRecipient[] = []
private _protectedHeader!: JWEHeaderParameters
private _protectedHeader!: types.JWEHeaderParameters
private _unprotectedHeader!: JWEHeaderParameters
private _unprotectedHeader!: types.JWEHeaderParameters
private _aad!: Uint8Array
@@ -118,7 +110,10 @@ export class GeneralEncrypt {
* See {@link https://github.com/panva/jose/issues/210#jwe-alg Algorithm Key Requirements}.
* @param options JWE Encryption options.
*/
addRecipient(key: CryptoKey | KeyObject | JWK | Uint8Array, options?: CritOption): Recipient {
addRecipient(
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.CritOption,
): Recipient {
const recipient = new IndividualRecipient(this, key, { crit: options?.crit })
this._recipients.push(recipient)
return recipient
@@ -129,7 +124,7 @@ export class GeneralEncrypt {
*
* @param protectedHeader JWE Protected Header object.
*/
setProtectedHeader(protectedHeader: JWEHeaderParameters): this {
setProtectedHeader(protectedHeader: types.JWEHeaderParameters): this {
if (this._protectedHeader) {
throw new TypeError('setProtectedHeader can only be called once')
}
@@ -142,7 +137,7 @@ export class GeneralEncrypt {
*
* @param sharedUnprotectedHeader JWE Shared Unprotected Header object.
*/
setSharedUnprotectedHeader(sharedUnprotectedHeader: JWEHeaderParameters): this {
setSharedUnprotectedHeader(sharedUnprotectedHeader: types.JWEHeaderParameters): this {
if (this._unprotectedHeader) {
throw new TypeError('setSharedUnprotectedHeader can only be called once')
}
@@ -161,7 +156,7 @@ export class GeneralEncrypt {
}
/** Encrypts and resolves the value of the General JWE object. */
async encrypt(): Promise<GeneralJWE> {
async encrypt(): Promise<types.GeneralJWE> {
if (!this._recipients.length) {
throw new JWEInvalid('at least one recipient must be added')
}
@@ -176,7 +171,7 @@ export class GeneralEncrypt {
.setUnprotectedHeader(recipient.unprotectedHeader!)
.encrypt(recipient.key, { ...recipient.options })
const jwe: GeneralJWE = {
const jwe: types.GeneralJWE = {
ciphertext: flattened.ciphertext,
iv: flattened.iv,
recipients: [{}],
@@ -242,7 +237,7 @@ export class GeneralEncrypt {
const cek = generateCek(enc)
const jwe: GeneralJWE = {
const jwe: types.GeneralJWE = {
ciphertext: '',
iv: '',
recipients: [],
@@ -251,7 +246,7 @@ export class GeneralEncrypt {
for (let i = 0; i < this._recipients.length; i++) {
const recipient = this._recipients[i]
const target: Record<string, string | JWEHeaderParameters> = {}
const target: Record<string, string | types.JWEHeaderParameters> = {}
jwe.recipients!.push(target)
const joseHeader = {
@@ -304,6 +299,6 @@ export class GeneralEncrypt {
target.header = { ...recipient.unprotectedHeader, ...parameters }
}
return jwe as GeneralJWE
return jwe as types.GeneralJWE
}
}
+4 -4
View File
@@ -1,4 +1,4 @@
import type { CryptoKey, FlattenedJWSInput, JWSHeaderParameters } from '../types.d.ts'
import type * as types from '../types.d.ts'
import { importJWK } from '../key/import.js'
import isObject from '../lib/is_object.js'
import { JWSInvalid } from '../util/errors.js'
@@ -28,9 +28,9 @@ import { JWSInvalid } from '../util/errors.js'
* ```
*/
export async function EmbeddedJWK(
protectedHeader?: JWSHeaderParameters,
token?: FlattenedJWSInput,
): Promise<CryptoKey> {
protectedHeader?: types.JWSHeaderParameters,
token?: types.FlattenedJWSInput,
): Promise<types.CryptoKey> {
const joseHeader = {
...protectedHeader,
...token?.header,
+6 -6
View File
@@ -1,9 +1,9 @@
import digest from '../runtime/digest.js'
import { encode as base64url } from '../runtime/base64url.js'
import digest from '../lib/digest.js'
import { encode as base64url } from '../lib/base64url.js'
import { JOSENotSupported, JWKInvalid } from '../util/errors.js'
import { encoder } from '../lib/buffer_utils.js'
import type { JWK } from '../types.d.ts'
import type * as types from '../types.d.ts'
import isObject from '../lib/is_object.js'
const check = (value: unknown, description: string) => {
@@ -39,7 +39,7 @@ const check = (value: unknown, description: string) => {
* @see {@link https://www.rfc-editor.org/rfc/rfc7638 RFC7638}
*/
export async function calculateJwkThumbprint(
jwk: JWK,
jwk: types.JWK,
digestAlgorithm?: 'sha256' | 'sha384' | 'sha512',
): Promise<string> {
if (!isObject(jwk)) {
@@ -56,7 +56,7 @@ export async function calculateJwkThumbprint(
throw new TypeError('digestAlgorithm must one of "sha256", "sha384", or "sha512"')
}
let components: JWK
let components: types.JWK
switch (jwk.kty) {
case 'EC':
check(jwk.crv, '"crv" (Curve) Parameter')
@@ -113,7 +113,7 @@ export async function calculateJwkThumbprint(
* @see {@link https://www.rfc-editor.org/rfc/rfc9278 RFC9278}
*/
export async function calculateJwkThumbprintUri(
jwk: JWK,
jwk: types.JWK,
digestAlgorithm?: 'sha256' | 'sha384' | 'sha512',
): Promise<string> {
digestAlgorithm ??= 'sha256'
+19 -24
View File
@@ -1,10 +1,4 @@
import type {
CryptoKey,
JWSHeaderParameters,
JWK,
JSONWebKeySet,
FlattenedJWSInput,
} from '../types.d.ts'
import type * as types from '../types.d.ts'
import { importJWK } from '../key/import.js'
import {
JWKSInvalid,
@@ -29,10 +23,10 @@ function getKtyFromAlg(alg: unknown) {
}
interface Cache {
[alg: string]: CryptoKey
[alg: string]: types.CryptoKey
}
function isJWKSLike(jwks: unknown): jwks is JSONWebKeySet {
function isJWKSLike(jwks: unknown): jwks is types.JSONWebKeySet {
return (
jwks &&
typeof jwks === 'object' &&
@@ -44,13 +38,11 @@ function isJWKSLike(jwks: unknown): jwks is JSONWebKeySet {
}
function isJWKLike(key: unknown) {
return isObject<JWK>(key)
return isObject<types.JWK>(key)
}
function clone<T>(obj: T): T {
// @ts-ignore
if (typeof structuredClone === 'function') {
// @ts-ignore
return structuredClone(obj)
}
@@ -58,22 +50,22 @@ function clone<T>(obj: T): T {
}
class LocalJWKSet {
private _jwks?: JSONWebKeySet
private _jwks?: types.JSONWebKeySet
private _cached: WeakMap<JWK, Cache> = new WeakMap()
private _cached: WeakMap<types.JWK, Cache> = new WeakMap()
constructor(jwks: unknown) {
if (!isJWKSLike(jwks)) {
throw new JWKSInvalid('JSON Web Key Set malformed')
}
this._jwks = clone<JSONWebKeySet>(jwks)
this._jwks = clone<types.JSONWebKeySet>(jwks)
}
async getKey(
protectedHeader?: JWSHeaderParameters,
token?: FlattenedJWSInput,
): Promise<CryptoKey> {
protectedHeader?: types.JWSHeaderParameters,
token?: types.FlattenedJWSInput,
): Promise<types.CryptoKey> {
const { alg, kid } = { ...protectedHeader, ...token?.header }
const kty = getKtyFromAlg(alg)
@@ -154,7 +146,7 @@ class LocalJWKSet {
}
}
async function importWithAlgCache(cache: WeakMap<JWK, Cache>, jwk: JWK, alg: string) {
async function importWithAlgCache(cache: WeakMap<types.JWK, Cache>, jwk: types.JWK, alg: string) {
const cached = cache.get(jwk) || cache.set(jwk, {}).get(jwk)!
if (cached[alg] === undefined) {
const key = await importJWK({ ...jwk, ext: true }, alg)
@@ -252,14 +244,17 @@ async function importWithAlgCache(cache: WeakMap<JWK, Cache>, jwk: JWK, alg: str
* @param jwks JSON Web Key Set formatted object.
*/
export function createLocalJWKSet(
jwks: JSONWebKeySet,
): (protectedHeader?: JWSHeaderParameters, token?: FlattenedJWSInput) => Promise<CryptoKey> {
jwks: types.JSONWebKeySet,
): (
protectedHeader?: types.JWSHeaderParameters,
token?: types.FlattenedJWSInput,
) => Promise<types.CryptoKey> {
const set = new LocalJWKSet(jwks)
const localJWKSet = async (
protectedHeader?: JWSHeaderParameters,
token?: FlattenedJWSInput,
): Promise<CryptoKey> => set.getKey(protectedHeader, token)
protectedHeader?: types.JWSHeaderParameters,
token?: types.FlattenedJWSInput,
): Promise<types.CryptoKey> => set.getKey(protectedHeader, token)
Object.defineProperties(localJWKSet, {
jwks: {
+186 -39
View File
@@ -1,12 +1,5 @@
import fetchJwks from '../runtime/fetch_jwks.js'
import type {
CryptoKey,
JWSHeaderParameters,
FlattenedJWSInput,
JSONWebKeySet,
} from '../types.d.ts'
import { JWKSNoMatchingKey } from '../util/errors.js'
import type * as types from '../types.d.ts'
import { JOSEError, JWKSNoMatchingKey, JWKSTimeout } from '../util/errors.js'
import { createLocalJWKSet } from './local.js'
import isObject from '../lib/is_object.js'
@@ -33,6 +26,160 @@ if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozi
USER_AGENT = `${NAME}/${VERSION}`
}
/**
* When passed to {@link jwks/remote.createRemoteJWKSet createRemoteJWKSet} this allows the resolver
* to make use of advanced fetch configurations, HTTP Proxies, retry on network errors, etc.
*
* @example
*
* Using [sindresorhus/ky](https://github.com/sindresorhus/ky) for retries and its hooks feature for
* logging outgoing requests and their responses.
*
* ```ts
* import ky from 'ky'
*
* let logRequest!: (request: Request) => void
* let logResponse!: (request: Request, response: Response) => void
* let logRetry!: (request: Request, error: Error, retryCount: number) => void
*
* const JWKS = jose.createRemoteJWKSet(url, {
* [jose.customFetch]: (...args) =>
* ky(args[0], {
* ...args[1],
* hooks: {
* beforeRequest: [
* (request) => {
* logRequest(request)
* },
* ],
* beforeRetry: [
* ({ request, error, retryCount }) => {
* logRetry(request, error, retryCount)
* },
* ],
* afterResponse: [
* (request, _, response) => {
* logResponse(request, response)
* },
* ],
* },
* }),
* })
* ```
*
* @example
*
* Using [nodejs/undici](https://github.com/nodejs/undici) to detect and use HTTP proxies.
*
* ```ts
* import * as undici from 'undici'
*
* // see https://undici.nodejs.org/#/docs/api/EnvHttpProxyAgent
* let envHttpProxyAgent = new undici.EnvHttpProxyAgent()
*
* // @ts-ignore
* const JWKS = jose.createRemoteJWKSet(url, {
* [jose.customFetch]: (...args) => {
* // @ts-ignore
* return undici.fetch(args[0], { ...args[1], dispatcher: envHttpProxyAgent }) // prettier-ignore
* },
* })
* ```
*
* @example
*
* Using [nodejs/undici](https://github.com/nodejs/undici) to automatically retry network errors.
*
* ```ts
* import * as undici from 'undici'
*
* // see https://undici.nodejs.org/#/docs/api/RetryAgent
* let retryAgent = new undici.RetryAgent(new undici.Agent(), {
* statusCodes: [],
* errorCodes: [
* 'ECONNRESET',
* 'ECONNREFUSED',
* 'ENOTFOUND',
* 'ENETDOWN',
* 'ENETUNREACH',
* 'EHOSTDOWN',
* 'UND_ERR_SOCKET',
* ],
* })
*
* // @ts-ignore
* const JWKS = jose.createRemoteJWKSet(url, {
* [jose.customFetch]: (...args) => {
* // @ts-ignore
* return undici.fetch(args[0], { ...args[1], dispatcher: retryAgent }) // prettier-ignore
* },
* })
* ```
*
* @example
*
* Using [nodejs/undici](https://github.com/nodejs/undici) to mock responses in tests.
*
* ```ts
* import * as undici from 'undici'
*
* // see https://undici.nodejs.org/#/docs/api/MockAgent
* let mockAgent = new undici.MockAgent()
* mockAgent.disableNetConnect()
*
* // @ts-ignore
* const JWKS = jose.createRemoteJWKSet(url, {
* [jose.customFetch]: (...args) => {
* // @ts-ignore
* return undici.fetch(args[0], { ...args[1], dispatcher: mockAgent }) // prettier-ignore
* },
* })
* ```
*/
export const customFetch: unique symbol = Symbol()
export type FetchImplementation = (
url: string,
options: {
headers: Headers
method: 'GET'
redirect: 'manual'
signal: AbortSignal
},
) => Promise<Response>
async function fetchJwks(
url: string,
options: {
headers: Headers
signal: AbortSignal
[customFetch]?: FetchImplementation
},
) {
const response = await (options?.[customFetch] || fetch)(url, {
method: 'GET',
signal: options.signal,
redirect: 'manual',
headers: options.headers,
}).catch((err) => {
if (err.name === 'TimeoutError') {
throw new JWKSTimeout()
}
throw err
})
if (response.status !== 200) {
throw new JOSEError('Expected 200 OK from the JSON Web Key Set HTTP response')
}
try {
return await response.json()
} catch {
throw new JOSEError('Failed to parse the JSON Web Key Set HTTP response as JSON')
}
}
/**
* DANGER ZONE - This option has security implications that must be understood, assessed for
* applicability, and accepted before use. It is critical that the JSON Web Key Set cache only be
@@ -108,29 +255,18 @@ export interface RemoteJWKSetOptions {
*/
cacheMaxAge?: number | typeof Infinity
/**
* An instance of {@link https://nodejs.org/api/http.html#class-httpagent http.Agent} or
* {@link https://nodejs.org/api/https.html#class-httpsagent https.Agent} to pass to the
* {@link https://nodejs.org/api/http.html#httpgetoptions-callback http.get} or
* {@link https://nodejs.org/api/https.html#httpsgetoptions-callback https.get} method's options.
* Use when behind an http(s) proxy. This is a Node.js runtime specific option, it is ignored when
* used outside of Node.js runtime.
*/
agent?: any
/**
* Headers to be sent with the HTTP request. Default is that `User-Agent: jose/v${version}` header
* is added unless the runtime is a browser in which adding an explicit headers fetch
* configuration would cause an unnecessary CORS preflight request.
*/
/** Headers to be sent with the HTTP request. */
headers?: Record<string, string>
/** See {@link jwksCache}. */
[jwksCache]?: JWKSCacheInput
/** See {@link customFetch}. */
[customFetch]?: FetchImplementation
}
export interface ExportedJWKSCache {
jwks: JSONWebKeySet
jwks: types.JSONWebKeySet
uat: number
}
@@ -147,7 +283,7 @@ function isFreshJwksCache(input: unknown, cacheMaxAge: number): input is Exporte
if (
!('jwks' in input) ||
!isObject<JSONWebKeySet>(input.jwks) ||
!isObject<types.JSONWebKeySet>(input.jwks) ||
!Array.isArray(input.jwks.keys) ||
!Array.prototype.every.call(input.jwks.keys, isObject)
) {
@@ -170,7 +306,7 @@ class RemoteJWKSet {
private _pendingFetch?: Promise<unknown>
private _options: Pick<RemoteJWKSetOptions, 'agent' | 'headers'>
private _options: Pick<RemoteJWKSetOptions, 'headers'>
private _local!: ReturnType<typeof createLocalJWKSet>
@@ -181,7 +317,7 @@ class RemoteJWKSet {
throw new TypeError('url must be an instance of URL')
}
this._url = new URL(url.href)
this._options = { agent: options?.agent, headers: options?.headers }
this._options = { headers: options?.headers }
this._timeoutDuration =
typeof options?.timeoutDuration === 'number' ? options?.timeoutDuration : 5000
this._cooldownDuration =
@@ -210,9 +346,9 @@ class RemoteJWKSet {
}
async getKey(
protectedHeader?: JWSHeaderParameters,
token?: FlattenedJWSInput,
): Promise<CryptoKey> {
protectedHeader?: types.JWSHeaderParameters,
token?: types.FlattenedJWSInput,
): Promise<types.CryptoKey> {
if (!this._local || !this.fresh()) {
await this.reload()
}
@@ -243,12 +379,20 @@ class RemoteJWKSet {
this._options.headers = Object.fromEntries(headers.entries())
}
this._pendingFetch ||= fetchJwks(this._url, this._timeoutDuration, this._options)
if (!headers.has('accept')) {
headers.set('accept', 'application/json')
headers.append('accept', 'application/jwk-set+json')
}
this._pendingFetch ||= fetchJwks(this._url.href, {
headers,
signal: AbortSignal.timeout(this._timeoutDuration),
})
.then((json) => {
this._local = createLocalJWKSet(json as unknown as JSONWebKeySet)
this._local = createLocalJWKSet(json as unknown as types.JSONWebKeySet)
if (this._cache) {
this._cache.uat = Date.now()
this._cache.jwks = json as unknown as JSONWebKeySet
this._cache.jwks = json as unknown as types.JSONWebKeySet
}
this._jwksTimestamp = Date.now()
this._pendingFetch = undefined
@@ -335,7 +479,10 @@ export function createRemoteJWKSet(
url: URL,
options?: RemoteJWKSetOptions,
): {
(protectedHeader?: JWSHeaderParameters, token?: FlattenedJWSInput): Promise<CryptoKey>
(
protectedHeader?: types.JWSHeaderParameters,
token?: types.FlattenedJWSInput,
): Promise<types.CryptoKey>
/** @ignore */
coolingDown: boolean
/** @ignore */
@@ -345,14 +492,14 @@ export function createRemoteJWKSet(
/** @ignore */
reload: () => Promise<void>
/** @ignore */
jwks: () => JSONWebKeySet | undefined
jwks: () => types.JSONWebKeySet | undefined
} {
const set = new RemoteJWKSet(url, options)
const remoteJWKSet = async (
protectedHeader?: JWSHeaderParameters,
token?: FlattenedJWSInput,
): Promise<CryptoKey> => set.getKey(protectedHeader, token)
protectedHeader?: types.JWSHeaderParameters,
token?: types.FlattenedJWSInput,
): Promise<types.CryptoKey> => set.getKey(protectedHeader, token)
Object.defineProperties(remoteJWKSet, {
coolingDown: {
+4 -10
View File
@@ -1,11 +1,5 @@
import type * as types from '../../types.d.ts'
import { FlattenedSign } from '../flattened/sign.js'
import type {
JWK,
CompactJWSHeaderParameters,
CryptoKey,
SignOptions,
KeyObject,
} from '../../types.d.ts'
/**
* The CompactSign class is used to build and sign Compact JWS strings.
@@ -38,7 +32,7 @@ export class CompactSign {
*
* @param protectedHeader JWS Protected Header.
*/
setProtectedHeader(protectedHeader: CompactJWSHeaderParameters): this {
setProtectedHeader(protectedHeader: types.CompactJWSHeaderParameters): this {
this._flattened.setProtectedHeader(protectedHeader)
return this
}
@@ -51,8 +45,8 @@ export class CompactSign {
* @param options JWS Sign options.
*/
async sign(
key: CryptoKey | KeyObject | JWK | Uint8Array,
options?: SignOptions,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.SignOptions,
): Promise<string> {
const jws = await this._flattened.sign(key, options)
+12 -22
View File
@@ -1,17 +1,7 @@
import type * as types from '../../types.d.ts'
import { flattenedVerify } from '../flattened/verify.js'
import { JWSInvalid } from '../../util/errors.js'
import { decoder } from '../../lib/buffer_utils.js'
import type {
JWK,
CompactVerifyResult,
FlattenedJWSInput,
GenericGetKeyFunction,
CompactJWSHeaderParameters,
CryptoKey,
VerifyOptions,
ResolvedKey,
KeyObject,
} from '../../types.d.ts'
/**
* Interface for Compact JWS Verification dynamic key resolution. No token components have been
@@ -20,10 +10,10 @@ import type {
* @see {@link jwks/remote.createRemoteJWKSet createRemoteJWKSet} to verify using a remote JSON Web Key Set.
*/
export interface CompactVerifyGetKey
extends GenericGetKeyFunction<
CompactJWSHeaderParameters,
FlattenedJWSInput,
CryptoKey | KeyObject | JWK | Uint8Array
extends types.GenericGetKeyFunction<
types.CompactJWSHeaderParameters,
types.FlattenedJWSInput,
types.CryptoKey | types.KeyObject | types.JWK | Uint8Array
> {}
/**
@@ -51,9 +41,9 @@ export interface CompactVerifyGetKey
*/
export function compactVerify(
jws: string | Uint8Array,
key: CryptoKey | KeyObject | JWK | Uint8Array,
options?: VerifyOptions,
): Promise<CompactVerifyResult>
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.VerifyOptions,
): Promise<types.CompactVerifyResult>
/**
* @param jws Compact JWS.
* @param getKey Function resolving a key to verify the JWS with. See
@@ -63,12 +53,12 @@ export function compactVerify(
export function compactVerify(
jws: string | Uint8Array,
getKey: CompactVerifyGetKey,
options?: VerifyOptions,
): Promise<CompactVerifyResult & ResolvedKey>
options?: types.VerifyOptions,
): Promise<types.CompactVerifyResult & types.ResolvedKey>
export async function compactVerify(
jws: string | Uint8Array,
key: CryptoKey | KeyObject | JWK | Uint8Array | CompactVerifyGetKey,
options?: VerifyOptions,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array | CompactVerifyGetKey,
options?: types.VerifyOptions,
) {
if (jws instanceof Uint8Array) {
jws = decoder.decode(jws)
+13 -20
View File
@@ -1,20 +1,13 @@
import { encode as base64url } from '../../runtime/base64url.js'
import sign from '../../runtime/sign.js'
import type * as types from '../../types.d.ts'
import { encode as base64url } from '../../lib/base64url.js'
import sign from '../../lib/sign.js'
import isDisjoint from '../../lib/is_disjoint.js'
import { JWSInvalid } from '../../util/errors.js'
import { encoder, decoder, concat } from '../../lib/buffer_utils.js'
import type {
JWK,
CryptoKey,
FlattenedJWS,
JWSHeaderParameters,
SignOptions,
KeyObject,
} from '../../types.d.ts'
import checkKeyType from '../../lib/check_key_type.js'
import validateCrit from '../../lib/validate_crit.js'
import normalizeKey from '../../runtime/normalize_key.js'
import normalizeKey from '../../lib/normalize_key.js'
/**
* The FlattenedSign class is used to build and sign Flattened JWS objects.
@@ -37,9 +30,9 @@ import normalizeKey from '../../runtime/normalize_key.js'
export class FlattenedSign {
private _payload: Uint8Array
private _protectedHeader!: JWSHeaderParameters
private _protectedHeader!: types.JWSHeaderParameters
private _unprotectedHeader!: JWSHeaderParameters
private _unprotectedHeader!: types.JWSHeaderParameters
/** @param payload Binary representation of the payload to sign. */
constructor(payload: Uint8Array) {
@@ -54,7 +47,7 @@ export class FlattenedSign {
*
* @param protectedHeader JWS Protected Header.
*/
setProtectedHeader(protectedHeader: JWSHeaderParameters): this {
setProtectedHeader(protectedHeader: types.JWSHeaderParameters): this {
if (this._protectedHeader) {
throw new TypeError('setProtectedHeader can only be called once')
}
@@ -67,7 +60,7 @@ export class FlattenedSign {
*
* @param unprotectedHeader JWS Unprotected Header.
*/
setUnprotectedHeader(unprotectedHeader: JWSHeaderParameters): this {
setUnprotectedHeader(unprotectedHeader: types.JWSHeaderParameters): this {
if (this._unprotectedHeader) {
throw new TypeError('setUnprotectedHeader can only be called once')
}
@@ -83,9 +76,9 @@ export class FlattenedSign {
* @param options JWS Sign options.
*/
async sign(
key: CryptoKey | KeyObject | JWK | Uint8Array,
options?: SignOptions,
): Promise<FlattenedJWS> {
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.SignOptions,
): Promise<types.FlattenedJWS> {
if (!this._protectedHeader && !this._unprotectedHeader) {
throw new JWSInvalid(
'either setProtectedHeader or setUnprotectedHeader must be called before #sign()',
@@ -98,7 +91,7 @@ export class FlattenedSign {
)
}
const joseHeader: JWSHeaderParameters = {
const joseHeader: types.JWSHeaderParameters = {
...this._protectedHeader,
...this._unprotectedHeader,
}
@@ -146,7 +139,7 @@ export class FlattenedSign {
const k = await normalizeKey(key, alg)
const signature = await sign(alg, k, data)
const jws: FlattenedJWS = {
const jws: types.FlattenedJWS = {
signature: base64url(signature),
payload: '',
}
+21 -32
View File
@@ -1,5 +1,6 @@
import { decode as base64url } from '../../runtime/base64url.js'
import verify from '../../runtime/verify.js'
import type * as types from '../../types.d.ts'
import { decode as base64url } from '../../lib/base64url.js'
import verify from '../../lib/verify.js'
import { JOSEAlgNotAllowed, JWSInvalid, JWSSignatureVerificationFailed } from '../../util/errors.js'
import { concat, encoder, decoder } from '../../lib/buffer_utils.js'
@@ -8,19 +9,7 @@ import isObject from '../../lib/is_object.js'
import checkKeyType from '../../lib/check_key_type.js'
import validateCrit from '../../lib/validate_crit.js'
import validateAlgorithms from '../../lib/validate_algorithms.js'
import normalizeKey from '../../runtime/normalize_key.js'
import type {
JWK,
FlattenedVerifyResult,
CryptoKey,
FlattenedJWSInput,
JWSHeaderParameters,
VerifyOptions,
GenericGetKeyFunction,
ResolvedKey,
KeyObject,
} from '../../types.d.ts'
import normalizeKey from '../../lib/normalize_key.js'
/**
* Interface for Flattened JWS Verification dynamic key resolution. No token components have been
@@ -29,10 +18,10 @@ import type {
* @see {@link jwks/remote.createRemoteJWKSet createRemoteJWKSet} to verify using a remote JSON Web Key Set.
*/
export interface FlattenedVerifyGetKey
extends GenericGetKeyFunction<
JWSHeaderParameters | undefined,
FlattenedJWSInput,
CryptoKey | KeyObject | JWK | Uint8Array
extends types.GenericGetKeyFunction<
types.JWSHeaderParameters | undefined,
types.FlattenedJWSInput,
types.CryptoKey | types.KeyObject | types.JWK | Uint8Array
> {}
/**
@@ -64,10 +53,10 @@ export interface FlattenedVerifyGetKey
* @param options JWS Verify options.
*/
export function flattenedVerify(
jws: FlattenedJWSInput,
key: CryptoKey | KeyObject | JWK | Uint8Array,
options?: VerifyOptions,
): Promise<FlattenedVerifyResult>
jws: types.FlattenedJWSInput,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.VerifyOptions,
): Promise<types.FlattenedVerifyResult>
/**
* @param jws Flattened JWS.
* @param getKey Function resolving a key to verify the JWS with. See
@@ -75,14 +64,14 @@ export function flattenedVerify(
* @param options JWS Verify options.
*/
export function flattenedVerify(
jws: FlattenedJWSInput,
jws: types.FlattenedJWSInput,
getKey: FlattenedVerifyGetKey,
options?: VerifyOptions,
): Promise<FlattenedVerifyResult & ResolvedKey>
options?: types.VerifyOptions,
): Promise<types.FlattenedVerifyResult & types.ResolvedKey>
export async function flattenedVerify(
jws: FlattenedJWSInput,
key: CryptoKey | KeyObject | JWK | Uint8Array | FlattenedVerifyGetKey,
options?: VerifyOptions,
jws: types.FlattenedJWSInput,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array | FlattenedVerifyGetKey,
options?: types.VerifyOptions,
) {
if (!isObject(jws)) {
throw new JWSInvalid('Flattened JWS must be an object')
@@ -108,7 +97,7 @@ export async function flattenedVerify(
throw new JWSInvalid('JWS Unprotected Header incorrect type')
}
let parsedProt: JWSHeaderParameters = {}
let parsedProt: types.JWSHeaderParameters = {}
if (jws.protected) {
try {
const protectedHeader = base64url(jws.protected)
@@ -123,7 +112,7 @@ export async function flattenedVerify(
)
}
const joseHeader: JWSHeaderParameters = {
const joseHeader: types.JWSHeaderParameters = {
...parsedProt,
...jws.header,
}
@@ -206,7 +195,7 @@ export async function flattenedVerify(
payload = jws.payload
}
const result: FlattenedVerifyResult = { payload }
const result: types.FlattenedVerifyResult = { payload }
if (jws.protected !== undefined) {
result.protectedHeader = parsedProt
+18 -23
View File
@@ -1,35 +1,27 @@
import type * as types from '../../types.d.ts'
import { FlattenedSign } from '../flattened/sign.js'
import { JWSInvalid } from '../../util/errors.js'
import type {
JWK,
CryptoKey,
GeneralJWS,
JWSHeaderParameters,
SignOptions,
KeyObject,
} from '../../types.d.ts'
export interface Signature {
/**
* Sets the JWS Protected Header on the Signature object.
*
* @param protectedHeader JWS Protected Header.
*/
setProtectedHeader(protectedHeader: JWSHeaderParameters): Signature
setProtectedHeader(protectedHeader: types.JWSHeaderParameters): Signature
/**
* Sets the JWS Unprotected Header on the Signature object.
*
* @param unprotectedHeader JWS Unprotected Header.
*/
setUnprotectedHeader(unprotectedHeader: JWSHeaderParameters): Signature
setUnprotectedHeader(unprotectedHeader: types.JWSHeaderParameters): Signature
/** A shorthand for calling addSignature() on the enclosing GeneralSign instance */
addSignature(...args: Parameters<GeneralSign['addSignature']>): Signature
/** A shorthand for calling encrypt() on the enclosing GeneralSign instance */
sign(...args: Parameters<GeneralSign['sign']>): Promise<GeneralJWS>
sign(...args: Parameters<GeneralSign['sign']>): Promise<types.GeneralJWS>
/** Returns the enclosing GeneralSign */
done(): GeneralSign
@@ -38,22 +30,22 @@ export interface Signature {
class IndividualSignature implements Signature {
private parent: GeneralSign
protectedHeader?: JWSHeaderParameters
unprotectedHeader?: JWSHeaderParameters
options?: SignOptions
key: CryptoKey | KeyObject | JWK | Uint8Array
protectedHeader?: types.JWSHeaderParameters
unprotectedHeader?: types.JWSHeaderParameters
options?: types.SignOptions
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array
constructor(
sig: GeneralSign,
key: CryptoKey | KeyObject | JWK | Uint8Array,
options?: SignOptions,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.SignOptions,
) {
this.parent = sig
this.key = key
this.options = options
}
setProtectedHeader(protectedHeader: JWSHeaderParameters) {
setProtectedHeader(protectedHeader: types.JWSHeaderParameters) {
if (this.protectedHeader) {
throw new TypeError('setProtectedHeader can only be called once')
}
@@ -61,7 +53,7 @@ class IndividualSignature implements Signature {
return this
}
setUnprotectedHeader(unprotectedHeader: JWSHeaderParameters) {
setUnprotectedHeader(unprotectedHeader: types.JWSHeaderParameters) {
if (this.unprotectedHeader) {
throw new TypeError('setUnprotectedHeader can only be called once')
}
@@ -120,19 +112,22 @@ export class GeneralSign {
* {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
* @param options JWS Sign options.
*/
addSignature(key: CryptoKey | KeyObject | JWK | Uint8Array, options?: SignOptions): Signature {
addSignature(
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.SignOptions,
): Signature {
const signature = new IndividualSignature(this, key, options)
this._signatures.push(signature)
return signature
}
/** Signs and resolves the value of the General JWS object. */
async sign(): Promise<GeneralJWS> {
async sign(): Promise<types.GeneralJWS> {
if (!this._signatures.length) {
throw new JWSInvalid('at least one signature must be added')
}
const jws: GeneralJWS = {
const jws: types.GeneralJWS = {
signatures: [],
payload: '',
}
+15 -26
View File
@@ -1,16 +1,5 @@
import type * as types from '../../types.d.ts'
import { flattenedVerify } from '../flattened/verify.js'
import type {
JWK,
GeneralJWSInput,
GeneralVerifyResult,
FlattenedJWSInput,
GenericGetKeyFunction,
JWSHeaderParameters,
CryptoKey,
VerifyOptions,
ResolvedKey,
KeyObject,
} from '../../types.d.ts'
import { JWSInvalid, JWSSignatureVerificationFailed } from '../../util/errors.js'
import isObject from '../../lib/is_object.js'
@@ -21,10 +10,10 @@ import isObject from '../../lib/is_object.js'
* @see {@link jwks/remote.createRemoteJWKSet createRemoteJWKSet} to verify using a remote JSON Web Key Set.
*/
export interface GeneralVerifyGetKey
extends GenericGetKeyFunction<
JWSHeaderParameters,
FlattenedJWSInput,
CryptoKey | KeyObject | JWK | Uint8Array
extends types.GenericGetKeyFunction<
types.JWSHeaderParameters,
types.FlattenedJWSInput,
types.CryptoKey | types.KeyObject | types.JWK | Uint8Array
> {}
/**
@@ -59,10 +48,10 @@ export interface GeneralVerifyGetKey
* @param options JWS Verify options.
*/
export function generalVerify(
jws: GeneralJWSInput,
key: CryptoKey | KeyObject | JWK | Uint8Array,
options?: VerifyOptions,
): Promise<GeneralVerifyResult>
jws: types.GeneralJWSInput,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.VerifyOptions,
): Promise<types.GeneralVerifyResult>
/**
* @param jws General JWS.
* @param getKey Function resolving a key to verify the JWS with. See
@@ -70,14 +59,14 @@ export function generalVerify(
* @param options JWS Verify options.
*/
export function generalVerify(
jws: GeneralJWSInput,
jws: types.GeneralJWSInput,
getKey: GeneralVerifyGetKey,
options?: VerifyOptions,
): Promise<GeneralVerifyResult & ResolvedKey>
options?: types.VerifyOptions,
): Promise<types.GeneralVerifyResult & types.ResolvedKey>
export async function generalVerify(
jws: GeneralJWSInput,
key: CryptoKey | KeyObject | JWK | Uint8Array | GeneralVerifyGetKey,
options?: VerifyOptions,
jws: types.GeneralJWSInput,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array | GeneralVerifyGetKey,
options?: types.VerifyOptions,
) {
if (!isObject(jws)) {
throw new JWSInvalid('General JWS must be an object')
+11 -21
View File
@@ -1,29 +1,19 @@
import type * as types from '../types.d.ts'
import { compactDecrypt } from '../jwe/compact/decrypt.js'
import type {
JWTPayload,
CryptoKey,
DecryptOptions,
JWTClaimVerificationOptions,
GetKeyFunction,
CompactJWEHeaderParameters,
FlattenedJWE,
JWTDecryptResult,
ResolvedKey,
KeyObject,
JWK,
} from '../types.d.ts'
import jwtPayload from '../lib/jwt_claims_set.js'
import { JWTClaimValidationFailed } from '../util/errors.js'
/** Combination of JWE Decryption options and JWT Claims Set verification options. */
export interface JWTDecryptOptions extends DecryptOptions, JWTClaimVerificationOptions {}
export interface JWTDecryptOptions
extends types.DecryptOptions,
types.JWTClaimVerificationOptions {}
/**
* Interface for JWT Decryption dynamic key resolution. No token components have been verified at
* the time of this function call.
*/
export interface JWTDecryptGetKey
extends GetKeyFunction<CompactJWEHeaderParameters, FlattenedJWE> {}
extends types.GetKeyFunction<types.CompactJWEHeaderParameters, types.FlattenedJWE> {}
/**
* Verifies the JWT format (to be a JWE Compact format), decrypts the ciphertext, validates the JWT
@@ -53,25 +43,25 @@ export interface JWTDecryptGetKey
* {@link https://github.com/panva/jose/issues/210#jwe-alg Algorithm Key Requirements}.
* @param options JWT Decryption and JWT Claims Set validation options.
*/
export async function jwtDecrypt<PayloadType = JWTPayload>(
export async function jwtDecrypt<PayloadType = types.JWTPayload>(
jwt: string | Uint8Array,
key: CryptoKey | KeyObject | JWK | Uint8Array,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: JWTDecryptOptions,
): Promise<JWTDecryptResult<PayloadType>>
): Promise<types.JWTDecryptResult<PayloadType>>
/**
* @param jwt JSON Web Token value (encoded as JWE).
* @param getKey Function resolving Private Key or Secret to decrypt and verify the JWT with. See
* {@link https://github.com/panva/jose/issues/210#jwe-alg Algorithm Key Requirements}.
* @param options JWT Decryption and JWT Claims Set validation options.
*/
export async function jwtDecrypt<PayloadType = JWTPayload>(
export async function jwtDecrypt<PayloadType = types.JWTPayload>(
jwt: string | Uint8Array,
getKey: JWTDecryptGetKey,
options?: JWTDecryptOptions,
): Promise<JWTDecryptResult<PayloadType> & ResolvedKey>
): Promise<types.JWTDecryptResult<PayloadType> & types.ResolvedKey>
export async function jwtDecrypt(
jwt: string | Uint8Array,
key: CryptoKey | KeyObject | JWK | Uint8Array | JWTDecryptGetKey,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array | JWTDecryptGetKey,
options?: JWTDecryptOptions,
) {
const decrypted = await compactDecrypt(jwt, key as Parameters<typeof compactDecrypt>[1], options)
+7 -14
View File
@@ -1,12 +1,5 @@
import type * as types from '../types.d.ts'
import { CompactEncrypt } from '../jwe/compact/encrypt.js'
import type {
EncryptOptions,
CompactJWEHeaderParameters,
JWEKeyManagementHeaderParameters,
CryptoKey,
KeyObject,
JWK,
} from '../types.d.ts'
import { encoder } from '../lib/buffer_utils.js'
import { ProduceJWT } from './produce.js'
@@ -36,9 +29,9 @@ export class EncryptJWT extends ProduceJWT {
private _iv!: Uint8Array
private _keyManagementParameters!: JWEKeyManagementHeaderParameters
private _keyManagementParameters!: types.JWEKeyManagementHeaderParameters
private _protectedHeader!: CompactJWEHeaderParameters
private _protectedHeader!: types.CompactJWEHeaderParameters
private _replicateIssuerAsHeader!: boolean
@@ -52,7 +45,7 @@ export class EncryptJWT extends ProduceJWT {
* @param protectedHeader JWE Protected Header. Must contain an "alg" (JWE Algorithm) and "enc"
* (JWE Encryption Algorithm) properties.
*/
setProtectedHeader(protectedHeader: CompactJWEHeaderParameters): this {
setProtectedHeader(protectedHeader: types.CompactJWEHeaderParameters): this {
if (this._protectedHeader) {
throw new TypeError('setProtectedHeader can only be called once')
}
@@ -68,7 +61,7 @@ export class EncryptJWT extends ProduceJWT {
*
* @param parameters JWE Key Management parameters.
*/
setKeyManagementParameters(parameters: JWEKeyManagementHeaderParameters): this {
setKeyManagementParameters(parameters: types.JWEKeyManagementHeaderParameters): this {
if (this._keyManagementParameters) {
throw new TypeError('setKeyManagementParameters can only be called once')
}
@@ -148,8 +141,8 @@ export class EncryptJWT extends ProduceJWT {
* @param options JWE Encryption options.
*/
async encrypt(
key: CryptoKey | KeyObject | JWK | Uint8Array,
options?: EncryptOptions,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.EncryptOptions,
): Promise<string> {
const enc = new CompactEncrypt(encoder.encode(JSON.stringify(this._payload)))
if (this._replicateIssuerAsHeader) {
+3 -3
View File
@@ -1,4 +1,4 @@
import type { JWTPayload } from '../types.d.ts'
import type * as types from '../types.d.ts'
import epoch from '../lib/epoch.js'
import isObject from '../lib/is_object.js'
import secs from '../lib/secs.js'
@@ -13,10 +13,10 @@ function validateInput(label: string, input: number) {
/** Generic class for JWT producing. */
export class ProduceJWT {
protected _payload!: JWTPayload
protected _payload!: types.JWTPayload
/** @param payload The JWT Claims Set object. Defaults to an empty object. */
constructor(payload: JWTPayload = {}) {
constructor(payload: types.JWTPayload = {}) {
if (!isObject(payload)) {
throw new TypeError('JWT Claims Set MUST be an object')
}
+5 -5
View File
@@ -1,6 +1,6 @@
import { CompactSign } from '../jws/compact/sign.js'
import { JWTInvalid } from '../util/errors.js'
import type { JWK, JWTHeaderParameters, CryptoKey, SignOptions, KeyObject } from '../types.d.ts'
import type * as types from '../types.d.ts'
import { encoder } from '../lib/buffer_utils.js'
import { ProduceJWT } from './produce.js'
@@ -109,14 +109,14 @@ import { ProduceJWT } from './produce.js'
* ```
*/
export class SignJWT extends ProduceJWT {
private _protectedHeader!: JWTHeaderParameters
private _protectedHeader!: types.JWTHeaderParameters
/**
* Sets the JWS Protected Header on the SignJWT object.
*
* @param protectedHeader JWS Protected Header. Must contain an "alg" (JWS Algorithm) property.
*/
setProtectedHeader(protectedHeader: JWTHeaderParameters): this {
setProtectedHeader(protectedHeader: types.JWTHeaderParameters): this {
this._protectedHeader = protectedHeader
return this
}
@@ -129,8 +129,8 @@ export class SignJWT extends ProduceJWT {
* @param options JWT Sign options.
*/
async sign(
key: CryptoKey | KeyObject | JWK | Uint8Array,
options?: SignOptions,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: types.SignOptions,
): Promise<string> {
const sig = new CompactSign(encoder.encode(JSON.stringify(this._payload)))
sig.setProtectedHeader(this._protectedHeader)
+8 -8
View File
@@ -1,14 +1,14 @@
import * as base64url from '../runtime/base64url.js'
import * as base64url from '../lib/base64url.js'
import type { JWSHeaderParameters, JWTClaimVerificationOptions, JWTPayload } from '../types.d.ts'
import type * as types from '../types.d.ts'
import { decoder } from '../lib/buffer_utils.js'
import { JWTInvalid } from '../util/errors.js'
import jwtPayload from '../lib/jwt_claims_set.js'
import { ProduceJWT } from './produce.js'
export interface UnsecuredResult<PayloadType = JWTPayload> {
payload: PayloadType & JWTPayload
header: JWSHeaderParameters
export interface UnsecuredResult<PayloadType = types.JWTPayload> {
payload: PayloadType & types.JWTPayload
header: types.JWSHeaderParameters
}
/**
@@ -60,9 +60,9 @@ export class UnsecuredJWT extends ProduceJWT {
* @param jwt Unsecured JWT to decode the payload of.
* @param options JWT Claims Set validation options.
*/
static decode<PayloadType = JWTPayload>(
static decode<PayloadType = types.JWTPayload>(
jwt: string,
options?: JWTClaimVerificationOptions,
options?: types.JWTClaimVerificationOptions,
): UnsecuredResult<PayloadType> {
if (typeof jwt !== 'string') {
throw new JWTInvalid('Unsecured JWT must be a string')
@@ -73,7 +73,7 @@ export class UnsecuredJWT extends ProduceJWT {
throw new JWTInvalid('Invalid Unsecured JWT')
}
let header: JWSHeaderParameters
let header: types.JWSHeaderParameters
try {
header = JSON.parse(decoder.decode(base64url.decode(encodedHeader)))
if (header.alg !== 'none') throw new Error()
+12 -24
View File
@@ -1,22 +1,10 @@
import { compactVerify } from '../jws/compact/verify.js'
import type {
JWK,
JWTPayload,
CryptoKey,
VerifyOptions,
JWTClaimVerificationOptions,
JWTHeaderParameters,
GenericGetKeyFunction,
FlattenedJWSInput,
JWTVerifyResult,
ResolvedKey,
KeyObject,
} from '../types.d.ts'
import type * as types from '../types.d.ts'
import jwtPayload from '../lib/jwt_claims_set.js'
import { JWTInvalid } from '../util/errors.js'
/** Combination of JWS Verification options and JWT Claims Set verification options. */
export interface JWTVerifyOptions extends VerifyOptions, JWTClaimVerificationOptions {}
export interface JWTVerifyOptions extends types.VerifyOptions, types.JWTClaimVerificationOptions {}
/**
* Interface for JWT Verification dynamic key resolution. No token components have been verified at
@@ -25,10 +13,10 @@ export interface JWTVerifyOptions extends VerifyOptions, JWTClaimVerificationOpt
* @see {@link jwks/remote.createRemoteJWKSet createRemoteJWKSet} to verify using a remote JSON Web Key Set.
*/
export interface JWTVerifyGetKey
extends GenericGetKeyFunction<
JWTHeaderParameters,
FlattenedJWSInput,
CryptoKey | KeyObject | JWK | Uint8Array
extends types.GenericGetKeyFunction<
types.JWTHeaderParameters,
types.FlattenedJWSInput,
types.CryptoKey | types.KeyObject | types.JWK | Uint8Array
> {}
/**
@@ -115,11 +103,11 @@ export interface JWTVerifyGetKey
* {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
* @param options JWT Decryption and JWT Claims Set validation options.
*/
export async function jwtVerify<PayloadType = JWTPayload>(
export async function jwtVerify<PayloadType = types.JWTPayload>(
jwt: string | Uint8Array,
key: CryptoKey | KeyObject | JWK | Uint8Array,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
options?: JWTVerifyOptions,
): Promise<JWTVerifyResult<PayloadType>>
): Promise<types.JWTVerifyResult<PayloadType>>
/**
* @example
@@ -142,15 +130,15 @@ export async function jwtVerify<PayloadType = JWTPayload>(
* {@link https://github.com/panva/jose/issues/210#jws-alg Algorithm Key Requirements}.
* @param options JWT Decryption and JWT Claims Set validation options.
*/
export async function jwtVerify<PayloadType = JWTPayload>(
export async function jwtVerify<PayloadType = types.JWTPayload>(
jwt: string | Uint8Array,
getKey: JWTVerifyGetKey,
options?: JWTVerifyOptions,
): Promise<JWTVerifyResult<PayloadType> & ResolvedKey>
): Promise<types.JWTVerifyResult<PayloadType> & types.ResolvedKey>
export async function jwtVerify(
jwt: string | Uint8Array,
key: CryptoKey | KeyObject | JWK | Uint8Array | JWTVerifyGetKey,
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array | JWTVerifyGetKey,
options?: JWTVerifyOptions,
) {
const verified = await compactVerify(jwt, key as Parameters<typeof compactVerify>[1], options)
+6 -7
View File
@@ -1,8 +1,7 @@
import { toSPKI as exportPublic } from '../runtime/asn1.js'
import { toPKCS8 as exportPrivate } from '../runtime/asn1.js'
import keyToJWK from '../runtime/key_to_jwk.js'
import { toSPKI as exportPublic, toPKCS8 as exportPrivate } from '../lib/asn1.js'
import keyToJWK from '../lib/key_to_jwk.js'
import type { JWK, CryptoKey } from '../types.d.ts'
import type * as types from '../types.d.ts'
/**
* Exports a public {@link !CryptoKey} to a PEM-encoded SPKI string format.
@@ -20,7 +19,7 @@ import type { JWK, CryptoKey } from '../types.d.ts'
*
* @param key Key to export to a PEM-encoded SPKI string format.
*/
export async function exportSPKI(key: CryptoKey): Promise<string> {
export async function exportSPKI(key: types.CryptoKey): Promise<string> {
return exportPublic(key)
}
@@ -40,7 +39,7 @@ export async function exportSPKI(key: CryptoKey): Promise<string> {
*
* @param key Key to export to a PEM-encoded PKCS8 string format.
*/
export async function exportPKCS8(key: CryptoKey): Promise<string> {
export async function exportPKCS8(key: types.CryptoKey): Promise<string> {
return exportPrivate(key)
}
@@ -62,6 +61,6 @@ export async function exportPKCS8(key: CryptoKey): Promise<string> {
*
* @param key Key to export as JWK.
*/
export async function exportJWK(key: CryptoKey | Uint8Array): Promise<JWK> {
export async function exportJWK(key: types.CryptoKey | Uint8Array): Promise<types.JWK> {
return keyToJWK(key)
}
+101 -6
View File
@@ -1,13 +1,13 @@
import { generateKeyPair as generate } from '../runtime/generate.js'
import { JOSENotSupported } from '../util/errors.js'
import type { CryptoKey } from '../types.d.ts'
import type * as types from '../types.d.ts'
export interface GenerateKeyPairResult {
/** The generated Private Key. */
privateKey: CryptoKey
privateKey: types.CryptoKey
/** Public Key corresponding to the generated Private Key. */
publicKey: CryptoKey
publicKey: types.CryptoKey
}
export interface GenerateKeyPairOptions {
@@ -39,6 +39,16 @@ export interface GenerateKeyPairOptions {
extractable?: boolean
}
function getModulusLengthOption(options?: GenerateKeyPairOptions) {
const modulusLength = options?.modulusLength ?? 2048
if (typeof modulusLength !== 'number' || modulusLength < 2048) {
throw new JOSENotSupported(
'Invalid or unsupported modulusLength option provided, 2048 bits or larger keys must be used',
)
}
return modulusLength
}
/**
* Generates a private and a public key for a given JWA algorithm identifier. This can only generate
* asymmetric key pairs. For symmetric secrets use the `generateSecret` function.
@@ -65,6 +75,91 @@ export async function generateKeyPair(
alg: string,
options?: GenerateKeyPairOptions,
): Promise<GenerateKeyPairResult> {
// @ts-ignore
return generate(alg, options)
let algorithm: RsaHashedKeyGenParams | EcKeyGenParams | KeyAlgorithm
let keyUsages: KeyUsage[]
switch (alg) {
case 'PS256':
case 'PS384':
case 'PS512':
algorithm = {
name: 'RSA-PSS',
hash: `SHA-${alg.slice(-3)}`,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
modulusLength: getModulusLengthOption(options),
}
keyUsages = ['sign', 'verify']
break
case 'RS256':
case 'RS384':
case 'RS512':
algorithm = {
name: 'RSASSA-PKCS1-v1_5',
hash: `SHA-${alg.slice(-3)}`,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
modulusLength: getModulusLengthOption(options),
}
keyUsages = ['sign', 'verify']
break
case 'RSA-OAEP':
case 'RSA-OAEP-256':
case 'RSA-OAEP-384':
case 'RSA-OAEP-512':
algorithm = {
name: 'RSA-OAEP',
hash: `SHA-${parseInt(alg.slice(-3), 10) || 1}`,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
modulusLength: getModulusLengthOption(options),
}
keyUsages = ['decrypt', 'unwrapKey', 'encrypt', 'wrapKey']
break
case 'ES256':
algorithm = { name: 'ECDSA', namedCurve: 'P-256' }
keyUsages = ['sign', 'verify']
break
case 'ES384':
algorithm = { name: 'ECDSA', namedCurve: 'P-384' }
keyUsages = ['sign', 'verify']
break
case 'ES512':
algorithm = { name: 'ECDSA', namedCurve: 'P-521' }
keyUsages = ['sign', 'verify']
break
case 'EdDSA': {
keyUsages = ['sign', 'verify']
algorithm = { name: 'Ed25519' }
break
}
case 'ECDH-ES':
case 'ECDH-ES+A128KW':
case 'ECDH-ES+A192KW':
case 'ECDH-ES+A256KW': {
keyUsages = ['deriveBits']
const crv = options?.crv ?? 'P-256'
switch (crv) {
case 'P-256':
case 'P-384':
case 'P-521': {
algorithm = { name: 'ECDH', namedCurve: crv }
break
}
case 'X25519':
algorithm = { name: 'X25519' }
break
default:
throw new JOSENotSupported(
'Invalid or unsupported crv option provided, supported values are P-256, P-384, P-521, and X25519',
)
}
break
}
default:
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value')
}
return crypto.subtle.generateKey(
algorithm,
options?.extractable ?? false,
keyUsages,
) as Promise<GenerateKeyPairResult>
}
+41 -5
View File
@@ -1,6 +1,6 @@
import { generateSecret as generate } from '../runtime/generate.js'
import { JOSENotSupported } from '../util/errors.js'
import type { CryptoKey } from '../types.d.ts'
import type * as types from '../types.d.ts'
export interface GenerateSecretOptions {
/** The value to use as {@link !SubtleCrypto.generateKey} `extractable` argument. Default is false. */
@@ -29,7 +29,43 @@ export interface GenerateSecretOptions {
export async function generateSecret(
alg: string,
options?: GenerateSecretOptions,
): Promise<CryptoKey | Uint8Array> {
// @ts-ignore
return generate(alg, options)
): Promise<types.CryptoKey | Uint8Array> {
let length: number
let algorithm: AesKeyGenParams | HmacKeyGenParams
let keyUsages: KeyUsage[]
switch (alg) {
case 'HS256':
case 'HS384':
case 'HS512':
length = parseInt(alg.slice(-3), 10)
algorithm = { name: 'HMAC', hash: `SHA-${length}`, length }
keyUsages = ['sign', 'verify']
break
case 'A128CBC-HS256':
case 'A192CBC-HS384':
case 'A256CBC-HS512':
length = parseInt(alg.slice(-3), 10)
return crypto.getRandomValues(new Uint8Array(length >> 3))
case 'A128KW':
case 'A192KW':
case 'A256KW':
length = parseInt(alg.slice(1, 4), 10)
algorithm = { name: 'AES-KW', length }
keyUsages = ['wrapKey', 'unwrapKey']
break
case 'A128GCMKW':
case 'A192GCMKW':
case 'A256GCMKW':
case 'A128GCM':
case 'A192GCM':
case 'A256GCM':
length = parseInt(alg.slice(1, 4), 10)
algorithm = { name: 'AES-GCM', length }
keyUsages = ['encrypt', 'decrypt']
break
default:
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value')
}
return crypto.subtle.generateKey(algorithm, options?.extractable ?? false, keyUsages)
}
+11 -12
View File
@@ -1,10 +1,10 @@
import { decode as decodeBase64URL } from '../runtime/base64url.js'
import { fromSPKI, fromPKCS8, fromX509 } from '../runtime/asn1.js'
import asKeyObject from '../runtime/jwk_to_key.js'
import { decode as decodeBase64URL } from '../lib/base64url.js'
import { fromSPKI, fromPKCS8, fromX509 } from '../lib/asn1.js'
import asKeyObject from '../lib/jwk_to_key.js'
import { JOSENotSupported } from '../util/errors.js'
import isObject from '../lib/is_object.js'
import type { JWK, CryptoKey } from '../types.d.ts'
import type * as types from '../types.d.ts'
export interface PEMImportOptions {
/** The value to use as {@link !SubtleCrypto.importKey} `extractable` argument. Default is false. */
@@ -40,11 +40,10 @@ export async function importSPKI(
spki: string,
alg: string,
options?: PEMImportOptions,
): Promise<CryptoKey> {
): Promise<types.CryptoKey> {
if (typeof spki !== 'string' || spki.indexOf('-----BEGIN PUBLIC KEY-----') !== 0) {
throw new TypeError('"spki" must be SPKI formatted string')
}
// @ts-ignore
return fromSPKI(spki, alg, options)
}
@@ -83,11 +82,10 @@ export async function importX509(
x509: string,
alg: string,
options?: PEMImportOptions,
): Promise<CryptoKey> {
): Promise<types.CryptoKey> {
if (typeof x509 !== 'string' || x509.indexOf('-----BEGIN CERTIFICATE-----') !== 0) {
throw new TypeError('"x509" must be X.509 formatted string')
}
// @ts-ignore
return fromX509(x509, alg, options)
}
@@ -121,11 +119,10 @@ export async function importPKCS8(
pkcs8: string,
alg: string,
options?: PEMImportOptions,
): Promise<CryptoKey> {
): Promise<types.CryptoKey> {
if (typeof pkcs8 !== 'string' || pkcs8.indexOf('-----BEGIN PRIVATE KEY-----') !== 0) {
throw new TypeError('"pkcs8" must be PKCS#8 formatted string')
}
// @ts-ignore
return fromPKCS8(pkcs8, alg, options)
}
@@ -167,7 +164,10 @@ export async function importPKCS8(
* property on the JWK. See
* {@link https://github.com/panva/jose/issues/210 Algorithm Key Requirements}.
*/
export async function importJWK(jwk: JWK, alg?: string): Promise<CryptoKey | Uint8Array> {
export async function importJWK(
jwk: types.JWK,
alg?: string,
): Promise<types.CryptoKey | Uint8Array> {
if (!isObject(jwk)) {
throw new TypeError('JWK must be an object')
}
@@ -189,7 +189,6 @@ export async function importJWK(jwk: JWK, alg?: string): Promise<CryptoKey | Uin
}
case 'EC':
case 'OKP':
// @ts-ignore
return asKeyObject({ ...jwk, alg })
default:
throw new JOSENotSupported('Unsupported "kty" (Key Type) Parameter value')
+3 -3
View File
@@ -1,6 +1,6 @@
import encrypt from '../runtime/encrypt.js'
import decrypt from '../runtime/decrypt.js'
import { encode as base64url } from '../runtime/base64url.js'
import encrypt from './encrypt.js'
import decrypt from './decrypt.js'
import { encode as base64url } from '../lib/base64url.js'
export async function wrap(alg: string, key: unknown, cek: Uint8Array, iv?: Uint8Array) {
const jweAlgorithm = alg.slice(0, 7)
+10 -6
View File
@@ -1,13 +1,13 @@
import type { CryptoKey } from '../types.d.ts'
import { checkEncCryptoKey } from '../lib/crypto_key.js'
import type * as types from '../types.d.ts'
import { checkEncCryptoKey } from './crypto_key.js'
function checkKeySize(key: CryptoKey, alg: string) {
function checkKeySize(key: types.CryptoKey, alg: string) {
if ((key.algorithm as AesKeyAlgorithm).length !== parseInt(alg.slice(1, 4), 10)) {
throw new TypeError(`Invalid key size for alg: ${alg}`)
}
}
function getCryptoKey(key: CryptoKey | Uint8Array, alg: string, usage: KeyUsage) {
function getCryptoKey(key: types.CryptoKey | Uint8Array, alg: string, usage: KeyUsage) {
if (key instanceof Uint8Array) {
return crypto.subtle.importKey('raw', key, 'AES-KW', true, [usage])
}
@@ -15,7 +15,7 @@ function getCryptoKey(key: CryptoKey | Uint8Array, alg: string, usage: KeyUsage)
return key
}
export async function wrap(alg: string, key: CryptoKey | Uint8Array, cek: Uint8Array) {
export async function wrap(alg: string, key: types.CryptoKey | Uint8Array, cek: Uint8Array) {
const cryptoKey = await getCryptoKey(key, alg, 'wrapKey')
checkKeySize(cryptoKey, alg)
@@ -32,7 +32,11 @@ export async function wrap(alg: string, key: CryptoKey | Uint8Array, cek: Uint8A
return new Uint8Array(await crypto.subtle.wrapKey('raw', cryptoKeyCek, cryptoKey, 'AES-KW'))
}
export async function unwrap(alg: string, key: CryptoKey | Uint8Array, encryptedKey: Uint8Array) {
export async function unwrap(
alg: string,
key: types.CryptoKey | Uint8Array,
encryptedKey: Uint8Array,
) {
const cryptoKey = await getCryptoKey(key, alg, 'unwrapKey')
checkKeySize(cryptoKey, alg)
+28 -14
View File
@@ -1,12 +1,16 @@
import type { PEMExportFunction, PEMImportFunction } from './interfaces.d.ts'
import invalidKeyInput from '../lib/invalid_key_input.js'
import type * as types from '../types.d.ts'
import invalidKeyInput from './invalid_key_input.js'
import { encodeBase64, decodeBase64 } from './base64url.js'
import formatPEM from '../lib/format_pem.js'
import { JOSENotSupported } from '../util/errors.js'
import { isCryptoKey } from './is_key_like.js'
import type { PEMImportOptions } from '../key/import.js'
const formatPEM = (b64: string, descriptor: string) => {
const newlined = (b64.match(/.{1,64}/g) || []).join('\n')
return `-----BEGIN ${descriptor}-----\n${newlined}\n-----END ${descriptor}-----`
}
const genericExport = async (
keyType: 'private' | 'public',
keyFormat: 'spki' | 'pkcs8',
@@ -30,11 +34,11 @@ const genericExport = async (
)
}
export const toSPKI: PEMExportFunction = (key) => {
export const toSPKI = (key: unknown): Promise<string> => {
return genericExport('public', 'spki', key)
}
export const toPKCS8: PEMExportFunction = (key) => {
export const toPKCS8 = (key: unknown): Promise<string> => {
return genericExport('private', 'pkcs8', key)
}
@@ -50,7 +54,7 @@ const findOid = (keyData: Uint8Array, oid: number[], from = 0): boolean => {
return sub.every((value, index) => value === oid[index]) || findOid(keyData, oid, i + 1)
}
const getNamedCurve = (keyData: Uint8Array): string => {
const getNamedCurve = (keyData: Uint8Array): string | undefined => {
switch (true) {
case findOid(keyData, [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07]):
return 'P-256'
@@ -58,12 +62,8 @@ const getNamedCurve = (keyData: Uint8Array): string => {
return 'P-384'
case findOid(keyData, [0x2b, 0x81, 0x04, 0x00, 0x23]):
return 'P-521'
case findOid(keyData, [0x2b, 0x65, 0x6e]):
return 'X25519'
case findOid(keyData, [0x2b, 0x65, 0x70]):
return 'Ed25519'
default:
throw new JOSENotSupported('Invalid or unsupported EC Key Curve or OKP Key Sub Type')
return undefined
}
}
@@ -125,7 +125,7 @@ const genericImport = async (
case 'ECDH-ES+A192KW':
case 'ECDH-ES+A256KW': {
const namedCurve = getNamedCurve(keyData)
algorithm = namedCurve.startsWith('P-') ? { name: 'ECDH', namedCurve } : { name: namedCurve }
algorithm = namedCurve?.startsWith('P-') ? { name: 'ECDH', namedCurve } : { name: 'X25519' }
keyUsages = isPublic ? [] : ['deriveBits']
break
}
@@ -134,7 +134,7 @@ const genericImport = async (
keyUsages = isPublic ? ['verify'] : ['sign']
break
case 'EdDSA':
algorithm = { name: getNamedCurve(keyData) }
algorithm = { name: 'Ed25519' }
keyUsages = isPublic ? ['verify'] : ['sign']
break
default:
@@ -150,6 +150,12 @@ const genericImport = async (
)
}
type PEMImportFunction = (
pem: string,
alg: string,
options?: PEMImportOptions,
) => Promise<types.CryptoKey>
export const fromPKCS8: PEMImportFunction = (pem, alg, options?) => {
return genericImport(/(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g, 'pkcs8', pem, alg, options)
}
@@ -230,7 +236,16 @@ function spkiFromX509(buf: Uint8Array) {
return encodeBase64(tbsCertificate[tbsCertificate[0].raw[0] === 0xa0 ? 6 : 5].raw)
}
let X509Certificate: any
function getSPKI(x509: string): string {
if (
// @ts-ignore
(X509Certificate ||= globalThis.process?.getBuiltinModule?.('node:crypto')?.X509Certificate)
) {
try {
return new X509Certificate(x509).publicKey.export({ format: 'pem', type: 'spki' })
} catch {}
}
const pem = x509.replace(/(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g, '')
const raw = decodeBase64(pem)
return formatPEM(spkiFromX509(raw), 'PUBLIC KEY')
@@ -241,7 +256,6 @@ export const fromX509: PEMImportFunction = (pem, alg, options?) => {
try {
spki = getSPKI(pem)
} catch (cause) {
// @ts-ignore
throw new TypeError('Failed to parse the X.509 certificate', { cause })
}
return fromSPKI(spki, alg, options)
@@ -1,6 +1,7 @@
import { encoder, decoder } from '../lib/buffer_utils.js'
import { encoder, decoder, Buffer } from '../lib/buffer_utils.js'
export const encodeBase64 = (input: Uint8Array | string) => {
export function encodeBase64(input: Uint8Array | string): string {
if (Buffer) return Buffer.from(input).toString('base64')
let unencoded = input
if (typeof unencoded === 'string') {
unencoded = encoder.encode(unencoded)
@@ -14,11 +15,13 @@ export const encodeBase64 = (input: Uint8Array | string) => {
return btoa(arr.join(''))
}
export const encode = (input: Uint8Array | string) => {
export function encode(input: Uint8Array | string): string {
if (Buffer) return Buffer.from(input).toString('base64url')
return encodeBase64(input).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
}
export const decodeBase64 = (encoded: string): Uint8Array => {
export function decodeBase64(encoded: string): Uint8Array {
if (Buffer) return Buffer.from(encoded, 'base64')
const binary = atob(encoded)
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
@@ -27,11 +30,12 @@ export const decodeBase64 = (encoded: string): Uint8Array => {
return bytes
}
export const decode = (input: Uint8Array | string) => {
export function decode(input: Uint8Array | string): Uint8Array {
let encoded = input
if (encoded instanceof Uint8Array) {
encoded = decoder.decode(encoded)
}
if (Buffer) return Buffer.from(encoded, 'base64url')
encoded = encoded.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, '')
try {
return decodeBase64(encoded)
+6 -23
View File
@@ -1,11 +1,15 @@
import digest from '../runtime/digest.js'
export const encoder = new TextEncoder()
export const decoder = new TextDecoder()
const MAX_INT32 = 2 ** 32
// @ts-expect-error
const Buffer: any = globalThis.process?.getBuiltinModule?.('node:buffer')?.Buffer
export { Buffer }
export function concat(...buffers: Uint8Array[]): Uint8Array {
if (Buffer) return Buffer.concat(buffers)
const size = buffers.reduce((acc, { length }) => acc + length, 0)
const buf = new Uint8Array(size)
let i = 0
@@ -16,10 +20,6 @@ export function concat(...buffers: Uint8Array[]): Uint8Array {
return buf
}
export function p2s(alg: string, p2sInput: Uint8Array) {
return concat(encoder.encode(alg), new Uint8Array([0]), p2sInput)
}
function writeUInt32BE(buf: Uint8Array, value: number, offset?: number) {
if (value < 0 || value >= MAX_INT32) {
throw new RangeError(`value must be >= 0 and <= ${MAX_INT32 - 1}. Received ${value}`)
@@ -41,20 +41,3 @@ export function uint32be(value: number) {
writeUInt32BE(buf, value)
return buf
}
export function lengthAndInput(input: Uint8Array) {
return concat(uint32be(input.length), input)
}
export async function concatKdf(secret: Uint8Array, bits: number, value: Uint8Array) {
const iterations = Math.ceil((bits >> 3) / 32)
const res = new Uint8Array(iterations * 32)
for (let iter = 0; iter < iterations; iter++) {
const buf = new Uint8Array(4 + secret.length + value.length)
buf.set(uint32be(iter + 1))
buf.set(secret, 4)
buf.set(value, 4 + secret.length)
res.set(await digest('sha256', buf), iter * 32)
}
return res.slice(0, bits >> 3)
}
+2 -2
View File
@@ -1,5 +1,4 @@
import { JOSENotSupported } from '../util/errors.js'
import random from '../runtime/random.js'
export function bitLength(alg: string) {
switch (alg) {
@@ -18,4 +17,5 @@ export function bitLength(alg: string) {
throw new JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`)
}
}
export default (alg: string): Uint8Array => random(new Uint8Array(bitLength(alg) >> 3))
export default (alg: string): Uint8Array =>
crypto.getRandomValues(new Uint8Array(bitLength(alg) >> 3))
@@ -1,6 +1,6 @@
import { JWEInvalid } from '../util/errors.js'
const checkCekLength = (cek: Uint8Array, expected: number) => {
export default (cek: Uint8Array, expected: number) => {
const actual = cek.byteLength << 3
if (actual !== expected) {
throw new JWEInvalid(
@@ -8,5 +8,3 @@ const checkCekLength = (cek: Uint8Array, expected: number) => {
)
}
}
export default checkCekLength
+1 -3
View File
@@ -1,10 +1,8 @@
import { JWEInvalid } from '../util/errors.js'
import { bitLength } from './iv.js'
const checkIvLength = (enc: string, iv: Uint8Array) => {
export default (enc: string, iv: Uint8Array) => {
if (iv.length << 3 !== bitLength(enc)) {
throw new JWEInvalid('Invalid Initialization Vector length')
}
}
export default checkIvLength
@@ -1,6 +1,6 @@
import type { CryptoKey } from '../types.d.ts'
import type * as types from '../types.d.ts'
export default (alg: string, key: CryptoKey) => {
export default (alg: string, key: types.CryptoKey) => {
if (alg.startsWith('RS') || alg.startsWith('PS')) {
const { modulusLength } = key.algorithm as RsaKeyAlgorithm
if (typeof modulusLength !== 'number' || modulusLength < 2048) {
+30 -24
View File
@@ -1,12 +1,12 @@
import { withAlg as invalidKeyInput } from './invalid_key_input.js'
import isKeyLike from '../runtime/is_key_like.js'
import isKeyLike from './is_key_like.js'
import * as jwk from './is_jwk.js'
import type { JWK } from '../types.d.ts'
import type * as types from '../types.d.ts'
// @ts-expect-error
const tag = (key: unknown): string => key?.[Symbol.toStringTag]
const jwkMatchesOp = (alg: string, key: JWK, usage: Usage) => {
const jwkMatchesOp = (alg: string, key: types.JWK, usage: Usage) => {
if (key.use !== undefined) {
let expected: string
switch (usage) {
@@ -112,34 +112,40 @@ const asymmetricTypeCheck = (alg: string, key: unknown, usage: Usage) => {
)
}
if (usage === 'sign' && key.type === 'public') {
throw new TypeError(
`${tag(key)} instances for asymmetric algorithm signing must be of type "private"`,
)
if (key.type === 'public') {
switch (usage) {
case 'sign':
throw new TypeError(
`${tag(key)} instances for asymmetric algorithm signing must be of type "private"`,
)
case 'decrypt':
throw new TypeError(
`${tag(key)} instances for asymmetric algorithm decryption must be of type "private"`,
)
default:
break
}
}
if (usage === 'decrypt' && key.type === 'public') {
throw new TypeError(
`${tag(key)} instances for asymmetric algorithm decryption must be of type "private"`,
)
}
if (usage === 'verify' && key.type === 'private') {
throw new TypeError(
`${tag(key)} instances for asymmetric algorithm verifying must be of type "public"`,
)
}
if (usage === 'encrypt' && key.type === 'private') {
throw new TypeError(
`${tag(key)} instances for asymmetric algorithm encryption must be of type "public"`,
)
if (key.type === 'private') {
switch (usage) {
case 'verify':
throw new TypeError(
`${tag(key)} instances for asymmetric algorithm verifying must be of type "public"`,
)
case 'encrypt':
throw new TypeError(
`${tag(key)} instances for asymmetric algorithm encryption must be of type "public"`,
)
default:
break
}
}
}
type Usage = 'sign' | 'verify' | 'encrypt' | 'decrypt'
export default function checkKeyType(alg: string, key: unknown, usage: Usage): void {
export default (alg: string, key: unknown, usage: Usage): void => {
const symmetric =
alg.startsWith('HS') ||
alg === 'dir' ||
-7
View File
@@ -1,7 +0,0 @@
import { JWEInvalid } from '../util/errors.js'
export default function checkP2s(p2s: Uint8Array) {
if (!(p2s instanceof Uint8Array) || p2s.length < 8) {
throw new JWEInvalid('PBES2 Salt Input must be 8 or more octets')
}
}
+4 -4
View File
@@ -1,4 +1,4 @@
import type { CryptoKey } from '../types.d.ts'
import type * as types from '../types.d.ts'
function unusable(name: string | number, prop = 'algorithm.name') {
return new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`)
@@ -28,7 +28,7 @@ function getNamedCurve(alg: string) {
}
}
function checkUsage(key: CryptoKey, usage?: KeyUsage) {
function checkUsage(key: types.CryptoKey, usage?: KeyUsage) {
if (usage && !key.usages.includes(usage)) {
throw new TypeError(
`CryptoKey does not support this operation, its usages must include ${usage}.`,
@@ -36,7 +36,7 @@ function checkUsage(key: CryptoKey, usage?: KeyUsage) {
}
}
export function checkSigCryptoKey(key: CryptoKey, alg: string, usage: KeyUsage) {
export function checkSigCryptoKey(key: types.CryptoKey, alg: string, usage: KeyUsage) {
switch (alg) {
case 'HS256':
case 'HS384':
@@ -90,7 +90,7 @@ export function checkSigCryptoKey(key: CryptoKey, alg: string, usage: KeyUsage)
checkUsage(key, usage)
}
export function checkEncCryptoKey(key: CryptoKey, alg: string, usage?: KeyUsage) {
export function checkEncCryptoKey(key: types.CryptoKey, alg: string, usage?: KeyUsage) {
switch (alg) {
case 'A128GCM':
case 'A192GCM':
+37 -15
View File
@@ -1,18 +1,40 @@
import { concat, uint64be } from '../lib/buffer_utils.js'
import { concat, uint64be } from './buffer_utils.js'
import type { CryptoKey } from '../types.d.ts'
import type { DecryptFunction } from './interfaces.d.ts'
import checkIvLength from '../lib/check_iv_length.js'
import type * as types from '../types.d.ts'
import checkIvLength from './check_iv_length.js'
import checkCekLength from './check_cek_length.js'
import timingSafeEqual from './timing_safe_equal.js'
import { JOSENotSupported, JWEDecryptionFailed, JWEInvalid } from '../util/errors.js'
import { checkEncCryptoKey } from '../lib/crypto_key.js'
import invalidKeyInput from '../lib/invalid_key_input.js'
import { checkEncCryptoKey } from './crypto_key.js'
import invalidKeyInput from './invalid_key_input.js'
import { isCryptoKey } from './is_key_like.js'
let timingSafeEqual: (a: Uint8Array, b: Uint8Array) => boolean =
// @ts-expect-error
globalThis.process?.getBuiltinModule?.('node:crypto')?.timingSafeEqual
timingSafeEqual ||= (a: Uint8Array, b: Uint8Array): boolean => {
if (!(a instanceof Uint8Array)) {
throw new TypeError('First argument must be a buffer')
}
if (!(b instanceof Uint8Array)) {
throw new TypeError('Second argument must be a buffer')
}
if (a.length !== b.length) {
throw new TypeError('Input buffers must have the same length')
}
const len = a.length
let out = 0
let i = -1
while (++i < len) {
out |= a[i] ^ b[i]
}
return out === 0
}
async function cbcDecrypt(
enc: string,
cek: Uint8Array | CryptoKey,
cek: Uint8Array | types.CryptoKey,
ciphertext: Uint8Array,
iv: Uint8Array,
tag: Uint8Array,
@@ -72,13 +94,13 @@ async function cbcDecrypt(
async function gcmDecrypt(
enc: string,
cek: Uint8Array | CryptoKey,
cek: Uint8Array | types.CryptoKey,
ciphertext: Uint8Array,
iv: Uint8Array,
tag: Uint8Array,
aad: Uint8Array,
) {
let encKey: CryptoKey
let encKey: types.CryptoKey
if (cek instanceof Uint8Array) {
encKey = await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, ['decrypt'])
} else {
@@ -104,16 +126,18 @@ async function gcmDecrypt(
}
}
const decrypt: DecryptFunction = async (
export default async (
enc: string,
cek: unknown,
ciphertext: Uint8Array,
iv: Uint8Array | undefined,
tag: Uint8Array | undefined,
aad: Uint8Array,
) => {
): Promise<Uint8Array> => {
if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) {
throw new TypeError(invalidKeyInput(cek, 'CryptoKey', 'KeyObject', 'Uint8Array'))
throw new TypeError(
invalidKeyInput(cek, 'CryptoKey', 'KeyObject', 'Uint8Array', 'JSON Web Key'),
)
}
if (!iv) {
@@ -140,5 +164,3 @@ const decrypt: DecryptFunction = async (
throw new JOSENotSupported('Unsupported JWE Content Encryption Algorithm')
}
}
export default decrypt
+24 -26
View File
@@ -1,24 +1,24 @@
import { unwrap as aesKw } from '../runtime/aeskw.js'
import * as ECDH from '../runtime/ecdhes.js'
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 * as aeskw from './aeskw.js'
import * as ecdhes from './ecdhes.js'
import * as pbes2kw from './pbes2kw.js'
import * as rsaes from './rsaes.js'
import * as base64url from '../lib/base64url.js'
import type { DecryptOptions, JWEHeaderParameters, CryptoKey, JWK } from '../types.d.ts'
import type * as types from '../types.d.ts'
import { JOSENotSupported, JWEInvalid } from '../util/errors.js'
import { bitLength as cekLength } from '../lib/cek.js'
import { importJWK } from '../key/import.js'
import isObject from './is_object.js'
import { unwrap as aesGcmKw } from './aesgcmkw.js'
import { assertCryptoKey } from '../runtime/is_key_like.js'
import { assertCryptoKey } from './is_key_like.js'
async function decryptKeyManagement(
export default async (
alg: string,
key: CryptoKey | Uint8Array,
key: types.CryptoKey | Uint8Array,
encryptedKey: Uint8Array | undefined,
joseHeader: JWEHeaderParameters,
options?: DecryptOptions,
): Promise<CryptoKey | Uint8Array> {
joseHeader: types.JWEHeaderParameters,
options?: types.DecryptOptions,
): Promise<types.CryptoKey | Uint8Array> => {
switch (alg) {
case 'dir': {
// Direct Encryption
@@ -36,11 +36,11 @@ async function decryptKeyManagement(
case 'ECDH-ES+A192KW':
case 'ECDH-ES+A256KW': {
// Direct Key Agreement
if (!isObject<JWK>(joseHeader.epk))
if (!isObject<types.JWK>(joseHeader.epk))
throw new JWEInvalid(`JOSE Header "epk" (Ephemeral Public Key) missing or invalid`)
assertCryptoKey(key)
if (!ECDH.ecdhAllowed(key))
if (!ecdhes.allowed(key))
throw new JOSENotSupported(
'ECDH with the provided key is not allowed or not supported by your javascript runtime',
)
@@ -54,7 +54,7 @@ async function decryptKeyManagement(
if (typeof joseHeader.apu !== 'string')
throw new JWEInvalid(`JOSE Header "apu" (Agreement PartyUInfo) invalid`)
try {
partyUInfo = base64url(joseHeader.apu)
partyUInfo = base64url.decode(joseHeader.apu)
} catch {
throw new JWEInvalid('Failed to base64url decode the apu')
}
@@ -64,13 +64,13 @@ async function decryptKeyManagement(
if (typeof joseHeader.apv !== 'string')
throw new JWEInvalid(`JOSE Header "apv" (Agreement PartyVInfo) invalid`)
try {
partyVInfo = base64url(joseHeader.apv)
partyVInfo = base64url.decode(joseHeader.apv)
} catch {
throw new JWEInvalid('Failed to base64url decode the apv')
}
}
const sharedSecret = await ECDH.deriveKey(
const sharedSecret = await ecdhes.deriveKey(
epk,
key,
alg === 'ECDH-ES' ? joseHeader.enc! : alg,
@@ -84,7 +84,7 @@ async function decryptKeyManagement(
// Key Agreement with Key Wrapping
if (encryptedKey === undefined) throw new JWEInvalid('JWE Encrypted Key missing')
return aesKw(alg.slice(-6), sharedSecret, encryptedKey)
return aeskw.unwrap(alg.slice(-6), sharedSecret, encryptedKey)
}
case 'RSA-OAEP':
case 'RSA-OAEP-256':
@@ -93,7 +93,7 @@ async function decryptKeyManagement(
// Key Encryption (RSA)
if (encryptedKey === undefined) throw new JWEInvalid('JWE Encrypted Key missing')
assertCryptoKey(key)
return rsaEs(alg, key, encryptedKey)
return rsaes.decrypt(alg, key, encryptedKey)
}
case 'PBES2-HS256+A128KW':
case 'PBES2-HS384+A192KW':
@@ -114,11 +114,11 @@ async function decryptKeyManagement(
let p2s: Uint8Array
try {
p2s = base64url(joseHeader.p2s)
p2s = base64url.decode(joseHeader.p2s)
} catch {
throw new JWEInvalid('Failed to base64url decode the p2s')
}
return pbes2Kw(alg, key, encryptedKey, joseHeader.p2c, p2s)
return pbes2kw.unwrap(alg, key, encryptedKey, joseHeader.p2c, p2s)
}
case 'A128KW':
case 'A192KW':
@@ -126,7 +126,7 @@ async function decryptKeyManagement(
// Key Wrapping (AES KW)
if (encryptedKey === undefined) throw new JWEInvalid('JWE Encrypted Key missing')
return aesKw(alg, key, encryptedKey)
return aeskw.unwrap(alg, key, encryptedKey)
}
case 'A128GCMKW':
case 'A192GCMKW':
@@ -142,13 +142,13 @@ async function decryptKeyManagement(
let iv: Uint8Array
try {
iv = base64url(joseHeader.iv)
iv = base64url.decode(joseHeader.iv)
} catch {
throw new JWEInvalid('Failed to base64url decode the iv')
}
let tag: Uint8Array
try {
tag = base64url(joseHeader.tag)
tag = base64url.decode(joseHeader.tag)
} catch {
throw new JWEInvalid('Failed to base64url decode the tag')
}
@@ -160,5 +160,3 @@ async function decryptKeyManagement(
}
}
}
export default decryptKeyManagement
+1 -4
View File
@@ -1,10 +1,7 @@
import type { DigestFunction } from './interfaces.d.ts'
const digest: DigestFunction = async (
export default async (
algorithm: 'sha256' | 'sha384' | 'sha512',
data: Uint8Array,
): Promise<Uint8Array> => {
const subtleDigest = `SHA-${algorithm.slice(-3)}`
return new Uint8Array(await crypto.subtle.digest(subtleDigest, data))
}
export default digest
+72
View File
@@ -0,0 +1,72 @@
import type * as types from '../types.d.ts'
import { encoder, concat, uint32be } from './buffer_utils.js'
import { checkEncCryptoKey } from './crypto_key.js'
import digest from './digest.js'
function lengthAndInput(input: Uint8Array) {
return concat(uint32be(input.length), input)
}
async function concatKdf(secret: Uint8Array, bits: number, value: Uint8Array) {
const iterations = Math.ceil((bits >> 3) / 32)
const res = new Uint8Array(iterations * 32)
for (let iter = 0; iter < iterations; iter++) {
const buf = new Uint8Array(4 + secret.length + value.length)
buf.set(uint32be(iter + 1))
buf.set(secret, 4)
buf.set(value, 4 + secret.length)
res.set(await digest('sha256', buf), iter * 32)
}
return res.slice(0, bits >> 3)
}
export async function deriveKey(
publicKey: types.CryptoKey,
privateKey: types.CryptoKey,
algorithm: string,
keyLength: number,
apu: Uint8Array = new Uint8Array(0),
apv: Uint8Array = new Uint8Array(0),
) {
checkEncCryptoKey(publicKey, 'ECDH')
checkEncCryptoKey(privateKey, 'ECDH', 'deriveBits')
const value = concat(
lengthAndInput(encoder.encode(algorithm)),
lengthAndInput(apu),
lengthAndInput(apv),
uint32be(keyLength),
)
let length: number
if (publicKey.algorithm.name === 'X25519') {
length = 256
} else {
length =
Math.ceil(parseInt((publicKey.algorithm as EcKeyAlgorithm).namedCurve.slice(-3), 10) / 8) << 3
}
const sharedSecret = new Uint8Array(
await crypto.subtle.deriveBits(
{
name: publicKey.algorithm.name,
public: publicKey,
},
privateKey,
length,
),
)
return concatKdf(sharedSecret, keyLength, value)
}
export function allowed(key: types.CryptoKey) {
switch ((key.algorithm as EcKeyAlgorithm).namedCurve) {
case 'P-256':
case 'P-384':
case 'P-521':
return true
default:
return key.algorithm.name === 'X25519'
}
}
+18 -15
View File
@@ -1,18 +1,17 @@
import type { CryptoKey } from '../types.d.ts'
import { concat, uint64be } from '../lib/buffer_utils.js'
import type { EncryptFunction } from './interfaces.d.ts'
import checkIvLength from '../lib/check_iv_length.js'
import type * as types from '../types.d.ts'
import { concat, uint64be } from './buffer_utils.js'
import checkIvLength from './check_iv_length.js'
import checkCekLength from './check_cek_length.js'
import { checkEncCryptoKey } from '../lib/crypto_key.js'
import invalidKeyInput from '../lib/invalid_key_input.js'
import generateIv from '../lib/iv.js'
import { checkEncCryptoKey } from './crypto_key.js'
import invalidKeyInput from './invalid_key_input.js'
import generateIv from './iv.js'
import { JOSENotSupported } from '../util/errors.js'
import { isCryptoKey } from './is_key_like.js'
async function cbcEncrypt(
enc: string,
plaintext: Uint8Array,
cek: Uint8Array | CryptoKey,
cek: Uint8Array | types.CryptoKey,
iv: Uint8Array,
aad: Uint8Array,
) {
@@ -60,11 +59,11 @@ async function cbcEncrypt(
async function gcmEncrypt(
enc: string,
plaintext: Uint8Array,
cek: Uint8Array | CryptoKey,
cek: Uint8Array | types.CryptoKey,
iv: Uint8Array,
aad: Uint8Array,
) {
let encKey: CryptoKey
let encKey: types.CryptoKey
if (cek instanceof Uint8Array) {
encKey = await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, ['encrypt'])
} else {
@@ -91,15 +90,21 @@ async function gcmEncrypt(
return { ciphertext, tag, iv }
}
const encrypt: EncryptFunction = async (
export default async (
enc: string,
plaintext: Uint8Array,
cek: unknown,
iv: Uint8Array | undefined,
aad: Uint8Array,
) => {
): Promise<{
ciphertext: Uint8Array
tag: Uint8Array | undefined
iv: Uint8Array | undefined
}> => {
if (!isCryptoKey(cek) && !(cek instanceof Uint8Array)) {
throw new TypeError(invalidKeyInput(cek, 'CryptoKey', 'KeyObject', 'Uint8Array'))
throw new TypeError(
invalidKeyInput(cek, 'CryptoKey', 'KeyObject', 'Uint8Array', 'JSON Web Key'),
)
}
if (iv) {
@@ -127,5 +132,3 @@ const encrypt: EncryptFunction = async (
throw new JOSENotSupported('Unsupported JWE Content Encryption Algorithm')
}
}
export default encrypt
+27 -31
View File
@@ -1,36 +1,32 @@
import { wrap as aesKw } from '../runtime/aeskw.js'
import * as ECDH from '../runtime/ecdhes.js'
import { encrypt as pbes2Kw } from '../runtime/pbes2kw.js'
import { encrypt as rsaEs } from '../runtime/rsaes.js'
import { encode as base64url } from '../runtime/base64url.js'
import normalizeKey from '../runtime/normalize_key.js'
import type * as types from '../types.d.ts'
import * as aeskw from './aeskw.js'
import * as ecdhes from './ecdhes.js'
import * as pbes2kw from './pbes2kw.js'
import * as rsaes from './rsaes.js'
import * as base64url from '../lib/base64url.js'
import normalizeKey from './normalize_key.js'
import type {
CryptoKey,
JWEKeyManagementHeaderParameters,
JWEHeaderParameters,
JWK,
} from '../types.d.ts'
import type { JWEKeyManagementHeaderParameters, JWEHeaderParameters, JWK } from '../types.d.ts'
import generateCek, { bitLength as cekLength } from '../lib/cek.js'
import { JOSENotSupported } from '../util/errors.js'
import { exportJWK } from '../key/export.js'
import { wrap as aesGcmKw } from './aesgcmkw.js'
import { assertCryptoKey } from '../runtime/is_key_like.js'
import { assertCryptoKey } from './is_key_like.js'
async function encryptKeyManagement(
export default async (
alg: string,
enc: string,
key: CryptoKey | Uint8Array,
key: types.CryptoKey | Uint8Array,
providedCek?: Uint8Array,
providedParameters: JWEKeyManagementHeaderParameters = {},
): Promise<{
cek: CryptoKey | Uint8Array
cek: types.CryptoKey | Uint8Array
encryptedKey?: Uint8Array
parameters?: JWEHeaderParameters
}> {
}> => {
let encryptedKey: Uint8Array | undefined
let parameters: (JWEHeaderParameters & { epk?: JWK }) | undefined
let cek: CryptoKey | Uint8Array
let cek: types.CryptoKey | Uint8Array
switch (alg) {
case 'dir': {
@@ -44,20 +40,22 @@ async function encryptKeyManagement(
case 'ECDH-ES+A256KW': {
assertCryptoKey(key)
// Direct Key Agreement
if (!ECDH.ecdhAllowed(key)) {
if (!ecdhes.allowed(key)) {
throw new JOSENotSupported(
'ECDH with the provided key is not allowed or not supported by your javascript runtime',
)
}
const { apu, apv } = providedParameters
let ephemeralKey: CryptoKey
let ephemeralKey: types.CryptoKey
if (providedParameters.epk) {
ephemeralKey = (await normalizeKey(providedParameters.epk, alg)) as CryptoKey
ephemeralKey = (await normalizeKey(providedParameters.epk, alg)) as types.CryptoKey
} else {
ephemeralKey = (await ECDH.generateEpk(key)).privateKey
ephemeralKey = (
await crypto.subtle.generateKey(key.algorithm as EcKeyAlgorithm, true, ['deriveBits'])
).privateKey
}
const { x, y, crv, kty } = await exportJWK(ephemeralKey!)
const sharedSecret = await ECDH.deriveKey(
const sharedSecret = await ecdhes.deriveKey(
key,
ephemeralKey,
alg === 'ECDH-ES' ? enc : alg,
@@ -67,8 +65,8 @@ async function encryptKeyManagement(
)
parameters = { epk: { x, crv, kty } }
if (kty === 'EC') parameters.epk!.y = y
if (apu) parameters.apu = base64url(apu)
if (apv) parameters.apv = base64url(apv)
if (apu) parameters.apu = base64url.encode(apu)
if (apv) parameters.apv = base64url.encode(apv)
if (alg === 'ECDH-ES') {
cek = sharedSecret
@@ -78,7 +76,7 @@ async function encryptKeyManagement(
// Key Agreement with Key Wrapping
cek = providedCek || generateCek(enc)
const kwAlg = alg.slice(-6)
encryptedKey = await aesKw(kwAlg, sharedSecret, cek)
encryptedKey = await aeskw.wrap(kwAlg, sharedSecret, cek)
break
}
case 'RSA-OAEP':
@@ -88,7 +86,7 @@ async function encryptKeyManagement(
// Key Encryption (RSA)
cek = providedCek || generateCek(enc)
assertCryptoKey(key)
encryptedKey = await rsaEs(alg, key, cek)
encryptedKey = await rsaes.encrypt(alg, key, cek)
break
}
case 'PBES2-HS256+A128KW':
@@ -97,7 +95,7 @@ async function encryptKeyManagement(
// Key Encryption (PBES2)
cek = providedCek || generateCek(enc)
const { p2c, p2s } = providedParameters
;({ encryptedKey, ...parameters } = await pbes2Kw(alg, key, cek, p2c, p2s))
;({ encryptedKey, ...parameters } = await pbes2kw.wrap(alg, key, cek, p2c, p2s))
break
}
case 'A128KW':
@@ -105,7 +103,7 @@ async function encryptKeyManagement(
case 'A256KW': {
// Key Wrapping (AES KW)
cek = providedCek || generateCek(enc)
encryptedKey = await aesKw(alg, key, cek)
encryptedKey = await aeskw.wrap(alg, key, cek)
break
}
case 'A128GCMKW':
@@ -124,5 +122,3 @@ async function encryptKeyManagement(
return { cek, encryptedKey, parameters }
}
export default encryptKeyManagement
-4
View File
@@ -1,4 +0,0 @@
export default (b64: string, descriptor: string) => {
const newlined = (b64.match(/.{1,64}/g) || []).join('\n')
return `-----BEGIN ${descriptor}-----\n${newlined}\n-----END ${descriptor}-----`
}
@@ -1,15 +1,11 @@
import type { CryptoKey } from '../types.d.ts'
import { checkSigCryptoKey } from '../lib/crypto_key.js'
import invalidKeyInput from '../lib/invalid_key_input.js'
import type * as types from '../types.d.ts'
import { checkSigCryptoKey } from './crypto_key.js'
import invalidKeyInput from './invalid_key_input.js'
export default async function getCryptoKey(
alg: string,
key: CryptoKey | Uint8Array,
usage: KeyUsage,
) {
export default async (alg: string, key: types.CryptoKey | Uint8Array, usage: KeyUsage) => {
if (key instanceof Uint8Array) {
if (!alg.startsWith('HS')) {
throw new TypeError(invalidKeyInput(key, 'CryptoKey', 'KeyObject'))
throw new TypeError(invalidKeyInput(key, 'CryptoKey', 'KeyObject', 'JSON Web Key'))
}
return crypto.subtle.importKey(
'raw',
+1 -2
View File
@@ -1,4 +1,4 @@
const isDisjoint = (...headers: Array<object | undefined>) => {
export default (...headers: Array<object | undefined>) => {
const sources = headers.filter(Boolean) as object[]
if (sources.length === 0 || sources.length === 1) {
@@ -23,4 +23,3 @@ const isDisjoint = (...headers: Array<object | undefined>) => {
return true
}
export default isDisjoint
+6 -6
View File
@@ -1,18 +1,18 @@
import type { JWK } from '../types.d.ts'
import type * as types from '../types.d.ts'
import isObject from './is_object.js'
export function isJWK(key: unknown): key is JWK & { kty: string } {
return isObject<JWK>(key) && typeof key.kty === 'string'
export function isJWK(key: unknown): key is types.JWK & { kty: string } {
return isObject<types.JWK>(key) && typeof key.kty === 'string'
}
export function isPrivateJWK(key: JWK & { kty: string }) {
export function isPrivateJWK(key: types.JWK & { kty: string }) {
return key.kty !== 'oct' && typeof key.d === 'string'
}
export function isPublicJWK(key: JWK & { kty: string }) {
export function isPublicJWK(key: types.JWK & { kty: string }) {
return key.kty !== 'oct' && typeof key.d === 'undefined'
}
export function isSecretJWK(key: JWK & { kty: string }) {
export function isSecretJWK(key: types.JWK & { kty: string }) {
return key.kty === 'oct' && typeof key.k === 'string'
}
+21
View File
@@ -0,0 +1,21 @@
import type * as types from '../types.d.ts'
export function assertCryptoKey(key: unknown): asserts key is types.CryptoKey {
if (!isCryptoKey(key)) {
throw new Error('CryptoKey instance expected')
}
}
export function isCryptoKey(key: unknown): key is types.CryptoKey {
// @ts-expect-error
return key?.[Symbol.toStringTag] === 'CryptoKey'
}
export function isKeyObject<T extends types.KeyObject = types.KeyObject>(key: unknown): key is T {
// @ts-expect-error
return key?.[Symbol.toStringTag] === 'KeyObject'
}
export default (key: unknown): key is types.CryptoKey | types.KeyObject => {
return isCryptoKey(key) || isKeyObject(key)
}
+1 -1
View File
@@ -2,7 +2,7 @@ function isObjectLike(value: unknown) {
return typeof value === 'object' && value !== null
}
export default function isObject<T = object>(input: unknown): input is T {
export default <T = object>(input: unknown): input is T => {
if (!isObjectLike(input) || Object.prototype.toString.call(input) !== '[object Object]') {
return false
}
+2 -2
View File
@@ -1,5 +1,4 @@
import { JOSENotSupported } from '../util/errors.js'
import random from '../runtime/random.js'
export function bitLength(alg: string) {
switch (alg) {
@@ -18,4 +17,5 @@ export function bitLength(alg: string) {
throw new JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`)
}
}
export default (alg: string): Uint8Array => random(new Uint8Array(bitLength(alg) >> 3))
export default (alg: string): Uint8Array =>
crypto.getRandomValues(new Uint8Array(bitLength(alg) >> 3))
@@ -1,7 +1,7 @@
import { JOSENotSupported } from '../util/errors.js'
import type { CryptoKey, JWK } from '../types.d.ts'
import type * as types from '../types.d.ts'
function subtleMapping(jwk: JWK): {
function subtleMapping(jwk: types.JWK): {
algorithm: RsaHashedImportParams | EcKeyAlgorithm | Algorithm
keyUsages: KeyUsage[]
} {
@@ -71,7 +71,7 @@ function subtleMapping(jwk: JWK): {
keyUsages = jwk.d ? ['sign'] : ['verify']
break
case 'EdDSA':
algorithm = { name: jwk.crv! }
algorithm = { name: 'Ed25519' }
keyUsages = jwk.d ? ['sign'] : ['verify']
break
case 'ECDH-ES':
@@ -93,21 +93,22 @@ function subtleMapping(jwk: JWK): {
return { algorithm, keyUsages }
}
const parse = async (jwk: JWK): Promise<CryptoKey> => {
export default async (jwk: types.JWK): Promise<types.CryptoKey> => {
if (!jwk.alg) {
throw new TypeError('"alg" argument is required when "jwk.alg" is not present')
}
const { algorithm, keyUsages } = subtleMapping(jwk)
const rest: [RsaHashedImportParams | EcKeyAlgorithm | Algorithm, boolean, KeyUsage[]] = [
algorithm,
jwk.ext ?? false,
(jwk.key_ops as KeyUsage[]) ?? keyUsages,
]
const keyData: JWK = { ...jwk }
const keyData: types.JWK = { ...jwk }
delete keyData.alg
delete keyData.use
return crypto.subtle.importKey('jwk', keyData, ...rest)
return crypto.subtle.importKey(
'jwk',
keyData,
algorithm,
jwk.ext ?? (jwk.d ? false : true),
(jwk.key_ops as KeyUsage[]) ?? keyUsages,
)
}
export default parse
+4 -9
View File
@@ -1,9 +1,4 @@
import type {
JWTPayload,
JWTClaimVerificationOptions,
JWEHeaderParameters,
JWSHeaderParameters,
} from '../types.d.ts'
import type * as types from '../types.d.ts'
import { JWTClaimValidationFailed, JWTExpired, JWTInvalid } from '../util/errors.js'
import { decoder } from './buffer_utils.js'
import epoch from './epoch.js'
@@ -27,9 +22,9 @@ const checkAudiencePresence = (audPayload: unknown, audOption: unknown[]) => {
}
export default (
protectedHeader: JWEHeaderParameters | JWSHeaderParameters,
protectedHeader: types.JWEHeaderParameters | types.JWSHeaderParameters,
encodedPayload: Uint8Array,
options: JWTClaimVerificationOptions = {},
options: types.JWTClaimVerificationOptions = {},
) => {
let payload!: { [propName: string]: unknown }
try {
@@ -177,5 +172,5 @@ export default (
}
}
return payload as JWTPayload
return payload as types.JWTPayload
}
@@ -1,10 +1,9 @@
import type { JWKExportFunction } from './interfaces.d.ts'
import type { JWK } from '../types.d.ts'
import invalidKeyInput from '../lib/invalid_key_input.js'
import type * as types from '../types.d.ts'
import invalidKeyInput from './invalid_key_input.js'
import { encode as base64url } from './base64url.js'
import { isCryptoKey } from './is_key_like.js'
const keyToJWK: JWKExportFunction = async (key: unknown): Promise<JWK> => {
export default async (key: unknown): Promise<types.JWK> => {
if (key instanceof Uint8Array) {
return {
kty: 'oct',
@@ -19,6 +18,5 @@ const keyToJWK: JWKExportFunction = async (key: unknown): Promise<JWK> => {
}
const { ext, key_ops, alg, use, ...jwk } = await crypto.subtle.exportKey('jwk', key)
return jwk as JWK
return jwk as types.JWK
}
export default keyToJWK
@@ -1,14 +1,14 @@
import { isJWK } from '../lib/is_jwk.js'
import type { JWK, CryptoKey, KeyObject } from '../types.d.ts'
import type * as types from '../types.d.ts'
import { isJWK } from './is_jwk.js'
import { decode } from './base64url.js'
import importJWK from './jwk_to_key.js'
import { isKeyObject } from './is_key_like.js'
import { isCryptoKey, isKeyObject } from './is_key_like.js'
let cache: WeakMap<object, Record<string, CryptoKey>>
interface ConvertableKeyObject extends KeyObject {
interface ConvertableKeyObject extends types.KeyObject {
export(): Uint8Array
export(opts: { format: 'jwk' }): JWK
export(opts: { format: 'jwk' }): types.JWK
asymmetricKeyType?: string
asymmetricKeyDetails?: { namedCurve?: string }
toCryptoKey(
@@ -20,10 +20,15 @@ interface ConvertableKeyObject extends KeyObject {
| AesKeyAlgorithm,
extractable: boolean,
usages: string[],
): CryptoKey
): types.CryptoKey
}
const handleJWK = async (key: KeyObject | JWK, jwk: JWK, alg: string, freeze = false) => {
const handleJWK = async (
key: types.KeyObject | types.JWK,
jwk: types.JWK,
alg: string,
freeze = false,
) => {
cache ||= new WeakMap()
let cached = cache.get(key)
if (cached?.[alg]) {
@@ -40,15 +45,18 @@ const handleJWK = async (key: KeyObject | JWK, jwk: JWK, alg: string, freeze = f
return cryptoKey
}
const handleKeyObject = (key: ConvertableKeyObject, alg: string) => {
const handleKeyObject = (keyObject: ConvertableKeyObject, alg: string) => {
cache ||= new WeakMap()
let cached = cache.get(key)
let cached = cache.get(keyObject)
if (cached?.[alg]) {
return cached[alg]
}
let cryptoKey: CryptoKey | undefined
if (key.asymmetricKeyType === 'x25519') {
const isPublic = keyObject.type === 'public'
const extractable = isPublic ? true : false
let cryptoKey: types.CryptoKey | undefined
if (keyObject.asymmetricKeyType === 'x25519') {
switch (alg) {
case 'ECDH-ES':
case 'ECDH-ES+A128KW':
@@ -57,27 +65,27 @@ const handleKeyObject = (key: ConvertableKeyObject, alg: string) => {
break
default:
throw new TypeError('TODO')
throw new TypeError('given KeyObject instance cannot be used for this algorithm')
}
cryptoKey = key.toCryptoKey(
key.asymmetricKeyType,
true,
key.type === 'private' ? ['deriveBits'] : [],
cryptoKey = keyObject.toCryptoKey(
keyObject.asymmetricKeyType,
extractable,
isPublic ? [] : ['deriveBits'],
)
}
if (key.asymmetricKeyType === 'ed25519') {
if (keyObject.asymmetricKeyType === 'ed25519') {
if (alg !== 'EdDSA') {
throw new TypeError('TODO')
throw new TypeError('given KeyObject instance cannot be used for this algorithm')
}
cryptoKey = key.toCryptoKey(key.asymmetricKeyType, true, [
key.type === 'private' ? 'sign' : 'verify',
cryptoKey = keyObject.toCryptoKey(keyObject.asymmetricKeyType, extractable, [
isPublic ? 'verify' : 'sign',
])
}
if (key.asymmetricKeyType === 'rsa') {
if (keyObject.asymmetricKeyType === 'rsa') {
let hash: string
switch (alg) {
case 'RSA-OAEP':
@@ -100,127 +108,136 @@ const handleKeyObject = (key: ConvertableKeyObject, alg: string) => {
break
default:
throw new TypeError('unsupported algorithm')
throw new TypeError('given KeyObject instance cannot be used for this algorithm')
}
if (alg.startsWith('RSA-OAEP')) {
return key.toCryptoKey(
return keyObject.toCryptoKey(
{
name: 'RSA-OAEP',
hash,
},
true,
key.type === 'private' ? ['decrypt', 'unwrapKey'] : ['encrypt', 'wrapKey'],
extractable,
isPublic ? ['encrypt'] : ['decrypt'],
)
}
cryptoKey = key.toCryptoKey(
cryptoKey = keyObject.toCryptoKey(
{
name: alg.startsWith('PS') ? 'RSA-PSS' : 'RSASSA-PKCS1-v1_5',
hash,
},
true,
[key.type === 'private' ? 'sign' : 'verify'],
extractable,
[isPublic ? 'verify' : 'sign'],
)
}
if (key.asymmetricKeyType === 'ec') {
if (keyObject.asymmetricKeyType === 'ec') {
const nist = new Map<unknown, string>([
['prime256v1', 'P-256'],
['secp384r1', 'P-384'],
['secp521r1', 'P-521'],
])
const namedCurve = nist.get(key.asymmetricKeyDetails?.namedCurve)
const namedCurve = nist.get(keyObject.asymmetricKeyDetails?.namedCurve)
if (!namedCurve) {
throw new TypeError('TODO')
throw new TypeError('given KeyObject instance cannot be used for this algorithm')
}
if (alg === 'ES256' && namedCurve === 'P-256') {
cryptoKey = key.toCryptoKey(
cryptoKey = keyObject.toCryptoKey(
{
name: 'ECDSA',
namedCurve,
},
true,
[key.type === 'private' ? 'sign' : 'verify'],
extractable,
[isPublic ? 'verify' : 'sign'],
)
}
if (alg === 'ES384' && namedCurve === 'P-384') {
cryptoKey = key.toCryptoKey(
cryptoKey = keyObject.toCryptoKey(
{
name: 'ECDSA',
namedCurve,
},
true,
[key.type === 'private' ? 'sign' : 'verify'],
extractable,
[isPublic ? 'verify' : 'sign'],
)
}
if (alg === 'ES512' && namedCurve === 'P-521') {
cryptoKey = key.toCryptoKey(
cryptoKey = keyObject.toCryptoKey(
{
name: 'ECDSA',
namedCurve,
},
true,
[key.type === 'private' ? 'sign' : 'verify'],
extractable,
[isPublic ? 'verify' : 'sign'],
)
}
if (alg.startsWith('ECDH-ES')) {
cryptoKey = key.toCryptoKey(
cryptoKey = keyObject.toCryptoKey(
{
name: 'ECDH',
namedCurve,
},
true,
key.type === 'private' ? ['deriveBits'] : [],
extractable,
isPublic ? [] : ['deriveBits'],
)
}
}
if (!cryptoKey) {
throw new TypeError('TODO')
throw new TypeError('given KeyObject instance cannot be used for this algorithm')
}
if (!cached) {
cache.set(key, { [alg]: cryptoKey })
cache.set(keyObject, { [alg]: cryptoKey })
} else {
cached[alg] = cryptoKey
}
return cryptoKey
}
export default async function normalizeKey(
key: CryptoKey | KeyObject | JWK | Uint8Array,
export default async (
key: types.CryptoKey | types.KeyObject | types.JWK | Uint8Array,
alg: string,
): Promise<CryptoKey | Uint8Array> {
): Promise<types.CryptoKey | Uint8Array> => {
if (key instanceof Uint8Array) {
return key
}
let normalized: CryptoKey | undefined
if (isKeyObject<KeyObject>(key)) {
if (isCryptoKey(key)) {
return key
}
if (isKeyObject(key)) {
if (key.type === 'secret') {
return (key as ConvertableKeyObject).export()
}
if ('toCryptoKey' in key && typeof key.toCryptoKey === 'function') {
normalized = handleKeyObject(key as ConvertableKeyObject, alg)
} else {
let jwk: JWK = (key as ConvertableKeyObject).export({ format: 'jwk' })
normalized = await handleJWK(key, jwk, alg)
try {
return handleKeyObject(key as ConvertableKeyObject, alg)
} catch (err) {
if (err instanceof TypeError) {
throw err
}
}
}
} else if (isJWK(key)) {
let jwk: types.JWK = (key as ConvertableKeyObject).export({ format: 'jwk' })
return handleJWK(key, jwk, alg)
}
if (isJWK(key)) {
if (key.k) {
return decode(key.k)
}
normalized = await handleJWK(key, key, alg, true)
} else {
normalized = key
return handleJWK(key, key, alg, true)
}
return normalized
throw new Error('unreachable')
}
+68
View File
@@ -0,0 +1,68 @@
import type * as types from '../types.d.ts'
import { encode as base64url } from './base64url.js'
import * as aeskw from './aeskw.js'
import { checkEncCryptoKey } from './crypto_key.js'
import { concat, encoder } from './buffer_utils.js'
import { JWEInvalid } from '../util/errors.js'
function getCryptoKey(key: types.CryptoKey | Uint8Array, alg: string) {
if (key instanceof Uint8Array) {
return crypto.subtle.importKey('raw', key, 'PBKDF2', false, ['deriveBits'])
}
checkEncCryptoKey(key, alg, 'deriveBits')
return key
}
const concatSalt = (alg: string, p2sInput: Uint8Array) =>
concat(encoder.encode(alg), new Uint8Array([0]), p2sInput)
async function deriveKey(
p2s: Uint8Array,
alg: string,
p2c: number,
key: types.CryptoKey | Uint8Array,
) {
if (!(p2s instanceof Uint8Array) || p2s.length < 8) {
throw new JWEInvalid('PBES2 Salt Input must be 8 or more octets')
}
const salt = concatSalt(alg, p2s)
const keylen = parseInt(alg.slice(13, 16), 10)
const subtleAlg = {
hash: `SHA-${alg.slice(8, 11)}`,
iterations: p2c,
name: 'PBKDF2',
salt,
}
const cryptoKey = await getCryptoKey(key, alg)
return new Uint8Array(await crypto.subtle.deriveBits(subtleAlg, cryptoKey, keylen))
}
export async function wrap(
alg: string,
key: types.CryptoKey | Uint8Array,
cek: Uint8Array,
p2c = 2048,
p2s: Uint8Array = crypto.getRandomValues(new Uint8Array(16)),
) {
const derived = await deriveKey(p2s, alg, p2c, key)
const encryptedKey = await aeskw.wrap(alg.slice(-6), derived, cek)
return { encryptedKey, p2c, p2s: base64url(p2s) }
}
export async function unwrap(
alg: string,
key: types.CryptoKey | Uint8Array,
encryptedKey: Uint8Array,
p2c: number,
p2s: Uint8Array,
) {
const derived = await deriveKey(p2s, alg, p2c, key)
return aeskw.unwrap(alg.slice(-6), derived, encryptedKey)
}
+32
View File
@@ -0,0 +1,32 @@
import type * as types from '../types.d.ts'
import { checkEncCryptoKey } from './crypto_key.js'
import checkKeyLength from './check_key_length.js'
import { JOSENotSupported } from '../util/errors.js'
const subtleAlgorithm = (alg: string) => {
switch (alg) {
case 'RSA-OAEP':
case 'RSA-OAEP-256':
case 'RSA-OAEP-384':
case 'RSA-OAEP-512':
return 'RSA-OAEP'
default:
throw new JOSENotSupported(
`alg ${alg} is not supported either by JOSE or your javascript runtime`,
)
}
}
export async function encrypt(alg: string, key: types.CryptoKey, cek: Uint8Array) {
checkEncCryptoKey(key, alg, 'encrypt')
checkKeyLength(alg, key)
return new Uint8Array(await crypto.subtle.encrypt(subtleAlgorithm(alg), key, cek))
}
export async function decrypt(alg: string, key: types.CryptoKey, encryptedKey: Uint8Array) {
checkEncCryptoKey(key, alg, 'decrypt')
checkKeyLength(alg, key)
return new Uint8Array(await crypto.subtle.decrypt(subtleAlgorithm(alg), key, encryptedKey))
}
+2 -2
View File
@@ -1,10 +1,10 @@
import type { CryptoKey } from '../types.d.ts'
import type * as types from '../types.d.ts'
import subtleAlgorithm from './subtle_dsa.js'
import checkKeyLength from './check_key_length.js'
import getSignKey from './get_sign_verify_key.js'
export default async function sign(alg: string, key: CryptoKey | Uint8Array, data: Uint8Array) {
export default async (alg: string, key: types.CryptoKey | Uint8Array, data: Uint8Array) => {
const cryptoKey = await getSignKey(alg, key, 'sign')
checkKeyLength(alg, cryptoKey)
const signature = await crypto.subtle.sign(
@@ -1,6 +1,6 @@
import { JOSENotSupported } from '../util/errors.js'
export default function subtleDsa(alg: string, algorithm: KeyAlgorithm | EcKeyAlgorithm) {
export default (alg: string, algorithm: KeyAlgorithm | EcKeyAlgorithm) => {
const hash = `SHA-${alg.slice(-3)}`
switch (alg) {
case 'HS256':
@@ -22,7 +22,7 @@ export default function subtleDsa(alg: string, algorithm: KeyAlgorithm | EcKeyAl
case 'Ed25519':
return { name: 'Ed25519' }
case 'EdDSA':
return { name: algorithm.name }
return { name: 'Ed25519' }
default:
throw new JOSENotSupported(
`alg ${alg} is not supported either by JOSE or your javascript runtime`,
+1 -3
View File
@@ -1,4 +1,4 @@
const validateAlgorithms = (option: string, algorithms?: string[]) => {
export default (option: string, algorithms?: string[]) => {
if (
algorithms !== undefined &&
(!Array.isArray(algorithms) || algorithms.some((s) => typeof s !== 'string'))
@@ -12,5 +12,3 @@ const validateAlgorithms = (option: string, algorithms?: string[]) => {
return new Set(algorithms)
}
export default validateAlgorithms
+2 -4
View File
@@ -6,13 +6,13 @@ interface CritCheckHeader {
[propName: string]: unknown
}
function validateCrit(
export default (
Err: typeof JWEInvalid | typeof JWSInvalid,
recognizedDefault: Map<string, boolean>,
recognizedOption: { [propName: string]: boolean } | undefined,
protectedHeader: CritCheckHeader | undefined,
joseHeader: CritCheckHeader,
) {
) => {
if (joseHeader.crit !== undefined && protectedHeader?.crit === undefined) {
throw new Err('"crit" (Critical) Header Parameter MUST be integrity protected')
}
@@ -53,5 +53,3 @@ function validateCrit(
return new Set(protectedHeader.crit)
}
export default validateCrit
+4 -4
View File
@@ -1,15 +1,15 @@
import type { CryptoKey } from '../types.d.ts'
import type * as types from '../types.d.ts'
import subtleAlgorithm from './subtle_dsa.js'
import checkKeyLength from './check_key_length.js'
import getVerifyKey from './get_sign_verify_key.js'
export default async function verify(
export default async (
alg: string,
key: CryptoKey | Uint8Array,
key: types.CryptoKey | Uint8Array,
signature: Uint8Array,
data: Uint8Array,
) {
) => {
const cryptoKey = await getVerifyKey(alg, key, 'verify')
checkKeyLength(alg, cryptoKey)
const algorithm = subtleAlgorithm(alg, cryptoKey.algorithm)
-54
View File
@@ -1,54 +0,0 @@
import type { CryptoKey } from '../types.d.ts'
import { encoder, concat, uint32be, lengthAndInput, concatKdf } from '../lib/buffer_utils.js'
import { checkEncCryptoKey } from '../lib/crypto_key.js'
export async function deriveKey(
publicKey: CryptoKey,
privateKey: CryptoKey,
algorithm: string,
keyLength: number,
apu: Uint8Array = new Uint8Array(0),
apv: Uint8Array = new Uint8Array(0),
) {
checkEncCryptoKey(publicKey, 'ECDH')
checkEncCryptoKey(privateKey, 'ECDH', 'deriveBits')
const value = concat(
lengthAndInput(encoder.encode(algorithm)),
lengthAndInput(apu),
lengthAndInput(apv),
uint32be(keyLength),
)
let length: number
if (publicKey.algorithm.name === 'X25519') {
length = 256
} else {
length =
Math.ceil(parseInt((publicKey.algorithm as EcKeyAlgorithm).namedCurve.slice(-3), 10) / 8) << 3
}
const sharedSecret = new Uint8Array(
await crypto.subtle.deriveBits(
{
name: publicKey.algorithm.name,
public: publicKey,
},
privateKey,
length,
),
)
return concatKdf(sharedSecret, keyLength, value)
}
export async function generateEpk(key: CryptoKey) {
return crypto.subtle.generateKey(key.algorithm as EcKeyAlgorithm, true, ['deriveBits'])
}
export function ecdhAllowed(key: CryptoKey) {
return (
['P-256', 'P-384', 'P-521'].includes((key.algorithm as EcKeyAlgorithm).namedCurve) ||
key.algorithm.name === 'X25519'
)
}
-43
View File
@@ -1,43 +0,0 @@
import type { FetchFunction } from './interfaces.d.ts'
import { JOSEError, JWKSTimeout } from '../util/errors.js'
type AcceptedRequestOptions = Pick<RequestInit, 'headers'>
const fetchJwks: FetchFunction = async (
url: URL,
timeout: number,
options: AcceptedRequestOptions,
) => {
let controller!: AbortController
let id!: ReturnType<typeof setTimeout>
let timedOut = false
if (typeof AbortController === 'function') {
controller = new AbortController()
id = setTimeout(() => {
timedOut = true
controller.abort()
}, timeout)
}
const response = await fetch(url.href, {
signal: controller ? controller.signal : undefined,
redirect: 'manual',
headers: options.headers,
}).catch((err) => {
if (timedOut) throw new JWKSTimeout()
throw err
})
if (id !== undefined) clearTimeout(id)
if (response.status !== 200) {
throw new JOSEError('Expected 200 OK from the JSON Web Key Set HTTP response')
}
try {
return await response.json()
} catch {
throw new JOSEError('Failed to parse the JSON Web Key Set HTTP response as JSON')
}
}
export default fetchJwks
-153
View File
@@ -1,153 +0,0 @@
import type { CryptoKey } from '../types.d.ts'
import { JOSENotSupported } from '../util/errors.js'
import random from './random.js'
import type { GenerateKeyPairOptions } from '../key/generate_key_pair.js'
import type { GenerateSecretOptions } from '../key/generate_secret.js'
export async function generateSecret(alg: string, options?: GenerateSecretOptions) {
let length: number
let algorithm: AesKeyGenParams | HmacKeyGenParams
let keyUsages: KeyUsage[]
switch (alg) {
case 'HS256':
case 'HS384':
case 'HS512':
length = parseInt(alg.slice(-3), 10)
algorithm = { name: 'HMAC', hash: `SHA-${length}`, length }
keyUsages = ['sign', 'verify']
break
case 'A128CBC-HS256':
case 'A192CBC-HS384':
case 'A256CBC-HS512':
length = parseInt(alg.slice(-3), 10)
return random(new Uint8Array(length >> 3))
case 'A128KW':
case 'A192KW':
case 'A256KW':
length = parseInt(alg.slice(1, 4), 10)
algorithm = { name: 'AES-KW', length }
keyUsages = ['wrapKey', 'unwrapKey']
break
case 'A128GCMKW':
case 'A192GCMKW':
case 'A256GCMKW':
case 'A128GCM':
case 'A192GCM':
case 'A256GCM':
length = parseInt(alg.slice(1, 4), 10)
algorithm = { name: 'AES-GCM', length }
keyUsages = ['encrypt', 'decrypt']
break
default:
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value')
}
return crypto.subtle.generateKey(
algorithm,
options?.extractable ?? false,
keyUsages,
) as unknown as Promise<CryptoKey>
}
function getModulusLengthOption(options?: GenerateKeyPairOptions) {
const modulusLength = options?.modulusLength ?? 2048
if (typeof modulusLength !== 'number' || modulusLength < 2048) {
throw new JOSENotSupported(
'Invalid or unsupported modulusLength option provided, 2048 bits or larger keys must be used',
)
}
return modulusLength
}
export async function generateKeyPair(alg: string, options?: GenerateKeyPairOptions) {
let algorithm: RsaHashedKeyGenParams | EcKeyGenParams | KeyAlgorithm
let keyUsages: KeyUsage[]
switch (alg) {
case 'PS256':
case 'PS384':
case 'PS512':
algorithm = {
name: 'RSA-PSS',
hash: `SHA-${alg.slice(-3)}`,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
modulusLength: getModulusLengthOption(options),
}
keyUsages = ['sign', 'verify']
break
case 'RS256':
case 'RS384':
case 'RS512':
algorithm = {
name: 'RSASSA-PKCS1-v1_5',
hash: `SHA-${alg.slice(-3)}`,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
modulusLength: getModulusLengthOption(options),
}
keyUsages = ['sign', 'verify']
break
case 'RSA-OAEP':
case 'RSA-OAEP-256':
case 'RSA-OAEP-384':
case 'RSA-OAEP-512':
algorithm = {
name: 'RSA-OAEP',
hash: `SHA-${parseInt(alg.slice(-3), 10) || 1}`,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
modulusLength: getModulusLengthOption(options),
}
keyUsages = ['decrypt', 'unwrapKey', 'encrypt', 'wrapKey']
break
case 'ES256':
algorithm = { name: 'ECDSA', namedCurve: 'P-256' }
keyUsages = ['sign', 'verify']
break
case 'ES384':
algorithm = { name: 'ECDSA', namedCurve: 'P-384' }
keyUsages = ['sign', 'verify']
break
case 'ES512':
algorithm = { name: 'ECDSA', namedCurve: 'P-521' }
keyUsages = ['sign', 'verify']
break
case 'Ed25519':
algorithm = { name: 'Ed25519' }
keyUsages = ['sign', 'verify']
break
case 'EdDSA': {
keyUsages = ['sign', 'verify']
algorithm = { name: 'Ed25519' }
break
}
case 'ECDH-ES':
case 'ECDH-ES+A128KW':
case 'ECDH-ES+A192KW':
case 'ECDH-ES+A256KW': {
keyUsages = ['deriveBits']
const crv = options?.crv ?? 'P-256'
switch (crv) {
case 'P-256':
case 'P-384':
case 'P-521': {
algorithm = { name: 'ECDH', namedCurve: crv }
break
}
case 'X25519':
algorithm = { name: crv }
break
default:
throw new JOSENotSupported(
'Invalid or unsupported crv option provided, supported values are P-256, P-384, P-521, and X25519',
)
}
break
}
default:
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value')
}
return crypto.subtle.generateKey(algorithm, options?.extractable ?? false, keyUsages) as Promise<{
publicKey: CryptoKey
privateKey: CryptoKey
}>
}
-65
View File
@@ -1,65 +0,0 @@
import type { JWK, CryptoKey } from '../types.d.ts'
import type { PEMImportOptions } from '../key/import.js'
export interface TimingSafeEqual {
(a: Uint8Array, b: Uint8Array): boolean
}
export interface Pbes2KWEncryptFunction {
(
alg: string,
key: unknown,
cek: Uint8Array,
p2c?: number,
p2s?: Uint8Array,
): Promise<{
encryptedKey: Uint8Array
p2c: number
p2s: string
}>
}
export interface Pbes2KWDecryptFunction {
(
alg: string,
key: unknown,
encryptedKey: Uint8Array,
p2c: number,
p2s: Uint8Array,
): Promise<Uint8Array>
}
export interface EncryptFunction {
(
enc: string,
plaintext: Uint8Array,
cek: unknown,
iv: Uint8Array | undefined,
aad: Uint8Array,
): Promise<{
ciphertext: Uint8Array
tag: Uint8Array | undefined
iv: Uint8Array | undefined
}>
}
export interface DecryptFunction {
(
enc: string,
cek: unknown,
ciphertext: Uint8Array,
iv: Uint8Array | undefined,
tag: Uint8Array | undefined,
additionalData: Uint8Array,
): Promise<Uint8Array>
}
export interface FetchFunction {
(url: URL, timeout: number, options?: any): Promise<{ [propName: string]: unknown }>
}
export interface DigestFunction {
(digest: 'sha256' | 'sha384' | 'sha512', data: Uint8Array): Promise<Uint8Array>
}
export interface PEMImportFunction {
(pem: string, alg: string, options?: PEMImportOptions): Promise<CryptoKey>
}
interface ExportFunction<T> {
(key: unknown): Promise<T>
}
export type JWKExportFunction = ExportFunction<JWK>
export type PEMExportFunction = ExportFunction<string>
-23
View File
@@ -1,23 +0,0 @@
import type { CryptoKey, KeyObject } from '../types.d.ts'
export function assertCryptoKey(key: unknown): asserts key is CryptoKey {
if (!isCryptoKey(key)) {
throw new Error('unreachable. report.')
}
}
export function isCryptoKey(key: unknown): key is CryptoKey {
// @ts-expect-error
return key?.[Symbol.toStringTag] === 'CryptoKey'
}
export function isKeyObject<T extends KeyObject = KeyObject>(key: unknown): key is T {
// @ts-expect-error
return key?.[Symbol.toStringTag] === 'KeyObject'
}
export default (key: unknown): key is CryptoKey | KeyObject => {
return isCryptoKey(key) || isKeyObject(key)
}
export const types = ['CryptoKey', 'KeyObject']
-59
View File
@@ -1,59 +0,0 @@
import type { CryptoKey } from '../types.d.ts'
import random from './random.js'
import { p2s as concatSalt } from '../lib/buffer_utils.js'
import { encode as base64url } from './base64url.js'
import { wrap, unwrap } from './aeskw.js'
import checkP2s from '../lib/check_p2s.js'
import { checkEncCryptoKey } from '../lib/crypto_key.js'
function getCryptoKey(key: CryptoKey | Uint8Array, alg: string) {
if (key instanceof Uint8Array) {
return crypto.subtle.importKey('raw', key, 'PBKDF2', false, ['deriveBits'])
}
checkEncCryptoKey(key, alg, 'deriveBits')
return key
}
async function deriveKey(p2s: Uint8Array, alg: string, p2c: number, key: CryptoKey | Uint8Array) {
checkP2s(p2s)
const salt = concatSalt(alg, p2s)
const keylen = parseInt(alg.slice(13, 16), 10)
const subtleAlg = {
hash: `SHA-${alg.slice(8, 11)}`,
iterations: p2c,
name: 'PBKDF2',
salt,
}
const cryptoKey = await getCryptoKey(key, alg)
return new Uint8Array(await crypto.subtle.deriveBits(subtleAlg, cryptoKey, keylen))
}
export async function encrypt(
alg: string,
key: CryptoKey | Uint8Array,
cek: Uint8Array,
p2c = 2048,
p2s: Uint8Array = random(new Uint8Array(16)),
) {
const derived = await deriveKey(p2s, alg, p2c, key)
const encryptedKey = await wrap(alg.slice(-6), derived, cek)
return { encryptedKey, p2c, p2s: base64url(p2s) }
}
export async function decrypt(
alg: string,
key: CryptoKey | Uint8Array,
encryptedKey: Uint8Array,
p2c: number,
p2s: Uint8Array,
) {
const derived = await deriveKey(p2s, alg, p2c, key)
return unwrap(alg.slice(-6), derived, encryptedKey)
}
-1
View File
@@ -1 +0,0 @@
export default crypto.getRandomValues.bind(crypto)
-18
View File
@@ -1,18 +0,0 @@
import type { CryptoKey } from '../types.d.ts'
import subtleAlgorithm from './subtle_rsaes.js'
import { checkEncCryptoKey } from '../lib/crypto_key.js'
import checkKeyLength from './check_key_length.js'
export async function encrypt(alg: string, key: CryptoKey, cek: Uint8Array) {
checkEncCryptoKey(key, alg, 'encrypt')
checkKeyLength(alg, key)
return new Uint8Array(await crypto.subtle.encrypt(subtleAlgorithm(alg), key, cek))
}
export async function decrypt(alg: string, key: CryptoKey, encryptedKey: Uint8Array) {
checkEncCryptoKey(key, alg, 'decrypt')
checkKeyLength(alg, key)
return new Uint8Array(await crypto.subtle.decrypt(subtleAlgorithm(alg), key, encryptedKey))
}
-1
View File
@@ -1 +0,0 @@
export default 'WebCryptoAPI'
-15
View File
@@ -1,15 +0,0 @@
import { JOSENotSupported } from '../util/errors.js'
export default function subtleRsaEs(alg: string) {
switch (alg) {
case 'RSA-OAEP':
case 'RSA-OAEP-256':
case 'RSA-OAEP-384':
case 'RSA-OAEP-512':
return 'RSA-OAEP'
default:
throw new JOSENotSupported(
`alg ${alg} is not supported either by JOSE or your javascript runtime`,
)
}
}
-23
View File
@@ -1,23 +0,0 @@
import type { TimingSafeEqual } from './interfaces.d.ts'
const timingSafeEqual: TimingSafeEqual = (a, b) => {
if (!(a instanceof Uint8Array)) {
throw new TypeError('First argument must be a buffer')
}
if (!(b instanceof Uint8Array)) {
throw new TypeError('Second argument must be a buffer')
}
if (a.length !== b.length) {
throw new TypeError('Input buffers must have the same length')
}
const len = a.length
let out = 0
let i = -1
while (++i < len) {
out |= a[i] ^ b[i]
}
return out === 0
}
export default timingSafeEqual
+11 -96
View File
@@ -1,97 +1,3 @@
/**
* KeyLike are runtime-specific classes representing asymmetric keys or symmetric secrets. These are
* instances of {@link !CryptoKey} and additionally {@link !KeyObject} in Node.js runtime.
* {@link !Uint8Array} instances are also accepted as symmetric secret representation only.
*
* {@link key/import Key Import Functions} can be used to import PEM, or JWK formatted asymmetric
* keys and certificates to these runtime-specific representations.
*
* In Node.js the {@link !Buffer} class is a subclass of {@link !Uint8Array} and so {@link !Buffer} can
* be provided for symmetric secrets as well.
*
* {@link !KeyObject} is a representation of a key/secret available in the Node.js runtime. In
* addition to the {@link key/import Key Import Functions} you may use the runtime APIs
* {@link !createPublicKey}, {@link !createPrivateKey}, and {@link !createSecretKey} to obtain a
* {@link !KeyObject} from your existing key material.
*
* {@link !CryptoKey} is a representation of a key/secret available in the Browser and
* Web-interoperable runtimes. In addition to the {@link key/import Key Import Functions} you may use
* the {@link !SubtleCrypto.importKey} API to obtain a {@link !CryptoKey} from your existing key
* material.
*
* @example
*
* Import a PEM-encoded SPKI Public Key
*
* ```js
* const algorithm = 'ES256'
* const spki = `-----BEGIN PUBLIC KEY-----
* MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFlHHWfLk0gLBbsLTcuCrbCqoHqmM
* YJepMC+Q+Dd6RBmBiA41evUsNMwLeN+PNFqib+xwi9JkJ8qhZkq8Y/IzGg==
* -----END PUBLIC KEY-----`
* const ecPublicKey = await jose.importSPKI(spki, algorithm)
* ```
*
* @example
*
* Import SPKI from an X.509 Certificate
*
* ```js
* const algorithm = 'ES256'
* const x509 = `-----BEGIN CERTIFICATE-----
* MIIBXjCCAQSgAwIBAgIGAXvykuMKMAoGCCqGSM49BAMCMDYxNDAyBgNVBAMMK3Np
* QXBNOXpBdk1VaXhXVWVGaGtjZXg1NjJRRzFyQUhXaV96UlFQTVpQaG8wHhcNMjEw
* OTE3MDcwNTE3WhcNMjIwNzE0MDcwNTE3WjA2MTQwMgYDVQQDDCtzaUFwTTl6QXZN
* VWl4V1VlRmhrY2V4NTYyUUcxckFIV2lfelJRUE1aUGhvMFkwEwYHKoZIzj0CAQYI
* KoZIzj0DAQcDQgAE8PbPvCv5D5xBFHEZlBp/q5OEUymq7RIgWIi7tkl9aGSpYE35
* UH+kBKDnphJO3odpPZ5gvgKs2nwRWcrDnUjYLDAKBggqhkjOPQQDAgNIADBFAiEA
* 1yyMTRe66MhEXID9+uVub7woMkNYd0LhSHwKSPMUUTkCIFQGsfm1ecXOpeGOufAh
* v+A1QWZMuTWqYt+uh/YSRNDn
* -----END CERTIFICATE-----`
* const ecPublicKey = await jose.importX509(x509, algorithm)
* ```
*
* @example
*
* Import a PEM-encoded PKCS8 Private Key
*
* ```js
* const algorithm = 'ES256'
* const pkcs8 = `-----BEGIN PRIVATE KEY-----
* MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgiyvo0X+VQ0yIrOaN
* nlrnUclopnvuuMfoc8HHly3505OhRANCAAQWUcdZ8uTSAsFuwtNy4KtsKqgeqYxg
* l6kwL5D4N3pEGYGIDjV69Sw0zAt43480WqJv7HCL0mQnyqFmSrxj8jMa
* -----END PRIVATE KEY-----`
* const ecPrivateKey = await jose.importPKCS8(pkcs8, algorithm)
* ```
*
* @example
*
* Import a JSON Web Key (JWK)
*
* ```js
* const ecPublicKey = await jose.importJWK(
* {
* crv: 'P-256',
* kty: 'EC',
* x: 'ySK38C1jBdLwDsNWKzzBHqKYEE5Cgv-qjWvorUXk9fw',
* y: '_LeQBw07cf5t57Iavn4j-BqJsAD1dpoz8gokd3sBsOo',
* },
* 'ES256',
* )
*
* const rsaPublicKey = await jose.importJWK(
* {
* kty: 'RSA',
* e: 'AQAB',
* n: '12oBZRhCiZFJLcPg59LkZZ9mdhSMTKAQZYq32k_ti5SBB6jerkh-WzOMAO664r_qyLkqHUSp3u5SbXtseZEpN3XPWGKSxjsy-1JyEFTdLSYe6f9gfrmxkUF_7DTpq0gn6rntP05g2-wFW50YO7mosfdslfrTJYWHFhJALabAeYirYD7-9kqq9ebfFMF4sRRELbv9oi36As6Q9B3Qb5_C1rAzqfao_PCsf9EPsTZsVVVkA5qoIAr47lo1ipfiBPxUCCNSdvkmDTYgvvRm6ZoMjFbvOtgyts55fXKdMWv7I9HMD5HwE9uW839PWA514qhbcIsXEYSFMPMV6fnlsiZvQQ',
* },
* 'PS256',
* )
* ```
*/
export type KeyLike = CryptoKey | KeyObject
/** Generic JSON Web Key Parameters. */
export interface JWKParameters {
/** JWK "kty" (Key Type) Parameter */
@@ -767,12 +673,21 @@ export interface JSONWebKeySet {
keys: JWK[]
}
export type CryptoRuntime = 'WebCryptoAPI' | 'node:crypto'
/**
* {@link !KeyObject} is a representation of a key/secret available in the Node.js runtime. You may
* use the Node.js runtime APIs {@link !createPublicKey}, {@link !createPrivateKey}, and
* {@link !createSecretKey} to obtain a {@link !KeyObject} from your existing key material.
*/
export interface KeyObject {
type: string
}
/**
* {@link !CryptoKey} is a representation of a key/secret available in all supported runtimes. In
* addition to the {@link key/import Key Import Functions} you may use the
* {@link !SubtleCrypto.importKey} API to obtain a {@link !CryptoKey} from your existing key
* material.
*/
export type CryptoKey = Extract<
Awaited<ReturnType<typeof crypto.subtle.generateKey>>,
{ type: string }
+1 -1
View File
@@ -1,4 +1,4 @@
import * as base64url from '../runtime/base64url.js'
import * as base64url from '../lib/base64url.js'
/**
* Utility function to encode a string or {@link !Uint8Array} as a base64url string.
+6 -3
View File
@@ -1,7 +1,7 @@
import { decode as base64url } from './base64url.js'
import { decoder } from '../lib/buffer_utils.js'
import isObject from '../lib/is_object.js'
import type { JWTPayload } from '../types.d.ts'
import type * as types from '../types.d.ts'
import { JWTInvalid } from './errors.js'
/**
@@ -22,7 +22,9 @@ import { JWTInvalid } from './errors.js'
*
* @param jwt JWT token in compact JWS serialization.
*/
export function decodeJwt<PayloadType = JWTPayload>(jwt: string): PayloadType & JWTPayload {
export function decodeJwt<PayloadType = types.JWTPayload>(
jwt: string,
): PayloadType & types.JWTPayload {
if (typeof jwt !== 'string')
throw new JWTInvalid('JWTs must use Compact JWS serialization, JWT must be a string')
@@ -46,7 +48,8 @@ export function decodeJwt<PayloadType = JWTPayload>(jwt: string): PayloadType &
throw new JWTInvalid('Failed to parse the decoded payload as JSON')
}
if (!isObject<PayloadType & JWTPayload>(result)) throw new JWTInvalid('Invalid JWT Claims Set')
if (!isObject<PayloadType & types.JWTPayload>(result))
throw new JWTInvalid('Invalid JWT Claims Set')
return result
}
+2 -2
View File
@@ -1,9 +1,9 @@
import { decode as base64url } from './base64url.js'
import { decoder } from '../lib/buffer_utils.js'
import isObject from '../lib/is_object.js'
import type { JWSHeaderParameters, JWEHeaderParameters } from '../types.d.ts'
import type * as types from '../types.d.ts'
export type ProtectedHeaderParameters = JWSHeaderParameters & JWEHeaderParameters
export type ProtectedHeaderParameters = types.JWSHeaderParameters & types.JWEHeaderParameters
/**
* Decodes the Protected Header of a JWE/JWS/JWT token utilizing any JOSE serialization.
+16 -6
View File
@@ -1,4 +1,4 @@
import type { JWTPayload, CryptoKey } from '../types.d.ts'
import type * as types from '../types.d.ts'
/**
* A generic Error that all other JOSE specific Error subclasses extend.
@@ -74,10 +74,15 @@ export class JWTClaimValidationFailed extends JOSEError {
* has however been verified. Claims Set verification happens after the JWS Signature or JWE
* Decryption processes.
*/
payload: JWTPayload
payload: types.JWTPayload
/** @ignore */
constructor(message: string, payload: JWTPayload, claim = 'unspecified', reason = 'unspecified') {
constructor(
message: string,
payload: types.JWTPayload,
claim = 'unspecified',
reason = 'unspecified',
) {
super(message, { cause: { claim, reason, payload } })
this.claim = claim
this.reason = reason
@@ -126,10 +131,15 @@ export class JWTExpired extends JOSEError implements JWTClaimValidationFailed {
* has however been verified. Claims Set verification happens after the JWS Signature or JWE
* Decryption processes.
*/
payload: JWTPayload
payload: types.JWTPayload
/** @ignore */
constructor(message: string, payload: JWTPayload, claim = 'unspecified', reason = 'unspecified') {
constructor(
message: string,
payload: types.JWTPayload,
claim = 'unspecified',
reason = 'unspecified',
) {
super(message, { cause: { claim, reason, payload } })
this.claim = claim
this.reason = reason
@@ -446,7 +456,7 @@ export class JWKSNoMatchingKey extends JOSEError {
*/
export class JWKSMultipleMatchingKeys extends JOSEError {
/** @ignore */
[Symbol.asyncIterator]!: () => AsyncIterableIterator<CryptoKey>
[Symbol.asyncIterator]!: () => AsyncIterableIterator<types.CryptoKey>
/** @ignore */
static override code = 'ERR_JWKS_MULTIPLE_MATCHING_KEYS'
+1 -1
View File
@@ -2,4 +2,4 @@ echo "Using $(deno --version | head -1)"
export RUST_BACKTRACE=1
deno run --allow-read --allow-net --allow-env --import-map tap/import_map.json --no-npm tap/run-deno.ts
deno run --unstable-sloppy-imports --allow-read --allow-net --allow-env --no-npm tap/run-deno.ts
+1 -12
View File
@@ -1,15 +1,4 @@
#!/bin/bash
./node_modules/.bin/esbuild \
--log-level=warning \
--format=esm \
--sourcemap \
--bundle \
--platform=node \
--external:electron \
--external:#dist \
--target=esnext \
--outfile=tap/run-electron.js \
tap/run-electron.ts
source .electron_flags.sh
electron tap/run-electron.js
electron tap/run-electron.ts
+2 -12
View File
@@ -1,21 +1,11 @@
#!/bin/bash
./node_modules/.bin/esbuild \
--log-level=warning \
--format=esm \
--sourcemap \
--bundle \
--platform=node \
--external:#dist \
--target=esnext \
--outfile=tap/run-node.mjs \
tap/run-node.ts
source .node_flags.sh
node tap/run-node.mjs
node tap/run-node.ts
WEB_CRYPTO_API=$?
node tap/run-node.mjs --keys='KeyObject'
node tap/run-node.ts --keys='KeyObject'
WEB_CRYPTO_API_WITH_KEYOBJECT=$?
echo ""
+3 -2
View File
@@ -1,7 +1,6 @@
import type QUnit from 'qunit'
import * as env from './env.js'
import type * as jose from '../src/index.js'
import random from './random.js'
import * as roundtrip from './encrypt.js'
export default (
@@ -35,7 +34,9 @@ export default (
function secretsFor(enc: string) {
return [
keys.generateSecret(enc, { extractable: true }),
random(parseInt(enc.endsWith('GCM') ? enc.slice(1, 4) : enc.slice(-3)) >> 3),
crypto.getRandomValues(
new Uint8Array(parseInt(enc.endsWith('GCM') ? enc.slice(1, 4) : enc.slice(-3)) >> 3),
),
]
}
+1 -2
View File
@@ -1,7 +1,6 @@
import type QUnit from 'qunit'
import * as env from './env.js'
import type * as jose from '../src/index.js'
import random from './random.js'
import * as roundtrip from './encrypt.js'
export default (
@@ -35,7 +34,7 @@ export default (
function secretsFor(alg: string) {
return [
keys.generateSecret(alg, { extractable: true }),
random(parseInt(alg.slice(1, 4), 10) >> 3),
crypto.getRandomValues(new Uint8Array(parseInt(alg.slice(1, 4), 10) >> 3)),
]
}
+3 -4
View File
@@ -1,6 +1,5 @@
import type QUnit from 'qunit'
import type * as jose from '../src/index.js'
import random from './random.js'
type keyType = Uint8Array | jose.CryptoKey | jose.KeyObject | jose.GenerateKeyPairResult
@@ -68,12 +67,12 @@ export async function jwe(
alg: string,
enc: string,
secretOrKeyPair: keyType,
cleartext = random(),
cleartext = crypto.getRandomValues(new Uint8Array(16)),
) {
// Test Uint8Array, CryptoKey, and KeyObject key inputs
{
const [eKey, dKey] = await getKeys(secretOrKeyPair, false, keys)
const aad = random()
const aad = crypto.getRandomValues(new Uint8Array(16))
const jwe = await new lib.FlattenedEncrypt(cleartext)
.setProtectedHeader({ alg, enc })
@@ -96,7 +95,7 @@ export async function jwe(
// Test JWK key input
{
const [eKey, dKey] = await getKeys(secretOrKeyPair, true, keys)
const aad = random()
const aad = crypto.getRandomValues(new Uint8Array(16))
const jwe = await new lib.FlattenedEncrypt(cleartext)
.setProtectedHeader({ alg, enc })
+5 -5
View File
@@ -4,19 +4,19 @@ import type * as jose from '../src/index.js'
export default async (
QUnit: QUnit,
lib: typeof jose,
keys: Pick<typeof jose, 'exportJWK' | 'generateKeyPair' | 'generateSecret' | 'importJWK'>,
_lib: Pick<typeof jose, 'exportJWK' | 'generateKeyPair' | 'generateSecret' | 'importJWK'>,
) => {
const { module, test } = QUnit
module('generate_options.ts')
const isWebCrypto =
typeof CryptoKey !== 'undefined' && (await keys.generateSecret('HS256')) instanceof CryptoKey
typeof CryptoKey !== 'undefined' && (await lib.generateSecret('HS256')) instanceof CryptoKey
if (isWebCrypto) {
for (const extractable of [undefined, true, false]) {
test(`secret CryptoKey extractable: ${extractable ?? 'default (false)'}`, async (t) => {
const expected = extractable ?? false
const secret = (await keys.generateSecret('HS256', { extractable })) as CryptoKey
const secret = (await lib.generateSecret('HS256', { extractable })) as CryptoKey
t.equal(secret.extractable, expected)
})
}
@@ -24,7 +24,7 @@ export default async (
for (const extractable of [undefined, true, false]) {
test(`CryptoKeyPair extractable: ${extractable ?? 'default (false)'}`, async (t) => {
const expected = extractable ?? false
const kp = (await keys.generateKeyPair('ES256', { extractable })) as CryptoKeyPair
const kp = (await lib.generateKeyPair('ES256', { extractable })) as CryptoKeyPair
t.equal(kp.privateKey.extractable, expected)
t.equal(kp.publicKey.extractable, true)
})
@@ -34,7 +34,7 @@ export default async (
for (const modulusLength of [undefined, 2048, 3072]) {
test(`RSA modulusLength ${modulusLength ?? 'default (2048)'}`, async (t) => {
const expected = modulusLength ?? 2048
const { publicKey } = (await keys.generateKeyPair('RS256', {
const { publicKey } = (await lib.generateKeyPair('RS256', {
modulusLength,
})) as CryptoKeyPair
+5 -3
View File
@@ -1,6 +1,5 @@
import type QUnit from 'qunit'
import type * as jose from '../src/index.js'
import random from './random.js'
import * as roundtrip from './sign.js'
export default (
@@ -16,13 +15,16 @@ export default (
function digestSizeSecretsFor(alg: string) {
return [
keys.generateSecret(alg, { extractable: true }),
random(parseInt(alg.slice(2, 5), 10) >> 3),
crypto.getRandomValues(new Uint8Array(parseInt(alg.slice(2, 5), 10) >> 3)),
]
}
function nonDigestSizeSecretFor(alg: string) {
const length = parseInt(alg.slice(2, 5), 10) >> 3
return [random(length - 1), random(length + 1)]
return [
crypto.getRandomValues(new Uint8Array(length - 1)),
crypto.getRandomValues(new Uint8Array(length + 1)),
]
}
for (const alg of algorithms) {
-1
View File
@@ -16,7 +16,6 @@
"./noop.js": "./noop.ts",
"./pbes2.js": "./pbes2.ts",
"./pem.js": "./pem.ts",
"./random.js": "./random.ts",
"./rsaes.js": "./rsaes.ts",
"./run.js": "./run.ts",
"./sign.js": "./sign.ts"
+7 -3
View File
@@ -6,7 +6,7 @@ import type * as jose from '../src/index.js'
export default (
QUnit: QUnit,
lib: typeof jose,
keys: Pick<typeof jose, 'exportJWK' | 'generateKeyPair' | 'generateSecret' | 'importJWK'>,
_keys: Pick<typeof jose, 'exportJWK' | 'generateKeyPair' | 'generateSecret' | 'importJWK'>,
) => {
const { module, test } = QUnit
module('jwk.ts')
@@ -82,12 +82,16 @@ export default (
}
const execute = async (t: typeof QUnit.assert) => {
const key = await lib.importJWK({ ...jwk, ext: true }, alg)
const key = await lib.importJWK({ ...jwk, ext: true } as jose.JWK, alg)
const exported = await lib.exportJWK(key)
for (const prop of [...new Set([...Object.keys(jwk), ...Object.keys(exported)])]) {
t.strictEqual(exported[prop], jwk[prop as keyof JsonWebKey], `${prop} mismatch`)
t.strictEqual(
exported[prop as keyof JsonWebKey],
jwk[prop as keyof JsonWebKey],
`${prop} mismatch`,
)
}
t.ok(1)
+6 -2
View File
@@ -12,11 +12,15 @@ const stub: Pick<
> = {
// @ts-expect-error
exportJWK(key) {
let k: crypto.KeyObject
if (key instanceof Uint8Array) {
key = crypto.createSecretKey(key)
k = crypto.createSecretKey(key)
} else {
// @ts-expect-error
k = key
}
return (key as crypto.KeyObject).export({ format: 'jwk' })
return (k as unknown as crypto.KeyObject).export({ format: 'jwk' })
},
// @ts-expect-error
importJWK(jwk) {
+1 -1
View File
@@ -10,7 +10,7 @@ function normalize(pem: string) {
export default (
QUnit: QUnit,
lib: typeof jose,
keys: Pick<typeof jose, 'exportJWK' | 'generateKeyPair' | 'generateSecret' | 'importJWK'>,
_keys: Pick<typeof jose, 'exportJWK' | 'generateKeyPair' | 'generateSecret' | 'importJWK'>,
) => {
const { module, test } = QUnit
module('pem.ts')
-10
View File
@@ -1,10 +0,0 @@
// @ts-ignore
const nodeCrypto = await import('crypto').catch(() => {})
export default (byteLength = 16) => {
try {
return crypto.getRandomValues(new Uint8Array(byteLength))
} catch {
return nodeCrypto.randomFillSync(new Uint8Array(byteLength))
}
}

Some files were not shown because too many files have changed in this diff Show More