@ledgerhq/revault-sdk 0.27.15 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +4 -7
  2. package/lib/bake.cjs +1 -1
  3. package/lib/bake.d.cts +1 -1
  4. package/lib/bake.d.ts +1 -1
  5. package/lib/bake.js +1 -1
  6. package/lib/{crypto-BxlPOWD9.cjs → crypto-B4xS0N1u.cjs} +2 -2
  7. package/lib/{crypto-BxlPOWD9.cjs.map → crypto-B4xS0N1u.cjs.map} +1 -1
  8. package/lib/{crypto-BHYSptBe.js → crypto-BZw0pZ1Z.js} +2 -2
  9. package/lib/{crypto-BHYSptBe.js.map → crypto-BZw0pZ1Z.js.map} +1 -1
  10. package/lib/crypto.cjs +1 -1
  11. package/lib/crypto.js +1 -1
  12. package/lib/{device-DBzLykrI.js → device-Bo6m3O1I.js} +51 -3
  13. package/lib/device-Bo6m3O1I.js.map +1 -0
  14. package/lib/{device-B6QZOEDT.cjs → device-dbZH_1Pt.cjs} +63 -2
  15. package/lib/device-dbZH_1Pt.cjs.map +1 -0
  16. package/lib/device.cjs +3 -1
  17. package/lib/device.d.cts +2 -2
  18. package/lib/device.d.ts +2 -2
  19. package/lib/device.js +2 -2
  20. package/lib/engine.cjs +195 -41
  21. package/lib/engine.cjs.map +1 -1
  22. package/lib/engine.d.cts +2 -2
  23. package/lib/engine.d.ts +2 -2
  24. package/lib/engine.js +195 -41
  25. package/lib/engine.js.map +1 -1
  26. package/lib/{index-sWTlrbOl.d.cts → index-C5AAKtIf.d.cts} +49 -4
  27. package/lib/{index-B7SUQ6NW.d.cts → index-D_EED8aS.d.cts} +1 -1
  28. package/lib/{index-CuWiM97X.d.ts → index-Ds_t9ilJ.d.ts} +49 -4
  29. package/lib/{index-DLUJ9Etg.d.ts → index-aYbRTZvr.d.ts} +1 -1
  30. package/lib/onboarding.cjs +1 -1
  31. package/lib/onboarding.d.cts +1 -1
  32. package/lib/onboarding.d.ts +1 -1
  33. package/lib/onboarding.js +1 -1
  34. package/lib/{types-BN2MzD_5.d.ts → types-ALsH_YtN.d.ts} +1 -1
  35. package/lib/{types-BXAwSr1F.d.cts → types-DP-rmxvy.d.cts} +1 -1
  36. package/package.json +2 -2
  37. package/lib/device-B6QZOEDT.cjs.map +0 -1
  38. package/lib/device-DBzLykrI.js.map +0 -1
package/lib/engine.cjs CHANGED
@@ -1,9 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
3
- const require_device = require("./device-B6QZOEDT.cjs");
3
+ const require_device = require("./device-dbZH_1Pt.cjs");
4
4
  const require_utils = require("./utils-CavXJGCh.cjs");
5
5
  let invariant = require("invariant");
6
6
  invariant = require_rolldown_runtime.__toESM(invariant, 1);
7
+ let ts_pattern = require("ts-pattern");
7
8
  let rxjs = require("rxjs");
8
9
  let _trpc_client = require("@trpc/client");
9
10
  let superjson = require("superjson");
@@ -66,6 +67,43 @@ const buildAttestation = (params) => {
66
67
  totalLengthBuffer.writeUInt16BE(data.length, 0);
67
68
  return Buffer.concat([totalLengthBuffer, data]);
68
69
  };
70
+ const buildPskRefreshPayload = (params) => {
71
+ const sigBuffer = Buffer.from(params.handshakeAttestation.signature, "base64");
72
+ const sigLengthBuffer = Buffer.alloc(1);
73
+ sigLengthBuffer.writeUIntBE(sigBuffer.length, 0, 1);
74
+ const certBuffer = Buffer.from(params.handshakeAttestation.certificate, "base64");
75
+ const certLengthBuffer = Buffer.alloc(1);
76
+ certLengthBuffer.writeUIntBE(certBuffer.length, 0, 1);
77
+ const pubBuffer = Buffer.from(params.handshakeAttestation.attestationPub, "base64");
78
+ const certChainData = Buffer.concat([
79
+ sigLengthBuffer,
80
+ sigBuffer,
81
+ pubBuffer,
82
+ certLengthBuffer,
83
+ certBuffer
84
+ ]);
85
+ const handshakeBuffer = Buffer.from(params.handshake, "base64");
86
+ (0, invariant.default)(handshakeBuffer.length <= 255, `PSK refresh handshake too long: ${handshakeBuffer.length} bytes (max 255)`);
87
+ const handshakeLengthBuffer = Buffer.alloc(1);
88
+ handshakeLengthBuffer.writeUInt8(handshakeBuffer.length, 0);
89
+ const encryptedPayloadBuffer = Buffer.from(params.encryptedPairingPayload, "base64");
90
+ (0, invariant.default)(encryptedPayloadBuffer.length <= 255, `PSK encrypted payload too long: ${encryptedPayloadBuffer.length} bytes (max 255)`);
91
+ const encryptedPayloadLengthBuffer = Buffer.alloc(1);
92
+ encryptedPayloadLengthBuffer.writeUInt8(encryptedPayloadBuffer.length, 0);
93
+ const certChainDataLengthBuff = Buffer.alloc(2);
94
+ certChainDataLengthBuff.writeUInt16BE(certChainData.length, 0);
95
+ const data = Buffer.concat([
96
+ handshakeLengthBuffer,
97
+ handshakeBuffer,
98
+ certChainDataLengthBuff,
99
+ certChainData,
100
+ encryptedPayloadLengthBuffer,
101
+ encryptedPayloadBuffer
102
+ ]);
103
+ const totalLengthBuffer = Buffer.alloc(2);
104
+ totalLengthBuffer.writeUInt16BE(data.length, 0);
105
+ return Buffer.concat([totalLengthBuffer, data]);
106
+ };
69
107
  /* v8 ignore next -- @preserve */
70
108
  const genericRetryOnError = (err) => {
71
109
  if (err instanceof require_device.LockedDeviceError) return true;
@@ -215,8 +253,10 @@ function createRealDevice(options, logger) {
215
253
  181
216
254
  ];
217
255
  const res = await run((transport) => transport.exchange(Buffer.from(apdu)));
218
- const hex = Buffer.from(res).toString("hex");
219
- return hex !== "6f45" && hex !== "6a87";
256
+ const swHex = res.subarray(res.length - 2).toString("hex");
257
+ if (swHex === "6f45" || swHex === "6a87") return "NO_PAIRING";
258
+ if (swHex === "6065") return "PSK_MISSING";
259
+ return "OK";
220
260
  },
221
261
  initPairingHandshake: async () => {
222
262
  const res = await run((transport) => transport.send(224, 70, 0, 0));
@@ -322,6 +362,21 @@ function createRealDevice(options, logger) {
322
362
  0
323
363
  ], final));
324
364
  },
365
+ signRefreshPskAuth: async () => {
366
+ logger.info(`[device] signRefreshPskAuth`);
367
+ const res = await run((transport) => transport.send(224, 85, 0, 0));
368
+ return res.slice(0, res.length - 2).toString("base64");
369
+ },
370
+ finalizePskRefresh: async (params) => {
371
+ logger.info(`[device] finalizePskRefresh`);
372
+ const payload = buildPskRefreshPayload(params);
373
+ await run((transport) => sendByChunk(transport, [
374
+ 224,
375
+ 83,
376
+ 0,
377
+ 0
378
+ ], payload));
379
+ },
325
380
  startKKPatternAsResponder: async (params) => {
326
381
  logger.info(`[device] startKKPatternAsResponder`);
327
382
  const data = buildAttestation(params);
@@ -347,6 +402,18 @@ function createRealDevice(options, logger) {
347
402
  require_device.SCRIPT_IDS.ACCOUNT_MANAGER_SESSION
348
403
  ], data));
349
404
  },
405
+ startPskSession: async (params) => {
406
+ logger.info(`[device] startPskSession`);
407
+ (0, invariant.default)(params.nonce.length === 64, `PSK nonce must be 32 bytes (64 hex chars), got ${params.nonce.length} chars`);
408
+ (0, invariant.default)(params.partitionId.length === 32, `partitionId must be 16 bytes (32 hex chars), got ${params.partitionId.length} chars`);
409
+ const data = Buffer.concat([Buffer.from(params.nonce, "hex"), Buffer.from(params.partitionId, "hex")]);
410
+ await run((transport) => sendByChunk(transport, [
411
+ 224,
412
+ 80,
413
+ 0,
414
+ 0
415
+ ], data));
416
+ },
350
417
  validateVaultOperation: async (params) => {
351
418
  logger.info(`[device] validateVaultOperation`);
352
419
  const path = getPathFromPathString(require_device.PATHS.VALIDATION);
@@ -427,6 +494,37 @@ const createClient = (options) => {
427
494
  };
428
495
  //#endregion
429
496
  //#region src/engine/index.ts
497
+ const PSK_NONCE_BYTE_LEN = 32;
498
+ const openSecureChannel = (device, challenge) => {
499
+ if (challenge.handshake.length === PSK_NONCE_BYTE_LEN * 2) return device.startPskSession({
500
+ nonce: challenge.handshake,
501
+ partitionId: challenge.partitionId
502
+ });
503
+ return device.startKPatternAsResponder({
504
+ handshake: challenge.handshake,
505
+ handshakeAttestation: challenge.handshakeAttestation,
506
+ partitionId: challenge.partitionId
507
+ });
508
+ };
509
+ const pairDevice = async ({ device, getPairing }) => {
510
+ const pairing = await getPairing(await device.getConfidentialityKey());
511
+ await device.setPairing({ pairing });
512
+ };
513
+ const refreshPsk = async ({ device, getRefreshPskData }) => {
514
+ const eph = await device.initPairingHandshake();
515
+ const [deviceAttestation, validationKeySignature] = await Promise.all([device.getAttestation(), device.signRefreshPskAuth({ ephemeralKey: eph.pubKey })]);
516
+ const refresh = await getRefreshPskData({
517
+ psdEphemeralKey: Buffer.from(eph.pubKey, "hex").toString("base64"),
518
+ psdEphemeralKeyAttestation: {
519
+ codeHash: deviceAttestation.attestation.codeHash,
520
+ attestationPub: deviceAttestation.attestation.attestationPub,
521
+ certificate: deviceAttestation.attestation.certificate,
522
+ signature: eph.signature
523
+ },
524
+ validationKeySignature
525
+ });
526
+ await device.finalizePskRefresh(refresh);
527
+ };
430
528
  async function genericRegister({ device, name, role, getChallenge, submitChallenge, onScreenDisplayed }) {
431
529
  const [attestation, u2fPubKey, confidentialityKey] = await Promise.all([
432
530
  device.getAttestation(),
@@ -566,18 +664,55 @@ function createEngine(options, logger) {
566
664
  });
567
665
  if (engine.device && engine.__onboardingId) decorate((engine) => {
568
666
  const { device, client, __onboardingId } = engine;
667
+ const isPskEnabled = async () => {
668
+ try {
669
+ return await client.onboarding.getPskFeatureFlag.query({ onboardingId: __onboardingId }) === true;
670
+ } catch {
671
+ return false;
672
+ }
673
+ };
569
674
  async function enforcePairing() {
570
- if (await device.hasPairing()) return;
571
- const [{ pubKey }, confidentialityKey] = await Promise.all([device.getU2FPubKey(), device.getConfidentialityKey()]);
572
- const pairing = await client.onboarding.pairDevicePartition.mutate({
573
- pubKey,
574
- onboardingId: __onboardingId,
575
- data: {
576
- confidentialityKeyCurve25519: confidentialityKey.confidentialityKeyCurve25519,
577
- confidentialityKeySignature: confidentialityKey.confidentialityKeySignature
578
- }
579
- });
580
- await device.setPairing({ pairing });
675
+ const status = await device.hasPairing();
676
+ if (status === "OK") return;
677
+ const pskEnabled = await isPskEnabled();
678
+ let pubKeyPromise;
679
+ const getPubKey = async () => {
680
+ pubKeyPromise ??= device.getU2FPubKey().then(({ pubKey }) => pubKey);
681
+ return pubKeyPromise;
682
+ };
683
+ const pairCurrentDevice = async () => {
684
+ const pubKey = await getPubKey();
685
+ await pairDevice({
686
+ device,
687
+ getPairing: (confidentialityKey) => client.onboarding.pairDevicePartition.mutate({
688
+ pubKey,
689
+ onboardingId: __onboardingId,
690
+ data: {
691
+ confidentialityKeyCurve25519: confidentialityKey.confidentialityKeyCurve25519,
692
+ confidentialityKeySignature: confidentialityKey.confidentialityKeySignature
693
+ }
694
+ })
695
+ });
696
+ };
697
+ const refreshCurrentPsk = async () => {
698
+ const pubKey = await getPubKey();
699
+ await refreshPsk({
700
+ device,
701
+ getRefreshPskData: (data) => client.onboarding.refreshPsk.mutate({
702
+ pubKey,
703
+ onboardingId: __onboardingId,
704
+ data
705
+ })
706
+ });
707
+ };
708
+ await (0, ts_pattern.match)([status, pskEnabled]).with(["NO_PAIRING", false], async () => {
709
+ await pairCurrentDevice();
710
+ }).with(["NO_PAIRING", true], async () => {
711
+ await pairCurrentDevice();
712
+ await refreshCurrentPsk();
713
+ }).with(["PSK_MISSING", false], async () => {}).with(["PSK_MISSING", true], async () => {
714
+ await refreshCurrentPsk();
715
+ }).exhaustive();
581
716
  }
582
717
  return {
583
718
  claimDevice: async (params) => {
@@ -639,11 +774,7 @@ function createEngine(options, logger) {
639
774
  reviewSharedOwner: async (challenge, params) => {
640
775
  await enforcePairing();
641
776
  const { pubKey } = await device.getU2FPubKey();
642
- await device.startKPatternAsResponder({
643
- handshake: challenge.handshake,
644
- handshakeAttestation: challenge.handshakeAttestation,
645
- partitionId: challenge.partitionId
646
- });
777
+ await openSecureChannel(device, challenge);
647
778
  await params?.onScreenDisplayed?.();
648
779
  const signature = await device.validateVaultOperation({ actions: challenge.wActions });
649
780
  await client.onboarding.submitReviewByMasterAdminSignature.mutate({
@@ -655,11 +786,7 @@ function createEngine(options, logger) {
655
786
  reviewExternalSigners: async (challenge, params) => {
656
787
  await enforcePairing();
657
788
  const { pubKey } = await device.getU2FPubKey();
658
- await device.startKPatternAsResponder({
659
- handshake: challenge.handshake,
660
- handshakeAttestation: challenge.handshakeAttestation,
661
- partitionId: challenge.partitionId
662
- });
789
+ await openSecureChannel(device, challenge);
663
790
  await params?.onScreenDisplayed?.();
664
791
  const signature = await device.validateVaultOperation({ actions: challenge.wActions });
665
792
  await client.onboarding.submitExternalSignersRegistration.mutate({
@@ -671,11 +798,7 @@ function createEngine(options, logger) {
671
798
  collectSeedFragment: async (challenge, params) => {
672
799
  await enforcePairing();
673
800
  const [{ pubKey }, { attestation }] = await Promise.all([device.getU2FPubKey(), device.getAttestation()]);
674
- await device.startKPatternAsResponder({
675
- handshake: challenge.handshake,
676
- handshakeAttestation: challenge.handshakeAttestation,
677
- partitionId: challenge.partitionId
678
- });
801
+ await openSecureChannel(device, challenge);
679
802
  await params?.onScreenDisplayed?.();
680
803
  const challengeResponse = await device.validateVaultOperation({ actions: challenge.wActions });
681
804
  const kkPattern = await device.startKKPatternAsResponder({
@@ -771,22 +894,53 @@ function createEngine(options, logger) {
771
894
  });
772
895
  if (engine.device && engine.__workspaceName && engine.__psdToken) decorate((engine) => {
773
896
  const { client, device } = engine;
897
+ const isPskEnabled = async () => {
898
+ try {
899
+ return await client.debug.getPskFeatureFlag.query() === true;
900
+ } catch {
901
+ return false;
902
+ }
903
+ };
774
904
  async function enforcePairing() {
775
- if (await device.hasPairing()) return;
776
- const confidentialityKey = await device.getConfidentialityKey();
777
- const pairing = await client.debug.pairDevicePartition.mutate({
778
- confidentialityKeyCurve25519: confidentialityKey.confidentialityKeyCurve25519,
779
- confidentialityKeySignature: confidentialityKey.confidentialityKeySignature
780
- });
781
- await device.setPairing({ pairing });
905
+ const status = await device.hasPairing();
906
+ if (status === "OK") return;
907
+ const pskEnabled = await isPskEnabled();
908
+ const pairCurrentDevice = async () => {
909
+ await pairDevice({
910
+ device,
911
+ getPairing: (confidentialityKey) => client.debug.pairDevicePartition.mutate({
912
+ confidentialityKeyCurve25519: confidentialityKey.confidentialityKeyCurve25519,
913
+ confidentialityKeySignature: confidentialityKey.confidentialityKeySignature
914
+ })
915
+ });
916
+ };
917
+ const refreshCurrentPsk = async () => {
918
+ await refreshPsk({
919
+ device,
920
+ getRefreshPskData: (data) => client.debug.refreshPsk.mutate(data)
921
+ });
922
+ };
923
+ await (0, ts_pattern.match)([status, pskEnabled]).with(["NO_PAIRING", false], async () => {
924
+ await pairCurrentDevice();
925
+ }).with(["NO_PAIRING", true], async () => {
926
+ await pairCurrentDevice();
927
+ await refreshCurrentPsk();
928
+ }).with(["PSK_MISSING", false], async () => {}).with(["PSK_MISSING", true], async () => {
929
+ await refreshCurrentPsk();
930
+ }).exhaustive();
782
931
  }
783
932
  const approveChallenge = async (challenge, params) => {
784
933
  await enforcePairing();
785
- await device.startKPatternAsResponder({
786
- handshake: challenge.handshake,
787
- handshakeAttestation: challenge.handshakeAttestation,
788
- partitionId: challenge.partitionId
789
- });
934
+ try {
935
+ await openSecureChannel(device, challenge);
936
+ } catch (err) {
937
+ if (!require_device.isPskMissingError(err)) throw err;
938
+ if (!await isPskEnabled()) logger.warning("Discarding SW_PSK_MISSING: PSK secure channel is disabled");
939
+ else {
940
+ await enforcePairing();
941
+ await openSecureChannel(device, challenge);
942
+ }
943
+ }
790
944
  await params?.onScreenDisplayed?.();
791
945
  return await device.validateVaultOperation({ actions: challenge.wActions });
792
946
  };