Skip to content

Commit 49fb62c

Browse files
committedSep 25, 2021
feat: return resolved key when verify and decrypt resolve functions are used
1 parent 6c17d7f commit 49fb62c

File tree

13 files changed

+209
-26
lines changed

13 files changed

+209
-26
lines changed
 

‎src/jwe/compact/decrypt.ts

+25-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
GetKeyFunction,
99
FlattenedJWE,
1010
CompactDecryptResult,
11+
ResolvedKey,
1112
} from '../../types.d'
1213

1314
/**
@@ -20,7 +21,7 @@ export interface CompactDecryptGetKey extends GetKeyFunction<JWEHeaderParameters
2021
* Decrypts a Compact JWE.
2122
*
2223
* @param jwe Compact JWE.
23-
* @param key Private Key or Secret, or a function resolving one, to decrypt the JWE with.
24+
* @param key Private Key or Secret to decrypt the JWE with.
2425
* @param options JWE Decryption options.
2526
*
2627
* @example ESM import
@@ -49,11 +50,26 @@ export interface CompactDecryptGetKey extends GetKeyFunction<JWEHeaderParameters
4950
* console.log(decoder.decode(plaintext))
5051
* ```
5152
*/
53+
async function compactDecrypt(
54+
jwe: string | Uint8Array,
55+
key: KeyLike,
56+
options?: DecryptOptions,
57+
): Promise<CompactDecryptResult>
58+
/**
59+
* @param jwe Compact JWE.
60+
* @param getKey Function resolving Private Key or Secret to decrypt the JWE with.
61+
* @param options JWE Decryption options.
62+
*/
63+
async function compactDecrypt(
64+
jwe: string | Uint8Array,
65+
getKey: CompactDecryptGetKey,
66+
options?: DecryptOptions,
67+
): Promise<CompactDecryptResult & ResolvedKey>
5268
async function compactDecrypt(
5369
jwe: string | Uint8Array,
5470
key: KeyLike | CompactDecryptGetKey,
5571
options?: DecryptOptions,
56-
): Promise<CompactDecryptResult> {
72+
) {
5773
if (jwe instanceof Uint8Array) {
5874
jwe = decoder.decode(jwe)
5975
}
@@ -86,7 +102,13 @@ async function compactDecrypt(
86102
options,
87103
)
88104

89-
return { plaintext: decrypted.plaintext, protectedHeader: decrypted.protectedHeader! }
105+
const result = { plaintext: decrypted.plaintext, protectedHeader: decrypted.protectedHeader! }
106+
107+
if (typeof key === 'function') {
108+
return { ...result, key: decrypted.key }
109+
}
110+
111+
return result
90112
}
91113

92114
export { compactDecrypt }

‎src/jwe/flattened/decrypt.ts

+24-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
JWEHeaderParameters,
1515
DecryptOptions,
1616
GetKeyFunction,
17+
ResolvedKey,
1718
} from '../../types.d'
1819
import { encoder, decoder, concat } from '../../lib/buffer_utils.js'
1920
import cekFactory from '../../lib/cek.js'
@@ -36,7 +37,7 @@ export interface FlattenedDecryptGetKey
3637
* Decrypts a Flattened JWE.
3738
*
3839
* @param jwe Flattened JWE.
39-
* @param key Public Key or Secret, or a function resolving one, to decrypt the JWE with.
40+
* @param key Private Key or Secret to decrypt the JWE with.
4041
* @param options JWE Decryption options.
4142
*
4243
* @example ESM import
@@ -77,11 +78,26 @@ export interface FlattenedDecryptGetKey
7778
* console.log(decoder.decode(additionalAuthenticatedData))
7879
* ```
7980
*/
81+
function flattenedDecrypt(
82+
jwe: FlattenedJWE,
83+
key: KeyLike,
84+
options?: DecryptOptions,
85+
): Promise<FlattenedDecryptResult>
86+
/**
87+
* @param jwe Flattened JWE.
88+
* @param getKey Function resolving Private Key or Secret to decrypt the JWE with.
89+
* @param options JWE Decryption options.
90+
*/
91+
function flattenedDecrypt(
92+
jwe: FlattenedJWE,
93+
getKey: FlattenedDecryptGetKey,
94+
options?: DecryptOptions,
95+
): Promise<FlattenedDecryptResult & ResolvedKey>
8096
async function flattenedDecrypt(
8197
jwe: FlattenedJWE,
8298
key: KeyLike | FlattenedDecryptGetKey,
8399
options?: DecryptOptions,
84-
): Promise<FlattenedDecryptResult> {
100+
) {
85101
if (!isObject(jwe)) {
86102
throw new JWEInvalid('Flattened JWE must be an object')
87103
}
@@ -183,8 +199,10 @@ async function flattenedDecrypt(
183199
encryptedKey = base64url(jwe.encrypted_key!)
184200
}
185201

202+
let resolvedKey = false
186203
if (typeof key === 'function') {
187204
key = await key(parsedProt, jwe)
205+
resolvedKey = true
188206
}
189207

190208
let cek: KeyLike
@@ -240,6 +258,10 @@ async function flattenedDecrypt(
240258
result.unprotectedHeader = jwe.header
241259
}
242260

261+
if (resolvedKey) {
262+
return { ...result, key }
263+
}
264+
243265
return result
244266
}
245267

‎src/jwe/general/decrypt.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
FlattenedJWE,
99
GeneralJWE,
1010
GeneralDecryptResult,
11+
ResolvedKey,
1112
} from '../../types.d'
1213
import isObject from '../../lib/is_object.js'
1314

@@ -21,7 +22,7 @@ export interface GeneralDecryptGetKey extends GetKeyFunction<JWEHeaderParameters
2122
* Decrypts a General JWE.
2223
*
2324
* @param jwe General JWE.
24-
* @param key Private Key or Secret, or a function resolving one, to decrypt the JWE with.
25+
* @param key Private Key or Secret to decrypt the JWE with.
2526
* @param options JWE Decryption options.
2627
*
2728
* @example ESM import
@@ -66,11 +67,26 @@ export interface GeneralDecryptGetKey extends GetKeyFunction<JWEHeaderParameters
6667
* console.log(decoder.decode(additionalAuthenticatedData))
6768
* ```
6869
*/
70+
function generalDecrypt(
71+
jwe: GeneralJWE,
72+
key: KeyLike,
73+
options?: DecryptOptions,
74+
): Promise<GeneralDecryptResult>
75+
/**
76+
* @param jwe General JWE.
77+
* @param getKey Function resolving Private Key or Secret to decrypt the JWE with.
78+
* @param options JWE Decryption options.
79+
*/
80+
function generalDecrypt(
81+
jwe: GeneralJWE,
82+
getKey: GeneralDecryptGetKey,
83+
options?: DecryptOptions,
84+
): Promise<GeneralDecryptResult & ResolvedKey>
6985
async function generalDecrypt(
7086
jwe: GeneralJWE,
7187
key: KeyLike | GeneralDecryptGetKey,
7288
options?: DecryptOptions,
73-
): Promise<GeneralDecryptResult> {
89+
) {
7490
if (!isObject(jwe)) {
7591
throw new JWEInvalid('General JWE must be an object')
7692
}

‎src/jws/compact/verify.ts

+25-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
JWSHeaderParameters,
99
KeyLike,
1010
VerifyOptions,
11+
ResolvedKey,
1112
} from '../../types.d'
1213

1314
/**
@@ -24,7 +25,7 @@ export interface CompactVerifyGetKey
2425
* Verifies the signature and format of and afterwards decodes the Compact JWS.
2526
*
2627
* @param jws Compact JWS.
27-
* @param key Key, or a function resolving a key, to verify the JWS with.
28+
* @param key Key to verify the JWS with.
2829
* @param options JWS Verify options.
2930
*
3031
* @example ESM import
@@ -53,11 +54,26 @@ export interface CompactVerifyGetKey
5354
* console.log(decoder.decode(payload))
5455
* ```
5556
*/
57+
function compactVerify(
58+
jws: string | Uint8Array,
59+
key: KeyLike,
60+
options?: VerifyOptions,
61+
): Promise<CompactVerifyResult>
62+
/**
63+
* @param jws Compact JWS.
64+
* @param getKey Function resolving a key to verify the JWS with.
65+
* @param options JWS Verify options.
66+
*/
67+
function compactVerify(
68+
jws: string | Uint8Array,
69+
getKey: CompactVerifyGetKey,
70+
options?: VerifyOptions,
71+
): Promise<CompactVerifyResult & ResolvedKey>
5672
async function compactVerify(
5773
jws: string | Uint8Array,
5874
key: KeyLike | CompactVerifyGetKey,
5975
options?: VerifyOptions,
60-
): Promise<CompactVerifyResult> {
76+
) {
6177
if (jws instanceof Uint8Array) {
6278
jws = decoder.decode(jws)
6379
}
@@ -81,7 +97,13 @@ async function compactVerify(
8197
options,
8298
)
8399

84-
return { payload: verified.payload, protectedHeader: verified.protectedHeader! }
100+
const result = { payload: verified.payload, protectedHeader: verified.protectedHeader! }
101+
102+
if (typeof key === 'function') {
103+
return { ...result, key: verified.key }
104+
}
105+
106+
return result
85107
}
86108

87109
export { compactVerify }

‎src/jws/flattened/verify.ts

+24-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
JWSHeaderParameters,
1717
VerifyOptions,
1818
GetKeyFunction,
19+
ResolvedKey,
1920
} from '../../types.d'
2021

2122
const checkExtensions = validateCrit.bind(undefined, JWSInvalid, new Map([['b64', true]]))
@@ -35,7 +36,7 @@ export interface FlattenedVerifyGetKey
3536
* Verifies the signature and format of and afterwards decodes the Flattened JWS.
3637
*
3738
* @param jws Flattened JWS.
38-
* @param key Key, or a function resolving a key, to verify the JWS with.
39+
* @param key Key to verify the JWS with.
3940
* @param options JWS Verify options.
4041
*
4142
* @example ESM import
@@ -68,11 +69,26 @@ export interface FlattenedVerifyGetKey
6869
* console.log(decoder.decode(payload))
6970
* ```
7071
*/
72+
function flattenedVerify(
73+
jws: FlattenedJWSInput,
74+
key: KeyLike,
75+
options?: VerifyOptions,
76+
): Promise<FlattenedVerifyResult>
77+
/**
78+
* @param jws Flattened JWS.
79+
* @param getKey Function resolving a key to verify the JWS with.
80+
* @param options JWS Verify options.
81+
*/
82+
function flattenedVerify(
83+
jws: FlattenedJWSInput,
84+
getKey: FlattenedVerifyGetKey,
85+
options?: VerifyOptions,
86+
): Promise<FlattenedVerifyResult & ResolvedKey>
7187
async function flattenedVerify(
7288
jws: FlattenedJWSInput,
7389
key: KeyLike | FlattenedVerifyGetKey,
7490
options?: VerifyOptions,
75-
): Promise<FlattenedVerifyResult> {
91+
) {
7692
if (!isObject(jws)) {
7793
throw new JWSInvalid('Flattened JWS must be an object')
7894
}
@@ -149,8 +165,10 @@ async function flattenedVerify(
149165
throw new JWSInvalid('JWS Payload must be a string or an Uint8Array instance')
150166
}
151167

168+
let resolvedKey = false
152169
if (typeof key === 'function') {
153170
key = await key(parsedProt, jws)
171+
resolvedKey = true
154172
}
155173

156174
checkKeyType(alg, key, 'verify')
@@ -186,6 +204,10 @@ async function flattenedVerify(
186204
result.unprotectedHeader = jws.header
187205
}
188206

207+
if (resolvedKey) {
208+
return { ...result, key }
209+
}
210+
189211
return result
190212
}
191213

‎src/jws/general/verify.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
JWSHeaderParameters,
88
KeyLike,
99
VerifyOptions,
10+
ResolvedKey,
1011
} from '../../types.d'
1112
import { JWSInvalid, JWSSignatureVerificationFailed } from '../../util/errors.js'
1213
import isObject from '../../lib/is_object.js'
@@ -25,7 +26,7 @@ export interface GeneralVerifyGetKey
2526
* Verifies the signature and format of and afterwards decodes the General JWS.
2627
*
2728
* @param jws General JWS.
28-
* @param key Key, or a function resolving a key, to verify the JWS with.
29+
* @param key Key to verify the JWS with.
2930
* @param options JWS Verify options.
3031
*
3132
* @example ESM import
@@ -62,11 +63,26 @@ export interface GeneralVerifyGetKey
6263
* console.log(decoder.decode(payload))
6364
* ```
6465
*/
66+
function generalVerify(
67+
jws: GeneralJWSInput,
68+
key: KeyLike,
69+
options?: VerifyOptions,
70+
): Promise<GeneralVerifyResult>
71+
/**
72+
* @param jws General JWS.
73+
* @param getKey Function resolving a key to verify the JWS with.
74+
* @param options JWS Verify options.
75+
*/
76+
function generalVerify(
77+
jws: GeneralJWSInput,
78+
getKey: GeneralVerifyGetKey,
79+
options?: VerifyOptions,
80+
): Promise<GeneralVerifyResult & ResolvedKey>
6581
async function generalVerify(
6682
jws: GeneralJWSInput,
6783
key: KeyLike | GeneralVerifyGetKey,
6884
options?: VerifyOptions,
69-
): Promise<GeneralVerifyResult> {
85+
) {
7086
if (!isObject(jws)) {
7187
throw new JWSInvalid('General JWS must be an object')
7288
}

‎src/jwt/decrypt.ts

+26-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
JWEHeaderParameters,
99
FlattenedJWE,
1010
JWTDecryptResult,
11+
ResolvedKey,
1112
} from '../types.d'
1213
import jwtPayload from '../lib/jwt_claims_set.js'
1314
import { JWTClaimValidationFailed } from '../util/errors.js'
@@ -27,7 +28,7 @@ export interface JWTDecryptGetKey extends GetKeyFunction<JWEHeaderParameters, Fl
2728
* Verifies the JWT format (to be a JWE Compact format), decrypts the ciphertext, validates the JWT Claims Set.
2829
*
2930
* @param jwt JSON Web Token value (encoded as JWE).
30-
* @param key Private Key or Secret, or a function resolving one, to decrypt and verify the JWT with.
31+
* @param key Private Key or Secret to decrypt and verify the JWT with.
3132
* @param options JWT Decryption and JWT Claims Set validation options.
3233
*
3334
* @example ESM import
@@ -58,12 +59,27 @@ export interface JWTDecryptGetKey extends GetKeyFunction<JWEHeaderParameters, Fl
5859
* console.log(payload)
5960
* ```
6061
*/
62+
async function jwtDecrypt(
63+
jwt: string | Uint8Array,
64+
key: KeyLike,
65+
options?: JWTDecryptOptions,
66+
): Promise<JWTDecryptResult>
67+
/**
68+
* @param jwt JSON Web Token value (encoded as JWE).
69+
* @param getKey Function resolving Private Key or Secret to decrypt and verify the JWT with.
70+
* @param options JWT Decryption and JWT Claims Set validation options.
71+
*/
72+
async function jwtDecrypt(
73+
jwt: string | Uint8Array,
74+
getKey: JWTDecryptGetKey,
75+
options?: JWTDecryptOptions,
76+
): Promise<JWTDecryptResult & ResolvedKey>
6177
async function jwtDecrypt(
6278
jwt: string | Uint8Array,
6379
key: KeyLike | JWTDecryptGetKey,
6480
options?: JWTDecryptOptions,
65-
): Promise<JWTDecryptResult> {
66-
const decrypted = await decrypt(jwt, key, options)
81+
) {
82+
const decrypted = await decrypt(jwt, <Parameters<typeof decrypt>[1]>key, options)
6783
const payload = jwtPayload(decrypted.protectedHeader, decrypted.plaintext, options)
6884

6985
const { protectedHeader } = decrypted
@@ -95,7 +111,13 @@ async function jwtDecrypt(
95111
)
96112
}
97113

98-
return { payload, protectedHeader }
114+
const result = { payload, protectedHeader }
115+
116+
if (typeof key === 'function') {
117+
return { ...result, key: decrypted.key }
118+
}
119+
120+
return result
99121
}
100122

101123
export { jwtDecrypt }

‎src/jwt/verify.ts

+24-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
GetKeyFunction,
99
FlattenedJWSInput,
1010
JWTVerifyResult,
11+
ResolvedKey,
1112
} from '../types.d'
1213
import jwtPayload from '../lib/jwt_claims_set.js'
1314
import { JWTInvalid } from '../util/errors.js'
@@ -30,7 +31,7 @@ export interface JWTVerifyGetKey extends GetKeyFunction<JWSHeaderParameters, Fla
3031
* Verifies the JWT format (to be a JWS Compact format), verifies the JWS signature, validates the JWT Claims Set.
3132
*
3233
* @param jwt JSON Web Token value (encoded as JWS).
33-
* @param key Key, or a function resolving a key, to verify the JWT with.
34+
* @param key Key to verify the JWT with.
3435
* @param options JWT Decryption and JWT Claims Set validation options.
3536
*
3637
* @example ESM import
@@ -61,17 +62,36 @@ export interface JWTVerifyGetKey extends GetKeyFunction<JWSHeaderParameters, Fla
6162
* console.log(payload)
6263
* ```
6364
*/
65+
async function jwtVerify(
66+
jwt: string | Uint8Array,
67+
key: KeyLike,
68+
options?: JWTVerifyOptions,
69+
): Promise<JWTVerifyResult>
70+
/**
71+
* @param jwt JSON Web Token value (encoded as JWS).
72+
* @param getKey Function resolving a key to verify the JWT with.
73+
* @param options JWT Decryption and JWT Claims Set validation options.
74+
*/
75+
async function jwtVerify(
76+
jwt: string | Uint8Array,
77+
getKey: JWTVerifyGetKey,
78+
options?: JWTVerifyOptions,
79+
): Promise<JWTVerifyResult & ResolvedKey>
6480
async function jwtVerify(
6581
jwt: string | Uint8Array,
6682
key: KeyLike | JWTVerifyGetKey,
6783
options?: JWTVerifyOptions,
68-
): Promise<JWTVerifyResult> {
69-
const verified = await verify(jwt, key, options)
84+
) {
85+
const verified = await verify(jwt, <Parameters<typeof verify>[1]>key, options)
7086
if (verified.protectedHeader.crit?.includes('b64') && verified.protectedHeader.b64 === false) {
7187
throw new JWTInvalid('JWTs MUST NOT use unencoded payload')
7288
}
7389
const payload = jwtPayload(verified.protectedHeader, verified.payload, options)
74-
return { payload, protectedHeader: verified.protectedHeader }
90+
const result = { payload, protectedHeader: verified.protectedHeader }
91+
if (typeof key === 'function') {
92+
return { ...result, key: verified.key }
93+
}
94+
return result
7595
}
7696

7797
export { jwtVerify }

‎src/types.d.ts

+7
Original file line numberDiff line numberDiff line change
@@ -721,3 +721,10 @@ export interface JWTDecryptResult {
721721
*/
722722
protectedHeader: JWEHeaderParameters
723723
}
724+
725+
export interface ResolvedKey {
726+
/**
727+
* Key resolved from the key resolver function.
728+
*/
729+
key: KeyLike
730+
}

‎test/jwk/embedded.test.mjs

+5-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ Promise.all([
6565
});
6666

6767
test('EmbeddedJWK', async (t) => {
68-
await t.notThrowsAsync(flattenedVerify(t.context.token, EmbeddedJWK));
68+
await t.notThrowsAsync(async () => {
69+
const { key: resolvedKey } = await flattenedVerify(t.context.token, EmbeddedJWK);
70+
t.truthy(resolvedKey);
71+
t.is(resolvedKey.type, 'public');
72+
});
6973
});
7074

7175
test('EmbeddedJWK requires "jwk" to be an object', async (t) => {

‎test/jwks/remote.test.mjs

+5-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,11 @@ Promise.all([
135135
const jwt = await new SignJWT({})
136136
.setProtectedHeader({ alg: 'PS256', kid: jwk.kid })
137137
.sign(key);
138-
await t.notThrowsAsync(jwtVerify(jwt, JWKS));
138+
await t.notThrowsAsync(async () => {
139+
const { key: resolvedKey } = await jwtVerify(jwt, JWKS);
140+
t.truthy(resolvedKey);
141+
t.is(resolvedKey.type, 'public');
142+
});
139143
}
140144
{
141145
const [jwk] = keys;

‎test/jwt/encrypt.test.mjs

+6-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,11 @@ Promise.all([
9797

9898
const jwt = await enc.encrypt(t.context.secret);
9999

100-
const { plaintext, protectedHeader } = await compactDecrypt(jwt, async (header, token) => {
100+
const {
101+
plaintext,
102+
protectedHeader,
103+
key: resolvedKey,
104+
} = await compactDecrypt(jwt, async (header, token) => {
101105
t.true('alg' in header);
102106
t.true('enc' in header);
103107
t.is(header.alg, 'dir');
@@ -108,6 +112,7 @@ Promise.all([
108112
t.true('tag' in token);
109113
return t.context.secret;
110114
});
115+
t.is(resolvedKey, t.context.secret);
111116
const payload = JSON.parse(new TextDecoder().decode(plaintext));
112117
t.is(payload[claim], expected);
113118
if (duplicate) {

‎test/jwt/sign.test.mjs

+2-1
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,15 @@ Promise.all([
9292
.setProtectedHeader({ alg: 'HS256' })
9393
[method](value)
9494
.sign(t.context.secret);
95-
const { payload } = await compactVerify(jwt, async (header, token) => {
95+
const { payload, key: resolvedKey } = await compactVerify(jwt, async (header, token) => {
9696
t.true('alg' in header);
9797
t.is(header.alg, 'HS256');
9898
t.true('payload' in token);
9999
t.true('protected' in token);
100100
t.true('signature' in token);
101101
return t.context.secret;
102102
});
103+
t.is(resolvedKey, t.context.secret);
103104
const claims = JSON.parse(new TextDecoder().decode(payload));
104105
t.true(claim in claims);
105106
t.is(claims[claim], expected);

0 commit comments

Comments
 (0)
Please sign in to comment.