openid-client
Advanced tools
Comparing version 5.5.0 to 5.6.0
@@ -7,2 +7,3 @@ const { inspect } = require('util'); | ||
const url = require('url'); | ||
const { URL, URLSearchParams } = require('url'); | ||
@@ -66,2 +67,8 @@ const jose = require('jose'); | ||
function getSearchParams(input) { | ||
const parsed = url.parse(input); | ||
if (!parsed.search) return {}; | ||
return querystring.parse(parsed.search.substring(1)); | ||
} | ||
function verifyPresence(payload, jwt, prop) { | ||
@@ -256,9 +263,17 @@ if (payload[prop] === undefined) { | ||
assertIssuerConfiguration(this.issuer, 'authorization_endpoint'); | ||
const target = url.parse(this.issuer.authorization_endpoint, true); | ||
target.search = null; | ||
target.query = { | ||
...target.query, | ||
...authorizationParams.call(this, params), | ||
}; | ||
return url.format(target); | ||
const target = new URL(this.issuer.authorization_endpoint); | ||
for (const [name, value] of Object.entries(authorizationParams.call(this, params))) { | ||
if (Array.isArray(value)) { | ||
target.searchParams.delete(name); | ||
for (const member of value) { | ||
target.searchParams.append(name, member); | ||
} | ||
} else { | ||
target.searchParams.set(name, value); | ||
} | ||
} | ||
// TODO: is the replace needed? | ||
return target.href.replace('+', '%20'); | ||
} | ||
@@ -303,6 +318,5 @@ | ||
const target = url.parse(this.issuer.end_session_endpoint, true); | ||
target.search = null; | ||
defaults( | ||
target.query, | ||
const target = url.parse(this.issuer.end_session_endpoint); | ||
const query = defaults( | ||
getSearchParams(this.issuer.end_session_endpoint), | ||
params, | ||
@@ -316,8 +330,11 @@ { | ||
Object.entries(target.query).forEach(([key, value]) => { | ||
Object.entries(query).forEach(([key, value]) => { | ||
if (value === null || value === undefined) { | ||
delete target.query[key]; | ||
delete query[key]; | ||
} | ||
}); | ||
target.search = null; | ||
target.query = query; | ||
return url.format(target); | ||
@@ -339,3 +356,3 @@ } | ||
case 'GET': | ||
return pickCb(url.parse(input.url, true).query); | ||
return pickCb(getSearchParams(input.url)); | ||
case 'POST': | ||
@@ -365,3 +382,3 @@ if (input.body === undefined) { | ||
} else { | ||
return pickCb(url.parse(input, true).query); | ||
return pickCb(getSearchParams(input)); | ||
} | ||
@@ -713,7 +730,11 @@ } | ||
for (const { keyObject: key } of keystore.all({ | ||
...jose.decodeProtectedHeader(jwe), | ||
const protectedHeader = jose.decodeProtectedHeader(jwe); | ||
for (const key of keystore.all({ | ||
...protectedHeader, | ||
use: 'enc', | ||
})) { | ||
plaintext = await jose.compactDecrypt(jwe, key).then(getPlaintext, () => {}); | ||
plaintext = await jose | ||
.compactDecrypt(jwe, await key.keyObject(protectedHeader.alg)) | ||
.then(getPlaintext, () => {}); | ||
if (plaintext) break; | ||
@@ -1027,3 +1048,9 @@ } | ||
assert.equal(key.type, 'public'); | ||
keys = [{ keyObject: key }]; | ||
keys = [ | ||
{ | ||
keyObject() { | ||
return key; | ||
}, | ||
}, | ||
]; | ||
} catch (err) { | ||
@@ -1053,3 +1080,3 @@ throw new RPError({ | ||
const verified = await jose | ||
.compactVerify(jwt, key instanceof Uint8Array ? key : key.keyObject) | ||
.compactVerify(jwt, key instanceof Uint8Array ? key : await key.keyObject(header.alg)) | ||
.catch(() => {}); | ||
@@ -1208,3 +1235,3 @@ if (verified) { | ||
targetUrl = new url.URL(targetUrl || this.issuer.userinfo_endpoint); | ||
targetUrl = new URL(targetUrl || this.issuer.userinfo_endpoint); | ||
@@ -1214,3 +1241,3 @@ if (via === 'body') { | ||
options.headers['Content-Type'] = 'application/x-www-form-urlencoded'; | ||
options.body = new url.URLSearchParams(); | ||
options.body = new URLSearchParams(); | ||
options.body.append( | ||
@@ -1235,3 +1262,3 @@ 'access_token', | ||
// POST && via header | ||
options.body = new url.URLSearchParams(); | ||
options.body = new URLSearchParams(); | ||
options.headers['Content-Type'] = 'application/x-www-form-urlencoded'; | ||
@@ -1531,3 +1558,3 @@ Object.entries(params).forEach(([key, value]) => { | ||
}) | ||
.sign(symmetric ? key : key.keyObject); | ||
.sign(symmetric ? key : await key.keyObject(signingAlgorithm)); | ||
} | ||
@@ -1556,3 +1583,3 @@ | ||
}) | ||
.encrypt(key instanceof Uint8Array ? key : key.keyObject); | ||
.encrypt(key instanceof Uint8Array ? key : await key.keyObject(fields.alg)); | ||
} | ||
@@ -1643,4 +1670,8 @@ | ||
privateKey = privateKeyInput; | ||
} else if (privateKeyInput[Symbol.toStringTag] === 'CryptoKey') { | ||
privateKey = privateKeyInput; | ||
} else if (jose.cryptoRuntime === 'node:crypto') { | ||
privateKey = crypto.createPrivateKey(privateKeyInput); | ||
} else { | ||
privateKey = crypto.createPrivateKey(privateKeyInput); | ||
throw new TypeError('unrecognized crypto runtime'); | ||
} | ||
@@ -1651,22 +1682,3 @@ | ||
} | ||
let alg; | ||
switch (privateKey.asymmetricKeyType) { | ||
case 'ed25519': | ||
case 'ed448': | ||
alg = 'EdDSA'; | ||
break; | ||
case 'ec': | ||
alg = determineEcAlgorithm(privateKey, privateKeyInput); | ||
break; | ||
case 'rsa': | ||
case rsaPssParams && 'rsa-pss': | ||
alg = determineRsaAlgorithm( | ||
privateKey, | ||
privateKeyInput, | ||
this.issuer.dpop_signing_alg_values_supported, | ||
); | ||
break; | ||
default: | ||
throw new TypeError('unsupported DPoP private key asymmetric key type'); | ||
} | ||
let alg = determineDPoPAlgorithm.call(this, privateKey, privateKeyInput); | ||
@@ -1694,77 +1706,134 @@ if (!alg) { | ||
const RSPS = /^(?:RS|PS)(?:256|384|512)$/; | ||
function determineRsaAlgorithm(privateKey, privateKeyInput, valuesSupported) { | ||
if ( | ||
typeof privateKeyInput === 'object' && | ||
typeof privateKeyInput.key === 'object' && | ||
privateKeyInput.key.alg | ||
) { | ||
return privateKeyInput.key.alg; | ||
function determineDPoPAlgorithmFromCryptoKey(cryptoKey) { | ||
switch (cryptoKey.algorithm.name) { | ||
case 'Ed25519': | ||
case 'Ed448': | ||
return 'EdDSA'; | ||
case 'ECDSA': { | ||
switch (cryptoKey.algorithm.namedCurve) { | ||
case 'P-256': | ||
return 'ES256'; | ||
case 'P-384': | ||
return 'ES384'; | ||
case 'P-521': | ||
return 'ES512'; | ||
default: | ||
break; | ||
} | ||
break; | ||
} | ||
case 'RSASSA-PKCS1-v1_5': | ||
return `RS${cryptoKey.algorithm.hash.name.slice(4)}`; | ||
case 'RSA-PSS': | ||
return `PS${cryptoKey.algorithm.hash.name.slice(4)}`; | ||
default: | ||
throw new TypeError('unsupported DPoP private key'); | ||
} | ||
} | ||
if (Array.isArray(valuesSupported)) { | ||
let candidates = valuesSupported.filter(RegExp.prototype.test.bind(RSPS)); | ||
if (privateKey.asymmetricKeyType === 'rsa-pss') { | ||
candidates = candidates.filter((value) => value.startsWith('PS')); | ||
let determineDPoPAlgorithm; | ||
if (jose.cryptoRuntime === 'node:crypto') { | ||
determineDPoPAlgorithm = function (privateKey, privateKeyInput) { | ||
if (privateKeyInput[Symbol.toStringTag] === 'CryptoKey') { | ||
return determineDPoPAlgorithmFromCryptoKey(privateKey); | ||
} | ||
return ['PS256', 'PS384', 'PS512', 'RS256', 'RS384', 'RS384'].find((preferred) => | ||
candidates.includes(preferred), | ||
); | ||
switch (privateKey.asymmetricKeyType) { | ||
case 'ed25519': | ||
case 'ed448': | ||
return 'EdDSA'; | ||
case 'ec': | ||
return determineEcAlgorithm(privateKey, privateKeyInput); | ||
case 'rsa': | ||
case rsaPssParams && 'rsa-pss': | ||
return determineRsaAlgorithm( | ||
privateKey, | ||
privateKeyInput, | ||
this.issuer.dpop_signing_alg_values_supported, | ||
); | ||
default: | ||
throw new TypeError('unsupported DPoP private key'); | ||
} | ||
}; | ||
const RSPS = /^(?:RS|PS)(?:256|384|512)$/; | ||
function determineRsaAlgorithm(privateKey, privateKeyInput, valuesSupported) { | ||
if ( | ||
typeof privateKeyInput === 'object' && | ||
privateKeyInput.format === 'jwk' && | ||
privateKeyInput.key && | ||
privateKeyInput.key.alg | ||
) { | ||
return privateKeyInput.key.alg; | ||
} | ||
if (Array.isArray(valuesSupported)) { | ||
let candidates = valuesSupported.filter(RegExp.prototype.test.bind(RSPS)); | ||
if (privateKey.asymmetricKeyType === 'rsa-pss') { | ||
candidates = candidates.filter((value) => value.startsWith('PS')); | ||
} | ||
return ['PS256', 'PS384', 'PS512', 'RS256', 'RS384', 'RS384'].find((preferred) => | ||
candidates.includes(preferred), | ||
); | ||
} | ||
return 'PS256'; | ||
} | ||
return 'PS256'; | ||
} | ||
const p256 = Buffer.from([42, 134, 72, 206, 61, 3, 1, 7]); | ||
const p384 = Buffer.from([43, 129, 4, 0, 34]); | ||
const p521 = Buffer.from([43, 129, 4, 0, 35]); | ||
const secp256k1 = Buffer.from([43, 129, 4, 0, 10]); | ||
const p256 = Buffer.from([42, 134, 72, 206, 61, 3, 1, 7]); | ||
const p384 = Buffer.from([43, 129, 4, 0, 34]); | ||
const p521 = Buffer.from([43, 129, 4, 0, 35]); | ||
const secp256k1 = Buffer.from([43, 129, 4, 0, 10]); | ||
function determineEcAlgorithm(privateKey, privateKeyInput) { | ||
// If input was a JWK | ||
switch ( | ||
typeof privateKeyInput === 'object' && | ||
typeof privateKeyInput.key === 'object' && | ||
privateKeyInput.key.crv | ||
) { | ||
case 'P-256': | ||
return 'ES256'; | ||
case 'secp256k1': | ||
return 'ES256K'; | ||
case 'P-384': | ||
return 'ES384'; | ||
case 'P-512': | ||
return 'ES512'; | ||
default: | ||
break; | ||
} | ||
function determineEcAlgorithm(privateKey, privateKeyInput) { | ||
// If input was a JWK | ||
switch ( | ||
typeof privateKeyInput === 'object' && | ||
typeof privateKeyInput.key === 'object' && | ||
privateKeyInput.key.crv | ||
) { | ||
case 'P-256': | ||
const buf = privateKey.export({ format: 'der', type: 'pkcs8' }); | ||
const i = buf[1] < 128 ? 17 : 18; | ||
const len = buf[i]; | ||
const curveOid = buf.slice(i + 1, i + 1 + len); | ||
if (curveOid.equals(p256)) { | ||
return 'ES256'; | ||
case 'secp256k1': | ||
return 'ES256K'; | ||
case 'P-384': | ||
} | ||
if (curveOid.equals(p384)) { | ||
return 'ES384'; | ||
case 'P-512': | ||
} | ||
if (curveOid.equals(p521)) { | ||
return 'ES512'; | ||
default: | ||
break; | ||
} | ||
} | ||
const buf = privateKey.export({ format: 'der', type: 'pkcs8' }); | ||
const i = buf[1] < 128 ? 17 : 18; | ||
const len = buf[i]; | ||
const curveOid = buf.slice(i + 1, i + 1 + len); | ||
if (curveOid.equals(p256)) { | ||
return 'ES256'; | ||
} | ||
if (curveOid.equals(secp256k1)) { | ||
return 'ES256K'; | ||
} | ||
if (curveOid.equals(p384)) { | ||
return 'ES384'; | ||
throw new TypeError('unsupported DPoP private key curve'); | ||
} | ||
if (curveOid.equals(p521)) { | ||
return 'ES512'; | ||
} | ||
if (curveOid.equals(secp256k1)) { | ||
return 'ES256K'; | ||
} | ||
throw new TypeError('unsupported DPoP private key curve'); | ||
} else { | ||
determineDPoPAlgorithm = determineDPoPAlgorithmFromCryptoKey; | ||
} | ||
const jwkCache = new WeakMap(); | ||
async function getJwk(privateKey, privateKeyInput) { | ||
async function getJwk(keyObject, privateKeyInput) { | ||
if ( | ||
jose.cryptoRuntime === 'node:crypto' && | ||
typeof privateKeyInput === 'object' && | ||
typeof privateKeyInput.key === 'object' && | ||
privateKeyInput.key.crv | ||
privateKeyInput.format === 'jwk' | ||
) { | ||
@@ -1778,5 +1847,5 @@ return pick(privateKeyInput.key, 'kty', 'crv', 'x', 'y', 'e', 'n'); | ||
const jwk = pick(await jose.exportJWK(privateKey), 'kty', 'crv', 'x', 'y', 'e', 'n'); | ||
const jwk = pick(await jose.exportJWK(keyObject), 'kty', 'crv', 'x', 'y', 'e', 'n'); | ||
if (isKeyObject(privateKeyInput)) { | ||
if (isKeyObject(privateKeyInput) || jose.cryptoRuntime === 'WebCryptoAPI') { | ||
jwkCache.set(privateKeyInput, jwk); | ||
@@ -1798,2 +1867,3 @@ } | ||
}; | ||
module.exports.BaseClient = BaseClient; |
@@ -73,3 +73,3 @@ const jose = require('jose'); | ||
.setProtectedHeader({ alg, kid: key.jwk && key.jwk.kid }) | ||
.sign(key.keyObject); | ||
.sign(await key.keyObject(alg)); | ||
} | ||
@@ -76,0 +76,0 @@ |
@@ -5,3 +5,2 @@ const jose = require('jose'); | ||
const isPlainObject = require('./is_plain_object'); | ||
const isKeyObject = require('./is_key_object'); | ||
@@ -55,3 +54,3 @@ const internal = Symbol(); | ||
// Ed25519, Ed448, and secp256k1 always have "alg" | ||
// OKP always has use | ||
// OKP always has "use" | ||
if (alg) { | ||
@@ -70,3 +69,16 @@ return new Set([alg]); | ||
if (use === 'sig' || use === undefined) { | ||
algs = algs.concat([`ES${crv.slice(-3)}`.replace('21', '12')]); | ||
switch (crv) { | ||
case 'P-256': | ||
case 'P-384': | ||
algs = algs.concat([`ES${crv.slice(-3)}`.replace('21', '12')]); | ||
break; | ||
case 'P-521': | ||
algs = algs.concat(['ES512']); | ||
break; | ||
case 'secp256k1': | ||
if (jose.cryptoRuntime === 'node:crypto') { | ||
algs = algs.concat(['ES256K']); | ||
} | ||
break; | ||
} | ||
} | ||
@@ -83,3 +95,6 @@ | ||
if (use === 'enc' || use === undefined) { | ||
algs = algs.concat(['RSA-OAEP', 'RSA-OAEP-256', 'RSA-OAEP-384', 'RSA-OAEP-512', 'RSA1_5']); | ||
algs = algs.concat(['RSA-OAEP', 'RSA-OAEP-256', 'RSA-OAEP-384', 'RSA-OAEP-512']); | ||
if (jose.cryptoRuntime === 'node:crypto') { | ||
algs = algs.concat(['RSA1_5']); | ||
} | ||
} | ||
@@ -232,32 +247,21 @@ | ||
const keyObject = await jose.importJWK(jwk, alg || fauxAlg(jwk.kty)).catch(() => {}); | ||
if (!keyObject) continue; | ||
if (keyObject instanceof Uint8Array || keyObject.type === 'secret') { | ||
if (onlyPrivate) { | ||
throw new Error('jwks must only contain private keys'); | ||
} | ||
continue; | ||
} | ||
if (!isKeyObject(keyObject)) { | ||
throw new Error('what?!'); | ||
} | ||
if (onlyPrivate && keyObject.type !== 'private') { | ||
if (onlyPrivate && (jwk.kty === 'oct' || !jwk.d)) { | ||
throw new Error('jwks must only contain private keys'); | ||
} | ||
if (onlyPublic && keyObject.type !== 'public') { | ||
if (onlyPublic && (jwk.d || jwk.k)) { | ||
continue; | ||
} | ||
if (kty === 'RSA' && keyObject.asymmetricKeySize < 2048) { | ||
continue; | ||
} | ||
keys.push({ | ||
jwk: { ...jwk, alg, use }, | ||
keyObject, | ||
async keyObject(alg) { | ||
if (this[alg]) { | ||
return this[alg]; | ||
} | ||
const keyObject = await jose.importJWK(this.jwk, alg); | ||
this[alg] = keyObject; | ||
return keyObject; | ||
}, | ||
get algorithms() { | ||
@@ -264,0 +268,0 @@ Object.defineProperty(this, 'algorithms', { |
{ | ||
"name": "openid-client", | ||
"version": "5.5.0", | ||
"version": "5.6.0", | ||
"description": "OpenID Connect Relying Party (RP, Client) implementation for Node.js runtime, supports passportjs", | ||
@@ -48,3 +48,3 @@ "keywords": [ | ||
"dependencies": { | ||
"jose": "^4.14.4", | ||
"jose": "^4.15.1", | ||
"lru-cache": "^6.0.0", | ||
@@ -55,13 +55,12 @@ "object-hash": "^2.2.0", | ||
"devDependencies": { | ||
"@types/node": "^16.18.31", | ||
"@types/passport": "^1.0.12", | ||
"@types/node": "^16.18.55", | ||
"@types/passport": "^1.0.13", | ||
"base64url": "^3.0.1", | ||
"chai": "^4.3.7", | ||
"jose2": "npm:jose@^2.0.6", | ||
"chai": "^4.3.10", | ||
"mocha": "^10.2.0", | ||
"nock": "^13.3.1", | ||
"nock": "^13.3.3", | ||
"prettier": "^2.8.8", | ||
"readable-mock-req": "^0.2.2", | ||
"sinon": "^9.2.4", | ||
"timekeeper": "^2.2.0" | ||
"timekeeper": "^2.3.1" | ||
}, | ||
@@ -68,0 +67,0 @@ "standard-version": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
134472
10
3690
Updatedjose@^4.15.1