@c9up/warden 0.1.4 → 0.1.6

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 (88) hide show
  1. package/README.md +58 -0
  2. package/dist/Guard.d.ts +9 -0
  3. package/dist/Guard.d.ts.map +1 -1
  4. package/dist/Guard.js +16 -0
  5. package/dist/Guard.js.map +1 -1
  6. package/dist/RedisBlacklistDriver.d.ts +32 -0
  7. package/dist/RedisBlacklistDriver.d.ts.map +1 -0
  8. package/dist/RedisBlacklistDriver.js +37 -0
  9. package/dist/RedisBlacklistDriver.js.map +1 -0
  10. package/dist/ResilientBlacklistDriver.d.ts +35 -0
  11. package/dist/ResilientBlacklistDriver.d.ts.map +1 -0
  12. package/dist/ResilientBlacklistDriver.js +61 -0
  13. package/dist/ResilientBlacklistDriver.js.map +1 -0
  14. package/dist/TokenBlacklist.d.ts +6 -2
  15. package/dist/TokenBlacklist.d.ts.map +1 -1
  16. package/dist/TokenBlacklist.js +2 -2
  17. package/dist/TokenBlacklist.js.map +1 -1
  18. package/dist/WardenProvider.d.ts.map +1 -1
  19. package/dist/WardenProvider.js +47 -6
  20. package/dist/WardenProvider.js.map +1 -1
  21. package/dist/config.d.ts +65 -1
  22. package/dist/config.d.ts.map +1 -1
  23. package/dist/config.js.map +1 -1
  24. package/dist/index.d.ts +15 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +8 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/mfa/BackupCodesProvider.d.ts +39 -0
  29. package/dist/mfa/BackupCodesProvider.d.ts.map +1 -0
  30. package/dist/mfa/BackupCodesProvider.js +87 -0
  31. package/dist/mfa/BackupCodesProvider.js.map +1 -0
  32. package/dist/mfa/MfaManager.d.ts +107 -0
  33. package/dist/mfa/MfaManager.d.ts.map +1 -0
  34. package/dist/mfa/MfaManager.js +219 -0
  35. package/dist/mfa/MfaManager.js.map +1 -0
  36. package/dist/mfa/OtpProvider.d.ts +67 -0
  37. package/dist/mfa/OtpProvider.d.ts.map +1 -0
  38. package/dist/mfa/OtpProvider.js +104 -0
  39. package/dist/mfa/OtpProvider.js.map +1 -0
  40. package/dist/mfa/TotpProvider.d.ts +52 -0
  41. package/dist/mfa/TotpProvider.d.ts.map +1 -0
  42. package/dist/mfa/TotpProvider.js +103 -0
  43. package/dist/mfa/TotpProvider.js.map +1 -0
  44. package/dist/mfa/WebauthnProvider.d.ts +175 -0
  45. package/dist/mfa/WebauthnProvider.d.ts.map +1 -0
  46. package/dist/mfa/WebauthnProvider.js +239 -0
  47. package/dist/mfa/WebauthnProvider.js.map +1 -0
  48. package/dist/mfa/base32.d.ts +13 -0
  49. package/dist/mfa/base32.d.ts.map +1 -0
  50. package/dist/mfa/base32.js +52 -0
  51. package/dist/mfa/base32.js.map +1 -0
  52. package/dist/mfa/webauthn-codec.d.ts +58 -0
  53. package/dist/mfa/webauthn-codec.d.ts.map +1 -0
  54. package/dist/mfa/webauthn-codec.js +221 -0
  55. package/dist/mfa/webauthn-codec.js.map +1 -0
  56. package/dist/middleware.d.ts +31 -0
  57. package/dist/middleware.d.ts.map +1 -1
  58. package/dist/middleware.js +77 -4
  59. package/dist/middleware.js.map +1 -1
  60. package/dist/standalone.d.ts.map +1 -1
  61. package/dist/standalone.js +12 -0
  62. package/dist/standalone.js.map +1 -1
  63. package/dist/strategies/ApiKeyStrategy.d.ts.map +1 -1
  64. package/dist/strategies/ApiKeyStrategy.js +14 -1
  65. package/dist/strategies/ApiKeyStrategy.js.map +1 -1
  66. package/index.darwin-arm64.node +0 -0
  67. package/index.darwin-x64.node +0 -0
  68. package/index.linux-arm64-gnu.node +0 -0
  69. package/index.linux-x64-gnu.node +0 -0
  70. package/index.win32-x64-msvc.node +0 -0
  71. package/package.json +2 -2
  72. package/src/Guard.ts +21 -0
  73. package/src/RedisBlacklistDriver.ts +60 -0
  74. package/src/ResilientBlacklistDriver.ts +84 -0
  75. package/src/TokenBlacklist.ts +7 -3
  76. package/src/WardenProvider.ts +53 -7
  77. package/src/config.ts +60 -1
  78. package/src/index.ts +58 -0
  79. package/src/mfa/BackupCodesProvider.ts +125 -0
  80. package/src/mfa/MfaManager.ts +307 -0
  81. package/src/mfa/OtpProvider.ts +177 -0
  82. package/src/mfa/TotpProvider.ts +140 -0
  83. package/src/mfa/WebauthnProvider.ts +416 -0
  84. package/src/mfa/base32.ts +54 -0
  85. package/src/mfa/webauthn-codec.ts +264 -0
  86. package/src/middleware.ts +111 -2
  87. package/src/standalone.ts +14 -1
  88. package/src/strategies/ApiKeyStrategy.ts +14 -1
@@ -0,0 +1,239 @@
1
+ /**
2
+ * WebauthnProvider — passkeys / FIDO2 via WebAuthn, implemented in-house on
3
+ * `node:crypto` (see `webauthn-codec.ts`). No third-party dependency: warden
4
+ * keeps its zero-runtime-dependency footprint.
5
+ *
6
+ * The browser side produces the standard `PublicKeyCredential` JSON; this
7
+ * provider builds the ceremony options and verifies the responses server-side.
8
+ *
9
+ * Attestation statements are NOT verified — this is a deliberate
10
+ * trust-on-registration posture (request `attestation: "none"` on the client),
11
+ * which is what the vast majority of relying parties use for 2FA / passkeys.
12
+ * Full attestation (packed/tpm/apple/MDS cert-chain validation) can be layered
13
+ * on later without changing this API.
14
+ *
15
+ * Both the per-ceremony challenge and the persisted passkeys go through
16
+ * pluggable stores (in-memory defaults), mirroring the rest of warden.
17
+ */
18
+ import { randomBytes } from "node:crypto";
19
+ import { WardenError } from "../errors.js";
20
+ import { base64urlToBuffer, bufferToBase64url, coseToKeyObject, decodeCbor, parseAuthenticatorData, sha256, verifyWebauthnSignature, } from "./webauthn-codec.js";
21
+ export class MemoryWebauthnChallengeStore {
22
+ #store = new Map();
23
+ async save(state, challenge) {
24
+ this.#store.set(state, challenge);
25
+ }
26
+ async take(state) {
27
+ const c = this.#store.get(state) ?? null;
28
+ this.#store.delete(state);
29
+ return c;
30
+ }
31
+ }
32
+ export class MemoryWebauthnCredentialStore {
33
+ #store = new Map();
34
+ async save(passkey) {
35
+ this.#store.set(passkey.id, passkey);
36
+ }
37
+ async findById(id) {
38
+ return this.#store.get(id) ?? null;
39
+ }
40
+ async findByUser(userId) {
41
+ return [...this.#store.values()].filter((p) => p.userId === userId);
42
+ }
43
+ async updateCounter(id, counter) {
44
+ const p = this.#store.get(id);
45
+ if (p) {
46
+ this.#store.set(id, { ...p, counter });
47
+ }
48
+ }
49
+ }
50
+ const DEFAULT_ALGS = [-7, -257, -8];
51
+ export class WebauthnProvider {
52
+ kind = "webauthn";
53
+ #rpName;
54
+ #rpID;
55
+ #origins;
56
+ #timeout;
57
+ #algorithms;
58
+ #challenges;
59
+ #credentials;
60
+ constructor(config) {
61
+ if (!config?.rpID || !config?.rpName || !config?.origin) {
62
+ throw new WardenError("INVALID_CONFIG", "WebauthnProvider requires rpName, rpID and origin");
63
+ }
64
+ this.#rpName = config.rpName;
65
+ this.#rpID = config.rpID;
66
+ this.#origins = Array.isArray(config.origin)
67
+ ? config.origin
68
+ : [config.origin];
69
+ this.#timeout = config.timeout ?? 60_000;
70
+ this.#algorithms = config.supportedAlgorithms ?? DEFAULT_ALGS;
71
+ this.#challenges =
72
+ config.challengeStore ?? new MemoryWebauthnChallengeStore();
73
+ this.#credentials =
74
+ config.credentialStore ?? new MemoryWebauthnCredentialStore();
75
+ }
76
+ /**
77
+ * Begin passkey registration. Returns the options to hand to the browser,
78
+ * plus an opaque `state` token the caller stashes in the session and passes
79
+ * back to `finishRegistration()`.
80
+ */
81
+ async startRegistration(user) {
82
+ const existing = await this.#credentials.findByUser(user.id);
83
+ const challenge = bufferToBase64url(randomBytes(32));
84
+ const options = {
85
+ challenge,
86
+ rp: { name: this.#rpName, id: this.#rpID },
87
+ user: {
88
+ id: bufferToBase64url(Buffer.from(user.id, "utf8")),
89
+ name: user.name,
90
+ displayName: user.displayName ?? user.name,
91
+ },
92
+ pubKeyCredParams: this.#algorithms.map((alg) => ({
93
+ type: "public-key",
94
+ alg,
95
+ })),
96
+ timeout: this.#timeout,
97
+ attestation: "none",
98
+ excludeCredentials: existing.map((c) => ({
99
+ id: c.id,
100
+ type: "public-key",
101
+ transports: c.transports,
102
+ })),
103
+ authenticatorSelection: {
104
+ residentKey: "preferred",
105
+ userVerification: "preferred",
106
+ },
107
+ };
108
+ const state = randomBytes(16).toString("hex");
109
+ await this.#challenges.save(state, challenge);
110
+ return { options, state };
111
+ }
112
+ /**
113
+ * Complete registration: validate client data + authenticator data against
114
+ * the stored challenge and persist the new passkey on success.
115
+ */
116
+ async finishRegistration(state, userId, response) {
117
+ const expectedChallenge = await this.#challenges.take(state);
118
+ if (!expectedChallenge) {
119
+ return { verified: false };
120
+ }
121
+ if (!this.#validClientData(response.response.clientDataJSON, "webauthn.create", expectedChallenge)) {
122
+ return { verified: false };
123
+ }
124
+ const attestation = decodeCbor(base64urlToBuffer(response.response.attestationObject)).value;
125
+ if (!(attestation instanceof Map)) {
126
+ return { verified: false };
127
+ }
128
+ const authDataRaw = attestation.get("authData");
129
+ if (!Buffer.isBuffer(authDataRaw)) {
130
+ return { verified: false };
131
+ }
132
+ const authData = parseAuthenticatorData(authDataRaw);
133
+ if (!this.#validAuthenticator(authData) ||
134
+ !authData.cosePublicKey ||
135
+ !authData.cosePublicKeyBytes ||
136
+ !authData.credentialId) {
137
+ return { verified: false };
138
+ }
139
+ const { alg } = coseToKeyObject(authData.cosePublicKey);
140
+ await this.#credentials.save({
141
+ id: bufferToBase64url(authData.credentialId),
142
+ userId,
143
+ publicKey: bufferToBase64url(authData.cosePublicKeyBytes),
144
+ alg,
145
+ counter: authData.signCount,
146
+ transports: response.response.transports,
147
+ });
148
+ return { verified: true };
149
+ }
150
+ /**
151
+ * Begin authentication. Pass `userId` to restrict to that user's passkeys
152
+ * (2FA step); omit it for a usernameless / discoverable-credential sign-in.
153
+ */
154
+ async startAuthentication(userId) {
155
+ const challenge = bufferToBase64url(randomBytes(32));
156
+ const allow = userId ? await this.#credentials.findByUser(userId) : [];
157
+ const options = {
158
+ challenge,
159
+ timeout: this.#timeout,
160
+ rpId: this.#rpID,
161
+ userVerification: "preferred",
162
+ allowCredentials: userId
163
+ ? allow.map((c) => ({
164
+ id: c.id,
165
+ type: "public-key",
166
+ transports: c.transports,
167
+ }))
168
+ : undefined,
169
+ };
170
+ const state = randomBytes(16).toString("hex");
171
+ await this.#challenges.save(state, challenge);
172
+ return { options, state };
173
+ }
174
+ /**
175
+ * Complete authentication: look up the asserted credential, verify the
176
+ * signature against the stored challenge, and advance the replay counter.
177
+ * Returns the owning `userId` on success.
178
+ */
179
+ async finishAuthentication(state, response) {
180
+ const expectedChallenge = await this.#challenges.take(state);
181
+ if (!expectedChallenge) {
182
+ return { verified: false };
183
+ }
184
+ if (!this.#validClientData(response.response.clientDataJSON, "webauthn.get", expectedChallenge)) {
185
+ return { verified: false };
186
+ }
187
+ const stored = await this.#credentials.findById(response.id);
188
+ if (!stored) {
189
+ return { verified: false };
190
+ }
191
+ const authDataRaw = base64urlToBuffer(response.response.authenticatorData);
192
+ const authData = parseAuthenticatorData(authDataRaw);
193
+ if (!this.#validAuthenticator(authData)) {
194
+ return { verified: false };
195
+ }
196
+ // Signed payload = authenticatorData ‖ SHA-256(clientDataJSON).
197
+ const clientHash = sha256(base64urlToBuffer(response.response.clientDataJSON));
198
+ const signedData = Buffer.concat([authDataRaw, clientHash]);
199
+ const { key } = coseToKeyObject(expectCoseMap(base64urlToBuffer(stored.publicKey)));
200
+ const ok = verifyWebauthnSignature(stored.alg, key, signedData, base64urlToBuffer(response.response.signature));
201
+ if (!ok) {
202
+ return { verified: false };
203
+ }
204
+ // Replay guard: a non-zero counter must strictly advance. Authenticators
205
+ // that always report 0 (e.g. many platform passkeys) are exempt.
206
+ if (authData.signCount !== 0 && authData.signCount <= stored.counter) {
207
+ return { verified: false };
208
+ }
209
+ await this.#credentials.updateCounter(stored.id, authData.signCount);
210
+ return { verified: true, userId: stored.userId };
211
+ }
212
+ /** Validate clientDataJSON: type, challenge match, and origin allow-list. */
213
+ #validClientData(clientDataB64, expectedType, expectedChallenge) {
214
+ let parsed;
215
+ try {
216
+ parsed = JSON.parse(base64urlToBuffer(clientDataB64).toString("utf8"));
217
+ }
218
+ catch {
219
+ return false;
220
+ }
221
+ return (parsed.type === expectedType &&
222
+ parsed.challenge === expectedChallenge &&
223
+ typeof parsed.origin === "string" &&
224
+ this.#origins.includes(parsed.origin));
225
+ }
226
+ /** Validate authenticator data: rpIdHash match and user-presence flag. */
227
+ #validAuthenticator(authData) {
228
+ const expectedRpIdHash = sha256(Buffer.from(this.#rpID, "utf8"));
229
+ return authData.flags.up && expectedRpIdHash.equals(authData.rpIdHash);
230
+ }
231
+ }
232
+ function expectCoseMap(bytes) {
233
+ const decoded = decodeCbor(bytes).value;
234
+ if (!(decoded instanceof Map)) {
235
+ throw new WardenError("INVALID_CREDENTIAL", "stored COSE key is malformed");
236
+ }
237
+ return decoded;
238
+ }
239
+ //# sourceMappingURL=WebauthnProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebauthnProvider.js","sourceRoot":"","sources":["../../src/mfa/WebauthnProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * RFC 4648 base32 (no padding) — the encoding authenticator apps expect for
3
+ * TOTP secrets. Kept dependency-free and local to the MFA subsystem.
4
+ */
5
+ /** Encode bytes to an unpadded, uppercase base32 string. */
6
+ export declare function base32Encode(bytes: Uint8Array): string;
7
+ /**
8
+ * Decode a base32 string to bytes. Tolerant of lowercase, spaces, and `=`
9
+ * padding (authenticator apps and manual entry produce all three). Throws on
10
+ * any character outside the alphabet.
11
+ */
12
+ export declare function base32Decode(input: string): Uint8Array;
13
+ //# sourceMappingURL=base32.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base32.d.ts","sourceRoot":"","sources":["../../src/mfa/base32.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAgBtD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAkBtD"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * RFC 4648 base32 (no padding) — the encoding authenticator apps expect for
3
+ * TOTP secrets. Kept dependency-free and local to the MFA subsystem.
4
+ */
5
+ const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
6
+ const CHAR_TO_VAL = {};
7
+ for (let i = 0; i < ALPHABET.length; i++) {
8
+ CHAR_TO_VAL[ALPHABET[i]] = i;
9
+ }
10
+ /** Encode bytes to an unpadded, uppercase base32 string. */
11
+ export function base32Encode(bytes) {
12
+ let bits = 0;
13
+ let value = 0;
14
+ let out = "";
15
+ for (const byte of bytes) {
16
+ value = (value << 8) | byte;
17
+ bits += 8;
18
+ while (bits >= 5) {
19
+ out += ALPHABET[(value >>> (bits - 5)) & 31];
20
+ bits -= 5;
21
+ }
22
+ }
23
+ if (bits > 0) {
24
+ out += ALPHABET[(value << (5 - bits)) & 31];
25
+ }
26
+ return out;
27
+ }
28
+ /**
29
+ * Decode a base32 string to bytes. Tolerant of lowercase, spaces, and `=`
30
+ * padding (authenticator apps and manual entry produce all three). Throws on
31
+ * any character outside the alphabet.
32
+ */
33
+ export function base32Decode(input) {
34
+ const clean = input.replace(/[\s=]/g, "").toUpperCase();
35
+ let bits = 0;
36
+ let value = 0;
37
+ const out = [];
38
+ for (const ch of clean) {
39
+ const v = CHAR_TO_VAL[ch];
40
+ if (v === undefined) {
41
+ throw new Error(`Invalid base32 character: ${ch}`);
42
+ }
43
+ value = (value << 5) | v;
44
+ bits += 5;
45
+ if (bits >= 8) {
46
+ out.push((value >>> (bits - 8)) & 0xff);
47
+ bits -= 8;
48
+ }
49
+ }
50
+ return Uint8Array.from(out);
51
+ }
52
+ //# sourceMappingURL=base32.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base32.js","sourceRoot":"","sources":["../../src/mfa/base32.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,QAAQ,GAAG,kCAAkC,CAAC;AACpD,MAAM,WAAW,GAA2B,EAAE,CAAC;AAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;IAC1C,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAC9B,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,YAAY,CAAC,KAAiB;IAC7C,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,KAAK,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;QAC5B,IAAI,IAAI,CAAC,CAAC;QACV,OAAO,IAAI,IAAI,CAAC,EAAE,CAAC;YAClB,GAAG,IAAI,QAAQ,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;YAC7C,IAAI,IAAI,CAAC,CAAC;QACX,CAAC;IACF,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;QACd,GAAG,IAAI,QAAQ,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACzC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACxD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;QAC1B,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,6BAA6B,EAAE,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,KAAK,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,CAAC;QACV,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;YACf,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;YACxC,IAAI,IAAI,CAAC,CAAC;QACX,CAAC;IACF,CAAC;IACD,OAAO,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * WebAuthn wire-format codec — the parsing and crypto primitives behind
3
+ * `WebauthnProvider`, implemented from scratch on `node:crypto` so warden
4
+ * carries no third-party dependency.
5
+ *
6
+ * Scope: the subset of CBOR / COSE / authenticator-data that FIDO2 ceremonies
7
+ * actually use, with public-key algorithms Node can import via JWK:
8
+ * - ES256 / ES384 / ES512 (ECDSA, COSE alg -7 / -35 / -36)
9
+ * - RS256 / RS384 / RS512 (RSASSA-PKCS1-v1_5, alg -257 / -258 / -259)
10
+ * - EdDSA / Ed25519 (alg -8)
11
+ *
12
+ * Attestation *statements* are intentionally not verified (see WebauthnProvider).
13
+ */
14
+ import { type KeyObject } from "node:crypto";
15
+ export type CborValue = number | string | Buffer | CborValue[] | CborMap | boolean | null;
16
+ export type CborMap = Map<number | string, CborValue>;
17
+ export declare function base64urlToBuffer(s: string): Buffer;
18
+ export declare function bufferToBase64url(b: Buffer | Uint8Array): string;
19
+ export declare function sha256(data: Buffer): Buffer;
20
+ interface Decoded {
21
+ value: CborValue;
22
+ /** Byte offset immediately after the decoded item. */
23
+ next: number;
24
+ }
25
+ /**
26
+ * Decode a single CBOR item starting at `start`. Definite-length only —
27
+ * indefinite-length items and floats are rejected (WebAuthn uses neither).
28
+ */
29
+ export declare function decodeCbor(buf: Buffer, start?: number): Decoded;
30
+ export interface AuthenticatorData {
31
+ rpIdHash: Buffer;
32
+ flags: {
33
+ up: boolean;
34
+ uv: boolean;
35
+ at: boolean;
36
+ ed: boolean;
37
+ };
38
+ signCount: number;
39
+ credentialId?: Buffer;
40
+ cosePublicKey?: CborMap;
41
+ /** Raw COSE_Key bytes, ready to persist and re-decode at sign-in time. */
42
+ cosePublicKeyBytes?: Buffer;
43
+ }
44
+ /** Parse the raw authenticator-data structure (§6.1 of the WebAuthn spec). */
45
+ export declare function parseAuthenticatorData(authData: Buffer): AuthenticatorData;
46
+ /** Convert a COSE_Key map into a Node public key plus its COSE algorithm id. */
47
+ export declare function coseToKeyObject(cose: CborMap): {
48
+ key: KeyObject;
49
+ alg: number;
50
+ };
51
+ /**
52
+ * Verify a WebAuthn assertion signature over `data` (= authenticatorData ‖
53
+ * SHA-256(clientDataJSON)). ECDSA signatures are DER-encoded, matching what
54
+ * authenticators emit.
55
+ */
56
+ export declare function verifyWebauthnSignature(alg: number, key: KeyObject, data: Buffer, signature: Buffer): boolean;
57
+ export {};
58
+ //# sourceMappingURL=webauthn-codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webauthn-codec.d.ts","sourceRoot":"","sources":["../../src/mfa/webauthn-codec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAKN,KAAK,SAAS,EACd,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,SAAS,GAClB,MAAM,GACN,MAAM,GACN,MAAM,GACN,SAAS,EAAE,GACX,OAAO,GACP,OAAO,GACP,IAAI,CAAC;AACR,MAAM,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,GAAG,MAAM,EAAE,SAAS,CAAC,CAAC;AAEtD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAEhE;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,UAAU,OAAO;IAChB,KAAK,EAAE,SAAS,CAAC;IACjB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,OAAO,CAwE1D;AAED,MAAM,WAAW,iBAAiB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0EAA0E;IAC1E,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,8EAA8E;AAC9E,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CA+B1E;AAmBD,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG;IAC/C,GAAG,EAAE,SAAS,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACZ,CAuCA;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACtC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GACf,OAAO,CAKT"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * WebAuthn wire-format codec — the parsing and crypto primitives behind
3
+ * `WebauthnProvider`, implemented from scratch on `node:crypto` so warden
4
+ * carries no third-party dependency.
5
+ *
6
+ * Scope: the subset of CBOR / COSE / authenticator-data that FIDO2 ceremonies
7
+ * actually use, with public-key algorithms Node can import via JWK:
8
+ * - ES256 / ES384 / ES512 (ECDSA, COSE alg -7 / -35 / -36)
9
+ * - RS256 / RS384 / RS512 (RSASSA-PKCS1-v1_5, alg -257 / -258 / -259)
10
+ * - EdDSA / Ed25519 (alg -8)
11
+ *
12
+ * Attestation *statements* are intentionally not verified (see WebauthnProvider).
13
+ */
14
+ import { createHash, createPublicKey, verify as cryptoVerify, } from "node:crypto";
15
+ export function base64urlToBuffer(s) {
16
+ return Buffer.from(s, "base64url");
17
+ }
18
+ export function bufferToBase64url(b) {
19
+ return Buffer.from(b).toString("base64url");
20
+ }
21
+ export function sha256(data) {
22
+ return createHash("sha256").update(data).digest();
23
+ }
24
+ /**
25
+ * Decode a single CBOR item starting at `start`. Definite-length only —
26
+ * indefinite-length items and floats are rejected (WebAuthn uses neither).
27
+ */
28
+ export function decodeCbor(buf, start = 0) {
29
+ const major = buf[start] >> 5;
30
+ const info = buf[start] & 0x1f;
31
+ let len;
32
+ let p = start + 1;
33
+ if (info < 24) {
34
+ len = info;
35
+ }
36
+ else if (info === 24) {
37
+ len = buf[p];
38
+ p += 1;
39
+ }
40
+ else if (info === 25) {
41
+ len = buf.readUInt16BE(p);
42
+ p += 2;
43
+ }
44
+ else if (info === 26) {
45
+ len = buf.readUInt32BE(p);
46
+ p += 4;
47
+ }
48
+ else if (info === 27) {
49
+ len = Number(buf.readBigUInt64BE(p));
50
+ p += 8;
51
+ }
52
+ else {
53
+ throw new Error("CBOR: indefinite or reserved length is not supported");
54
+ }
55
+ switch (major) {
56
+ case 0: // unsigned int
57
+ return { value: len, next: p };
58
+ case 1: // negative int
59
+ return { value: -1 - len, next: p };
60
+ case 2: // byte string
61
+ return { value: buf.subarray(p, p + len), next: p + len };
62
+ case 3: // text string
63
+ return { value: buf.toString("utf8", p, p + len), next: p + len };
64
+ case 4: {
65
+ // array
66
+ const arr = [];
67
+ let cur = p;
68
+ for (let i = 0; i < len; i++) {
69
+ const d = decodeCbor(buf, cur);
70
+ arr.push(d.value);
71
+ cur = d.next;
72
+ }
73
+ return { value: arr, next: cur };
74
+ }
75
+ case 5: {
76
+ // map
77
+ const map = new Map();
78
+ let cur = p;
79
+ for (let i = 0; i < len; i++) {
80
+ const k = decodeCbor(buf, cur);
81
+ cur = k.next;
82
+ const v = decodeCbor(buf, cur);
83
+ cur = v.next;
84
+ if (typeof k.value !== "number" && typeof k.value !== "string") {
85
+ throw new Error("CBOR: only integer/text map keys are supported");
86
+ }
87
+ map.set(k.value, v.value);
88
+ }
89
+ return { value: map, next: cur };
90
+ }
91
+ case 6: {
92
+ // tag — decode and surface the tagged content
93
+ const d = decodeCbor(buf, p);
94
+ return { value: d.value, next: d.next };
95
+ }
96
+ case 7:
97
+ if (info === 20)
98
+ return { value: false, next: p };
99
+ if (info === 21)
100
+ return { value: true, next: p };
101
+ if (info === 22)
102
+ return { value: null, next: p };
103
+ throw new Error("CBOR: simple/float values are not supported");
104
+ default:
105
+ throw new Error(`CBOR: unknown major type ${major}`);
106
+ }
107
+ }
108
+ /** Parse the raw authenticator-data structure (§6.1 of the WebAuthn spec). */
109
+ export function parseAuthenticatorData(authData) {
110
+ if (authData.length < 37) {
111
+ throw new Error("authenticatorData is too short");
112
+ }
113
+ const flagsByte = authData[32];
114
+ const flags = {
115
+ up: (flagsByte & 0x01) !== 0,
116
+ uv: (flagsByte & 0x04) !== 0,
117
+ at: (flagsByte & 0x40) !== 0,
118
+ ed: (flagsByte & 0x80) !== 0,
119
+ };
120
+ const result = {
121
+ rpIdHash: authData.subarray(0, 32),
122
+ flags,
123
+ signCount: authData.readUInt32BE(33),
124
+ };
125
+ if (flags.at) {
126
+ if (authData.length < 55) {
127
+ throw new Error("attested credential data is truncated");
128
+ }
129
+ const credIdLen = authData.readUInt16BE(53);
130
+ result.credentialId = authData.subarray(55, 55 + credIdLen);
131
+ const coseStart = 55 + credIdLen;
132
+ const decoded = decodeCbor(authData, coseStart);
133
+ if (!(decoded.value instanceof Map)) {
134
+ throw new Error("COSE public key is not a CBOR map");
135
+ }
136
+ result.cosePublicKey = decoded.value;
137
+ result.cosePublicKeyBytes = authData.subarray(coseStart, decoded.next);
138
+ }
139
+ return result;
140
+ }
141
+ // COSE algorithm id → the hash Node should use ( `null` = pre-hashed / Ed25519 ).
142
+ const ALG_HASH = {
143
+ [-7]: "sha256",
144
+ [-35]: "sha384",
145
+ [-36]: "sha512",
146
+ [-257]: "sha256",
147
+ [-258]: "sha384",
148
+ [-259]: "sha512",
149
+ [-8]: null,
150
+ };
151
+ const EC_CURVES = {
152
+ 1: "P-256",
153
+ 2: "P-384",
154
+ 3: "P-521",
155
+ };
156
+ /** Convert a COSE_Key map into a Node public key plus its COSE algorithm id. */
157
+ export function coseToKeyObject(cose) {
158
+ const kty = expectNumber(cose.get(1), "COSE kty");
159
+ const alg = expectNumber(cose.get(3), "COSE alg");
160
+ let jwk;
161
+ if (kty === 2) {
162
+ // EC2
163
+ const curve = EC_CURVES[expectNumber(cose.get(-1), "EC curve")];
164
+ if (!curve) {
165
+ throw new Error("unsupported COSE EC curve");
166
+ }
167
+ jwk = {
168
+ kty: "EC",
169
+ crv: curve,
170
+ x: bufferToBase64url(expectBytes(cose.get(-2), "EC x")),
171
+ y: bufferToBase64url(expectBytes(cose.get(-3), "EC y")),
172
+ };
173
+ }
174
+ else if (kty === 3) {
175
+ // RSA
176
+ jwk = {
177
+ kty: "RSA",
178
+ n: bufferToBase64url(expectBytes(cose.get(-1), "RSA n")),
179
+ e: bufferToBase64url(expectBytes(cose.get(-2), "RSA e")),
180
+ };
181
+ }
182
+ else if (kty === 1) {
183
+ // OKP / Ed25519
184
+ if (expectNumber(cose.get(-1), "OKP curve") !== 6) {
185
+ throw new Error("unsupported COSE OKP curve (only Ed25519)");
186
+ }
187
+ jwk = {
188
+ kty: "OKP",
189
+ crv: "Ed25519",
190
+ x: bufferToBase64url(expectBytes(cose.get(-2), "OKP x")),
191
+ };
192
+ }
193
+ else {
194
+ throw new Error(`unsupported COSE key type ${kty}`);
195
+ }
196
+ return { key: createPublicKey({ key: jwk, format: "jwk" }), alg };
197
+ }
198
+ /**
199
+ * Verify a WebAuthn assertion signature over `data` (= authenticatorData ‖
200
+ * SHA-256(clientDataJSON)). ECDSA signatures are DER-encoded, matching what
201
+ * authenticators emit.
202
+ */
203
+ export function verifyWebauthnSignature(alg, key, data, signature) {
204
+ if (!(alg in ALG_HASH)) {
205
+ throw new Error(`unsupported COSE algorithm ${alg}`);
206
+ }
207
+ return cryptoVerify(ALG_HASH[alg], data, key, signature);
208
+ }
209
+ function expectNumber(v, what) {
210
+ if (typeof v !== "number") {
211
+ throw new Error(`${what}: expected an integer`);
212
+ }
213
+ return v;
214
+ }
215
+ function expectBytes(v, what) {
216
+ if (!Buffer.isBuffer(v)) {
217
+ throw new Error(`${what}: expected a byte string`);
218
+ }
219
+ return v;
220
+ }
221
+ //# sourceMappingURL=webauthn-codec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webauthn-codec.js","sourceRoot":"","sources":["../../src/mfa/webauthn-codec.ts"],"names":[],"mappings":"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"}