@yume-chan/adb-credential-web 2.1.0 → 3.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +4 -1
- package/esm/index.d.ts +2 -23
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +2 -102
- package/esm/index.js.map +1 -1
- package/esm/manager.d.ts +9 -0
- package/esm/manager.d.ts.map +1 -0
- package/esm/manager.js +56 -0
- package/esm/manager.js.map +1 -0
- package/esm/storage/index.d.ts +6 -0
- package/esm/storage/index.d.ts.map +1 -0
- package/esm/storage/index.js +6 -0
- package/esm/storage/index.js.map +1 -0
- package/esm/storage/indexed-db/index.d.ts +3 -0
- package/esm/storage/indexed-db/index.d.ts.map +1 -0
- package/esm/storage/indexed-db/index.js +3 -0
- package/esm/storage/indexed-db/index.js.map +1 -0
- package/esm/storage/indexed-db/shared.d.ts +4 -0
- package/esm/storage/indexed-db/shared.d.ts.map +1 -0
- package/esm/storage/indexed-db/shared.js +50 -0
- package/esm/storage/indexed-db/shared.js.map +1 -0
- package/esm/storage/indexed-db/v1.d.ts +5 -0
- package/esm/storage/indexed-db/v1.d.ts.map +1 -0
- package/esm/storage/indexed-db/v1.js +22 -0
- package/esm/storage/indexed-db/v1.js.map +1 -0
- package/esm/storage/indexed-db/v2.d.ts +14 -0
- package/esm/storage/indexed-db/v2.d.ts.map +1 -0
- package/esm/storage/indexed-db/v2.js +78 -0
- package/esm/storage/indexed-db/v2.js.map +1 -0
- package/esm/storage/local-storage.d.ts +8 -0
- package/esm/storage/local-storage.d.ts.map +1 -0
- package/esm/storage/local-storage.js +25 -0
- package/esm/storage/local-storage.js.map +1 -0
- package/esm/storage/password.d.ts +27 -0
- package/esm/storage/password.d.ts.map +1 -0
- package/esm/storage/password.js +129 -0
- package/esm/storage/password.js.map +1 -0
- package/esm/storage/prf/index.d.ts +4 -0
- package/esm/storage/prf/index.d.ts.map +1 -0
- package/esm/storage/prf/index.js +4 -0
- package/esm/storage/prf/index.js.map +1 -0
- package/esm/storage/prf/source.d.ts +27 -0
- package/esm/storage/prf/source.d.ts.map +1 -0
- package/esm/storage/prf/source.js +2 -0
- package/esm/storage/prf/source.js.map +1 -0
- package/esm/storage/prf/storage.d.ts +19 -0
- package/esm/storage/prf/storage.d.ts.map +1 -0
- package/esm/storage/prf/storage.js +125 -0
- package/esm/storage/prf/storage.js.map +1 -0
- package/esm/storage/prf/web-authn.d.ts +55 -0
- package/esm/storage/prf/web-authn.d.ts.map +1 -0
- package/esm/storage/prf/web-authn.js +138 -0
- package/esm/storage/prf/web-authn.js.map +1 -0
- package/esm/storage/type.d.ts +11 -0
- package/esm/storage/type.d.ts.map +1 -0
- package/esm/storage/type.js +2 -0
- package/esm/storage/type.js.map +1 -0
- package/package.json +8 -5
- package/src/index.ts +2 -121
- package/src/manager.ts +82 -0
- package/src/storage/index.ts +5 -0
- package/src/storage/indexed-db/index.ts +2 -0
- package/src/storage/indexed-db/shared.ts +62 -0
- package/src/storage/indexed-db/v1.ts +29 -0
- package/src/storage/indexed-db/v2.ts +95 -0
- package/src/storage/local-storage.ts +37 -0
- package/src/storage/password.ts +245 -0
- package/src/storage/prf/index.ts +3 -0
- package/src/storage/prf/source.ts +35 -0
- package/src/storage/prf/storage.ts +191 -0
- package/src/storage/prf/web-authn.ts +175 -0
- package/src/storage/type.ts +18 -0
- package/CHANGELOG.md +0 -149
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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,IACE,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,GAC9B,aAAa,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;CAC7C"}
|
|
@@ -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": "
|
|
3
|
+
"version": "3.0.0-beta.0",
|
|
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/
|
|
28
|
+
"@yume-chan/async": "^4.1.3",
|
|
29
|
+
"@yume-chan/adb": "3.0.0-beta.0",
|
|
30
|
+
"@yume-chan/stream-extra": "3.0.0-beta.0",
|
|
31
|
+
"@yume-chan/struct": "3.0.0-beta.0"
|
|
29
32
|
},
|
|
30
33
|
"devDependencies": {
|
|
31
|
-
"prettier": "^3.
|
|
32
|
-
"typescript": "^
|
|
34
|
+
"prettier": "^3.8.4",
|
|
35
|
+
"typescript": "^6.0.3",
|
|
33
36
|
"@yume-chan/eslint-config": "^1.0.0",
|
|
34
37
|
"@yume-chan/tsconfig": "^1.0.0"
|
|
35
38
|
},
|
|
36
39
|
"scripts": {
|
|
37
|
-
"build": "tsc -
|
|
40
|
+
"build": "tsc -p tsconfig.build.json",
|
|
38
41
|
"lint": "run-eslint && prettier src/**/*.ts --write --tab-width 4"
|
|
39
42
|
}
|
|
40
43
|
}
|