@yume-chan/adb-credential-web 2.1.0 → 3.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +4 -1
  3. package/esm/index.d.ts +2 -23
  4. package/esm/index.d.ts.map +1 -1
  5. package/esm/index.js +2 -102
  6. package/esm/index.js.map +1 -1
  7. package/esm/manager.d.ts +9 -0
  8. package/esm/manager.d.ts.map +1 -0
  9. package/esm/manager.js +56 -0
  10. package/esm/manager.js.map +1 -0
  11. package/esm/storage/index.d.ts +6 -0
  12. package/esm/storage/index.d.ts.map +1 -0
  13. package/esm/storage/index.js +6 -0
  14. package/esm/storage/index.js.map +1 -0
  15. package/esm/storage/indexed-db/index.d.ts +3 -0
  16. package/esm/storage/indexed-db/index.d.ts.map +1 -0
  17. package/esm/storage/indexed-db/index.js +3 -0
  18. package/esm/storage/indexed-db/index.js.map +1 -0
  19. package/esm/storage/indexed-db/shared.d.ts +4 -0
  20. package/esm/storage/indexed-db/shared.d.ts.map +1 -0
  21. package/esm/storage/indexed-db/shared.js +50 -0
  22. package/esm/storage/indexed-db/shared.js.map +1 -0
  23. package/esm/storage/indexed-db/v1.d.ts +5 -0
  24. package/esm/storage/indexed-db/v1.d.ts.map +1 -0
  25. package/esm/storage/indexed-db/v1.js +22 -0
  26. package/esm/storage/indexed-db/v1.js.map +1 -0
  27. package/esm/storage/indexed-db/v2.d.ts +14 -0
  28. package/esm/storage/indexed-db/v2.d.ts.map +1 -0
  29. package/esm/storage/indexed-db/v2.js +78 -0
  30. package/esm/storage/indexed-db/v2.js.map +1 -0
  31. package/esm/storage/local-storage.d.ts +8 -0
  32. package/esm/storage/local-storage.d.ts.map +1 -0
  33. package/esm/storage/local-storage.js +25 -0
  34. package/esm/storage/local-storage.js.map +1 -0
  35. package/esm/storage/password.d.ts +27 -0
  36. package/esm/storage/password.d.ts.map +1 -0
  37. package/esm/storage/password.js +129 -0
  38. package/esm/storage/password.js.map +1 -0
  39. package/esm/storage/prf/index.d.ts +4 -0
  40. package/esm/storage/prf/index.d.ts.map +1 -0
  41. package/esm/storage/prf/index.js +4 -0
  42. package/esm/storage/prf/index.js.map +1 -0
  43. package/esm/storage/prf/source.d.ts +27 -0
  44. package/esm/storage/prf/source.d.ts.map +1 -0
  45. package/esm/storage/prf/source.js +2 -0
  46. package/esm/storage/prf/source.js.map +1 -0
  47. package/esm/storage/prf/storage.d.ts +19 -0
  48. package/esm/storage/prf/storage.d.ts.map +1 -0
  49. package/esm/storage/prf/storage.js +125 -0
  50. package/esm/storage/prf/storage.js.map +1 -0
  51. package/esm/storage/prf/web-authn.d.ts +55 -0
  52. package/esm/storage/prf/web-authn.d.ts.map +1 -0
  53. package/esm/storage/prf/web-authn.js +138 -0
  54. package/esm/storage/prf/web-authn.js.map +1 -0
  55. package/esm/storage/type.d.ts +11 -0
  56. package/esm/storage/type.d.ts.map +1 -0
  57. package/esm/storage/type.js +2 -0
  58. package/esm/storage/type.js.map +1 -0
  59. package/package.json +10 -7
  60. package/src/index.ts +2 -121
  61. package/src/manager.ts +82 -0
  62. package/src/storage/index.ts +5 -0
  63. package/src/storage/indexed-db/index.ts +2 -0
  64. package/src/storage/indexed-db/shared.ts +62 -0
  65. package/src/storage/indexed-db/v1.ts +29 -0
  66. package/src/storage/indexed-db/v2.ts +95 -0
  67. package/src/storage/local-storage.ts +37 -0
  68. package/src/storage/password.ts +245 -0
  69. package/src/storage/prf/index.ts +3 -0
  70. package/src/storage/prf/source.ts +35 -0
  71. package/src/storage/prf/storage.ts +191 -0
  72. package/src/storage/prf/web-authn.ts +175 -0
  73. package/src/storage/type.ts +17 -0
  74. package/CHANGELOG.md +0 -149
  75. package/tsconfig.build.tsbuildinfo +0 -1
@@ -0,0 +1,129 @@
1
+ import { encodeUtf8, toLocalUint8Array } from "@yume-chan/adb";
2
+ import { buffer, struct, u16, u32, u8, Uint8ArrayExactReadable, } from "@yume-chan/struct";
3
+ const DefaultPbkdf2SaltLength = 16; // Recommended
4
+ const MinimalPbkdf2SaltLength = 8; // Not recommended but still could be considered secure
5
+ const MaximalPbkdf2SaltLength = 255; // Max length can be stored in `Bundle`
6
+ const DefaultPbkdf2Iterations = 1_000_000; // Very secure according to OWASP Cheat Sheet
7
+ const MinimalPbkdf2Iterations = 10_000; // Not recommended but still could be considered secure
8
+ const MaximalPbkdf2Iterations = 4294967295; // 2^32 - 1, max value can be stored in `Bundle`
9
+ const DefaultAesIvLength = 12; // 12 bytes (96 bits) is the native IV length
10
+ const MinimalAesIvLength = 1; // Any value except 12 is not recommended, but could work
11
+ const MaximalAesIvLength = 255; // Max length can be stored in `Bundle`
12
+ const Bundle = struct({
13
+ pbkdf2Salt: buffer(u8),
14
+ pbkdf2Iterations: u32,
15
+ aesIv: buffer(u8),
16
+ encrypted: buffer(u16),
17
+ }, { littleEndian: true });
18
+ async function deriveAesKey(password, saltOrLength, iterations) {
19
+ const baseKey = await crypto.subtle.importKey("raw", encodeUtf8(password), "PBKDF2", false, ["deriveKey"]);
20
+ let salt;
21
+ if (typeof saltOrLength === "number") {
22
+ salt = new Uint8Array(saltOrLength);
23
+ crypto.getRandomValues(salt);
24
+ }
25
+ else {
26
+ salt = saltOrLength;
27
+ }
28
+ const aesKey = await crypto.subtle.deriveKey({
29
+ name: "PBKDF2",
30
+ salt,
31
+ iterations,
32
+ hash: "SHA-256",
33
+ }, baseKey, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
34
+ return { salt, aesKey };
35
+ }
36
+ class PasswordIncorrectError extends Error {
37
+ #keyName;
38
+ get keyName() {
39
+ return this.#keyName;
40
+ }
41
+ constructor(keyName) {
42
+ super("Password incorrect");
43
+ this.#keyName = keyName;
44
+ }
45
+ }
46
+ // eslint-disable-next-line @typescript-eslint/max-params
47
+ function checkIntegerRange(name, value, defaultValue, min, max) {
48
+ if (value === undefined) {
49
+ return defaultValue;
50
+ }
51
+ if (!Number.isInteger(value) || value < min || value > max) {
52
+ throw new Error(`${name} must be an integer between ${min} and ${max}`);
53
+ }
54
+ return value;
55
+ }
56
+ export class TangoPasswordProtectedStorage {
57
+ static PasswordIncorrectError = PasswordIncorrectError;
58
+ #storage;
59
+ #requestPassword;
60
+ #pbkdf2SaltLength;
61
+ #pbkdf2Iterations;
62
+ #aesIvLength;
63
+ constructor(options) {
64
+ this.#storage = options.storage;
65
+ this.#requestPassword = options.requestPassword;
66
+ this.#pbkdf2SaltLength = checkIntegerRange("pbkdf2SaltLength", options.pbkdf2SaltLength, DefaultPbkdf2SaltLength, MinimalPbkdf2SaltLength, MaximalPbkdf2SaltLength);
67
+ this.#pbkdf2Iterations = checkIntegerRange("pbkdf2Iterations", options.pbkdf2Iterations, DefaultPbkdf2Iterations, MinimalPbkdf2Iterations, MaximalPbkdf2Iterations);
68
+ this.#aesIvLength = checkIntegerRange("aesIvLength", options.aesIvLength, DefaultAesIvLength, MinimalAesIvLength, MaximalAesIvLength);
69
+ }
70
+ async save(privateKey, name) {
71
+ const password = await this.#requestPassword("save", name);
72
+ const { salt, aesKey } = await deriveAesKey(password, this.#pbkdf2SaltLength, this.#pbkdf2Iterations);
73
+ const iv = new Uint8Array(this.#aesIvLength);
74
+ crypto.getRandomValues(iv);
75
+ const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, toLocalUint8Array(privateKey));
76
+ const bundle = Bundle.serialize({
77
+ pbkdf2Salt: salt,
78
+ pbkdf2Iterations: this.#pbkdf2Iterations,
79
+ aesIv: iv,
80
+ encrypted: new Uint8Array(encrypted),
81
+ });
82
+ await this.#storage.save(bundle, name);
83
+ // Clear secret memory
84
+ // * No way to clear `password` and `aesKey`
85
+ // * `salt`, `iv`, `encrypted` and `bundle` are not secrets
86
+ // * `privateKey` is owned by caller and will be cleared by caller
87
+ }
88
+ async *load() {
89
+ for await (const result of this.#storage.load()) {
90
+ if (result instanceof Error) {
91
+ yield result;
92
+ continue;
93
+ }
94
+ const { privateKey: serialized, name } = result;
95
+ try {
96
+ const bundle = Bundle.deserialize(new Uint8ArrayExactReadable(serialized));
97
+ const password = await this.#requestPassword("load", name);
98
+ const { aesKey } = await deriveAesKey(password, bundle.pbkdf2Salt, bundle.pbkdf2Iterations);
99
+ const decrypted = await crypto.subtle.decrypt({
100
+ name: "AES-GCM",
101
+ iv: bundle.aesIv,
102
+ }, aesKey, bundle.encrypted);
103
+ try {
104
+ yield {
105
+ privateKey: new Uint8Array(decrypted),
106
+ name,
107
+ };
108
+ }
109
+ finally {
110
+ // Clear secret memory
111
+ // * No way to clear `password` and `aesKey`
112
+ // * all values in `bundle` are not secrets
113
+ // * Caller is not allowed to use `decrypted` after `yield` returns
114
+ new Uint8Array(decrypted).fill(0);
115
+ }
116
+ }
117
+ catch (e) {
118
+ if (e instanceof DOMException && e.name === "OperationError") {
119
+ yield new PasswordIncorrectError(name);
120
+ continue;
121
+ }
122
+ yield e instanceof Error
123
+ ? e
124
+ : new Error(String(e), { cause: e });
125
+ }
126
+ }
127
+ }
128
+ }
129
+ //# sourceMappingURL=password.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password.js","sourceRoot":"","sources":["../../src/storage/password.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export * from "./source.js";
2
+ export * from "./storage.js";
3
+ export * from "./web-authn.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/storage/prf/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from "./source.js";
2
+ export * from "./storage.js";
3
+ export * from "./web-authn.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/storage/prf/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { MaybePromiseLike } from "@yume-chan/async";
2
+ export interface TangoPrfCreationResult {
3
+ /**
4
+ * The generated PRF output
5
+ */
6
+ output: BufferSource;
7
+ /**
8
+ * ID of the created secret key
9
+ */
10
+ id: Uint8Array<ArrayBuffer>;
11
+ }
12
+ export interface TangoPrfSource {
13
+ /**
14
+ * Creates a new secret key and generate PRF output using the key and input data.
15
+ *
16
+ * @param input The input data
17
+ */
18
+ create(input: Uint8Array<ArrayBuffer>): MaybePromiseLike<TangoPrfCreationResult>;
19
+ /**
20
+ * Generates PRF output using the secret key and input data.
21
+ *
22
+ * @param id ID of the secret key
23
+ * @param input The input data
24
+ */
25
+ get(id: BufferSource, input: Uint8Array<ArrayBuffer>): MaybePromiseLike<BufferSource>;
26
+ }
27
+ //# sourceMappingURL=source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../src/storage/prf/source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,MAAM,WAAW,sBAAsB;IACnC;;OAEG;IACH,MAAM,EAAE,YAAY,CAAC;IAErB;;OAEG;IACH,EAAE,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC3B;;;;OAIG;IACH,MAAM,CACF,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,GAC/B,gBAAgB,CAAC,sBAAsB,CAAC,CAAC;IAE5C;;;;;OAKG;IACH,GAAG,CACC,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,GAC/B,gBAAgB,CAAC,YAAY,CAAC,CAAC;CACrC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../../../src/storage/prf/source.ts"],"names":[],"mappings":""}
@@ -0,0 +1,19 @@
1
+ import type { MaybeError } from "@yume-chan/adb";
2
+ import type { TangoKey, TangoKeyStorage } from "../type.js";
3
+ import type { TangoPrfSource } from "./source.js";
4
+ /**
5
+ * A `TangoDataStorage` that encrypts and decrypts data using PRF
6
+ */
7
+ export declare class TangoPrfStorage implements TangoKeyStorage {
8
+ #private;
9
+ /**
10
+ * Creates a new instance of `TangoPrfStorage`
11
+ *
12
+ * @param storage Another `TangoDataStorage` to store and retrieve the encrypted data
13
+ * @param source The `TangoPrfSource` to generate PRF output
14
+ */
15
+ constructor(storage: TangoKeyStorage, source: TangoPrfSource);
16
+ save(privateKey: Uint8Array, name: string | undefined): Promise<undefined>;
17
+ load(): AsyncGenerator<MaybeError<TangoKey>, void, void>;
18
+ }
19
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/storage/prf/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAUjD,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAkDlD;;GAEG;AACH,qBAAa,eAAgB,YAAW,eAAe;;IAKnD;;;;;OAKG;gBACS,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc;IAKtD,IAAI,CACN,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,MAAM,GAAG,SAAS,GACzB,OAAO,CAAC,SAAS,CAAC;IAkDd,IAAI,IAAI,cAAc,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC;CAwDlE"}
@@ -0,0 +1,125 @@
1
+ import { toLocalUint8Array } from "@yume-chan/adb";
2
+ import { toUint8Array } from "@yume-chan/stream-extra";
3
+ import { buffer, struct, u16, Uint8ArrayExactReadable, } from "@yume-chan/struct";
4
+ // PRF generally uses FIDO HMAC secret extension, which uses HMAC with SHA-256,
5
+ // and this input is used as salt, so should be 32 bytes
6
+ const PrfInputLength = 32;
7
+ const HkdfInfoLength = 32;
8
+ // We use HMAC with SHA-512, so should be 64 bytes
9
+ const HkdfSaltLength = 64;
10
+ // AES-GCM recommends 12-byte (96-bit) IV for performance and interoperability
11
+ const AesIvLength = 12;
12
+ async function deriveAesKey(source, info, salt) {
13
+ const baseKey = await crypto.subtle.importKey("raw", source, "HKDF", false, ["deriveKey"]);
14
+ return await crypto.subtle.deriveKey({
15
+ name: "HKDF",
16
+ hash: "SHA-512",
17
+ info,
18
+ salt,
19
+ }, baseKey, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
20
+ }
21
+ const Bundle = struct({
22
+ id: buffer(u16),
23
+ prfInput: buffer(PrfInputLength),
24
+ hkdfInfo: buffer(HkdfInfoLength),
25
+ hkdfSalt: buffer(HkdfSaltLength),
26
+ aesIv: buffer(AesIvLength),
27
+ encrypted: buffer(u16),
28
+ }, { littleEndian: true });
29
+ /**
30
+ * A `TangoDataStorage` that encrypts and decrypts data using PRF
31
+ */
32
+ export class TangoPrfStorage {
33
+ #storage;
34
+ #source;
35
+ #prevId;
36
+ /**
37
+ * Creates a new instance of `TangoPrfStorage`
38
+ *
39
+ * @param storage Another `TangoDataStorage` to store and retrieve the encrypted data
40
+ * @param source The `TangoPrfSource` to generate PRF output
41
+ */
42
+ constructor(storage, source) {
43
+ this.#storage = storage;
44
+ this.#source = source;
45
+ }
46
+ async save(privateKey, name) {
47
+ const prfInput = new Uint8Array(PrfInputLength);
48
+ crypto.getRandomValues(prfInput);
49
+ // Maybe reuse the credential, but use different PRF input and HKDF params
50
+ let id;
51
+ let prfOutput;
52
+ if (this.#prevId) {
53
+ prfOutput = await this.#source.get(this.#prevId, prfInput);
54
+ id = this.#prevId;
55
+ }
56
+ else {
57
+ ({ output: prfOutput, id } = await this.#source.create(prfInput));
58
+ this.#prevId = id;
59
+ }
60
+ const info = new Uint8Array(HkdfInfoLength);
61
+ crypto.getRandomValues(info);
62
+ const salt = new Uint8Array(HkdfSaltLength);
63
+ crypto.getRandomValues(salt);
64
+ let aesKey;
65
+ try {
66
+ aesKey = await deriveAesKey(prfOutput, info, salt);
67
+ }
68
+ finally {
69
+ // Clear secret memory
70
+ toUint8Array(prfOutput).fill(0);
71
+ }
72
+ const iv = new Uint8Array(AesIvLength);
73
+ crypto.getRandomValues(iv);
74
+ const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, toLocalUint8Array(privateKey));
75
+ const bundle = Bundle.serialize({
76
+ id,
77
+ prfInput,
78
+ hkdfInfo: info,
79
+ hkdfSalt: salt,
80
+ aesIv: iv,
81
+ encrypted: new Uint8Array(encrypted),
82
+ });
83
+ await this.#storage.save(bundle, name);
84
+ }
85
+ async *load() {
86
+ for await (const result of this.#storage.load()) {
87
+ if (result instanceof Error) {
88
+ yield result;
89
+ continue;
90
+ }
91
+ const { privateKey: serialized, name } = result;
92
+ try {
93
+ const bundle = Bundle.deserialize(new Uint8ArrayExactReadable(serialized));
94
+ const prfOutput = await this.#source.get(bundle.id, bundle.prfInput);
95
+ this.#prevId = bundle.id;
96
+ let aesKey;
97
+ try {
98
+ aesKey = await deriveAesKey(prfOutput, bundle.hkdfInfo, bundle.hkdfSalt);
99
+ }
100
+ finally {
101
+ // Clear secret memory
102
+ toUint8Array(prfOutput).fill(0);
103
+ }
104
+ const decrypted = await crypto.subtle.decrypt({
105
+ name: "AES-GCM",
106
+ iv: bundle.aesIv,
107
+ }, aesKey, bundle.encrypted);
108
+ try {
109
+ yield { privateKey: new Uint8Array(decrypted), name };
110
+ }
111
+ finally {
112
+ // Clear secret memory
113
+ // Caller is not allowed to use `decrypted` after `yield` returns
114
+ new Uint8Array(decrypted).fill(0);
115
+ }
116
+ }
117
+ catch (e) {
118
+ yield e instanceof Error
119
+ ? e
120
+ : new Error(String(e), { cause: e });
121
+ }
122
+ }
123
+ }
124
+ }
125
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/storage/prf/storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import type { TangoPrfSource } from "./source.js";
2
+ declare class NotSupportedError extends Error {
3
+ constructor();
4
+ }
5
+ declare class OperationCancelledError extends Error {
6
+ constructor();
7
+ }
8
+ export declare class TangoWebAuthnPrfSource implements TangoPrfSource {
9
+ #private;
10
+ static NotSupportedError: typeof NotSupportedError;
11
+ static OperationCancelledError: typeof OperationCancelledError;
12
+ /**
13
+ * Checks if the runtime supports WebAuthn PRF extension.
14
+ *
15
+ * Note that using the extension also requires a supported authenticator.
16
+ * Whether an authenticator supports the extension can only be checked
17
+ * during the `create` process.
18
+ * @returns `true` if the runtime supports WebAuthn PRF extension
19
+ */
20
+ static isSupported(): Promise<boolean>;
21
+ /**
22
+ * Creates a new instance of `TangoWebAuthnPrfSource`
23
+ *
24
+ * @param appName Name of your website shows in Passkey manager
25
+ * @param userName Display name of the credential shows in Passkey manager
26
+ */
27
+ constructor(appName: string, userName: string);
28
+ /**
29
+ * Creates a new credential and generate PRF output using the credential and input data.
30
+ *
31
+ * @param input The input data
32
+ * @returns The credential ID and PRF output
33
+ * @throws `NotSupportedError` if the runtime or authenticator doesn't support PRF extension
34
+ * @throws `OperationCancelledError` if the attestation is either cancelled by user or timed out
35
+ */
36
+ create(input: Uint8Array<ArrayBuffer>): Promise<{
37
+ output: BufferSource;
38
+ id: Uint8Array<ArrayBuffer>;
39
+ }>;
40
+ /**
41
+ * Generates PRF output using a credential and input data.
42
+ *
43
+ * @param id ID of a previously created credential
44
+ * @param input The input data
45
+ * @returns PRF output
46
+ * @throws `OperationCancelledError` if the attestation is either cancelled by user or timed out
47
+ */
48
+ get(id: BufferSource, input: Uint8Array<ArrayBuffer>): Promise<BufferSource>;
49
+ }
50
+ export declare namespace TangoWebAuthnPrfSource {
51
+ type NotSupportedError = typeof NotSupportedError;
52
+ type OperationCancelledError = typeof OperationCancelledError;
53
+ }
54
+ export {};
55
+ //# sourceMappingURL=web-authn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-authn.d.ts","sourceRoot":"","sources":["../../../src/storage/prf/web-authn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAqBlD,cAAM,iBAAkB,SAAQ,KAAK;;CAIpC;AAED,cAAM,uBAAwB,SAAQ,KAAK;;CAI1C;AAED,qBAAa,sBAAuB,YAAW,cAAc;;IACzD,MAAM,CAAC,iBAAiB,2BAAqB;IAC7C,MAAM,CAAC,uBAAuB,iCAA2B;IAEzD;;;;;;;OAOG;WACU,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAqB5C;;;;;OAKG;gBACS,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAK7C;;;;;;;OAOG;IACG,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC;QAClD,MAAM,EAAE,YAAY,CAAC;QACrB,EAAE,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;KAC/B,CAAC;IA4CF;;;;;;;OAOG;IACG,GAAG,CACL,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,GAC/B,OAAO,CAAC,YAAY,CAAC;CA0B3B;AAED,yBAAiB,sBAAsB,CAAC;IACpC,KAAY,iBAAiB,GAAG,OAAO,iBAAiB,CAAC;IACzD,KAAY,uBAAuB,GAAG,OAAO,uBAAuB,CAAC;CACxE"}
@@ -0,0 +1,138 @@
1
+ function checkCredential(credential) {
2
+ if (!credential || !(credential instanceof PublicKeyCredential)) {
3
+ throw new Error("Can't create credential");
4
+ }
5
+ }
6
+ function getPrfOutput(credential) {
7
+ const extensions = credential.getClientExtensionResults();
8
+ const prf = extensions["prf"];
9
+ if (!prf) {
10
+ throw new NotSupportedError();
11
+ }
12
+ return prf;
13
+ }
14
+ class NotSupportedError extends Error {
15
+ constructor() {
16
+ super("PRF extension is not supported");
17
+ }
18
+ }
19
+ class OperationCancelledError extends Error {
20
+ constructor() {
21
+ super("The operation is either cancelled by user or timed out");
22
+ }
23
+ }
24
+ export class TangoWebAuthnPrfSource {
25
+ static NotSupportedError = NotSupportedError;
26
+ static OperationCancelledError = OperationCancelledError;
27
+ /**
28
+ * Checks if the runtime supports WebAuthn PRF extension.
29
+ *
30
+ * Note that using the extension also requires a supported authenticator.
31
+ * Whether an authenticator supports the extension can only be checked
32
+ * during the `create` process.
33
+ * @returns `true` if the runtime supports WebAuthn PRF extension
34
+ */
35
+ static async isSupported() {
36
+ if (typeof PublicKeyCredential === "undefined") {
37
+ return false;
38
+ }
39
+ if (!PublicKeyCredential.getClientCapabilities) {
40
+ return false;
41
+ }
42
+ const clientCapabilities = await PublicKeyCredential.getClientCapabilities();
43
+ if (!clientCapabilities["extension:prf"]) {
44
+ return false;
45
+ }
46
+ return true;
47
+ }
48
+ #appName;
49
+ #userName;
50
+ /**
51
+ * Creates a new instance of `TangoWebAuthnPrfSource`
52
+ *
53
+ * @param appName Name of your website shows in Passkey manager
54
+ * @param userName Display name of the credential shows in Passkey manager
55
+ */
56
+ constructor(appName, userName) {
57
+ this.#appName = appName;
58
+ this.#userName = userName;
59
+ }
60
+ /**
61
+ * Creates a new credential and generate PRF output using the credential and input data.
62
+ *
63
+ * @param input The input data
64
+ * @returns The credential ID and PRF output
65
+ * @throws `NotSupportedError` if the runtime or authenticator doesn't support PRF extension
66
+ * @throws `OperationCancelledError` if the attestation is either cancelled by user or timed out
67
+ */
68
+ async create(input) {
69
+ const challenge = new Uint8Array(32);
70
+ crypto.getRandomValues(challenge);
71
+ let attestation;
72
+ try {
73
+ attestation = await navigator.credentials.create({
74
+ publicKey: {
75
+ challenge,
76
+ extensions: { prf: { eval: { first: input } } },
77
+ pubKeyCredParams: [
78
+ { type: "public-key", alg: -7 },
79
+ { type: "public-key", alg: -257 },
80
+ ],
81
+ rp: { name: this.#appName },
82
+ user: {
83
+ id: challenge,
84
+ name: this.#userName,
85
+ displayName: this.#userName,
86
+ },
87
+ },
88
+ });
89
+ }
90
+ catch {
91
+ throw new OperationCancelledError();
92
+ }
93
+ checkCredential(attestation);
94
+ const prf = getPrfOutput(attestation);
95
+ if (prf.enabled === undefined) {
96
+ throw new NotSupportedError();
97
+ }
98
+ const id = new Uint8Array(attestation.rawId);
99
+ if (prf.results) {
100
+ return { output: prf.results.first, id };
101
+ }
102
+ // Some authenticators only support getting PRF in assertion
103
+ const output = await this.get(id, input);
104
+ return { output, id };
105
+ }
106
+ /**
107
+ * Generates PRF output using a credential and input data.
108
+ *
109
+ * @param id ID of a previously created credential
110
+ * @param input The input data
111
+ * @returns PRF output
112
+ * @throws `OperationCancelledError` if the attestation is either cancelled by user or timed out
113
+ */
114
+ async get(id, input) {
115
+ const challenge = new Uint8Array(32);
116
+ crypto.getRandomValues(challenge);
117
+ let assertion;
118
+ try {
119
+ assertion = await navigator.credentials.get({
120
+ publicKey: {
121
+ allowCredentials: [{ type: "public-key", id }],
122
+ challenge,
123
+ extensions: { prf: { eval: { first: input } } },
124
+ },
125
+ });
126
+ }
127
+ catch {
128
+ throw new OperationCancelledError();
129
+ }
130
+ checkCredential(assertion);
131
+ const prfOutput = getPrfOutput(assertion);
132
+ if (!prfOutput.results) {
133
+ throw new NotSupportedError();
134
+ }
135
+ return prfOutput.results.first;
136
+ }
137
+ }
138
+ //# sourceMappingURL=web-authn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-authn.js","sourceRoot":"","sources":["../../../src/storage/prf/web-authn.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { MaybeError } from "@yume-chan/adb";
2
+ import type { MaybePromiseLike } from "@yume-chan/async";
3
+ export interface TangoKey {
4
+ privateKey: Uint8Array;
5
+ name: string | undefined;
6
+ }
7
+ export interface TangoKeyStorage {
8
+ save(privateKey: Uint8Array, name: string | undefined): MaybePromiseLike<undefined>;
9
+ load(): Iterable<MaybeError<TangoKey>> | AsyncIterable<MaybeError<TangoKey>>;
10
+ }
11
+ //# sourceMappingURL=type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../src/storage/type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,MAAM,WAAW,QAAQ;IACrB,UAAU,EAAE,UAAU,CAAC;IACvB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC5B,IAAI,CACA,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,MAAM,GAAG,SAAS,GACzB,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAE/B,IAAI,IACA,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;CAC5E"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.js","sourceRoot":"","sources":["../../src/storage/type.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yume-chan/adb-credential-web",
3
- "version": "2.1.0",
3
+ "version": "3.0.0-beta.1",
4
4
  "description": "Credential Store for `@yume-chan/adb` using WebCrypto and IndexedDB APIs.",
5
5
  "keywords": [
6
6
  "adb"
@@ -25,16 +25,19 @@
25
25
  "types": "esm/index.d.ts",
26
26
  "sideEffects": false,
27
27
  "dependencies": {
28
- "@yume-chan/adb": "^2.1.0"
28
+ "@yume-chan/async": "^4.1.3",
29
+ "@yume-chan/adb": "3.0.0-beta.1",
30
+ "@yume-chan/stream-extra": "3.0.0-beta.1",
31
+ "@yume-chan/struct": "3.0.0-beta.1"
29
32
  },
30
33
  "devDependencies": {
31
- "prettier": "^3.5.3",
32
- "typescript": "^5.8.3",
33
- "@yume-chan/eslint-config": "^1.0.0",
34
- "@yume-chan/tsconfig": "^1.0.0"
34
+ "prettier": "^3.9.6",
35
+ "typescript": "^6.0.3",
36
+ "@yume-chan/tsconfig": "^1.0.0",
37
+ "@yume-chan/eslint-config": "^1.0.0"
35
38
  },
36
39
  "scripts": {
37
- "build": "tsc -b tsconfig.build.json",
40
+ "build": "tsc -p tsconfig.build.json",
38
41
  "lint": "run-eslint && prettier src/**/*.ts --write --tab-width 4"
39
42
  }
40
43
  }