@keyneom/sync-kit 0.1.0 → 0.2.0-rc.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/README.md +137 -11
- package/dist/auth/google-web/identity.d.ts +49 -0
- package/dist/auth/google-web/identity.d.ts.map +1 -0
- package/dist/auth/google-web/identity.js +102 -0
- package/dist/auth/google-web/identity.js.map +1 -0
- package/dist/auth/google-web/index.d.ts +1 -0
- package/dist/auth/google-web/index.d.ts.map +1 -1
- package/dist/auth/google-web/index.js +1 -0
- package/dist/auth/google-web/index.js.map +1 -1
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +22 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/crypto/canonical.d.ts +5 -0
- package/dist/crypto/canonical.d.ts.map +1 -1
- package/dist/crypto/canonical.js +8 -1
- package/dist/crypto/canonical.js.map +1 -1
- package/dist/crypto/envelope.d.ts.map +1 -1
- package/dist/crypto/envelope.js +1 -0
- package/dist/crypto/envelope.js.map +1 -1
- package/dist/crypto/profiles.d.ts +6 -46
- package/dist/crypto/profiles.d.ts.map +1 -1
- package/dist/crypto/profiles.js +33 -46
- package/dist/crypto/profiles.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/keys/web-passkey/index.d.ts +8 -2
- package/dist/keys/web-passkey/index.d.ts.map +1 -1
- package/dist/keys/web-passkey/index.js +32 -10
- package/dist/keys/web-passkey/index.js.map +1 -1
- package/dist/sharing/account-binding.d.ts +57 -0
- package/dist/sharing/account-binding.d.ts.map +1 -0
- package/dist/sharing/account-binding.js +305 -0
- package/dist/sharing/account-binding.js.map +1 -0
- package/dist/sharing/controller.d.ts +139 -0
- package/dist/sharing/controller.d.ts.map +1 -0
- package/dist/sharing/controller.js +549 -0
- package/dist/sharing/controller.js.map +1 -0
- package/dist/sharing/index.d.ts +119 -0
- package/dist/sharing/index.d.ts.map +1 -0
- package/dist/sharing/index.js +360 -0
- package/dist/sharing/index.js.map +1 -0
- package/dist/sharing/transport.d.ts +65 -0
- package/dist/sharing/transport.d.ts.map +1 -0
- package/dist/sharing/transport.js +2 -0
- package/dist/sharing/transport.js.map +1 -0
- package/dist/sharing/web-crypto.d.ts +68 -0
- package/dist/sharing/web-crypto.d.ts.map +1 -0
- package/dist/sharing/web-crypto.js +645 -0
- package/dist/sharing/web-crypto.js.map +1 -0
- package/dist/sharing/web-passkey.d.ts +68 -0
- package/dist/sharing/web-passkey.d.ts.map +1 -0
- package/dist/sharing/web-passkey.js +303 -0
- package/dist/sharing/web-passkey.js.map +1 -0
- package/dist/snapshot/controller.d.ts.map +1 -1
- package/dist/snapshot/controller.js +4 -8
- package/dist/snapshot/controller.js.map +1 -1
- package/dist/stores/google-drive/index.d.ts +147 -1
- package/dist/stores/google-drive/index.d.ts.map +1 -1
- package/dist/stores/google-drive/index.js +397 -0
- package/dist/stores/google-drive/index.js.map +1 -1
- package/dist/stores/google-drive/picker.d.ts +89 -0
- package/dist/stores/google-drive/picker.d.ts.map +1 -0
- package/dist/stores/google-drive/picker.js +179 -0
- package/dist/stores/google-drive/picker.js.map +1 -0
- package/dist/stores/google-drive/sharing.d.ts +55 -0
- package/dist/stores/google-drive/sharing.d.ts.map +1 -0
- package/dist/stores/google-drive/sharing.js +295 -0
- package/dist/stores/google-drive/sharing.js.map +1 -0
- package/package.json +67 -12
- package/fixtures/v1/easybc-web-android-gzip.json +0 -41
- package/fixtures/v1/easybc-web-uncompressed.json +0 -35
- package/fixtures/v1/failures.json +0 -42
- package/fixtures/v1/family-chores-web-uncompressed.json +0 -73
- package/fixtures/v1/profiles.json +0 -51
package/dist/crypto/profiles.js
CHANGED
|
@@ -1,48 +1,35 @@
|
|
|
1
1
|
export const V1_ALGORITHM = "AES-256-GCM+HKDF-SHA-256";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
kdfSaltBytes: 32,
|
|
36
|
-
prfInputBytes: 32,
|
|
37
|
-
tagBits: 128,
|
|
38
|
-
passkey: {
|
|
39
|
-
rpName: "Family Chores",
|
|
40
|
-
userName: "encrypted-sync",
|
|
41
|
-
userDisplayName: "Family Chores encrypted sync",
|
|
42
|
-
algorithm: -7,
|
|
43
|
-
residentKey: "required",
|
|
44
|
-
userVerification: "required",
|
|
45
|
-
timeoutMs: 60_000,
|
|
46
|
-
},
|
|
47
|
-
};
|
|
2
|
+
/**
|
|
3
|
+
* Creates a consumer-owned v1 profile while fixing protocol-level constants.
|
|
4
|
+
* Application profiles are configuration, not package presets.
|
|
5
|
+
*/
|
|
6
|
+
export function defineV1CompatibilityProfile(input) {
|
|
7
|
+
for (const [name, value] of Object.entries({
|
|
8
|
+
appId: input.appId,
|
|
9
|
+
filename: input.filename,
|
|
10
|
+
aad: input.aad,
|
|
11
|
+
hkdfInfo: input.hkdfInfo,
|
|
12
|
+
rpName: input.passkey.rpName,
|
|
13
|
+
userName: input.passkey.userName,
|
|
14
|
+
userDisplayName: input.passkey.userDisplayName,
|
|
15
|
+
})) {
|
|
16
|
+
if (!value.trim())
|
|
17
|
+
throw new TypeError(`${name} must not be empty.`);
|
|
18
|
+
}
|
|
19
|
+
if (!Number.isFinite(input.passkey.timeoutMs) ||
|
|
20
|
+
input.passkey.timeoutMs <= 0) {
|
|
21
|
+
throw new TypeError("passkey.timeoutMs must be positive.");
|
|
22
|
+
}
|
|
23
|
+
return Object.freeze({
|
|
24
|
+
...input,
|
|
25
|
+
passkey: Object.freeze({ ...input.passkey }),
|
|
26
|
+
algorithm: V1_ALGORITHM,
|
|
27
|
+
readVersions: [1],
|
|
28
|
+
writeVersion: 1,
|
|
29
|
+
nonceBytes: 12,
|
|
30
|
+
kdfSaltBytes: 32,
|
|
31
|
+
prfInputBytes: 32,
|
|
32
|
+
tagBits: 128,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
48
35
|
//# sourceMappingURL=profiles.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profiles.js","sourceRoot":"","sources":["../../src/crypto/profiles.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,0BAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"profiles.js","sourceRoot":"","sources":["../../src/crypto/profiles.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,0BAAmC,CAAC;AAmChE;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAC1C,KAAkC;IAElC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC;QACzC,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM;QAC5B,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ;QAChC,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,eAAe;KAC/C,CAAC,EAAE,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;YAAE,MAAM,IAAI,SAAS,CAAC,GAAG,IAAI,qBAAqB,CAAC,CAAC;IACvE,CAAC;IACD,IACE,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;QACzC,KAAK,CAAC,OAAO,CAAC,SAAS,IAAI,CAAC,EAC5B,CAAC;QACD,MAAM,IAAI,SAAS,CAAC,qCAAqC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,GAAG,KAAK;QACR,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;QAC5C,SAAS,EAAE,YAAY;QACvB,YAAY,EAAE,CAAC,CAAC,CAAU;QAC1B,YAAY,EAAE,CAAC;QACf,UAAU,EAAE,EAAE;QACd,YAAY,EAAE,EAAE;QAChB,aAAa,EAAE,EAAE;QACjB,OAAO,EAAE,GAAG;KACb,CAAC,CAAC;AACL,CAAC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC"}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { KeyProvider } from "../../core/types.js";
|
|
2
2
|
import { type CryptoBackend, type SyncEnvelopeV1, type V1CompatibilityProfile, type V1KeyMetadata } from "../../crypto/index.js";
|
|
3
|
+
export type WebPasskeyKeyMetadata = V1KeyMetadata & {
|
|
4
|
+
credentialPublicKey?: JsonWebKey;
|
|
5
|
+
};
|
|
3
6
|
export type WebPasskeyProviderOptions<K> = {
|
|
4
7
|
rpId: string;
|
|
5
8
|
deriveKey: (secret: Uint8Array, salt: Uint8Array) => Promise<K>;
|
|
6
|
-
crypto?: Pick<Crypto, "getRandomValues"
|
|
9
|
+
crypto?: Pick<Crypto, "getRandomValues"> & {
|
|
10
|
+
subtle?: SubtleCrypto;
|
|
11
|
+
};
|
|
7
12
|
navigator?: Navigator;
|
|
8
13
|
secureContext?: () => boolean;
|
|
9
14
|
};
|
|
@@ -14,10 +19,11 @@ export declare class WebPasskeyProvider<K> implements KeyProvider<SyncEnvelopeV1
|
|
|
14
19
|
private pending;
|
|
15
20
|
constructor(profile: V1CompatibilityProfile, options: WebPasskeyProviderOptions<K>);
|
|
16
21
|
create(): Promise<{
|
|
17
|
-
metadata:
|
|
22
|
+
metadata: WebPasskeyKeyMetadata;
|
|
18
23
|
key: K;
|
|
19
24
|
}>;
|
|
20
25
|
unlock(envelope: SyncEnvelopeV1): Promise<K>;
|
|
26
|
+
unlockMetadata(metadata: V1KeyMetadata): Promise<K>;
|
|
21
27
|
clear(): void;
|
|
22
28
|
supported(): boolean;
|
|
23
29
|
private unlockNow;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/keys/web-passkey/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EACnB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/keys/web-passkey/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EACnB,MAAM,uBAAuB,CAAC;AAgB/B,MAAM,MAAM,qBAAqB,GAAG,aAAa,GAAG;IAClD,mBAAmB,CAAC,EAAE,UAAU,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAChE,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAAG;QACzC,MAAM,CAAC,EAAE,YAAY,CAAC;KACvB,CAAC;IACF,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC;CAC/B,CAAC;AAEF,qBAAa,kBAAkB,CAAC,CAAC,CAC/B,YAAW,WAAW,CAAC,cAAc,EAAE,CAAC,EAAE,aAAa,CAAC;IAMtD,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,MAAM,CAA6C;IAC3D,OAAO,CAAC,OAAO,CAA0D;gBAGtD,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,yBAAyB,CAAC,CAAC,CAAC;IAGlD,MAAM,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,qBAAqB,CAAC;QAAC,GAAG,EAAE,CAAC,CAAA;KAAE,CAAC;IA2D9D,MAAM,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAS5C,cAAc,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,CAAC,CAAC;IAqBzD,KAAK,IAAI,IAAI;IAKb,SAAS,IAAI,OAAO;YASN,SAAS;YAkBT,WAAW;YA6BX,aAAa;IAW3B,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,MAAM;IAUd,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,YAAY;CAMrB;AA8BD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,IAAI,CAAC,yBAAyB,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC,GAAG;IACjE,OAAO,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CACpC,GACA,kBAAkB,CAAC,SAAS,CAAC,CAO/B"}
|
|
@@ -45,27 +45,37 @@ export class WebPasskeyProvider {
|
|
|
45
45
|
const secret = returnedSecret ??
|
|
46
46
|
(await this.evaluatePrf(credentialId, prfInput, this.options.rpId));
|
|
47
47
|
const key = await this.deriveAndZero(secret, kdfSalt);
|
|
48
|
+
const credentialPublicKey = await exportCredentialPublicKey(credential, cryptoImplementation.subtle);
|
|
48
49
|
const metadata = {
|
|
49
50
|
credentialId: bytesToBase64Url(credentialId),
|
|
50
51
|
rpId: this.options.rpId,
|
|
51
52
|
prfInput,
|
|
52
53
|
kdfSalt,
|
|
54
|
+
...(credentialPublicKey ? { credentialPublicKey } : {}),
|
|
53
55
|
};
|
|
54
56
|
this.cached = { identity: metadataIdentity(metadata), key };
|
|
55
57
|
return { metadata, key };
|
|
56
58
|
}
|
|
57
59
|
async unlock(envelope) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
return this.unlockMetadata({
|
|
61
|
+
credentialId: envelope.credentialId,
|
|
62
|
+
rpId: envelope.rpId,
|
|
63
|
+
prfInput: base64UrlToBytes(envelope.prfInput),
|
|
64
|
+
kdfSalt: base64UrlToBytes(envelope.kdfSalt),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async unlockMetadata(metadata) {
|
|
68
|
+
if (metadata.rpId !== this.options.rpId) {
|
|
69
|
+
throw new SyncKitError("compatibility", `The protected key belongs to ${metadata.rpId}, not ${this.options.rpId}.`);
|
|
60
70
|
}
|
|
61
|
-
const identity =
|
|
71
|
+
const identity = metadataIdentity(metadata);
|
|
62
72
|
if (this.cached?.identity === identity)
|
|
63
73
|
return this.cached.key;
|
|
64
74
|
if (this.cached)
|
|
65
75
|
this.clear();
|
|
66
76
|
if (this.pending?.identity === identity)
|
|
67
77
|
return this.pending.promise;
|
|
68
|
-
const promise = this.unlockNow(
|
|
78
|
+
const promise = this.unlockNow(metadata, identity);
|
|
69
79
|
this.pending = { identity, promise };
|
|
70
80
|
try {
|
|
71
81
|
return await promise;
|
|
@@ -85,10 +95,10 @@ export class WebPasskeyProvider {
|
|
|
85
95
|
(typeof window !== "undefined" && window.isSecureContext)) &&
|
|
86
96
|
Boolean(navigatorImplementation?.credentials));
|
|
87
97
|
}
|
|
88
|
-
async unlockNow(
|
|
98
|
+
async unlockNow(metadata, identity) {
|
|
89
99
|
this.assertSupported();
|
|
90
|
-
const secret = await this.evaluatePrf(base64UrlToBytes(
|
|
91
|
-
const key = await this.deriveAndZero(secret,
|
|
100
|
+
const secret = await this.evaluatePrf(base64UrlToBytes(metadata.credentialId), metadata.prfInput, metadata.rpId);
|
|
101
|
+
const key = await this.deriveAndZero(secret, metadata.kdfSalt);
|
|
92
102
|
this.cached = { identity, key };
|
|
93
103
|
return key;
|
|
94
104
|
}
|
|
@@ -143,6 +153,20 @@ export class WebPasskeyProvider {
|
|
|
143
153
|
(typeof navigator === "undefined" ? undefined : navigator));
|
|
144
154
|
}
|
|
145
155
|
}
|
|
156
|
+
async function exportCredentialPublicKey(credential, subtle) {
|
|
157
|
+
if (!subtle || !credential.response)
|
|
158
|
+
return undefined;
|
|
159
|
+
const response = credential.response;
|
|
160
|
+
if (response.getPublicKeyAlgorithm?.() !== -7 ||
|
|
161
|
+
typeof response.getPublicKey !== "function") {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
const spki = response.getPublicKey();
|
|
165
|
+
if (!spki)
|
|
166
|
+
return undefined;
|
|
167
|
+
const key = await subtle.importKey("spki", spki, { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]);
|
|
168
|
+
return subtle.exportKey("jwk", key);
|
|
169
|
+
}
|
|
146
170
|
export function createWebPasskeyProvider(profile, options) {
|
|
147
171
|
const backend = options.backend ?? createWebCryptoBackend();
|
|
148
172
|
return new WebPasskeyProvider(profile, {
|
|
@@ -173,13 +197,11 @@ function prfExtensions(prfInput) {
|
|
|
173
197
|
function randomBytes(length, cryptoImplementation) {
|
|
174
198
|
return cryptoImplementation.getRandomValues(new Uint8Array(length));
|
|
175
199
|
}
|
|
176
|
-
function envelopeIdentity(envelope) {
|
|
177
|
-
return [envelope.rpId, envelope.credentialId, envelope.kdfSalt].join("\n");
|
|
178
|
-
}
|
|
179
200
|
function metadataIdentity(metadata) {
|
|
180
201
|
return [
|
|
181
202
|
metadata.rpId,
|
|
182
203
|
metadata.credentialId,
|
|
204
|
+
bytesToBase64Url(metadata.prfInput),
|
|
183
205
|
bytesToBase64Url(metadata.kdfSalt),
|
|
184
206
|
].join("\n");
|
|
185
207
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/keys/web-passkey/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,GAKjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/keys/web-passkey/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { SharingAccountBindingV1, SharingPasskeyAssertionV1 } from "./index.js";
|
|
2
|
+
export type SharingAccountBindingContext = {
|
|
3
|
+
appId: string;
|
|
4
|
+
exchangeId: string;
|
|
5
|
+
sharingKeyId: string;
|
|
6
|
+
credentialId: string;
|
|
7
|
+
};
|
|
8
|
+
export type VerifiedGoogleAccount = {
|
|
9
|
+
subject: string;
|
|
10
|
+
audience: string;
|
|
11
|
+
email?: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function createBackendlessSharingAccountBinding(context: Omit<SharingAccountBindingContext, "credentialId">, options: {
|
|
14
|
+
credential(): Promise<{
|
|
15
|
+
credentialId: string;
|
|
16
|
+
credentialPublicKey: JsonWebKey;
|
|
17
|
+
}>;
|
|
18
|
+
requestGoogleIdToken(nonce: string): Promise<string>;
|
|
19
|
+
rpId: string;
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
navigator?: Navigator;
|
|
22
|
+
crypto?: Crypto;
|
|
23
|
+
}): Promise<SharingAccountBindingV1>;
|
|
24
|
+
export declare function createSharingAccountBindingChallenge(context: SharingAccountBindingContext, cryptoImplementation?: Crypto): Promise<string>;
|
|
25
|
+
export declare function createSharingAccountBindingV1(challenge: string, googleIdToken: string, passkey: SharingPasskeyAssertionV1): SharingAccountBindingV1;
|
|
26
|
+
export declare function verifySharingAccountBindingV1(binding: SharingAccountBindingV1, context: SharingAccountBindingContext, options: {
|
|
27
|
+
googleAudience: string;
|
|
28
|
+
rpId: string;
|
|
29
|
+
allowedOrigins: string[];
|
|
30
|
+
requireUserVerification?: boolean;
|
|
31
|
+
fetch?: typeof fetch;
|
|
32
|
+
crypto?: Crypto;
|
|
33
|
+
now?: () => number;
|
|
34
|
+
}): Promise<VerifiedGoogleAccount>;
|
|
35
|
+
export declare function getSharingPasskeyAssertion(input: {
|
|
36
|
+
challenge: string;
|
|
37
|
+
rpId: string;
|
|
38
|
+
credentialId: string;
|
|
39
|
+
credentialPublicKey: JsonWebKey;
|
|
40
|
+
timeoutMs?: number;
|
|
41
|
+
navigator?: Navigator;
|
|
42
|
+
}): Promise<SharingPasskeyAssertionV1>;
|
|
43
|
+
export declare function verifyPasskeyAssertion(assertion: SharingPasskeyAssertionV1, options: {
|
|
44
|
+
challenge: string;
|
|
45
|
+
rpId: string;
|
|
46
|
+
allowedOrigins: string[];
|
|
47
|
+
requireUserVerification?: boolean;
|
|
48
|
+
crypto?: Crypto;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
export declare function verifyGoogleIdToken(token: string, options: {
|
|
51
|
+
audience: string;
|
|
52
|
+
nonce: string;
|
|
53
|
+
fetch?: typeof fetch;
|
|
54
|
+
crypto?: Crypto;
|
|
55
|
+
now?: () => number;
|
|
56
|
+
}): Promise<VerifiedGoogleAccount>;
|
|
57
|
+
//# sourceMappingURL=account-binding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-binding.d.ts","sourceRoot":"","sources":["../../src/sharing/account-binding.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,uBAAuB,EACvB,yBAAyB,EAC1B,MAAM,YAAY,CAAC;AAMpB,MAAM,MAAM,4BAA4B,GAAG;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,sCAAsC,CAC1D,OAAO,EAAE,IAAI,CAAC,4BAA4B,EAAE,cAAc,CAAC,EAC3D,OAAO,EAAE;IACP,UAAU,IAAI,OAAO,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,mBAAmB,EAAE,UAAU,CAAC;KACjC,CAAC,CAAC;IACH,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACA,OAAO,CAAC,uBAAuB,CAAC,CAuBlC;AAED,wBAAsB,oCAAoC,CACxD,OAAO,EAAE,4BAA4B,EACrC,oBAAoB,GAAE,MAA0B,GAC/C,OAAO,CAAC,MAAM,CAAC,CAYjB;AAED,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,yBAAyB,GACjC,uBAAuB,CAczB;AAED,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,uBAAuB,EAChC,OAAO,EAAE,4BAA4B,EACrC,OAAO,EAAE;IACP,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,GACA,OAAO,CAAC,qBAAqB,CAAC,CA6BhC;AAED,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE;IACL,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,UAAU,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB,GACA,OAAO,CAAC,yBAAyB,CAAC,CAkDpC;AAED,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,yBAAyB,EACpC,OAAO,EAAE;IACP,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACA,OAAO,CAAC,IAAI,CAAC,CAoHf;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,GACA,OAAO,CAAC,qBAAqB,CAAC,CA4GhC"}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { SyncKitError } from "../core/errors.js";
|
|
2
|
+
import { base64UrlToBytes, bytesToBase64Url } from "../crypto/base64url.js";
|
|
3
|
+
import { canonicalAad } from "../crypto/canonical.js";
|
|
4
|
+
import { copyBuffer } from "../crypto/runtime.js";
|
|
5
|
+
const GOOGLE_CERTS = "https://www.googleapis.com/oauth2/v3/certs";
|
|
6
|
+
const encoder = new TextEncoder();
|
|
7
|
+
const decoder = new TextDecoder();
|
|
8
|
+
export async function createBackendlessSharingAccountBinding(context, options) {
|
|
9
|
+
const credential = await options.credential();
|
|
10
|
+
const challenge = await createSharingAccountBindingChallenge({
|
|
11
|
+
...context,
|
|
12
|
+
credentialId: credential.credentialId,
|
|
13
|
+
}, options.crypto ?? globalThis.crypto);
|
|
14
|
+
const passkey = await getSharingPasskeyAssertion({
|
|
15
|
+
challenge,
|
|
16
|
+
rpId: options.rpId,
|
|
17
|
+
credentialId: credential.credentialId,
|
|
18
|
+
credentialPublicKey: credential.credentialPublicKey,
|
|
19
|
+
...(options.timeoutMs ? { timeoutMs: options.timeoutMs } : {}),
|
|
20
|
+
...(options.navigator ? { navigator: options.navigator } : {}),
|
|
21
|
+
});
|
|
22
|
+
const googleIdToken = await options.requestGoogleIdToken(challenge);
|
|
23
|
+
return createSharingAccountBindingV1(challenge, googleIdToken, passkey);
|
|
24
|
+
}
|
|
25
|
+
export async function createSharingAccountBindingChallenge(context, cryptoImplementation = globalThis.crypto) {
|
|
26
|
+
for (const [name, value] of Object.entries(context)) {
|
|
27
|
+
if (!value.trim())
|
|
28
|
+
throw new TypeError(`${name} must not be empty.`);
|
|
29
|
+
}
|
|
30
|
+
return bytesToBase64Url(new Uint8Array(await cryptoImplementation.subtle.digest("SHA-256", copyBuffer(canonicalAad(context)))));
|
|
31
|
+
}
|
|
32
|
+
export function createSharingAccountBindingV1(challenge, googleIdToken, passkey) {
|
|
33
|
+
if (base64UrlToBytes(challenge).length !== 32) {
|
|
34
|
+
throw new TypeError("challenge must be a SHA-256 value.");
|
|
35
|
+
}
|
|
36
|
+
if (!googleIdToken.trim()) {
|
|
37
|
+
throw new TypeError("googleIdToken must not be empty.");
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
schemaVersion: 1,
|
|
41
|
+
kind: "sync-kit-sharing-account-binding",
|
|
42
|
+
challenge,
|
|
43
|
+
googleIdToken,
|
|
44
|
+
passkey,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export async function verifySharingAccountBindingV1(binding, context, options) {
|
|
48
|
+
const cryptoImplementation = options.crypto ?? globalThis.crypto;
|
|
49
|
+
const expectedChallenge = await createSharingAccountBindingChallenge(context, cryptoImplementation);
|
|
50
|
+
if (binding.challenge !== expectedChallenge ||
|
|
51
|
+
binding.passkey.credentialId !== context.credentialId) {
|
|
52
|
+
throw new SyncKitError("authorization", "The account binding does not match this exchange and sharing key.");
|
|
53
|
+
}
|
|
54
|
+
await verifyPasskeyAssertion(binding.passkey, {
|
|
55
|
+
challenge: expectedChallenge,
|
|
56
|
+
rpId: options.rpId,
|
|
57
|
+
allowedOrigins: options.allowedOrigins,
|
|
58
|
+
requireUserVerification: options.requireUserVerification ?? true,
|
|
59
|
+
crypto: cryptoImplementation,
|
|
60
|
+
});
|
|
61
|
+
return verifyGoogleIdToken(binding.googleIdToken, {
|
|
62
|
+
audience: options.googleAudience,
|
|
63
|
+
nonce: expectedChallenge,
|
|
64
|
+
...(options.fetch ? { fetch: options.fetch } : {}),
|
|
65
|
+
crypto: cryptoImplementation,
|
|
66
|
+
...(options.now ? { now: options.now } : {}),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export async function getSharingPasskeyAssertion(input) {
|
|
70
|
+
const navigatorImplementation = input.navigator ??
|
|
71
|
+
(typeof navigator === "undefined" ? undefined : navigator);
|
|
72
|
+
if (!navigatorImplementation?.credentials) {
|
|
73
|
+
throw new SyncKitError("configuration", "WebAuthn credentials are unavailable.");
|
|
74
|
+
}
|
|
75
|
+
const credential = await navigatorImplementation.credentials.get({
|
|
76
|
+
publicKey: {
|
|
77
|
+
challenge: copyBuffer(base64UrlToBytes(input.challenge)),
|
|
78
|
+
rpId: input.rpId,
|
|
79
|
+
allowCredentials: [
|
|
80
|
+
{
|
|
81
|
+
type: "public-key",
|
|
82
|
+
id: copyBuffer(base64UrlToBytes(input.credentialId)),
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
userVerification: "required",
|
|
86
|
+
timeout: input.timeoutMs ?? 60_000,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
if (!credential ||
|
|
90
|
+
!("rawId" in credential) ||
|
|
91
|
+
!("response" in credential)) {
|
|
92
|
+
throw new SyncKitError("key", "The passkey assertion was cancelled.");
|
|
93
|
+
}
|
|
94
|
+
const publicKeyCredential = credential;
|
|
95
|
+
const response = publicKeyCredential.response;
|
|
96
|
+
return {
|
|
97
|
+
credentialId: bytesToBase64Url(new Uint8Array(publicKeyCredential.rawId)),
|
|
98
|
+
credentialPublicKey: input.credentialPublicKey,
|
|
99
|
+
authenticatorData: bytesToBase64Url(new Uint8Array(response.authenticatorData)),
|
|
100
|
+
clientDataJSON: bytesToBase64Url(new Uint8Array(response.clientDataJSON)),
|
|
101
|
+
signature: bytesToBase64Url(new Uint8Array(response.signature)),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export async function verifyPasskeyAssertion(assertion, options) {
|
|
105
|
+
const cryptoImplementation = options.crypto ?? globalThis.crypto;
|
|
106
|
+
const clientDataBytes = base64UrlToBytes(assertion.clientDataJSON);
|
|
107
|
+
let clientData;
|
|
108
|
+
try {
|
|
109
|
+
clientData = JSON.parse(decoder.decode(clientDataBytes));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
throw new SyncKitError("compatibility", "The WebAuthn client data is invalid.", { cause: error });
|
|
113
|
+
}
|
|
114
|
+
if (!clientData ||
|
|
115
|
+
typeof clientData !== "object" ||
|
|
116
|
+
Array.isArray(clientData)) {
|
|
117
|
+
throw new SyncKitError("compatibility", "The WebAuthn client data is malformed.");
|
|
118
|
+
}
|
|
119
|
+
const client = clientData;
|
|
120
|
+
if (client.type !== "webauthn.get" ||
|
|
121
|
+
client.challenge !== options.challenge ||
|
|
122
|
+
typeof client.origin !== "string" ||
|
|
123
|
+
!options.allowedOrigins.includes(client.origin)) {
|
|
124
|
+
throw new SyncKitError("authorization", "The WebAuthn assertion context is invalid.");
|
|
125
|
+
}
|
|
126
|
+
const authenticatorData = base64UrlToBytes(assertion.authenticatorData);
|
|
127
|
+
if (authenticatorData.length < 37) {
|
|
128
|
+
throw new SyncKitError("compatibility", "The WebAuthn authenticator data is truncated.");
|
|
129
|
+
}
|
|
130
|
+
const expectedRpHash = new Uint8Array(await cryptoImplementation.subtle.digest("SHA-256", copyBuffer(encoder.encode(options.rpId))));
|
|
131
|
+
if (!equalBytes(authenticatorData.slice(0, 32), expectedRpHash)) {
|
|
132
|
+
throw new SyncKitError("authorization", "The WebAuthn assertion belongs to another relying party.");
|
|
133
|
+
}
|
|
134
|
+
const flags = authenticatorData[32] ?? 0;
|
|
135
|
+
if ((flags & 0x01) === 0) {
|
|
136
|
+
throw new SyncKitError("authorization", "The WebAuthn assertion lacks user presence.");
|
|
137
|
+
}
|
|
138
|
+
if ((options.requireUserVerification ?? true) && (flags & 0x04) === 0) {
|
|
139
|
+
throw new SyncKitError("authorization", "The WebAuthn assertion lacks user verification.");
|
|
140
|
+
}
|
|
141
|
+
const clientDataHash = new Uint8Array(await cryptoImplementation.subtle.digest("SHA-256", copyBuffer(clientDataBytes)));
|
|
142
|
+
const signed = new Uint8Array(authenticatorData.length + clientDataHash.length);
|
|
143
|
+
signed.set(authenticatorData);
|
|
144
|
+
signed.set(clientDataHash, authenticatorData.length);
|
|
145
|
+
const publicKey = assertion.credentialPublicKey;
|
|
146
|
+
if (publicKey.kty !== "EC" ||
|
|
147
|
+
publicKey.crv !== "P-256" ||
|
|
148
|
+
!publicKey.x ||
|
|
149
|
+
!publicKey.y) {
|
|
150
|
+
throw new SyncKitError("compatibility", "The passkey public key must be an ES256 P-256 JWK.");
|
|
151
|
+
}
|
|
152
|
+
const imported = await cryptoImplementation.subtle.importKey("jwk", {
|
|
153
|
+
kty: "EC",
|
|
154
|
+
crv: "P-256",
|
|
155
|
+
x: publicKey.x,
|
|
156
|
+
y: publicKey.y,
|
|
157
|
+
ext: true,
|
|
158
|
+
key_ops: ["verify"],
|
|
159
|
+
}, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
|
|
160
|
+
const valid = await cryptoImplementation.subtle.verify({ name: "ECDSA", hash: "SHA-256" }, imported, copyBuffer(derEcdsaToP1363(base64UrlToBytes(assertion.signature), 32)), copyBuffer(signed));
|
|
161
|
+
if (!valid) {
|
|
162
|
+
throw new SyncKitError("authorization", "The WebAuthn assertion signature is invalid.");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
export async function verifyGoogleIdToken(token, options) {
|
|
166
|
+
const cryptoImplementation = options.crypto ?? globalThis.crypto;
|
|
167
|
+
const parts = token.split(".");
|
|
168
|
+
if (parts.length !== 3) {
|
|
169
|
+
throw new SyncKitError("compatibility", "The Google ID token is not a JWT.");
|
|
170
|
+
}
|
|
171
|
+
const [encodedHeader, encodedPayload, encodedSignature] = parts;
|
|
172
|
+
if (!encodedHeader || !encodedPayload || !encodedSignature) {
|
|
173
|
+
throw new SyncKitError("compatibility", "The Google ID token is incomplete.");
|
|
174
|
+
}
|
|
175
|
+
const header = parseJwtObject(encodedHeader, "header");
|
|
176
|
+
const claims = parseJwtObject(encodedPayload, "claims");
|
|
177
|
+
if (header.alg !== "RS256" ||
|
|
178
|
+
typeof header.kid !== "string" ||
|
|
179
|
+
!header.kid) {
|
|
180
|
+
throw new SyncKitError("compatibility", "The Google ID token algorithm is unsupported.");
|
|
181
|
+
}
|
|
182
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
183
|
+
if (!fetchImplementation) {
|
|
184
|
+
throw new SyncKitError("configuration", "Fetch is required to verify Google identity tokens.");
|
|
185
|
+
}
|
|
186
|
+
const response = await fetchImplementation(GOOGLE_CERTS);
|
|
187
|
+
if (!response.ok) {
|
|
188
|
+
throw new SyncKitError("provider", `Google signing keys could not be loaded (${response.status}).`, { status: response.status });
|
|
189
|
+
}
|
|
190
|
+
const keySet = (await response.json());
|
|
191
|
+
const jwk = keySet.keys?.find((key) => key.kid === header.kid);
|
|
192
|
+
if (!jwk) {
|
|
193
|
+
throw new SyncKitError("authorization", "The Google ID token signing key is unknown.");
|
|
194
|
+
}
|
|
195
|
+
const key = await cryptoImplementation.subtle.importKey("jwk", { ...jwk, key_ops: ["verify"], ext: true }, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["verify"]);
|
|
196
|
+
const valid = await cryptoImplementation.subtle.verify("RSASSA-PKCS1-v1_5", key, copyBuffer(base64UrlToBytes(encodedSignature)), copyBuffer(encoder.encode(`${encodedHeader}.${encodedPayload}`)));
|
|
197
|
+
if (!valid) {
|
|
198
|
+
throw new SyncKitError("authorization", "The Google ID token signature is invalid.");
|
|
199
|
+
}
|
|
200
|
+
const nowSeconds = Math.floor((options.now?.() ?? Date.now()) / 1_000);
|
|
201
|
+
const audiences = Array.isArray(claims.aud)
|
|
202
|
+
? claims.aud
|
|
203
|
+
: [claims.aud];
|
|
204
|
+
if ((claims.iss !== "https://accounts.google.com" &&
|
|
205
|
+
claims.iss !== "accounts.google.com") ||
|
|
206
|
+
!audiences.includes(options.audience) ||
|
|
207
|
+
typeof claims.exp !== "number" ||
|
|
208
|
+
claims.exp <= nowSeconds ||
|
|
209
|
+
claims.nonce !== options.nonce ||
|
|
210
|
+
typeof claims.sub !== "string" ||
|
|
211
|
+
!claims.sub) {
|
|
212
|
+
throw new SyncKitError("authorization", "The Google ID token claims do not match this account binding.");
|
|
213
|
+
}
|
|
214
|
+
if (claims.email !== undefined &&
|
|
215
|
+
(typeof claims.email !== "string" ||
|
|
216
|
+
claims.email_verified !== true)) {
|
|
217
|
+
throw new SyncKitError("authorization", "The Google ID token email is not verified.");
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
subject: claims.sub,
|
|
221
|
+
audience: options.audience,
|
|
222
|
+
...(typeof claims.email === "string"
|
|
223
|
+
? { email: claims.email }
|
|
224
|
+
: {}),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function parseJwtObject(encoded, label) {
|
|
228
|
+
try {
|
|
229
|
+
const value = JSON.parse(decoder.decode(base64UrlToBytes(encoded)));
|
|
230
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
231
|
+
throw new TypeError("not an object");
|
|
232
|
+
}
|
|
233
|
+
return value;
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
throw new SyncKitError("compatibility", `The Google ID token ${label} is invalid.`, { cause: error });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function derEcdsaToP1363(der, width) {
|
|
240
|
+
let offset = 0;
|
|
241
|
+
if (der[offset++] !== 0x30)
|
|
242
|
+
throw invalidDer();
|
|
243
|
+
const sequenceLength = readDerLength(der, offset);
|
|
244
|
+
offset = sequenceLength.offset;
|
|
245
|
+
if (sequenceLength.length !== der.length - offset)
|
|
246
|
+
throw invalidDer();
|
|
247
|
+
if (der[offset++] !== 0x02)
|
|
248
|
+
throw invalidDer();
|
|
249
|
+
const rLength = readDerLength(der, offset);
|
|
250
|
+
offset = rLength.offset;
|
|
251
|
+
const r = der.slice(offset, offset + rLength.length);
|
|
252
|
+
offset += rLength.length;
|
|
253
|
+
if (der[offset++] !== 0x02)
|
|
254
|
+
throw invalidDer();
|
|
255
|
+
const sLength = readDerLength(der, offset);
|
|
256
|
+
offset = sLength.offset;
|
|
257
|
+
const s = der.slice(offset, offset + sLength.length);
|
|
258
|
+
offset += sLength.length;
|
|
259
|
+
if (offset !== der.length)
|
|
260
|
+
throw invalidDer();
|
|
261
|
+
const result = new Uint8Array(width * 2);
|
|
262
|
+
result.set(normalizeInteger(r, width), 0);
|
|
263
|
+
result.set(normalizeInteger(s, width), width);
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
function readDerLength(value, offset) {
|
|
267
|
+
const first = value[offset];
|
|
268
|
+
if (first === undefined)
|
|
269
|
+
throw invalidDer();
|
|
270
|
+
if ((first & 0x80) === 0)
|
|
271
|
+
return { length: first, offset: offset + 1 };
|
|
272
|
+
const bytes = first & 0x7f;
|
|
273
|
+
if (bytes === 0 || bytes > 2 || offset + bytes >= value.length) {
|
|
274
|
+
throw invalidDer();
|
|
275
|
+
}
|
|
276
|
+
let length = 0;
|
|
277
|
+
for (let index = 0; index < bytes; index += 1) {
|
|
278
|
+
length = (length << 8) | (value[offset + 1 + index] ?? 0);
|
|
279
|
+
}
|
|
280
|
+
return { length, offset: offset + 1 + bytes };
|
|
281
|
+
}
|
|
282
|
+
function normalizeInteger(value, width) {
|
|
283
|
+
let normalized = value;
|
|
284
|
+
while (normalized.length > 1 && normalized[0] === 0) {
|
|
285
|
+
normalized = normalized.slice(1);
|
|
286
|
+
}
|
|
287
|
+
if (normalized.length > width)
|
|
288
|
+
throw invalidDer();
|
|
289
|
+
const result = new Uint8Array(width);
|
|
290
|
+
result.set(normalized, width - normalized.length);
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
function invalidDer() {
|
|
294
|
+
return new SyncKitError("compatibility", "The WebAuthn ECDSA signature is malformed.");
|
|
295
|
+
}
|
|
296
|
+
function equalBytes(left, right) {
|
|
297
|
+
if (left.length !== right.length)
|
|
298
|
+
return false;
|
|
299
|
+
let difference = 0;
|
|
300
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
301
|
+
difference |= (left[index] ?? 0) ^ (right[index] ?? 0);
|
|
302
|
+
}
|
|
303
|
+
return difference === 0;
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=account-binding.js.map
|