@metamask-previews/mfa-recovery-controller 0.0.0-preview-21968aee3 → 0.0.0-preview-c5fdaa9c8
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 +5 -1
- package/dist/MfaRecoveryController.d.ts.map +1 -1
- package/dist/MfaRecoveryController.js +52 -19
- package/dist/MfaRecoveryController.js.map +1 -1
- package/dist/auth-controller-token.d.ts +36 -0
- package/dist/auth-controller-token.d.ts.map +1 -0
- package/dist/auth-controller-token.js +36 -0
- package/dist/auth-controller-token.js.map +1 -0
- package/dist/crypto.d.ts +18 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +26 -5
- package/dist/crypto.js.map +1 -1
- package/dist/errors.d.ts +9 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +12 -0
- package/dist/errors.js.map +1 -1
- package/dist/escrow-providers/cubist-escrow-provider.d.ts +5 -3
- package/dist/escrow-providers/cubist-escrow-provider.d.ts.map +1 -1
- package/dist/escrow-providers/cubist-escrow-provider.js +21 -16
- package/dist/escrow-providers/cubist-escrow-provider.js.map +1 -1
- package/dist/identifier-auth.d.ts +7 -30
- package/dist/identifier-auth.d.ts.map +1 -1
- package/dist/identifier-auth.js +5 -29
- package/dist/identifier-auth.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pending-operation-validation.d.ts.map +1 -1
- package/dist/pending-operation-validation.js +6 -24
- package/dist/pending-operation-validation.js.map +1 -1
- package/dist/types.d.ts +25 -18
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +11 -11
package/CHANGELOG.md
CHANGED
|
@@ -22,11 +22,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
22
22
|
- `MutationReceipt` includes `receiptKeyId`. `RecoveryEscrowProvider.verifyReceipt` takes the expected escrow id and verifies with that replica's receipt key
|
|
23
23
|
- `AuthControllerToken.expiresAt` and `PoPChallenge.expiresAt` are Unix seconds
|
|
24
24
|
- Mutations require idle pending state (`resume()` / `abort()` first). `abort()` drops `authorizing` only; `writing` must be finished with `resume()`
|
|
25
|
-
- `CubistEscrowProvider` invokes Cubist escrow commands through `@cubist-labs/cubesigner-sdk` (`generateChallenge`, `applyMutation`, `getSecret` on `cubist_secret_escrow`) and verifies receipts with a pinned receipt public key
|
|
25
|
+
- `CubistEscrowProvider` invokes Cubist escrow commands through `@cubist-labs/cubesigner-sdk` `policyInvoke` (`ping`, `generateChallenge`, `applyMutation`, `getSecret` on `cubist_secret_escrow`) and verifies receipts with a pinned receipt public key
|
|
26
26
|
- `authenticateIdentifier` returns an in-memory `IdentifierSession` for key-bound recovery-secret reads
|
|
27
27
|
|
|
28
28
|
### Changed
|
|
29
29
|
|
|
30
30
|
- **BREAKING:** `getRecoverySecret` now accepts an `IdentifierSession` returned by `authenticateIdentifier` instead of an `Identifier`
|
|
31
31
|
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Clear a pending mutation when every escrow rejects it before applying, so a second `register` or an update with a stale epoch does not stay in `writing`. `resume()` still retries an incomplete write, and `abort()` still refuses a `writing` mutation that may have been applied
|
|
35
|
+
|
|
32
36
|
[Unreleased]: https://github.com/MetaMask/core/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MfaRecoveryController.d.ts","sourceRoot":"","sources":["../src/MfaRecoveryController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAE5B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"MfaRecoveryController.d.ts","sourceRoot":"","sources":["../src/MfaRecoveryController.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { BaseController } from '@metamask/base-controller';
|
|
2
2
|
import { bytesToHex } from '@metamask/utils';
|
|
3
|
+
import { decodeAuthControllerToken } from './auth-controller-token.js';
|
|
3
4
|
import { decodeHex, decryptFromPublic, encryptToPublic, generateSigningKey, hash, randomId, unixNow, wrapKeyId, } from './crypto.js';
|
|
4
|
-
import { IncompleteMutationError, MfaRecoveryError } from './errors.js';
|
|
5
|
+
import { IncompleteMutationError, MfaRecoveryError, MutationRejectedError, } from './errors.js';
|
|
5
6
|
import { isFulfilledResult, isMutationReceipt, selectHighestConsistentVersion, verifyMutationReceipt, } from './escrow-utils.js';
|
|
6
|
-
import {
|
|
7
|
+
import { authorizeEscrowsWithToken, countDistinctIdentifiers, getIdentifierAuthMode, MIN_IDENTIFIERS, requestKeyBoundIdentifierToken, } from './identifier-auth.js';
|
|
7
8
|
import { assertValidPendingOperation, pendingPayloadHasIdentifiers, pendingPayloadHasRecoverySecret, } from './pending-operation-validation.js';
|
|
8
9
|
import { assertAbortAllowed, getRecoveryPhase } from './state-machine.js';
|
|
9
10
|
const CONTROLLER_NAME = 'MfaRecoveryController';
|
|
@@ -137,14 +138,13 @@ export class MfaRecoveryController extends BaseController {
|
|
|
137
138
|
requestId,
|
|
138
139
|
pkE,
|
|
139
140
|
});
|
|
140
|
-
const
|
|
141
|
+
const credential = await requestKeyBoundIdentifierToken({
|
|
141
142
|
identifier,
|
|
142
143
|
requestHash,
|
|
143
144
|
identifierAuthProvider: this.#identifierAuthProvider,
|
|
144
145
|
});
|
|
145
146
|
return {
|
|
146
|
-
|
|
147
|
-
proofPrivateKey,
|
|
147
|
+
...credential,
|
|
148
148
|
requestId,
|
|
149
149
|
ephemeralPrivateKey: ephemeral.privateKey,
|
|
150
150
|
pkE,
|
|
@@ -175,10 +175,9 @@ export class MfaRecoveryController extends BaseController {
|
|
|
175
175
|
if (available.length === 0) {
|
|
176
176
|
throw new MfaRecoveryError('No escrow is available', 'no_available_escrow');
|
|
177
177
|
}
|
|
178
|
-
const authorizedEscrows = await
|
|
178
|
+
const authorizedEscrows = await authorizeEscrowsWithToken({
|
|
179
179
|
escrows: available,
|
|
180
|
-
|
|
181
|
-
proofPrivateKey: session.proofPrivateKey,
|
|
180
|
+
credential: session,
|
|
182
181
|
});
|
|
183
182
|
const results = await Promise.allSettled(authorizedEscrows.map(async ({ escrow, authorization }) => {
|
|
184
183
|
const response = await escrow.getSecret(authorization, requestId, pkE);
|
|
@@ -231,6 +230,7 @@ export class MfaRecoveryController extends BaseController {
|
|
|
231
230
|
if (await this.#loadPending()) {
|
|
232
231
|
throw new MfaRecoveryError('A pending mutation must be resumed or aborted first', 'pending_mutation');
|
|
233
232
|
}
|
|
233
|
+
await this.#assertEscrowsAvailable(this.#escrows);
|
|
234
234
|
const profileId = await this.#authProvider.getAuthenticatedProfileId();
|
|
235
235
|
const audiences = this.#escrows.map((escrow) => escrow.id);
|
|
236
236
|
const currentVersion = this.#resolveCurrentRecoveryVersion(epoch);
|
|
@@ -267,6 +267,7 @@ export class MfaRecoveryController extends BaseController {
|
|
|
267
267
|
async #authorizeMutation(mutation, payload) {
|
|
268
268
|
return await this.#authProvider.authorizeRecoveryRequest({
|
|
269
269
|
requestHash: mutation.requestHash,
|
|
270
|
+
audiences: this.#escrows.map((escrow) => escrow.authAudience),
|
|
270
271
|
...(mutation.operation === 'register' ? {} : { requireTwoFactor: true }),
|
|
271
272
|
...(pendingPayloadHasIdentifiers(payload)
|
|
272
273
|
? { identifiers: payload.identifiers }
|
|
@@ -279,6 +280,7 @@ export class MfaRecoveryController extends BaseController {
|
|
|
279
280
|
return;
|
|
280
281
|
}
|
|
281
282
|
if (pending.phase === 'authorizing') {
|
|
283
|
+
await this.#assertEscrowsAvailable(this.#escrows);
|
|
282
284
|
await this.#replicateMutation({
|
|
283
285
|
...pending,
|
|
284
286
|
phase: 'writing',
|
|
@@ -297,17 +299,15 @@ export class MfaRecoveryController extends BaseController {
|
|
|
297
299
|
await this.#clearPending();
|
|
298
300
|
return;
|
|
299
301
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
? await this.#authorizeMutation(mutation, payload)
|
|
306
|
-
: pending.authControllerToken;
|
|
302
|
+
await this.#assertEscrowsAvailable(remainingEscrows);
|
|
303
|
+
const claims = decodeAuthControllerToken(pending.authControllerToken);
|
|
304
|
+
const authControllerToken = claims && claims.exp > this.#now()
|
|
305
|
+
? pending.authControllerToken
|
|
306
|
+
: await this.#authorizeMutation(mutation, payload);
|
|
307
307
|
const writing = { ...pending, authControllerToken };
|
|
308
308
|
const authorizedEscrows = mutation.operation === 'register'
|
|
309
309
|
? null
|
|
310
|
-
: await this.#
|
|
310
|
+
: await this.#authorizeEscrows({
|
|
311
311
|
escrows: remainingEscrows,
|
|
312
312
|
identifier: identifier,
|
|
313
313
|
requestHash: mutation.requestHash,
|
|
@@ -320,6 +320,11 @@ export class MfaRecoveryController extends BaseController {
|
|
|
320
320
|
await this.#persistPending(writing);
|
|
321
321
|
const results = await Promise.allSettled(remainingEscrows.map((escrow, index) => escrow.applyMutation(mutation, writing.authControllerToken, authorizedEscrows?.[index]?.authorization ?? null, payloads[index])));
|
|
322
322
|
const { receipts, hasInvalidReceipt } = this.#collectMutationReceipts(results, remainingEscrows, mutation, writing.receipts);
|
|
323
|
+
const rejection = unanimousMutationRejection(results, writing.receipts);
|
|
324
|
+
if (rejection) {
|
|
325
|
+
await this.#clearPending();
|
|
326
|
+
throw rejection;
|
|
327
|
+
}
|
|
323
328
|
await this.#persistPending({ ...writing, receipts });
|
|
324
329
|
if (hasInvalidReceipt) {
|
|
325
330
|
throw new MfaRecoveryError('Invalid mutation receipt', 'invalid_receipt');
|
|
@@ -330,6 +335,13 @@ export class MfaRecoveryController extends BaseController {
|
|
|
330
335
|
}
|
|
331
336
|
await this.#clearPending();
|
|
332
337
|
}
|
|
338
|
+
async #assertEscrowsAvailable(escrows) {
|
|
339
|
+
const availableEscrows = await this.#getAvailableEscrows(escrows);
|
|
340
|
+
// Do we want to wait for all escrows to be available?
|
|
341
|
+
if (availableEscrows.length !== escrows.length) {
|
|
342
|
+
throw new MfaRecoveryError('All configured escrows are required for mutation', 'escrow_unavailable');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
333
345
|
#getRemainingEscrows(pending) {
|
|
334
346
|
const { mutation } = pending;
|
|
335
347
|
const remainingEscrows = [];
|
|
@@ -366,14 +378,14 @@ export class MfaRecoveryController extends BaseController {
|
|
|
366
378
|
});
|
|
367
379
|
return { receipts, hasInvalidReceipt };
|
|
368
380
|
}
|
|
369
|
-
async #
|
|
381
|
+
async #authorizeEscrows({ escrows, identifier, requestHash, }) {
|
|
370
382
|
getIdentifierAuthMode(identifier.type);
|
|
371
|
-
|
|
372
|
-
escrows,
|
|
383
|
+
const credential = await requestKeyBoundIdentifierToken({
|
|
373
384
|
identifier,
|
|
374
385
|
requestHash,
|
|
375
386
|
identifierAuthProvider: this.#identifierAuthProvider,
|
|
376
387
|
});
|
|
388
|
+
return await authorizeEscrowsWithToken({ escrows, credential });
|
|
377
389
|
}
|
|
378
390
|
#payloadForEscrow(pending, escrow) {
|
|
379
391
|
const { payload } = pending;
|
|
@@ -453,4 +465,25 @@ export class MfaRecoveryController extends BaseController {
|
|
|
453
465
|
return await run;
|
|
454
466
|
}
|
|
455
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* Every remaining escrow refused the mutation, and none has a receipt.
|
|
470
|
+
*
|
|
471
|
+
* @param results - Settled apply results for the remaining escrows.
|
|
472
|
+
* @param existingReceipts - Receipts already stored for this mutation.
|
|
473
|
+
* @returns The first refusal, or `null` when the write may have been applied.
|
|
474
|
+
*/
|
|
475
|
+
function unanimousMutationRejection(results, existingReceipts) {
|
|
476
|
+
if (existingReceipts.length > 0 || results.length === 0) {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
let rejection = null;
|
|
480
|
+
for (const result of results) {
|
|
481
|
+
if (result.status !== 'rejected' ||
|
|
482
|
+
!(result.reason instanceof MutationRejectedError)) {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
rejection ??= result.reason;
|
|
486
|
+
}
|
|
487
|
+
return rejection;
|
|
488
|
+
}
|
|
456
489
|
//# sourceMappingURL=MfaRecoveryController.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MfaRecoveryController.js","sourceRoot":"","sources":["../src/MfaRecoveryController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport { bytesToHex } from '@metamask/utils';\n\nimport {\n decodeHex,\n decryptFromPublic,\n encryptToPublic,\n generateSigningKey,\n hash,\n randomId,\n unixNow,\n wrapKeyId,\n} from './crypto.js';\nimport { IncompleteMutationError, MfaRecoveryError } from './errors.js';\nimport {\n isFulfilledResult,\n isMutationReceipt,\n selectHighestConsistentVersion,\n verifyMutationReceipt,\n} from './escrow-utils.js';\nimport {\n authorizeKeyBoundIdentifier,\n authorizeWithKeyBoundToken,\n countDistinctIdentifiers,\n getIdentifierAuthMode,\n MIN_IDENTIFIERS,\n requestKeyBoundIdentifierToken,\n} from './identifier-auth.js';\nimport type { AuthorizedEscrow } from './identifier-auth.js';\nimport type { MfaRecoveryControllerMethodActions } from './MfaRecoveryController-method-action-types.js';\nimport {\n assertValidPendingOperation,\n pendingPayloadHasIdentifiers,\n pendingPayloadHasRecoverySecret,\n} from './pending-operation-validation.js';\nimport { assertAbortAllowed, getRecoveryPhase } from './state-machine.js';\nimport type {\n AuthControllerToken,\n EcPublicJwk,\n Identifier,\n IdentifierSession,\n Mutation,\n MutationPayload,\n MutationReceipt,\n PendingMutationPayload,\n PendingOperation,\n PendingOperationEncryptor,\n PendingRegisterPayload,\n PendingUpdateIdentifiersPayload,\n PendingUpdateRecoverySecretPayload,\n RecoveryAuthProvider,\n RecoveryEscrowProvider,\n RecoveryIdentifierAuthProvider,\n RecoveryPhase,\n RecoveredSecret,\n WrappedSecret,\n WritingPendingOperation,\n} from './types.js';\n\nconst CONTROLLER_NAME = 'MfaRecoveryController';\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'register',\n 'updateRecoverySecret',\n 'updateIdentifiers',\n 'authenticateIdentifier',\n 'getRecoverySecret',\n 'resume',\n 'abort',\n 'getPhase',\n] as const;\n\nexport type MfaRecoveryControllerState = {\n /**\n * Encrypted pending mutation, or `null` when idle.\n */\n pendingOperation: string | null;\n};\n\nconst mfaRecoveryControllerMetadata = {\n pendingOperation: {\n persist: true,\n includeInDebugSnapshot: false,\n includeInStateLogs: false,\n usedInUi: true,\n },\n} satisfies StateMetadata<MfaRecoveryControllerState>;\n\n/**\n * @returns The default {@link MfaRecoveryController} state.\n */\nexport function getDefaultMfaRecoveryControllerState(): MfaRecoveryControllerState {\n return { pendingOperation: null };\n}\n\nexport type MfaRecoveryControllerGetStateAction = ControllerGetStateAction<\n typeof CONTROLLER_NAME,\n MfaRecoveryControllerState\n>;\n\nexport type MfaRecoveryControllerActions =\n | MfaRecoveryControllerGetStateAction\n | MfaRecoveryControllerMethodActions;\n\ntype AllowedActions = never;\n\nexport type MfaRecoveryControllerStateChangedEvent =\n ControllerStateChangedEvent<\n typeof CONTROLLER_NAME,\n MfaRecoveryControllerState\n >;\n\nexport type MfaRecoveryControllerEvents =\n MfaRecoveryControllerStateChangedEvent;\n\ntype AllowedEvents = never;\n\nexport type MfaRecoveryControllerMessenger = Messenger<\n typeof CONTROLLER_NAME,\n MfaRecoveryControllerActions | AllowedActions,\n MfaRecoveryControllerEvents | AllowedEvents\n>;\n\nexport type MfaRecoveryControllerOptions = {\n /**\n * The messenger suited for this controller.\n */\n messenger: MfaRecoveryControllerMessenger;\n\n /**\n * The desired state with which to initialize this controller. Missing\n * properties will be filled in with defaults.\n */\n state?: Partial<MfaRecoveryControllerState>;\n\n /**\n * Authenticates the MetaMask profile and issues request-bound attestations.\n */\n authProvider: RecoveryAuthProvider;\n\n /**\n * Obtains key-bound identifier tokens from an IdP.\n */\n identifierAuthProvider: RecoveryIdentifierAuthProvider;\n\n /**\n * Ordered, non-empty set of recovery escrow replicas. This is build-time\n * configuration; public methods cannot override it.\n */\n escrows: RecoveryEscrowProvider[];\n\n /**\n * Encrypts pending mutation state using wallet secure storage.\n */\n pendingOperationEncryptor: PendingOperationEncryptor;\n\n /**\n * Clock used for AuthController token expiry checks. Unix seconds.\n * Defaults to {@link unixNow}.\n */\n now?: () => number;\n};\n\ntype MutateParams = {\n epoch: number;\n} & (\n | {\n operation: 'register';\n payload: PendingRegisterPayload;\n identifier: null;\n }\n | {\n operation: 'updateRecoverySecret';\n payload: PendingUpdateRecoverySecretPayload;\n identifier: Identifier;\n }\n | {\n operation: 'updateIdentifiers';\n payload: PendingUpdateIdentifiersPayload;\n identifier: Identifier;\n }\n);\n\n/**\n * Coordinates MFA recovery secret replication across independent escrows.\n */\nexport class MfaRecoveryController extends BaseController<\n typeof CONTROLLER_NAME,\n MfaRecoveryControllerState,\n MfaRecoveryControllerMessenger\n> {\n readonly #authProvider: RecoveryAuthProvider;\n\n readonly #identifierAuthProvider: RecoveryIdentifierAuthProvider;\n\n readonly #escrows: RecoveryEscrowProvider[];\n\n readonly #encryptor: PendingOperationEncryptor;\n\n readonly #now: () => number;\n\n #lock: Promise<void> = Promise.resolve();\n\n constructor({\n messenger,\n state,\n authProvider,\n identifierAuthProvider,\n escrows,\n pendingOperationEncryptor,\n now = unixNow,\n }: MfaRecoveryControllerOptions) {\n if (escrows.length === 0) {\n throw new MfaRecoveryError(\n 'At least one escrow is required',\n 'empty_escrow_set',\n );\n }\n const escrowIds = escrows.map((escrow) => escrow.id);\n if (new Set(escrowIds).size !== escrowIds.length) {\n throw new MfaRecoveryError('Duplicate escrow id', 'duplicate_escrow_id');\n }\n\n super({\n messenger,\n metadata: mfaRecoveryControllerMetadata,\n name: CONTROLLER_NAME,\n state: {\n ...getDefaultMfaRecoveryControllerState(),\n ...state,\n },\n });\n\n this.#authProvider = authProvider;\n this.#identifierAuthProvider = identifierAuthProvider;\n this.#escrows = [...escrows];\n this.#encryptor = pendingOperationEncryptor;\n this.#now = now;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Creates version 1 of a recovery record.\n *\n * @param recoverySecret - Secret replicated in full to every escrow.\n * @param identifiers - Ownership-approved identifier set. Must contain at\n * least two identifiers.\n */\n async register(\n recoverySecret: Uint8Array,\n identifiers: Identifier[],\n ): Promise<void> {\n this.#assertKnownIdentifierTypes(identifiers);\n this.#assertMinIdentifiers(identifiers);\n await this.#mutate({\n operation: 'register',\n payload: {\n identifiers,\n recoverySecret: bytesToHex(recoverySecret),\n },\n identifier: null,\n epoch: 0,\n });\n }\n\n /**\n * Replaces the recovery secret.\n *\n * @param identifier - Currently registered identifier used to authorize.\n * @param recoverySecret - New secret.\n * @param epoch - Current recovery version, used as `expectedVersion`.\n */\n async updateRecoverySecret(\n identifier: Identifier,\n recoverySecret: Uint8Array,\n epoch: number,\n ): Promise<void> {\n this.#assertKnownIdentifierTypes([identifier]);\n await this.#mutate({\n operation: 'updateRecoverySecret',\n payload: {\n recoverySecret: bytesToHex(recoverySecret),\n },\n identifier,\n epoch,\n });\n }\n\n /**\n * Replaces the complete identifier set.\n *\n * @param identifier - Currently registered identifier used to authorize.\n * @param identifiers - New identifier set. Must contain at least two\n * identifiers.\n * @param epoch - Current recovery version, used as `expectedVersion`.\n */\n async updateIdentifiers(\n identifier: Identifier,\n identifiers: Identifier[],\n epoch: number,\n ): Promise<void> {\n this.#assertKnownIdentifierTypes([identifier, ...identifiers]);\n this.#assertMinIdentifiers(identifiers);\n await this.#mutate({\n operation: 'updateIdentifiers',\n payload: { identifiers },\n identifier,\n epoch,\n });\n }\n\n /**\n * Authenticates an identifier for a subsequent recovery-secret read.\n *\n * @param identifier - Identifier to authenticate.\n * @returns Ephemeral session data required by {@link getRecoverySecret}.\n */\n async authenticateIdentifier(\n identifier: Identifier,\n ): Promise<IdentifierSession> {\n this.#assertKnownIdentifierTypes([identifier]);\n const requestId = randomId();\n const ephemeral = generateSigningKey();\n const pkE = JSON.parse(ephemeral.publicKey) as EcPublicJwk;\n const requestHash = hash({\n operation: 'getRecoverySecret',\n requestId,\n pkE,\n });\n const { token, proofPrivateKey } =\n await requestKeyBoundIdentifierToken({\n identifier,\n requestHash,\n identifierAuthProvider: this.#identifierAuthProvider,\n });\n return {\n token,\n proofPrivateKey,\n requestId,\n ephemeralPrivateKey: ephemeral.privateKey,\n pkE,\n };\n }\n\n /**\n * Reads the recovery secret from available escrows and returns the highest\n * consistent version. `epoch` is the current recovery version and is required\n * by later mutations. Does not wait for a pending mutation to be repaired.\n *\n * @param session - Identifier authentication session used to authorize the\n * read.\n * @returns Recovered secret bytes and the selected epoch.\n */\n async getRecoverySecret(\n session: IdentifierSession,\n ): Promise<RecoveredSecret> {\n return await this.#withLock(async () => {\n const { token, requestId, pkE } = session;\n this.#assertKnownIdentifierTypes([token.identifier]);\n const requestHash = hash({\n operation: 'getRecoverySecret',\n requestId,\n pkE,\n });\n if (token.requestHash !== requestHash) {\n throw new MfaRecoveryError(\n 'Invalid identifier session',\n 'invalid_identifier_session',\n );\n }\n const available = await this.#getAvailableEscrows();\n if (available.length === 0) {\n throw new MfaRecoveryError(\n 'No escrow is available',\n 'no_available_escrow',\n );\n }\n const authorizedEscrows = await authorizeWithKeyBoundToken({\n escrows: available,\n token,\n proofPrivateKey: session.proofPrivateKey,\n });\n const results = await Promise.allSettled(\n authorizedEscrows.map(async ({ escrow, authorization }) => {\n const response = await escrow.getSecret(\n authorization,\n requestId,\n pkE,\n );\n if (response.wrapKeyId !== wrapKeyId(escrow.wrapPublicKey)) {\n throw new MfaRecoveryError(\n 'Unexpected wrap key',\n 'wrap_key_mismatch',\n );\n }\n return {\n escrowId: escrow.id,\n recoverySecret: decryptFromPublic(\n session.ephemeralPrivateKey,\n escrow.wrapPublicKey,\n response.recoverySecret.ciphertext,\n ),\n version: response.version,\n lastMutationId: response.lastMutationId,\n };\n }),\n );\n const selected = selectHighestConsistentVersion(results);\n return {\n recoverySecret: selected.recoverySecret,\n epoch: selected.version,\n };\n });\n }\n\n /**\n * Completes a persisted pending mutation, if any.\n */\n async resume(): Promise<void> {\n await this.#withLock(async () => {\n await this.#repairPendingMutation();\n });\n }\n\n /**\n * Drops `authorizing` pending. `writing` must be finished with {@link resume}.\n */\n async abort(): Promise<void> {\n await this.#withLock(async () => {\n const pending = await this.#loadPending();\n assertAbortAllowed(pending);\n if (pending) {\n await this.#clearPending();\n }\n });\n }\n\n /**\n * @returns Current recovery phase.\n */\n async getPhase(): Promise<RecoveryPhase> {\n return await this.#withLock(async () =>\n getRecoveryPhase(await this.#loadPending()),\n );\n }\n\n async #mutate(params: MutateParams): Promise<void> {\n const { operation, payload, identifier, epoch } = params;\n await this.#withLock(async () => {\n if (await this.#loadPending()) {\n throw new MfaRecoveryError(\n 'A pending mutation must be resumed or aborted first',\n 'pending_mutation',\n );\n }\n\n const profileId = await this.#authProvider.getAuthenticatedProfileId();\n const audiences = this.#escrows.map((escrow) => escrow.id);\n const currentVersion = this.#resolveCurrentRecoveryVersion(epoch);\n const payloadHash = hash(payload);\n\n const mutationFields = {\n id: randomId(),\n profileId,\n operation,\n expectedVersion: currentVersion,\n newVersion: currentVersion + 1,\n payloadHash,\n audiences,\n };\n const mutation: Mutation = {\n ...mutationFields,\n requestHash: hash(mutationFields),\n };\n\n const pending: PendingOperation = {\n phase: 'authorizing',\n mutation,\n payload,\n identifier,\n };\n await this.#persistPending(pending);\n\n const authControllerToken = await this.#authorizeMutation(\n mutation,\n payload,\n );\n await this.#replicateMutation({\n ...pending,\n phase: 'writing',\n authControllerToken,\n receipts: [],\n });\n });\n }\n\n async #authorizeMutation(\n mutation: Mutation,\n payload: PendingMutationPayload,\n ): Promise<AuthControllerToken> {\n return await this.#authProvider.authorizeRecoveryRequest({\n requestHash: mutation.requestHash,\n ...(mutation.operation === 'register' ? {} : { requireTwoFactor: true }),\n ...(pendingPayloadHasIdentifiers(payload)\n ? { identifiers: payload.identifiers }\n : {}),\n });\n }\n\n async #repairPendingMutation(): Promise<void> {\n const pending = await this.#loadPending();\n if (!pending) {\n return;\n }\n\n if (pending.phase === 'authorizing') {\n await this.#replicateMutation({\n ...pending,\n phase: 'writing',\n authControllerToken: await this.#authorizeMutation(\n pending.mutation,\n pending.payload,\n ),\n receipts: [],\n });\n return;\n }\n\n // writing phase\n await this.#replicateMutation(pending);\n }\n\n async #replicateMutation(pending: WritingPendingOperation): Promise<void> {\n const { mutation, payload, identifier } = pending;\n const remainingEscrows = this.#getRemainingEscrows(pending);\n if (remainingEscrows.length === 0) {\n await this.#clearPending();\n return;\n }\n\n const availableEscrows = await this.#getAvailableEscrows(remainingEscrows);\n if (availableEscrows.length !== remainingEscrows.length) {\n throw new MfaRecoveryError(\n 'All configured escrows are required for mutation',\n 'escrow_unavailable',\n );\n }\n\n const authControllerToken =\n pending.authControllerToken.expiresAt <= this.#now()\n ? await this.#authorizeMutation(mutation, payload)\n : pending.authControllerToken;\n const writing = { ...pending, authControllerToken };\n const authorizedEscrows =\n mutation.operation === 'register'\n ? null\n : await this.#authorizeIdentifier({\n escrows: remainingEscrows,\n identifier: identifier as Identifier,\n requestHash: mutation.requestHash,\n });\n if (\n authorizedEscrows !== null &&\n authorizedEscrows.length !== remainingEscrows.length\n ) {\n throw new MfaRecoveryError(\n 'Unable to authorize every escrow',\n 'identifier_auth_failed',\n );\n }\n const payloads = remainingEscrows.map((escrow) =>\n this.#payloadForEscrow(writing, escrow),\n );\n\n await this.#persistPending(writing);\n const results = await Promise.allSettled(\n remainingEscrows.map((escrow, index) =>\n escrow.applyMutation(\n mutation,\n writing.authControllerToken,\n authorizedEscrows?.[index]?.authorization ?? null,\n payloads[index],\n ),\n ),\n );\n\n const { receipts, hasInvalidReceipt } = this.#collectMutationReceipts(\n results,\n remainingEscrows,\n mutation,\n writing.receipts,\n );\n await this.#persistPending({ ...writing, receipts });\n\n if (hasInvalidReceipt) {\n throw new MfaRecoveryError('Invalid mutation receipt', 'invalid_receipt');\n }\n\n if (\n new Set(receipts.map((receipt) => receipt.escrowId)).size !==\n this.#escrows.length\n ) {\n throw new IncompleteMutationError(mutation.id);\n }\n await this.#clearPending();\n }\n\n #getRemainingEscrows(\n pending: WritingPendingOperation,\n ): RecoveryEscrowProvider[] {\n const { mutation } = pending;\n const remainingEscrows: RecoveryEscrowProvider[] = [];\n for (const escrow of this.#escrows) {\n const receipt = pending.receipts.find(\n (item) => item.escrowId === escrow.id,\n );\n if (receipt === undefined) {\n remainingEscrows.push(escrow);\n continue;\n }\n if (!verifyMutationReceipt(receipt, mutation, escrow)) {\n throw new MfaRecoveryError(\n 'Invalid mutation receipt',\n 'invalid_receipt',\n );\n }\n }\n if (\n remainingEscrows.length + pending.receipts.length !==\n this.#escrows.length\n ) {\n throw new MfaRecoveryError(\n 'Receipt escrow is not configured',\n 'unknown_receipt_escrow',\n );\n }\n return remainingEscrows;\n }\n\n #collectMutationReceipts(\n results: PromiseSettledResult<MutationReceipt>[],\n escrows: RecoveryEscrowProvider[],\n mutation: Mutation,\n existingReceipts: MutationReceipt[],\n ): { receipts: MutationReceipt[]; hasInvalidReceipt: boolean } {\n const receipts = [...existingReceipts];\n let hasInvalidReceipt = false;\n results.forEach((result, index) => {\n if (!isFulfilledResult(result)) {\n return;\n }\n const escrow = escrows[index];\n if (\n !isMutationReceipt(result.value) ||\n !verifyMutationReceipt(result.value, mutation, escrow)\n ) {\n hasInvalidReceipt = true;\n return;\n }\n receipts.push(result.value);\n });\n return { receipts, hasInvalidReceipt };\n }\n\n async #authorizeIdentifier({\n escrows,\n identifier,\n requestHash,\n }: {\n escrows: RecoveryEscrowProvider[];\n identifier: Identifier;\n requestHash: string;\n }): Promise<AuthorizedEscrow[]> {\n getIdentifierAuthMode(identifier.type);\n return await authorizeKeyBoundIdentifier({\n escrows,\n identifier,\n requestHash,\n identifierAuthProvider: this.#identifierAuthProvider,\n });\n }\n\n #payloadForEscrow(\n pending: WritingPendingOperation,\n escrow: RecoveryEscrowProvider,\n ): MutationPayload {\n const { payload } = pending;\n if (!pendingPayloadHasRecoverySecret(payload)) {\n return {\n identifiers: payload.identifiers,\n };\n }\n const recoverySecret = this.#wrapRecoverySecret(\n decodeHex(payload.recoverySecret),\n escrow.wrapPublicKey,\n );\n if (pendingPayloadHasIdentifiers(payload)) {\n return {\n identifiers: payload.identifiers,\n recoverySecret,\n };\n }\n return { recoverySecret };\n }\n\n #resolveCurrentRecoveryVersion(epoch: number): number {\n if (!Number.isInteger(epoch) || epoch < 0) {\n throw new MfaRecoveryError('Invalid epoch', 'invalid_epoch');\n }\n return epoch;\n }\n\n async #getAvailableEscrows(\n escrows: RecoveryEscrowProvider[] = this.#escrows,\n ): Promise<RecoveryEscrowProvider[]> {\n const flags = await Promise.allSettled(\n escrows.map(async (escrow) => ({\n escrow,\n available: await escrow.isAvailable(),\n })),\n );\n return flags.flatMap((result) =>\n isFulfilledResult(result) && result.value.available\n ? [result.value.escrow]\n : [],\n );\n }\n\n #assertKnownIdentifierTypes(identifiers: Identifier[]): void {\n for (const identifier of identifiers) {\n getIdentifierAuthMode(identifier.type);\n }\n }\n\n #assertMinIdentifiers(identifiers: Identifier[]): void {\n if (countDistinctIdentifiers(identifiers) < MIN_IDENTIFIERS) {\n throw new MfaRecoveryError(\n `Requires at least ${MIN_IDENTIFIERS} identifiers`,\n 'too_few_identifiers',\n );\n }\n }\n\n #wrapRecoverySecret(\n plaintext: Uint8Array,\n wrapPublicKey: string,\n ): WrappedSecret {\n const ephemeral = generateSigningKey();\n return {\n pkE: JSON.parse(ephemeral.publicKey) as EcPublicJwk,\n ciphertext: encryptToPublic(\n ephemeral.privateKey,\n wrapPublicKey,\n plaintext,\n ),\n };\n }\n\n async #loadPending(): Promise<PendingOperation | null> {\n const { pendingOperation } = this.state;\n if (pendingOperation === null) {\n return null;\n }\n const pending = (await this.#encryptor.decrypt(\n pendingOperation,\n )) as unknown;\n await this.#assertPending(pending);\n return pending as PendingOperation;\n }\n\n async #persistPending(operation: PendingOperation): Promise<void> {\n await this.#assertPending(operation);\n const encrypted = await this.#encryptor.encrypt(operation);\n this.update((state) => {\n state.pendingOperation = encrypted;\n });\n }\n\n async #assertPending(pending: unknown): Promise<void> {\n await assertValidPendingOperation(\n pending,\n this.#escrows.map((escrow) => escrow.id),\n );\n }\n\n async #clearPending(): Promise<void> {\n this.update((state) => {\n state.pendingOperation = null;\n });\n }\n\n async #withLock<ReturnValue>(\n fn: () => Promise<ReturnValue>,\n ): Promise<ReturnValue> {\n const run = this.#lock.then(fn, fn);\n this.#lock = run.then(\n () => undefined,\n () => undefined,\n );\n return await run;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"MfaRecoveryController.js","sourceRoot":"","sources":["../src/MfaRecoveryController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport { bytesToHex } from '@metamask/utils';\n\nimport { decodeAuthControllerToken } from './auth-controller-token.js';\nimport {\n decodeHex,\n decryptFromPublic,\n encryptToPublic,\n generateSigningKey,\n hash,\n randomId,\n unixNow,\n wrapKeyId,\n} from './crypto.js';\nimport {\n IncompleteMutationError,\n MfaRecoveryError,\n MutationRejectedError,\n} from './errors.js';\nimport {\n isFulfilledResult,\n isMutationReceipt,\n selectHighestConsistentVersion,\n verifyMutationReceipt,\n} from './escrow-utils.js';\nimport {\n authorizeEscrowsWithToken,\n countDistinctIdentifiers,\n getIdentifierAuthMode,\n MIN_IDENTIFIERS,\n requestKeyBoundIdentifierToken,\n} from './identifier-auth.js';\nimport type { AuthorizedEscrow } from './identifier-auth.js';\nimport type { MfaRecoveryControllerMethodActions } from './MfaRecoveryController-method-action-types.js';\nimport {\n assertValidPendingOperation,\n pendingPayloadHasIdentifiers,\n pendingPayloadHasRecoverySecret,\n} from './pending-operation-validation.js';\nimport { assertAbortAllowed, getRecoveryPhase } from './state-machine.js';\nimport type {\n AuthControllerToken,\n EcPublicJwk,\n Identifier,\n IdentifierSession,\n Mutation,\n MutationPayload,\n MutationReceipt,\n PendingMutationPayload,\n PendingOperation,\n PendingOperationEncryptor,\n PendingRegisterPayload,\n PendingUpdateIdentifiersPayload,\n PendingUpdateRecoverySecretPayload,\n RecoveryAuthProvider,\n RecoveryEscrowProvider,\n RecoveryIdentifierAuthProvider,\n RecoveryPhase,\n RecoveredSecret,\n WrappedSecret,\n WritingPendingOperation,\n} from './types.js';\n\nconst CONTROLLER_NAME = 'MfaRecoveryController';\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'register',\n 'updateRecoverySecret',\n 'updateIdentifiers',\n 'authenticateIdentifier',\n 'getRecoverySecret',\n 'resume',\n 'abort',\n 'getPhase',\n] as const;\n\nexport type MfaRecoveryControllerState = {\n /**\n * Encrypted pending mutation, or `null` when idle.\n */\n pendingOperation: string | null;\n};\n\nconst mfaRecoveryControllerMetadata = {\n pendingOperation: {\n persist: true,\n includeInDebugSnapshot: false,\n includeInStateLogs: false,\n usedInUi: true,\n },\n} satisfies StateMetadata<MfaRecoveryControllerState>;\n\n/**\n * @returns The default {@link MfaRecoveryController} state.\n */\nexport function getDefaultMfaRecoveryControllerState(): MfaRecoveryControllerState {\n return { pendingOperation: null };\n}\n\nexport type MfaRecoveryControllerGetStateAction = ControllerGetStateAction<\n typeof CONTROLLER_NAME,\n MfaRecoveryControllerState\n>;\n\nexport type MfaRecoveryControllerActions =\n | MfaRecoveryControllerGetStateAction\n | MfaRecoveryControllerMethodActions;\n\ntype AllowedActions = never;\n\nexport type MfaRecoveryControllerStateChangedEvent =\n ControllerStateChangedEvent<\n typeof CONTROLLER_NAME,\n MfaRecoveryControllerState\n >;\n\nexport type MfaRecoveryControllerEvents =\n MfaRecoveryControllerStateChangedEvent;\n\ntype AllowedEvents = never;\n\nexport type MfaRecoveryControllerMessenger = Messenger<\n typeof CONTROLLER_NAME,\n MfaRecoveryControllerActions | AllowedActions,\n MfaRecoveryControllerEvents | AllowedEvents\n>;\n\nexport type MfaRecoveryControllerOptions = {\n /**\n * The messenger suited for this controller.\n */\n messenger: MfaRecoveryControllerMessenger;\n\n /**\n * The desired state with which to initialize this controller. Missing\n * properties will be filled in with defaults.\n */\n state?: Partial<MfaRecoveryControllerState>;\n\n /**\n * Authenticates the MetaMask profile and issues request-bound attestations.\n */\n authProvider: RecoveryAuthProvider;\n\n /**\n * Obtains key-bound identifier tokens from an IdP.\n */\n identifierAuthProvider: RecoveryIdentifierAuthProvider;\n\n /**\n * Ordered, non-empty set of recovery escrow replicas. This is build-time\n * configuration; public methods cannot override it.\n */\n escrows: RecoveryEscrowProvider[];\n\n /**\n * Encrypts pending mutation state using wallet secure storage.\n */\n pendingOperationEncryptor: PendingOperationEncryptor;\n\n /**\n * Clock used for AuthController token expiry checks. Unix seconds.\n * Defaults to {@link unixNow}.\n */\n now?: () => number;\n};\n\ntype MutateParams = {\n epoch: number;\n} & (\n | {\n operation: 'register';\n payload: PendingRegisterPayload;\n identifier: null;\n }\n | {\n operation: 'updateRecoverySecret';\n payload: PendingUpdateRecoverySecretPayload;\n identifier: Identifier;\n }\n | {\n operation: 'updateIdentifiers';\n payload: PendingUpdateIdentifiersPayload;\n identifier: Identifier;\n }\n);\n\n/**\n * Coordinates MFA recovery secret replication across independent escrows.\n */\nexport class MfaRecoveryController extends BaseController<\n typeof CONTROLLER_NAME,\n MfaRecoveryControllerState,\n MfaRecoveryControllerMessenger\n> {\n readonly #authProvider: RecoveryAuthProvider;\n\n readonly #identifierAuthProvider: RecoveryIdentifierAuthProvider;\n\n readonly #escrows: RecoveryEscrowProvider[];\n\n readonly #encryptor: PendingOperationEncryptor;\n\n readonly #now: () => number;\n\n #lock: Promise<void> = Promise.resolve();\n\n constructor({\n messenger,\n state,\n authProvider,\n identifierAuthProvider,\n escrows,\n pendingOperationEncryptor,\n now = unixNow,\n }: MfaRecoveryControllerOptions) {\n if (escrows.length === 0) {\n throw new MfaRecoveryError(\n 'At least one escrow is required',\n 'empty_escrow_set',\n );\n }\n const escrowIds = escrows.map((escrow) => escrow.id);\n if (new Set(escrowIds).size !== escrowIds.length) {\n throw new MfaRecoveryError('Duplicate escrow id', 'duplicate_escrow_id');\n }\n\n super({\n messenger,\n metadata: mfaRecoveryControllerMetadata,\n name: CONTROLLER_NAME,\n state: {\n ...getDefaultMfaRecoveryControllerState(),\n ...state,\n },\n });\n\n this.#authProvider = authProvider;\n this.#identifierAuthProvider = identifierAuthProvider;\n this.#escrows = [...escrows];\n this.#encryptor = pendingOperationEncryptor;\n this.#now = now;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Creates version 1 of a recovery record.\n *\n * @param recoverySecret - Secret replicated in full to every escrow.\n * @param identifiers - Ownership-approved identifier set. Must contain at\n * least two identifiers.\n */\n async register(\n recoverySecret: Uint8Array,\n identifiers: Identifier[],\n ): Promise<void> {\n this.#assertKnownIdentifierTypes(identifiers);\n this.#assertMinIdentifiers(identifiers);\n await this.#mutate({\n operation: 'register',\n payload: {\n identifiers,\n recoverySecret: bytesToHex(recoverySecret),\n },\n identifier: null,\n epoch: 0,\n });\n }\n\n /**\n * Replaces the recovery secret.\n *\n * @param identifier - Currently registered identifier used to authorize.\n * @param recoverySecret - New secret.\n * @param epoch - Current recovery version, used as `expectedVersion`.\n */\n async updateRecoverySecret(\n identifier: Identifier,\n recoverySecret: Uint8Array,\n epoch: number,\n ): Promise<void> {\n this.#assertKnownIdentifierTypes([identifier]);\n await this.#mutate({\n operation: 'updateRecoverySecret',\n payload: {\n recoverySecret: bytesToHex(recoverySecret),\n },\n identifier,\n epoch,\n });\n }\n\n /**\n * Replaces the complete identifier set.\n *\n * @param identifier - Currently registered identifier used to authorize.\n * @param identifiers - New identifier set. Must contain at least two\n * identifiers.\n * @param epoch - Current recovery version, used as `expectedVersion`.\n */\n async updateIdentifiers(\n identifier: Identifier,\n identifiers: Identifier[],\n epoch: number,\n ): Promise<void> {\n this.#assertKnownIdentifierTypes([identifier, ...identifiers]);\n this.#assertMinIdentifiers(identifiers);\n await this.#mutate({\n operation: 'updateIdentifiers',\n payload: { identifiers },\n identifier,\n epoch,\n });\n }\n\n /**\n * Authenticates an identifier for a subsequent recovery-secret read.\n *\n * @param identifier - Identifier to authenticate.\n * @returns Ephemeral session data required by {@link getRecoverySecret}.\n */\n async authenticateIdentifier(\n identifier: Identifier,\n ): Promise<IdentifierSession> {\n this.#assertKnownIdentifierTypes([identifier]);\n const requestId = randomId();\n const ephemeral = generateSigningKey();\n const pkE = JSON.parse(ephemeral.publicKey) as EcPublicJwk;\n const requestHash = hash({\n operation: 'getRecoverySecret',\n requestId,\n pkE,\n });\n const credential = await requestKeyBoundIdentifierToken({\n identifier,\n requestHash,\n identifierAuthProvider: this.#identifierAuthProvider,\n });\n return {\n ...credential,\n requestId,\n ephemeralPrivateKey: ephemeral.privateKey,\n pkE,\n };\n }\n\n /**\n * Reads the recovery secret from available escrows and returns the highest\n * consistent version. `epoch` is the current recovery version and is required\n * by later mutations. Does not wait for a pending mutation to be repaired.\n *\n * @param session - Identifier authentication session used to authorize the\n * read.\n * @returns Recovered secret bytes and the selected epoch.\n */\n async getRecoverySecret(\n session: IdentifierSession,\n ): Promise<RecoveredSecret> {\n return await this.#withLock(async () => {\n const { token, requestId, pkE } = session;\n this.#assertKnownIdentifierTypes([token.identifier]);\n const requestHash = hash({\n operation: 'getRecoverySecret',\n requestId,\n pkE,\n });\n if (token.requestHash !== requestHash) {\n throw new MfaRecoveryError(\n 'Invalid identifier session',\n 'invalid_identifier_session',\n );\n }\n const available = await this.#getAvailableEscrows();\n if (available.length === 0) {\n throw new MfaRecoveryError(\n 'No escrow is available',\n 'no_available_escrow',\n );\n }\n const authorizedEscrows = await authorizeEscrowsWithToken({\n escrows: available,\n credential: session,\n });\n const results = await Promise.allSettled(\n authorizedEscrows.map(async ({ escrow, authorization }) => {\n const response = await escrow.getSecret(\n authorization,\n requestId,\n pkE,\n );\n if (response.wrapKeyId !== wrapKeyId(escrow.wrapPublicKey)) {\n throw new MfaRecoveryError(\n 'Unexpected wrap key',\n 'wrap_key_mismatch',\n );\n }\n return {\n escrowId: escrow.id,\n recoverySecret: decryptFromPublic(\n session.ephemeralPrivateKey,\n escrow.wrapPublicKey,\n response.recoverySecret.ciphertext,\n ),\n version: response.version,\n lastMutationId: response.lastMutationId,\n };\n }),\n );\n const selected = selectHighestConsistentVersion(results);\n return {\n recoverySecret: selected.recoverySecret,\n epoch: selected.version,\n };\n });\n }\n\n /**\n * Completes a persisted pending mutation, if any.\n */\n async resume(): Promise<void> {\n await this.#withLock(async () => {\n await this.#repairPendingMutation();\n });\n }\n\n /**\n * Drops `authorizing` pending. `writing` must be finished with {@link resume}.\n */\n async abort(): Promise<void> {\n await this.#withLock(async () => {\n const pending = await this.#loadPending();\n assertAbortAllowed(pending);\n if (pending) {\n await this.#clearPending();\n }\n });\n }\n\n /**\n * @returns Current recovery phase.\n */\n async getPhase(): Promise<RecoveryPhase> {\n return await this.#withLock(async () =>\n getRecoveryPhase(await this.#loadPending()),\n );\n }\n\n async #mutate(params: MutateParams): Promise<void> {\n const { operation, payload, identifier, epoch } = params;\n await this.#withLock(async () => {\n if (await this.#loadPending()) {\n throw new MfaRecoveryError(\n 'A pending mutation must be resumed or aborted first',\n 'pending_mutation',\n );\n }\n\n await this.#assertEscrowsAvailable(this.#escrows);\n\n const profileId = await this.#authProvider.getAuthenticatedProfileId();\n const audiences = this.#escrows.map((escrow) => escrow.id);\n const currentVersion = this.#resolveCurrentRecoveryVersion(epoch);\n const payloadHash = hash(payload);\n\n const mutationFields = {\n id: randomId(),\n profileId,\n operation,\n expectedVersion: currentVersion,\n newVersion: currentVersion + 1,\n payloadHash,\n audiences,\n };\n const mutation: Mutation = {\n ...mutationFields,\n requestHash: hash(mutationFields),\n };\n\n const pending: PendingOperation = {\n phase: 'authorizing',\n mutation,\n payload,\n identifier,\n };\n await this.#persistPending(pending);\n\n const authControllerToken = await this.#authorizeMutation(\n mutation,\n payload,\n );\n await this.#replicateMutation({\n ...pending,\n phase: 'writing',\n authControllerToken,\n receipts: [],\n });\n });\n }\n\n async #authorizeMutation(\n mutation: Mutation,\n payload: PendingMutationPayload,\n ): Promise<AuthControllerToken> {\n return await this.#authProvider.authorizeRecoveryRequest({\n requestHash: mutation.requestHash,\n audiences: this.#escrows.map((escrow) => escrow.authAudience),\n ...(mutation.operation === 'register' ? {} : { requireTwoFactor: true }),\n ...(pendingPayloadHasIdentifiers(payload)\n ? { identifiers: payload.identifiers }\n : {}),\n });\n }\n\n async #repairPendingMutation(): Promise<void> {\n const pending = await this.#loadPending();\n if (!pending) {\n return;\n }\n\n if (pending.phase === 'authorizing') {\n await this.#assertEscrowsAvailable(this.#escrows);\n await this.#replicateMutation({\n ...pending,\n phase: 'writing',\n authControllerToken: await this.#authorizeMutation(\n pending.mutation,\n pending.payload,\n ),\n receipts: [],\n });\n return;\n }\n\n // writing phase\n await this.#replicateMutation(pending);\n }\n\n async #replicateMutation(pending: WritingPendingOperation): Promise<void> {\n const { mutation, payload, identifier } = pending;\n const remainingEscrows = this.#getRemainingEscrows(pending);\n if (remainingEscrows.length === 0) {\n await this.#clearPending();\n return;\n }\n\n await this.#assertEscrowsAvailable(remainingEscrows);\n\n const claims = decodeAuthControllerToken(pending.authControllerToken);\n const authControllerToken =\n claims && claims.exp > this.#now()\n ? pending.authControllerToken\n : await this.#authorizeMutation(mutation, payload);\n const writing = { ...pending, authControllerToken };\n const authorizedEscrows =\n mutation.operation === 'register'\n ? null\n : await this.#authorizeEscrows({\n escrows: remainingEscrows,\n identifier: identifier as Identifier,\n requestHash: mutation.requestHash,\n });\n if (\n authorizedEscrows !== null &&\n authorizedEscrows.length !== remainingEscrows.length\n ) {\n throw new MfaRecoveryError(\n 'Unable to authorize every escrow',\n 'identifier_auth_failed',\n );\n }\n const payloads = remainingEscrows.map((escrow) =>\n this.#payloadForEscrow(writing, escrow),\n );\n\n await this.#persistPending(writing);\n const results = await Promise.allSettled(\n remainingEscrows.map((escrow, index) =>\n escrow.applyMutation(\n mutation,\n writing.authControllerToken,\n authorizedEscrows?.[index]?.authorization ?? null,\n payloads[index],\n ),\n ),\n );\n\n const { receipts, hasInvalidReceipt } = this.#collectMutationReceipts(\n results,\n remainingEscrows,\n mutation,\n writing.receipts,\n );\n const rejection = unanimousMutationRejection(results, writing.receipts);\n if (rejection) {\n await this.#clearPending();\n throw rejection;\n }\n await this.#persistPending({ ...writing, receipts });\n\n if (hasInvalidReceipt) {\n throw new MfaRecoveryError('Invalid mutation receipt', 'invalid_receipt');\n }\n\n if (\n new Set(receipts.map((receipt) => receipt.escrowId)).size !==\n this.#escrows.length\n ) {\n throw new IncompleteMutationError(mutation.id);\n }\n await this.#clearPending();\n }\n\n async #assertEscrowsAvailable(\n escrows: RecoveryEscrowProvider[],\n ): Promise<void> {\n const availableEscrows = await this.#getAvailableEscrows(escrows);\n // Do we want to wait for all escrows to be available?\n if (availableEscrows.length !== escrows.length) {\n throw new MfaRecoveryError(\n 'All configured escrows are required for mutation',\n 'escrow_unavailable',\n );\n }\n }\n\n #getRemainingEscrows(\n pending: WritingPendingOperation,\n ): RecoveryEscrowProvider[] {\n const { mutation } = pending;\n const remainingEscrows: RecoveryEscrowProvider[] = [];\n for (const escrow of this.#escrows) {\n const receipt = pending.receipts.find(\n (item) => item.escrowId === escrow.id,\n );\n if (receipt === undefined) {\n remainingEscrows.push(escrow);\n continue;\n }\n if (!verifyMutationReceipt(receipt, mutation, escrow)) {\n throw new MfaRecoveryError(\n 'Invalid mutation receipt',\n 'invalid_receipt',\n );\n }\n }\n if (\n remainingEscrows.length + pending.receipts.length !==\n this.#escrows.length\n ) {\n throw new MfaRecoveryError(\n 'Receipt escrow is not configured',\n 'unknown_receipt_escrow',\n );\n }\n return remainingEscrows;\n }\n\n #collectMutationReceipts(\n results: PromiseSettledResult<MutationReceipt>[],\n escrows: RecoveryEscrowProvider[],\n mutation: Mutation,\n existingReceipts: MutationReceipt[],\n ): { receipts: MutationReceipt[]; hasInvalidReceipt: boolean } {\n const receipts = [...existingReceipts];\n let hasInvalidReceipt = false;\n results.forEach((result, index) => {\n if (!isFulfilledResult(result)) {\n return;\n }\n const escrow = escrows[index];\n if (\n !isMutationReceipt(result.value) ||\n !verifyMutationReceipt(result.value, mutation, escrow)\n ) {\n hasInvalidReceipt = true;\n return;\n }\n receipts.push(result.value);\n });\n return { receipts, hasInvalidReceipt };\n }\n\n async #authorizeEscrows({\n escrows,\n identifier,\n requestHash,\n }: {\n escrows: RecoveryEscrowProvider[];\n identifier: Identifier;\n requestHash: string;\n }): Promise<AuthorizedEscrow[]> {\n getIdentifierAuthMode(identifier.type);\n const credential = await requestKeyBoundIdentifierToken({\n identifier,\n requestHash,\n identifierAuthProvider: this.#identifierAuthProvider,\n });\n return await authorizeEscrowsWithToken({ escrows, credential });\n }\n\n #payloadForEscrow(\n pending: WritingPendingOperation,\n escrow: RecoveryEscrowProvider,\n ): MutationPayload {\n const { payload } = pending;\n if (!pendingPayloadHasRecoverySecret(payload)) {\n return {\n identifiers: payload.identifiers,\n };\n }\n const recoverySecret = this.#wrapRecoverySecret(\n decodeHex(payload.recoverySecret),\n escrow.wrapPublicKey,\n );\n if (pendingPayloadHasIdentifiers(payload)) {\n return {\n identifiers: payload.identifiers,\n recoverySecret,\n };\n }\n return { recoverySecret };\n }\n\n #resolveCurrentRecoveryVersion(epoch: number): number {\n if (!Number.isInteger(epoch) || epoch < 0) {\n throw new MfaRecoveryError('Invalid epoch', 'invalid_epoch');\n }\n return epoch;\n }\n\n async #getAvailableEscrows(\n escrows: RecoveryEscrowProvider[] = this.#escrows,\n ): Promise<RecoveryEscrowProvider[]> {\n const flags = await Promise.allSettled(\n escrows.map(async (escrow) => ({\n escrow,\n available: await escrow.isAvailable(),\n })),\n );\n return flags.flatMap((result) =>\n isFulfilledResult(result) && result.value.available\n ? [result.value.escrow]\n : [],\n );\n }\n\n #assertKnownIdentifierTypes(identifiers: Identifier[]): void {\n for (const identifier of identifiers) {\n getIdentifierAuthMode(identifier.type);\n }\n }\n\n #assertMinIdentifiers(identifiers: Identifier[]): void {\n if (countDistinctIdentifiers(identifiers) < MIN_IDENTIFIERS) {\n throw new MfaRecoveryError(\n `Requires at least ${MIN_IDENTIFIERS} identifiers`,\n 'too_few_identifiers',\n );\n }\n }\n\n #wrapRecoverySecret(\n plaintext: Uint8Array,\n wrapPublicKey: string,\n ): WrappedSecret {\n const ephemeral = generateSigningKey();\n return {\n pkE: JSON.parse(ephemeral.publicKey) as EcPublicJwk,\n ciphertext: encryptToPublic(\n ephemeral.privateKey,\n wrapPublicKey,\n plaintext,\n ),\n };\n }\n\n async #loadPending(): Promise<PendingOperation | null> {\n const { pendingOperation } = this.state;\n if (pendingOperation === null) {\n return null;\n }\n const pending = (await this.#encryptor.decrypt(\n pendingOperation,\n )) as unknown;\n await this.#assertPending(pending);\n return pending as PendingOperation;\n }\n\n async #persistPending(operation: PendingOperation): Promise<void> {\n await this.#assertPending(operation);\n const encrypted = await this.#encryptor.encrypt(operation);\n this.update((state) => {\n state.pendingOperation = encrypted;\n });\n }\n\n async #assertPending(pending: unknown): Promise<void> {\n await assertValidPendingOperation(\n pending,\n this.#escrows.map((escrow) => escrow.id),\n );\n }\n\n async #clearPending(): Promise<void> {\n this.update((state) => {\n state.pendingOperation = null;\n });\n }\n\n async #withLock<ReturnValue>(\n fn: () => Promise<ReturnValue>,\n ): Promise<ReturnValue> {\n const run = this.#lock.then(fn, fn);\n this.#lock = run.then(\n () => undefined,\n () => undefined,\n );\n return await run;\n }\n}\n\n/**\n * Every remaining escrow refused the mutation, and none has a receipt.\n *\n * @param results - Settled apply results for the remaining escrows.\n * @param existingReceipts - Receipts already stored for this mutation.\n * @returns The first refusal, or `null` when the write may have been applied.\n */\nfunction unanimousMutationRejection(\n results: PromiseSettledResult<MutationReceipt>[],\n existingReceipts: MutationReceipt[],\n): MutationRejectedError | null {\n if (existingReceipts.length > 0 || results.length === 0) {\n return null;\n }\n let rejection: MutationRejectedError | null = null;\n for (const result of results) {\n if (\n result.status !== 'rejected' ||\n !(result.reason instanceof MutationRejectedError)\n ) {\n return null;\n }\n rejection ??= result.reason;\n }\n return rejection;\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The claims the controller reads from an AuthController (Hydra) access token,
|
|
3
|
+
* in the token's shape with camelCase names. The Auth API token hook adds the
|
|
4
|
+
* step-up bindings under `ext`; a token that did not step up has none of them.
|
|
5
|
+
*/
|
|
6
|
+
export type AuthControllerTokenClaims = {
|
|
7
|
+
/**
|
|
8
|
+
* The profile id.
|
|
9
|
+
*/
|
|
10
|
+
sub: string;
|
|
11
|
+
/**
|
|
12
|
+
* Unix time in seconds.
|
|
13
|
+
*/
|
|
14
|
+
exp: number;
|
|
15
|
+
ext: {
|
|
16
|
+
requestHash: string;
|
|
17
|
+
/**
|
|
18
|
+
* Authentication assurance level.
|
|
19
|
+
*/
|
|
20
|
+
aal?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Only present when the step-up was bound to a payload's identifiers.
|
|
23
|
+
*/
|
|
24
|
+
identifiersHash?: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Decodes the claims of an AuthController token without verifying its
|
|
29
|
+
* signature; the escrow verifies the token. Required claims must be present;
|
|
30
|
+
* optional ones may be absent but, when present, must have the right type.
|
|
31
|
+
*
|
|
32
|
+
* @param token - Compact JWT.
|
|
33
|
+
* @returns The claims, or `undefined` if the token is malformed.
|
|
34
|
+
*/
|
|
35
|
+
export declare function decodeAuthControllerToken(token: unknown): AuthControllerTokenClaims | undefined;
|
|
36
|
+
//# sourceMappingURL=auth-controller-token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-controller-token.d.ts","sourceRoot":"","sources":["../src/auth-controller-token.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE;QACH,WAAW,EAAE,MAAM,CAAC;QACpB;;WAEG;QACH,GAAG,CAAC,EAAE,MAAM,CAAC;QACb;;WAEG;QACH,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,GACb,yBAAyB,GAAG,SAAS,CA+BvC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { bytesToString, isObject } from '@metamask/utils';
|
|
2
|
+
import { fromBase64Url } from './crypto.js';
|
|
3
|
+
/**
|
|
4
|
+
* Decodes the claims of an AuthController token without verifying its
|
|
5
|
+
* signature; the escrow verifies the token. Required claims must be present;
|
|
6
|
+
* optional ones may be absent but, when present, must have the right type.
|
|
7
|
+
*
|
|
8
|
+
* @param token - Compact JWT.
|
|
9
|
+
* @returns The claims, or `undefined` if the token is malformed.
|
|
10
|
+
*/
|
|
11
|
+
export function decodeAuthControllerToken(token) {
|
|
12
|
+
if (typeof token !== 'string') {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
let claims;
|
|
16
|
+
try {
|
|
17
|
+
claims = JSON.parse(bytesToString(fromBase64Url(token.split('.')[1] ?? '')));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
if (!isObject(claims) || !isObject(claims.ext)) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const { sub, exp, ext } = claims;
|
|
26
|
+
const { aal, request_hash: requestHash, identifiers_hash: identifiersHash, } = ext;
|
|
27
|
+
if (typeof sub !== 'string' ||
|
|
28
|
+
typeof exp !== 'number' ||
|
|
29
|
+
typeof requestHash !== 'string' ||
|
|
30
|
+
(aal !== undefined && typeof aal !== 'number') ||
|
|
31
|
+
(identifiersHash !== undefined && typeof identifiersHash !== 'string')) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return { sub, exp, ext: { requestHash, aal, identifiersHash } };
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=auth-controller-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-controller-token.js","sourceRoot":"","sources":["../src/auth-controller-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA6B5C;;;;;;;GAOG;AACH,MAAM,UAAU,yBAAyB,CACvC,KAAc;IAEd,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CACjB,aAAa,CAAC,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CACxD,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;IACjC,MAAM,EACJ,GAAG,EACH,YAAY,EAAE,WAAW,EACzB,gBAAgB,EAAE,eAAe,GAClC,GAAG,GAAG,CAAC;IACR,IACE,OAAO,GAAG,KAAK,QAAQ;QACvB,OAAO,GAAG,KAAK,QAAQ;QACvB,OAAO,WAAW,KAAK,QAAQ;QAC/B,CAAC,GAAG,KAAK,SAAS,IAAI,OAAO,GAAG,KAAK,QAAQ,CAAC;QAC9C,CAAC,eAAe,KAAK,SAAS,IAAI,OAAO,eAAe,KAAK,QAAQ,CAAC,EACtE,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,eAAe,EAAE,EAAE,CAAC;AAClE,CAAC","sourcesContent":["import { bytesToString, isObject } from '@metamask/utils';\n\nimport { fromBase64Url } from './crypto.js';\n\n/**\n * The claims the controller reads from an AuthController (Hydra) access token,\n * in the token's shape with camelCase names. The Auth API token hook adds the\n * step-up bindings under `ext`; a token that did not step up has none of them.\n */\nexport type AuthControllerTokenClaims = {\n /**\n * The profile id.\n */\n sub: string;\n /**\n * Unix time in seconds.\n */\n exp: number;\n ext: {\n requestHash: string;\n /**\n * Authentication assurance level.\n */\n aal?: number;\n /**\n * Only present when the step-up was bound to a payload's identifiers.\n */\n identifiersHash?: string;\n };\n};\n\n/**\n * Decodes the claims of an AuthController token without verifying its\n * signature; the escrow verifies the token. Required claims must be present;\n * optional ones may be absent but, when present, must have the right type.\n *\n * @param token - Compact JWT.\n * @returns The claims, or `undefined` if the token is malformed.\n */\nexport function decodeAuthControllerToken(\n token: unknown,\n): AuthControllerTokenClaims | undefined {\n if (typeof token !== 'string') {\n return undefined;\n }\n let claims: unknown;\n try {\n claims = JSON.parse(\n bytesToString(fromBase64Url(token.split('.')[1] ?? '')),\n );\n } catch {\n return undefined;\n }\n if (!isObject(claims) || !isObject(claims.ext)) {\n return undefined;\n }\n const { sub, exp, ext } = claims;\n const {\n aal,\n request_hash: requestHash,\n identifiers_hash: identifiersHash,\n } = ext;\n if (\n typeof sub !== 'string' ||\n typeof exp !== 'number' ||\n typeof requestHash !== 'string' ||\n (aal !== undefined && typeof aal !== 'number') ||\n (identifiersHash !== undefined && typeof identifiersHash !== 'string')\n ) {\n return undefined;\n }\n return { sub, exp, ext: { requestHash, aal, identifiersHash } };\n}\n"]}
|
package/dist/crypto.d.ts
CHANGED
|
@@ -14,11 +14,21 @@ export declare function canonicalize(value: unknown): string;
|
|
|
14
14
|
* @returns Hex digest.
|
|
15
15
|
*/
|
|
16
16
|
export declare function hash(value: unknown): Hex;
|
|
17
|
+
/**
|
|
18
|
+
* Orders two strings by their UTF-8 bytes. The escrow sorts identifiers the same
|
|
19
|
+
* way (Rust `String` ordering), so this must not be locale or UTF-16 aware.
|
|
20
|
+
*
|
|
21
|
+
* @param left - First string.
|
|
22
|
+
* @param right - Second string.
|
|
23
|
+
* @returns Negative, zero, or positive like `Array.prototype.sort` expects.
|
|
24
|
+
*/
|
|
25
|
+
export declare function compareUtf8(left: string, right: string): number;
|
|
17
26
|
/**
|
|
18
27
|
* Canonical identifier list bound into AuthController `identifiersHash`.
|
|
19
28
|
*
|
|
20
29
|
* @param identifiers - Identifier set.
|
|
21
|
-
* @returns
|
|
30
|
+
* @returns Identifier records including verifier material, sorted by the UTF-8
|
|
31
|
+
* bytes of their canonical JSON.
|
|
22
32
|
*/
|
|
23
33
|
export declare function canonicalizeIdentifiers(identifiers: Identifier[]): unknown;
|
|
24
34
|
/**
|
|
@@ -108,4 +118,11 @@ export declare function encryptToPublic(privateKey: string, publicKey: string, p
|
|
|
108
118
|
* @returns Plaintext bytes.
|
|
109
119
|
*/
|
|
110
120
|
export declare function decryptFromPublic(privateKey: string, publicKey: string, ciphertextHex: string): Uint8Array;
|
|
121
|
+
/**
|
|
122
|
+
* Decodes unpadded base64url, as used by JWK coordinates and JWT segments.
|
|
123
|
+
*
|
|
124
|
+
* @param value - Base64url string.
|
|
125
|
+
* @returns Raw bytes.
|
|
126
|
+
*/
|
|
127
|
+
export declare function fromBase64Url(value: string): Uint8Array;
|
|
111
128
|
//# sourceMappingURL=crypto.d.ts.map
|
package/dist/crypto.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAM3C,OAAO,KAAK,EAAe,UAAU,EAAE,MAAM,YAAY,CAAC;AAM1D;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEnD;AAED;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,GAAG,CAExC;AAED
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAM3C,OAAO,KAAK,EAAe,UAAU,EAAE,MAAM,YAAY,CAAC;AAM1D;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEnD;AAED;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,GAAG,CAExC;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAW/D;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,CAW1E;AAED;;GAEG;AACH,wBAAgB,QAAQ,IAAI,GAAG,CAE9B;AAED;;;;GAIG;AACH,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,CAgBA;AAED;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG,CAU7D;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAkBT;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,GAAG,CAQN;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,aAAa,EAAE,MAAM,GAAG,GAAG,CAEpD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,UAAU,GACpB,MAAM,CAKR;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,UAAU,CASZ;AAyDD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAOvD"}
|
package/dist/crypto.js
CHANGED
|
@@ -24,11 +24,32 @@ export function canonicalize(value) {
|
|
|
24
24
|
export function hash(value) {
|
|
25
25
|
return bytesToHex(sha256Sync(stringToBytes(canonicalize(value))));
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Orders two strings by their UTF-8 bytes. The escrow sorts identifiers the same
|
|
29
|
+
* way (Rust `String` ordering), so this must not be locale or UTF-16 aware.
|
|
30
|
+
*
|
|
31
|
+
* @param left - First string.
|
|
32
|
+
* @param right - Second string.
|
|
33
|
+
* @returns Negative, zero, or positive like `Array.prototype.sort` expects.
|
|
34
|
+
*/
|
|
35
|
+
export function compareUtf8(left, right) {
|
|
36
|
+
const leftBytes = stringToBytes(left);
|
|
37
|
+
const rightBytes = stringToBytes(right);
|
|
38
|
+
const shared = Math.min(leftBytes.length, rightBytes.length);
|
|
39
|
+
for (let index = 0; index < shared; index += 1) {
|
|
40
|
+
const difference = leftBytes[index] - rightBytes[index];
|
|
41
|
+
if (difference !== 0) {
|
|
42
|
+
return difference;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return leftBytes.length - rightBytes.length;
|
|
46
|
+
}
|
|
27
47
|
/**
|
|
28
48
|
* Canonical identifier list bound into AuthController `identifiersHash`.
|
|
29
49
|
*
|
|
30
50
|
* @param identifiers - Identifier set.
|
|
31
|
-
* @returns
|
|
51
|
+
* @returns Identifier records including verifier material, sorted by the UTF-8
|
|
52
|
+
* bytes of their canonical JSON.
|
|
32
53
|
*/
|
|
33
54
|
export function canonicalizeIdentifiers(identifiers) {
|
|
34
55
|
return [...identifiers]
|
|
@@ -38,7 +59,7 @@ export function canonicalizeIdentifiers(identifiers) {
|
|
|
38
59
|
value: identifier.value,
|
|
39
60
|
verifier: identifier.verifier,
|
|
40
61
|
}))
|
|
41
|
-
.sort((left, right) => canonicalize(left)
|
|
62
|
+
.sort((left, right) => compareUtf8(canonicalize(left), canonicalize(right)));
|
|
42
63
|
}
|
|
43
64
|
/**
|
|
44
65
|
* @returns A random 0x-prefixed id.
|
|
@@ -144,7 +165,7 @@ export function hashMutationReceipt(receipt) {
|
|
|
144
165
|
* @returns Raw bytes.
|
|
145
166
|
*/
|
|
146
167
|
export function decodeHex(hexString) {
|
|
147
|
-
return hexToBytes(
|
|
168
|
+
return hexToBytes(hexString.startsWith('0x') ? hexString : `0x${hexString}`);
|
|
148
169
|
}
|
|
149
170
|
/**
|
|
150
171
|
* SHA-256 of the uncompressed P-256 wrap public key, as `0x` hex.
|
|
@@ -234,12 +255,12 @@ function randomBytes(length) {
|
|
|
234
255
|
return globalThis.crypto.getRandomValues(new Uint8Array(length));
|
|
235
256
|
}
|
|
236
257
|
/**
|
|
237
|
-
* Decodes unpadded base64url JWK coordinates.
|
|
258
|
+
* Decodes unpadded base64url, as used by JWK coordinates and JWT segments.
|
|
238
259
|
*
|
|
239
260
|
* @param value - Base64url string.
|
|
240
261
|
* @returns Raw bytes.
|
|
241
262
|
*/
|
|
242
|
-
function fromBase64Url(value) {
|
|
263
|
+
export function fromBase64Url(value) {
|
|
243
264
|
return base64ToBytes(value
|
|
244
265
|
.replace(/-/gu, '+')
|
|
245
266
|
.replace(/_/gu, '/')
|