@ledgerhq/revault-sdk 0.28.11 → 0.28.14

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.
Files changed (47) hide show
  1. package/lib/bake.cjs +3 -3
  2. package/lib/bake.cjs.map +1 -1
  3. package/lib/bake.d.cts +2 -2
  4. package/lib/bake.d.ts +2 -2
  5. package/lib/bake.js +3 -3
  6. package/lib/bake.js.map +1 -1
  7. package/lib/{crypto-BTN8FS8v.cjs → crypto-CKLQSwr3.cjs} +48 -12
  8. package/lib/crypto-CKLQSwr3.cjs.map +1 -0
  9. package/lib/{crypto-BcqzGi2f.js → crypto-hrnmToQa.js} +48 -11
  10. package/lib/crypto-hrnmToQa.js.map +1 -0
  11. package/lib/crypto.cjs +1 -1
  12. package/lib/crypto.js +1 -1
  13. package/lib/{device-DQyYUWPl.js → device-BnO26Hlr.js} +90 -34
  14. package/lib/device-BnO26Hlr.js.map +1 -0
  15. package/lib/{device-DQ32fSI-.cjs → device-CvgFoib2.cjs} +113 -33
  16. package/lib/device-CvgFoib2.cjs.map +1 -0
  17. package/lib/device.cjs +3 -1
  18. package/lib/device.d.cts +2 -2
  19. package/lib/device.d.ts +2 -2
  20. package/lib/device.js +2 -2
  21. package/lib/engine.cjs +93 -53
  22. package/lib/engine.cjs.map +1 -1
  23. package/lib/engine.d.cts +2 -2
  24. package/lib/engine.d.ts +2 -2
  25. package/lib/engine.js +94 -54
  26. package/lib/engine.js.map +1 -1
  27. package/lib/{index-xlQiIxlX.d.ts → index-16zV3onR.d.ts} +1 -1
  28. package/lib/{index-B3IUJM2r.d.cts → index-BAb2WCcL.d.cts} +25 -14
  29. package/lib/{index-Doiqe3tk.d.cts → index-CliA8Mh-.d.cts} +1 -1
  30. package/lib/{index-B1WQdM71.d.ts → index-DQgupTPR.d.ts} +25 -14
  31. package/lib/index.cjs +1 -1
  32. package/lib/index.cjs.map +1 -1
  33. package/lib/index.js +1 -1
  34. package/lib/index.js.map +1 -1
  35. package/lib/onboarding.cjs +6 -2
  36. package/lib/onboarding.cjs.map +1 -1
  37. package/lib/onboarding.d.cts +2 -2
  38. package/lib/onboarding.d.ts +2 -2
  39. package/lib/onboarding.js +6 -2
  40. package/lib/onboarding.js.map +1 -1
  41. package/lib/{types-DzZQZPXO.d.cts → types-bfqkmpgo.d.cts} +2 -1
  42. package/lib/{types-DqjcDy4H.d.ts → types-tRODcWIL.d.ts} +2 -1
  43. package/package.json +4 -5
  44. package/lib/crypto-BTN8FS8v.cjs.map +0 -1
  45. package/lib/crypto-BcqzGi2f.js.map +0 -1
  46. package/lib/device-DQ32fSI-.cjs.map +0 -1
  47. package/lib/device-DQyYUWPl.js.map +0 -1
package/lib/engine.js CHANGED
@@ -1,7 +1,7 @@
1
- import { a as isPskMissingError, c as withDevicePolling, d as createApiDevice, f as APPID_VAULT_ADMINISTRATOR, g as STR_ROLES, h as SCRIPT_IDS, l as DisconnectedDevice, m as PATHS, o as isTransportStatusError, p as PAGINATED_STATUS, u as LockedDeviceError } from "./device-DQyYUWPl.js";
1
+ import { _ as DeviceOperationAbortedError, a as isPskMissingError, b as throwIfDeviceOperationAborted, c as withDevicePolling, d as createApiDevice, f as APPID_VAULT_ADMINISTRATOR, g as STR_ROLES, h as SCRIPT_IDS, l as DisconnectedDevice, m as PATHS, o as isTransportStatusError, p as PAGINATED_STATUS, u as LockedDeviceError, v as deviceOperationDelay } from "./device-BnO26Hlr.js";
2
2
  import { i as fetchAllCursor, r as fetchAll, t as buildTrpcContext } from "./utils-CW0gdJME.js";
3
3
  import invariant from "invariant";
4
- import { from, lastValueFrom } from "rxjs";
4
+ import { Observable, from, lastValueFrom } from "rxjs";
5
5
  import { createTRPCClient, httpLink } from "@trpc/client";
6
6
  import superjson from "superjson";
7
7
  //#region src/device/real-device.ts
@@ -15,10 +15,11 @@ const splitChunks = (chunk, buffer) => {
15
15
  return chunks;
16
16
  };
17
17
  const removeStatus = (result) => result.slice(0, result.length - 2);
18
- const sendByChunk = async (transport, command, data, chunkSize = MAX_CHUNK_LENGTH) => {
18
+ const sendByChunk = async (transport, command, data, signal, chunkSize = MAX_CHUNK_LENGTH) => {
19
19
  const chunks = splitChunks(chunkSize, data);
20
20
  let response = Buffer.from([]);
21
21
  for (let i = 0; i < chunks.length; i++) {
22
+ throwIfDeviceOperationAborted(signal);
22
23
  const chunk = chunks[i];
23
24
  const apdu = [...command];
24
25
  /* v8 ignore next -- @preserve */
@@ -108,19 +109,31 @@ const genericRetryOnError = (err) => {
108
109
  };
109
110
  function createRealDevice(options, logger) {
110
111
  const { transportId } = options;
111
- async function run(job, retryOnError = genericRetryOnError) {
112
- return lastValueFrom(withDevicePolling(transportId)((transport) => from(job(transport)), retryOnError));
112
+ async function run(job, retryOnError = genericRetryOnError, signal) {
113
+ throwIfDeviceOperationAborted(signal);
114
+ const polling = withDevicePolling(transportId)((transport) => from(job(transport)), retryOnError);
115
+ const abortablePolling = new Observable((subscriber) => {
116
+ const subscription = polling.subscribe(subscriber);
117
+ const onAbort = () => subscriber.error(new DeviceOperationAbortedError());
118
+ signal?.addEventListener("abort", onAbort, { once: true });
119
+ return () => {
120
+ signal?.removeEventListener("abort", onAbort);
121
+ subscription.unsubscribe();
122
+ };
123
+ });
124
+ return lastValueFrom(abortablePolling);
113
125
  }
114
126
  /* v8 ignore next -- @preserve */
115
127
  const noRetry = () => false;
116
128
  /* v8 ignore next -- @preserve */
117
129
  const alwaysRetry = () => true;
118
130
  const poll = async (opts = {}) => {
119
- await run(async (_transport) => {}, opts.retryOnError ?? alwaysRetry);
131
+ await run(async (_transport) => {}, opts.retryOnError ?? alwaysRetry, opts.signal);
120
132
  try {
121
133
  await getPublicKey(PATHS.U2F, {
122
134
  secp256k1: true,
123
- retryOnError: noRetry
135
+ retryOnError: noRetry,
136
+ signal: opts.signal
124
137
  });
125
138
  return { isAppOpened: true };
126
139
  } catch (err) {
@@ -128,12 +141,12 @@ function createRealDevice(options, logger) {
128
141
  throw err;
129
142
  }
130
143
  };
131
- const pollApp = async (appName) => {
132
- const res = await run((transport) => transport.send(224, 216, 0, 0, Buffer.from(appName, "utf8")));
133
- await new Promise((r) => setTimeout(r, 1e3));
144
+ const pollApp = async (appName, opts = {}) => {
145
+ const res = await run((transport) => transport.send(224, 216, 0, 0, Buffer.from(appName, "utf8")), genericRetryOnError, opts.signal);
146
+ await deviceOperationDelay(1e3, opts.signal);
134
147
  return res.toString("hex");
135
148
  };
136
- const getPublicKey = async (pathString, { secp256k1, retryOnError }) => {
149
+ const getPublicKey = async (pathString, { secp256k1, retryOnError, signal }) => {
137
150
  logger.info(`[device] getPublicKey (${pathString})`);
138
151
  const path = getPathFromPathString(pathString);
139
152
  const data = Buffer.concat([Buffer.from([path.length]), ...path.map((derivation) => {
@@ -142,7 +155,7 @@ function createRealDevice(options, logger) {
142
155
  return buf;
143
156
  })]);
144
157
  const curve = secp256k1 ? 1 : 2;
145
- const response = await run((transport) => transport.send(224, 64, curve, 0, data), retryOnError ?? genericRetryOnError);
158
+ const response = await run((transport) => transport.send(224, 64, curve, 0, data), retryOnError ?? genericRetryOnError, signal);
146
159
  const pubKeyLength = response.readInt8(0);
147
160
  return {
148
161
  pubKey: response.slice(1, pubKeyLength + 1).toString("hex").toUpperCase(),
@@ -212,8 +225,8 @@ function createRealDevice(options, logger) {
212
225
  const response = await run((transport) => transport.send(224, 68, 1, 0, data));
213
226
  return removeStatus(response).toString("base64");
214
227
  },
215
- getAttestation: async () => {
216
- const bufWithStatus = await run((transport) => transport.send(224, 65, 0, 0));
228
+ getAttestation: async (opts = {}) => {
229
+ const bufWithStatus = await run((transport) => transport.send(224, 65, 0, 0), genericRetryOnError, opts.signal);
217
230
  const buffer = removeStatus(bufWithStatus);
218
231
  const certLen = buffer.readInt8(98);
219
232
  return {
@@ -225,9 +238,9 @@ function createRealDevice(options, logger) {
225
238
  buffer
226
239
  };
227
240
  },
228
- getConfidentialityKey: async () => {
241
+ getConfidentialityKey: async (opts = {}) => {
229
242
  logger.info(`[device] getConfidentialityKey`);
230
- const bufWithStatus = await run((transport) => transport.send(224, 77, 0, 0));
243
+ const bufWithStatus = await run((transport) => transport.send(224, 77, 0, 0), genericRetryOnError, opts.signal);
231
244
  const buf = removeStatus(bufWithStatus);
232
245
  const keyLength = buf[0];
233
246
  const key = buf.slice(1, 1 + keyLength);
@@ -245,7 +258,7 @@ function createRealDevice(options, logger) {
245
258
  },
246
259
  getU2FPubKey: () => getPublicKey(PATHS.U2F, { secp256k1: false }),
247
260
  getValidationPubKey: () => getPublicKey(PATHS.VALIDATION, { secp256k1: true }),
248
- hasPairing: async () => {
261
+ hasPairing: async (opts = {}) => {
249
262
  logger.info(`[device] hasPairing`);
250
263
  const apdu = [
251
264
  224,
@@ -254,14 +267,14 @@ function createRealDevice(options, logger) {
254
267
  0,
255
268
  181
256
269
  ];
257
- const res = await run((transport) => transport.exchange(Buffer.from(apdu)));
270
+ const res = await run((transport) => transport.exchange(Buffer.from(apdu)), genericRetryOnError, opts.signal);
258
271
  const swHex = res.subarray(res.length - 2).toString("hex");
259
272
  if (swHex === "6f45" || swHex === "6a87") return "NO_PAIRING";
260
273
  if (swHex === "6065") return "PSK_MISSING";
261
274
  return "OK";
262
275
  },
263
- initPairingHandshake: async () => {
264
- const res = await run((transport) => transport.send(224, 70, 0, 0));
276
+ initPairingHandshake: async (opts = {}) => {
277
+ const res = await run((transport) => transport.send(224, 70, 0, 0), genericRetryOnError, opts.signal);
265
278
  const pubKeyLength = res.readInt8(0);
266
279
  return {
267
280
  pubKey: res.slice(1, pubKeyLength + 1).toString("hex").toUpperCase(),
@@ -352,7 +365,7 @@ function createRealDevice(options, logger) {
352
365
  const response = await run((transport) => transport.send(224, 3, 0, 0, challenge));
353
366
  return removeStatus(response).toString("hex");
354
367
  },
355
- setPairing: async ({ pairing }) => {
368
+ setPairing: async ({ pairing, signal }) => {
356
369
  logger.info(`[device] setPairing`);
357
370
  const psdPayload = Buffer.from(pairing.blob, "base64");
358
371
  const length = Buffer.alloc(2);
@@ -363,11 +376,11 @@ function createRealDevice(options, logger) {
363
376
  78,
364
377
  0,
365
378
  0
366
- ], final));
379
+ ], final, signal), genericRetryOnError, signal);
367
380
  },
368
- signRefreshPskAuth: async () => {
381
+ signRefreshPskAuth: async ({ signal }) => {
369
382
  logger.info(`[device] signRefreshPskAuth`);
370
- const res = await run((transport) => transport.send(224, 85, 0, 0));
383
+ const res = await run((transport) => transport.send(224, 85, 0, 0), genericRetryOnError, signal);
371
384
  return res.slice(0, res.length - 2).toString("base64");
372
385
  },
373
386
  finalizePskRefresh: async (params) => {
@@ -378,7 +391,7 @@ function createRealDevice(options, logger) {
378
391
  83,
379
392
  0,
380
393
  0
381
- ], payload));
394
+ ], payload, params.signal), genericRetryOnError, params.signal);
382
395
  },
383
396
  startKKPatternAsResponder: async (params) => {
384
397
  logger.info(`[device] startKKPatternAsResponder`);
@@ -404,7 +417,7 @@ function createRealDevice(options, logger) {
404
417
  73,
405
418
  0,
406
419
  SCRIPT_IDS.ACCOUNT_MANAGER_SESSION
407
- ], data));
420
+ ], data, params.signal), genericRetryOnError, params.signal);
408
421
  },
409
422
  startPskSession: async (params) => {
410
423
  logger.info(`[device] startPskSession`);
@@ -416,7 +429,7 @@ function createRealDevice(options, logger) {
416
429
  80,
417
430
  0,
418
431
  0
419
- ], data));
432
+ ], data, params.signal), genericRetryOnError, params.signal);
420
433
  },
421
434
  validateVaultOperation: async (params) => {
422
435
  logger.info(`[device] validateVaultOperation`);
@@ -442,7 +455,7 @@ function createRealDevice(options, logger) {
442
455
  69,
443
456
  0,
444
457
  0
445
- ], data));
458
+ ], data, params.signal), genericRetryOnError, params.signal);
446
459
  const responseType = response.readInt8(0);
447
460
  let responseStatus = response.readUInt16BE(response.length - 2);
448
461
  switch (responseType) {
@@ -452,7 +465,7 @@ function createRealDevice(options, logger) {
452
465
  case 2:
453
466
  finalResponse = response.slice(3, response.length - 2);
454
467
  while (responseStatus !== 36864) {
455
- const resp = await run((transport) => transport.send(0, 192, 0, 0));
468
+ const resp = await run((transport) => transport.send(0, 192, 0, 0), genericRetryOnError, params.signal);
456
469
  responseStatus = resp.readUInt16BE(resp.length - 2);
457
470
  finalResponse = Buffer.concat([finalResponse, removeStatus(resp)]);
458
471
  }
@@ -467,8 +480,8 @@ function createRealDevice(options, logger) {
467
480
  const res = await run((transport) => transport.exchange(Buffer.from(apduCommand, "hex")));
468
481
  return Buffer.from(res).toString("hex");
469
482
  },
470
- getVersion: async () => {
471
- const res = await run((transport) => transport.send(176, 1, 0, 0));
483
+ getVersion: async (opts = {}) => {
484
+ const res = await run((transport) => transport.send(176, 1, 0, 0), genericRetryOnError, opts.signal);
472
485
  const appNameLen = res.readInt8(1);
473
486
  const appVersionLen = res.readInt8(2 + appNameLen);
474
487
  const appVersionHex = res.slice(3 + appNameLen, 2 + appNameLen + appVersionLen + 1);
@@ -498,24 +511,34 @@ const createClient = (options) => {
498
511
  };
499
512
  //#endregion
500
513
  //#region src/engine/index.ts
501
- const openSecureChannel = (device, challenge) => {
514
+ const openSecureChannel = (device, challenge, signal) => {
502
515
  if (challenge.handshake.length === 64) return device.startPskSession({
503
516
  nonce: challenge.handshake,
504
- partitionId: challenge.partitionId
517
+ partitionId: challenge.partitionId,
518
+ signal
505
519
  });
506
520
  return device.startKPatternAsResponder({
507
521
  handshake: challenge.handshake,
508
522
  handshakeAttestation: challenge.handshakeAttestation,
509
- partitionId: challenge.partitionId
523
+ partitionId: challenge.partitionId,
524
+ signal
510
525
  });
511
526
  };
512
- const pairDevice = async ({ device, getPairing }) => {
513
- const pairing = await getPairing(await device.getConfidentialityKey());
514
- await device.setPairing({ pairing });
527
+ const pairDevice = async ({ device, getPairing, signal }) => {
528
+ const pairing = await getPairing(await device.getConfidentialityKey({ signal }));
529
+ throwIfDeviceOperationAborted(signal);
530
+ await device.setPairing({
531
+ pairing,
532
+ signal
533
+ });
515
534
  };
516
- const refreshPsk = async ({ device, getRefreshPskData }) => {
517
- const eph = await device.initPairingHandshake();
518
- const [deviceAttestation, validationKeySignature] = await Promise.all([device.getAttestation(), device.signRefreshPskAuth({ ephemeralKey: eph.pubKey })]);
535
+ const refreshPsk = async ({ device, getRefreshPskData, signal }) => {
536
+ const eph = await device.initPairingHandshake({ signal });
537
+ const [deviceAttestation, validationKeySignature] = await Promise.all([device.getAttestation({ signal }), device.signRefreshPskAuth({
538
+ ephemeralKey: eph.pubKey,
539
+ signal
540
+ })]);
541
+ throwIfDeviceOperationAborted(signal);
519
542
  const refresh = await getRefreshPskData({
520
543
  psdEphemeralKey: Buffer.from(eph.pubKey, "hex").toString("base64"),
521
544
  psdEphemeralKeyAttestation: {
@@ -526,7 +549,11 @@ const refreshPsk = async ({ device, getRefreshPskData }) => {
526
549
  },
527
550
  validationKeySignature
528
551
  });
529
- await device.finalizePskRefresh(refresh);
552
+ throwIfDeviceOperationAborted(signal);
553
+ await device.finalizePskRefresh({
554
+ ...refresh,
555
+ signal
556
+ });
530
557
  };
531
558
  async function genericRegister({ device, name, role, getChallenge, submitChallenge, onScreenDisplayed }) {
532
559
  const [attestation, u2fPubKey, confidentialityKey] = await Promise.all([
@@ -883,13 +910,14 @@ function createEngine(options, logger) {
883
910
  });
884
911
  if (engine.device && engine.__workspaceName && engine.__psdToken) decorate((engine) => {
885
912
  const { client, device } = engine;
886
- async function enforcePairing() {
887
- const status = await device.hasPairing();
913
+ async function enforcePairing(signal) {
914
+ const status = await device.hasPairing({ signal });
888
915
  if (status === "OK") return;
889
916
  const refreshCurrentPsk = async () => {
890
917
  await refreshPsk({
891
918
  device,
892
- getRefreshPskData: (data) => client.debug.refreshPsk.mutate(data)
919
+ getRefreshPskData: (data) => client.debug.refreshPsk.mutate(data),
920
+ signal
893
921
  });
894
922
  };
895
923
  if (status === "NO_PAIRING") await pairDevice({
@@ -897,28 +925,37 @@ function createEngine(options, logger) {
897
925
  getPairing: (confidentialityKey) => client.debug.pairDevicePartition.mutate({
898
926
  confidentialityKeyCurve25519: confidentialityKey.confidentialityKeyCurve25519,
899
927
  confidentialityKeySignature: confidentialityKey.confidentialityKeySignature
900
- })
928
+ }),
929
+ signal
901
930
  });
902
931
  await refreshCurrentPsk();
903
932
  }
904
933
  const approveChallenge = async (challenge, params) => {
905
- await enforcePairing();
934
+ await enforcePairing(params?.signal);
906
935
  try {
907
- await openSecureChannel(device, challenge);
936
+ await openSecureChannel(device, challenge, params?.signal);
908
937
  } catch (err) {
909
938
  if (!isPskMissingError(err)) throw err;
910
- await enforcePairing();
911
- await openSecureChannel(device, challenge);
939
+ await enforcePairing(params?.signal);
940
+ await openSecureChannel(device, challenge, params?.signal);
912
941
  }
942
+ throwIfDeviceOperationAborted(params?.signal);
913
943
  await params?.onScreenDisplayed?.();
914
- return await device.validateVaultOperation({ actions: challenge.wActions });
944
+ throwIfDeviceOperationAborted(params?.signal);
945
+ return await device.validateVaultOperation({
946
+ actions: challenge.wActions,
947
+ signal: params?.signal
948
+ });
915
949
  };
916
950
  return {
917
951
  approve: async (request, params) => {
952
+ throwIfDeviceOperationAborted(params?.signal);
918
953
  const { challenge } = await client.requests.getChallenge.query({ id: request.id });
919
954
  if (challenge.challengeType === "PSD") {
920
955
  const signature = await approveChallenge(challenge, params);
956
+ throwIfDeviceOperationAborted(params?.signal);
921
957
  await params?.onPsdChallengeSigned?.();
958
+ throwIfDeviceOperationAborted(params?.signal);
922
959
  return client.requests.approve.mutate({
923
960
  id: request.id,
924
961
  data: {
@@ -927,10 +964,13 @@ function createEngine(options, logger) {
927
964
  }
928
965
  }, buildTrpcContext(params));
929
966
  }
930
- if (challenge.challengeType === "SOFT") return client.requests.approve.mutate({
931
- id: request.id,
932
- data: { challengeType: "SOFT" }
933
- }, buildTrpcContext(params));
967
+ if (challenge.challengeType === "SOFT") {
968
+ throwIfDeviceOperationAborted(params?.signal);
969
+ return client.requests.approve.mutate({
970
+ id: request.id,
971
+ data: { challengeType: "SOFT" }
972
+ }, buildTrpcContext(params));
973
+ }
934
974
  throw new Error("Challenge type must be either PSD or SOFT");
935
975
  },
936
976
  approveChallenge,