@metamask-previews/passkey-controller 2.1.0-preview-eade524 → 2.1.0-preview-44376e0
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/CHANGELOG.md +0 -21
- package/README.md +20 -85
- package/dist/PasskeyController-method-action-types.cjs.map +1 -1
- package/dist/PasskeyController-method-action-types.d.cts +7 -85
- package/dist/PasskeyController-method-action-types.d.cts.map +1 -1
- package/dist/PasskeyController-method-action-types.d.mts +7 -85
- package/dist/PasskeyController-method-action-types.d.mts.map +1 -1
- package/dist/PasskeyController-method-action-types.mjs.map +1 -1
- package/dist/PasskeyController.cjs +164 -326
- package/dist/PasskeyController.cjs.map +1 -1
- package/dist/PasskeyController.d.cts +50 -88
- package/dist/PasskeyController.d.cts.map +1 -1
- package/dist/PasskeyController.d.mts +50 -88
- package/dist/PasskeyController.d.mts.map +1 -1
- package/dist/PasskeyController.mjs +164 -326
- package/dist/PasskeyController.mjs.map +1 -1
- package/dist/constants.cjs +0 -3
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +1 -4
- package/dist/constants.d.cts.map +1 -1
- package/dist/constants.d.mts +1 -4
- package/dist/constants.d.mts.map +1 -1
- package/dist/constants.mjs +0 -3
- package/dist/constants.mjs.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/types.cjs +0 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +0 -38
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +0 -38
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs +1 -1
- package/dist/types.mjs.map +1 -1
- package/package.json +2 -4
|
@@ -10,12 +10,11 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
11
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
12
|
};
|
|
13
|
-
var _PasskeyController_instances, _PasskeyController_ceremonyManager, _PasskeyController_expectedRPIDs, _PasskeyController_rpId, _PasskeyController_rpName, _PasskeyController_expectedOrigin, _PasskeyController_userName, _PasskeyController_userDisplayName,
|
|
13
|
+
var _PasskeyController_instances, _PasskeyController_ceremonyManager, _PasskeyController_expectedRPIDs, _PasskeyController_rpId, _PasskeyController_rpName, _PasskeyController_expectedOrigin, _PasskeyController_userName, _PasskeyController_userDisplayName, _PasskeyController_requireEnrolled, _PasskeyController_getChallengeFromClientData, _PasskeyController_verifyAuthenticationResponse;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.PasskeyController = exports.passkeyControllerSelectors = exports.getDefaultPasskeyControllerState = void 0;
|
|
16
16
|
const base_controller_1 = require("@metamask/base-controller");
|
|
17
17
|
const utils_1 = require("@metamask/utils");
|
|
18
|
-
const async_mutex_1 = require("async-mutex");
|
|
19
18
|
const ceremony_manager_1 = require("./ceremony-manager.cjs");
|
|
20
19
|
const constants_1 = require("./constants.cjs");
|
|
21
20
|
const errors_1 = require("./errors.cjs");
|
|
@@ -62,14 +61,9 @@ const MESSENGER_EXPOSED_METHODS = [
|
|
|
62
61
|
'generateAuthenticationOptions',
|
|
63
62
|
'protectVaultKeyWithPasskey',
|
|
64
63
|
'retrieveVaultKeyWithPasskey',
|
|
65
|
-
'unlockWithPasskey',
|
|
66
64
|
'verifyPasskeyAuthentication',
|
|
67
65
|
'renewVaultKeyProtection',
|
|
68
|
-
'
|
|
69
|
-
'exportSeedPhraseWithPasskey',
|
|
70
|
-
'exportAccountsWithPasskey',
|
|
71
|
-
'removePasskeyWithPasskeyVerification',
|
|
72
|
-
'removePasskeyWithPasswordVerification',
|
|
66
|
+
'removePasskey',
|
|
73
67
|
'clearState',
|
|
74
68
|
'destroy',
|
|
75
69
|
];
|
|
@@ -81,18 +75,20 @@ class PasskeyController extends base_controller_1.BaseController {
|
|
|
81
75
|
/**
|
|
82
76
|
* Creates a passkey controller with WebAuthn relying-party settings.
|
|
83
77
|
*
|
|
84
|
-
* @param
|
|
85
|
-
* @param
|
|
86
|
-
* @param
|
|
87
|
-
* @param
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* @param
|
|
91
|
-
*
|
|
92
|
-
* @param
|
|
93
|
-
* @param
|
|
78
|
+
* @param args - Constructor options.
|
|
79
|
+
* @param args.messenger - Controller messenger.
|
|
80
|
+
* @param args.state - Partial initial state; merged with {@link getDefaultPasskeyControllerState}.
|
|
81
|
+
* @param args.expectedRPID - Relying party ID(s) for verification (SHA-256 hash match in
|
|
82
|
+
* authenticator data). Pass a string or array of strings; an empty array skips RP ID
|
|
83
|
+
* allowlist checks in {@link verifyRegistrationResponse} / {@link verifyAuthenticationResponse}.
|
|
84
|
+
* @param args.rpId - When set, included as `rp.id` on registration options and `rpId` on
|
|
85
|
+
* authentication options. When omitted, those fields are left unset (client default RP ID).
|
|
86
|
+
* @param args.rpName - Relying party name shown in the platform passkey UI.
|
|
87
|
+
* @param args.expectedOrigin - Allowed value(s) for the WebAuthn client origin.
|
|
88
|
+
* @param args.userName - Optional passkey user name; defaults to `rpName`.
|
|
89
|
+
* @param args.userDisplayName - Optional display name; defaults to `rpName`.
|
|
94
90
|
*/
|
|
95
|
-
constructor({ messenger, state = {}, rpId, expectedRPID, rpName, expectedOrigin, userName, userDisplayName,
|
|
91
|
+
constructor({ messenger, state = {}, rpId, expectedRPID, rpName, expectedOrigin, userName, userDisplayName, }) {
|
|
96
92
|
super({
|
|
97
93
|
messenger,
|
|
98
94
|
metadata: passkeyControllerMetadata,
|
|
@@ -107,8 +103,6 @@ class PasskeyController extends base_controller_1.BaseController {
|
|
|
107
103
|
_PasskeyController_expectedOrigin.set(this, void 0);
|
|
108
104
|
_PasskeyController_userName.set(this, void 0);
|
|
109
105
|
_PasskeyController_userDisplayName.set(this, void 0);
|
|
110
|
-
_PasskeyController_getIsOnboardingCompleted.set(this, void 0);
|
|
111
|
-
_PasskeyController_operationMutex.set(this, new async_mutex_1.Mutex());
|
|
112
106
|
const expectedRPIDs = Array.isArray(expectedRPID)
|
|
113
107
|
? expectedRPID
|
|
114
108
|
: [expectedRPID];
|
|
@@ -118,7 +112,6 @@ class PasskeyController extends base_controller_1.BaseController {
|
|
|
118
112
|
__classPrivateFieldSet(this, _PasskeyController_expectedOrigin, expectedOrigin, "f");
|
|
119
113
|
__classPrivateFieldSet(this, _PasskeyController_userName, userName ?? rpName, "f");
|
|
120
114
|
__classPrivateFieldSet(this, _PasskeyController_userDisplayName, userDisplayName ?? rpName, "f");
|
|
121
|
-
__classPrivateFieldSet(this, _PasskeyController_getIsOnboardingCompleted, getIsOnboardingCompleted, "f");
|
|
122
115
|
this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
123
116
|
}
|
|
124
117
|
/**
|
|
@@ -264,72 +257,113 @@ class PasskeyController extends base_controller_1.BaseController {
|
|
|
264
257
|
* Verifies registration and post-registration authentication, then stores the
|
|
265
258
|
* vault key encrypted under the new passkey.
|
|
266
259
|
*
|
|
267
|
-
* Fetches the current vault encryption key from KeyringController before wrapping.
|
|
268
|
-
* When onboarding is complete, requires `password` for step-up verification first.
|
|
269
|
-
*
|
|
270
260
|
* @param params - Enrollment completion inputs.
|
|
271
261
|
* @param params.registrationResponse - Result of `navigator.credentials.create()`.
|
|
272
262
|
* @param params.authenticationResponse - Result of `navigator.credentials.get()` after {@link generatePostRegistrationAuthenticationOptions}.
|
|
273
|
-
* @param params.
|
|
274
|
-
* @returns Resolves when enrollment completes.
|
|
263
|
+
* @param params.vaultKey - Vault encryption key to encrypt and persist.
|
|
275
264
|
*/
|
|
276
265
|
async protectVaultKeyWithPasskey(params) {
|
|
277
|
-
|
|
266
|
+
if (this.isPasskeyEnrolled()) {
|
|
267
|
+
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.AlreadyEnrolled, { code: constants_1.PasskeyControllerErrorCode.AlreadyEnrolled });
|
|
268
|
+
}
|
|
269
|
+
const { registrationResponse, authenticationResponse, vaultKey } = params;
|
|
270
|
+
// get registration ceremony
|
|
271
|
+
const challenge = __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_getChallengeFromClientData).call(this, registrationResponse.response.clientDataJSON);
|
|
272
|
+
const registrationCeremony = __classPrivateFieldGet(this, _PasskeyController_ceremonyManager, "f").getRegistrationCeremony(challenge);
|
|
273
|
+
if (!registrationCeremony) {
|
|
274
|
+
log('No active passkey registration ceremony for challenge');
|
|
275
|
+
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.NoRegistrationCeremony, { code: constants_1.PasskeyControllerErrorCode.NoRegistrationCeremony });
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
// verify registration response
|
|
279
|
+
const { verified, registrationInfo } = await (0, verify_registration_response_1.verifyRegistrationResponse)({
|
|
280
|
+
response: registrationResponse,
|
|
281
|
+
expectedChallenge: registrationCeremony.challenge,
|
|
282
|
+
expectedOrigin: __classPrivateFieldGet(this, _PasskeyController_expectedOrigin, "f"),
|
|
283
|
+
expectedRPIDs: __classPrivateFieldGet(this, _PasskeyController_expectedRPIDs, "f"),
|
|
284
|
+
requireUserVerification: true,
|
|
285
|
+
}).catch((error) => {
|
|
286
|
+
log('Error verifying passkey registration response', error);
|
|
287
|
+
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.RegistrationVerificationFailed, {
|
|
288
|
+
code: constants_1.PasskeyControllerErrorCode.RegistrationVerificationFailed,
|
|
289
|
+
cause: error instanceof Error ? error : new Error(String(error)),
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
if (!verified || !registrationInfo) {
|
|
293
|
+
log('Passkey registration verification returned unverified or missing registration info');
|
|
294
|
+
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.RegistrationVerificationFailed, { code: constants_1.PasskeyControllerErrorCode.RegistrationVerificationFailed });
|
|
295
|
+
}
|
|
296
|
+
// verify authentication response
|
|
297
|
+
const credential = {
|
|
298
|
+
id: registrationInfo.credentialId,
|
|
299
|
+
publicKey: (0, encoding_1.bytesToBase64URL)(registrationInfo.publicKey),
|
|
300
|
+
counter: registrationInfo.counter,
|
|
301
|
+
transports: registrationInfo.transports,
|
|
302
|
+
aaguid: registrationInfo.aaguid,
|
|
303
|
+
};
|
|
304
|
+
const { newCounter } = await __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_verifyAuthenticationResponse).call(this, authenticationResponse, credential);
|
|
305
|
+
// determine key derivation method
|
|
306
|
+
const prfFirst = authenticationResponse.clientExtensionResults?.prf?.results?.first;
|
|
307
|
+
const authHasPrfOutput = typeof prfFirst === 'string' && prfFirst.length > 0;
|
|
308
|
+
const keyDerivation = authHasPrfOutput && registrationCeremony.prfSalt
|
|
309
|
+
? { method: 'prf', prfSalt: registrationCeremony.prfSalt }
|
|
310
|
+
: { method: 'userHandle' };
|
|
311
|
+
if (keyDerivation.method === 'userHandle' &&
|
|
312
|
+
authenticationResponse.response.userHandle !==
|
|
313
|
+
registrationCeremony.userHandle) {
|
|
314
|
+
log('Post-registration assertion userHandle does not match registration ceremony');
|
|
315
|
+
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.AuthenticationVerificationFailed, { code: constants_1.PasskeyControllerErrorCode.AuthenticationVerificationFailed });
|
|
316
|
+
}
|
|
317
|
+
// derive key and encrypt vault key
|
|
318
|
+
const encKey = (0, key_derivation_1.deriveKeyFromAuthenticationResponse)(authenticationResponse, { credential, keyDerivation });
|
|
319
|
+
const { ciphertext, iv } = (0, crypto_1.encryptWithKey)(vaultKey, encKey);
|
|
320
|
+
// persist passkey record
|
|
321
|
+
this.update((state) => {
|
|
322
|
+
state.passkeyRecord = {
|
|
323
|
+
credential: {
|
|
324
|
+
...credential,
|
|
325
|
+
counter: Math.max(newCounter, credential.counter),
|
|
326
|
+
},
|
|
327
|
+
encryptedVaultKey: { ciphertext, iv },
|
|
328
|
+
keyDerivation,
|
|
329
|
+
};
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
finally {
|
|
333
|
+
// delete registration ceremony
|
|
334
|
+
__classPrivateFieldGet(this, _PasskeyController_ceremonyManager, "f").deleteRegistrationCeremony(challenge);
|
|
335
|
+
}
|
|
278
336
|
}
|
|
279
337
|
/**
|
|
280
338
|
* Verifies an authentication assertion and returns the decrypted vault key.
|
|
281
339
|
*
|
|
282
|
-
* Prefer orchestrated methods ({@link unlockWithPasskey},
|
|
283
|
-
* {@link exportSeedPhraseWithPasskey}, {@link exportAccountsWithPasskey}) for product
|
|
284
|
-
* flows instead of calling KeyringController with the returned key manually.
|
|
285
|
-
*
|
|
286
340
|
* @param authenticationResponse - Result of `navigator.credentials.get()`.
|
|
287
341
|
* @returns The plaintext vault encryption key.
|
|
288
342
|
*/
|
|
289
343
|
async retrieveVaultKeyWithPasskey(authenticationResponse) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
* @returns Resolves when the keyring is unlocked.
|
|
297
|
-
*/
|
|
298
|
-
async unlockWithPasskey(authenticationResponse) {
|
|
299
|
-
return __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_withOperationLock).call(this, async () => {
|
|
300
|
-
const vaultKey = await __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_retrieveVaultKeyWithPasskey).call(this, authenticationResponse);
|
|
301
|
-
await this.messenger.call('KeyringController:submitEncryptionKey', vaultKey);
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Exports the seed phrase after passkey step-up authentication.
|
|
306
|
-
*
|
|
307
|
-
* @param authenticationResponse - Result of `navigator.credentials.get()`.
|
|
308
|
-
* @param keyringId - Optional keyring id; defaults to the primary HD keyring.
|
|
309
|
-
* @returns Raw seed phrase bytes from KeyringController.
|
|
310
|
-
*/
|
|
311
|
-
async exportSeedPhraseWithPasskey(authenticationResponse, keyringId) {
|
|
312
|
-
return __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_withOperationLock).call(this, async () => {
|
|
313
|
-
const vaultKey = await __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_retrieveVaultKeyWithPasskey).call(this, authenticationResponse);
|
|
314
|
-
return await this.messenger.call('KeyringController:exportSeedPhrase', { encryptionKey: vaultKey }, keyringId);
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Exports private keys for the given addresses after passkey step-up authentication.
|
|
319
|
-
*
|
|
320
|
-
* @param authenticationResponse - Result of `navigator.credentials.get()`.
|
|
321
|
-
* @param addresses - Account addresses to export.
|
|
322
|
-
* @returns Private keys in the same order as `addresses`.
|
|
323
|
-
*/
|
|
324
|
-
async exportAccountsWithPasskey(authenticationResponse, addresses) {
|
|
325
|
-
return __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_withOperationLock).call(this, async () => {
|
|
326
|
-
const vaultKey = await __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_retrieveVaultKeyWithPasskey).call(this, authenticationResponse);
|
|
327
|
-
const privateKeys = [];
|
|
328
|
-
for (const address of addresses) {
|
|
329
|
-
privateKeys.push(await this.messenger.call('KeyringController:exportAccount', { encryptionKey: vaultKey }, address));
|
|
344
|
+
const passkeyRecord = __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_requireEnrolled).call(this);
|
|
345
|
+
// verify authentication response and update counter
|
|
346
|
+
const { newCounter } = await __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_verifyAuthenticationResponse).call(this, authenticationResponse, passkeyRecord.credential);
|
|
347
|
+
this.update((state) => {
|
|
348
|
+
if (!state.passkeyRecord) {
|
|
349
|
+
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.NotEnrolled, { code: constants_1.PasskeyControllerErrorCode.NotEnrolled });
|
|
330
350
|
}
|
|
331
|
-
|
|
351
|
+
state.passkeyRecord.credential.counter = Math.max(newCounter, state.passkeyRecord.credential.counter);
|
|
332
352
|
});
|
|
353
|
+
// derive key
|
|
354
|
+
const encKey = (0, key_derivation_1.deriveKeyFromAuthenticationResponse)(authenticationResponse, passkeyRecord);
|
|
355
|
+
// decrypt vault key
|
|
356
|
+
try {
|
|
357
|
+
const vaultKey = (0, crypto_1.decryptWithKey)(passkeyRecord.encryptedVaultKey.ciphertext, passkeyRecord.encryptedVaultKey.iv, encKey);
|
|
358
|
+
return vaultKey;
|
|
359
|
+
}
|
|
360
|
+
catch (cause) {
|
|
361
|
+
log('Error decrypting vault key with passkey', cause instanceof Error ? cause : new Error(String(cause)));
|
|
362
|
+
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.VaultKeyDecryptionFailed, {
|
|
363
|
+
code: constants_1.PasskeyControllerErrorCode.VaultKeyDecryptionFailed,
|
|
364
|
+
cause: cause instanceof Error ? cause : new Error(String(cause)),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
333
367
|
}
|
|
334
368
|
/**
|
|
335
369
|
* Checks whether the given authentication assertion is valid for the enrolled passkey.
|
|
@@ -341,7 +375,16 @@ class PasskeyController extends base_controller_1.BaseController {
|
|
|
341
375
|
* @returns `true` if verification succeeds, otherwise `false`.
|
|
342
376
|
*/
|
|
343
377
|
async verifyPasskeyAuthentication(authenticationResponse) {
|
|
344
|
-
|
|
378
|
+
try {
|
|
379
|
+
await this.retrieveVaultKeyWithPasskey(authenticationResponse);
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
if (error instanceof errors_1.PasskeyControllerError && error.code !== undefined) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
345
388
|
}
|
|
346
389
|
/**
|
|
347
390
|
* Re-wraps the vault key after rotation. Updates persisted `encryptedVaultKey` on success.
|
|
@@ -351,62 +394,59 @@ class PasskeyController extends base_controller_1.BaseController {
|
|
|
351
394
|
* pass the same `authenticationResponse` you just verified (e.g. from
|
|
352
395
|
* {@link retrieveVaultKeyWithPasskey} / {@link verifyPasskeyAuthentication}).
|
|
353
396
|
*
|
|
354
|
-
* For password change with passkey step-up, prefer
|
|
355
|
-
* {@link changePasswordWithPasskeyVerification}, which orchestrates keyring export,
|
|
356
|
-
* `changePassword`, and re-wrap in one call.
|
|
357
|
-
*
|
|
358
397
|
* @param params - Re-wrap inputs.
|
|
359
398
|
* @param params.authenticationResponse - Used to derive the wrapping key.
|
|
360
399
|
* @param params.oldVaultKey - Expected current vault key.
|
|
361
400
|
* @param params.newVaultKey - New vault key to encrypt under the passkey.
|
|
362
|
-
* @returns Resolves when the passkey record is updated.
|
|
363
401
|
*/
|
|
364
402
|
async renewVaultKeyProtection(params) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
403
|
+
const { authenticationResponse } = params;
|
|
404
|
+
const passkeyRecord = __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_requireEnrolled).call(this);
|
|
405
|
+
// derive key
|
|
406
|
+
const encKey = (0, key_derivation_1.deriveKeyFromAuthenticationResponse)(authenticationResponse, passkeyRecord);
|
|
407
|
+
// decrypt vault key
|
|
408
|
+
let decryptedVaultKey;
|
|
409
|
+
try {
|
|
410
|
+
decryptedVaultKey = (0, crypto_1.decryptWithKey)(passkeyRecord.encryptedVaultKey.ciphertext, passkeyRecord.encryptedVaultKey.iv, encKey);
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
log('Error decrypting vault key during passkey vault key renewal', error instanceof Error ? error : new Error(String(error)));
|
|
414
|
+
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.VaultKeyDecryptionFailed, {
|
|
415
|
+
code: constants_1.PasskeyControllerErrorCode.VaultKeyDecryptionFailed,
|
|
416
|
+
cause: error instanceof Error ? error : new Error(String(error)),
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
// check if vault key matches
|
|
420
|
+
const { oldVaultKey, newVaultKey } = params;
|
|
421
|
+
if (!(0, utils_1.areUint8ArraysEqual)((0, utils_1.stringToBytes)(decryptedVaultKey), (0, utils_1.stringToBytes)(oldVaultKey))) {
|
|
422
|
+
log('Passkey renewal rejected: decrypted vault key does not match oldVaultKey');
|
|
423
|
+
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.VaultKeyMismatch, { code: constants_1.PasskeyControllerErrorCode.VaultKeyMismatch });
|
|
424
|
+
}
|
|
425
|
+
// encrypt new vault key
|
|
426
|
+
const { ciphertext, iv } = (0, crypto_1.encryptWithKey)(newVaultKey, encKey);
|
|
427
|
+
// persist passkey record (mutate current state only for vault key material)
|
|
428
|
+
this.update((state) => {
|
|
429
|
+
if (!state.passkeyRecord) {
|
|
430
|
+
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.NotEnrolled, {
|
|
431
|
+
code: constants_1.PasskeyControllerErrorCode.NotEnrolled,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
state.passkeyRecord.encryptedVaultKey = { ciphertext, iv };
|
|
435
|
+
});
|
|
391
436
|
}
|
|
392
437
|
/**
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
* @param password - Wallet password for step-up verification.
|
|
396
|
-
* @returns Resolves when the passkey is removed.
|
|
438
|
+
* Clears enrolled passkey state and in-flight ceremonies. Call only after the same
|
|
439
|
+
* auth gate as renewal (verified passkey assertion or password).
|
|
397
440
|
*/
|
|
398
|
-
|
|
399
|
-
|
|
441
|
+
removePasskey() {
|
|
442
|
+
this.update(() => getDefaultPasskeyControllerState());
|
|
443
|
+
__classPrivateFieldGet(this, _PasskeyController_ceremonyManager, "f").clear();
|
|
400
444
|
}
|
|
401
445
|
/**
|
|
402
446
|
* Resets state and clears in-flight registration/authentication ceremonies.
|
|
403
|
-
*
|
|
404
|
-
* For user-facing passkey removal with step-up, use
|
|
405
|
-
* {@link removePasskeyWithPasskeyVerification} or
|
|
406
|
-
* {@link removePasskeyWithPasswordVerification}.
|
|
407
447
|
*/
|
|
408
448
|
clearState() {
|
|
409
|
-
|
|
449
|
+
this.removePasskey();
|
|
410
450
|
}
|
|
411
451
|
/**
|
|
412
452
|
* Releases all in-flight ceremony state and tears down the messenger.
|
|
@@ -417,188 +457,16 @@ class PasskeyController extends base_controller_1.BaseController {
|
|
|
417
457
|
}
|
|
418
458
|
}
|
|
419
459
|
exports.PasskeyController = PasskeyController;
|
|
420
|
-
_PasskeyController_ceremonyManager = new WeakMap(), _PasskeyController_expectedRPIDs = new WeakMap(), _PasskeyController_rpId = new WeakMap(), _PasskeyController_rpName = new WeakMap(), _PasskeyController_expectedOrigin = new WeakMap(), _PasskeyController_userName = new WeakMap(), _PasskeyController_userDisplayName = new WeakMap(),
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const vaultKey = await this.messenger.call('KeyringController:exportEncryptionKey');
|
|
426
|
-
const { registrationResponse, authenticationResponse } = params;
|
|
427
|
-
// get registration ceremony
|
|
428
|
-
const challenge = __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_getChallengeFromClientData).call(this, registrationResponse.response.clientDataJSON);
|
|
429
|
-
const registrationCeremony = __classPrivateFieldGet(this, _PasskeyController_ceremonyManager, "f").getRegistrationCeremony(challenge);
|
|
430
|
-
if (!registrationCeremony) {
|
|
431
|
-
log('No active passkey registration ceremony for challenge');
|
|
432
|
-
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.NoRegistrationCeremony, { code: constants_1.PasskeyControllerErrorCode.NoRegistrationCeremony });
|
|
433
|
-
}
|
|
434
|
-
try {
|
|
435
|
-
// verify registration response
|
|
436
|
-
const { verified, registrationInfo } = await (0, verify_registration_response_1.verifyRegistrationResponse)({
|
|
437
|
-
response: registrationResponse,
|
|
438
|
-
expectedChallenge: registrationCeremony.challenge,
|
|
439
|
-
expectedOrigin: __classPrivateFieldGet(this, _PasskeyController_expectedOrigin, "f"),
|
|
440
|
-
expectedRPIDs: __classPrivateFieldGet(this, _PasskeyController_expectedRPIDs, "f"),
|
|
441
|
-
requireUserVerification: true,
|
|
442
|
-
}).catch((error) => {
|
|
443
|
-
log('Error verifying passkey registration response', error);
|
|
444
|
-
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.RegistrationVerificationFailed, {
|
|
445
|
-
code: constants_1.PasskeyControllerErrorCode.RegistrationVerificationFailed,
|
|
446
|
-
cause: error instanceof Error ? error : new Error(String(error)),
|
|
447
|
-
});
|
|
448
|
-
});
|
|
449
|
-
if (!verified || !registrationInfo) {
|
|
450
|
-
log('Passkey registration verification returned unverified or missing registration info');
|
|
451
|
-
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.RegistrationVerificationFailed, { code: constants_1.PasskeyControllerErrorCode.RegistrationVerificationFailed });
|
|
452
|
-
}
|
|
453
|
-
// verify authentication response
|
|
454
|
-
const credential = {
|
|
455
|
-
id: registrationInfo.credentialId,
|
|
456
|
-
publicKey: (0, encoding_1.bytesToBase64URL)(registrationInfo.publicKey),
|
|
457
|
-
counter: registrationInfo.counter,
|
|
458
|
-
transports: registrationInfo.transports,
|
|
459
|
-
aaguid: registrationInfo.aaguid,
|
|
460
|
-
};
|
|
461
|
-
const { newCounter } = await __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_verifyAuthenticationResponse).call(this, authenticationResponse, credential);
|
|
462
|
-
// determine key derivation method
|
|
463
|
-
const prfFirst = authenticationResponse.clientExtensionResults?.prf?.results?.first;
|
|
464
|
-
const authHasPrfOutput = typeof prfFirst === 'string' && prfFirst.length > 0;
|
|
465
|
-
const keyDerivation = authHasPrfOutput && registrationCeremony.prfSalt
|
|
466
|
-
? { method: 'prf', prfSalt: registrationCeremony.prfSalt }
|
|
467
|
-
: { method: 'userHandle' };
|
|
468
|
-
if (keyDerivation.method === 'userHandle' &&
|
|
469
|
-
authenticationResponse.response.userHandle !==
|
|
470
|
-
registrationCeremony.userHandle) {
|
|
471
|
-
log('Post-registration assertion userHandle does not match registration ceremony');
|
|
472
|
-
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.AuthenticationVerificationFailed, { code: constants_1.PasskeyControllerErrorCode.AuthenticationVerificationFailed });
|
|
473
|
-
}
|
|
474
|
-
// derive key and encrypt vault key
|
|
475
|
-
const encKey = (0, key_derivation_1.deriveKeyFromAuthenticationResponse)(authenticationResponse, { credential, keyDerivation });
|
|
476
|
-
const { ciphertext, iv } = (0, crypto_1.encryptWithKey)(vaultKey, encKey);
|
|
477
|
-
// persist passkey record
|
|
478
|
-
this.update((state) => {
|
|
479
|
-
state.passkeyRecord = {
|
|
480
|
-
credential: {
|
|
481
|
-
...credential,
|
|
482
|
-
counter: Math.max(newCounter, credential.counter),
|
|
483
|
-
},
|
|
484
|
-
encryptedVaultKey: { ciphertext, iv },
|
|
485
|
-
keyDerivation,
|
|
486
|
-
};
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
finally {
|
|
490
|
-
// delete registration ceremony
|
|
491
|
-
__classPrivateFieldGet(this, _PasskeyController_ceremonyManager, "f").deleteRegistrationCeremony(challenge);
|
|
492
|
-
}
|
|
493
|
-
}, _PasskeyController_retrieveVaultKeyWithPasskey = async function _PasskeyController_retrieveVaultKeyWithPasskey(authenticationResponse) {
|
|
494
|
-
const passkeyRecord = __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_requireEnrolled).call(this);
|
|
495
|
-
// verify authentication response and update counter
|
|
496
|
-
const { newCounter } = await __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_verifyAuthenticationResponse).call(this, authenticationResponse, passkeyRecord.credential);
|
|
497
|
-
this.update((state) => {
|
|
498
|
-
if (!state.passkeyRecord) {
|
|
499
|
-
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.NotEnrolled, { code: constants_1.PasskeyControllerErrorCode.NotEnrolled });
|
|
500
|
-
}
|
|
501
|
-
state.passkeyRecord.credential.counter = Math.max(newCounter, state.passkeyRecord.credential.counter);
|
|
502
|
-
});
|
|
503
|
-
// derive key
|
|
504
|
-
const encKey = (0, key_derivation_1.deriveKeyFromAuthenticationResponse)(authenticationResponse, passkeyRecord);
|
|
505
|
-
// decrypt vault key
|
|
506
|
-
try {
|
|
507
|
-
const vaultKey = (0, crypto_1.decryptWithKey)(passkeyRecord.encryptedVaultKey.ciphertext, passkeyRecord.encryptedVaultKey.iv, encKey);
|
|
508
|
-
return vaultKey;
|
|
509
|
-
}
|
|
510
|
-
catch (cause) {
|
|
511
|
-
log('Error decrypting vault key with passkey', cause instanceof Error ? cause : new Error(String(cause)));
|
|
512
|
-
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.VaultKeyDecryptionFailed, {
|
|
513
|
-
code: constants_1.PasskeyControllerErrorCode.VaultKeyDecryptionFailed,
|
|
514
|
-
cause: cause instanceof Error ? cause : new Error(String(cause)),
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
}, _PasskeyController_verifyPasskeyAuthentication = async function _PasskeyController_verifyPasskeyAuthentication(authenticationResponse) {
|
|
518
|
-
try {
|
|
519
|
-
await __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_retrieveVaultKeyWithPasskey).call(this, authenticationResponse);
|
|
520
|
-
return true;
|
|
521
|
-
}
|
|
522
|
-
catch (error) {
|
|
523
|
-
if (error instanceof errors_1.PasskeyControllerError && error.code !== undefined) {
|
|
524
|
-
return false;
|
|
525
|
-
}
|
|
526
|
-
throw error;
|
|
527
|
-
}
|
|
528
|
-
}, _PasskeyController_renewVaultKeyProtection = async function _PasskeyController_renewVaultKeyProtection(params) {
|
|
529
|
-
const { authenticationResponse } = params;
|
|
530
|
-
const passkeyRecord = __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_requireEnrolled).call(this);
|
|
531
|
-
// derive key
|
|
532
|
-
const encKey = (0, key_derivation_1.deriveKeyFromAuthenticationResponse)(authenticationResponse, passkeyRecord);
|
|
533
|
-
// decrypt vault key
|
|
534
|
-
let decryptedVaultKey;
|
|
535
|
-
try {
|
|
536
|
-
decryptedVaultKey = (0, crypto_1.decryptWithKey)(passkeyRecord.encryptedVaultKey.ciphertext, passkeyRecord.encryptedVaultKey.iv, encKey);
|
|
537
|
-
}
|
|
538
|
-
catch (error) {
|
|
539
|
-
log('Error decrypting vault key during passkey vault key renewal', error instanceof Error ? error : new Error(String(error)));
|
|
540
|
-
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.VaultKeyDecryptionFailed, {
|
|
541
|
-
code: constants_1.PasskeyControllerErrorCode.VaultKeyDecryptionFailed,
|
|
542
|
-
cause: error instanceof Error ? error : new Error(String(error)),
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
// check if vault key matches
|
|
546
|
-
const { oldVaultKey, newVaultKey } = params;
|
|
547
|
-
if (!(0, utils_1.areUint8ArraysEqual)((0, utils_1.stringToBytes)(decryptedVaultKey), (0, utils_1.stringToBytes)(oldVaultKey))) {
|
|
548
|
-
log('Passkey renewal rejected: decrypted vault key does not match oldVaultKey');
|
|
549
|
-
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.VaultKeyMismatch, { code: constants_1.PasskeyControllerErrorCode.VaultKeyMismatch });
|
|
550
|
-
}
|
|
551
|
-
// encrypt new vault key
|
|
552
|
-
const { ciphertext, iv } = (0, crypto_1.encryptWithKey)(newVaultKey, encKey);
|
|
553
|
-
// persist passkey record (mutate current state only for vault key material)
|
|
554
|
-
this.update((state) => {
|
|
555
|
-
if (!state.passkeyRecord) {
|
|
556
|
-
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.NotEnrolled, {
|
|
557
|
-
code: constants_1.PasskeyControllerErrorCode.NotEnrolled,
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
state.passkeyRecord.encryptedVaultKey = { ciphertext, iv };
|
|
561
|
-
});
|
|
562
|
-
}, _PasskeyController_changePasswordWithPasskeyVerification = async function _PasskeyController_changePasswordWithPasskeyVerification(params) {
|
|
563
|
-
__classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_requireEnrolled).call(this);
|
|
564
|
-
const verified = await __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_verifyPasskeyAuthentication).call(this, params.authenticationResponse);
|
|
565
|
-
if (!verified) {
|
|
566
|
-
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.AuthenticationVerificationFailed, { code: constants_1.PasskeyControllerErrorCode.AuthenticationVerificationFailed });
|
|
567
|
-
}
|
|
568
|
-
const renewVaultKeyProtection = params.options?.renewVaultKeyProtection ?? true;
|
|
569
|
-
if (!renewVaultKeyProtection) {
|
|
570
|
-
await this.messenger.call('KeyringController:changePassword', params.newPassword);
|
|
571
|
-
__classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_removePasskey).call(this);
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
const vaultKeyBefore = await this.messenger.call('KeyringController:exportEncryptionKey');
|
|
575
|
-
await this.messenger.call('KeyringController:changePassword', params.newPassword);
|
|
576
|
-
try {
|
|
577
|
-
const vaultKeyAfter = await this.messenger.call('KeyringController:exportEncryptionKey');
|
|
578
|
-
await __classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_renewVaultKeyProtection).call(this, {
|
|
579
|
-
authenticationResponse: params.authenticationResponse,
|
|
580
|
-
oldVaultKey: vaultKeyBefore,
|
|
581
|
-
newVaultKey: vaultKeyAfter,
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
catch (error) {
|
|
585
|
-
__classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_removePasskey).call(this);
|
|
586
|
-
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.VaultKeyRenewalFailed, {
|
|
587
|
-
code: constants_1.PasskeyControllerErrorCode.VaultKeyRenewalFailed,
|
|
588
|
-
cause: error instanceof Error ? error : new Error(String(error)),
|
|
460
|
+
_PasskeyController_ceremonyManager = new WeakMap(), _PasskeyController_expectedRPIDs = new WeakMap(), _PasskeyController_rpId = new WeakMap(), _PasskeyController_rpName = new WeakMap(), _PasskeyController_expectedOrigin = new WeakMap(), _PasskeyController_userName = new WeakMap(), _PasskeyController_userDisplayName = new WeakMap(), _PasskeyController_instances = new WeakSet(), _PasskeyController_requireEnrolled = function _PasskeyController_requireEnrolled() {
|
|
461
|
+
const record = this.state.passkeyRecord;
|
|
462
|
+
if (!record) {
|
|
463
|
+
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.NotEnrolled, {
|
|
464
|
+
code: constants_1.PasskeyControllerErrorCode.NotEnrolled,
|
|
589
465
|
});
|
|
590
466
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if (!verified) {
|
|
595
|
-
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.AuthenticationVerificationFailed, { code: constants_1.PasskeyControllerErrorCode.AuthenticationVerificationFailed });
|
|
596
|
-
}
|
|
597
|
-
__classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_removePasskey).call(this);
|
|
598
|
-
}, _PasskeyController_removePasskeyWithPasswordVerification = async function _PasskeyController_removePasskeyWithPasswordVerification(password) {
|
|
599
|
-
__classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_requireEnrolled).call(this);
|
|
600
|
-
await this.messenger.call('KeyringController:verifyPassword', password);
|
|
601
|
-
__classPrivateFieldGet(this, _PasskeyController_instances, "m", _PasskeyController_removePasskey).call(this);
|
|
467
|
+
return record;
|
|
468
|
+
}, _PasskeyController_getChallengeFromClientData = function _PasskeyController_getChallengeFromClientData(clientDataJSON) {
|
|
469
|
+
return (0, decode_client_data_json_1.decodeClientDataJSON)(clientDataJSON).challenge;
|
|
602
470
|
}, _PasskeyController_verifyAuthenticationResponse =
|
|
603
471
|
/**
|
|
604
472
|
* Validates a WebAuthn authentication response against stored credential data.
|
|
@@ -649,35 +517,5 @@ async function _PasskeyController_verifyAuthenticationResponse(authenticationRes
|
|
|
649
517
|
// delete authentication ceremony
|
|
650
518
|
__classPrivateFieldGet(this, _PasskeyController_ceremonyManager, "f").deleteAuthenticationCeremony(challenge);
|
|
651
519
|
}
|
|
652
|
-
}, _PasskeyController_withOperationLock =
|
|
653
|
-
/**
|
|
654
|
-
* Serializes orchestrated passkey operations that mutate state or call KeyringController.
|
|
655
|
-
*
|
|
656
|
-
* @param callback - Operation to run while the mutex is held.
|
|
657
|
-
* @returns The result of the callback.
|
|
658
|
-
*/
|
|
659
|
-
async function _PasskeyController_withOperationLock(callback) {
|
|
660
|
-
return __classPrivateFieldGet(this, _PasskeyController_operationMutex, "f").runExclusive(callback);
|
|
661
|
-
}, _PasskeyController_assertEnrollmentAllowed = async function _PasskeyController_assertEnrollmentAllowed(password) {
|
|
662
|
-
if (!__classPrivateFieldGet(this, _PasskeyController_getIsOnboardingCompleted, "f").call(this)) {
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
if (!password) {
|
|
666
|
-
throw new Error(constants_1.PasskeyControllerErrorMessage.EnrollmentPasswordRequired);
|
|
667
|
-
}
|
|
668
|
-
await this.messenger.call('KeyringController:verifyPassword', password);
|
|
669
|
-
}, _PasskeyController_requireEnrolled = function _PasskeyController_requireEnrolled() {
|
|
670
|
-
const record = this.state.passkeyRecord;
|
|
671
|
-
if (!record) {
|
|
672
|
-
throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.NotEnrolled, {
|
|
673
|
-
code: constants_1.PasskeyControllerErrorCode.NotEnrolled,
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
return record;
|
|
677
|
-
}, _PasskeyController_getChallengeFromClientData = function _PasskeyController_getChallengeFromClientData(clientDataJSON) {
|
|
678
|
-
return (0, decode_client_data_json_1.decodeClientDataJSON)(clientDataJSON).challenge;
|
|
679
|
-
}, _PasskeyController_removePasskey = function _PasskeyController_removePasskey() {
|
|
680
|
-
this.update(() => getDefaultPasskeyControllerState());
|
|
681
|
-
__classPrivateFieldGet(this, _PasskeyController_ceremonyManager, "f").clear();
|
|
682
520
|
};
|
|
683
521
|
//# sourceMappingURL=PasskeyController.cjs.map
|