@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 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":"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"}
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 { authorizeKeyBoundIdentifier, authorizeWithKeyBoundToken, countDistinctIdentifiers, getIdentifierAuthMode, MIN_IDENTIFIERS, requestKeyBoundIdentifierToken, } from './identifier-auth.js';
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 { token, proofPrivateKey } = await requestKeyBoundIdentifierToken({
141
+ const credential = await requestKeyBoundIdentifierToken({
141
142
  identifier,
142
143
  requestHash,
143
144
  identifierAuthProvider: this.#identifierAuthProvider,
144
145
  });
145
146
  return {
146
- token,
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 authorizeWithKeyBoundToken({
178
+ const authorizedEscrows = await authorizeEscrowsWithToken({
179
179
  escrows: available,
180
- token,
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
- const availableEscrows = await this.#getAvailableEscrows(remainingEscrows);
301
- if (availableEscrows.length !== remainingEscrows.length) {
302
- throw new MfaRecoveryError('All configured escrows are required for mutation', 'escrow_unavailable');
303
- }
304
- const authControllerToken = pending.authControllerToken.expiresAt <= this.#now()
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.#authorizeIdentifier({
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 #authorizeIdentifier({ escrows, identifier, requestHash, }) {
381
+ async #authorizeEscrows({ escrows, identifier, requestHash, }) {
370
382
  getIdentifierAuthMode(identifier.type);
371
- return await authorizeKeyBoundIdentifier({
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 Sorted identifier records including verifier material.
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
@@ -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;;;;;GAKG;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,CAIvD;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"}
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 Sorted identifier records including verifier material.
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).localeCompare(canonicalize(right)));
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((hexString.startsWith('0x') ? hexString : `0x${hexString}`));
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, '/')