@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.
- package/README.md +4 -7
- package/lib/bake.cjs +1 -1
- package/lib/bake.d.cts +1 -1
- package/lib/bake.d.ts +1 -1
- package/lib/bake.js +1 -1
- package/lib/{crypto-BxlPOWD9.cjs → crypto-B4xS0N1u.cjs} +2 -2
- package/lib/{crypto-BxlPOWD9.cjs.map → crypto-B4xS0N1u.cjs.map} +1 -1
- package/lib/{crypto-BHYSptBe.js → crypto-BZw0pZ1Z.js} +2 -2
- package/lib/{crypto-BHYSptBe.js.map → crypto-BZw0pZ1Z.js.map} +1 -1
- package/lib/crypto.cjs +1 -1
- package/lib/crypto.js +1 -1
- package/lib/{device-DBzLykrI.js → device-Bo6m3O1I.js} +51 -3
- package/lib/device-Bo6m3O1I.js.map +1 -0
- package/lib/{device-B6QZOEDT.cjs → device-dbZH_1Pt.cjs} +63 -2
- package/lib/device-dbZH_1Pt.cjs.map +1 -0
- package/lib/device.cjs +3 -1
- package/lib/device.d.cts +2 -2
- package/lib/device.d.ts +2 -2
- package/lib/device.js +2 -2
- package/lib/engine.cjs +195 -41
- package/lib/engine.cjs.map +1 -1
- package/lib/engine.d.cts +2 -2
- package/lib/engine.d.ts +2 -2
- package/lib/engine.js +195 -41
- package/lib/engine.js.map +1 -1
- package/lib/{index-sWTlrbOl.d.cts → index-C5AAKtIf.d.cts} +49 -4
- package/lib/{index-B7SUQ6NW.d.cts → index-D_EED8aS.d.cts} +1 -1
- package/lib/{index-CuWiM97X.d.ts → index-Ds_t9ilJ.d.ts} +49 -4
- package/lib/{index-DLUJ9Etg.d.ts → index-aYbRTZvr.d.ts} +1 -1
- package/lib/onboarding.cjs +1 -1
- package/lib/onboarding.d.cts +1 -1
- package/lib/onboarding.d.ts +1 -1
- package/lib/onboarding.js +1 -1
- package/lib/{types-BN2MzD_5.d.ts → types-ALsH_YtN.d.ts} +1 -1
- package/lib/{types-BXAwSr1F.d.cts → types-DP-rmxvy.d.cts} +1 -1
- package/package.json +2 -2
- package/lib/device-B6QZOEDT.cjs.map +0 -1
- 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-
|
|
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
|
|
219
|
-
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
776
|
-
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
};
|