lacewing 1.0.0
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/CHANGELOG.md +11 -0
- package/LICENSE +9 -0
- package/NOTICE.md +1 -0
- package/README.md +313 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/src/cookbook/access-refresh.js +86 -0
- package/dist/src/cookbook/access-refresh.js.map +1 -0
- package/dist/src/http/bearer.js +44 -0
- package/dist/src/http/bearer.js.map +1 -0
- package/dist/src/http/cookies.js +96 -0
- package/dist/src/http/cookies.js.map +1 -0
- package/dist/src/jwks/local.js +85 -0
- package/dist/src/jwks/local.js.map +1 -0
- package/dist/src/jwks/remote.js +121 -0
- package/dist/src/jwks/remote.js.map +1 -0
- package/dist/src/jwt/decrypt.js +126 -0
- package/dist/src/jwt/decrypt.js.map +1 -0
- package/dist/src/jwt/decryption_profile.js +78 -0
- package/dist/src/jwt/decryption_profile.js.map +1 -0
- package/dist/src/jwt/encrypt.js +149 -0
- package/dist/src/jwt/encrypt.js.map +1 -0
- package/dist/src/jwt/profile.js +102 -0
- package/dist/src/jwt/profile.js.map +1 -0
- package/dist/src/jwt/sign.js +173 -0
- package/dist/src/jwt/sign.js.map +1 -0
- package/dist/src/jwt/verify.js +118 -0
- package/dist/src/jwt/verify.js.map +1 -0
- package/dist/src/key/encryption.js +132 -0
- package/dist/src/key/encryption.js.map +1 -0
- package/dist/src/key/export.js +46 -0
- package/dist/src/key/export.js.map +1 -0
- package/dist/src/key/generate.js +47 -0
- package/dist/src/key/generate.js.map +1 -0
- package/dist/src/key/import.js +136 -0
- package/dist/src/key/import.js.map +1 -0
- package/dist/src/legacy/rs256.js +11 -0
- package/dist/src/legacy/rs256.js.map +1 -0
- package/dist/src/legacy/rsa.js +37 -0
- package/dist/src/legacy/rsa.js.map +1 -0
- package/dist/src/lib/algorithms.js +60 -0
- package/dist/src/lib/algorithms.js.map +1 -0
- package/dist/src/lib/base64url.js +28 -0
- package/dist/src/lib/base64url.js.map +1 -0
- package/dist/src/lib/claims.js +111 -0
- package/dist/src/lib/claims.js.map +1 -0
- package/dist/src/lib/duration.js +24 -0
- package/dist/src/lib/duration.js.map +1 -0
- package/dist/src/lib/entropy.js +70 -0
- package/dist/src/lib/entropy.js.map +1 -0
- package/dist/src/lib/headers.js +75 -0
- package/dist/src/lib/headers.js.map +1 -0
- package/dist/src/lib/invariants.js +12 -0
- package/dist/src/lib/invariants.js.map +1 -0
- package/dist/src/lib/json.js +140 -0
- package/dist/src/lib/json.js.map +1 -0
- package/dist/src/lib/jwe_algorithms.js +81 -0
- package/dist/src/lib/jwe_algorithms.js.map +1 -0
- package/dist/src/lib/payload_hygiene.js +116 -0
- package/dist/src/lib/payload_hygiene.js.map +1 -0
- package/dist/src/lib/utf8.js +27 -0
- package/dist/src/lib/utf8.js.map +1 -0
- package/dist/src/revocation/memory.js +67 -0
- package/dist/src/revocation/memory.js.map +1 -0
- package/dist/src/revocation/store.js +25 -0
- package/dist/src/revocation/store.js.map +1 -0
- package/dist/src/types.js +51 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/util/errors.js +96 -0
- package/dist/src/util/errors.js.map +1 -0
- package/dist/src/util/unsafe-decode.js +45 -0
- package/dist/src/util/unsafe-decode.js.map +1 -0
- package/dist/types/index.d.ts +29 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/src/cookbook/access-refresh.d.ts +77 -0
- package/dist/types/src/cookbook/access-refresh.d.ts.map +1 -0
- package/dist/types/src/http/bearer.d.ts +20 -0
- package/dist/types/src/http/bearer.d.ts.map +1 -0
- package/dist/types/src/http/cookies.d.ts +38 -0
- package/dist/types/src/http/cookies.d.ts.map +1 -0
- package/dist/types/src/jwks/local.d.ts +16 -0
- package/dist/types/src/jwks/local.d.ts.map +1 -0
- package/dist/types/src/jwks/remote.d.ts +23 -0
- package/dist/types/src/jwks/remote.d.ts.map +1 -0
- package/dist/types/src/jwt/decrypt.d.ts +23 -0
- package/dist/types/src/jwt/decrypt.d.ts.map +1 -0
- package/dist/types/src/jwt/decryption_profile.d.ts +32 -0
- package/dist/types/src/jwt/decryption_profile.d.ts.map +1 -0
- package/dist/types/src/jwt/encrypt.d.ts +34 -0
- package/dist/types/src/jwt/encrypt.d.ts.map +1 -0
- package/dist/types/src/jwt/profile.d.ts +37 -0
- package/dist/types/src/jwt/profile.d.ts.map +1 -0
- package/dist/types/src/jwt/sign.d.ts +59 -0
- package/dist/types/src/jwt/sign.d.ts.map +1 -0
- package/dist/types/src/jwt/verify.d.ts +41 -0
- package/dist/types/src/jwt/verify.d.ts.map +1 -0
- package/dist/types/src/key/encryption.d.ts +37 -0
- package/dist/types/src/key/encryption.d.ts.map +1 -0
- package/dist/types/src/key/export.d.ts +10 -0
- package/dist/types/src/key/export.d.ts.map +1 -0
- package/dist/types/src/key/generate.d.ts +27 -0
- package/dist/types/src/key/generate.d.ts.map +1 -0
- package/dist/types/src/key/import.d.ts +20 -0
- package/dist/types/src/key/import.d.ts.map +1 -0
- package/dist/types/src/legacy/rs256.d.ts +11 -0
- package/dist/types/src/legacy/rs256.d.ts.map +1 -0
- package/dist/types/src/legacy/rsa.d.ts +25 -0
- package/dist/types/src/legacy/rsa.d.ts.map +1 -0
- package/dist/types/src/lib/algorithms.d.ts +34 -0
- package/dist/types/src/lib/algorithms.d.ts.map +1 -0
- package/dist/types/src/lib/base64url.d.ts +8 -0
- package/dist/types/src/lib/base64url.d.ts.map +1 -0
- package/dist/types/src/lib/claims.d.ts +15 -0
- package/dist/types/src/lib/claims.d.ts.map +1 -0
- package/dist/types/src/lib/duration.d.ts +7 -0
- package/dist/types/src/lib/duration.d.ts.map +1 -0
- package/dist/types/src/lib/entropy.d.ts +21 -0
- package/dist/types/src/lib/entropy.d.ts.map +1 -0
- package/dist/types/src/lib/headers.d.ts +19 -0
- package/dist/types/src/lib/headers.d.ts.map +1 -0
- package/dist/types/src/lib/invariants.d.ts +6 -0
- package/dist/types/src/lib/invariants.d.ts.map +1 -0
- package/dist/types/src/lib/json.d.ts +24 -0
- package/dist/types/src/lib/json.d.ts.map +1 -0
- package/dist/types/src/lib/jwe_algorithms.d.ts +46 -0
- package/dist/types/src/lib/jwe_algorithms.d.ts.map +1 -0
- package/dist/types/src/lib/payload_hygiene.d.ts +20 -0
- package/dist/types/src/lib/payload_hygiene.d.ts.map +1 -0
- package/dist/types/src/lib/utf8.d.ts +7 -0
- package/dist/types/src/lib/utf8.d.ts.map +1 -0
- package/dist/types/src/revocation/memory.d.ts +22 -0
- package/dist/types/src/revocation/memory.d.ts.map +1 -0
- package/dist/types/src/revocation/store.d.ts +17 -0
- package/dist/types/src/revocation/store.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +196 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/src/util/errors.d.ts +89 -0
- package/dist/types/src/util/errors.d.ts.map +1 -0
- package/dist/types/src/util/unsafe-decode.d.ts +21 -0
- package/dist/types/src/util/unsafe-decode.d.ts.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key generation with safe defaults:
|
|
3
|
+
* asymmetric-first - EdDSA unless you say otherwise - and secrets that
|
|
4
|
+
* are always cryptographically random at the algorithm's minimum size.
|
|
5
|
+
*/
|
|
6
|
+
import { generateKeyPair as joseGenerateKeyPair } from "jose";
|
|
7
|
+
import { KeyImportFailed, KeyTypeMismatch } from "../util/errors.js";
|
|
8
|
+
import { getAlgorithmProperties } from "../lib/algorithms.js";
|
|
9
|
+
import { createLacewingKey, SetAlg } from "../types.js";
|
|
10
|
+
/**
|
|
11
|
+
* Generate an asymmetric key pair, bound to `algorithm` (default EdDSA).
|
|
12
|
+
*/
|
|
13
|
+
export async function generateKeyPair(algorithm = "EdDSA", options = {}) {
|
|
14
|
+
const info = getAlgorithmProperties(algorithm);
|
|
15
|
+
if (info.kty === "oct") {
|
|
16
|
+
throw new KeyTypeMismatch(`${algorithm} is symmetric - use generateSecret() instead`);
|
|
17
|
+
}
|
|
18
|
+
const alg = SetAlg(algorithm);
|
|
19
|
+
try {
|
|
20
|
+
const { publicKey, privateKey } = await joseGenerateKeyPair(algorithm, {
|
|
21
|
+
extractable: options.extractable ?? false,
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
publicKey: createLacewingKey(alg, "public", publicKey),
|
|
25
|
+
privateKey: createLacewingKey(alg, "private", privateKey),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch (cause) {
|
|
29
|
+
throw new KeyImportFailed("Key generation failed", { cause });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Generate a random HMAC secret at the algorithm's minimum size
|
|
34
|
+
* (default HS256 -> 256 bits). Always passes the entropy checks by
|
|
35
|
+
* construction.
|
|
36
|
+
*/
|
|
37
|
+
export function generateSecret(algorithm = "HS256") {
|
|
38
|
+
const info = getAlgorithmProperties(algorithm);
|
|
39
|
+
if (info.kty !== "oct") {
|
|
40
|
+
throw new KeyTypeMismatch(`${algorithm} is asymmetric - use generateKeyPair() instead`);
|
|
41
|
+
}
|
|
42
|
+
const alg = SetAlg(algorithm);
|
|
43
|
+
const secret = new Uint8Array(Math.ceil(info.minKeyBits / 8));
|
|
44
|
+
globalThis.crypto.getRandomValues(secret);
|
|
45
|
+
return createLacewingKey(alg, "secret", secret);
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../src/key/generate.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,IAAI,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAoB,MAAM,aAAa,CAAC;AAU1E;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACpC,YAAiB,OAAc,EAC/B,UAAkC,EAAE;IAEpC,MAAM,IAAI,GAAG,sBAAsB,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,IAAI,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC;QACxB,MAAM,IAAI,eAAe,CACxB,GAAG,SAAS,8CAA8C,CAC1D,CAAC;IACH,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAoC,CAAC;IACjE,IAAI,CAAC;QACJ,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,MAAM,mBAAmB,CAAC,SAAS,EAAE;YACtE,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,KAAK;SACzC,CAAC,CAAC;QACH,OAAO;YACN,SAAS,EAAE,iBAAiB,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,CAAC;YACtD,UAAU,EAAE,iBAAiB,CAAC,GAAG,EAAE,SAAS,EAAE,UAAU,CAAC;SACzD,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,eAAe,CAAC,uBAAuB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/D,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC7B,YAAiB,OAAc;IAE/B,MAAM,IAAI,GAAG,sBAAsB,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,IAAI,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC;QACxB,MAAM,IAAI,eAAe,CACxB,GAAG,SAAS,gDAAgD,CAC5D,CAAC;IACH,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAoC,CAAC;IACjE,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9D,UAAU,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAC1C,OAAO,iBAAiB,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key import with algorithm binding (RFC 8725 §3.1/§3.2/§3.5).
|
|
3
|
+
*
|
|
4
|
+
* Every key enters Lacewing through this module and leaves bound to
|
|
5
|
+
* exactly one algorithm, carried in the type system. Sign/verify never
|
|
6
|
+
* accept raw `CryptoKey`s, so the alg-to-key binding cannot be bypassed.
|
|
7
|
+
*/
|
|
8
|
+
import { importJWK, importPKCS8, importSPKI } from "jose";
|
|
9
|
+
import { EntropyCheckFailed, KeyImportFailed, KeyTypeMismatch, } from "../util/errors.js";
|
|
10
|
+
import { getAlgorithmProperties } from "../lib/algorithms.js";
|
|
11
|
+
import { validateHMACSecret } from "../lib/entropy.js";
|
|
12
|
+
import { decodeBase64url } from "../lib/base64url.js";
|
|
13
|
+
import { createLacewingKey, SetAlg, } from "../types.js";
|
|
14
|
+
function expectedWebCryptoName(alg, info) {
|
|
15
|
+
if (alg === "EdDSA")
|
|
16
|
+
return "Ed25519";
|
|
17
|
+
if (info.kty === "EC")
|
|
18
|
+
return "ECDSA";
|
|
19
|
+
if (info.kty === "oct")
|
|
20
|
+
return "HMAC";
|
|
21
|
+
// RSA: PS* uses RSA-PSS; legacy RS* uses PKCS#1 v1.5.
|
|
22
|
+
return alg.startsWith("PS") ? "RSA-PSS" : "RSASSA-PKCS1-v1_5";
|
|
23
|
+
}
|
|
24
|
+
// Only called for asymmetric JWKs; oct keys take the importSecret path.
|
|
25
|
+
function keyTypeOfJwk(jwk) {
|
|
26
|
+
return typeof jwk.d === "string" ? "private" : "public";
|
|
27
|
+
}
|
|
28
|
+
function assertJwkMatches(jwk, alg, info) {
|
|
29
|
+
if (jwk.kty !== info.kty) {
|
|
30
|
+
throw new KeyTypeMismatch(`Key type "${jwk.kty}" cannot be used with ${alg} (expected ${info.kty})`);
|
|
31
|
+
}
|
|
32
|
+
if (info.crv !== undefined && jwk.crv !== info.crv) {
|
|
33
|
+
throw new KeyTypeMismatch(`${alg} requires curve ${info.crv}`);
|
|
34
|
+
}
|
|
35
|
+
if (typeof jwk.alg === "string" && jwk.alg !== alg) {
|
|
36
|
+
throw new KeyTypeMismatch("JWK declares a different algorithm than it is being imported for");
|
|
37
|
+
}
|
|
38
|
+
if (info.kty === "RSA" && typeof jwk.n === "string") {
|
|
39
|
+
const modulusBits = decodeBase64url(jwk.n).length * 8;
|
|
40
|
+
if (modulusBits < info.minKeyBits) {
|
|
41
|
+
throw new KeyTypeMismatch(`${alg} requires an RSA modulus of at least ${info.minKeyBits} bits`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function assertCryptoKeyMatches(key, alg, info) {
|
|
46
|
+
const algorithm = key.algorithm;
|
|
47
|
+
if (algorithm.name !== expectedWebCryptoName(alg, info)) {
|
|
48
|
+
throw new KeyTypeMismatch(`CryptoKey algorithm "${algorithm.name}" cannot be used with ${alg}`);
|
|
49
|
+
}
|
|
50
|
+
if (info.kty === "EC" && algorithm.namedCurve !== info.crv) {
|
|
51
|
+
throw new KeyTypeMismatch(`${alg} requires curve ${info.crv}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function importSecret(material, alg) {
|
|
55
|
+
let secret;
|
|
56
|
+
if (material instanceof Uint8Array) {
|
|
57
|
+
secret = material;
|
|
58
|
+
}
|
|
59
|
+
else if (typeof material === "string") {
|
|
60
|
+
// RS256->HS256 key-confusion defense (CVE-2016-5431 class): a PEM -
|
|
61
|
+
// typically the issuer's *public* key - is never a valid HMAC secret.
|
|
62
|
+
if (material.includes("-----BEGIN")) {
|
|
63
|
+
throw new KeyTypeMismatch("PEM key material cannot be used as an HMAC secret");
|
|
64
|
+
}
|
|
65
|
+
secret = new TextEncoder().encode(material);
|
|
66
|
+
}
|
|
67
|
+
else if (typeof material === "object" &&
|
|
68
|
+
material !== null &&
|
|
69
|
+
!(material instanceof CryptoKey) &&
|
|
70
|
+
material.kty === "oct" &&
|
|
71
|
+
typeof material.k === "string") {
|
|
72
|
+
secret = decodeBase64url(material.k);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
throw new KeyImportFailed("HMAC secrets must be a Uint8Array, string, or oct JWK");
|
|
76
|
+
}
|
|
77
|
+
validateHMACSecret(secret, alg);
|
|
78
|
+
return { key: secret };
|
|
79
|
+
}
|
|
80
|
+
async function importAsymmetric(material, alg, info) {
|
|
81
|
+
if (material instanceof CryptoKey) {
|
|
82
|
+
assertCryptoKeyMatches(material, alg, info);
|
|
83
|
+
const keyType = material.type;
|
|
84
|
+
if (keyType !== "public" && keyType !== "private") {
|
|
85
|
+
throw new KeyTypeMismatch(`A secret key cannot be used with ${alg}`);
|
|
86
|
+
}
|
|
87
|
+
return { key: material, keyType };
|
|
88
|
+
}
|
|
89
|
+
if (typeof material === "string") {
|
|
90
|
+
if (material.includes("-----BEGIN PRIVATE KEY-----")) {
|
|
91
|
+
return { key: await importPKCS8(material, alg), keyType: "private" };
|
|
92
|
+
}
|
|
93
|
+
if (material.includes("-----BEGIN PUBLIC KEY-----")) {
|
|
94
|
+
return { key: await importSPKI(material, alg), keyType: "public" };
|
|
95
|
+
}
|
|
96
|
+
throw new KeyImportFailed("Unsupported PEM - only PKCS#8 private keys and SPKI public keys are accepted");
|
|
97
|
+
}
|
|
98
|
+
if (material instanceof Uint8Array) {
|
|
99
|
+
throw new KeyTypeMismatch(`Raw bytes cannot be used with ${alg}`);
|
|
100
|
+
}
|
|
101
|
+
if (typeof material === "object" && material !== null) {
|
|
102
|
+
assertJwkMatches(material, alg, info);
|
|
103
|
+
const key = await importJWK(material, alg);
|
|
104
|
+
return { key, keyType: keyTypeOfJwk(material) };
|
|
105
|
+
}
|
|
106
|
+
throw new KeyImportFailed("Unsupported key material");
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Import key material and bind it to `algorithm`. The returned
|
|
110
|
+
* {@link LacewingKey} can only ever be used with that algorithm.
|
|
111
|
+
*
|
|
112
|
+
* Throws {@link KeyTypeMismatch} when material and algorithm disagree,
|
|
113
|
+
* {@link EntropyCheckFailed} for weak HMAC secrets, and
|
|
114
|
+
* {@link KeyImportFailed} for anything else that goes wrong.
|
|
115
|
+
*/
|
|
116
|
+
export async function importKey(material, algorithm) {
|
|
117
|
+
const info = getAlgorithmProperties(algorithm);
|
|
118
|
+
const alg = SetAlg(algorithm);
|
|
119
|
+
try {
|
|
120
|
+
if (info.kty === "oct") {
|
|
121
|
+
const { key } = importSecret(material, algorithm);
|
|
122
|
+
return createLacewingKey(alg, "secret", key);
|
|
123
|
+
}
|
|
124
|
+
const { key, keyType } = await importAsymmetric(material, algorithm, info);
|
|
125
|
+
return createLacewingKey(alg, keyType, key);
|
|
126
|
+
}
|
|
127
|
+
catch (cause) {
|
|
128
|
+
if (cause instanceof KeyTypeMismatch ||
|
|
129
|
+
cause instanceof EntropyCheckFailed ||
|
|
130
|
+
cause instanceof KeyImportFailed) {
|
|
131
|
+
throw cause;
|
|
132
|
+
}
|
|
133
|
+
throw new KeyImportFailed("Key import failed", { cause });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=import.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import.js","sourceRoot":"","sources":["../../../src/key/import.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LEGACY INTEROP - explicit opt-in only.
|
|
3
|
+
*
|
|
4
|
+
* `RS256` (RSASSA-PKCS1-v1_5) is not in Lacewing's default registry. This
|
|
5
|
+
* module is kept for backwards compatibility and re-exports the RS256 opt-in
|
|
6
|
+
* from {@link module:legacy/rsa}, where the rest of the `RS*` family lives.
|
|
7
|
+
* Calling {@link enableLegacyRS256} is the grep-loud marker code review looks
|
|
8
|
+
* for; prefer `PS256`/`EdDSA` everywhere you control both sides.
|
|
9
|
+
*/
|
|
10
|
+
export { enableLegacyRS256 } from "./rsa.js";
|
|
11
|
+
//# sourceMappingURL=rs256.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rs256.js","sourceRoot":"","sources":["../../../src/legacy/rs256.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LEGACY INTEROP - explicit opt-in only.
|
|
3
|
+
*
|
|
4
|
+
* The `RS*` family (RSASSA-PKCS1-v1_5) is not in Lacewing's default registry:
|
|
5
|
+
* PKCS#1 v1.5 signatures are the deprecated ancestor of RSA-PSS and only
|
|
6
|
+
* belong in systems that must interoperate with issuers that cannot emit
|
|
7
|
+
* `PS256`/`EdDSA`. Each `enable...` call is the grep-loud marker code review
|
|
8
|
+
* looks for; prefer `PS256`/`EdDSA` everywhere you control both sides.
|
|
9
|
+
*
|
|
10
|
+
* `none` can never enter through this door - `registerLegacyAlgorithm` refuses
|
|
11
|
+
* it - and neither can any algorithm not on this curated legacy list.
|
|
12
|
+
*/
|
|
13
|
+
import { registerLegacyAlgorithm } from "../lib/algorithms.js";
|
|
14
|
+
const RSA_MIN_KEY_BITS = 2048;
|
|
15
|
+
/** Register legacy `RS256`. After this, importKey/profiles/JWKS accept it. */
|
|
16
|
+
export function enableLegacyRS256() {
|
|
17
|
+
registerLegacyAlgorithm({ name: "RS256", kty: "RSA", minKeyBits: RSA_MIN_KEY_BITS });
|
|
18
|
+
}
|
|
19
|
+
/** Register legacy `RS384`. Prefer `PS384`/`EdDSA` where you control both sides. */
|
|
20
|
+
export function enableLegacyRS384() {
|
|
21
|
+
registerLegacyAlgorithm({ name: "RS384", kty: "RSA", minKeyBits: RSA_MIN_KEY_BITS });
|
|
22
|
+
}
|
|
23
|
+
/** Register legacy `RS512`. Prefer `PS512`/`EdDSA` where you control both sides. */
|
|
24
|
+
export function enableLegacyRS512() {
|
|
25
|
+
registerLegacyAlgorithm({ name: "RS512", kty: "RSA", minKeyBits: RSA_MIN_KEY_BITS });
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Register the whole legacy `RS256`/`RS384`/`RS512` family at once, for
|
|
29
|
+
* interop with an issuer that may rotate across hash sizes. Still an explicit,
|
|
30
|
+
* grep-loud opt-in - do not call it "just in case".
|
|
31
|
+
*/
|
|
32
|
+
export function enableLegacyRSA() {
|
|
33
|
+
enableLegacyRS256();
|
|
34
|
+
enableLegacyRS384();
|
|
35
|
+
enableLegacyRS512();
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=rsa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rsa.js","sourceRoot":"","sources":["../../../src/legacy/rsa.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B,8EAA8E;AAC9E,MAAM,UAAU,iBAAiB;IAChC,uBAAuB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC,CAAC;AACtF,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,iBAAiB;IAChC,uBAAuB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC,CAAC;AACtF,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,iBAAiB;IAChC,uBAAuB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC,CAAC;AACtF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe;IAC9B,iBAAiB,EAAE,CAAC;IACpB,iBAAiB,EAAE,CAAC;IACpB,iBAAiB,EAAE,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The algorithm registry - single source of truth for which algorithms
|
|
3
|
+
* exist and which key types they pair with (RFC 8725 §3.1/§3.2).
|
|
4
|
+
*
|
|
5
|
+
* `none`, `RSA1_5` and `RS*` are simply absent. Adding an algorithm is a
|
|
6
|
+
* one-entry change here (cryptographic agility, §3.2.4). Legacy interop
|
|
7
|
+
* algorithms can only enter through `registerLegacyAlgorithm`, which is
|
|
8
|
+
* reachable exclusively via the explicit `legacy/` imports.
|
|
9
|
+
*/
|
|
10
|
+
import { AlgorithmNotAllowed } from "../util/errors.js";
|
|
11
|
+
import { SetAlg } from "../types.js";
|
|
12
|
+
const CORE_ALGORITHMS = [
|
|
13
|
+
{ name: "EdDSA", kty: "OKP", crv: "Ed25519", minKeyBits: 256 },
|
|
14
|
+
{ name: "ES256", kty: "EC", crv: "P-256", minKeyBits: 256 },
|
|
15
|
+
{ name: "ES384", kty: "EC", crv: "P-384", minKeyBits: 384 },
|
|
16
|
+
{ name: "ES512", kty: "EC", crv: "P-521", minKeyBits: 521 },
|
|
17
|
+
{ name: "PS256", kty: "RSA", minKeyBits: 2048 },
|
|
18
|
+
{ name: "PS384", kty: "RSA", minKeyBits: 2048 },
|
|
19
|
+
{ name: "PS512", kty: "RSA", minKeyBits: 2048 },
|
|
20
|
+
// RFC 7518 §3.2: HMAC keys must be at least as large as the hash output.
|
|
21
|
+
{ name: "HS256", kty: "oct", minKeyBits: 256 },
|
|
22
|
+
{ name: "HS384", kty: "oct", minKeyBits: 384 },
|
|
23
|
+
{ name: "HS512", kty: "oct", minKeyBits: 512 },
|
|
24
|
+
];
|
|
25
|
+
const registry = new Map(CORE_ALGORITHMS.map((info) => [info.name, info]));
|
|
26
|
+
/** Type guard: is this exact string an allowed algorithm? Case sensitive. */
|
|
27
|
+
export function isValidAlgorithm(alg) {
|
|
28
|
+
return registry.has(alg);
|
|
29
|
+
}
|
|
30
|
+
/** Look up an algorithm's properties, or throw {@link AlgorithmNotAllowed}. */
|
|
31
|
+
export function getAlgorithmProperties(alg) {
|
|
32
|
+
const info = registry.get(alg);
|
|
33
|
+
if (info === undefined) {
|
|
34
|
+
// Deliberately does not echo `alg`: it may be attacker-controlled.
|
|
35
|
+
throw new AlgorithmNotAllowed("Algorithm is not in the Lacewing allowlist");
|
|
36
|
+
}
|
|
37
|
+
return info;
|
|
38
|
+
}
|
|
39
|
+
/** Validate against the registry and brand the string as {@link ValidAlg}. */
|
|
40
|
+
export function toValidAlg(alg) {
|
|
41
|
+
getAlgorithmProperties(alg);
|
|
42
|
+
return SetAlg(alg);
|
|
43
|
+
}
|
|
44
|
+
/** Currently registered algorithm names (core + any enabled legacy). */
|
|
45
|
+
export function listAlgorithms() {
|
|
46
|
+
return [...registry.keys()];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Internal - only the `legacy/` modules call this. Registers an interop
|
|
50
|
+
* algorithm so it becomes importable/verifiable. Never accepts `none`.
|
|
51
|
+
*/
|
|
52
|
+
export function registerLegacyAlgorithm(info) {
|
|
53
|
+
if (info.name.toLowerCase() === "none") {
|
|
54
|
+
throw new AlgorithmNotAllowed("Algorithm 'none' is not allowed");
|
|
55
|
+
}
|
|
56
|
+
if (!registry.has(info.name)) {
|
|
57
|
+
registry.set(info.name, info);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=algorithms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"algorithms.js","sourceRoot":"","sources":["../../../src/lib/algorithms.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAiB,MAAM,aAAa,CAAC;AAapD,MAAM,eAAe,GAA6B;IACjD,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,EAAE;IAC9D,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE;IAC3D,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE;IAC3D,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE;IAC3D,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE;IAC/C,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE;IAC/C,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE;IAC/C,yEAAyE;IACzE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE;IAC9C,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE;IAC9C,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE;CAC9C,CAAC;AAEF,MAAM,QAAQ,GAAG,IAAI,GAAG,CACvB,eAAe,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAChD,CAAC;AAEF,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC3C,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,sBAAsB,CAAC,GAAW;IACjD,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACxB,mEAAmE;QACnE,MAAM,IAAI,mBAAmB,CAAC,4CAA4C,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,UAAU,CAAC,GAAW;IACrC,sBAAsB,CAAC,GAAG,CAAC,CAAC;IAC5B,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACpB,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,cAAc;IAC7B,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAmB;IAC1D,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;QACxC,MAAM,IAAI,mBAAmB,CAAC,iCAAiC,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict base64url (RFC 7515 §2: no padding, no whitespace, no URL-unsafe
|
|
3
|
+
* characters). Non-canonical encodings are rejected to close parser
|
|
4
|
+
* differentials between Lacewing and other JWT consumers.
|
|
5
|
+
*/
|
|
6
|
+
import { Buffer } from "node:buffer";
|
|
7
|
+
import { JWTInvalid } from "../util/errors.js";
|
|
8
|
+
const B64URL = /^[A-Za-z0-9_-]*$/;
|
|
9
|
+
export function encodeBase64url(bytes) {
|
|
10
|
+
return Buffer.from(bytes).toString("base64url");
|
|
11
|
+
}
|
|
12
|
+
export function decodeBase64url(encoded) {
|
|
13
|
+
if (typeof encoded !== "string" || !B64URL.test(encoded)) {
|
|
14
|
+
throw new JWTInvalid("Invalid base64url");
|
|
15
|
+
}
|
|
16
|
+
// A single leftover character can never encode a full byte.
|
|
17
|
+
if (encoded.length % 4 === 1) {
|
|
18
|
+
throw new JWTInvalid("Invalid base64url length");
|
|
19
|
+
}
|
|
20
|
+
const bytes = Buffer.from(encoded, "base64url");
|
|
21
|
+
// Canonical check: trailing bits that don't round-trip are rejected
|
|
22
|
+
// (two encodings of the same bytes would otherwise both verify).
|
|
23
|
+
if (bytes.toString("base64url") !== encoded) {
|
|
24
|
+
throw new JWTInvalid("Non-canonical base64url");
|
|
25
|
+
}
|
|
26
|
+
return new Uint8Array(bytes);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=base64url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64url.js","sourceRoot":"","sources":["../../../src/lib/base64url.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,MAAM,GAAG,kBAAkB,CAAC;AAElC,MAAM,UAAU,eAAe,CAAC,KAAiB;IAChD,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAe;IAC9C,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,UAAU,CAAC,mBAAmB,CAAC,CAAC;IAC3C,CAAC;IACD,4DAA4D;IAC5D,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,UAAU,CAAC,0BAA0B,CAAC,CAAC;IAClD,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAChD,oEAAoE;IACpE,iEAAiE;IACjE,IAAI,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,OAAO,EAAE,CAAC;QAC7C,MAAM,IAAI,UAAU,CAAC,yBAAyB,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claims validation engine (RFC 8725 §3.8/§3.9).
|
|
3
|
+
*
|
|
4
|
+
* One pass, fail-fast, all-or-nothing. Rejection messages never echo
|
|
5
|
+
* untrusted token content (log-injection hygiene, §3.10) - errors name
|
|
6
|
+
* the claim, not its value.
|
|
7
|
+
*/
|
|
8
|
+
import { JWTClaimValidationFailed, JWTExpired, MissingClaim, } from "../util/errors.js";
|
|
9
|
+
const MAX_STRING_CLAIM_LENGTH = 1024;
|
|
10
|
+
function requireString(payload, claim) {
|
|
11
|
+
const value = payload[claim];
|
|
12
|
+
if (value === undefined) {
|
|
13
|
+
throw new MissingClaim(claim);
|
|
14
|
+
}
|
|
15
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
16
|
+
throw new JWTClaimValidationFailed(claim, `Claim "${claim}" must be a non-empty string`);
|
|
17
|
+
}
|
|
18
|
+
if (value.length > MAX_STRING_CLAIM_LENGTH) {
|
|
19
|
+
throw new JWTClaimValidationFailed(claim, `Claim "${claim}" exceeds the length cap`);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function requireNumber(payload, claim) {
|
|
24
|
+
const value = payload[claim];
|
|
25
|
+
if (value === undefined) {
|
|
26
|
+
throw new MissingClaim(claim);
|
|
27
|
+
}
|
|
28
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
29
|
+
throw new JWTClaimValidationFailed(claim, `Claim "${claim}" must be a finite number`);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function audienceMatches(aud, expected) {
|
|
34
|
+
if (typeof aud === "string") {
|
|
35
|
+
return aud === expected;
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(aud)) {
|
|
38
|
+
return aud.some((entry) => typeof entry === "string" && entry === expected);
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Validate a decoded payload against a profile. Only called after the
|
|
44
|
+
* signature has been verified. `nowSeconds` is injectable for tests.
|
|
45
|
+
*/
|
|
46
|
+
export async function validateClaims(payload, profile, nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
47
|
+
const skew = profile.maxClockSkew;
|
|
48
|
+
// iss - must exactly match the trusted issuer (§3.8).
|
|
49
|
+
const iss = requireString(payload, "iss");
|
|
50
|
+
if (iss !== profile.iss) {
|
|
51
|
+
throw new JWTClaimValidationFailed("iss", "Token issuer is not trusted by this profile");
|
|
52
|
+
}
|
|
53
|
+
// aud - the profile's audience must be present (§3.9).
|
|
54
|
+
if (payload.aud === undefined) {
|
|
55
|
+
throw new MissingClaim("aud");
|
|
56
|
+
}
|
|
57
|
+
if (!audienceMatches(payload.aud, profile.aud)) {
|
|
58
|
+
throw new JWTClaimValidationFailed("aud", "Token audience does not include this profile's audience");
|
|
59
|
+
}
|
|
60
|
+
// exp - required, with bounded clock skew.
|
|
61
|
+
const exp = requireNumber(payload, "exp");
|
|
62
|
+
if (nowSeconds >= exp + skew) {
|
|
63
|
+
throw new JWTExpired("Token has expired");
|
|
64
|
+
}
|
|
65
|
+
// iat - required, may not be in the future.
|
|
66
|
+
const iat = requireNumber(payload, "iat");
|
|
67
|
+
if (iat > nowSeconds + skew) {
|
|
68
|
+
throw new JWTClaimValidationFailed("iat", "Token issued-at time is in the future");
|
|
69
|
+
}
|
|
70
|
+
// maxTokenAge - enforced independently of exp (LW-life.2), so a
|
|
71
|
+
// compromised signer can't mint decade-long tokens we accept.
|
|
72
|
+
if (nowSeconds - iat > profile.maxTokenAge + skew) {
|
|
73
|
+
throw new JWTExpired("Token exceeds the profile's maximum age");
|
|
74
|
+
}
|
|
75
|
+
// nbf - optional, but honored when present.
|
|
76
|
+
if (payload.nbf !== undefined) {
|
|
77
|
+
const nbf = requireNumber(payload, "nbf");
|
|
78
|
+
if (nbf > nowSeconds + skew) {
|
|
79
|
+
throw new JWTClaimValidationFailed("nbf", "Token is not yet valid");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// jti - optional per RFC 7519, but bounded when present.
|
|
83
|
+
if (payload.jti !== undefined) {
|
|
84
|
+
requireString(payload, "jti");
|
|
85
|
+
}
|
|
86
|
+
// sub - validated when the profile pins one (§3.8.2).
|
|
87
|
+
if (payload.sub !== undefined) {
|
|
88
|
+
requireString(payload, "sub");
|
|
89
|
+
}
|
|
90
|
+
if (profile.subject !== undefined) {
|
|
91
|
+
const sub = requireString(payload, "sub");
|
|
92
|
+
if (sub !== profile.subject) {
|
|
93
|
+
throw new JWTClaimValidationFailed("sub", "Token subject does not match this profile");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Custom claim validators - run last, after all standard checks.
|
|
97
|
+
if (profile.claimValidators !== undefined) {
|
|
98
|
+
for (const [claim, validate] of Object.entries(profile.claimValidators)) {
|
|
99
|
+
try {
|
|
100
|
+
await validate(payload[claim], payload);
|
|
101
|
+
}
|
|
102
|
+
catch (cause) {
|
|
103
|
+
if (cause instanceof JWTClaimValidationFailed || cause instanceof MissingClaim) {
|
|
104
|
+
throw cause;
|
|
105
|
+
}
|
|
106
|
+
throw new JWTClaimValidationFailed(claim, `Custom validation for claim "${claim}" failed`, { cause });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=claims.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claims.js","sourceRoot":"","sources":["../../../src/lib/claims.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-friendly duration parsing: public APIs accept `900` (seconds) or
|
|
3
|
+
* `"15m"`; everything internal works in branded {@link DurationSeconds}.
|
|
4
|
+
*/
|
|
5
|
+
import { toSeconds } from "../types.js";
|
|
6
|
+
const DURATION = /^(\d{1,9})(s|m|h|d)$/;
|
|
7
|
+
const MULTIPLIER = {
|
|
8
|
+
s: 1,
|
|
9
|
+
m: 60,
|
|
10
|
+
h: 3600,
|
|
11
|
+
d: 86400,
|
|
12
|
+
};
|
|
13
|
+
export function parseDuration(value) {
|
|
14
|
+
if (typeof value === "number") {
|
|
15
|
+
return toSeconds(value);
|
|
16
|
+
}
|
|
17
|
+
const match = DURATION.exec(value.trim());
|
|
18
|
+
if (match === null) {
|
|
19
|
+
throw new TypeError(`Invalid duration "${value}" - use seconds or "30s" / "15m" / "1h" / "7d"`);
|
|
20
|
+
}
|
|
21
|
+
const [, amount, unit] = match;
|
|
22
|
+
return toSeconds(Number(amount) * MULTIPLIER[unit]);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=duration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duration.js","sourceRoot":"","sources":["../../../src/lib/duration.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAwB,MAAM,aAAa,CAAC;AAE9D,MAAM,QAAQ,GAAG,sBAAsB,CAAC;AAExC,MAAM,UAAU,GAA2B;IAC1C,CAAC,EAAE,CAAC;IACJ,CAAC,EAAE,EAAE;IACL,CAAC,EAAE,IAAI;IACP,CAAC,EAAE,KAAK;CACR,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,KAAsB;IACnD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1C,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,SAAS,CAClB,qBAAqB,KAAK,gDAAgD,CAC1E,CAAC;IACH,CAAC;IACD,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC;IAC/B,OAAO,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,GAAI,UAAU,CAAC,IAAc,CAAY,CAAC,CAAC;AAC3E,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HMAC secret strength checks (RFC 8725 §3.5).
|
|
3
|
+
*
|
|
4
|
+
* Two gates at import time:
|
|
5
|
+
* 1. Minimum length per algorithm (RFC 7518: >= hash output size).
|
|
6
|
+
* 2. A heuristic that rejects password-looking strings - the classic
|
|
7
|
+
* `secret: "my-secret"` misuse - even when they are long enough.
|
|
8
|
+
*/
|
|
9
|
+
import { EntropyCheckFailed } from "../util/errors.js";
|
|
10
|
+
import { getAlgorithmProperties } from "./algorithms.js";
|
|
11
|
+
// Substrings that only ever show up in human-chosen secrets.
|
|
12
|
+
const COMMON_WORDS = [
|
|
13
|
+
"password",
|
|
14
|
+
"passwort",
|
|
15
|
+
"secret",
|
|
16
|
+
"letmein",
|
|
17
|
+
"qwerty",
|
|
18
|
+
"123456",
|
|
19
|
+
"abcdef",
|
|
20
|
+
"changeme",
|
|
21
|
+
"default",
|
|
22
|
+
];
|
|
23
|
+
function shannonBitsPerByte(bytes) {
|
|
24
|
+
const counts = new Map();
|
|
25
|
+
for (const byte of bytes) {
|
|
26
|
+
counts.set(byte, (counts.get(byte) ?? 0) + 1);
|
|
27
|
+
}
|
|
28
|
+
let bits = 0;
|
|
29
|
+
for (const count of counts.values()) {
|
|
30
|
+
const p = count / bytes.length;
|
|
31
|
+
bits -= p * Math.log2(p);
|
|
32
|
+
}
|
|
33
|
+
return bits;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Heuristic: does this byte sequence look like a human-chosen password
|
|
37
|
+
* rather than a random key? Only printable-ASCII inputs are suspected -
|
|
38
|
+
* raw random bytes virtually never stay inside that range.
|
|
39
|
+
*/
|
|
40
|
+
export function isPasswordLike(bytes) {
|
|
41
|
+
const printableAscii = bytes.every((b) => b >= 0x20 && b <= 0x7e);
|
|
42
|
+
if (!printableAscii) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const text = String.fromCharCode(...bytes).toLowerCase();
|
|
46
|
+
if (COMMON_WORDS.some((word) => text.includes(word))) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
// Random keys encoded as hex/base64 sit well above 3.5 bits/byte at
|
|
50
|
+
// these lengths; keyboard-pattern and dictionary strings sit below.
|
|
51
|
+
return shannonBitsPerByte(bytes) < 3.5;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Validate an HMAC secret for the given HS* algorithm.
|
|
55
|
+
* Throws {@link EntropyCheckFailed} when the secret is too short or
|
|
56
|
+
* looks like a password.
|
|
57
|
+
*/
|
|
58
|
+
export function validateHMACSecret(secret, algorithmName) {
|
|
59
|
+
const { minKeyBits } = getAlgorithmProperties(algorithmName);
|
|
60
|
+
const minBytes = Math.ceil(minKeyBits / 8);
|
|
61
|
+
if (secret.length < minBytes) {
|
|
62
|
+
throw new EntropyCheckFailed(`HMAC secret for ${algorithmName} must be at least ${minBytes} bytes ` +
|
|
63
|
+
"of cryptographically random data");
|
|
64
|
+
}
|
|
65
|
+
if (isPasswordLike(secret)) {
|
|
66
|
+
throw new EntropyCheckFailed("HMAC secret looks like a human-chosen password; use " +
|
|
67
|
+
"cryptographically random bytes (e.g. generateSecret())");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=entropy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entropy.js","sourceRoot":"","sources":["../../../src/lib/entropy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,6DAA6D;AAC7D,MAAM,YAAY,GAAG;IACpB,UAAU;IACV,UAAU;IACV,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,SAAS;CACT,CAAC;AAEF,SAAS,kBAAkB,CAAC,KAAiB;IAC5C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;QAC/B,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,KAAiB;IAC/C,MAAM,cAAc,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;IAClE,IAAI,CAAC,cAAc,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACd,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IACzD,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACtD,OAAO,IAAI,CAAC;IACb,CAAC;IACD,oEAAoE;IACpE,oEAAoE;IACpE,OAAO,kBAAkB,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAkB,EAAE,aAAqB;IAC3E,MAAM,EAAE,UAAU,EAAE,GAAG,sBAAsB,CAAC,aAAa,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;IAC3C,IAAI,MAAM,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,kBAAkB,CAC3B,mBAAmB,aAAa,qBAAqB,QAAQ,SAAS;YACrE,kCAAkC,CACnC,CAAC;IACH,CAAC;IACD,IAAI,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,kBAAkB,CAC3B,sDAAsD;YACrD,wDAAwD,CACzD,CAAC;IACH,CAAC;AACF,CAAC"}
|