tiny-http-mcp-server 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/composition.json +6 -1
- package/node_modules/jose/LICENSE.md +21 -0
- package/node_modules/jose/README.md +153 -0
- package/node_modules/jose/dist/types/index.d.ts +55 -0
- package/node_modules/jose/dist/types/jwe/compact/decrypt.d.ts +31 -0
- package/node_modules/jose/dist/types/jwe/compact/encrypt.d.ts +65 -0
- package/node_modules/jose/dist/types/jwe/flattened/decrypt.d.ts +31 -0
- package/node_modules/jose/dist/types/jwe/flattened/encrypt.d.ts +83 -0
- package/node_modules/jose/dist/types/jwe/general/decrypt.d.ts +38 -0
- package/node_modules/jose/dist/types/jwe/general/encrypt.d.ts +74 -0
- package/node_modules/jose/dist/types/jwk/embedded.d.ts +17 -0
- package/node_modules/jose/dist/types/jwk/thumbprint.d.ts +32 -0
- package/node_modules/jose/dist/types/jwks/local.d.ts +29 -0
- package/node_modules/jose/dist/types/jwks/remote.d.ts +237 -0
- package/node_modules/jose/dist/types/jws/compact/sign.d.ts +36 -0
- package/node_modules/jose/dist/types/jws/compact/verify.d.ts +33 -0
- package/node_modules/jose/dist/types/jws/flattened/sign.d.ts +42 -0
- package/node_modules/jose/dist/types/jws/flattened/verify.d.ts +33 -0
- package/node_modules/jose/dist/types/jws/general/sign.d.ts +53 -0
- package/node_modules/jose/dist/types/jws/general/verify.d.ts +41 -0
- package/node_modules/jose/dist/types/jwt/decrypt.d.ts +35 -0
- package/node_modules/jose/dist/types/jwt/encrypt.d.ts +91 -0
- package/node_modules/jose/dist/types/jwt/sign.d.ts +43 -0
- package/node_modules/jose/dist/types/jwt/unsecured.d.ts +43 -0
- package/node_modules/jose/dist/types/jwt/verify.d.ts +37 -0
- package/node_modules/jose/dist/types/key/export.d.ts +33 -0
- package/node_modules/jose/dist/types/key/generate_key_pair.d.ts +47 -0
- package/node_modules/jose/dist/types/key/generate_secret.d.ts +35 -0
- package/node_modules/jose/dist/types/key/import.d.ts +83 -0
- package/node_modules/jose/dist/types/types.d.ts +852 -0
- package/node_modules/jose/dist/types/util/base64url.d.ts +9 -0
- package/node_modules/jose/dist/types/util/decode_jwt.d.ts +18 -0
- package/node_modules/jose/dist/types/util/decode_protected_header.d.ts +17 -0
- package/node_modules/jose/dist/types/util/errors.d.ts +213 -0
- package/node_modules/jose/dist/webapi/index.js +32 -0
- package/node_modules/jose/dist/webapi/jwe/compact/decrypt.js +27 -0
- package/node_modules/jose/dist/webapi/jwe/compact/encrypt.js +27 -0
- package/node_modules/jose/dist/webapi/jwe/flattened/decrypt.js +159 -0
- package/node_modules/jose/dist/webapi/jwe/flattened/encrypt.js +167 -0
- package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +31 -0
- package/node_modules/jose/dist/webapi/jwe/general/encrypt.js +182 -0
- package/node_modules/jose/dist/webapi/jwk/embedded.js +17 -0
- package/node_modules/jose/dist/webapi/jwk/thumbprint.js +68 -0
- package/node_modules/jose/dist/webapi/jwks/local.js +119 -0
- package/node_modules/jose/dist/webapi/jwks/remote.js +179 -0
- package/node_modules/jose/dist/webapi/jws/compact/sign.js +18 -0
- package/node_modules/jose/dist/webapi/jws/compact/verify.js +21 -0
- package/node_modules/jose/dist/webapi/jws/flattened/sign.js +89 -0
- package/node_modules/jose/dist/webapi/jws/flattened/verify.js +110 -0
- package/node_modules/jose/dist/webapi/jws/general/sign.js +70 -0
- package/node_modules/jose/dist/webapi/jws/general/verify.js +24 -0
- package/node_modules/jose/dist/webapi/jwt/decrypt.js +23 -0
- package/node_modules/jose/dist/webapi/jwt/encrypt.js +101 -0
- package/node_modules/jose/dist/webapi/jwt/sign.js +52 -0
- package/node_modules/jose/dist/webapi/jwt/unsecured.js +63 -0
- package/node_modules/jose/dist/webapi/jwt/verify.js +15 -0
- package/node_modules/jose/dist/webapi/key/export.js +11 -0
- package/node_modules/jose/dist/webapi/key/generate_key_pair.js +97 -0
- package/node_modules/jose/dist/webapi/key/generate_secret.js +40 -0
- package/node_modules/jose/dist/webapi/key/import.js +57 -0
- package/node_modules/jose/dist/webapi/lib/aesgcmkw.js +15 -0
- package/node_modules/jose/dist/webapi/lib/aeskw.js +25 -0
- package/node_modules/jose/dist/webapi/lib/asn1.js +243 -0
- package/node_modules/jose/dist/webapi/lib/base64.js +22 -0
- package/node_modules/jose/dist/webapi/lib/buffer_utils.js +43 -0
- package/node_modules/jose/dist/webapi/lib/check_key_type.js +122 -0
- package/node_modules/jose/dist/webapi/lib/content_encryption.js +217 -0
- package/node_modules/jose/dist/webapi/lib/crypto_key.js +136 -0
- package/node_modules/jose/dist/webapi/lib/deflate.js +44 -0
- package/node_modules/jose/dist/webapi/lib/ecdhes.js +52 -0
- package/node_modules/jose/dist/webapi/lib/helpers.js +19 -0
- package/node_modules/jose/dist/webapi/lib/invalid_key_input.js +27 -0
- package/node_modules/jose/dist/webapi/lib/is_key_like.js +17 -0
- package/node_modules/jose/dist/webapi/lib/jwk_to_key.js +107 -0
- package/node_modules/jose/dist/webapi/lib/jwt_claims_set.js +238 -0
- package/node_modules/jose/dist/webapi/lib/key_management.js +186 -0
- package/node_modules/jose/dist/webapi/lib/key_to_jwk.js +31 -0
- package/node_modules/jose/dist/webapi/lib/normalize_key.js +166 -0
- package/node_modules/jose/dist/webapi/lib/pbes2kw.js +42 -0
- package/node_modules/jose/dist/webapi/lib/rsaes.js +24 -0
- package/node_modules/jose/dist/webapi/lib/signing.js +68 -0
- package/node_modules/jose/dist/webapi/lib/type_checks.js +40 -0
- package/node_modules/jose/dist/webapi/lib/validate_algorithms.js +10 -0
- package/node_modules/jose/dist/webapi/lib/validate_crit.js +33 -0
- package/node_modules/jose/dist/webapi/util/base64url.js +30 -0
- package/node_modules/jose/dist/webapi/util/decode_jwt.js +32 -0
- package/node_modules/jose/dist/webapi/util/decode_protected_header.js +34 -0
- package/node_modules/jose/dist/webapi/util/errors.js +99 -0
- package/node_modules/jose/package.json +200 -0
- package/node_modules/mcp-oauth/package.json +0 -3
- package/package.json +5 -4
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { JWTClaimValidationFailed, JWTExpired, JWTInvalid } from '../util/errors.js';
|
|
2
|
+
import { encoder, decoder } from './buffer_utils.js';
|
|
3
|
+
import { isObject } from './type_checks.js';
|
|
4
|
+
const epoch = (date) => Math.floor(date.getTime() / 1000);
|
|
5
|
+
const minute = 60;
|
|
6
|
+
const hour = minute * 60;
|
|
7
|
+
const day = hour * 24;
|
|
8
|
+
const week = day * 7;
|
|
9
|
+
const year = day * 365.25;
|
|
10
|
+
const REGEX = /^(\+|\-)? ?(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i;
|
|
11
|
+
export function secs(str) {
|
|
12
|
+
const matched = REGEX.exec(str);
|
|
13
|
+
if (!matched || (matched[4] && matched[1])) {
|
|
14
|
+
throw new TypeError('Invalid time period format');
|
|
15
|
+
}
|
|
16
|
+
const value = parseFloat(matched[2]);
|
|
17
|
+
const unit = matched[3].toLowerCase();
|
|
18
|
+
let numericDate;
|
|
19
|
+
switch (unit) {
|
|
20
|
+
case 'sec':
|
|
21
|
+
case 'secs':
|
|
22
|
+
case 'second':
|
|
23
|
+
case 'seconds':
|
|
24
|
+
case 's':
|
|
25
|
+
numericDate = Math.round(value);
|
|
26
|
+
break;
|
|
27
|
+
case 'minute':
|
|
28
|
+
case 'minutes':
|
|
29
|
+
case 'min':
|
|
30
|
+
case 'mins':
|
|
31
|
+
case 'm':
|
|
32
|
+
numericDate = Math.round(value * minute);
|
|
33
|
+
break;
|
|
34
|
+
case 'hour':
|
|
35
|
+
case 'hours':
|
|
36
|
+
case 'hr':
|
|
37
|
+
case 'hrs':
|
|
38
|
+
case 'h':
|
|
39
|
+
numericDate = Math.round(value * hour);
|
|
40
|
+
break;
|
|
41
|
+
case 'day':
|
|
42
|
+
case 'days':
|
|
43
|
+
case 'd':
|
|
44
|
+
numericDate = Math.round(value * day);
|
|
45
|
+
break;
|
|
46
|
+
case 'week':
|
|
47
|
+
case 'weeks':
|
|
48
|
+
case 'w':
|
|
49
|
+
numericDate = Math.round(value * week);
|
|
50
|
+
break;
|
|
51
|
+
default:
|
|
52
|
+
numericDate = Math.round(value * year);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
if (matched[1] === '-' || matched[4] === 'ago') {
|
|
56
|
+
return -numericDate;
|
|
57
|
+
}
|
|
58
|
+
return numericDate;
|
|
59
|
+
}
|
|
60
|
+
function validateInput(label, input) {
|
|
61
|
+
if (!Number.isFinite(input)) {
|
|
62
|
+
throw new TypeError(`Invalid ${label} input`);
|
|
63
|
+
}
|
|
64
|
+
return input;
|
|
65
|
+
}
|
|
66
|
+
const normalizeTyp = (value) => {
|
|
67
|
+
if (value.includes('/')) {
|
|
68
|
+
return value.toLowerCase();
|
|
69
|
+
}
|
|
70
|
+
return `application/${value.toLowerCase()}`;
|
|
71
|
+
};
|
|
72
|
+
const checkAudiencePresence = (audPayload, audOption) => {
|
|
73
|
+
if (typeof audPayload === 'string') {
|
|
74
|
+
return audOption.includes(audPayload);
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(audPayload)) {
|
|
77
|
+
return audOption.some(Set.prototype.has.bind(new Set(audPayload)));
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
};
|
|
81
|
+
export function validateClaimsSet(protectedHeader, encodedPayload, options = {}) {
|
|
82
|
+
let payload;
|
|
83
|
+
try {
|
|
84
|
+
payload = JSON.parse(decoder.decode(encodedPayload));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
}
|
|
88
|
+
if (!isObject(payload)) {
|
|
89
|
+
throw new JWTInvalid('JWT Claims Set must be a top-level JSON object');
|
|
90
|
+
}
|
|
91
|
+
const { typ } = options;
|
|
92
|
+
if (typ &&
|
|
93
|
+
(typeof protectedHeader.typ !== 'string' ||
|
|
94
|
+
normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ))) {
|
|
95
|
+
throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', payload, 'typ', 'check_failed');
|
|
96
|
+
}
|
|
97
|
+
const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options;
|
|
98
|
+
const presenceCheck = [...requiredClaims];
|
|
99
|
+
if (maxTokenAge !== undefined)
|
|
100
|
+
presenceCheck.push('iat');
|
|
101
|
+
if (audience !== undefined)
|
|
102
|
+
presenceCheck.push('aud');
|
|
103
|
+
if (subject !== undefined)
|
|
104
|
+
presenceCheck.push('sub');
|
|
105
|
+
if (issuer !== undefined)
|
|
106
|
+
presenceCheck.push('iss');
|
|
107
|
+
for (const claim of new Set(presenceCheck.reverse())) {
|
|
108
|
+
if (!(claim in payload)) {
|
|
109
|
+
throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, payload, claim, 'missing');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (issuer &&
|
|
113
|
+
!(Array.isArray(issuer) ? issuer : [issuer]).includes(payload.iss)) {
|
|
114
|
+
throw new JWTClaimValidationFailed('unexpected "iss" claim value', payload, 'iss', 'check_failed');
|
|
115
|
+
}
|
|
116
|
+
if (subject && payload.sub !== subject) {
|
|
117
|
+
throw new JWTClaimValidationFailed('unexpected "sub" claim value', payload, 'sub', 'check_failed');
|
|
118
|
+
}
|
|
119
|
+
if (audience &&
|
|
120
|
+
!checkAudiencePresence(payload.aud, typeof audience === 'string' ? [audience] : audience)) {
|
|
121
|
+
throw new JWTClaimValidationFailed('unexpected "aud" claim value', payload, 'aud', 'check_failed');
|
|
122
|
+
}
|
|
123
|
+
let tolerance;
|
|
124
|
+
switch (typeof options.clockTolerance) {
|
|
125
|
+
case 'string':
|
|
126
|
+
tolerance = secs(options.clockTolerance);
|
|
127
|
+
break;
|
|
128
|
+
case 'number':
|
|
129
|
+
tolerance = options.clockTolerance;
|
|
130
|
+
break;
|
|
131
|
+
case 'undefined':
|
|
132
|
+
tolerance = 0;
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
throw new TypeError('Invalid clockTolerance option type');
|
|
136
|
+
}
|
|
137
|
+
const { currentDate } = options;
|
|
138
|
+
const now = epoch(currentDate || new Date());
|
|
139
|
+
if ((payload.iat !== undefined || maxTokenAge) && typeof payload.iat !== 'number') {
|
|
140
|
+
throw new JWTClaimValidationFailed('"iat" claim must be a number', payload, 'iat', 'invalid');
|
|
141
|
+
}
|
|
142
|
+
if (payload.nbf !== undefined) {
|
|
143
|
+
if (typeof payload.nbf !== 'number') {
|
|
144
|
+
throw new JWTClaimValidationFailed('"nbf" claim must be a number', payload, 'nbf', 'invalid');
|
|
145
|
+
}
|
|
146
|
+
if (payload.nbf > now + tolerance) {
|
|
147
|
+
throw new JWTClaimValidationFailed('"nbf" claim timestamp check failed', payload, 'nbf', 'check_failed');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (payload.exp !== undefined) {
|
|
151
|
+
if (typeof payload.exp !== 'number') {
|
|
152
|
+
throw new JWTClaimValidationFailed('"exp" claim must be a number', payload, 'exp', 'invalid');
|
|
153
|
+
}
|
|
154
|
+
if (payload.exp <= now - tolerance) {
|
|
155
|
+
throw new JWTExpired('"exp" claim timestamp check failed', payload, 'exp', 'check_failed');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (maxTokenAge) {
|
|
159
|
+
const age = now - payload.iat;
|
|
160
|
+
const max = typeof maxTokenAge === 'number' ? maxTokenAge : secs(maxTokenAge);
|
|
161
|
+
if (age - tolerance > max) {
|
|
162
|
+
throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', payload, 'iat', 'check_failed');
|
|
163
|
+
}
|
|
164
|
+
if (age < 0 - tolerance) {
|
|
165
|
+
throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', payload, 'iat', 'check_failed');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return payload;
|
|
169
|
+
}
|
|
170
|
+
export class JWTClaimsBuilder {
|
|
171
|
+
#payload;
|
|
172
|
+
constructor(payload) {
|
|
173
|
+
if (!isObject(payload)) {
|
|
174
|
+
throw new TypeError('JWT Claims Set MUST be an object');
|
|
175
|
+
}
|
|
176
|
+
this.#payload = structuredClone(payload);
|
|
177
|
+
}
|
|
178
|
+
data() {
|
|
179
|
+
return encoder.encode(JSON.stringify(this.#payload));
|
|
180
|
+
}
|
|
181
|
+
get iss() {
|
|
182
|
+
return this.#payload.iss;
|
|
183
|
+
}
|
|
184
|
+
set iss(value) {
|
|
185
|
+
this.#payload.iss = value;
|
|
186
|
+
}
|
|
187
|
+
get sub() {
|
|
188
|
+
return this.#payload.sub;
|
|
189
|
+
}
|
|
190
|
+
set sub(value) {
|
|
191
|
+
this.#payload.sub = value;
|
|
192
|
+
}
|
|
193
|
+
get aud() {
|
|
194
|
+
return this.#payload.aud;
|
|
195
|
+
}
|
|
196
|
+
set aud(value) {
|
|
197
|
+
this.#payload.aud = value;
|
|
198
|
+
}
|
|
199
|
+
set jti(value) {
|
|
200
|
+
this.#payload.jti = value;
|
|
201
|
+
}
|
|
202
|
+
set nbf(value) {
|
|
203
|
+
if (typeof value === 'number') {
|
|
204
|
+
this.#payload.nbf = validateInput('setNotBefore', value);
|
|
205
|
+
}
|
|
206
|
+
else if (value instanceof Date) {
|
|
207
|
+
this.#payload.nbf = validateInput('setNotBefore', epoch(value));
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
this.#payload.nbf = epoch(new Date()) + secs(value);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
set exp(value) {
|
|
214
|
+
if (typeof value === 'number') {
|
|
215
|
+
this.#payload.exp = validateInput('setExpirationTime', value);
|
|
216
|
+
}
|
|
217
|
+
else if (value instanceof Date) {
|
|
218
|
+
this.#payload.exp = validateInput('setExpirationTime', epoch(value));
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
this.#payload.exp = epoch(new Date()) + secs(value);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
set iat(value) {
|
|
225
|
+
if (value === undefined) {
|
|
226
|
+
this.#payload.iat = epoch(new Date());
|
|
227
|
+
}
|
|
228
|
+
else if (value instanceof Date) {
|
|
229
|
+
this.#payload.iat = validateInput('setIssuedAt', epoch(value));
|
|
230
|
+
}
|
|
231
|
+
else if (typeof value === 'string') {
|
|
232
|
+
this.#payload.iat = validateInput('setIssuedAt', epoch(new Date()) + secs(value));
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
this.#payload.iat = validateInput('setIssuedAt', value);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import * as aeskw from './aeskw.js';
|
|
2
|
+
import * as ecdhes from './ecdhes.js';
|
|
3
|
+
import * as pbes2kw from './pbes2kw.js';
|
|
4
|
+
import * as rsaes from './rsaes.js';
|
|
5
|
+
import { encode as b64u } from '../util/base64url.js';
|
|
6
|
+
import { normalizeKey } from './normalize_key.js';
|
|
7
|
+
import { JOSENotSupported, JWEInvalid } from '../util/errors.js';
|
|
8
|
+
import { decodeBase64url } from './helpers.js';
|
|
9
|
+
import { generateCek, cekLength } from './content_encryption.js';
|
|
10
|
+
import { importJWK } from '../key/import.js';
|
|
11
|
+
import { exportJWK } from '../key/export.js';
|
|
12
|
+
import { isObject } from './type_checks.js';
|
|
13
|
+
import { wrap as aesGcmKwWrap, unwrap as aesGcmKwUnwrap } from './aesgcmkw.js';
|
|
14
|
+
import { assertCryptoKey } from './is_key_like.js';
|
|
15
|
+
const unsupportedAlgHeader = 'Invalid or unsupported "alg" (JWE Algorithm) header value';
|
|
16
|
+
function assertEncryptedKey(encryptedKey) {
|
|
17
|
+
if (encryptedKey === undefined)
|
|
18
|
+
throw new JWEInvalid('JWE Encrypted Key missing');
|
|
19
|
+
}
|
|
20
|
+
export async function decryptKeyManagement(alg, key, encryptedKey, joseHeader, options) {
|
|
21
|
+
switch (alg) {
|
|
22
|
+
case 'dir': {
|
|
23
|
+
if (encryptedKey !== undefined)
|
|
24
|
+
throw new JWEInvalid('Encountered unexpected JWE Encrypted Key');
|
|
25
|
+
return key;
|
|
26
|
+
}
|
|
27
|
+
case 'ECDH-ES':
|
|
28
|
+
if (encryptedKey !== undefined)
|
|
29
|
+
throw new JWEInvalid('Encountered unexpected JWE Encrypted Key');
|
|
30
|
+
case 'ECDH-ES+A128KW':
|
|
31
|
+
case 'ECDH-ES+A192KW':
|
|
32
|
+
case 'ECDH-ES+A256KW': {
|
|
33
|
+
if (!isObject(joseHeader.epk))
|
|
34
|
+
throw new JWEInvalid(`JOSE Header "epk" (Ephemeral Public Key) missing or invalid`);
|
|
35
|
+
assertCryptoKey(key);
|
|
36
|
+
if (!ecdhes.allowed(key))
|
|
37
|
+
throw new JOSENotSupported('ECDH with the provided key is not allowed or not supported by your javascript runtime');
|
|
38
|
+
const epk = await importJWK(joseHeader.epk, alg);
|
|
39
|
+
assertCryptoKey(epk);
|
|
40
|
+
let partyUInfo;
|
|
41
|
+
let partyVInfo;
|
|
42
|
+
if (joseHeader.apu !== undefined) {
|
|
43
|
+
if (typeof joseHeader.apu !== 'string')
|
|
44
|
+
throw new JWEInvalid(`JOSE Header "apu" (Agreement PartyUInfo) invalid`);
|
|
45
|
+
partyUInfo = decodeBase64url(joseHeader.apu, 'apu', JWEInvalid);
|
|
46
|
+
}
|
|
47
|
+
if (joseHeader.apv !== undefined) {
|
|
48
|
+
if (typeof joseHeader.apv !== 'string')
|
|
49
|
+
throw new JWEInvalid(`JOSE Header "apv" (Agreement PartyVInfo) invalid`);
|
|
50
|
+
partyVInfo = decodeBase64url(joseHeader.apv, 'apv', JWEInvalid);
|
|
51
|
+
}
|
|
52
|
+
const sharedSecret = await ecdhes.deriveKey(epk, key, alg === 'ECDH-ES' ? joseHeader.enc : alg, alg === 'ECDH-ES' ? cekLength(joseHeader.enc) : parseInt(alg.slice(-5, -2), 10), partyUInfo, partyVInfo);
|
|
53
|
+
if (alg === 'ECDH-ES')
|
|
54
|
+
return sharedSecret;
|
|
55
|
+
assertEncryptedKey(encryptedKey);
|
|
56
|
+
return aeskw.unwrap(alg.slice(-6), sharedSecret, encryptedKey);
|
|
57
|
+
}
|
|
58
|
+
case 'RSA-OAEP':
|
|
59
|
+
case 'RSA-OAEP-256':
|
|
60
|
+
case 'RSA-OAEP-384':
|
|
61
|
+
case 'RSA-OAEP-512': {
|
|
62
|
+
assertEncryptedKey(encryptedKey);
|
|
63
|
+
assertCryptoKey(key);
|
|
64
|
+
return rsaes.decrypt(alg, key, encryptedKey);
|
|
65
|
+
}
|
|
66
|
+
case 'PBES2-HS256+A128KW':
|
|
67
|
+
case 'PBES2-HS384+A192KW':
|
|
68
|
+
case 'PBES2-HS512+A256KW': {
|
|
69
|
+
assertEncryptedKey(encryptedKey);
|
|
70
|
+
if (typeof joseHeader.p2c !== 'number')
|
|
71
|
+
throw new JWEInvalid(`JOSE Header "p2c" (PBES2 Count) missing or invalid`);
|
|
72
|
+
const p2cLimit = options?.maxPBES2Count || 10_000;
|
|
73
|
+
if (joseHeader.p2c > p2cLimit)
|
|
74
|
+
throw new JWEInvalid(`JOSE Header "p2c" (PBES2 Count) out is of acceptable bounds`);
|
|
75
|
+
if (typeof joseHeader.p2s !== 'string')
|
|
76
|
+
throw new JWEInvalid(`JOSE Header "p2s" (PBES2 Salt) missing or invalid`);
|
|
77
|
+
let p2s;
|
|
78
|
+
p2s = decodeBase64url(joseHeader.p2s, 'p2s', JWEInvalid);
|
|
79
|
+
return pbes2kw.unwrap(alg, key, encryptedKey, joseHeader.p2c, p2s);
|
|
80
|
+
}
|
|
81
|
+
case 'A128KW':
|
|
82
|
+
case 'A192KW':
|
|
83
|
+
case 'A256KW': {
|
|
84
|
+
assertEncryptedKey(encryptedKey);
|
|
85
|
+
return aeskw.unwrap(alg, key, encryptedKey);
|
|
86
|
+
}
|
|
87
|
+
case 'A128GCMKW':
|
|
88
|
+
case 'A192GCMKW':
|
|
89
|
+
case 'A256GCMKW': {
|
|
90
|
+
assertEncryptedKey(encryptedKey);
|
|
91
|
+
if (typeof joseHeader.iv !== 'string')
|
|
92
|
+
throw new JWEInvalid(`JOSE Header "iv" (Initialization Vector) missing or invalid`);
|
|
93
|
+
if (typeof joseHeader.tag !== 'string')
|
|
94
|
+
throw new JWEInvalid(`JOSE Header "tag" (Authentication Tag) missing or invalid`);
|
|
95
|
+
let iv;
|
|
96
|
+
iv = decodeBase64url(joseHeader.iv, 'iv', JWEInvalid);
|
|
97
|
+
let tag;
|
|
98
|
+
tag = decodeBase64url(joseHeader.tag, 'tag', JWEInvalid);
|
|
99
|
+
return aesGcmKwUnwrap(alg, key, encryptedKey, iv, tag);
|
|
100
|
+
}
|
|
101
|
+
default: {
|
|
102
|
+
throw new JOSENotSupported(unsupportedAlgHeader);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export async function encryptKeyManagement(alg, enc, key, providedCek, providedParameters = {}) {
|
|
107
|
+
let encryptedKey;
|
|
108
|
+
let parameters;
|
|
109
|
+
let cek;
|
|
110
|
+
switch (alg) {
|
|
111
|
+
case 'dir': {
|
|
112
|
+
cek = key;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case 'ECDH-ES':
|
|
116
|
+
case 'ECDH-ES+A128KW':
|
|
117
|
+
case 'ECDH-ES+A192KW':
|
|
118
|
+
case 'ECDH-ES+A256KW': {
|
|
119
|
+
assertCryptoKey(key);
|
|
120
|
+
if (!ecdhes.allowed(key)) {
|
|
121
|
+
throw new JOSENotSupported('ECDH with the provided key is not allowed or not supported by your javascript runtime');
|
|
122
|
+
}
|
|
123
|
+
const { apu, apv } = providedParameters;
|
|
124
|
+
let ephemeralKey;
|
|
125
|
+
if (providedParameters.epk) {
|
|
126
|
+
ephemeralKey = (await normalizeKey(providedParameters.epk, alg));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
ephemeralKey = (await crypto.subtle.generateKey(key.algorithm, true, ['deriveBits'])).privateKey;
|
|
130
|
+
}
|
|
131
|
+
const { x, y, crv, kty } = await exportJWK(ephemeralKey);
|
|
132
|
+
const sharedSecret = await ecdhes.deriveKey(key, ephemeralKey, alg === 'ECDH-ES' ? enc : alg, alg === 'ECDH-ES' ? cekLength(enc) : parseInt(alg.slice(-5, -2), 10), apu, apv);
|
|
133
|
+
parameters = { epk: { x, crv, kty } };
|
|
134
|
+
if (kty === 'EC')
|
|
135
|
+
parameters.epk.y = y;
|
|
136
|
+
if (apu)
|
|
137
|
+
parameters.apu = b64u(apu);
|
|
138
|
+
if (apv)
|
|
139
|
+
parameters.apv = b64u(apv);
|
|
140
|
+
if (alg === 'ECDH-ES') {
|
|
141
|
+
cek = sharedSecret;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
cek = providedCek || generateCek(enc);
|
|
145
|
+
const kwAlg = alg.slice(-6);
|
|
146
|
+
encryptedKey = await aeskw.wrap(kwAlg, sharedSecret, cek);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case 'RSA-OAEP':
|
|
150
|
+
case 'RSA-OAEP-256':
|
|
151
|
+
case 'RSA-OAEP-384':
|
|
152
|
+
case 'RSA-OAEP-512': {
|
|
153
|
+
cek = providedCek || generateCek(enc);
|
|
154
|
+
assertCryptoKey(key);
|
|
155
|
+
encryptedKey = await rsaes.encrypt(alg, key, cek);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
case 'PBES2-HS256+A128KW':
|
|
159
|
+
case 'PBES2-HS384+A192KW':
|
|
160
|
+
case 'PBES2-HS512+A256KW': {
|
|
161
|
+
cek = providedCek || generateCek(enc);
|
|
162
|
+
const { p2c, p2s } = providedParameters;
|
|
163
|
+
({ encryptedKey, ...parameters } = await pbes2kw.wrap(alg, key, cek, p2c, p2s));
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case 'A128KW':
|
|
167
|
+
case 'A192KW':
|
|
168
|
+
case 'A256KW': {
|
|
169
|
+
cek = providedCek || generateCek(enc);
|
|
170
|
+
encryptedKey = await aeskw.wrap(alg, key, cek);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case 'A128GCMKW':
|
|
174
|
+
case 'A192GCMKW':
|
|
175
|
+
case 'A256GCMKW': {
|
|
176
|
+
cek = providedCek || generateCek(enc);
|
|
177
|
+
const { iv } = providedParameters;
|
|
178
|
+
({ encryptedKey, ...parameters } = await aesGcmKwWrap(alg, key, cek, iv));
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
default: {
|
|
182
|
+
throw new JOSENotSupported(unsupportedAlgHeader);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { cek, encryptedKey, parameters };
|
|
186
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { invalidKeyInput } from './invalid_key_input.js';
|
|
2
|
+
import { encode as b64u } from '../util/base64url.js';
|
|
3
|
+
import { isCryptoKey, isKeyObject } from './is_key_like.js';
|
|
4
|
+
export async function keyToJWK(key) {
|
|
5
|
+
if (isKeyObject(key)) {
|
|
6
|
+
if (key.type === 'secret') {
|
|
7
|
+
key = key.export();
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
return key.export({ format: 'jwk' });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (key instanceof Uint8Array) {
|
|
14
|
+
return {
|
|
15
|
+
kty: 'oct',
|
|
16
|
+
k: b64u(key),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (!isCryptoKey(key)) {
|
|
20
|
+
throw new TypeError(invalidKeyInput(key, 'CryptoKey', 'KeyObject', 'Uint8Array'));
|
|
21
|
+
}
|
|
22
|
+
if (!key.extractable) {
|
|
23
|
+
throw new TypeError('non-extractable CryptoKey cannot be exported as a JWK');
|
|
24
|
+
}
|
|
25
|
+
const { ext, key_ops, alg, use, ...jwk } = await crypto.subtle.exportKey('jwk', key);
|
|
26
|
+
if (jwk.kty === 'AKP') {
|
|
27
|
+
;
|
|
28
|
+
jwk.alg = alg;
|
|
29
|
+
}
|
|
30
|
+
return jwk;
|
|
31
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { isJWK } from './type_checks.js';
|
|
2
|
+
import { decode } from '../util/base64url.js';
|
|
3
|
+
import { jwkToKey } from './jwk_to_key.js';
|
|
4
|
+
import { isCryptoKey, isKeyObject } from './is_key_like.js';
|
|
5
|
+
const unusableForAlg = 'given KeyObject instance cannot be used for this algorithm';
|
|
6
|
+
let cache;
|
|
7
|
+
const handleJWK = async (key, jwk, alg, freeze = false) => {
|
|
8
|
+
cache ||= new WeakMap();
|
|
9
|
+
let cached = cache.get(key);
|
|
10
|
+
if (cached?.[alg]) {
|
|
11
|
+
return cached[alg];
|
|
12
|
+
}
|
|
13
|
+
const cryptoKey = await jwkToKey({ ...jwk, alg });
|
|
14
|
+
if (freeze)
|
|
15
|
+
Object.freeze(key);
|
|
16
|
+
if (!cached) {
|
|
17
|
+
cache.set(key, { [alg]: cryptoKey });
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
cached[alg] = cryptoKey;
|
|
21
|
+
}
|
|
22
|
+
return cryptoKey;
|
|
23
|
+
};
|
|
24
|
+
const handleKeyObject = (keyObject, alg) => {
|
|
25
|
+
cache ||= new WeakMap();
|
|
26
|
+
let cached = cache.get(keyObject);
|
|
27
|
+
if (cached?.[alg]) {
|
|
28
|
+
return cached[alg];
|
|
29
|
+
}
|
|
30
|
+
const isPublic = keyObject.type === 'public';
|
|
31
|
+
const extractable = isPublic ? true : false;
|
|
32
|
+
let cryptoKey;
|
|
33
|
+
if (keyObject.asymmetricKeyType === 'x25519') {
|
|
34
|
+
switch (alg) {
|
|
35
|
+
case 'ECDH-ES':
|
|
36
|
+
case 'ECDH-ES+A128KW':
|
|
37
|
+
case 'ECDH-ES+A192KW':
|
|
38
|
+
case 'ECDH-ES+A256KW':
|
|
39
|
+
break;
|
|
40
|
+
default:
|
|
41
|
+
throw new TypeError(unusableForAlg);
|
|
42
|
+
}
|
|
43
|
+
cryptoKey = keyObject.toCryptoKey(keyObject.asymmetricKeyType, extractable, isPublic ? [] : ['deriveBits']);
|
|
44
|
+
}
|
|
45
|
+
if (keyObject.asymmetricKeyType === 'ed25519') {
|
|
46
|
+
if (alg !== 'EdDSA' && alg !== 'Ed25519') {
|
|
47
|
+
throw new TypeError(unusableForAlg);
|
|
48
|
+
}
|
|
49
|
+
cryptoKey = keyObject.toCryptoKey(keyObject.asymmetricKeyType, extractable, [
|
|
50
|
+
isPublic ? 'verify' : 'sign',
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
switch (keyObject.asymmetricKeyType) {
|
|
54
|
+
case 'ml-dsa-44':
|
|
55
|
+
case 'ml-dsa-65':
|
|
56
|
+
case 'ml-dsa-87': {
|
|
57
|
+
if (alg !== keyObject.asymmetricKeyType.toUpperCase()) {
|
|
58
|
+
throw new TypeError(unusableForAlg);
|
|
59
|
+
}
|
|
60
|
+
cryptoKey = keyObject.toCryptoKey(keyObject.asymmetricKeyType, extractable, [
|
|
61
|
+
isPublic ? 'verify' : 'sign',
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (keyObject.asymmetricKeyType === 'rsa') {
|
|
66
|
+
let hash;
|
|
67
|
+
switch (alg) {
|
|
68
|
+
case 'RSA-OAEP':
|
|
69
|
+
hash = 'SHA-1';
|
|
70
|
+
break;
|
|
71
|
+
case 'RS256':
|
|
72
|
+
case 'PS256':
|
|
73
|
+
case 'RSA-OAEP-256':
|
|
74
|
+
hash = 'SHA-256';
|
|
75
|
+
break;
|
|
76
|
+
case 'RS384':
|
|
77
|
+
case 'PS384':
|
|
78
|
+
case 'RSA-OAEP-384':
|
|
79
|
+
hash = 'SHA-384';
|
|
80
|
+
break;
|
|
81
|
+
case 'RS512':
|
|
82
|
+
case 'PS512':
|
|
83
|
+
case 'RSA-OAEP-512':
|
|
84
|
+
hash = 'SHA-512';
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
throw new TypeError(unusableForAlg);
|
|
88
|
+
}
|
|
89
|
+
if (alg.startsWith('RSA-OAEP')) {
|
|
90
|
+
return keyObject.toCryptoKey({
|
|
91
|
+
name: 'RSA-OAEP',
|
|
92
|
+
hash,
|
|
93
|
+
}, extractable, isPublic ? ['encrypt'] : ['decrypt']);
|
|
94
|
+
}
|
|
95
|
+
cryptoKey = keyObject.toCryptoKey({
|
|
96
|
+
name: alg.startsWith('PS') ? 'RSA-PSS' : 'RSASSA-PKCS1-v1_5',
|
|
97
|
+
hash,
|
|
98
|
+
}, extractable, [isPublic ? 'verify' : 'sign']);
|
|
99
|
+
}
|
|
100
|
+
if (keyObject.asymmetricKeyType === 'ec') {
|
|
101
|
+
const nist = new Map([
|
|
102
|
+
['prime256v1', 'P-256'],
|
|
103
|
+
['secp384r1', 'P-384'],
|
|
104
|
+
['secp521r1', 'P-521'],
|
|
105
|
+
]);
|
|
106
|
+
const namedCurve = nist.get(keyObject.asymmetricKeyDetails?.namedCurve);
|
|
107
|
+
if (!namedCurve) {
|
|
108
|
+
throw new TypeError(unusableForAlg);
|
|
109
|
+
}
|
|
110
|
+
const expectedCurve = { ES256: 'P-256', ES384: 'P-384', ES512: 'P-521' };
|
|
111
|
+
if (expectedCurve[alg] && namedCurve === expectedCurve[alg]) {
|
|
112
|
+
cryptoKey = keyObject.toCryptoKey({
|
|
113
|
+
name: 'ECDSA',
|
|
114
|
+
namedCurve,
|
|
115
|
+
}, extractable, [isPublic ? 'verify' : 'sign']);
|
|
116
|
+
}
|
|
117
|
+
if (alg.startsWith('ECDH-ES')) {
|
|
118
|
+
cryptoKey = keyObject.toCryptoKey({
|
|
119
|
+
name: 'ECDH',
|
|
120
|
+
namedCurve,
|
|
121
|
+
}, extractable, isPublic ? [] : ['deriveBits']);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!cryptoKey) {
|
|
125
|
+
throw new TypeError(unusableForAlg);
|
|
126
|
+
}
|
|
127
|
+
if (!cached) {
|
|
128
|
+
cache.set(keyObject, { [alg]: cryptoKey });
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
cached[alg] = cryptoKey;
|
|
132
|
+
}
|
|
133
|
+
return cryptoKey;
|
|
134
|
+
};
|
|
135
|
+
export async function normalizeKey(key, alg) {
|
|
136
|
+
if (key instanceof Uint8Array) {
|
|
137
|
+
return key;
|
|
138
|
+
}
|
|
139
|
+
if (isCryptoKey(key)) {
|
|
140
|
+
return key;
|
|
141
|
+
}
|
|
142
|
+
if (isKeyObject(key)) {
|
|
143
|
+
if (key.type === 'secret') {
|
|
144
|
+
return key.export();
|
|
145
|
+
}
|
|
146
|
+
if ('toCryptoKey' in key && typeof key.toCryptoKey === 'function') {
|
|
147
|
+
try {
|
|
148
|
+
return handleKeyObject(key, alg);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
if (err instanceof TypeError) {
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
let jwk = key.export({ format: 'jwk' });
|
|
157
|
+
return handleJWK(key, jwk, alg);
|
|
158
|
+
}
|
|
159
|
+
if (isJWK(key)) {
|
|
160
|
+
if (key.k) {
|
|
161
|
+
return decode(key.k);
|
|
162
|
+
}
|
|
163
|
+
return handleJWK(key, key, alg, true);
|
|
164
|
+
}
|
|
165
|
+
throw new Error('unreachable');
|
|
166
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { encode as b64u } from '../util/base64url.js';
|
|
2
|
+
import * as aeskw from './aeskw.js';
|
|
3
|
+
import { checkEncCryptoKey } from './crypto_key.js';
|
|
4
|
+
import { concat, encode } from './buffer_utils.js';
|
|
5
|
+
import { JWEInvalid } from '../util/errors.js';
|
|
6
|
+
function getCryptoKey(key, alg) {
|
|
7
|
+
if (key instanceof Uint8Array) {
|
|
8
|
+
return crypto.subtle.importKey('raw', key, 'PBKDF2', false, [
|
|
9
|
+
'deriveBits',
|
|
10
|
+
]);
|
|
11
|
+
}
|
|
12
|
+
checkEncCryptoKey(key, alg, 'deriveBits');
|
|
13
|
+
return key;
|
|
14
|
+
}
|
|
15
|
+
const concatSalt = (alg, p2sInput) => concat(encode(alg), Uint8Array.of(0x00), p2sInput);
|
|
16
|
+
async function deriveKey(p2s, alg, p2c, key) {
|
|
17
|
+
if (!(p2s instanceof Uint8Array) || p2s.length < 8) {
|
|
18
|
+
throw new JWEInvalid('PBES2 Salt Input must be 8 or more octets');
|
|
19
|
+
}
|
|
20
|
+
if (!Number.isSafeInteger(p2c) || Math.sign(p2c) !== 1) {
|
|
21
|
+
throw new JWEInvalid('PBES2 Count Input must be a positive integer');
|
|
22
|
+
}
|
|
23
|
+
const salt = concatSalt(alg, p2s);
|
|
24
|
+
const keylen = parseInt(alg.slice(13, 16), 10);
|
|
25
|
+
const subtleAlg = {
|
|
26
|
+
hash: `SHA-${alg.slice(8, 11)}`,
|
|
27
|
+
iterations: p2c,
|
|
28
|
+
name: 'PBKDF2',
|
|
29
|
+
salt,
|
|
30
|
+
};
|
|
31
|
+
const cryptoKey = await getCryptoKey(key, alg);
|
|
32
|
+
return new Uint8Array(await crypto.subtle.deriveBits(subtleAlg, cryptoKey, keylen));
|
|
33
|
+
}
|
|
34
|
+
export async function wrap(alg, key, cek, p2c = 2048, p2s = crypto.getRandomValues(new Uint8Array(16))) {
|
|
35
|
+
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
36
|
+
const encryptedKey = await aeskw.wrap(alg.slice(-6), derived, cek);
|
|
37
|
+
return { encryptedKey, p2c, p2s: b64u(p2s) };
|
|
38
|
+
}
|
|
39
|
+
export async function unwrap(alg, key, encryptedKey, p2c, p2s) {
|
|
40
|
+
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
41
|
+
return aeskw.unwrap(alg.slice(-6), derived, encryptedKey);
|
|
42
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { checkEncCryptoKey } from './crypto_key.js';
|
|
2
|
+
import { checkKeyLength } from './signing.js';
|
|
3
|
+
import { JOSENotSupported } from '../util/errors.js';
|
|
4
|
+
const subtleAlgorithm = (alg) => {
|
|
5
|
+
switch (alg) {
|
|
6
|
+
case 'RSA-OAEP':
|
|
7
|
+
case 'RSA-OAEP-256':
|
|
8
|
+
case 'RSA-OAEP-384':
|
|
9
|
+
case 'RSA-OAEP-512':
|
|
10
|
+
return 'RSA-OAEP';
|
|
11
|
+
default:
|
|
12
|
+
throw new JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
export async function encrypt(alg, key, cek) {
|
|
16
|
+
checkEncCryptoKey(key, alg, 'encrypt');
|
|
17
|
+
checkKeyLength(alg, key);
|
|
18
|
+
return new Uint8Array(await crypto.subtle.encrypt(subtleAlgorithm(alg), key, cek));
|
|
19
|
+
}
|
|
20
|
+
export async function decrypt(alg, key, encryptedKey) {
|
|
21
|
+
checkEncCryptoKey(key, alg, 'decrypt');
|
|
22
|
+
checkKeyLength(alg, key);
|
|
23
|
+
return new Uint8Array(await crypto.subtle.decrypt(subtleAlgorithm(alg), key, encryptedKey));
|
|
24
|
+
}
|