@tinfoilsh/passkey-kit 0.1.0 → 0.2.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 (50) hide show
  1. package/README.md +138 -81
  2. package/dist/crypto.d.ts +11 -54
  3. package/dist/crypto.d.ts.map +1 -1
  4. package/dist/crypto.js +146 -97
  5. package/dist/crypto.js.map +1 -1
  6. package/dist/errors.d.ts +11 -21
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +10 -27
  9. package/dist/errors.js.map +1 -1
  10. package/dist/index.d.ts +7 -10
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -7
  13. package/dist/index.js.map +1 -1
  14. package/dist/kit.d.ts +2 -62
  15. package/dist/kit.d.ts.map +1 -1
  16. package/dist/kit.js +305 -159
  17. package/dist/kit.js.map +1 -1
  18. package/dist/storage.d.ts +15 -29
  19. package/dist/storage.d.ts.map +1 -1
  20. package/dist/storage.js +113 -62
  21. package/dist/storage.js.map +1 -1
  22. package/dist/support.d.ts +2 -14
  23. package/dist/support.d.ts.map +1 -1
  24. package/dist/support.js +32 -47
  25. package/dist/support.js.map +1 -1
  26. package/dist/types.d.ts +85 -89
  27. package/dist/types.d.ts.map +1 -1
  28. package/dist/webauthn.d.ts +10 -33
  29. package/dist/webauthn.d.ts.map +1 -1
  30. package/dist/webauthn.js +89 -177
  31. package/dist/webauthn.js.map +1 -1
  32. package/dist/wrapped-key-record-codec.d.ts +4 -0
  33. package/dist/wrapped-key-record-codec.d.ts.map +1 -0
  34. package/dist/wrapped-key-record-codec.js +89 -0
  35. package/dist/wrapped-key-record-codec.js.map +1 -0
  36. package/package.json +4 -2
  37. package/src/crypto.ts +171 -137
  38. package/src/errors.ts +29 -33
  39. package/src/index.ts +26 -41
  40. package/src/kit.ts +405 -263
  41. package/src/storage.ts +126 -60
  42. package/src/support.ts +36 -50
  43. package/src/types.ts +99 -99
  44. package/src/webauthn.ts +121 -237
  45. package/src/wrapped-key-record-codec.ts +93 -0
  46. package/dist/protocol.d.ts +0 -13
  47. package/dist/protocol.d.ts.map +0 -1
  48. package/dist/protocol.js +0 -13
  49. package/dist/protocol.js.map +0 -1
  50. package/src/protocol.ts +0 -15
package/dist/webauthn.js CHANGED
@@ -1,189 +1,101 @@
1
- /**
2
- * WebAuthn PRF ceremonies: credential creation and assertion with the PRF
3
- * extension. Pure ceremony logic — persistence of the results is handled
4
- * by the kit through the hooks on {@link CeremonyContext}.
5
- */
6
- import { base64UrlToBytes, bufferSourceToArrayBuffer, bytesToBase64Url, } from "./codec.js";
7
- import { PasskeyKitError, PasskeyTimeoutError, PrfNotSupportedError, } from "./errors.js";
8
- const MAX_USER_HANDLE_BYTE_COUNT = 64;
9
- async function withStuckTimeout(promise, ctx) {
10
- let timer;
11
- try {
12
- return await Promise.race([
13
- promise,
14
- new Promise((_, reject) => {
15
- timer = setTimeout(() => reject(new PasskeyTimeoutError(ctx.errorMessages?.timeout)), ctx.stuckTimeoutMs);
16
- }),
17
- ]);
1
+ import { base64UrlToBytes, bufferSourceToArrayBuffer, bytesToBase64Url } from "./codec.js";
2
+ import { invalidInput, PasskeyKeyError } from "./errors.js";
3
+ const CHALLENGE_BYTES = 32;
4
+ const MAX_USER_HANDLE_BYTES = 64;
5
+ const PRF_OUTPUT_BYTES = 32;
6
+ function extensionResults(credential) {
7
+ return credential.getClientExtensionResults();
8
+ }
9
+ function credentialId(credential) {
10
+ const rawId = new Uint8Array(credential.rawId);
11
+ if (rawId.length === 0)
12
+ throw invalidInput("credential ID must not be empty");
13
+ return bytesToBase64Url(rawId);
14
+ }
15
+ function resultFromCredential(credential) {
16
+ const first = extensionResults(credential).prf?.results?.first;
17
+ if (!first) {
18
+ throw new PasskeyKeyError("unsupported", "the authenticator returned no PRF output");
18
19
  }
19
- finally {
20
- if (timer !== undefined)
21
- clearTimeout(timer);
20
+ const prfOutput = new Uint8Array(bufferSourceToArrayBuffer(first));
21
+ if (prfOutput.length !== PRF_OUTPUT_BYTES) {
22
+ throw invalidInput(`PRF output must be ${PRF_OUTPUT_BYTES} bytes`);
22
23
  }
24
+ return {
25
+ credentialId: credentialId(credential),
26
+ prfOutput,
27
+ isPlatformAuthenticator: credential.authenticatorAttachment === "platform",
28
+ };
23
29
  }
24
- /**
25
- * Create a new PRF-capable passkey for the given user.
26
- *
27
- * Returns the credential ID and PRF output, or null if the user cancels.
28
- * Throws {@link PrfNotSupportedError} when the authenticator cannot supply
29
- * PRF output and {@link PasskeyTimeoutError} when the provider hangs.
30
- */
31
- export async function createPrfPasskey(ctx, user) {
32
- const userIdBytes = new TextEncoder().encode(user.id);
33
- if (userIdBytes.byteLength > MAX_USER_HANDLE_BYTE_COUNT) {
34
- throw new PasskeyKitError(`passkey-kit: user id must be at most ${MAX_USER_HANDLE_BYTE_COUNT} UTF-8 bytes`);
30
+ export async function createPrfCredential(context, user, signal) {
31
+ if (!(user?.id instanceof Uint8Array) || user.id.length === 0) {
32
+ throw invalidInput("user.id must be a non-empty Uint8Array");
35
33
  }
36
- try {
37
- const credential = (await withStuckTimeout(navigator.credentials.create({
38
- publicKey: {
39
- challenge: crypto.getRandomValues(new Uint8Array(32)),
40
- rp: { id: ctx.rpId, name: ctx.rpName },
41
- user: {
42
- id: userIdBytes,
43
- name: user.name,
44
- displayName: user.displayName || user.name,
45
- },
46
- pubKeyCredParams: [
47
- { type: "public-key", alg: -7 }, // ES256
48
- { type: "public-key", alg: -257 }, // RS256 (broader compat)
49
- ],
50
- authenticatorSelection: {
51
- residentKey: "preferred",
52
- userVerification: "required",
53
- },
54
- timeout: ctx.webauthnTimeoutMs,
55
- extensions: {
56
- prf: { eval: { first: ctx.prfSalt } },
57
- },
58
- },
59
- }), ctx));
60
- if (!credential) {
61
- return null;
62
- }
63
- const extensionResults = credential.getClientExtensionResults();
64
- const prfResults = extensionResults.prf;
65
- if (!prfResults?.enabled) {
66
- ctx.logger.info?.("Authenticator does not support PRF", {
67
- action: "createPrfPasskey",
68
- });
69
- throw new PrfNotSupportedError(ctx.errorMessages?.prfNotSupported);
70
- }
71
- const credentialId = bytesToBase64Url(new Uint8Array(credential.rawId));
72
- // Some authenticators return PRF results during creation, others don't.
73
- // "Not all authenticators support evaluating the PRFs during credential
74
- // creation so outputs may, or may not, be provided."
75
- // — https://w3c.github.io/webauthn/#prf-extension (eval description)
76
- if (prfResults.results?.first) {
77
- const result = {
78
- credentialId,
79
- prfOutput: bufferSourceToArrayBuffer(prfResults.results.first),
80
- };
81
- ctx.onPrfResult(result, credential);
82
- return result;
83
- }
84
- // PRF enabled but no results during create — do an immediate get()
85
- ctx.logger.info?.("PRF enabled but no results during creation, doing immediate auth", { action: "createPrfPasskey" });
86
- // Pass throwOnCancel so a user-cancelled assertion surfaces as a
87
- // DOMException we can handle below — otherwise a `null` return would
88
- // be indistinguishable from "provider returned no PRF output" and we'd
89
- // show the misleading "PRF not supported" error for a plain cancel.
90
- const postCreateAuth = await authenticatePrfPasskey(ctx, [credentialId], {
91
- throwOnCancel: true,
92
- });
93
- if (!postCreateAuth) {
94
- // The provider claimed PRF support during creation but didn't deliver
95
- // a PRF output on the immediately-following assertion. Treat this as
96
- // a lack of real PRF support rather than a silent failure.
97
- throw new PrfNotSupportedError(ctx.errorMessages?.prfNotSupported);
98
- }
99
- return postCreateAuth;
34
+ if (user.id.length > MAX_USER_HANDLE_BYTES) {
35
+ throw invalidInput(`user.id must be at most ${MAX_USER_HANDLE_BYTES} bytes`);
100
36
  }
101
- catch (error) {
102
- if (error instanceof PrfNotSupportedError)
103
- throw error;
104
- if (error instanceof PasskeyTimeoutError)
105
- throw error;
106
- // DOMException with name "NotAllowedError" means the user cancelled
107
- if (error instanceof DOMException && error.name === "NotAllowedError") {
108
- ctx.logger.info?.("User cancelled passkey creation", {
109
- action: "createPrfPasskey",
110
- });
111
- return null;
112
- }
113
- ctx.logger.error?.("Failed to create PRF passkey", error, {
114
- action: "createPrfPasskey",
115
- });
116
- throw error;
37
+ if (typeof user.name !== "string" || user.name.length === 0) {
38
+ throw invalidInput("user.name must be a non-empty string");
117
39
  }
118
- }
119
- /**
120
- * Authenticate with an existing PRF passkey to derive the PRF output.
121
- *
122
- * @param credentialIds - base64url-encoded credential IDs to allow. Pass all
123
- * known PRF credential IDs so the browser can select the right one.
124
- * @returns The matched credential ID and PRF output, or null on failure/cancel.
125
- */
126
- export async function authenticatePrfPasskey(ctx, credentialIds, options = {}) {
127
- const { throwOnCancel = false } = options;
128
- const allowCredentials = credentialIds.map((id) => ({
129
- id: base64UrlToBytes(id),
130
- type: "public-key",
131
- }));
132
- try {
133
- const assertion = (await withStuckTimeout(navigator.credentials.get({
134
- publicKey: {
135
- challenge: crypto.getRandomValues(new Uint8Array(32)),
136
- rpId: ctx.rpId,
137
- allowCredentials,
40
+ if (user.displayName !== undefined && (typeof user.displayName !== "string" || !user.displayName)) {
41
+ throw invalidInput("user.displayName must be a non-empty string");
42
+ }
43
+ const credential = (await navigator.credentials.create({
44
+ signal,
45
+ publicKey: {
46
+ challenge: crypto.getRandomValues(new Uint8Array(CHALLENGE_BYTES)),
47
+ rp: { id: context.rpId, name: context.rpName },
48
+ user: {
49
+ id: user.id.slice(),
50
+ name: user.name,
51
+ displayName: user.displayName ?? user.name,
52
+ },
53
+ pubKeyCredParams: [
54
+ { type: "public-key", alg: -7 },
55
+ { type: "public-key", alg: -257 },
56
+ ],
57
+ authenticatorSelection: {
58
+ authenticatorAttachment: "platform",
59
+ residentKey: "preferred",
138
60
  userVerification: "required",
139
- timeout: ctx.webauthnTimeoutMs,
140
- extensions: {
141
- prf: { eval: { first: ctx.prfSalt } },
142
- },
143
61
  },
144
- }), ctx));
145
- if (!assertion) {
146
- ctx.logger.info?.("passkey assertion returned no credential", {
147
- action: "authenticatePrfPasskey",
148
- allowedCredentials: credentialIds.length,
149
- });
150
- return null;
151
- }
152
- const extensionResults = assertion.getClientExtensionResults();
153
- const prfOutput = extensionResults.prf?.results?.first;
154
- if (!prfOutput) {
155
- ctx.logger.error?.("PRF output missing from assertion", undefined, {
156
- action: "authenticatePrfPasskey",
157
- });
158
- throw new PrfNotSupportedError(ctx.errorMessages?.prfNotSupported);
159
- }
160
- const result = {
161
- credentialId: bytesToBase64Url(new Uint8Array(assertion.rawId)),
162
- prfOutput: bufferSourceToArrayBuffer(prfOutput),
163
- };
164
- ctx.onPrfResult(result, assertion);
165
- return result;
62
+ timeout: context.timeoutMs,
63
+ extensions: {
64
+ prf: { eval: { first: context.prfInput.slice() } },
65
+ },
66
+ },
67
+ }));
68
+ if (!credential) {
69
+ throw new PasskeyKeyError("cancelled", "credential creation was cancelled or no eligible credential was available");
70
+ }
71
+ const extension = extensionResults(credential).prf;
72
+ if (!extension?.enabled) {
73
+ throw new PasskeyKeyError("unsupported", "the authenticator does not support PRF");
166
74
  }
167
- catch (error) {
168
- if (error instanceof PasskeyTimeoutError)
169
- throw error;
170
- if (error instanceof DOMException && error.name === "NotAllowedError") {
171
- // NotAllowedError covers both a user cancel and the case where the
172
- // provider has no usable credential for any of the allowed ids
173
- // (e.g. the passkey was created in a different browser/profile and
174
- // never persisted on this device).
175
- ctx.logger.info?.("passkey authentication not allowed (cancelled or no usable credential)", {
176
- action: "authenticatePrfPasskey",
177
- allowedCredentials: credentialIds.length,
178
- });
179
- if (throwOnCancel)
180
- throw error;
181
- return null;
182
- }
183
- ctx.logger.error?.("Failed to authenticate with PRF passkey", error, {
184
- action: "authenticatePrfPasskey",
185
- });
186
- throw error;
75
+ if (extension.results?.first)
76
+ return resultFromCredential(credential);
77
+ return evaluatePrfCredential(context, [credentialId(credential)], signal);
78
+ }
79
+ export async function evaluatePrfCredential(context, credentialIds, signal) {
80
+ const assertion = (await navigator.credentials.get({
81
+ signal,
82
+ publicKey: {
83
+ challenge: crypto.getRandomValues(new Uint8Array(CHALLENGE_BYTES)),
84
+ rpId: context.rpId,
85
+ allowCredentials: credentialIds.map((credentialId) => ({
86
+ type: "public-key",
87
+ id: base64UrlToBytes(credentialId),
88
+ })),
89
+ userVerification: "required",
90
+ timeout: context.timeoutMs,
91
+ extensions: {
92
+ prf: { eval: { first: context.prfInput.slice() } },
93
+ },
94
+ },
95
+ }));
96
+ if (!assertion) {
97
+ throw new PasskeyKeyError("cancelled", "credential evaluation was cancelled or no eligible credential was available");
187
98
  }
99
+ return resultFromCredential(assertion);
188
100
  }
189
101
  //# sourceMappingURL=webauthn.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"webauthn.js","sourceRoot":"","sources":["../src/webauthn.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"webauthn.js","sourceRoot":"","sources":["../src/webauthn.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { WrappedKey } from "./types.js";
2
+ export declare function encodeWrappedKeyRecord(wrappedKey: WrappedKey): string;
3
+ export declare function decodeWrappedKeyRecord(json: string): WrappedKey;
4
+ //# sourceMappingURL=wrapped-key-record-codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapped-key-record-codec.d.ts","sourceRoot":"","sources":["../src/wrapped-key-record-codec.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAoB,MAAM,YAAY,CAAC;AAgB/D,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAmBrE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CA+C/D"}
@@ -0,0 +1,89 @@
1
+ import { bytesToBase64Url } from "./codec.js";
2
+ import { copyAndValidateProfile, decodeCanonicalBase64Url, PROFILE_KEYS, validateWrappedKey, } from "./crypto.js";
3
+ import { invalidInput } from "./errors.js";
4
+ const RECORD_FIELDS = [
5
+ "version",
6
+ "profile",
7
+ "credentialId",
8
+ "kekIvHex",
9
+ "wrappedKeyHex",
10
+ ];
11
+ function hasExactFields(value, fields) {
12
+ if (!value || typeof value !== "object" || Array.isArray(value))
13
+ return false;
14
+ const keys = Object.keys(value).sort();
15
+ const expected = [...fields].sort();
16
+ return keys.length === expected.length && keys.every((key, index) => key === expected[index]);
17
+ }
18
+ export function encodeWrappedKeyRecord(wrappedKey) {
19
+ if (!wrappedKey || typeof wrappedKey !== "object") {
20
+ throw invalidInput("wrapped key is required");
21
+ }
22
+ const profile = copyAndValidateProfile(wrappedKey.profile);
23
+ validateWrappedKey(wrappedKey, profile);
24
+ const record = {
25
+ version: 1,
26
+ profile: {
27
+ version: 1,
28
+ relyingPartyId: profile.relyingPartyId,
29
+ prfSalt: bytesToBase64Url(profile.prfSalt),
30
+ hkdfInfo: bytesToBase64Url(profile.hkdfInfo),
31
+ },
32
+ credentialId: wrappedKey.credentialId,
33
+ kekIvHex: wrappedKey.kekIvHex,
34
+ wrappedKeyHex: wrappedKey.wrappedKeyHex,
35
+ };
36
+ return JSON.stringify(record);
37
+ }
38
+ export function decodeWrappedKeyRecord(json) {
39
+ if (typeof json !== "string")
40
+ throw invalidInput("wrapped key JSON must be a string");
41
+ let parsed;
42
+ try {
43
+ parsed = JSON.parse(json);
44
+ }
45
+ catch {
46
+ throw invalidInput("wrapped key JSON is malformed");
47
+ }
48
+ if (!hasExactFields(parsed, RECORD_FIELDS)) {
49
+ throw invalidInput("wrapped key record has unexpected fields");
50
+ }
51
+ if (parsed.version !== 1)
52
+ throw invalidInput("wrapped key record version must be 1");
53
+ if (!hasExactFields(parsed.profile, PROFILE_KEYS)) {
54
+ throw invalidInput("wrapped key profile record has unexpected fields");
55
+ }
56
+ if (parsed.profile.version !== 1) {
57
+ throw invalidInput("wrapped key profile version must be 1");
58
+ }
59
+ if (typeof parsed.profile.relyingPartyId !== "string") {
60
+ throw invalidInput("profile.relyingPartyId must be a string");
61
+ }
62
+ if (typeof parsed.credentialId !== "string") {
63
+ throw invalidInput("credentialId must be a string");
64
+ }
65
+ if (typeof parsed.kekIvHex !== "string") {
66
+ throw invalidInput("kekIvHex must be a string");
67
+ }
68
+ if (typeof parsed.wrappedKeyHex !== "string") {
69
+ throw invalidInput("wrappedKeyHex must be a string");
70
+ }
71
+ const wrappedKey = {
72
+ profile: {
73
+ version: 1,
74
+ relyingPartyId: parsed.profile.relyingPartyId,
75
+ prfSalt: decodeCanonicalBase64Url(parsed.profile.prfSalt, "profile.prfSalt"),
76
+ hkdfInfo: decodeCanonicalBase64Url(parsed.profile.hkdfInfo, "profile.hkdfInfo"),
77
+ },
78
+ credentialId: parsed.credentialId,
79
+ kekIvHex: parsed.kekIvHex,
80
+ wrappedKeyHex: parsed.wrappedKeyHex,
81
+ };
82
+ const profile = copyAndValidateProfile(wrappedKey.profile);
83
+ validateWrappedKey(wrappedKey, profile);
84
+ return {
85
+ ...wrappedKey,
86
+ profile,
87
+ };
88
+ }
89
+ //# sourceMappingURL=wrapped-key-record-codec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapped-key-record-codec.js","sourceRoot":"","sources":["../src/wrapped-key-record-codec.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinfoilsh/passkey-kit",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "WebAuthn PRF passkey SDK for protecting encryption keys with passkey-derived keys.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -16,7 +16,9 @@
16
16
  },
17
17
  "files": [
18
18
  "dist",
19
- "src"
19
+ "src",
20
+ "LICENSE",
21
+ "README.md"
20
22
  ],
21
23
  "scripts": {
22
24
  "clean": "node --input-type=module -e \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true })\"",