unjwt 0.6.1 → 0.7.1
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/README.md +27 -363
- package/dist/_chunks/_internal.mjs +88 -0
- package/dist/_chunks/index.d.mts +537 -80
- package/dist/_chunks/jwe.d.mts +84 -0
- package/dist/_chunks/jwe.mjs +407 -0
- package/dist/_chunks/{_crypto.mjs → jwk.mjs} +637 -26
- package/dist/_chunks/jws.d.mts +111 -0
- package/dist/_chunks/jws.mjs +264 -2031
- package/dist/_chunks/libs/unsecure.d.mts +94 -0
- package/dist/_chunks/libs/unsecure.mjs +149 -0
- package/dist/_chunks/utils.mjs +222 -211
- package/dist/adapters/h3v1/index.d.mts +17 -17
- package/dist/adapters/h3v1/index.mjs +15 -77
- package/dist/adapters/h3v2/index.d.mts +17 -17
- package/dist/adapters/h3v2/index.mjs +15 -39
- package/dist/core/jwe.d.mts +3 -40
- package/dist/core/jwe.mjs +3 -161
- package/dist/core/jwk.d.mts +205 -49
- package/dist/core/jwk.mjs +3 -686
- package/dist/core/jws.d.mts +3 -31
- package/dist/core/jws.mjs +3 -104
- package/dist/core/utils/index.d.mts +96 -63
- package/dist/core/utils/index.mjs +3 -2
- package/dist/index.d.mts +6 -7
- package/dist/index.mjs +5 -5
- package/package.json +24 -19
- package/dist/_chunks/error.d.mts +0 -67
- package/dist/_chunks/jwk.d.mts +0 -708
- package/dist/core/_crypto/index.d.mts +0 -71
- package/dist/core/_crypto/index.mjs +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { D as JWTError, a as isCryptoKeyPair, i as isCryptoKey, n as concatUint8Arrays, o as isJWK, r as isAsymmetricJWK, s as isJWKSet, t as assertCryptoKey } from "./utils.mjs";
|
|
2
|
+
import { a as base64UrlEncode, i as base64UrlDecode, n as base64Decode, r as base64Encode, s as textEncoder, t as secureRandomBytes, u as sanitizeObjectCopy } from "./libs/unsecure.mjs";
|
|
3
3
|
async function sign(alg, key, data) {
|
|
4
4
|
checkSigCryptoKey(key, alg, "sign");
|
|
5
5
|
const signature = await crypto.subtle.sign(subtleAlgorithm(alg, key.algorithm), key, data);
|
|
@@ -109,8 +109,6 @@ function getNamedCurve$1(alg) {
|
|
|
109
109
|
function checkUsage$4(key, usage) {
|
|
110
110
|
if (usage && !key.usages.includes(usage)) throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
|
111
111
|
}
|
|
112
|
-
//#endregion
|
|
113
|
-
//#region src/core/_crypto/_aes.ts
|
|
114
112
|
const IV_BIT_LENGTHS = {
|
|
115
113
|
A128GCM: 96,
|
|
116
114
|
A128GCMKW: 96,
|
|
@@ -386,8 +384,6 @@ function uint64be(value) {
|
|
|
386
384
|
writeUInt32BE$1(buf, low, 4);
|
|
387
385
|
return buf;
|
|
388
386
|
}
|
|
389
|
-
//#endregion
|
|
390
|
-
//#region src/core/_crypto/_key-codec.ts
|
|
391
387
|
async function jwkTokey(jwk) {
|
|
392
388
|
if (!jwk.alg) throw new TypeError("\"alg\" argument is required when \"jwk.alg\" is not present");
|
|
393
389
|
const { algorithm, keyUsages } = subtleMapping(jwk);
|
|
@@ -499,8 +495,6 @@ function subtleMapping(jwk) {
|
|
|
499
495
|
keyUsages
|
|
500
496
|
};
|
|
501
497
|
}
|
|
502
|
-
//#endregion
|
|
503
|
-
//#region src/core/_crypto/_rsa.ts
|
|
504
498
|
async function encryptRSAES(alg, key, cek) {
|
|
505
499
|
checkRSACryptoKey(key, alg, "encrypt");
|
|
506
500
|
checkRSAModulusLength(alg, key);
|
|
@@ -535,8 +529,6 @@ function getHashLength(hash) {
|
|
|
535
529
|
function checkUsage$2(key, usage) {
|
|
536
530
|
if (usage && !key.usages.includes(usage)) throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
|
537
531
|
}
|
|
538
|
-
//#endregion
|
|
539
|
-
//#region src/core/_crypto/_ecdh.ts
|
|
540
532
|
async function deriveECDHESKey(publicKey, privateKey, algorithm, keyLength, apu = new Uint8Array(0), apv = new Uint8Array(0)) {
|
|
541
533
|
checkECDHCryptoKey(publicKey, "ECDH");
|
|
542
534
|
checkECDHCryptoKey(privateKey, "ECDH", "deriveBits");
|
|
@@ -601,8 +593,8 @@ function uint32be(value) {
|
|
|
601
593
|
writeUInt32BE(buf, value, 0);
|
|
602
594
|
return buf;
|
|
603
595
|
}
|
|
604
|
-
|
|
605
|
-
|
|
596
|
+
const DEFAULT_PBES2_MIN_ITERATIONS = 1e3;
|
|
597
|
+
const DEFAULT_PBES2_MAX_ITERATIONS = 1e6;
|
|
606
598
|
async function pbes2Wrap(alg, key, cek, p2c, p2s) {
|
|
607
599
|
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
608
600
|
return {
|
|
@@ -611,7 +603,9 @@ async function pbes2Wrap(alg, key, cek, p2c, p2s) {
|
|
|
611
603
|
p2s: base64UrlEncode(p2s)
|
|
612
604
|
};
|
|
613
605
|
}
|
|
614
|
-
async function pbes2Unwrap(alg, key, encryptedKey, p2c, p2s) {
|
|
606
|
+
async function pbes2Unwrap(alg, key, encryptedKey, p2c, p2s, minIterations = DEFAULT_PBES2_MIN_ITERATIONS, maxIterations = DEFAULT_PBES2_MAX_ITERATIONS) {
|
|
607
|
+
if (p2c < minIterations) throw new JWTError(`PBES2 "p2c" below the minimum of ${minIterations} iterations (got ${p2c}).`, "ERR_JWE_INVALID");
|
|
608
|
+
if (p2c > maxIterations) throw new JWTError(`PBES2 "p2c" above the maximum of ${maxIterations} iterations (got ${p2c}).`, "ERR_JWE_INVALID");
|
|
615
609
|
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
616
610
|
return aesKwUnwrap(alg.slice(-6), derived, encryptedKey);
|
|
617
611
|
}
|
|
@@ -648,8 +642,6 @@ async function getPBKDF2CryptoKey(key, alg, options = {}) {
|
|
|
648
642
|
checkPBES2CryptoKey(key, alg, usage);
|
|
649
643
|
return key;
|
|
650
644
|
}
|
|
651
|
-
//#endregion
|
|
652
|
-
//#region src/core/_crypto/_key-encryption.ts
|
|
653
645
|
const PUBLIC_EPK_FIELDS = new Set([
|
|
654
646
|
"kty",
|
|
655
647
|
"crv",
|
|
@@ -745,7 +737,7 @@ async function normalizeKey(key, alg) {
|
|
|
745
737
|
if (key instanceof Uint8Array) return key;
|
|
746
738
|
if (isCryptoKey(key)) return key;
|
|
747
739
|
if (isJWK(key)) {
|
|
748
|
-
if ("k" in key && key.k) return base64UrlDecode(key.k,
|
|
740
|
+
if ("k" in key && key.k) return base64UrlDecode(key.k, { returnAs: "uint8array" });
|
|
749
741
|
return jwkTokey(key.alg ? key : {
|
|
750
742
|
...key,
|
|
751
743
|
alg
|
|
@@ -769,7 +761,7 @@ function toPublicEpkJwk(jwk) {
|
|
|
769
761
|
};
|
|
770
762
|
if (typeof y === "string") publicJwk.y = y;
|
|
771
763
|
else delete publicJwk.y;
|
|
772
|
-
return
|
|
764
|
+
return sanitizeObjectCopy(publicJwk);
|
|
773
765
|
}
|
|
774
766
|
async function ensurePrivateKey(alg, value) {
|
|
775
767
|
const normalized = await normalizeKey(value, alg);
|
|
@@ -788,8 +780,6 @@ async function exportPublicJwkFrom(alg, value) {
|
|
|
788
780
|
if (!(normalized instanceof CryptoKey)) throw new TypeError("ECDH-ES ephemeral public key must be a CryptoKey or convertible JWK.");
|
|
789
781
|
return exportPublicJwkFrom(alg, normalized);
|
|
790
782
|
}
|
|
791
|
-
//#endregion
|
|
792
|
-
//#region src/core/_crypto/_pem.ts
|
|
793
783
|
const toSPKI = (key) => {
|
|
794
784
|
return genericExport("public", "spki", key);
|
|
795
785
|
};
|
|
@@ -797,10 +787,10 @@ const toPKCS8 = (key) => {
|
|
|
797
787
|
return genericExport("private", "pkcs8", key);
|
|
798
788
|
};
|
|
799
789
|
const fromPKCS8 = (pem, alg, options) => {
|
|
800
|
-
return genericImport(/(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g, "pkcs8", pem, alg, options);
|
|
790
|
+
return genericImport(/(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g, "PRIVATE KEY", "pkcs8", pem, alg, options);
|
|
801
791
|
};
|
|
802
792
|
const fromSPKI = (pem, alg, options) => {
|
|
803
|
-
return genericImport(/(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g, "spki", pem, alg, options);
|
|
793
|
+
return genericImport(/(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g, "PUBLIC KEY", "spki", pem, alg, options);
|
|
804
794
|
};
|
|
805
795
|
const fromX509 = (pem, alg, options) => {
|
|
806
796
|
let spki;
|
|
@@ -814,16 +804,25 @@ const fromX509 = (pem, alg, options) => {
|
|
|
814
804
|
const formatPEM = (b64, descriptor) => {
|
|
815
805
|
return `-----BEGIN ${descriptor}-----\n${(b64.match(/.{1,64}/g) || []).join("\n")}\n-----END ${descriptor}-----`;
|
|
816
806
|
};
|
|
807
|
+
const assertPEMLabel = (pem, descriptor) => {
|
|
808
|
+
const begin = `-----BEGIN ${descriptor}-----`;
|
|
809
|
+
const end = `-----END ${descriptor}-----`;
|
|
810
|
+
if (!pem.includes(begin) || !pem.includes(end)) {
|
|
811
|
+
const found = pem.match(/-----BEGIN [^-]+-----/)?.[0];
|
|
812
|
+
throw new JWTError(found ? `PEM label mismatch: expected "${begin}", got "${found}".` : `PEM label missing: expected "${begin}".`, "ERR_JWK_INVALID");
|
|
813
|
+
}
|
|
814
|
+
};
|
|
817
815
|
const genericExport = async (keyType, keyFormat, key) => {
|
|
818
816
|
if (!isCryptoKey(key)) throw new TypeError(`Key must be ${key} of type CryptoKey`);
|
|
819
817
|
if (!key.extractable) throw new TypeError("CryptoKey is not extractable");
|
|
820
818
|
if (key.type !== keyType) throw new TypeError(`key is not a ${keyType} key`);
|
|
821
819
|
return formatPEM(base64Encode(new Uint8Array(await crypto.subtle.exportKey(keyFormat, key))), `${keyType.toUpperCase()} KEY`);
|
|
822
820
|
};
|
|
823
|
-
const genericImport = async (replace, keyFormat, pem, alg, options) => {
|
|
821
|
+
const genericImport = async (replace, descriptor, keyFormat, pem, alg, options) => {
|
|
824
822
|
let algorithm;
|
|
825
823
|
let keyUsages;
|
|
826
|
-
|
|
824
|
+
assertPEMLabel(pem, descriptor);
|
|
825
|
+
const keyData = base64Decode(pem.replace(replace, ""), { returnAs: "uint8array" });
|
|
827
826
|
const isPublic = keyFormat === "spki";
|
|
828
827
|
switch (alg) {
|
|
829
828
|
case "PS256":
|
|
@@ -1008,7 +1007,619 @@ const getSPKI = (x509) => {
|
|
|
1008
1007
|
type: "spki"
|
|
1009
1008
|
});
|
|
1010
1009
|
} catch {}
|
|
1011
|
-
return formatPEM(spkiFromX509(base64Decode(x509.replace(/(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g, ""),
|
|
1010
|
+
return formatPEM(spkiFromX509(base64Decode(x509.replace(/(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g, ""), { returnAs: "uint8array" })), "PUBLIC KEY");
|
|
1011
|
+
};
|
|
1012
|
+
async function generateKey(alg, options = {}) {
|
|
1013
|
+
const exportToJWK = options.toJWK !== void 0 && options.toJWK !== false;
|
|
1014
|
+
const defaultExtractable = options.extractable !== false;
|
|
1015
|
+
if (alg === "A128CBC-HS256" || alg === "A192CBC-HS384" || alg === "A256CBC-HS512") {
|
|
1016
|
+
const keyBytes = secureRandomBytes(bitLengthCEK(alg) >> 3);
|
|
1017
|
+
if (exportToJWK) return keyToJWK(keyBytes);
|
|
1018
|
+
return keyBytes;
|
|
1019
|
+
}
|
|
1020
|
+
const { algorithm, keyUsages } = getGenerateKeyParams(alg, options);
|
|
1021
|
+
const key = await crypto.subtle.generateKey(algorithm, defaultExtractable, keyUsages);
|
|
1022
|
+
if (exportToJWK) if (key instanceof CryptoKey) return {
|
|
1023
|
+
...await exportKey(key),
|
|
1024
|
+
alg
|
|
1025
|
+
};
|
|
1026
|
+
else {
|
|
1027
|
+
const [pub, priv] = await Promise.all([exportKey(key.publicKey), exportKey(key.privateKey)]);
|
|
1028
|
+
return {
|
|
1029
|
+
privateKey: {
|
|
1030
|
+
...priv,
|
|
1031
|
+
alg
|
|
1032
|
+
},
|
|
1033
|
+
publicKey: {
|
|
1034
|
+
...pub,
|
|
1035
|
+
alg
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
return key;
|
|
1040
|
+
}
|
|
1041
|
+
async function generateJWK(alg, options) {
|
|
1042
|
+
const { keyUsage, extractable, modulusLength, publicExponent, namedCurve, ...jwkParams } = options ?? {};
|
|
1043
|
+
const kid = typeof jwkParams.kid === "string" ? jwkParams.kid : crypto.randomUUID();
|
|
1044
|
+
const extraParams = {
|
|
1045
|
+
...jwkParams,
|
|
1046
|
+
kid
|
|
1047
|
+
};
|
|
1048
|
+
const result = await generateKey(alg, {
|
|
1049
|
+
keyUsage,
|
|
1050
|
+
extractable,
|
|
1051
|
+
modulusLength,
|
|
1052
|
+
publicExponent,
|
|
1053
|
+
namedCurve,
|
|
1054
|
+
toJWK: true
|
|
1055
|
+
});
|
|
1056
|
+
if (result && typeof result === "object" && "privateKey" in result && "publicKey" in result) {
|
|
1057
|
+
const pair = result;
|
|
1058
|
+
return {
|
|
1059
|
+
privateKey: {
|
|
1060
|
+
...extraParams,
|
|
1061
|
+
...pair.privateKey
|
|
1062
|
+
},
|
|
1063
|
+
publicKey: {
|
|
1064
|
+
...extraParams,
|
|
1065
|
+
...pair.publicKey
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
return {
|
|
1070
|
+
...extraParams,
|
|
1071
|
+
...result
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
async function deriveKeyFromPassword(password, alg, options) {
|
|
1075
|
+
const { salt, iterations, extractable, keyUsage } = options;
|
|
1076
|
+
const exportToJWK = options.toJWK !== void 0 && options.toJWK !== false;
|
|
1077
|
+
if (!(salt instanceof Uint8Array) || salt.length < 8) throw new JWTError("PBES2 Salt Input (salt) must be 8 or more octets", "ERR_JWE_INVALID");
|
|
1078
|
+
if (typeof iterations !== "number" || iterations < 1) throw new JWTError("PBES2 Iteration Count (iterations) must be a positive integer", "ERR_JWE_INVALID");
|
|
1079
|
+
const derivedBytes = await deriveKey(salt, alg, iterations, typeof password === "string" ? textEncoder.encode(password) : password);
|
|
1080
|
+
const wrappingAlg = alg.slice(-6);
|
|
1081
|
+
const finalUsages = keyUsage ?? ["wrapKey", "unwrapKey"];
|
|
1082
|
+
const finalExtractable = exportToJWK ? true : extractable === true;
|
|
1083
|
+
const derivedKey = await crypto.subtle.importKey("raw", derivedBytes, { name: "AES-KW" }, finalExtractable, finalUsages);
|
|
1084
|
+
if (exportToJWK) return {
|
|
1085
|
+
...await keyToJWK(derivedKey),
|
|
1086
|
+
alg: wrappingAlg,
|
|
1087
|
+
kty: "oct"
|
|
1088
|
+
};
|
|
1089
|
+
return derivedKey;
|
|
1090
|
+
}
|
|
1091
|
+
async function deriveJWKFromPassword(password, alg, options) {
|
|
1092
|
+
const { salt, iterations, keyUsage, extractable, ...jwkParams } = options;
|
|
1093
|
+
const result = await deriveKeyFromPassword(password, alg, {
|
|
1094
|
+
salt,
|
|
1095
|
+
iterations,
|
|
1096
|
+
keyUsage,
|
|
1097
|
+
extractable,
|
|
1098
|
+
toJWK: true
|
|
1099
|
+
});
|
|
1100
|
+
return Object.keys(jwkParams).length > 0 ? {
|
|
1101
|
+
...jwkParams,
|
|
1102
|
+
...result
|
|
1103
|
+
} : result;
|
|
1104
|
+
}
|
|
1105
|
+
var WeakMapJWKCache = class {
|
|
1106
|
+
_map = /* @__PURE__ */ new WeakMap();
|
|
1107
|
+
get(jwk, alg) {
|
|
1108
|
+
return this._map.get(jwk)?.[alg];
|
|
1109
|
+
}
|
|
1110
|
+
set(jwk, alg, key) {
|
|
1111
|
+
let entry = this._map.get(jwk);
|
|
1112
|
+
if (!entry) this._map.set(jwk, { [alg]: key });
|
|
1113
|
+
else entry[alg] = key;
|
|
1114
|
+
}
|
|
1012
1115
|
};
|
|
1013
|
-
|
|
1014
|
-
|
|
1116
|
+
let _activeCache = new WeakMapJWKCache();
|
|
1117
|
+
function configureJWKCache(cache) {
|
|
1118
|
+
_activeCache = cache;
|
|
1119
|
+
}
|
|
1120
|
+
function clearJWKCache() {
|
|
1121
|
+
_activeCache = new WeakMapJWKCache();
|
|
1122
|
+
}
|
|
1123
|
+
async function importKey(key, algOrOptions) {
|
|
1124
|
+
const alg = typeof algOrOptions === "string" ? algOrOptions : algOrOptions?.alg ?? void 0;
|
|
1125
|
+
const expect = typeof algOrOptions === "object" ? algOrOptions.expect ?? void 0 : void 0;
|
|
1126
|
+
if (typeof key === "string") key = textEncoder.encode(key);
|
|
1127
|
+
if (key instanceof Uint8Array) return key;
|
|
1128
|
+
if (isCryptoKey(key)) {
|
|
1129
|
+
assertIntentOnCryptoKey(key, expect);
|
|
1130
|
+
return key;
|
|
1131
|
+
}
|
|
1132
|
+
if (isJWK(key)) if ("k" in key && typeof key.k === "string") {
|
|
1133
|
+
const rawBytes = base64UrlDecode(key.k, { returnAs: "uint8array" });
|
|
1134
|
+
if (typeof algOrOptions === "object" && algOrOptions.asCryptoKey) {
|
|
1135
|
+
const { algorithm, usage, extractable = false } = algOrOptions;
|
|
1136
|
+
if (!algorithm || !usage) throw new JWTError("asCryptoKey option requires `algorithm` and `usage` to be provided.", "ERR_JWK_INVALID");
|
|
1137
|
+
return crypto.subtle.importKey("raw", rawBytes, algorithm, extractable, usage);
|
|
1138
|
+
}
|
|
1139
|
+
return rawBytes;
|
|
1140
|
+
} else {
|
|
1141
|
+
if (!key.alg && !alg) throw new JWTError("Algorithm must be provided when importing non-oct JWK", "ERR_JWK_INVALID");
|
|
1142
|
+
assertIntentOnJWK(key, expect);
|
|
1143
|
+
const effectiveAlg = key.alg || alg;
|
|
1144
|
+
if (_activeCache) {
|
|
1145
|
+
const cached = _activeCache.get(key, effectiveAlg);
|
|
1146
|
+
if (cached) return cached;
|
|
1147
|
+
}
|
|
1148
|
+
const cryptoKey = await jwkTokey(key.alg ? key : {
|
|
1149
|
+
...key,
|
|
1150
|
+
alg
|
|
1151
|
+
});
|
|
1152
|
+
if (_activeCache) _activeCache.set(key, effectiveAlg, cryptoKey);
|
|
1153
|
+
return cryptoKey;
|
|
1154
|
+
}
|
|
1155
|
+
throw new JWTError("Invalid key type provided to importKey", "ERR_JWK_INVALID");
|
|
1156
|
+
}
|
|
1157
|
+
function assertIntentOnJWK(jwk, expect) {
|
|
1158
|
+
if (expect === void 0 || jwk.kty === "oct") return;
|
|
1159
|
+
const hasPrivateScalar = "d" in jwk && typeof jwk.d === "string";
|
|
1160
|
+
if (expect === "public" && hasPrivateScalar) throw new JWTError("Private JWK passed where a public key is expected. Strip `d` (and CRT fields on RSA) before import.", "ERR_JWK_INVALID");
|
|
1161
|
+
if (expect === "private" && !hasPrivateScalar) throw new JWTError("Public JWK passed where a private key is expected.", "ERR_JWK_INVALID");
|
|
1162
|
+
}
|
|
1163
|
+
function assertIntentOnCryptoKey(key, expect) {
|
|
1164
|
+
if (expect === void 0 || key.type === "secret") return;
|
|
1165
|
+
if (expect === "public" && key.type === "private") throw new JWTError("Private CryptoKey passed where a public key is expected.", "ERR_JWK_INVALID");
|
|
1166
|
+
if (expect === "private" && key.type === "public") throw new JWTError("Public CryptoKey passed where a private key is expected.", "ERR_JWK_INVALID");
|
|
1167
|
+
}
|
|
1168
|
+
async function exportKey(key, jwkParams) {
|
|
1169
|
+
const exportedJwk = await keyToJWK(key);
|
|
1170
|
+
if (jwkParams) return {
|
|
1171
|
+
...exportedJwk,
|
|
1172
|
+
...sanitizeObjectCopy(jwkParams)
|
|
1173
|
+
};
|
|
1174
|
+
return exportedJwk;
|
|
1175
|
+
}
|
|
1176
|
+
async function resolveWrappingKey(alg, key, expect) {
|
|
1177
|
+
if (alg.startsWith("PBES2") && (typeof key === "string" || key instanceof Uint8Array)) return typeof key === "string" ? textEncoder.encode(key) : key;
|
|
1178
|
+
if (typeof key === "string") throw new TypeError(`Key must be CryptoKey, JWK, or Uint8Array for ${alg}`);
|
|
1179
|
+
if (expect !== void 0) return importKey(key, {
|
|
1180
|
+
alg,
|
|
1181
|
+
expect
|
|
1182
|
+
});
|
|
1183
|
+
return importKey(key, alg);
|
|
1184
|
+
}
|
|
1185
|
+
async function wrapKey(alg, keyToWrap, wrappingKey, options = {}) {
|
|
1186
|
+
const cekBytes = keyToWrap instanceof Uint8Array ? keyToWrap : new Uint8Array(await crypto.subtle.exportKey("raw", keyToWrap));
|
|
1187
|
+
const importedWrappingKey = await resolveWrappingKey(alg, wrappingKey, "public");
|
|
1188
|
+
switch (alg) {
|
|
1189
|
+
case "dir": return { encryptedKey: new Uint8Array(0) };
|
|
1190
|
+
case "A128KW":
|
|
1191
|
+
case "A192KW":
|
|
1192
|
+
case "A256KW": return { encryptedKey: await aesKwWrap(alg, importedWrappingKey, cekBytes) };
|
|
1193
|
+
case "PBES2-HS256+A128KW":
|
|
1194
|
+
case "PBES2-HS384+A192KW":
|
|
1195
|
+
case "PBES2-HS512+A256KW": {
|
|
1196
|
+
const { p2s, p2c } = options;
|
|
1197
|
+
if (!p2s || typeof p2c !== "number") throw new JWTError("PBES2 requires 'p2s' (salt) and 'p2c' (count) options", "ERR_JWE_INVALID");
|
|
1198
|
+
return pbes2Wrap(alg, importedWrappingKey, cekBytes, p2c, p2s);
|
|
1199
|
+
}
|
|
1200
|
+
case "A128GCMKW":
|
|
1201
|
+
case "A192GCMKW":
|
|
1202
|
+
case "A256GCMKW": {
|
|
1203
|
+
const { encryptedKey, iv, tag } = await gcmkwEncrypt(alg, importedWrappingKey, cekBytes, options.iv);
|
|
1204
|
+
return {
|
|
1205
|
+
encryptedKey,
|
|
1206
|
+
iv,
|
|
1207
|
+
tag
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
case "RSA-OAEP":
|
|
1211
|
+
case "RSA-OAEP-256":
|
|
1212
|
+
case "RSA-OAEP-384":
|
|
1213
|
+
case "RSA-OAEP-512": return { encryptedKey: await encryptRSAES(alg, importedWrappingKey, cekBytes) };
|
|
1214
|
+
case "ECDH-ES":
|
|
1215
|
+
case "ECDH-ES+A128KW":
|
|
1216
|
+
case "ECDH-ES+A192KW":
|
|
1217
|
+
case "ECDH-ES+A256KW": {
|
|
1218
|
+
if (!(importedWrappingKey instanceof CryptoKey)) throw new TypeError("ECDH-ES requires the wrapping key to be a CryptoKey (recipient's public key)");
|
|
1219
|
+
if (!isECDHKeyAllowed(importedWrappingKey)) throw new JWTError("ECDH with the provided key is not allowed or not supported", "ERR_JWK_INVALID");
|
|
1220
|
+
const apu = options.ecdh?.partyUInfo ?? new Uint8Array(0);
|
|
1221
|
+
const apv = options.ecdh?.partyVInfo ?? new Uint8Array(0);
|
|
1222
|
+
const { ephemeralPrivateKey, epkJwk } = await resolveECDHEphemeralPair(importedWrappingKey, options.ecdh?.ephemeralKey);
|
|
1223
|
+
const infoAlg = alg === "ECDH-ES" ? (() => {
|
|
1224
|
+
if (!options.ecdh?.enc) throw new JWTError("ECDH-ES direct key agreement requires options.ecdh.enc (the target content encryption algorithm)", "ERR_JWK_INVALID");
|
|
1225
|
+
return options.ecdh.enc;
|
|
1226
|
+
})() : alg;
|
|
1227
|
+
const sharedSecret = await deriveECDHESKey(importedWrappingKey, ephemeralPrivateKey, infoAlg, alg === "ECDH-ES" ? bitLengthCEK(infoAlg) : Number.parseInt(alg.slice(-5, -2), 10), apu, apv);
|
|
1228
|
+
const wrapResult = {
|
|
1229
|
+
encryptedKey: new Uint8Array(0),
|
|
1230
|
+
epk: epkJwk
|
|
1231
|
+
};
|
|
1232
|
+
if (apu.length > 0) wrapResult.apu = base64UrlEncode(apu);
|
|
1233
|
+
if (apv.length > 0) wrapResult.apv = base64UrlEncode(apv);
|
|
1234
|
+
if (alg === "ECDH-ES") return wrapResult;
|
|
1235
|
+
wrapResult.encryptedKey = await aesKwWrap(alg.slice(-6), sharedSecret, cekBytes);
|
|
1236
|
+
return wrapResult;
|
|
1237
|
+
}
|
|
1238
|
+
default: throw new JWTError(`Unsupported key wrapping algorithm: ${alg}`, "ERR_JWK_UNSUPPORTED");
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
async function unwrapKey(alg, wrappedKey, unwrappingKey, options = {}) {
|
|
1242
|
+
const returnRaw = options.format === "raw";
|
|
1243
|
+
const defaultExtractable = options.extractable !== false;
|
|
1244
|
+
const importedUnwrappingKey = await resolveWrappingKey(alg, unwrappingKey, "private");
|
|
1245
|
+
let unwrappedCekBytes;
|
|
1246
|
+
switch (alg) {
|
|
1247
|
+
case "dir":
|
|
1248
|
+
if (importedUnwrappingKey instanceof Uint8Array) unwrappedCekBytes = importedUnwrappingKey;
|
|
1249
|
+
else {
|
|
1250
|
+
if (!returnRaw) return importedUnwrappingKey;
|
|
1251
|
+
unwrappedCekBytes = new Uint8Array(await crypto.subtle.exportKey("raw", importedUnwrappingKey));
|
|
1252
|
+
}
|
|
1253
|
+
break;
|
|
1254
|
+
case "A128KW":
|
|
1255
|
+
case "A192KW":
|
|
1256
|
+
case "A256KW":
|
|
1257
|
+
unwrappedCekBytes = await aesKwUnwrap(alg, importedUnwrappingKey, wrappedKey);
|
|
1258
|
+
break;
|
|
1259
|
+
case "PBES2-HS256+A128KW":
|
|
1260
|
+
case "PBES2-HS384+A192KW":
|
|
1261
|
+
case "PBES2-HS512+A256KW": {
|
|
1262
|
+
const { p2s, p2c, minIterations, maxIterations } = options;
|
|
1263
|
+
if (!p2s || typeof p2c !== "number") throw new JWTError("PBES2 requires 'p2s' (salt) and 'p2c' (count) options", "ERR_JWE_INVALID");
|
|
1264
|
+
unwrappedCekBytes = await pbes2Unwrap(alg, importedUnwrappingKey, wrappedKey, p2c, typeof p2s === "string" ? base64UrlDecode(p2s, { returnAs: "uint8array" }) : p2s, minIterations, maxIterations);
|
|
1265
|
+
break;
|
|
1266
|
+
}
|
|
1267
|
+
case "A128GCMKW":
|
|
1268
|
+
case "A192GCMKW":
|
|
1269
|
+
case "A256GCMKW":
|
|
1270
|
+
if (!options.iv || !options.tag) throw new JWTError("AES-GCMKW requires 'iv' and 'tag' options for unwrapping", "ERR_JWE_INVALID");
|
|
1271
|
+
unwrappedCekBytes = await gcmkwDecrypt(alg, importedUnwrappingKey, wrappedKey, typeof options.iv === "string" ? base64UrlDecode(options.iv, { returnAs: "uint8array" }) : options.iv, typeof options.tag === "string" ? base64UrlDecode(options.tag, { returnAs: "uint8array" }) : options.tag);
|
|
1272
|
+
break;
|
|
1273
|
+
case "RSA-OAEP":
|
|
1274
|
+
case "RSA-OAEP-256":
|
|
1275
|
+
case "RSA-OAEP-384":
|
|
1276
|
+
case "RSA-OAEP-512": {
|
|
1277
|
+
if (!(importedUnwrappingKey instanceof CryptoKey)) throw new TypeError("RSA-OAEP requires the unwrapping key to be provided as a CryptoKey");
|
|
1278
|
+
unwrappedCekBytes = await decryptRSAES(alg, importedUnwrappingKey, wrappedKey);
|
|
1279
|
+
if (returnRaw) break;
|
|
1280
|
+
const inferredAlgorithm = options.unwrappedKeyAlgorithm || inferAesImportAlgorithm(options.enc, unwrappedCekBytes.length);
|
|
1281
|
+
if (!inferredAlgorithm) throw new JWTError("Unable to infer algorithm for RSA-OAEP unwrapped key. Provide \"unwrappedKeyAlgorithm\" in options.", "ERR_JWE_INVALID");
|
|
1282
|
+
const keyUsages = options.keyUsage || ["encrypt", "decrypt"];
|
|
1283
|
+
return await crypto.subtle.importKey("raw", unwrappedCekBytes, inferredAlgorithm, defaultExtractable, keyUsages);
|
|
1284
|
+
}
|
|
1285
|
+
case "ECDH-ES":
|
|
1286
|
+
case "ECDH-ES+A128KW":
|
|
1287
|
+
case "ECDH-ES+A192KW":
|
|
1288
|
+
case "ECDH-ES+A256KW": {
|
|
1289
|
+
if (!options.epk) throw new JWTError("ECDH-ES requires 'epk' (Ephemeral Public Key) option", "ERR_JWE_INVALID");
|
|
1290
|
+
if (!(importedUnwrappingKey instanceof CryptoKey)) throw new TypeError("ECDH-ES requires the unwrapping key to be a CryptoKey");
|
|
1291
|
+
if (!isECDHKeyAllowed(importedUnwrappingKey)) throw new JWTError("ECDH with the provided key is not allowed or not supported", "ERR_JWK_INVALID");
|
|
1292
|
+
const epkCandidate = options.epk instanceof CryptoKey ? options.epk : await normalizeKey(options.epk, alg);
|
|
1293
|
+
if (!(epkCandidate instanceof CryptoKey)) throw new JWTError("Failed to normalize ECDH ephemeral public key", "ERR_JWK_INVALID");
|
|
1294
|
+
const apuBytes = typeof options.apu === "string" ? base64UrlDecode(options.apu, { returnAs: "uint8array" }) : options.apu ?? new Uint8Array(0);
|
|
1295
|
+
const apvBytes = typeof options.apv === "string" ? base64UrlDecode(options.apv, { returnAs: "uint8array" }) : options.apv ?? new Uint8Array(0);
|
|
1296
|
+
const infoAlg = alg === "ECDH-ES" ? options.enc : alg;
|
|
1297
|
+
if (!infoAlg) throw new JWTError("ECDH-ES requires content encryption algorithm ('enc') to derive the shared secret", "ERR_JWE_INVALID");
|
|
1298
|
+
const sharedSecret = await deriveECDHESKey(epkCandidate, importedUnwrappingKey, infoAlg, alg === "ECDH-ES" ? bitLengthCEK(infoAlg) : Number.parseInt(alg.slice(-5, -2), 10), apuBytes, apvBytes);
|
|
1299
|
+
if (alg === "ECDH-ES") unwrappedCekBytes = sharedSecret;
|
|
1300
|
+
else {
|
|
1301
|
+
const kwAlg = alg.slice(-6);
|
|
1302
|
+
if (!(wrappedKey instanceof Uint8Array) || wrappedKey.length === 0) throw new JWTError("ECDH-ES key agreement with key wrapping requires an encrypted key", "ERR_JWE_INVALID");
|
|
1303
|
+
unwrappedCekBytes = await aesKwUnwrap(kwAlg, sharedSecret, wrappedKey);
|
|
1304
|
+
}
|
|
1305
|
+
break;
|
|
1306
|
+
}
|
|
1307
|
+
default: throw new JWTError(`Unsupported key unwrapping algorithm: ${alg}`, "ERR_JWK_UNSUPPORTED");
|
|
1308
|
+
}
|
|
1309
|
+
if (returnRaw) return unwrappedCekBytes;
|
|
1310
|
+
return await crypto.subtle.importKey("raw", unwrappedCekBytes, options.unwrappedKeyAlgorithm || { name: "AES-GCM" }, defaultExtractable, options.keyUsage || ["encrypt", "decrypt"]);
|
|
1311
|
+
}
|
|
1312
|
+
async function importPEM(pem, alg, options) {
|
|
1313
|
+
const extractable = options?.extractable !== false;
|
|
1314
|
+
const pemType = options?.pemType ?? inferPEMType(pem);
|
|
1315
|
+
if (!pemType) throw new JWTError("Cannot infer PEM type from the input; pass \"options.pemType\" explicitly.", "ERR_JWK_INVALID");
|
|
1316
|
+
let cryptoKey;
|
|
1317
|
+
switch (pemType) {
|
|
1318
|
+
case "pkcs8":
|
|
1319
|
+
cryptoKey = await fromPKCS8(pem, alg, { extractable });
|
|
1320
|
+
break;
|
|
1321
|
+
case "spki":
|
|
1322
|
+
cryptoKey = await fromSPKI(pem, alg, { extractable });
|
|
1323
|
+
break;
|
|
1324
|
+
case "x509":
|
|
1325
|
+
cryptoKey = await fromX509(pem, alg, { extractable });
|
|
1326
|
+
break;
|
|
1327
|
+
default: throw new JWTError(`Unsupported PEM type: ${pemType}`, "ERR_JWK_UNSUPPORTED");
|
|
1328
|
+
}
|
|
1329
|
+
return {
|
|
1330
|
+
...await exportKey(cryptoKey, options?.jwkParams),
|
|
1331
|
+
alg
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
async function importFromPEM(pem, pemType, alg, options) {
|
|
1335
|
+
return importPEM(pem, alg, {
|
|
1336
|
+
pemType,
|
|
1337
|
+
...options
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
async function exportPEM(jwk, options) {
|
|
1341
|
+
if (jwk.kty === "oct") throw new JWTError("Octet (symmetric) JWKs (kty: 'oct') cannot be exported to PKCS8 or SPKI PEM formats.", "ERR_JWK_UNSUPPORTED");
|
|
1342
|
+
const effectiveAlg = jwk.alg || options?.alg;
|
|
1343
|
+
if (!effectiveAlg && (jwk.kty === "RSA" || jwk.kty === "EC" || jwk.kty === "OKP")) throw new JWTError("Algorithm (alg) must be provided in the JWK or via options for converting this JWK type to a CryptoKey.", "ERR_JWK_INVALID");
|
|
1344
|
+
const pemFormat = options?.pemFormat ?? ("d" in jwk ? "pkcs8" : "spki");
|
|
1345
|
+
const cryptoKeyCandidate = await importKey({
|
|
1346
|
+
...jwk,
|
|
1347
|
+
ext: true
|
|
1348
|
+
}, {
|
|
1349
|
+
alg: effectiveAlg,
|
|
1350
|
+
expect: pemFormat === "pkcs8" ? "private" : "public"
|
|
1351
|
+
});
|
|
1352
|
+
if (!isCryptoKey(cryptoKeyCandidate)) throw new JWTError("Failed to convert JWK to a CryptoKey instance suitable for PEM export.", "ERR_JWK_INVALID");
|
|
1353
|
+
const cryptoKey = cryptoKeyCandidate;
|
|
1354
|
+
switch (pemFormat) {
|
|
1355
|
+
case "pkcs8":
|
|
1356
|
+
if (cryptoKey.type !== "private") throw new JWTError(`Only 'private' type CryptoKeys can be exported to PKCS8 PEM format. Key type is '${cryptoKey.type}'.`, "ERR_JWK_INVALID");
|
|
1357
|
+
return toPKCS8(cryptoKey);
|
|
1358
|
+
case "spki":
|
|
1359
|
+
if (cryptoKey.type !== "public") throw new JWTError(`Only 'public' type CryptoKeys can be exported to SPKI PEM format. Key type is '${cryptoKey.type}'.`, "ERR_JWK_INVALID");
|
|
1360
|
+
return toSPKI(cryptoKey);
|
|
1361
|
+
default: throw new JWTError(`Unsupported PEM format: ${pemFormat}`, "ERR_JWK_UNSUPPORTED");
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
async function exportToPEM(jwk, pemFormat, algForCryptoKeyImport) {
|
|
1365
|
+
return exportPEM(jwk, {
|
|
1366
|
+
pemFormat,
|
|
1367
|
+
alg: algForCryptoKeyImport
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
function inferPEMType(pem) {
|
|
1371
|
+
if (pem.includes("-----BEGIN PRIVATE KEY-----")) return "pkcs8";
|
|
1372
|
+
if (pem.includes("-----BEGIN PUBLIC KEY-----")) return "spki";
|
|
1373
|
+
if (pem.includes("-----BEGIN CERTIFICATE-----")) return "x509";
|
|
1374
|
+
}
|
|
1375
|
+
async function deriveSharedSecret(publicKey, privateKey, alg, options) {
|
|
1376
|
+
const pubCryptoKey = isCryptoKey(publicKey) ? publicKey : await importKey(publicKey, {
|
|
1377
|
+
alg: "ECDH-ES",
|
|
1378
|
+
expect: "public"
|
|
1379
|
+
});
|
|
1380
|
+
const privCryptoKey = isCryptoKey(privateKey) ? privateKey : await importKey(privateKey, {
|
|
1381
|
+
alg: "ECDH-ES",
|
|
1382
|
+
expect: "private"
|
|
1383
|
+
});
|
|
1384
|
+
let keyLength;
|
|
1385
|
+
if (options?.keyLength !== void 0) keyLength = options.keyLength;
|
|
1386
|
+
else if (alg === "ECDH-ES") throw new JWTError("deriveSharedSecret with alg \"ECDH-ES\" requires an explicit keyLength in options", "ERR_JWK_INVALID");
|
|
1387
|
+
else if (alg.startsWith("ECDH-ES+")) keyLength = Number.parseInt(alg.slice(-5, -2), 10);
|
|
1388
|
+
else keyLength = bitLengthCEK(alg);
|
|
1389
|
+
return deriveECDHESKey(pubCryptoKey, privCryptoKey, alg, keyLength, options?.partyUInfo ?? new Uint8Array(0), options?.partyVInfo ?? new Uint8Array(0));
|
|
1390
|
+
}
|
|
1391
|
+
function getJWKFromSet(jwkSet, kidOrProtectedHeader) {
|
|
1392
|
+
if (!jwkSet || !isJWKSet(jwkSet)) throw new JWTError("Invalid JWK Set provided", "ERR_JWK_INVALID");
|
|
1393
|
+
if (typeof kidOrProtectedHeader === "string") {
|
|
1394
|
+
const kid = kidOrProtectedHeader;
|
|
1395
|
+
const selectedKey = jwkSet.keys.find((k) => k.kid === kid);
|
|
1396
|
+
if (!selectedKey) throw new JWTError(`No key found in JWK Set with kid "${kid}".`, "ERR_JWK_KEY_NOT_FOUND");
|
|
1397
|
+
return selectedKey;
|
|
1398
|
+
} else if (typeof kidOrProtectedHeader === "object") {
|
|
1399
|
+
const { kid, alg, kty } = kidOrProtectedHeader;
|
|
1400
|
+
if (!kid) {
|
|
1401
|
+
if (jwkSet.keys.length === 1) return jwkSet.keys[0];
|
|
1402
|
+
throw new JWTError("JWS Protected Header is missing 'kid' (Key ID) and the JWK Set contains multiple keys. Add a 'kid' parameter to the token header and to the matching JWK to enable automatic selection.", "ERR_JWK_KEY_NOT_FOUND");
|
|
1403
|
+
}
|
|
1404
|
+
const selectedKey = jwkSet.keys.find((k) => {
|
|
1405
|
+
return k.kid === kid && (!alg || k.alg === alg) && (!kty || k.kty === kty);
|
|
1406
|
+
});
|
|
1407
|
+
if (!selectedKey) {
|
|
1408
|
+
let errorMessage = `No key found in JWK Set with kid "${kid}"`;
|
|
1409
|
+
if (alg) errorMessage += ` and alg "${alg}"`;
|
|
1410
|
+
if (kty) errorMessage += ` and kty "${kty}"`;
|
|
1411
|
+
errorMessage += ".";
|
|
1412
|
+
throw new JWTError(errorMessage, "ERR_JWK_KEY_NOT_FOUND");
|
|
1413
|
+
}
|
|
1414
|
+
return selectedKey;
|
|
1415
|
+
}
|
|
1416
|
+
throw new TypeError("`kidOrProtectedHeader` must be a string (kid) or an object (JOSE Protected Header).");
|
|
1417
|
+
}
|
|
1418
|
+
function getJWKsFromSet(jwkSet, filter) {
|
|
1419
|
+
if (!jwkSet || !isJWKSet(jwkSet)) throw new JWTError("Invalid JWK Set provided", "ERR_JWK_INVALID");
|
|
1420
|
+
if (!filter) return [...jwkSet.keys];
|
|
1421
|
+
return jwkSet.keys.filter(filter);
|
|
1422
|
+
}
|
|
1423
|
+
function getGenerateKeyParams(alg, options) {
|
|
1424
|
+
let algorithm;
|
|
1425
|
+
let keyUsages;
|
|
1426
|
+
const defaultKeyUsage = options?.keyUsage;
|
|
1427
|
+
switch (alg) {
|
|
1428
|
+
case "HS256":
|
|
1429
|
+
case "HS384":
|
|
1430
|
+
case "HS512":
|
|
1431
|
+
algorithm = {
|
|
1432
|
+
name: "HMAC",
|
|
1433
|
+
hash: `SHA-${alg.slice(2)}`
|
|
1434
|
+
};
|
|
1435
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1436
|
+
break;
|
|
1437
|
+
case "RS256":
|
|
1438
|
+
case "RS384":
|
|
1439
|
+
case "RS512":
|
|
1440
|
+
algorithm = {
|
|
1441
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
1442
|
+
modulusLength: options?.modulusLength ?? 2048,
|
|
1443
|
+
publicExponent: options?.publicExponent ?? new Uint8Array([
|
|
1444
|
+
1,
|
|
1445
|
+
0,
|
|
1446
|
+
1
|
|
1447
|
+
]),
|
|
1448
|
+
hash: `SHA-${alg.slice(2)}`
|
|
1449
|
+
};
|
|
1450
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1451
|
+
break;
|
|
1452
|
+
case "PS256":
|
|
1453
|
+
case "PS384":
|
|
1454
|
+
case "PS512":
|
|
1455
|
+
algorithm = {
|
|
1456
|
+
name: "RSA-PSS",
|
|
1457
|
+
modulusLength: options?.modulusLength ?? 2048,
|
|
1458
|
+
publicExponent: options?.publicExponent ?? new Uint8Array([
|
|
1459
|
+
1,
|
|
1460
|
+
0,
|
|
1461
|
+
1
|
|
1462
|
+
]),
|
|
1463
|
+
hash: `SHA-${alg.slice(2)}`
|
|
1464
|
+
};
|
|
1465
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1466
|
+
break;
|
|
1467
|
+
case "RSA-OAEP":
|
|
1468
|
+
case "RSA-OAEP-256":
|
|
1469
|
+
case "RSA-OAEP-384":
|
|
1470
|
+
case "RSA-OAEP-512":
|
|
1471
|
+
algorithm = {
|
|
1472
|
+
name: "RSA-OAEP",
|
|
1473
|
+
modulusLength: options?.modulusLength ?? 2048,
|
|
1474
|
+
publicExponent: options?.publicExponent ?? new Uint8Array([
|
|
1475
|
+
1,
|
|
1476
|
+
0,
|
|
1477
|
+
1
|
|
1478
|
+
]),
|
|
1479
|
+
hash: `SHA-${Number.parseInt(alg.slice(9), 10) || 1}`
|
|
1480
|
+
};
|
|
1481
|
+
keyUsages = defaultKeyUsage ?? [
|
|
1482
|
+
"encrypt",
|
|
1483
|
+
"decrypt",
|
|
1484
|
+
"wrapKey",
|
|
1485
|
+
"unwrapKey"
|
|
1486
|
+
];
|
|
1487
|
+
break;
|
|
1488
|
+
case "A128KW":
|
|
1489
|
+
case "A192KW":
|
|
1490
|
+
case "A256KW":
|
|
1491
|
+
algorithm = {
|
|
1492
|
+
name: "AES-KW",
|
|
1493
|
+
length: Number.parseInt(alg.slice(1, 4), 10)
|
|
1494
|
+
};
|
|
1495
|
+
keyUsages = defaultKeyUsage ?? ["wrapKey", "unwrapKey"];
|
|
1496
|
+
break;
|
|
1497
|
+
case "A128GCM":
|
|
1498
|
+
case "A192GCM":
|
|
1499
|
+
case "A256GCM":
|
|
1500
|
+
case "A128GCMKW":
|
|
1501
|
+
case "A192GCMKW":
|
|
1502
|
+
case "A256GCMKW": {
|
|
1503
|
+
const baseAlg = alg.replace("KW", "");
|
|
1504
|
+
algorithm = {
|
|
1505
|
+
name: "AES-GCM",
|
|
1506
|
+
length: Number.parseInt(baseAlg.slice(1, 4), 10)
|
|
1507
|
+
};
|
|
1508
|
+
keyUsages = defaultKeyUsage ?? (alg.endsWith("KW") ? ["wrapKey", "unwrapKey"] : ["encrypt", "decrypt"]);
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
case "ES256":
|
|
1512
|
+
case "ES384":
|
|
1513
|
+
case "ES512":
|
|
1514
|
+
algorithm = {
|
|
1515
|
+
name: "ECDSA",
|
|
1516
|
+
namedCurve: `P-${alg.slice(2)}`
|
|
1517
|
+
};
|
|
1518
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1519
|
+
break;
|
|
1520
|
+
case "Ed25519":
|
|
1521
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1522
|
+
algorithm = { name: "Ed25519" };
|
|
1523
|
+
break;
|
|
1524
|
+
case "EdDSA": {
|
|
1525
|
+
keyUsages = defaultKeyUsage ?? ["sign", "verify"];
|
|
1526
|
+
const namedCurve = options?.namedCurve ?? "Ed25519";
|
|
1527
|
+
switch (namedCurve) {
|
|
1528
|
+
case "Ed25519":
|
|
1529
|
+
case "Ed448":
|
|
1530
|
+
algorithm = { name: namedCurve };
|
|
1531
|
+
break;
|
|
1532
|
+
default: throw new JWTError("Unsupported namedCurve provided. Supported values are: Ed25519 and Ed448", "ERR_JWK_UNSUPPORTED");
|
|
1533
|
+
}
|
|
1534
|
+
break;
|
|
1535
|
+
}
|
|
1536
|
+
case "ECDH-ES":
|
|
1537
|
+
case "ECDH-ES+A128KW":
|
|
1538
|
+
case "ECDH-ES+A192KW":
|
|
1539
|
+
case "ECDH-ES+A256KW": {
|
|
1540
|
+
keyUsages = ["deriveBits"];
|
|
1541
|
+
const namedCurve = options?.namedCurve ?? "P-256";
|
|
1542
|
+
switch (namedCurve) {
|
|
1543
|
+
case "P-256":
|
|
1544
|
+
case "P-384":
|
|
1545
|
+
case "P-521":
|
|
1546
|
+
algorithm = {
|
|
1547
|
+
name: "ECDH",
|
|
1548
|
+
namedCurve
|
|
1549
|
+
};
|
|
1550
|
+
break;
|
|
1551
|
+
case "X25519":
|
|
1552
|
+
algorithm = { name: "X25519" };
|
|
1553
|
+
break;
|
|
1554
|
+
default: throw new JWTError("Unsupported namedCurve provided. Supported values are: P-256, P-384, P-521 and X25519", "ERR_JWK_UNSUPPORTED");
|
|
1555
|
+
}
|
|
1556
|
+
break;
|
|
1557
|
+
}
|
|
1558
|
+
default: throw new JWTError(`Unsupported or invalid algorithm for key generation: ${alg}`, "ERR_JWK_UNSUPPORTED");
|
|
1559
|
+
}
|
|
1560
|
+
return {
|
|
1561
|
+
algorithm,
|
|
1562
|
+
keyUsages
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
async function resolveECDHEphemeralPair(recipientPublicKey, ephemeralKeyInput) {
|
|
1566
|
+
if (!ephemeralKeyInput) {
|
|
1567
|
+
const generated = await crypto.subtle.generateKey(recipientPublicKey.algorithm, true, ["deriveBits"]);
|
|
1568
|
+
const rawJwk = await keyToJWK(generated.publicKey);
|
|
1569
|
+
return {
|
|
1570
|
+
ephemeralPrivateKey: generated.privateKey,
|
|
1571
|
+
epkJwk: stripPrivateJwkFields(rawJwk)
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
if (isCryptoKeyPair(ephemeralKeyInput)) {
|
|
1575
|
+
const rawJwk = await keyToJWK(ephemeralKeyInput.publicKey);
|
|
1576
|
+
return {
|
|
1577
|
+
ephemeralPrivateKey: ephemeralKeyInput.privateKey,
|
|
1578
|
+
epkJwk: stripPrivateJwkFields(rawJwk)
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
if (typeof ephemeralKeyInput === "object" && "publicKey" in ephemeralKeyInput && "privateKey" in ephemeralKeyInput) {
|
|
1582
|
+
const { publicKey, privateKey } = ephemeralKeyInput;
|
|
1583
|
+
return {
|
|
1584
|
+
ephemeralPrivateKey: isCryptoKey(privateKey) ? privateKey : await importKey(privateKey, {
|
|
1585
|
+
alg: "ECDH-ES",
|
|
1586
|
+
expect: "private"
|
|
1587
|
+
}),
|
|
1588
|
+
epkJwk: isCryptoKey(publicKey) ? stripPrivateJwkFields(await keyToJWK(publicKey)) : stripPrivateJwkFields(publicKey)
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
if (isCryptoKey(ephemeralKeyInput)) {
|
|
1592
|
+
if (ephemeralKeyInput.type !== "private") throw new JWTError("ECDH-ES ephemeral CryptoKey must be a private key", "ERR_JWK_INVALID");
|
|
1593
|
+
return {
|
|
1594
|
+
ephemeralPrivateKey: ephemeralKeyInput,
|
|
1595
|
+
epkJwk: stripPrivateJwkFields(await keyToJWK(ephemeralKeyInput))
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
if (isJWK(ephemeralKeyInput) && "d" in ephemeralKeyInput) return {
|
|
1599
|
+
ephemeralPrivateKey: await importKey(ephemeralKeyInput, {
|
|
1600
|
+
alg: "ECDH-ES",
|
|
1601
|
+
expect: "private"
|
|
1602
|
+
}),
|
|
1603
|
+
epkJwk: stripPrivateJwkFields(ephemeralKeyInput)
|
|
1604
|
+
};
|
|
1605
|
+
throw new JWTError("Unsupported ECDH-ES ephemeral key material", "ERR_JWK_INVALID");
|
|
1606
|
+
}
|
|
1607
|
+
function stripPrivateJwkFields(jwk) {
|
|
1608
|
+
const { d: _d, ...pub } = jwk;
|
|
1609
|
+
return pub;
|
|
1610
|
+
}
|
|
1611
|
+
function inferAesImportAlgorithm(enc, cekLengthBytes) {
|
|
1612
|
+
const bitLength = cekLengthBytes << 3;
|
|
1613
|
+
if (enc) {
|
|
1614
|
+
if (enc.includes("GCM")) return {
|
|
1615
|
+
name: "AES-GCM",
|
|
1616
|
+
length: Number.parseInt(enc.slice(1, 4), 10)
|
|
1617
|
+
};
|
|
1618
|
+
if (enc.includes("CBC-HS")) return;
|
|
1619
|
+
}
|
|
1620
|
+
if (bitLength === 128 || bitLength === 192 || bitLength === 256) return {
|
|
1621
|
+
name: "AES-GCM",
|
|
1622
|
+
length: bitLength
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
export { generateIV as C, generateCEK as S, verify as T, unwrapKey as _, deriveKeyFromPassword as a, decrypt as b, exportPEM as c, generateKey as d, getJWKFromSet as f, importPEM as g, importKey as h, deriveJWKFromPassword as i, exportToPEM as l, importFromPEM as m, clearJWKCache as n, deriveSharedSecret as o, getJWKsFromSet as p, configureJWKCache as r, exportKey as s, WeakMapJWKCache as t, generateJWK as u, wrapKey as v, sign as w, encrypt as x, encryptKey as y };
|