@ledgerhq/revault-sdk 0.27.16 → 0.28.1
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.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as LockedDeviceError, d as PAGINATED_STATUS, f as PATHS, i as isTransportStatusError, l as createApiDevice, m as STR_ROLES, o as withDevicePolling, p as SCRIPT_IDS, r as isPskMissingError, s as DisconnectedDevice, u as APPID_VAULT_ADMINISTRATOR } from "./device-Bo6m3O1I.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 { match } from "ts-pattern";
|
|
4
5
|
import { from, lastValueFrom } from "rxjs";
|
|
5
6
|
import { createTRPCClient, httpLink } from "@trpc/client";
|
|
6
7
|
import superjson from "superjson";
|
|
@@ -62,6 +63,43 @@ const buildAttestation = (params) => {
|
|
|
62
63
|
totalLengthBuffer.writeUInt16BE(data.length, 0);
|
|
63
64
|
return Buffer.concat([totalLengthBuffer, data]);
|
|
64
65
|
};
|
|
66
|
+
const buildPskRefreshPayload = (params) => {
|
|
67
|
+
const sigBuffer = Buffer.from(params.handshakeAttestation.signature, "base64");
|
|
68
|
+
const sigLengthBuffer = Buffer.alloc(1);
|
|
69
|
+
sigLengthBuffer.writeUIntBE(sigBuffer.length, 0, 1);
|
|
70
|
+
const certBuffer = Buffer.from(params.handshakeAttestation.certificate, "base64");
|
|
71
|
+
const certLengthBuffer = Buffer.alloc(1);
|
|
72
|
+
certLengthBuffer.writeUIntBE(certBuffer.length, 0, 1);
|
|
73
|
+
const pubBuffer = Buffer.from(params.handshakeAttestation.attestationPub, "base64");
|
|
74
|
+
const certChainData = Buffer.concat([
|
|
75
|
+
sigLengthBuffer,
|
|
76
|
+
sigBuffer,
|
|
77
|
+
pubBuffer,
|
|
78
|
+
certLengthBuffer,
|
|
79
|
+
certBuffer
|
|
80
|
+
]);
|
|
81
|
+
const handshakeBuffer = Buffer.from(params.handshake, "base64");
|
|
82
|
+
invariant(handshakeBuffer.length <= 255, `PSK refresh handshake too long: ${handshakeBuffer.length} bytes (max 255)`);
|
|
83
|
+
const handshakeLengthBuffer = Buffer.alloc(1);
|
|
84
|
+
handshakeLengthBuffer.writeUInt8(handshakeBuffer.length, 0);
|
|
85
|
+
const encryptedPayloadBuffer = Buffer.from(params.encryptedPairingPayload, "base64");
|
|
86
|
+
invariant(encryptedPayloadBuffer.length <= 255, `PSK encrypted payload too long: ${encryptedPayloadBuffer.length} bytes (max 255)`);
|
|
87
|
+
const encryptedPayloadLengthBuffer = Buffer.alloc(1);
|
|
88
|
+
encryptedPayloadLengthBuffer.writeUInt8(encryptedPayloadBuffer.length, 0);
|
|
89
|
+
const certChainDataLengthBuff = Buffer.alloc(2);
|
|
90
|
+
certChainDataLengthBuff.writeUInt16BE(certChainData.length, 0);
|
|
91
|
+
const data = Buffer.concat([
|
|
92
|
+
handshakeLengthBuffer,
|
|
93
|
+
handshakeBuffer,
|
|
94
|
+
certChainDataLengthBuff,
|
|
95
|
+
certChainData,
|
|
96
|
+
encryptedPayloadLengthBuffer,
|
|
97
|
+
encryptedPayloadBuffer
|
|
98
|
+
]);
|
|
99
|
+
const totalLengthBuffer = Buffer.alloc(2);
|
|
100
|
+
totalLengthBuffer.writeUInt16BE(data.length, 0);
|
|
101
|
+
return Buffer.concat([totalLengthBuffer, data]);
|
|
102
|
+
};
|
|
65
103
|
/* v8 ignore next -- @preserve */
|
|
66
104
|
const genericRetryOnError = (err) => {
|
|
67
105
|
if (err instanceof LockedDeviceError) return true;
|
|
@@ -211,8 +249,10 @@ function createRealDevice(options, logger) {
|
|
|
211
249
|
181
|
|
212
250
|
];
|
|
213
251
|
const res = await run((transport) => transport.exchange(Buffer.from(apdu)));
|
|
214
|
-
const
|
|
215
|
-
|
|
252
|
+
const swHex = res.subarray(res.length - 2).toString("hex");
|
|
253
|
+
if (swHex === "6f45" || swHex === "6a87") return "NO_PAIRING";
|
|
254
|
+
if (swHex === "6065") return "PSK_MISSING";
|
|
255
|
+
return "OK";
|
|
216
256
|
},
|
|
217
257
|
initPairingHandshake: async () => {
|
|
218
258
|
const res = await run((transport) => transport.send(224, 70, 0, 0));
|
|
@@ -318,6 +358,21 @@ function createRealDevice(options, logger) {
|
|
|
318
358
|
0
|
|
319
359
|
], final));
|
|
320
360
|
},
|
|
361
|
+
signRefreshPskAuth: async () => {
|
|
362
|
+
logger.info(`[device] signRefreshPskAuth`);
|
|
363
|
+
const res = await run((transport) => transport.send(224, 85, 0, 0));
|
|
364
|
+
return res.slice(0, res.length - 2).toString("base64");
|
|
365
|
+
},
|
|
366
|
+
finalizePskRefresh: async (params) => {
|
|
367
|
+
logger.info(`[device] finalizePskRefresh`);
|
|
368
|
+
const payload = buildPskRefreshPayload(params);
|
|
369
|
+
await run((transport) => sendByChunk(transport, [
|
|
370
|
+
224,
|
|
371
|
+
83,
|
|
372
|
+
0,
|
|
373
|
+
0
|
|
374
|
+
], payload));
|
|
375
|
+
},
|
|
321
376
|
startKKPatternAsResponder: async (params) => {
|
|
322
377
|
logger.info(`[device] startKKPatternAsResponder`);
|
|
323
378
|
const data = buildAttestation(params);
|
|
@@ -343,6 +398,18 @@ function createRealDevice(options, logger) {
|
|
|
343
398
|
SCRIPT_IDS.ACCOUNT_MANAGER_SESSION
|
|
344
399
|
], data));
|
|
345
400
|
},
|
|
401
|
+
startPskSession: async (params) => {
|
|
402
|
+
logger.info(`[device] startPskSession`);
|
|
403
|
+
invariant(params.nonce.length === 64, `PSK nonce must be 32 bytes (64 hex chars), got ${params.nonce.length} chars`);
|
|
404
|
+
invariant(params.partitionId.length === 32, `partitionId must be 16 bytes (32 hex chars), got ${params.partitionId.length} chars`);
|
|
405
|
+
const data = Buffer.concat([Buffer.from(params.nonce, "hex"), Buffer.from(params.partitionId, "hex")]);
|
|
406
|
+
await run((transport) => sendByChunk(transport, [
|
|
407
|
+
224,
|
|
408
|
+
80,
|
|
409
|
+
0,
|
|
410
|
+
0
|
|
411
|
+
], data));
|
|
412
|
+
},
|
|
346
413
|
validateVaultOperation: async (params) => {
|
|
347
414
|
logger.info(`[device] validateVaultOperation`);
|
|
348
415
|
const path = getPathFromPathString(PATHS.VALIDATION);
|
|
@@ -423,6 +490,37 @@ const createClient = (options) => {
|
|
|
423
490
|
};
|
|
424
491
|
//#endregion
|
|
425
492
|
//#region src/engine/index.ts
|
|
493
|
+
const PSK_NONCE_BYTE_LEN = 32;
|
|
494
|
+
const openSecureChannel = (device, challenge) => {
|
|
495
|
+
if (challenge.handshake.length === PSK_NONCE_BYTE_LEN * 2) return device.startPskSession({
|
|
496
|
+
nonce: challenge.handshake,
|
|
497
|
+
partitionId: challenge.partitionId
|
|
498
|
+
});
|
|
499
|
+
return device.startKPatternAsResponder({
|
|
500
|
+
handshake: challenge.handshake,
|
|
501
|
+
handshakeAttestation: challenge.handshakeAttestation,
|
|
502
|
+
partitionId: challenge.partitionId
|
|
503
|
+
});
|
|
504
|
+
};
|
|
505
|
+
const pairDevice = async ({ device, getPairing }) => {
|
|
506
|
+
const pairing = await getPairing(await device.getConfidentialityKey());
|
|
507
|
+
await device.setPairing({ pairing });
|
|
508
|
+
};
|
|
509
|
+
const refreshPsk = async ({ device, getRefreshPskData }) => {
|
|
510
|
+
const eph = await device.initPairingHandshake();
|
|
511
|
+
const [deviceAttestation, validationKeySignature] = await Promise.all([device.getAttestation(), device.signRefreshPskAuth({ ephemeralKey: eph.pubKey })]);
|
|
512
|
+
const refresh = await getRefreshPskData({
|
|
513
|
+
psdEphemeralKey: Buffer.from(eph.pubKey, "hex").toString("base64"),
|
|
514
|
+
psdEphemeralKeyAttestation: {
|
|
515
|
+
codeHash: deviceAttestation.attestation.codeHash,
|
|
516
|
+
attestationPub: deviceAttestation.attestation.attestationPub,
|
|
517
|
+
certificate: deviceAttestation.attestation.certificate,
|
|
518
|
+
signature: eph.signature
|
|
519
|
+
},
|
|
520
|
+
validationKeySignature
|
|
521
|
+
});
|
|
522
|
+
await device.finalizePskRefresh(refresh);
|
|
523
|
+
};
|
|
426
524
|
async function genericRegister({ device, name, role, getChallenge, submitChallenge, onScreenDisplayed }) {
|
|
427
525
|
const [attestation, u2fPubKey, confidentialityKey] = await Promise.all([
|
|
428
526
|
device.getAttestation(),
|
|
@@ -562,18 +660,55 @@ function createEngine(options, logger) {
|
|
|
562
660
|
});
|
|
563
661
|
if (engine.device && engine.__onboardingId) decorate((engine) => {
|
|
564
662
|
const { device, client, __onboardingId } = engine;
|
|
663
|
+
const isPskEnabled = async () => {
|
|
664
|
+
try {
|
|
665
|
+
return await client.onboarding.getPskFeatureFlag.query({ onboardingId: __onboardingId }) === true;
|
|
666
|
+
} catch {
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
};
|
|
565
670
|
async function enforcePairing() {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
671
|
+
const status = await device.hasPairing();
|
|
672
|
+
if (status === "OK") return;
|
|
673
|
+
const pskEnabled = await isPskEnabled();
|
|
674
|
+
let pubKeyPromise;
|
|
675
|
+
const getPubKey = async () => {
|
|
676
|
+
pubKeyPromise ??= device.getU2FPubKey().then(({ pubKey }) => pubKey);
|
|
677
|
+
return pubKeyPromise;
|
|
678
|
+
};
|
|
679
|
+
const pairCurrentDevice = async () => {
|
|
680
|
+
const pubKey = await getPubKey();
|
|
681
|
+
await pairDevice({
|
|
682
|
+
device,
|
|
683
|
+
getPairing: (confidentialityKey) => client.onboarding.pairDevicePartition.mutate({
|
|
684
|
+
pubKey,
|
|
685
|
+
onboardingId: __onboardingId,
|
|
686
|
+
data: {
|
|
687
|
+
confidentialityKeyCurve25519: confidentialityKey.confidentialityKeyCurve25519,
|
|
688
|
+
confidentialityKeySignature: confidentialityKey.confidentialityKeySignature
|
|
689
|
+
}
|
|
690
|
+
})
|
|
691
|
+
});
|
|
692
|
+
};
|
|
693
|
+
const refreshCurrentPsk = async () => {
|
|
694
|
+
const pubKey = await getPubKey();
|
|
695
|
+
await refreshPsk({
|
|
696
|
+
device,
|
|
697
|
+
getRefreshPskData: (data) => client.onboarding.refreshPsk.mutate({
|
|
698
|
+
pubKey,
|
|
699
|
+
onboardingId: __onboardingId,
|
|
700
|
+
data
|
|
701
|
+
})
|
|
702
|
+
});
|
|
703
|
+
};
|
|
704
|
+
await match([status, pskEnabled]).with(["NO_PAIRING", false], async () => {
|
|
705
|
+
await pairCurrentDevice();
|
|
706
|
+
}).with(["NO_PAIRING", true], async () => {
|
|
707
|
+
await pairCurrentDevice();
|
|
708
|
+
await refreshCurrentPsk();
|
|
709
|
+
}).with(["PSK_MISSING", false], async () => {}).with(["PSK_MISSING", true], async () => {
|
|
710
|
+
await refreshCurrentPsk();
|
|
711
|
+
}).exhaustive();
|
|
577
712
|
}
|
|
578
713
|
return {
|
|
579
714
|
claimDevice: async (params) => {
|
|
@@ -635,11 +770,7 @@ function createEngine(options, logger) {
|
|
|
635
770
|
reviewSharedOwner: async (challenge, params) => {
|
|
636
771
|
await enforcePairing();
|
|
637
772
|
const { pubKey } = await device.getU2FPubKey();
|
|
638
|
-
await device
|
|
639
|
-
handshake: challenge.handshake,
|
|
640
|
-
handshakeAttestation: challenge.handshakeAttestation,
|
|
641
|
-
partitionId: challenge.partitionId
|
|
642
|
-
});
|
|
773
|
+
await openSecureChannel(device, challenge);
|
|
643
774
|
await params?.onScreenDisplayed?.();
|
|
644
775
|
const signature = await device.validateVaultOperation({ actions: challenge.wActions });
|
|
645
776
|
await client.onboarding.submitReviewByMasterAdminSignature.mutate({
|
|
@@ -651,11 +782,7 @@ function createEngine(options, logger) {
|
|
|
651
782
|
reviewExternalSigners: async (challenge, params) => {
|
|
652
783
|
await enforcePairing();
|
|
653
784
|
const { pubKey } = await device.getU2FPubKey();
|
|
654
|
-
await device
|
|
655
|
-
handshake: challenge.handshake,
|
|
656
|
-
handshakeAttestation: challenge.handshakeAttestation,
|
|
657
|
-
partitionId: challenge.partitionId
|
|
658
|
-
});
|
|
785
|
+
await openSecureChannel(device, challenge);
|
|
659
786
|
await params?.onScreenDisplayed?.();
|
|
660
787
|
const signature = await device.validateVaultOperation({ actions: challenge.wActions });
|
|
661
788
|
await client.onboarding.submitExternalSignersRegistration.mutate({
|
|
@@ -667,11 +794,7 @@ function createEngine(options, logger) {
|
|
|
667
794
|
collectSeedFragment: async (challenge, params) => {
|
|
668
795
|
await enforcePairing();
|
|
669
796
|
const [{ pubKey }, { attestation }] = await Promise.all([device.getU2FPubKey(), device.getAttestation()]);
|
|
670
|
-
await device
|
|
671
|
-
handshake: challenge.handshake,
|
|
672
|
-
handshakeAttestation: challenge.handshakeAttestation,
|
|
673
|
-
partitionId: challenge.partitionId
|
|
674
|
-
});
|
|
797
|
+
await openSecureChannel(device, challenge);
|
|
675
798
|
await params?.onScreenDisplayed?.();
|
|
676
799
|
const challengeResponse = await device.validateVaultOperation({ actions: challenge.wActions });
|
|
677
800
|
const kkPattern = await device.startKKPatternAsResponder({
|
|
@@ -767,22 +890,53 @@ function createEngine(options, logger) {
|
|
|
767
890
|
});
|
|
768
891
|
if (engine.device && engine.__workspaceName && engine.__psdToken) decorate((engine) => {
|
|
769
892
|
const { client, device } = engine;
|
|
893
|
+
const isPskEnabled = async () => {
|
|
894
|
+
try {
|
|
895
|
+
return await client.debug.getPskFeatureFlag.query() === true;
|
|
896
|
+
} catch {
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
899
|
+
};
|
|
770
900
|
async function enforcePairing() {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
901
|
+
const status = await device.hasPairing();
|
|
902
|
+
if (status === "OK") return;
|
|
903
|
+
const pskEnabled = await isPskEnabled();
|
|
904
|
+
const pairCurrentDevice = async () => {
|
|
905
|
+
await pairDevice({
|
|
906
|
+
device,
|
|
907
|
+
getPairing: (confidentialityKey) => client.debug.pairDevicePartition.mutate({
|
|
908
|
+
confidentialityKeyCurve25519: confidentialityKey.confidentialityKeyCurve25519,
|
|
909
|
+
confidentialityKeySignature: confidentialityKey.confidentialityKeySignature
|
|
910
|
+
})
|
|
911
|
+
});
|
|
912
|
+
};
|
|
913
|
+
const refreshCurrentPsk = async () => {
|
|
914
|
+
await refreshPsk({
|
|
915
|
+
device,
|
|
916
|
+
getRefreshPskData: (data) => client.debug.refreshPsk.mutate(data)
|
|
917
|
+
});
|
|
918
|
+
};
|
|
919
|
+
await match([status, pskEnabled]).with(["NO_PAIRING", false], async () => {
|
|
920
|
+
await pairCurrentDevice();
|
|
921
|
+
}).with(["NO_PAIRING", true], async () => {
|
|
922
|
+
await pairCurrentDevice();
|
|
923
|
+
await refreshCurrentPsk();
|
|
924
|
+
}).with(["PSK_MISSING", false], async () => {}).with(["PSK_MISSING", true], async () => {
|
|
925
|
+
await refreshCurrentPsk();
|
|
926
|
+
}).exhaustive();
|
|
778
927
|
}
|
|
779
928
|
const approveChallenge = async (challenge, params) => {
|
|
780
929
|
await enforcePairing();
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
930
|
+
try {
|
|
931
|
+
await openSecureChannel(device, challenge);
|
|
932
|
+
} catch (err) {
|
|
933
|
+
if (!isPskMissingError(err)) throw err;
|
|
934
|
+
if (!await isPskEnabled()) logger.warning("Discarding SW_PSK_MISSING: PSK secure channel is disabled");
|
|
935
|
+
else {
|
|
936
|
+
await enforcePairing();
|
|
937
|
+
await openSecureChannel(device, challenge);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
786
940
|
await params?.onScreenDisplayed?.();
|
|
787
941
|
return await device.validateVaultOperation({ actions: challenge.wActions });
|
|
788
942
|
};
|