@1shotapi/ows-signer 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -0
- package/html/index.html +54 -0
- package/package.json +37 -0
- package/src/constants.js +30 -0
- package/src/crypto/aes256.js +115 -0
- package/src/crypto/prf.js +119 -0
- package/src/crypto/recovery.js +81 -0
- package/src/crypto/sign.js +59 -0
- package/src/crypto/vendor/noble-ed25519.js +385 -0
- package/src/crypto/vendor/noble-secp256k1.js +602 -0
- package/src/debug.js +42 -0
- package/src/handlers.js +691 -0
- package/src/hex.js +68 -0
- package/src/main.js +40 -0
- package/src/rpc.js +48 -0
- package/src/state.js +96 -0
- package/src/ui.js +68 -0
- package/src/webauthn.js +150 -0
package/src/handlers.js
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
import {
|
|
2
|
+
API_VERSION,
|
|
3
|
+
METHODS,
|
|
4
|
+
PRF_LABEL_ED25519,
|
|
5
|
+
SIGN_SCHEMES,
|
|
6
|
+
SIGNER_VERSION,
|
|
7
|
+
} from "./constants.js";
|
|
8
|
+
import {
|
|
9
|
+
decryptAes256Batch,
|
|
10
|
+
encryptAes256Batch,
|
|
11
|
+
} from "./crypto/aes256.js";
|
|
12
|
+
import {
|
|
13
|
+
deriveEd25519SeedFromPrf,
|
|
14
|
+
deriveKeysFromCredential,
|
|
15
|
+
hkdfExpand,
|
|
16
|
+
normalizePrfOutputToArrayBuffer,
|
|
17
|
+
} from "./crypto/prf.js";
|
|
18
|
+
import { getPublicKeyAsync as edGetPublicKeyAsync } from "./crypto/vendor/noble-ed25519.js";
|
|
19
|
+
import { getPublicKey as secpGetPublicKey } from "./crypto/vendor/noble-secp256k1.js";
|
|
20
|
+
import { debugLog, describePrfExtensionResults } from "./debug.js";
|
|
21
|
+
import { decryptPrivateKey, encryptPrivateKey } from "./crypto/recovery.js";
|
|
22
|
+
import { signWithScheme, validateSignPayload } from "./crypto/sign.js";
|
|
23
|
+
import { parse0xHex, to0xHex } from "./hex.js";
|
|
24
|
+
import { emitEvent } from "./rpc.js";
|
|
25
|
+
import {
|
|
26
|
+
clearRecoveryPrivateKey,
|
|
27
|
+
getRecoveryPrivateKey,
|
|
28
|
+
hasRecoverySession,
|
|
29
|
+
setRecoveryPrivateKey,
|
|
30
|
+
withCeremony,
|
|
31
|
+
zeroize,
|
|
32
|
+
} from "./state.js";
|
|
33
|
+
import { clearUi, promptPassphrase, showPrivateKey } from "./ui.js";
|
|
34
|
+
import {
|
|
35
|
+
createPasskeyCredential,
|
|
36
|
+
getAssertionSignatureBase64Url,
|
|
37
|
+
getCredentialId,
|
|
38
|
+
getPasskeyAssertion,
|
|
39
|
+
getPasskeyPublicKeyBase64Url,
|
|
40
|
+
} from "./webauthn.js";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {PublicKeyCredential} credential
|
|
44
|
+
* @returns {Promise<Uint8Array>}
|
|
45
|
+
*/
|
|
46
|
+
async function ed25519SeedFromCredential(credential) {
|
|
47
|
+
const prfRaw = /** @type {{ prf?: { results?: { first?: unknown } } }} */ (
|
|
48
|
+
credential.getClientExtensionResults()
|
|
49
|
+
).prf?.results?.first;
|
|
50
|
+
const prfBuf = normalizePrfOutputToArrayBuffer(prfRaw);
|
|
51
|
+
if (!prfBuf) throw new Error("noPrf");
|
|
52
|
+
return deriveEd25519SeedFromPrf(prfBuf);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {Uint8Array} secp256k1Scalar
|
|
57
|
+
* @returns {Promise<Uint8Array>}
|
|
58
|
+
*/
|
|
59
|
+
async function ed25519SeedFromSecp256k1Scalar(secp256k1Scalar) {
|
|
60
|
+
return hkdfExpand(
|
|
61
|
+
secp256k1Scalar.buffer.slice(
|
|
62
|
+
secp256k1Scalar.byteOffset,
|
|
63
|
+
secp256k1Scalar.byteOffset + secp256k1Scalar.byteLength,
|
|
64
|
+
),
|
|
65
|
+
PRF_LABEL_ED25519,
|
|
66
|
+
32,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} targetOrigin
|
|
72
|
+
* @param {string | undefined} correlationId
|
|
73
|
+
* @param {Uint8Array} secp256k1PublicKey
|
|
74
|
+
* @param {Uint8Array} ed25519PublicKey
|
|
75
|
+
*/
|
|
76
|
+
function emitKeyDerived(
|
|
77
|
+
targetOrigin,
|
|
78
|
+
correlationId,
|
|
79
|
+
secp256k1PublicKey,
|
|
80
|
+
ed25519PublicKey,
|
|
81
|
+
) {
|
|
82
|
+
emitEvent(window.parent, targetOrigin, "KeyDerived", correlationId, {
|
|
83
|
+
secp256k1PublicKey: to0xHex(secp256k1PublicKey),
|
|
84
|
+
ed25519PublicKey: to0xHex(ed25519PublicKey),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {string} method
|
|
90
|
+
* @param {Record<string, unknown>} params
|
|
91
|
+
* @param {string | undefined} correlationId
|
|
92
|
+
* @param {string} targetOrigin
|
|
93
|
+
*/
|
|
94
|
+
export async function handleRequest(
|
|
95
|
+
method,
|
|
96
|
+
params,
|
|
97
|
+
correlationId,
|
|
98
|
+
targetOrigin,
|
|
99
|
+
) {
|
|
100
|
+
if (!METHODS.includes(method)) {
|
|
101
|
+
emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, {
|
|
102
|
+
reason: "unknownMethod",
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
switch (method) {
|
|
109
|
+
case "getVersion":
|
|
110
|
+
emitEvent(window.parent, targetOrigin, "Version", correlationId, {
|
|
111
|
+
apiVersion: API_VERSION,
|
|
112
|
+
signerVersion: SIGNER_VERSION,
|
|
113
|
+
recoverySessionActive: hasRecoverySession(),
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
|
|
117
|
+
case "createCredential":
|
|
118
|
+
await handleCreateCredential(params, correlationId, targetOrigin);
|
|
119
|
+
return;
|
|
120
|
+
|
|
121
|
+
case "signDigest":
|
|
122
|
+
await handleSignDigest(params, correlationId, targetOrigin);
|
|
123
|
+
return;
|
|
124
|
+
|
|
125
|
+
case "revealPrivateKey":
|
|
126
|
+
await handleRevealPrivateKey(params, correlationId, targetOrigin);
|
|
127
|
+
return;
|
|
128
|
+
|
|
129
|
+
case "createRecoveryData":
|
|
130
|
+
await handleCreateRecoveryData(params, correlationId, targetOrigin);
|
|
131
|
+
return;
|
|
132
|
+
|
|
133
|
+
case "recoverKey":
|
|
134
|
+
await handleRecoverKey(params, correlationId, targetOrigin);
|
|
135
|
+
return;
|
|
136
|
+
|
|
137
|
+
case "getPublicKey":
|
|
138
|
+
await handleGetPublicKey(params, correlationId, targetOrigin);
|
|
139
|
+
return;
|
|
140
|
+
|
|
141
|
+
case "clearRecoverySession":
|
|
142
|
+
clearRecoveryPrivateKey();
|
|
143
|
+
clearUi();
|
|
144
|
+
emitEvent(
|
|
145
|
+
window.parent,
|
|
146
|
+
targetOrigin,
|
|
147
|
+
"RecoverySessionCleared",
|
|
148
|
+
correlationId,
|
|
149
|
+
{},
|
|
150
|
+
);
|
|
151
|
+
return;
|
|
152
|
+
|
|
153
|
+
case "encryptAES256":
|
|
154
|
+
await handleEncryptAES256(params, correlationId, targetOrigin);
|
|
155
|
+
return;
|
|
156
|
+
|
|
157
|
+
case "decryptAES256":
|
|
158
|
+
await handleDecryptAES256(params, correlationId, targetOrigin);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
handleError(error, correlationId, targetOrigin);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* PRF bytes are returned on assertion (`prf.eval`), not registration (`prf.enable`).
|
|
168
|
+
* Some platforms may return results on create; otherwise run a follow-up get.
|
|
169
|
+
*
|
|
170
|
+
* @param {PublicKeyCredential} credential
|
|
171
|
+
* @param {string} [credentialId]
|
|
172
|
+
* @returns {Promise<PublicKeyCredential>}
|
|
173
|
+
*/
|
|
174
|
+
async function credentialForKeyDerivation(credential, credentialId) {
|
|
175
|
+
debugLog("ceremony extension results", describePrfExtensionResults(credential));
|
|
176
|
+
|
|
177
|
+
const rawPrf = credential.getClientExtensionResults()?.prf?.results?.first;
|
|
178
|
+
if (normalizePrfOutputToArrayBuffer(rawPrf)) {
|
|
179
|
+
debugLog("using PRF output from current ceremony");
|
|
180
|
+
return credential;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const id = credentialId ?? getCredentialId(credential);
|
|
184
|
+
debugLog("no PRF results on registration; running assertion with prf.eval", {
|
|
185
|
+
credentialId: id,
|
|
186
|
+
});
|
|
187
|
+
const assertion = await getPasskeyAssertion(undefined, id);
|
|
188
|
+
debugLog(
|
|
189
|
+
"assertion extension results",
|
|
190
|
+
describePrfExtensionResults(assertion),
|
|
191
|
+
);
|
|
192
|
+
return assertion;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @param {Record<string, unknown>} params
|
|
197
|
+
* @param {string | undefined} correlationId
|
|
198
|
+
* @param {string} targetOrigin
|
|
199
|
+
*/
|
|
200
|
+
async function handleCreateCredential(params, correlationId, targetOrigin) {
|
|
201
|
+
const name = params.name;
|
|
202
|
+
if (typeof name !== "string" || !name) {
|
|
203
|
+
emitInvalid(correlationId, targetOrigin, "invalidName");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const options =
|
|
207
|
+
params.options && typeof params.options === "object"
|
|
208
|
+
? /** @type {{ rpName?: string, userDisplayName?: string, userId?: string }} */ (
|
|
209
|
+
params.options
|
|
210
|
+
)
|
|
211
|
+
: {};
|
|
212
|
+
|
|
213
|
+
await withCeremony(async () => {
|
|
214
|
+
const credential = await createPasskeyCredential(name, options);
|
|
215
|
+
const credentialId = getCredentialId(credential);
|
|
216
|
+
const prfCredential = await credentialForKeyDerivation(
|
|
217
|
+
credential,
|
|
218
|
+
credentialId,
|
|
219
|
+
);
|
|
220
|
+
const keys = await deriveKeysFromCredential(prfCredential);
|
|
221
|
+
emitKeyDerived(
|
|
222
|
+
targetOrigin,
|
|
223
|
+
correlationId,
|
|
224
|
+
keys.secp256k1PublicKey,
|
|
225
|
+
keys.ed25519PublicKey,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
emitEvent(window.parent, targetOrigin, "CredentialCreated", correlationId, {
|
|
229
|
+
credentialId,
|
|
230
|
+
passkeyPublicKey: getPasskeyPublicKeyBase64Url(credential),
|
|
231
|
+
secp256k1PublicKey: to0xHex(keys.secp256k1PublicKey),
|
|
232
|
+
});
|
|
233
|
+
zeroize(keys.secp256k1PrivateKey);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* @param {Record<string, unknown>} params
|
|
239
|
+
* @param {string | undefined} correlationId
|
|
240
|
+
* @param {string} targetOrigin
|
|
241
|
+
*/
|
|
242
|
+
async function handleSignDigest(params, correlationId, targetOrigin) {
|
|
243
|
+
const digestData = params.digestData;
|
|
244
|
+
const scheme = params.scheme;
|
|
245
|
+
const credentialId =
|
|
246
|
+
typeof params.credentialId === "string" ? params.credentialId : undefined;
|
|
247
|
+
|
|
248
|
+
if (typeof digestData !== "string" || typeof scheme !== "string") {
|
|
249
|
+
emitInvalid(correlationId, targetOrigin, "invalidParams");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (
|
|
253
|
+
!SIGN_SCHEMES.includes(
|
|
254
|
+
/** @type {import('./constants.js').SignScheme} */ (scheme),
|
|
255
|
+
)
|
|
256
|
+
) {
|
|
257
|
+
emitInvalid(correlationId, targetOrigin, "unknownScheme");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const digest = parse0xHex(digestData);
|
|
262
|
+
validateSignPayload(
|
|
263
|
+
/** @type {import('./constants.js').SignScheme} */ (scheme),
|
|
264
|
+
digest,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
if (hasRecoverySession()) {
|
|
268
|
+
const cached = getRecoveryPrivateKey();
|
|
269
|
+
if (!cached) throw new Error("recoverySessionEmpty");
|
|
270
|
+
const ed25519Seed = await ed25519SeedFromSecp256k1Scalar(cached);
|
|
271
|
+
const signature = await signWithScheme(
|
|
272
|
+
/** @type {import('./constants.js').SignScheme} */ (scheme),
|
|
273
|
+
digest,
|
|
274
|
+
cached,
|
|
275
|
+
ed25519Seed,
|
|
276
|
+
);
|
|
277
|
+
zeroize(ed25519Seed);
|
|
278
|
+
emitEvent(window.parent, targetOrigin, "DigestSigned", correlationId, {
|
|
279
|
+
digest: digestData,
|
|
280
|
+
scheme,
|
|
281
|
+
signature,
|
|
282
|
+
credentialId: credentialId ?? null,
|
|
283
|
+
});
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
await withCeremony(async () => {
|
|
288
|
+
const credential = await getPasskeyAssertion(undefined, credentialId);
|
|
289
|
+
const keys = await deriveKeysFromCredential(credential);
|
|
290
|
+
const ed25519Seed = await ed25519SeedFromCredential(credential);
|
|
291
|
+
emitKeyDerived(
|
|
292
|
+
targetOrigin,
|
|
293
|
+
correlationId,
|
|
294
|
+
keys.secp256k1PublicKey,
|
|
295
|
+
keys.ed25519PublicKey,
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const signature = await signWithScheme(
|
|
299
|
+
/** @type {import('./constants.js').SignScheme} */ (scheme),
|
|
300
|
+
digest,
|
|
301
|
+
keys.secp256k1PrivateKey,
|
|
302
|
+
ed25519Seed,
|
|
303
|
+
);
|
|
304
|
+
zeroize(keys.secp256k1PrivateKey);
|
|
305
|
+
zeroize(ed25519Seed);
|
|
306
|
+
|
|
307
|
+
emitEvent(window.parent, targetOrigin, "DigestSigned", correlationId, {
|
|
308
|
+
digest: digestData,
|
|
309
|
+
scheme,
|
|
310
|
+
signature,
|
|
311
|
+
credentialId: credentialId ?? getCredentialId(credential),
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* @param {Record<string, unknown>} params
|
|
318
|
+
* @param {string | undefined} correlationId
|
|
319
|
+
* @param {string} targetOrigin
|
|
320
|
+
*/
|
|
321
|
+
async function handleRevealPrivateKey(params, correlationId, targetOrigin) {
|
|
322
|
+
const credentialId =
|
|
323
|
+
typeof params.credentialId === "string" ? params.credentialId : undefined;
|
|
324
|
+
|
|
325
|
+
await withCeremony(async () => {
|
|
326
|
+
const credential = await getPasskeyAssertion(undefined, credentialId);
|
|
327
|
+
const keys = await deriveKeysFromCredential(credential);
|
|
328
|
+
emitKeyDerived(
|
|
329
|
+
targetOrigin,
|
|
330
|
+
correlationId,
|
|
331
|
+
keys.secp256k1PublicKey,
|
|
332
|
+
keys.ed25519PublicKey,
|
|
333
|
+
);
|
|
334
|
+
showPrivateKey(keys.secp256k1PrivateKey);
|
|
335
|
+
zeroize(keys.secp256k1PrivateKey);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* @param {Record<string, unknown>} params
|
|
341
|
+
* @param {string | undefined} correlationId
|
|
342
|
+
* @param {string} targetOrigin
|
|
343
|
+
*/
|
|
344
|
+
async function handleCreateRecoveryData(params, correlationId, targetOrigin) {
|
|
345
|
+
const passwordText = params.passwordText;
|
|
346
|
+
const buttonText = params.buttonText;
|
|
347
|
+
const minPasswordLength = params.minPasswordLength;
|
|
348
|
+
const credentialId =
|
|
349
|
+
typeof params.credentialId === "string" ? params.credentialId : undefined;
|
|
350
|
+
|
|
351
|
+
if (
|
|
352
|
+
typeof passwordText !== "string" ||
|
|
353
|
+
typeof buttonText !== "string" ||
|
|
354
|
+
typeof minPasswordLength !== "number"
|
|
355
|
+
) {
|
|
356
|
+
emitInvalid(correlationId, targetOrigin, "invalidParams");
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const passphrase = await promptPassphrase(
|
|
361
|
+
passwordText,
|
|
362
|
+
buttonText,
|
|
363
|
+
minPasswordLength,
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
// Recovery session already holds the secp256k1 scalar — no WebAuthn needed.
|
|
367
|
+
if (hasRecoverySession()) {
|
|
368
|
+
const cached = getRecoveryPrivateKey();
|
|
369
|
+
if (!cached) throw new Error("recoverySessionEmpty");
|
|
370
|
+
const secp256k1PublicKey = secpGetPublicKey(cached, false);
|
|
371
|
+
const ed25519Seed = await ed25519SeedFromSecp256k1Scalar(cached);
|
|
372
|
+
const ed25519PublicKey = await edGetPublicKeyAsync(ed25519Seed);
|
|
373
|
+
emitKeyDerived(
|
|
374
|
+
targetOrigin,
|
|
375
|
+
correlationId,
|
|
376
|
+
secp256k1PublicKey,
|
|
377
|
+
ed25519PublicKey,
|
|
378
|
+
);
|
|
379
|
+
const encryptedPrivateKey = await encryptPrivateKey(cached, passphrase);
|
|
380
|
+
zeroize(ed25519Seed);
|
|
381
|
+
clearUi();
|
|
382
|
+
emitEvent(
|
|
383
|
+
window.parent,
|
|
384
|
+
targetOrigin,
|
|
385
|
+
"RecoveryDataCreated",
|
|
386
|
+
correlationId,
|
|
387
|
+
{ encryptedPrivateKey },
|
|
388
|
+
);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
await withCeremony(async () => {
|
|
393
|
+
const credential = await getPasskeyAssertion(undefined, credentialId);
|
|
394
|
+
const keys = await deriveKeysFromCredential(credential);
|
|
395
|
+
emitKeyDerived(
|
|
396
|
+
targetOrigin,
|
|
397
|
+
correlationId,
|
|
398
|
+
keys.secp256k1PublicKey,
|
|
399
|
+
keys.ed25519PublicKey,
|
|
400
|
+
);
|
|
401
|
+
const encryptedPrivateKey = await encryptPrivateKey(
|
|
402
|
+
keys.secp256k1PrivateKey,
|
|
403
|
+
passphrase,
|
|
404
|
+
);
|
|
405
|
+
zeroize(keys.secp256k1PrivateKey);
|
|
406
|
+
emitEvent(
|
|
407
|
+
window.parent,
|
|
408
|
+
targetOrigin,
|
|
409
|
+
"RecoveryDataCreated",
|
|
410
|
+
correlationId,
|
|
411
|
+
{ encryptedPrivateKey },
|
|
412
|
+
);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* @param {Record<string, unknown>} params
|
|
418
|
+
* @param {string | undefined} correlationId
|
|
419
|
+
* @param {string} targetOrigin
|
|
420
|
+
*/
|
|
421
|
+
async function handleRecoverKey(params, correlationId, targetOrigin) {
|
|
422
|
+
const envelope = params.aes256EncryptedPrivateKey;
|
|
423
|
+
const passwordText = params.passwordText;
|
|
424
|
+
const buttonText = params.buttonText;
|
|
425
|
+
const credentialId =
|
|
426
|
+
typeof params.credentialId === "string" ? params.credentialId : undefined;
|
|
427
|
+
|
|
428
|
+
if (
|
|
429
|
+
typeof envelope !== "string" ||
|
|
430
|
+
typeof passwordText !== "string" ||
|
|
431
|
+
typeof buttonText !== "string"
|
|
432
|
+
) {
|
|
433
|
+
emitInvalid(correlationId, targetOrigin, "invalidParams");
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const passphrase = await promptPassphrase(passwordText, buttonText, 1);
|
|
438
|
+
const privateKey = await decryptPrivateKey(envelope, passphrase);
|
|
439
|
+
setRecoveryPrivateKey(privateKey);
|
|
440
|
+
|
|
441
|
+
// Emit public keys so OWSSigner can cache addresses (uncompressed secp256k1 —
|
|
442
|
+
// viem publicKeyToAddress requires 0x04 ‖ X ‖ Y).
|
|
443
|
+
const secp256k1PublicKey = secpGetPublicKey(privateKey, false);
|
|
444
|
+
const ed25519Seed = await ed25519SeedFromSecp256k1Scalar(privateKey);
|
|
445
|
+
const ed25519PublicKey = await edGetPublicKeyAsync(ed25519Seed);
|
|
446
|
+
emitKeyDerived(
|
|
447
|
+
targetOrigin,
|
|
448
|
+
correlationId,
|
|
449
|
+
secp256k1PublicKey,
|
|
450
|
+
ed25519PublicKey,
|
|
451
|
+
);
|
|
452
|
+
zeroize(ed25519Seed);
|
|
453
|
+
|
|
454
|
+
showPrivateKey(privateKey);
|
|
455
|
+
|
|
456
|
+
if (credentialId) {
|
|
457
|
+
await withCeremony(async () => {
|
|
458
|
+
await getPasskeyAssertion(undefined, credentialId);
|
|
459
|
+
clearRecoveryPrivateKey();
|
|
460
|
+
});
|
|
461
|
+
emitEvent(
|
|
462
|
+
window.parent,
|
|
463
|
+
targetOrigin,
|
|
464
|
+
"RecoverySessionCleared",
|
|
465
|
+
correlationId,
|
|
466
|
+
{ rebound: true },
|
|
467
|
+
);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
emitEvent(
|
|
472
|
+
window.parent,
|
|
473
|
+
targetOrigin,
|
|
474
|
+
"RecoverySessionStarted",
|
|
475
|
+
correlationId,
|
|
476
|
+
{ recoverySessionActive: true },
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* @param {unknown} value
|
|
482
|
+
* @returns {value is string[]}
|
|
483
|
+
*/
|
|
484
|
+
function isStringArray(value) {
|
|
485
|
+
return (
|
|
486
|
+
Array.isArray(value) && value.every((item) => typeof item === "string")
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Batch AES-256-GCM seal using the secp256k1 scalar (same as `signDigest`).
|
|
492
|
+
*
|
|
493
|
+
* @param {Record<string, unknown>} params
|
|
494
|
+
* @param {string | undefined} correlationId
|
|
495
|
+
* @param {string} targetOrigin
|
|
496
|
+
*/
|
|
497
|
+
async function handleEncryptAES256(params, correlationId, targetOrigin) {
|
|
498
|
+
const plaintexts = params.plaintexts;
|
|
499
|
+
const credentialId =
|
|
500
|
+
typeof params.credentialId === "string" ? params.credentialId : undefined;
|
|
501
|
+
|
|
502
|
+
if (!isStringArray(plaintexts)) {
|
|
503
|
+
emitInvalid(correlationId, targetOrigin, "invalidParams");
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (plaintexts.length === 0) {
|
|
508
|
+
emitEvent(window.parent, targetOrigin, "AES256Encrypted", correlationId, {
|
|
509
|
+
ciphertexts: [],
|
|
510
|
+
});
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (hasRecoverySession()) {
|
|
515
|
+
const cached = getRecoveryPrivateKey();
|
|
516
|
+
if (!cached) throw new Error("recoverySessionEmpty");
|
|
517
|
+
const ciphertexts = await encryptAes256Batch(plaintexts, cached);
|
|
518
|
+
emitEvent(window.parent, targetOrigin, "AES256Encrypted", correlationId, {
|
|
519
|
+
ciphertexts,
|
|
520
|
+
});
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
await withCeremony(async () => {
|
|
525
|
+
const credential = await getPasskeyAssertion(undefined, credentialId);
|
|
526
|
+
const keys = await deriveKeysFromCredential(credential);
|
|
527
|
+
emitKeyDerived(
|
|
528
|
+
targetOrigin,
|
|
529
|
+
correlationId,
|
|
530
|
+
keys.secp256k1PublicKey,
|
|
531
|
+
keys.ed25519PublicKey,
|
|
532
|
+
);
|
|
533
|
+
try {
|
|
534
|
+
const ciphertexts = await encryptAes256Batch(
|
|
535
|
+
plaintexts,
|
|
536
|
+
keys.secp256k1PrivateKey,
|
|
537
|
+
);
|
|
538
|
+
emitEvent(window.parent, targetOrigin, "AES256Encrypted", correlationId, {
|
|
539
|
+
ciphertexts,
|
|
540
|
+
});
|
|
541
|
+
} finally {
|
|
542
|
+
zeroize(keys.secp256k1PrivateKey);
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Batch AES-256-GCM unseal using the secp256k1 scalar (same as `signDigest`).
|
|
549
|
+
*
|
|
550
|
+
* @param {Record<string, unknown>} params
|
|
551
|
+
* @param {string | undefined} correlationId
|
|
552
|
+
* @param {string} targetOrigin
|
|
553
|
+
*/
|
|
554
|
+
async function handleDecryptAES256(params, correlationId, targetOrigin) {
|
|
555
|
+
const ciphertexts = params.ciphertexts;
|
|
556
|
+
const credentialId =
|
|
557
|
+
typeof params.credentialId === "string" ? params.credentialId : undefined;
|
|
558
|
+
|
|
559
|
+
if (!isStringArray(ciphertexts)) {
|
|
560
|
+
emitInvalid(correlationId, targetOrigin, "invalidParams");
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (ciphertexts.length === 0) {
|
|
565
|
+
emitEvent(window.parent, targetOrigin, "AES256Decrypted", correlationId, {
|
|
566
|
+
plaintexts: [],
|
|
567
|
+
});
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (hasRecoverySession()) {
|
|
572
|
+
const cached = getRecoveryPrivateKey();
|
|
573
|
+
if (!cached) throw new Error("recoverySessionEmpty");
|
|
574
|
+
const plaintexts = await decryptAes256Batch(ciphertexts, cached);
|
|
575
|
+
emitEvent(window.parent, targetOrigin, "AES256Decrypted", correlationId, {
|
|
576
|
+
plaintexts,
|
|
577
|
+
});
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
await withCeremony(async () => {
|
|
582
|
+
const credential = await getPasskeyAssertion(undefined, credentialId);
|
|
583
|
+
const keys = await deriveKeysFromCredential(credential);
|
|
584
|
+
emitKeyDerived(
|
|
585
|
+
targetOrigin,
|
|
586
|
+
correlationId,
|
|
587
|
+
keys.secp256k1PublicKey,
|
|
588
|
+
keys.ed25519PublicKey,
|
|
589
|
+
);
|
|
590
|
+
try {
|
|
591
|
+
const plaintexts = await decryptAes256Batch(
|
|
592
|
+
ciphertexts,
|
|
593
|
+
keys.secp256k1PrivateKey,
|
|
594
|
+
);
|
|
595
|
+
emitEvent(window.parent, targetOrigin, "AES256Decrypted", correlationId, {
|
|
596
|
+
plaintexts,
|
|
597
|
+
});
|
|
598
|
+
} finally {
|
|
599
|
+
zeroize(keys.secp256k1PrivateKey);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* @param {Record<string, unknown>} params
|
|
606
|
+
* @param {string | undefined} correlationId
|
|
607
|
+
* @param {string} targetOrigin
|
|
608
|
+
*/
|
|
609
|
+
async function handleGetPublicKey(params, correlationId, targetOrigin) {
|
|
610
|
+
const credentialId =
|
|
611
|
+
typeof params.credentialId === "string" ? params.credentialId : undefined;
|
|
612
|
+
const challenge =
|
|
613
|
+
typeof params.challenge === "string"
|
|
614
|
+
? parse0xHex(params.challenge)
|
|
615
|
+
: undefined;
|
|
616
|
+
|
|
617
|
+
await withCeremony(async () => {
|
|
618
|
+
const credential = await getPasskeyAssertion(challenge, credentialId);
|
|
619
|
+
const keys = await deriveKeysFromCredential(credential);
|
|
620
|
+
emitKeyDerived(
|
|
621
|
+
targetOrigin,
|
|
622
|
+
correlationId,
|
|
623
|
+
keys.secp256k1PublicKey,
|
|
624
|
+
keys.ed25519PublicKey,
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
emitEvent(window.parent, targetOrigin, "PublicKey", correlationId, {
|
|
628
|
+
credentialId: getCredentialId(credential),
|
|
629
|
+
passkeyPublicKey: getPasskeyPublicKeyBase64Url(credential),
|
|
630
|
+
secp256k1PublicKey: to0xHex(keys.secp256k1PublicKey),
|
|
631
|
+
ed25519PublicKey: to0xHex(keys.ed25519PublicKey),
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
if (challenge) {
|
|
635
|
+
const signature = getAssertionSignatureBase64Url(credential);
|
|
636
|
+
if (signature) {
|
|
637
|
+
emitEvent(
|
|
638
|
+
window.parent,
|
|
639
|
+
targetOrigin,
|
|
640
|
+
"ChallengeSigned",
|
|
641
|
+
correlationId,
|
|
642
|
+
{
|
|
643
|
+
challenge: to0xHex(challenge),
|
|
644
|
+
signature,
|
|
645
|
+
},
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
zeroize(keys.secp256k1PrivateKey);
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* @param {unknown} error
|
|
656
|
+
* @param {string | undefined} correlationId
|
|
657
|
+
* @param {string} targetOrigin
|
|
658
|
+
*/
|
|
659
|
+
function handleError(error, correlationId, targetOrigin) {
|
|
660
|
+
const name = error instanceof Error ? error.name : "";
|
|
661
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
662
|
+
|
|
663
|
+
if (name === "NotAllowedError" || message.includes("NotAllowed")) {
|
|
664
|
+
emitEvent(window.parent, targetOrigin, "NotAllowed", correlationId, {
|
|
665
|
+
reason: message || "notAllowed",
|
|
666
|
+
});
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (message === "ceremonyInProgress") {
|
|
671
|
+
emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, {
|
|
672
|
+
reason: "ceremonyInProgress",
|
|
673
|
+
});
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, {
|
|
678
|
+
reason: message || "error",
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* @param {string | undefined} correlationId
|
|
684
|
+
* @param {string} targetOrigin
|
|
685
|
+
* @param {string} reason
|
|
686
|
+
*/
|
|
687
|
+
function emitInvalid(correlationId, targetOrigin, reason) {
|
|
688
|
+
emitEvent(window.parent, targetOrigin, "InvalidRequest", correlationId, {
|
|
689
|
+
reason,
|
|
690
|
+
});
|
|
691
|
+
}
|