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.
Files changed (141) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +9 -0
  3. package/NOTICE.md +1 -0
  4. package/README.md +313 -0
  5. package/dist/index.js +40 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/src/cookbook/access-refresh.js +86 -0
  8. package/dist/src/cookbook/access-refresh.js.map +1 -0
  9. package/dist/src/http/bearer.js +44 -0
  10. package/dist/src/http/bearer.js.map +1 -0
  11. package/dist/src/http/cookies.js +96 -0
  12. package/dist/src/http/cookies.js.map +1 -0
  13. package/dist/src/jwks/local.js +85 -0
  14. package/dist/src/jwks/local.js.map +1 -0
  15. package/dist/src/jwks/remote.js +121 -0
  16. package/dist/src/jwks/remote.js.map +1 -0
  17. package/dist/src/jwt/decrypt.js +126 -0
  18. package/dist/src/jwt/decrypt.js.map +1 -0
  19. package/dist/src/jwt/decryption_profile.js +78 -0
  20. package/dist/src/jwt/decryption_profile.js.map +1 -0
  21. package/dist/src/jwt/encrypt.js +149 -0
  22. package/dist/src/jwt/encrypt.js.map +1 -0
  23. package/dist/src/jwt/profile.js +102 -0
  24. package/dist/src/jwt/profile.js.map +1 -0
  25. package/dist/src/jwt/sign.js +173 -0
  26. package/dist/src/jwt/sign.js.map +1 -0
  27. package/dist/src/jwt/verify.js +118 -0
  28. package/dist/src/jwt/verify.js.map +1 -0
  29. package/dist/src/key/encryption.js +132 -0
  30. package/dist/src/key/encryption.js.map +1 -0
  31. package/dist/src/key/export.js +46 -0
  32. package/dist/src/key/export.js.map +1 -0
  33. package/dist/src/key/generate.js +47 -0
  34. package/dist/src/key/generate.js.map +1 -0
  35. package/dist/src/key/import.js +136 -0
  36. package/dist/src/key/import.js.map +1 -0
  37. package/dist/src/legacy/rs256.js +11 -0
  38. package/dist/src/legacy/rs256.js.map +1 -0
  39. package/dist/src/legacy/rsa.js +37 -0
  40. package/dist/src/legacy/rsa.js.map +1 -0
  41. package/dist/src/lib/algorithms.js +60 -0
  42. package/dist/src/lib/algorithms.js.map +1 -0
  43. package/dist/src/lib/base64url.js +28 -0
  44. package/dist/src/lib/base64url.js.map +1 -0
  45. package/dist/src/lib/claims.js +111 -0
  46. package/dist/src/lib/claims.js.map +1 -0
  47. package/dist/src/lib/duration.js +24 -0
  48. package/dist/src/lib/duration.js.map +1 -0
  49. package/dist/src/lib/entropy.js +70 -0
  50. package/dist/src/lib/entropy.js.map +1 -0
  51. package/dist/src/lib/headers.js +75 -0
  52. package/dist/src/lib/headers.js.map +1 -0
  53. package/dist/src/lib/invariants.js +12 -0
  54. package/dist/src/lib/invariants.js.map +1 -0
  55. package/dist/src/lib/json.js +140 -0
  56. package/dist/src/lib/json.js.map +1 -0
  57. package/dist/src/lib/jwe_algorithms.js +81 -0
  58. package/dist/src/lib/jwe_algorithms.js.map +1 -0
  59. package/dist/src/lib/payload_hygiene.js +116 -0
  60. package/dist/src/lib/payload_hygiene.js.map +1 -0
  61. package/dist/src/lib/utf8.js +27 -0
  62. package/dist/src/lib/utf8.js.map +1 -0
  63. package/dist/src/revocation/memory.js +67 -0
  64. package/dist/src/revocation/memory.js.map +1 -0
  65. package/dist/src/revocation/store.js +25 -0
  66. package/dist/src/revocation/store.js.map +1 -0
  67. package/dist/src/types.js +51 -0
  68. package/dist/src/types.js.map +1 -0
  69. package/dist/src/util/errors.js +96 -0
  70. package/dist/src/util/errors.js.map +1 -0
  71. package/dist/src/util/unsafe-decode.js +45 -0
  72. package/dist/src/util/unsafe-decode.js.map +1 -0
  73. package/dist/types/index.d.ts +29 -0
  74. package/dist/types/index.d.ts.map +1 -0
  75. package/dist/types/src/cookbook/access-refresh.d.ts +77 -0
  76. package/dist/types/src/cookbook/access-refresh.d.ts.map +1 -0
  77. package/dist/types/src/http/bearer.d.ts +20 -0
  78. package/dist/types/src/http/bearer.d.ts.map +1 -0
  79. package/dist/types/src/http/cookies.d.ts +38 -0
  80. package/dist/types/src/http/cookies.d.ts.map +1 -0
  81. package/dist/types/src/jwks/local.d.ts +16 -0
  82. package/dist/types/src/jwks/local.d.ts.map +1 -0
  83. package/dist/types/src/jwks/remote.d.ts +23 -0
  84. package/dist/types/src/jwks/remote.d.ts.map +1 -0
  85. package/dist/types/src/jwt/decrypt.d.ts +23 -0
  86. package/dist/types/src/jwt/decrypt.d.ts.map +1 -0
  87. package/dist/types/src/jwt/decryption_profile.d.ts +32 -0
  88. package/dist/types/src/jwt/decryption_profile.d.ts.map +1 -0
  89. package/dist/types/src/jwt/encrypt.d.ts +34 -0
  90. package/dist/types/src/jwt/encrypt.d.ts.map +1 -0
  91. package/dist/types/src/jwt/profile.d.ts +37 -0
  92. package/dist/types/src/jwt/profile.d.ts.map +1 -0
  93. package/dist/types/src/jwt/sign.d.ts +59 -0
  94. package/dist/types/src/jwt/sign.d.ts.map +1 -0
  95. package/dist/types/src/jwt/verify.d.ts +41 -0
  96. package/dist/types/src/jwt/verify.d.ts.map +1 -0
  97. package/dist/types/src/key/encryption.d.ts +37 -0
  98. package/dist/types/src/key/encryption.d.ts.map +1 -0
  99. package/dist/types/src/key/export.d.ts +10 -0
  100. package/dist/types/src/key/export.d.ts.map +1 -0
  101. package/dist/types/src/key/generate.d.ts +27 -0
  102. package/dist/types/src/key/generate.d.ts.map +1 -0
  103. package/dist/types/src/key/import.d.ts +20 -0
  104. package/dist/types/src/key/import.d.ts.map +1 -0
  105. package/dist/types/src/legacy/rs256.d.ts +11 -0
  106. package/dist/types/src/legacy/rs256.d.ts.map +1 -0
  107. package/dist/types/src/legacy/rsa.d.ts +25 -0
  108. package/dist/types/src/legacy/rsa.d.ts.map +1 -0
  109. package/dist/types/src/lib/algorithms.d.ts +34 -0
  110. package/dist/types/src/lib/algorithms.d.ts.map +1 -0
  111. package/dist/types/src/lib/base64url.d.ts +8 -0
  112. package/dist/types/src/lib/base64url.d.ts.map +1 -0
  113. package/dist/types/src/lib/claims.d.ts +15 -0
  114. package/dist/types/src/lib/claims.d.ts.map +1 -0
  115. package/dist/types/src/lib/duration.d.ts +7 -0
  116. package/dist/types/src/lib/duration.d.ts.map +1 -0
  117. package/dist/types/src/lib/entropy.d.ts +21 -0
  118. package/dist/types/src/lib/entropy.d.ts.map +1 -0
  119. package/dist/types/src/lib/headers.d.ts +19 -0
  120. package/dist/types/src/lib/headers.d.ts.map +1 -0
  121. package/dist/types/src/lib/invariants.d.ts +6 -0
  122. package/dist/types/src/lib/invariants.d.ts.map +1 -0
  123. package/dist/types/src/lib/json.d.ts +24 -0
  124. package/dist/types/src/lib/json.d.ts.map +1 -0
  125. package/dist/types/src/lib/jwe_algorithms.d.ts +46 -0
  126. package/dist/types/src/lib/jwe_algorithms.d.ts.map +1 -0
  127. package/dist/types/src/lib/payload_hygiene.d.ts +20 -0
  128. package/dist/types/src/lib/payload_hygiene.d.ts.map +1 -0
  129. package/dist/types/src/lib/utf8.d.ts +7 -0
  130. package/dist/types/src/lib/utf8.d.ts.map +1 -0
  131. package/dist/types/src/revocation/memory.d.ts +22 -0
  132. package/dist/types/src/revocation/memory.d.ts.map +1 -0
  133. package/dist/types/src/revocation/store.d.ts +17 -0
  134. package/dist/types/src/revocation/store.d.ts.map +1 -0
  135. package/dist/types/src/types.d.ts +196 -0
  136. package/dist/types/src/types.d.ts.map +1 -0
  137. package/dist/types/src/util/errors.d.ts +89 -0
  138. package/dist/types/src/util/errors.d.ts.map +1 -0
  139. package/dist/types/src/util/unsafe-decode.d.ts +21 -0
  140. package/dist/types/src/util/unsafe-decode.d.ts.map +1 -0
  141. package/package.json +70 -0
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Header parsing and hardening (RFC 8725 §3.1/§3.2/§3.6/§3.10/§3.11).
3
+ *
4
+ * Header values are untrusted input: `kid` is length-capped and
5
+ * character-whitelisted before any lookup, `jku`/`x5u`/`jwk`/`x5c` are
6
+ * never honored, `zip` is rejected outright, and `crit` is rejected
7
+ * because Lacewing supports no header extensions.
8
+ */
9
+ import { AlgorithmNotAllowed, JWTClaimValidationFailed, JWTInvalid } from "../util/errors.js";
10
+ import { getAlgorithmProperties } from "./algorithms.js";
11
+ import { SetAlg } from "../types.js";
12
+ const KID_MAX_LENGTH = 128;
13
+ const KID_CHARSET = /^[A-Za-z0-9._:-]+$/;
14
+ // Never followed / never honored (§3.6, §3.10) plus `crit`, since we
15
+ // implement no extensions and RFC 7515 requires rejecting unknown ones.
16
+ const FORBIDDEN_PARAMS = ["jku", "x5u", "jwk", "x5c", "x5t", "zip", "crit"];
17
+ /**
18
+ * Sanitize an untrusted `kid` before it is used for key lookup (§3.10):
19
+ * bounded length, conservative charset.
20
+ */
21
+ export function sanitizeKid(kid) {
22
+ if (typeof kid !== "string" ||
23
+ kid.length === 0 ||
24
+ kid.length > KID_MAX_LENGTH ||
25
+ !KID_CHARSET.test(kid)) {
26
+ throw new JWTInvalid("Token key identifier failed sanitization");
27
+ }
28
+ return kid;
29
+ }
30
+ /** Compare `typ` values per RFC 9068: optional media-type prefix, case-insensitive. */
31
+ export function normalizeTyp(typ) {
32
+ const lower = typ.toLowerCase();
33
+ return lower.startsWith("application/") ? lower.slice("application/".length) : lower;
34
+ }
35
+ /** Validate a decoded (but unverified) header object against a profile. */
36
+ export function validateHeader(raw, profile) {
37
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
38
+ throw new JWTInvalid("Token header is not a JSON object");
39
+ }
40
+ const header = raw;
41
+ for (const param of FORBIDDEN_PARAMS) {
42
+ if (param in header) {
43
+ throw new JWTInvalid(`Token header parameter "${param}" is not supported`);
44
+ }
45
+ }
46
+ const alg = header.alg;
47
+ if (typeof alg !== "string" || alg.toLowerCase() === "none") {
48
+ throw new AlgorithmNotAllowed("Algorithm 'none' (or a missing alg) is not allowed");
49
+ }
50
+ if (!profile.alg.some((allowed) => allowed === alg)) {
51
+ throw new AlgorithmNotAllowed("Token algorithm is not in this profile's allowlist");
52
+ }
53
+ // Defense in depth: the allowlist is registry-validated at profile
54
+ // creation, but re-check in case the profile object was hand-built.
55
+ getAlgorithmProperties(alg);
56
+ const typ = header.typ;
57
+ if (typeof typ !== "string" || typ.length === 0) {
58
+ throw new JWTInvalid("Token header is missing a typ (explicit typing is mandatory)");
59
+ }
60
+ if (normalizeTyp(typ) !== normalizeTyp(profile.typ)) {
61
+ throw new JWTClaimValidationFailed("typ", "Unexpected token type for this profile");
62
+ }
63
+ const result = { alg: SetAlg(alg), typ };
64
+ if (header.kid !== undefined) {
65
+ result.kid = sanitizeKid(header.kid);
66
+ }
67
+ if (header.cty !== undefined) {
68
+ if (typeof header.cty !== "string") {
69
+ throw new JWTInvalid("Token header cty must be a string");
70
+ }
71
+ result.cty = header.cty;
72
+ }
73
+ return result;
74
+ }
75
+ //# sourceMappingURL=headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../../../src/lib/headers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /** Internal assertion helpers. */
2
+ /** Throw if `condition` is false. For states that must be impossible. */
3
+ export function invariant(condition, message) {
4
+ if (!condition) {
5
+ throw new Error(`Invariant violation: ${message}`);
6
+ }
7
+ }
8
+ /** Exhaustiveness check for switch statements. */
9
+ export function assertNever(value) {
10
+ throw new Error(`Unexpected value: ${String(value)}`);
11
+ }
12
+ //# sourceMappingURL=invariants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invariants.js","sourceRoot":"","sources":["../../../src/lib/invariants.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAElC,yEAAyE;AACzE,MAAM,UAAU,SAAS,CAAC,SAAkB,EAAE,OAAe;IAC5D,IAAI,CAAC,SAAS,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,wBAAwB,OAAO,EAAE,CAAC,CAAC;IACpD,CAAC;AACF,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,WAAW,CAAC,KAAY;IACvC,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Strict JSON object parsing for token segments (RFC 8725 §3.7 strictness).
3
+ *
4
+ * `JSON.parse` silently keeps the *last* value when a member name is
5
+ * duplicated. That is a parser differential: an attacker crafts a header or
6
+ * payload where Lacewing and another consumer disagree about, say, which
7
+ * `alg` or `aud` is authoritative. RFC 8259 §4 says names SHOULD be unique
8
+ * and that behavior is otherwise unpredictable - Lacewing treats a duplicate
9
+ * key at any nesting level as malformed and rejects the token.
10
+ *
11
+ * The scan runs only after `JSON.parse` has confirmed the text is
12
+ * well-formed, so it can assume valid JSON and stay small.
13
+ */
14
+ import { JWTInvalid } from "../util/errors.js";
15
+ function readString(text, start) {
16
+ // text[start] === '"'
17
+ let i = start + 1;
18
+ let out = "";
19
+ const n = text.length;
20
+ while (i < n) {
21
+ const c = text[i];
22
+ if (c === "\"") {
23
+ return { value: out, next: i + 1 };
24
+ }
25
+ if (c === "\\") {
26
+ const e = text[i + 1];
27
+ switch (e) {
28
+ case "\"":
29
+ out += "\"";
30
+ break;
31
+ case "\\":
32
+ out += "\\";
33
+ break;
34
+ case "/":
35
+ out += "/";
36
+ break;
37
+ case "b":
38
+ out += "\b";
39
+ break;
40
+ case "f":
41
+ out += "\f";
42
+ break;
43
+ case "n":
44
+ out += "\n";
45
+ break;
46
+ case "r":
47
+ out += "\r";
48
+ break;
49
+ case "t":
50
+ out += "\t";
51
+ break;
52
+ case "u":
53
+ out += String.fromCharCode(parseInt(text.slice(i + 2, i + 6), 16));
54
+ i += 4;
55
+ break;
56
+ default:
57
+ out += e;
58
+ }
59
+ i += 2;
60
+ continue;
61
+ }
62
+ out += c;
63
+ i++;
64
+ }
65
+ // Unreachable for JSON that already parsed.
66
+ return { value: out, next: i };
67
+ }
68
+ /**
69
+ * Throw {@link JWTInvalid} if `text` (already known to be valid JSON) contains
70
+ * a duplicated member name in any object at any depth.
71
+ */
72
+ export function assertNoDuplicateKeys(text, what) {
73
+ const stack = [];
74
+ let i = 0;
75
+ const n = text.length;
76
+ while (i < n) {
77
+ const c = text[i];
78
+ if (c === "{") {
79
+ stack.push({ seen: new Set(), expectingKey: true });
80
+ i++;
81
+ continue;
82
+ }
83
+ if (c === "[") {
84
+ stack.push("array");
85
+ i++;
86
+ continue;
87
+ }
88
+ if (c === "}" || c === "]") {
89
+ stack.pop();
90
+ i++;
91
+ continue;
92
+ }
93
+ if (c === "\"") {
94
+ const top = stack[stack.length - 1];
95
+ if (top !== undefined && top !== "array" && top.expectingKey) {
96
+ const { value, next } = readString(text, i);
97
+ if (top.seen.has(value)) {
98
+ throw new JWTInvalid(`Token ${what} contains a duplicate JSON key`);
99
+ }
100
+ top.seen.add(value);
101
+ top.expectingKey = false;
102
+ i = next;
103
+ }
104
+ else {
105
+ i = readString(text, i).next;
106
+ }
107
+ continue;
108
+ }
109
+ if (c === ",") {
110
+ const top = stack[stack.length - 1];
111
+ if (top !== undefined && top !== "array") {
112
+ top.expectingKey = true;
113
+ }
114
+ i++;
115
+ continue;
116
+ }
117
+ i++;
118
+ }
119
+ }
120
+ /**
121
+ * Decode a token segment's UTF-8 text into a JSON object, rejecting anything
122
+ * that is not a plain object and any duplicate member names.
123
+ */
124
+ export function parseJsonObject(text, what) {
125
+ let parsed;
126
+ try {
127
+ parsed = JSON.parse(text);
128
+ }
129
+ catch (cause) {
130
+ if (cause instanceof JWTInvalid)
131
+ throw cause;
132
+ throw new JWTInvalid(`Token ${what} is not valid JSON`, { cause });
133
+ }
134
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
135
+ throw new JWTInvalid(`Token ${what} is not a JSON object`);
136
+ }
137
+ assertNoDuplicateKeys(text, what);
138
+ return parsed;
139
+ }
140
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../../src/lib/json.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The JWE algorithm registry (encrypted JWTs). Two axes, both curated:
3
+ *
4
+ * - key management (`alg`): how the content-encryption key is agreed/wrapped.
5
+ * - content encryption (`enc`): the AEAD that actually encrypts the payload.
6
+ *
7
+ * Deliberately absent, exactly as on the JWS side: `none`, `RSA1_5`
8
+ * (Bleichenbacher - CVE-2023-... class), `RSA-OAEP` with SHA-1, and the `PBES2*`
9
+ * password-based algorithms (Lacewing does not do human-memorable secrets).
10
+ * As with JWS, the token header never chooses - the decryption profile does.
11
+ *
12
+ * Portability note: the `A192*` entries work on Node >= 24 (the engine floor)
13
+ * but 192-bit AES is not implemented by browser WebCrypto and some edge
14
+ * runtimes. Documented in the README; prefer `A128*`/`A256*` for tokens that
15
+ * must be decrypted outside Node.
16
+ */
17
+ import { AlgorithmNotAllowed } from "../util/errors.js";
18
+ import { SetAlg } from "../types.js";
19
+ const KEY_MANAGEMENT = [
20
+ { name: "dir", kind: "dir", kty: "oct" },
21
+ { name: "A128KW", kind: "aeskw", kty: "oct", secretBytes: 16 },
22
+ { name: "A192KW", kind: "aeskw", kty: "oct", secretBytes: 24 },
23
+ { name: "A256KW", kind: "aeskw", kty: "oct", secretBytes: 32 },
24
+ { name: "A128GCMKW", kind: "aeskw", kty: "oct", secretBytes: 16 },
25
+ { name: "A192GCMKW", kind: "aeskw", kty: "oct", secretBytes: 24 },
26
+ { name: "A256GCMKW", kind: "aeskw", kty: "oct", secretBytes: 32 },
27
+ { name: "ECDH-ES", kind: "ecdh", kty: "EC" },
28
+ { name: "ECDH-ES+A128KW", kind: "ecdh", kty: "EC" },
29
+ { name: "ECDH-ES+A192KW", kind: "ecdh", kty: "EC" },
30
+ { name: "ECDH-ES+A256KW", kind: "ecdh", kty: "EC" },
31
+ { name: "RSA-OAEP-256", kind: "rsa", kty: "RSA", minKeyBits: 2048 },
32
+ ];
33
+ const CONTENT_ENCRYPTION = [
34
+ { name: "A128GCM", cekBytes: 16 },
35
+ { name: "A192GCM", cekBytes: 24 },
36
+ { name: "A256GCM", cekBytes: 32 },
37
+ { name: "A128CBC-HS256", cekBytes: 32 },
38
+ { name: "A192CBC-HS384", cekBytes: 48 },
39
+ { name: "A256CBC-HS512", cekBytes: 64 },
40
+ ];
41
+ const keyManagement = new Map(KEY_MANAGEMENT.map((info) => [info.name, info]));
42
+ const contentEncryption = new Map(CONTENT_ENCRYPTION.map((info) => [info.name, info]));
43
+ export function isValidKeyManagementAlgorithm(alg) {
44
+ return keyManagement.has(alg);
45
+ }
46
+ export function isValidContentEncryptionAlgorithm(enc) {
47
+ return contentEncryption.has(enc);
48
+ }
49
+ /** Look up a key-management algorithm, or throw {@link AlgorithmNotAllowed}. */
50
+ export function getKeyManagementProperties(alg) {
51
+ const info = keyManagement.get(alg);
52
+ if (info === undefined) {
53
+ throw new AlgorithmNotAllowed("Key-management algorithm is not in the Lacewing allowlist");
54
+ }
55
+ return info;
56
+ }
57
+ /** Look up a content-encryption algorithm, or throw {@link AlgorithmNotAllowed}. */
58
+ export function getContentEncryptionProperties(enc) {
59
+ const info = contentEncryption.get(enc);
60
+ if (info === undefined) {
61
+ throw new AlgorithmNotAllowed("Content-encryption algorithm is not in the Lacewing allowlist");
62
+ }
63
+ return info;
64
+ }
65
+ /** Validate against the registry and brand the key-management alg. */
66
+ export function toValidKeyManagementAlg(alg) {
67
+ getKeyManagementProperties(alg);
68
+ return SetAlg(alg);
69
+ }
70
+ /** Validate against the registry and brand the content-encryption alg. */
71
+ export function toValidContentEncryptionAlg(enc) {
72
+ getContentEncryptionProperties(enc);
73
+ return SetAlg(enc);
74
+ }
75
+ export function listKeyManagementAlgorithms() {
76
+ return [...keyManagement.keys()];
77
+ }
78
+ export function listContentEncryptionAlgorithms() {
79
+ return [...contentEncryption.keys()];
80
+ }
81
+ //# sourceMappingURL=jwe_algorithms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwe_algorithms.js","sourceRoot":"","sources":["../../../src/lib/jwe_algorithms.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Sign-time payload hygiene scanner (LW-payload).
3
+ *
4
+ * JWS payloads are base64url-encoded **plaintext** - encoding, not
5
+ * encryption. Two tiers, applied recursively so nesting offers no blind
6
+ * spots:
7
+ * - claim *names* matching a deny-heuristic (`password`, `secret`, ...)
8
+ * - claim *values* matching high-confidence secret patterns (Luhn-valid
9
+ * card numbers, PEM private-key blocks, other JWTs)
10
+ *
11
+ * False positives are waived per top-level claim via the grep-loud
12
+ * `SignJWT.unsafeAllowClaim(name)`.
13
+ */
14
+ import { PayloadHygieneViolation } from "../util/errors.js";
15
+ const SENSITIVE_NAME_FRAGMENTS = [
16
+ "password",
17
+ "passwd",
18
+ "passphrase",
19
+ "secret",
20
+ "apikey",
21
+ "privatekey",
22
+ "ssn",
23
+ "socialsecurity",
24
+ "creditcard",
25
+ "cardnumber",
26
+ "cvv",
27
+ "cvc",
28
+ "token",
29
+ "authorization",
30
+ "bearer",
31
+ ];
32
+ const PEM_PRIVATE_KEY = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/;
33
+ const JWT_LIKE = /^[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}$/;
34
+ function normalizeName(name) {
35
+ return name.toLowerCase().replace(/[^a-z0-9]/g, "");
36
+ }
37
+ function sensitiveNameFragment(name) {
38
+ const normalized = normalizeName(name);
39
+ return SENSITIVE_NAME_FRAGMENTS.find((fragment) => normalized.includes(fragment));
40
+ }
41
+ function isLuhnValidCardNumber(value) {
42
+ const digits = value.replace(/[\s-]/g, "");
43
+ if (!/^\d{13,19}$/.test(digits)) {
44
+ return false;
45
+ }
46
+ let sum = 0;
47
+ let double = false;
48
+ for (let i = digits.length - 1; i >= 0; i--) {
49
+ let digit = digits.charCodeAt(i) - 48;
50
+ if (double) {
51
+ digit *= 2;
52
+ if (digit > 9)
53
+ digit -= 9;
54
+ }
55
+ sum += digit;
56
+ double = !double;
57
+ }
58
+ return sum % 10 === 0;
59
+ }
60
+ function checkValue(topLevelClaim, value) {
61
+ if (PEM_PRIVATE_KEY.test(value)) {
62
+ throw new PayloadHygieneViolation(topLevelClaim, `Claim "${topLevelClaim}" contains a PEM private-key block. ` +
63
+ "JWS payloads are readable plaintext - never embed key material.");
64
+ }
65
+ if (JWT_LIKE.test(value)) {
66
+ throw new PayloadHygieneViolation(topLevelClaim, `Claim "${topLevelClaim}" contains what looks like another JWT. ` +
67
+ "Embedding tokens in tokens leaks credentials to anyone who can read the payload.");
68
+ }
69
+ if (isLuhnValidCardNumber(value)) {
70
+ throw new PayloadHygieneViolation(topLevelClaim, `Claim "${topLevelClaim}" contains what looks like a payment card number. ` +
71
+ "JWS payloads are readable plaintext - never embed cardholder data.");
72
+ }
73
+ }
74
+ function scanNode(topLevelClaim, value) {
75
+ if (typeof value === "string") {
76
+ checkValue(topLevelClaim, value);
77
+ return;
78
+ }
79
+ if (Array.isArray(value)) {
80
+ for (const entry of value) {
81
+ scanNode(topLevelClaim, entry);
82
+ }
83
+ return;
84
+ }
85
+ if (typeof value === "object" && value !== null) {
86
+ for (const [name, nested] of Object.entries(value)) {
87
+ const fragment = sensitiveNameFragment(name);
88
+ if (fragment !== undefined) {
89
+ throw new PayloadHygieneViolation(topLevelClaim, `Claim "${topLevelClaim}" contains a nested field whose name matches "${fragment}". ` +
90
+ "JWS payloads are readable plaintext - do not put secrets in tokens. " +
91
+ "If this is a false positive, waive it with unsafeAllowClaim().");
92
+ }
93
+ scanNode(topLevelClaim, nested);
94
+ }
95
+ }
96
+ }
97
+ /**
98
+ * Scan a payload about to be signed. Throws {@link PayloadHygieneViolation}
99
+ * naming the offending claim (never echoing its value). Claims listed in
100
+ * `allowedClaims` are skipped entirely, subtree included.
101
+ */
102
+ export function scanPayloadForSensitiveData(payload, allowedClaims = new Set()) {
103
+ for (const [claim, value] of Object.entries(payload)) {
104
+ if (allowedClaims.has(claim)) {
105
+ continue;
106
+ }
107
+ const fragment = sensitiveNameFragment(claim);
108
+ if (fragment !== undefined) {
109
+ throw new PayloadHygieneViolation(claim, `Claim name "${claim}" matches the sensitive-name heuristic ("${fragment}"). ` +
110
+ "JWS payloads are readable plaintext - do not put secrets in tokens. " +
111
+ "If this is a false positive, waive it with unsafeAllowClaim().");
112
+ }
113
+ scanNode(claim, value);
114
+ }
115
+ }
116
+ //# sourceMappingURL=payload_hygiene.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload_hygiene.js","sourceRoot":"","sources":["../../../src/lib/payload_hygiene.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Strict UTF-8 encode/decode (RFC 8725 §3.7). Malformed sequences reject
3
+ * the token - no replacement characters, no silently stripped BOM.
4
+ */
5
+ import { JWTInvalid } from "../util/errors.js";
6
+ const encoder = new TextEncoder();
7
+ // fatal: rejects malformed/overlong sequences. ignoreBOM: a leading BOM is
8
+ // preserved as U+FEFF so downstream JSON parsing rejects it (BOM tricks).
9
+ const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
10
+ // Matches unpaired surrogate halves, which TextEncoder would otherwise
11
+ // silently replace with U+FFFD.
12
+ const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
13
+ export function encodeUTF8(text) {
14
+ if (LONE_SURROGATE.test(text)) {
15
+ throw new JWTInvalid("Text contains unpaired surrogates");
16
+ }
17
+ return encoder.encode(text);
18
+ }
19
+ export function decodeUTF8(bytes) {
20
+ try {
21
+ return decoder.decode(bytes);
22
+ }
23
+ catch (cause) {
24
+ throw new JWTInvalid("Malformed UTF-8", { cause });
25
+ }
26
+ }
27
+ //# sourceMappingURL=utf8.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utf8.js","sourceRoot":"","sources":["../../../src/lib/utf8.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAClC,2EAA2E;AAC3E,0EAA0E;AAC1E,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAE3E,uEAAuE;AACvE,gCAAgC;AAChC,MAAM,cAAc,GACnB,wEAAwE,CAAC;AAE1E,MAAM,UAAU,UAAU,CAAC,IAAY;IACtC,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,UAAU,CAAC,mCAAmC,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAiB;IAC3C,IAAI,CAAC;QACJ,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,UAAU,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACpD,CAAC;AACF,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * In-memory {@link RevocationStore}.
3
+ *
4
+ * Entries evict once the revoked token's `exp` passes: a revocation only
5
+ * needs to outlive the token it revokes. Suitable for single-process
6
+ * deployments and tests; multi-instance services want a shared backend
7
+ * (Redis/DB) implementing the same interface.
8
+ */
9
+ const SWEEP_INTERVAL_OPS = 256;
10
+ export class MemoryRevocationStore {
11
+ /** jti -> token expiry (unix seconds). */
12
+ #revoked = new Map();
13
+ #opsSinceSweep = 0;
14
+ /**
15
+ * Revoke a token by its `jti`. `expiresAt` is the token's `exp`
16
+ * (unix seconds or Date) - the entry is dropped after that moment.
17
+ */
18
+ revoke(jti, expiresAt) {
19
+ if (typeof jti !== "string" || jti.length === 0) {
20
+ throw new TypeError("revoke() requires the token's jti");
21
+ }
22
+ const expSeconds = expiresAt instanceof Date ? Math.ceil(expiresAt.getTime() / 1000) : expiresAt;
23
+ if (!Number.isFinite(expSeconds)) {
24
+ throw new TypeError("revoke() requires the token's expiry");
25
+ }
26
+ this.#revoked.set(jti, expSeconds);
27
+ this.#maybeSweep();
28
+ }
29
+ async isRevoked(context) {
30
+ this.#maybeSweep();
31
+ if (context.jti === undefined) {
32
+ return false;
33
+ }
34
+ const exp = this.#revoked.get(context.jti);
35
+ if (exp === undefined) {
36
+ return false;
37
+ }
38
+ if (exp <= this.#nowSeconds()) {
39
+ this.#revoked.delete(context.jti);
40
+ return false;
41
+ }
42
+ return true;
43
+ }
44
+ async isAnyRevoked(contexts) {
45
+ return Promise.all(contexts.map((context) => this.isRevoked(context)));
46
+ }
47
+ /** Number of live revocations (expired entries may linger until a sweep). */
48
+ get size() {
49
+ return this.#revoked.size;
50
+ }
51
+ #nowSeconds() {
52
+ return Math.floor(Date.now() / 1000);
53
+ }
54
+ #maybeSweep() {
55
+ if (++this.#opsSinceSweep < SWEEP_INTERVAL_OPS) {
56
+ return;
57
+ }
58
+ this.#opsSinceSweep = 0;
59
+ const now = this.#nowSeconds();
60
+ for (const [jti, exp] of this.#revoked) {
61
+ if (exp <= now) {
62
+ this.#revoked.delete(jti);
63
+ }
64
+ }
65
+ }
66
+ }
67
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/revocation/memory.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,MAAM,OAAO,qBAAqB;IACjC,0CAA0C;IACjC,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC9C,cAAc,GAAG,CAAC,CAAC;IAEnB;;;OAGG;IACH,MAAM,CAAC,GAAW,EAAE,SAAwB;QAC3C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,SAAS,CAAC,mCAAmC,CAAC,CAAC;QAC1D,CAAC;QACD,MAAM,UAAU,GACf,SAAS,YAAY,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC/E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,SAAS,CAAC,sCAAsC,CAAC,CAAC;QAC7D,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QACnC,IAAI,CAAC,WAAW,EAAE,CAAC;IACpB,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,OAA+B;QAC9C,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;YAC/B,OAAO,KAAK,CAAC;QACd,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,KAAK,CAAC;QACd,CAAC;QACD,IAAI,GAAG,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YAC/B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAClC,OAAO,KAAK,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,QAAkC;QACpD,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,6EAA6E;IAC7E,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC3B,CAAC;IAED,WAAW;QACV,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,WAAW;QACV,IAAI,EAAE,IAAI,CAAC,cAAc,GAAG,kBAAkB,EAAE,CAAC;YAChD,OAAO;QACR,CAAC;QACD,IAAI,CAAC,cAAc,GAAG,CAAC,CAAC;QACxB,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAC/B,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACxC,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC;gBAChB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC3B,CAAC;QACF,CAAC;IACF,CAAC;CACD"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Revocation contract.
3
+ *
4
+ * A {@link RevocationStore} answers "has this token been revoked?" and is
5
+ * consulted by `jwtVerify` *only after* signature and claims checks pass
6
+ * (LW-rev.3), so unauthenticated input can never drive store lookups.
7
+ * Store errors fail closed (LW-rev.4).
8
+ *
9
+ * The interface is shaped for async backends (Redis, a database): see
10
+ * `MemoryRevocationStore` for the reference implementation and the
11
+ * eviction contract - a revocation only needs to outlive its token.
12
+ */
13
+ /** Extract the revocation-relevant slice of a verified payload. */
14
+ export function buildRevocationContext(payload) {
15
+ const { jti, sub, sid, exp, iat } = payload;
16
+ const context = { exp, iat };
17
+ if (typeof jti === "string")
18
+ context.jti = jti;
19
+ if (typeof sub === "string")
20
+ context.sub = sub;
21
+ if (typeof sid === "string")
22
+ context.sid = sid;
23
+ return context;
24
+ }
25
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/revocation/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,mEAAmE;AACnE,MAAM,UAAU,sBAAsB,CACrC,OAAgC;IAEhC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,OAAyC,CAAC;IAC9E,MAAM,OAAO,GAA2B,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACrD,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC;IAC/C,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC;IAC/C,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC;IAC/C,OAAO,OAAO,CAAC;AAChB,CAAC"}
@@ -0,0 +1,51 @@
1
+ /*
2
+ * Portions of this code are copied or adapted from the 'jose' library.
3
+ * Copyright (c) Filip Skokan (panva)
4
+ * Licensed under the MIT License.
5
+ */
6
+ /**
7
+ * Brand a string as a {@link ValidAlg}. Rejects `"none"` in any casing.
8
+ * Registry membership is enforced separately by `lib/algorithms.ts`;
9
+ * this brand only guarantees the value is not the `none` algorithm.
10
+ */
11
+ export const SetAlg = (value) => {
12
+ if (value.toLowerCase() === "none") {
13
+ throw new Error("Algorithm 'none' is not allowed");
14
+ }
15
+ return value;
16
+ };
17
+ export function toSeconds(value) {
18
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
19
+ throw new TypeError("Duration must be a non-negative integer number of seconds");
20
+ }
21
+ return value;
22
+ }
23
+ /** Internal constructor - use `importKey` / `generateKeyPair` instead. */
24
+ export function createLacewingKey(algorithm, keyType, key) {
25
+ return Object.freeze({
26
+ __brand: "LacewingKey",
27
+ algorithm,
28
+ keyType,
29
+ key,
30
+ });
31
+ }
32
+ export function isLacewingKey(value) {
33
+ return (typeof value === "object" &&
34
+ value !== null &&
35
+ value.__brand === "LacewingKey");
36
+ }
37
+ /** Internal constructor - use `importEncryptionKey` / `generateEncryptionKeyPair`. */
38
+ export function createLacewingEncryptionKey(algorithm, keyType, key) {
39
+ return Object.freeze({
40
+ __brand: "LacewingEncryptionKey",
41
+ algorithm,
42
+ keyType,
43
+ key,
44
+ });
45
+ }
46
+ export function isLacewingEncryptionKey(value) {
47
+ return (typeof value === "object" &&
48
+ value !== null &&
49
+ value.__brand === "LacewingEncryptionKey");
50
+ }
51
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,KAAa,EAAY,EAAE;IACjD,IAAI,KAAK,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,KAAiB,CAAC;AAC1B,CAAC,CAAC;AAQF,MAAM,UAAU,SAAS,CAAC,KAAa;IACtC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,SAAS,CAAC,2DAA2D,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,KAAwB,CAAC;AACjC,CAAC;AAiDD,0EAA0E;AAC1E,MAAM,UAAU,iBAAiB,CAChC,SAAyB,EACzB,OAAwC,EACxC,GAAY;IAEZ,OAAO,MAAM,CAAC,MAAM,CAAC;QACpB,OAAO,EAAE,aAAa;QACtB,SAAS;QACT,OAAO;QACP,GAAG;KACH,CAAqB,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAc;IAC3C,OAAO,CACN,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACb,KAA+B,CAAC,OAAO,KAAK,aAAa,CAC1D,CAAC;AACH,CAAC;AAqFD,sFAAsF;AACtF,MAAM,UAAU,2BAA2B,CAC1C,SAAyB,EACzB,OAAwC,EACxC,GAAY;IAEZ,OAAO,MAAM,CAAC,MAAM,CAAC;QACpB,OAAO,EAAE,uBAAuB;QAChC,SAAS;QACT,OAAO;QACP,GAAG;KACH,CAA+B,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACrD,OAAO,CACN,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACb,KAA+B,CAAC,OAAO,KAAK,uBAAuB,CACpE,CAAC;AACH,CAAC"}