@onekeyfe/hwk-ledger-adapter 1.1.34-alpha.2 → 1.1.34-alpha.4
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/dist/index.d.mts +46 -1
- package/dist/index.d.ts +46 -1
- package/dist/index.js +353 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +353 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _onekeyfe_hwk_adapter_core from '@onekeyfe/hwk-adapter-core';
|
|
2
|
-
import { IHardwareWallet, IConnector, TransportType, UiResponseEvent, SearchDevicesOptions, DeviceInfo, Response, ChainCapability, NullableCallArg, IHardwareCallParams, EvmGetAddressParams, EvmAddress, EvmSignTxLedgerParams, EvmSignedTx, EvmSignMsgParams, EvmSignature, EvmSignTypedDataParams, BtcGetAddressParams, BtcAddress, BtcGetPublicKeyParams, BtcPublicKey, BtcSignTxParams, BtcSignedTx, BtcSignPsbtParams, BtcSignedPsbt, BtcSignMsgParams, BtcSignature, IHardwareCommonCallParams, SolGetAddressParams, SolAddress, SolSignTxParams, SolSignedTx, SolSignMsgParams, SolSignature, TronGetAddressParams, TronAddress, TronSignTxParams, TronSignedTx, TronSignMsgParams, TronSignature, HardwareEventMap, DeviceEventListener, ChainForFingerprint, DeviceDescriptor, DeviceChangeEvent, ConnectionType, ConnectorDevice, ConnectorSession, ConnectorCallResult, ConnectorEventType, ConnectorEventMap, Failure, HardwareErrorCode } from '@onekeyfe/hwk-adapter-core';
|
|
2
|
+
import { IHardwareWallet, IConnector, TransportType, UiResponseEvent, SearchDevicesOptions, DeviceInfo, Response, ChainCapability, NullableCallArg, IHardwareCallParams, EvmGetAddressParams, EvmAddress, EvmSignTxLedgerParams, EvmSignedTx, EvmSignMsgParams, EvmSignature, EvmSignTypedDataParams, BtcGetAddressParams, BtcAddress, BtcGetPublicKeyParams, BtcPublicKey, BtcSignTxParams, BtcSignedTx, BtcSignPsbtParams, BtcSignedPsbt, BtcSignMsgParams, BtcSignature, IHardwareCommonCallParams, SolGetAddressParams, SolAddress, SolSignTxParams, SolSignedTx, SolSignMsgParams, SolSignature, TronGetAddressParams, TronAddress, TronSignTxParams, TronSignedTx, TronSignMsgParams, TronSignature, DeviceAuthenticityParams, DeviceAuthenticityResult, HardwareEventMap, DeviceEventListener, ChainForFingerprint, DeviceDescriptor, DeviceChangeEvent, ConnectionType, ConnectorDevice, ConnectorSession, ConnectorCallResult, ConnectorEventType, ConnectorEventMap, ConnectorConfig, Failure, HardwareErrorCode } from '@onekeyfe/hwk-adapter-core';
|
|
3
3
|
import { DeviceActionState as DeviceActionState$1, DiscoveredDevice, ExecuteDeviceActionReturnType, DeviceManagementKit } from '@ledgerhq/device-management-kit';
|
|
4
4
|
import { Address, Signature, SignerEth as SignerEth$1, TypedData } from '@ledgerhq/device-signer-kit-ethereum';
|
|
5
5
|
import { ContextModule } from '@ledgerhq/context-module';
|
|
@@ -93,6 +93,19 @@ interface LedgerDeviceInfo extends FirmwareVersion {
|
|
|
93
93
|
|
|
94
94
|
type LedgerCallParams<T> = NullableCallArg<IHardwareCallParams<T>>;
|
|
95
95
|
type LedgerCommonParams = NullableCallArg<IHardwareCommonCallParams>;
|
|
96
|
+
type LedgerAttestationBridgeDevice = {
|
|
97
|
+
id: string;
|
|
98
|
+
modelId: 'nanoS' | 'nanoSP' | 'nanoX' | 'stax' | 'flex' | 'apexp';
|
|
99
|
+
name?: string;
|
|
100
|
+
connectionType?: 'USB' | 'BLE';
|
|
101
|
+
};
|
|
102
|
+
type LedgerAttestationApduBridge = {
|
|
103
|
+
device: LedgerAttestationBridgeDevice;
|
|
104
|
+
exchangeApdu: (apduHex: string, timeoutMs?: number) => Promise<{
|
|
105
|
+
dataHex: string;
|
|
106
|
+
statusCodeHex: string;
|
|
107
|
+
}>;
|
|
108
|
+
};
|
|
96
109
|
declare class LedgerAdapter implements IHardwareWallet {
|
|
97
110
|
readonly vendor: "ledger";
|
|
98
111
|
private readonly connector;
|
|
@@ -102,6 +115,7 @@ declare class LedgerAdapter implements IHardwareWallet {
|
|
|
102
115
|
private readonly _uiRegistry;
|
|
103
116
|
private _btcHighIndexConfirmedThisSession;
|
|
104
117
|
private readonly _jobQueue;
|
|
118
|
+
private _deviceAuthenticityQueueTail;
|
|
105
119
|
private _doConnectAbortController;
|
|
106
120
|
private readonly _defaultAutoInstallApp;
|
|
107
121
|
constructor(connector: IConnector, options?: {
|
|
@@ -162,6 +176,25 @@ declare class LedgerAdapter implements IHardwareWallet {
|
|
|
162
176
|
listAvailableApps(connectId: string): Promise<Response<AppMetadata[]>>;
|
|
163
177
|
getLedgerFirmwareVersion(connectId: string): Promise<Response<FirmwareVersion>>;
|
|
164
178
|
getLedgerDeviceInfo(connectId: string): Promise<Response<LedgerDeviceInfo>>;
|
|
179
|
+
/**
|
|
180
|
+
* Reserves the existing physical Ledger session while a server-owned DMK
|
|
181
|
+
* Genuine Check drives it. The callback can only exchange raw APDUs; the
|
|
182
|
+
* authoritative verdict remains in the server state machine.
|
|
183
|
+
*/
|
|
184
|
+
runDeviceAttestationApduBridge<T>(connectId: string, run: (bridge: LedgerAttestationApduBridge) => Promise<T>): Promise<Response<T>>;
|
|
185
|
+
/**
|
|
186
|
+
* Runs Ledger's official genuine check (DMK GenuineCheckDeviceAction) over the
|
|
187
|
+
* SAME secure-channel backend as app install
|
|
188
|
+
* (wss://scriptrunner.api.live.ledger.com/update/genuine). It returns Ledger's
|
|
189
|
+
* HSM verdict (`verified`) and a stable per-device id = sha3-256 of the device
|
|
190
|
+
* attestation public key, which DMK reads inside that session. The id survives
|
|
191
|
+
* wipe/recovery and cannot be forged from a seed.
|
|
192
|
+
*
|
|
193
|
+
* Requires network access to Ledger's backend and an on-device
|
|
194
|
+
* "Allow secure connection" confirmation the first time.
|
|
195
|
+
*/
|
|
196
|
+
verifyDeviceAuthenticity(connectId: string, params?: DeviceAuthenticityParams): Promise<Response<DeviceAuthenticityResult>>;
|
|
197
|
+
private _verifyDeviceAuthenticityExclusive;
|
|
165
198
|
on<K extends keyof HardwareEventMap>(event: K, listener: (event: HardwareEventMap[K]) => void): void;
|
|
166
199
|
on(event: string, listener: DeviceEventListener): void;
|
|
167
200
|
off<K extends keyof HardwareEventMap>(event: K, listener: (event: HardwareEventMap[K]) => void): void;
|
|
@@ -410,8 +443,14 @@ declare class LedgerConnectorBase implements IConnector {
|
|
|
410
443
|
private readonly _eventHandlers;
|
|
411
444
|
private readonly _providedDmk;
|
|
412
445
|
private readonly _createTransport;
|
|
446
|
+
private _ledgerGenuineCheckWebSocketUrl;
|
|
413
447
|
readonly connectionType: ConnectionType;
|
|
414
448
|
private readonly _cancellers;
|
|
449
|
+
/**
|
|
450
|
+
* A server-owned attestation relay temporarily owns the raw APDU stream.
|
|
451
|
+
* The stored function re-enables DMK's session refresher when ownership ends.
|
|
452
|
+
*/
|
|
453
|
+
private readonly _attestationBridgeReleasers;
|
|
415
454
|
private readonly _sessionStateSubs;
|
|
416
455
|
/**
|
|
417
456
|
* Resolves a Ledger signer kit module by package name.
|
|
@@ -471,11 +510,15 @@ declare class LedgerConnectorBase implements IConnector {
|
|
|
471
510
|
on<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void;
|
|
472
511
|
off<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void;
|
|
473
512
|
reset(): void;
|
|
513
|
+
configure(config: ConnectorConfig): Promise<void>;
|
|
474
514
|
/**
|
|
475
515
|
* Lazily create or return the DMK instance.
|
|
476
516
|
* If a DMK was provided via constructor, it is used directly.
|
|
477
517
|
* Otherwise, one is created via the transport factory.
|
|
478
518
|
*/
|
|
519
|
+
private _capturedGenuineCertPubKeys;
|
|
520
|
+
private _tapTransportForCert;
|
|
521
|
+
private _tapConnectedDevice;
|
|
479
522
|
protected _getOrCreateDmk(): Promise<DeviceManagementKit>;
|
|
480
523
|
private _initManagers;
|
|
481
524
|
private _getDeviceManager;
|
|
@@ -497,6 +540,8 @@ declare class LedgerConnectorBase implements IConnector {
|
|
|
497
540
|
*/
|
|
498
541
|
private _resetSignersAndSessions;
|
|
499
542
|
private _resetAll;
|
|
543
|
+
private _releaseAttestationBridge;
|
|
544
|
+
private _releaseAllAttestationBridges;
|
|
500
545
|
protected _emit<K extends ConnectorEventType>(event: K, data: ConnectorEventMap[K]): void;
|
|
501
546
|
/**
|
|
502
547
|
* Return a per-call ctx with the chain's Ledger app name pre-bound to
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _onekeyfe_hwk_adapter_core from '@onekeyfe/hwk-adapter-core';
|
|
2
|
-
import { IHardwareWallet, IConnector, TransportType, UiResponseEvent, SearchDevicesOptions, DeviceInfo, Response, ChainCapability, NullableCallArg, IHardwareCallParams, EvmGetAddressParams, EvmAddress, EvmSignTxLedgerParams, EvmSignedTx, EvmSignMsgParams, EvmSignature, EvmSignTypedDataParams, BtcGetAddressParams, BtcAddress, BtcGetPublicKeyParams, BtcPublicKey, BtcSignTxParams, BtcSignedTx, BtcSignPsbtParams, BtcSignedPsbt, BtcSignMsgParams, BtcSignature, IHardwareCommonCallParams, SolGetAddressParams, SolAddress, SolSignTxParams, SolSignedTx, SolSignMsgParams, SolSignature, TronGetAddressParams, TronAddress, TronSignTxParams, TronSignedTx, TronSignMsgParams, TronSignature, HardwareEventMap, DeviceEventListener, ChainForFingerprint, DeviceDescriptor, DeviceChangeEvent, ConnectionType, ConnectorDevice, ConnectorSession, ConnectorCallResult, ConnectorEventType, ConnectorEventMap, Failure, HardwareErrorCode } from '@onekeyfe/hwk-adapter-core';
|
|
2
|
+
import { IHardwareWallet, IConnector, TransportType, UiResponseEvent, SearchDevicesOptions, DeviceInfo, Response, ChainCapability, NullableCallArg, IHardwareCallParams, EvmGetAddressParams, EvmAddress, EvmSignTxLedgerParams, EvmSignedTx, EvmSignMsgParams, EvmSignature, EvmSignTypedDataParams, BtcGetAddressParams, BtcAddress, BtcGetPublicKeyParams, BtcPublicKey, BtcSignTxParams, BtcSignedTx, BtcSignPsbtParams, BtcSignedPsbt, BtcSignMsgParams, BtcSignature, IHardwareCommonCallParams, SolGetAddressParams, SolAddress, SolSignTxParams, SolSignedTx, SolSignMsgParams, SolSignature, TronGetAddressParams, TronAddress, TronSignTxParams, TronSignedTx, TronSignMsgParams, TronSignature, DeviceAuthenticityParams, DeviceAuthenticityResult, HardwareEventMap, DeviceEventListener, ChainForFingerprint, DeviceDescriptor, DeviceChangeEvent, ConnectionType, ConnectorDevice, ConnectorSession, ConnectorCallResult, ConnectorEventType, ConnectorEventMap, ConnectorConfig, Failure, HardwareErrorCode } from '@onekeyfe/hwk-adapter-core';
|
|
3
3
|
import { DeviceActionState as DeviceActionState$1, DiscoveredDevice, ExecuteDeviceActionReturnType, DeviceManagementKit } from '@ledgerhq/device-management-kit';
|
|
4
4
|
import { Address, Signature, SignerEth as SignerEth$1, TypedData } from '@ledgerhq/device-signer-kit-ethereum';
|
|
5
5
|
import { ContextModule } from '@ledgerhq/context-module';
|
|
@@ -93,6 +93,19 @@ interface LedgerDeviceInfo extends FirmwareVersion {
|
|
|
93
93
|
|
|
94
94
|
type LedgerCallParams<T> = NullableCallArg<IHardwareCallParams<T>>;
|
|
95
95
|
type LedgerCommonParams = NullableCallArg<IHardwareCommonCallParams>;
|
|
96
|
+
type LedgerAttestationBridgeDevice = {
|
|
97
|
+
id: string;
|
|
98
|
+
modelId: 'nanoS' | 'nanoSP' | 'nanoX' | 'stax' | 'flex' | 'apexp';
|
|
99
|
+
name?: string;
|
|
100
|
+
connectionType?: 'USB' | 'BLE';
|
|
101
|
+
};
|
|
102
|
+
type LedgerAttestationApduBridge = {
|
|
103
|
+
device: LedgerAttestationBridgeDevice;
|
|
104
|
+
exchangeApdu: (apduHex: string, timeoutMs?: number) => Promise<{
|
|
105
|
+
dataHex: string;
|
|
106
|
+
statusCodeHex: string;
|
|
107
|
+
}>;
|
|
108
|
+
};
|
|
96
109
|
declare class LedgerAdapter implements IHardwareWallet {
|
|
97
110
|
readonly vendor: "ledger";
|
|
98
111
|
private readonly connector;
|
|
@@ -102,6 +115,7 @@ declare class LedgerAdapter implements IHardwareWallet {
|
|
|
102
115
|
private readonly _uiRegistry;
|
|
103
116
|
private _btcHighIndexConfirmedThisSession;
|
|
104
117
|
private readonly _jobQueue;
|
|
118
|
+
private _deviceAuthenticityQueueTail;
|
|
105
119
|
private _doConnectAbortController;
|
|
106
120
|
private readonly _defaultAutoInstallApp;
|
|
107
121
|
constructor(connector: IConnector, options?: {
|
|
@@ -162,6 +176,25 @@ declare class LedgerAdapter implements IHardwareWallet {
|
|
|
162
176
|
listAvailableApps(connectId: string): Promise<Response<AppMetadata[]>>;
|
|
163
177
|
getLedgerFirmwareVersion(connectId: string): Promise<Response<FirmwareVersion>>;
|
|
164
178
|
getLedgerDeviceInfo(connectId: string): Promise<Response<LedgerDeviceInfo>>;
|
|
179
|
+
/**
|
|
180
|
+
* Reserves the existing physical Ledger session while a server-owned DMK
|
|
181
|
+
* Genuine Check drives it. The callback can only exchange raw APDUs; the
|
|
182
|
+
* authoritative verdict remains in the server state machine.
|
|
183
|
+
*/
|
|
184
|
+
runDeviceAttestationApduBridge<T>(connectId: string, run: (bridge: LedgerAttestationApduBridge) => Promise<T>): Promise<Response<T>>;
|
|
185
|
+
/**
|
|
186
|
+
* Runs Ledger's official genuine check (DMK GenuineCheckDeviceAction) over the
|
|
187
|
+
* SAME secure-channel backend as app install
|
|
188
|
+
* (wss://scriptrunner.api.live.ledger.com/update/genuine). It returns Ledger's
|
|
189
|
+
* HSM verdict (`verified`) and a stable per-device id = sha3-256 of the device
|
|
190
|
+
* attestation public key, which DMK reads inside that session. The id survives
|
|
191
|
+
* wipe/recovery and cannot be forged from a seed.
|
|
192
|
+
*
|
|
193
|
+
* Requires network access to Ledger's backend and an on-device
|
|
194
|
+
* "Allow secure connection" confirmation the first time.
|
|
195
|
+
*/
|
|
196
|
+
verifyDeviceAuthenticity(connectId: string, params?: DeviceAuthenticityParams): Promise<Response<DeviceAuthenticityResult>>;
|
|
197
|
+
private _verifyDeviceAuthenticityExclusive;
|
|
165
198
|
on<K extends keyof HardwareEventMap>(event: K, listener: (event: HardwareEventMap[K]) => void): void;
|
|
166
199
|
on(event: string, listener: DeviceEventListener): void;
|
|
167
200
|
off<K extends keyof HardwareEventMap>(event: K, listener: (event: HardwareEventMap[K]) => void): void;
|
|
@@ -410,8 +443,14 @@ declare class LedgerConnectorBase implements IConnector {
|
|
|
410
443
|
private readonly _eventHandlers;
|
|
411
444
|
private readonly _providedDmk;
|
|
412
445
|
private readonly _createTransport;
|
|
446
|
+
private _ledgerGenuineCheckWebSocketUrl;
|
|
413
447
|
readonly connectionType: ConnectionType;
|
|
414
448
|
private readonly _cancellers;
|
|
449
|
+
/**
|
|
450
|
+
* A server-owned attestation relay temporarily owns the raw APDU stream.
|
|
451
|
+
* The stored function re-enables DMK's session refresher when ownership ends.
|
|
452
|
+
*/
|
|
453
|
+
private readonly _attestationBridgeReleasers;
|
|
415
454
|
private readonly _sessionStateSubs;
|
|
416
455
|
/**
|
|
417
456
|
* Resolves a Ledger signer kit module by package name.
|
|
@@ -471,11 +510,15 @@ declare class LedgerConnectorBase implements IConnector {
|
|
|
471
510
|
on<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void;
|
|
472
511
|
off<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void;
|
|
473
512
|
reset(): void;
|
|
513
|
+
configure(config: ConnectorConfig): Promise<void>;
|
|
474
514
|
/**
|
|
475
515
|
* Lazily create or return the DMK instance.
|
|
476
516
|
* If a DMK was provided via constructor, it is used directly.
|
|
477
517
|
* Otherwise, one is created via the transport factory.
|
|
478
518
|
*/
|
|
519
|
+
private _capturedGenuineCertPubKeys;
|
|
520
|
+
private _tapTransportForCert;
|
|
521
|
+
private _tapConnectedDevice;
|
|
479
522
|
protected _getOrCreateDmk(): Promise<DeviceManagementKit>;
|
|
480
523
|
private _initManagers;
|
|
481
524
|
private _getDeviceManager;
|
|
@@ -497,6 +540,8 @@ declare class LedgerConnectorBase implements IConnector {
|
|
|
497
540
|
*/
|
|
498
541
|
private _resetSignersAndSessions;
|
|
499
542
|
private _resetAll;
|
|
543
|
+
private _releaseAttestationBridge;
|
|
544
|
+
private _releaseAllAttestationBridges;
|
|
500
545
|
protected _emit<K extends ConnectorEventType>(event: K, data: ConnectorEventMap[K]): void;
|
|
501
546
|
/**
|
|
502
547
|
* Return a per-call ctx with the chain's Ledger app name pre-bound to
|
package/dist/index.js
CHANGED
|
@@ -772,6 +772,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
772
772
|
// The Ledger device itself still requires a per-call confirmation — that's
|
|
773
773
|
// the Ledger app's safety boundary, not ours to bypass.
|
|
774
774
|
this._btcHighIndexConfirmedThisSession = false;
|
|
775
|
+
// Runtime relay configuration mutates connector-wide DMK state. Serialize
|
|
776
|
+
// the complete configure → genuine check → clear lifecycle so concurrent
|
|
777
|
+
// callers cannot reset or overwrite each other's one-shot relay.
|
|
778
|
+
this._deviceAuthenticityQueueTail = Promise.resolve();
|
|
775
779
|
// Shared across concurrent callers — only `cancel()` aborts.
|
|
776
780
|
this._doConnectAbortController = null;
|
|
777
781
|
this._installProgressLastEmittedValue = -Infinity;
|
|
@@ -1322,6 +1326,137 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1322
1326
|
return this.errorToFailure(err);
|
|
1323
1327
|
}
|
|
1324
1328
|
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Reserves the existing physical Ledger session while a server-owned DMK
|
|
1331
|
+
* Genuine Check drives it. The callback can only exchange raw APDUs; the
|
|
1332
|
+
* authoritative verdict remains in the server state machine.
|
|
1333
|
+
*/
|
|
1334
|
+
async runDeviceAttestationApduBridge(connectId, run) {
|
|
1335
|
+
const queueKey = connectId || "__ledger_default__";
|
|
1336
|
+
try {
|
|
1337
|
+
const payload = await this._jobQueue.enqueue(
|
|
1338
|
+
queueKey,
|
|
1339
|
+
async (signal) => {
|
|
1340
|
+
const device = await this._runConnectorCall(
|
|
1341
|
+
connectId,
|
|
1342
|
+
"startDeviceAttestationApduBridge",
|
|
1343
|
+
{},
|
|
1344
|
+
signal
|
|
1345
|
+
);
|
|
1346
|
+
const sessionId = this._sessions.get(connectId) ?? (this._sessions.size === 1 ? this._sessions.values().next().value : void 0);
|
|
1347
|
+
if (!sessionId) {
|
|
1348
|
+
throw new Error(
|
|
1349
|
+
"Ledger attestation bridge started without an active device session"
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
try {
|
|
1353
|
+
return await run({
|
|
1354
|
+
device,
|
|
1355
|
+
exchangeApdu: async (apduHex, timeoutMs) => await this._callConnector(
|
|
1356
|
+
sessionId,
|
|
1357
|
+
"exchangeDeviceAttestationApdu",
|
|
1358
|
+
{ apduHex, timeoutMs },
|
|
1359
|
+
signal
|
|
1360
|
+
)
|
|
1361
|
+
});
|
|
1362
|
+
} finally {
|
|
1363
|
+
try {
|
|
1364
|
+
await this._callConnector(
|
|
1365
|
+
sessionId,
|
|
1366
|
+
"stopDeviceAttestationApduBridge",
|
|
1367
|
+
{}
|
|
1368
|
+
);
|
|
1369
|
+
} catch {
|
|
1370
|
+
this.connector.reset();
|
|
1371
|
+
this.resetState();
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
},
|
|
1375
|
+
{
|
|
1376
|
+
label: "ledgerDeviceAttestationApduBridge",
|
|
1377
|
+
rejectIfBusy: true,
|
|
1378
|
+
busyError: _LedgerAdapter._createDeviceBusyError(
|
|
1379
|
+
"ledgerDeviceAttestationApduBridge"
|
|
1380
|
+
)
|
|
1381
|
+
}
|
|
1382
|
+
);
|
|
1383
|
+
return (0, import_hwk_adapter_core3.success)(payload);
|
|
1384
|
+
} catch (error) {
|
|
1385
|
+
return this.errorToFailure(error);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Runs Ledger's official genuine check (DMK GenuineCheckDeviceAction) over the
|
|
1390
|
+
* SAME secure-channel backend as app install
|
|
1391
|
+
* (wss://scriptrunner.api.live.ledger.com/update/genuine). It returns Ledger's
|
|
1392
|
+
* HSM verdict (`verified`) and a stable per-device id = sha3-256 of the device
|
|
1393
|
+
* attestation public key, which DMK reads inside that session. The id survives
|
|
1394
|
+
* wipe/recovery and cannot be forged from a seed.
|
|
1395
|
+
*
|
|
1396
|
+
* Requires network access to Ledger's backend and an on-device
|
|
1397
|
+
* "Allow secure connection" confirmation the first time.
|
|
1398
|
+
*/
|
|
1399
|
+
async verifyDeviceAuthenticity(connectId, params = {}) {
|
|
1400
|
+
const waitForPrevious = this._deviceAuthenticityQueueTail;
|
|
1401
|
+
let releaseQueue = () => void 0;
|
|
1402
|
+
this._deviceAuthenticityQueueTail = new Promise((resolve) => {
|
|
1403
|
+
releaseQueue = resolve;
|
|
1404
|
+
});
|
|
1405
|
+
await waitForPrevious;
|
|
1406
|
+
try {
|
|
1407
|
+
return await this._verifyDeviceAuthenticityExclusive(connectId, params);
|
|
1408
|
+
} finally {
|
|
1409
|
+
releaseQueue();
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
async _verifyDeviceAuthenticityExclusive(connectId, params) {
|
|
1413
|
+
const relayUrl = params.ledgerGenuineCheckWebSocketUrl;
|
|
1414
|
+
try {
|
|
1415
|
+
if (relayUrl) {
|
|
1416
|
+
if (!this.connector.configure) {
|
|
1417
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1418
|
+
import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
|
|
1419
|
+
"This Ledger connector does not support genuine-check relay configuration"
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1422
|
+
await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
|
|
1423
|
+
this.resetState();
|
|
1424
|
+
}
|
|
1425
|
+
const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
|
|
1426
|
+
if (!result.isGenuine) {
|
|
1427
|
+
return (0, import_hwk_adapter_core3.success)({
|
|
1428
|
+
vendor: "ledger",
|
|
1429
|
+
verified: false,
|
|
1430
|
+
note: "Ledger genuine-check returned NOT genuine."
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
if (!result.deviceId) {
|
|
1434
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1435
|
+
import_hwk_adapter_core3.HardwareErrorCode.UnknownError,
|
|
1436
|
+
`Genuine check completed (isGenuine=${result.isGenuine}) but no deviceId was captured`
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
return (0, import_hwk_adapter_core3.success)({
|
|
1440
|
+
vendor: "ledger",
|
|
1441
|
+
verified: result.isGenuine,
|
|
1442
|
+
deviceId: result.deviceId,
|
|
1443
|
+
attestationPubKey: result.attestationPubKey,
|
|
1444
|
+
note: "Verified by Ledger genuine-check backend; deviceId = sha3_256(attestation pubkey)."
|
|
1445
|
+
});
|
|
1446
|
+
} catch (err) {
|
|
1447
|
+
return this.errorToFailure(err);
|
|
1448
|
+
} finally {
|
|
1449
|
+
if (relayUrl) {
|
|
1450
|
+
try {
|
|
1451
|
+
await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
|
|
1452
|
+
this.resetState();
|
|
1453
|
+
} catch {
|
|
1454
|
+
this.connector.reset();
|
|
1455
|
+
this.resetState();
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1325
1460
|
on(event, listener) {
|
|
1326
1461
|
this.emitter.on(event, listener);
|
|
1327
1462
|
}
|
|
@@ -2294,6 +2429,7 @@ var LedgerAdapter = _LedgerAdapter;
|
|
|
2294
2429
|
|
|
2295
2430
|
// src/connector/LedgerConnectorBase.ts
|
|
2296
2431
|
var import_hwk_adapter_core13 = require("@onekeyfe/hwk-adapter-core");
|
|
2432
|
+
var sha3 = __toESM(require("@noble/hashes/sha3"));
|
|
2297
2433
|
|
|
2298
2434
|
// src/device/LedgerDeviceManager.ts
|
|
2299
2435
|
var LedgerDeviceManager = class {
|
|
@@ -4053,6 +4189,25 @@ var DeviceAppsManager = class {
|
|
|
4053
4189
|
};
|
|
4054
4190
|
|
|
4055
4191
|
// src/connector/LedgerConnectorBase.ts
|
|
4192
|
+
function extractCertPubKey(data) {
|
|
4193
|
+
try {
|
|
4194
|
+
const buf = Buffer.from(data);
|
|
4195
|
+
let offset = 0;
|
|
4196
|
+
if (offset >= buf.length) return null;
|
|
4197
|
+
const headerLen = buf[offset];
|
|
4198
|
+
offset += 1 + headerLen;
|
|
4199
|
+
if (offset >= buf.length) return null;
|
|
4200
|
+
const keyLen = buf[offset];
|
|
4201
|
+
offset += 1;
|
|
4202
|
+
if (keyLen === 0 || offset + keyLen > buf.length) return null;
|
|
4203
|
+
return buf.subarray(offset, offset + keyLen).toString("hex");
|
|
4204
|
+
} catch {
|
|
4205
|
+
return null;
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4208
|
+
function sha3HexOf(pubKeyHex) {
|
|
4209
|
+
return Buffer.from(sha3.sha3_256(Uint8Array.from(Buffer.from(pubKeyHex, "hex")))).toString("hex");
|
|
4210
|
+
}
|
|
4056
4211
|
var METHOD_PREFIX_TO_APP_NAME = {
|
|
4057
4212
|
evm: "Ethereum",
|
|
4058
4213
|
btc: "Bitcoin",
|
|
@@ -4109,6 +4264,11 @@ var LedgerConnectorBase = class {
|
|
|
4109
4264
|
// unsubscribes the observable and releases DMK's IntentQueue slot.
|
|
4110
4265
|
// ---------------------------------------------------------------------------
|
|
4111
4266
|
this._cancellers = /* @__PURE__ */ new Map();
|
|
4267
|
+
/**
|
|
4268
|
+
* A server-owned attestation relay temporarily owns the raw APDU stream.
|
|
4269
|
+
* The stored function re-enables DMK's session refresher when ownership ends.
|
|
4270
|
+
*/
|
|
4271
|
+
this._attestationBridgeReleasers = /* @__PURE__ */ new Map();
|
|
4112
4272
|
// ---------------------------------------------------------------------------
|
|
4113
4273
|
// Per-session DMK state subscriptions
|
|
4114
4274
|
//
|
|
@@ -4121,6 +4281,17 @@ var LedgerConnectorBase = class {
|
|
|
4121
4281
|
// reset / observable completion to avoid leaks.
|
|
4122
4282
|
// ---------------------------------------------------------------------------
|
|
4123
4283
|
this._sessionStateSubs = /* @__PURE__ */ new Map();
|
|
4284
|
+
// ---------------------------------------------------------------------------
|
|
4285
|
+
// Private -- DMK / Manager lifecycle
|
|
4286
|
+
// ---------------------------------------------------------------------------
|
|
4287
|
+
/**
|
|
4288
|
+
* Lazily create or return the DMK instance.
|
|
4289
|
+
* If a DMK was provided via constructor, it is used directly.
|
|
4290
|
+
* Otherwise, one is created via the transport factory.
|
|
4291
|
+
*/
|
|
4292
|
+
// Attestation public keys (hex) seen in GET CERTIFICATE (E0 52) responses on
|
|
4293
|
+
// the transport. Only populated during a genuine check; cleared per call.
|
|
4294
|
+
this._capturedGenuineCertPubKeys = [];
|
|
4124
4295
|
this._createTransport = createTransport;
|
|
4125
4296
|
this.connectionType = options?.connectionType ?? "usb";
|
|
4126
4297
|
this._providedDmk = options?.dmk;
|
|
@@ -4343,6 +4514,7 @@ var LedgerConnectorBase = class {
|
|
|
4343
4514
|
}
|
|
4344
4515
|
}
|
|
4345
4516
|
async disconnect(sessionId) {
|
|
4517
|
+
this._releaseAttestationBridge(sessionId);
|
|
4346
4518
|
if (!this._deviceManager) return;
|
|
4347
4519
|
const deviceId = this._deviceManager.getDeviceId(sessionId);
|
|
4348
4520
|
this._signerManager?.invalidate(sessionId);
|
|
@@ -4404,6 +4576,7 @@ var LedgerConnectorBase = class {
|
|
|
4404
4576
|
externalConnectId
|
|
4405
4577
|
);
|
|
4406
4578
|
this._unwatchSessionState(sessionId);
|
|
4579
|
+
this._releaseAttestationBridge(sessionId);
|
|
4407
4580
|
this._signerManager?.invalidate(sessionId);
|
|
4408
4581
|
this._cancellers.get(sessionId)?.({
|
|
4409
4582
|
code: import_hwk_adapter_core13.HardwareErrorCode.DeviceDisconnected,
|
|
@@ -4584,6 +4757,92 @@ var LedgerConnectorBase = class {
|
|
|
4584
4757
|
ctx.clearCanceller(sessionId);
|
|
4585
4758
|
}
|
|
4586
4759
|
}
|
|
4760
|
+
case "getDeviceGenuineCheck": {
|
|
4761
|
+
try {
|
|
4762
|
+
this._capturedGenuineCertPubKeys = [];
|
|
4763
|
+
const dmk = await ctx.getOrCreateDmk();
|
|
4764
|
+
const kit = await ctx.importLedgerKit("@ledgerhq/device-management-kit");
|
|
4765
|
+
const action = dmk.executeDeviceAction({
|
|
4766
|
+
sessionId,
|
|
4767
|
+
deviceAction: new kit.GenuineCheckDeviceAction({ input: {} })
|
|
4768
|
+
});
|
|
4769
|
+
let deviceId;
|
|
4770
|
+
const output = await deviceActionToPromise(
|
|
4771
|
+
action,
|
|
4772
|
+
(interaction) => ctx.emit("ui-event", {
|
|
4773
|
+
type: collapseSignerInteraction(interaction),
|
|
4774
|
+
payload: { sessionId }
|
|
4775
|
+
}),
|
|
4776
|
+
// Generous timeout: covers websocket round-trips + the on-device
|
|
4777
|
+
// "Allow secure connection" tap (the idle watchdog does not pause
|
|
4778
|
+
// for it, only for unlock-device).
|
|
4779
|
+
5 * 6e4,
|
|
4780
|
+
(cancel) => ctx.registerCanceller(sessionId, cancel),
|
|
4781
|
+
(intermediateValue) => {
|
|
4782
|
+
const iv = intermediateValue;
|
|
4783
|
+
if (iv?.deviceId instanceof Uint8Array && !deviceId) {
|
|
4784
|
+
deviceId = Buffer.from(iv.deviceId).toString("hex");
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
4787
|
+
);
|
|
4788
|
+
const attestationPubKey = deviceId && this._capturedGenuineCertPubKeys.length > 0 ? this._capturedGenuineCertPubKeys.find((pk) => sha3HexOf(pk) === deviceId) : void 0;
|
|
4789
|
+
this._capturedGenuineCertPubKeys = [];
|
|
4790
|
+
return { isGenuine: output.isGenuine, deviceId, attestationPubKey };
|
|
4791
|
+
} catch (err) {
|
|
4792
|
+
ctx.invalidateSession(sessionId);
|
|
4793
|
+
throw ctx.wrapError(err);
|
|
4794
|
+
} finally {
|
|
4795
|
+
ctx.clearCanceller(sessionId);
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
case "startDeviceAttestationApduBridge": {
|
|
4799
|
+
if (this._attestationBridgeReleasers.has(sessionId)) {
|
|
4800
|
+
throw new Error("Ledger device attestation APDU bridge is already active");
|
|
4801
|
+
}
|
|
4802
|
+
const dmk = await ctx.getOrCreateDmk();
|
|
4803
|
+
const release = dmk.disableDeviceSessionRefresher({
|
|
4804
|
+
sessionId,
|
|
4805
|
+
blockerId: "onekey-ledger-attestation-relay"
|
|
4806
|
+
});
|
|
4807
|
+
try {
|
|
4808
|
+
const device = dmk.getConnectedDevice({ sessionId });
|
|
4809
|
+
this._attestationBridgeReleasers.set(sessionId, release);
|
|
4810
|
+
return {
|
|
4811
|
+
id: device.id,
|
|
4812
|
+
modelId: device.modelId,
|
|
4813
|
+
name: device.name,
|
|
4814
|
+
connectionType: device.type
|
|
4815
|
+
};
|
|
4816
|
+
} catch (error) {
|
|
4817
|
+
release();
|
|
4818
|
+
throw error;
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4821
|
+
case "exchangeDeviceAttestationApdu": {
|
|
4822
|
+
if (!this._attestationBridgeReleasers.has(sessionId)) {
|
|
4823
|
+
throw new Error("Ledger device attestation APDU bridge is not active");
|
|
4824
|
+
}
|
|
4825
|
+
const p = params;
|
|
4826
|
+
if (typeof p.apduHex !== "string" || p.apduHex.length < 8 || p.apduHex.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(p.apduHex) || p.apduHex.length / 2 > 8 * 1024) {
|
|
4827
|
+
throw new Error("Invalid Ledger device attestation APDU");
|
|
4828
|
+
}
|
|
4829
|
+
const requestedTimeout = typeof p.timeoutMs === "number" && Number.isFinite(p.timeoutMs) ? p.timeoutMs : 3e4;
|
|
4830
|
+
const timeoutMs = Math.max(1e3, Math.min(requestedTimeout, 6e4));
|
|
4831
|
+
const dmk = await ctx.getOrCreateDmk();
|
|
4832
|
+
const response = await dmk.sendApdu({
|
|
4833
|
+
sessionId,
|
|
4834
|
+
apdu: Uint8Array.from(Buffer.from(p.apduHex, "hex")),
|
|
4835
|
+
abortTimeout: timeoutMs,
|
|
4836
|
+
triggersDisconnection: false
|
|
4837
|
+
});
|
|
4838
|
+
return {
|
|
4839
|
+
dataHex: Buffer.from(response.data).toString("hex"),
|
|
4840
|
+
statusCodeHex: Buffer.from(response.statusCode).toString("hex")
|
|
4841
|
+
};
|
|
4842
|
+
}
|
|
4843
|
+
case "stopDeviceAttestationApduBridge":
|
|
4844
|
+
this._releaseAttestationBridge(sessionId);
|
|
4845
|
+
return void 0;
|
|
4587
4846
|
default:
|
|
4588
4847
|
throw new Error(`LedgerConnector: unknown method "${method}"`);
|
|
4589
4848
|
}
|
|
@@ -4616,16 +4875,80 @@ var LedgerConnectorBase = class {
|
|
|
4616
4875
|
// IConnector -- Reset
|
|
4617
4876
|
// ---------------------------------------------------------------------------
|
|
4618
4877
|
reset() {
|
|
4878
|
+
this._ledgerGenuineCheckWebSocketUrl = void 0;
|
|
4619
4879
|
this._resetAll();
|
|
4620
4880
|
}
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4881
|
+
async configure(config) {
|
|
4882
|
+
const nextUrl = config.ledgerGenuineCheckWebSocketUrl;
|
|
4883
|
+
if (nextUrl) {
|
|
4884
|
+
let parsed;
|
|
4885
|
+
try {
|
|
4886
|
+
parsed = new URL(nextUrl);
|
|
4887
|
+
} catch {
|
|
4888
|
+
throw new Error("Ledger genuine-check relay URL is invalid");
|
|
4889
|
+
}
|
|
4890
|
+
if (parsed.protocol !== "wss:") {
|
|
4891
|
+
throw new Error("Ledger genuine-check relay URL must use wss");
|
|
4892
|
+
}
|
|
4893
|
+
if (this._providedDmk) {
|
|
4894
|
+
throw new Error("Cannot change Ledger genuine-check relay on a pre-built DMK");
|
|
4895
|
+
}
|
|
4896
|
+
}
|
|
4897
|
+
if (nextUrl === this._ledgerGenuineCheckWebSocketUrl) {
|
|
4898
|
+
return;
|
|
4899
|
+
}
|
|
4900
|
+
this._resetAll();
|
|
4901
|
+
this._ledgerGenuineCheckWebSocketUrl = nextUrl;
|
|
4902
|
+
}
|
|
4903
|
+
// Wrap a TransportFactory so every connected device's `sendApdu` is tapped:
|
|
4904
|
+
// when DMK's genuine-check secure channel reads the device attestation
|
|
4905
|
+
// certificate (E0 52), we capture the raw public key from the response. The
|
|
4906
|
+
// tap is best-effort and can NEVER disturb the real APDU flow (it always
|
|
4907
|
+
// returns the original result, all capture logic is in try/catch).
|
|
4908
|
+
_tapTransportForCert(factory) {
|
|
4909
|
+
return (args) => {
|
|
4910
|
+
const transport = factory(args);
|
|
4911
|
+
const originalConnect = transport?.connect;
|
|
4912
|
+
if (typeof originalConnect === "function") {
|
|
4913
|
+
transport.connect = async (params) => {
|
|
4914
|
+
const either = await originalConnect.call(transport, params);
|
|
4915
|
+
try {
|
|
4916
|
+
if (either && typeof either.map === "function") {
|
|
4917
|
+
return either.map((device) => this._tapConnectedDevice(device));
|
|
4918
|
+
}
|
|
4919
|
+
} catch {
|
|
4920
|
+
}
|
|
4921
|
+
return either;
|
|
4922
|
+
};
|
|
4923
|
+
}
|
|
4924
|
+
return transport;
|
|
4925
|
+
};
|
|
4926
|
+
}
|
|
4927
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4928
|
+
_tapConnectedDevice(device) {
|
|
4929
|
+
const originalSendApdu = device?.sendApdu;
|
|
4930
|
+
if (typeof originalSendApdu !== "function") return device;
|
|
4931
|
+
const wrapped = async (apdu, ...rest) => {
|
|
4932
|
+
const result = await originalSendApdu.call(device, apdu, ...rest);
|
|
4933
|
+
try {
|
|
4934
|
+
if (apdu?.[0] === 224 && apdu?.[1] === 82 && result && typeof result.isRight === "function" && result.isRight()) {
|
|
4935
|
+
const resp = result.extract();
|
|
4936
|
+
if (resp && Buffer.from(resp.statusCode).toString("hex") === "9000") {
|
|
4937
|
+
const pubKey = extractCertPubKey(resp.data);
|
|
4938
|
+
if (pubKey) this._capturedGenuineCertPubKeys.push(pubKey);
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
} catch {
|
|
4942
|
+
}
|
|
4943
|
+
return result;
|
|
4944
|
+
};
|
|
4945
|
+
return new Proxy(device, {
|
|
4946
|
+
get(target, prop, receiver) {
|
|
4947
|
+
if (prop === "sendApdu") return wrapped;
|
|
4948
|
+
return Reflect.get(target, prop, receiver);
|
|
4949
|
+
}
|
|
4950
|
+
});
|
|
4951
|
+
}
|
|
4629
4952
|
async _getOrCreateDmk() {
|
|
4630
4953
|
debugLog(
|
|
4631
4954
|
"[DMK] _getOrCreateDmk called, _dmk exists:",
|
|
@@ -4641,9 +4964,13 @@ var LedgerConnectorBase = class {
|
|
|
4641
4964
|
const { DeviceManagementKitBuilder } = await this._importLedgerKit(
|
|
4642
4965
|
"@ledgerhq/device-management-kit"
|
|
4643
4966
|
);
|
|
4644
|
-
const transportFactory = await this._createTransport();
|
|
4967
|
+
const transportFactory = this._tapTransportForCert(await this._createTransport());
|
|
4645
4968
|
debugLog("[DMK] _getOrCreateDmk: transportFactory type:", typeof transportFactory);
|
|
4646
|
-
const
|
|
4969
|
+
const builder = new DeviceManagementKitBuilder().addTransport(transportFactory);
|
|
4970
|
+
if (this._ledgerGenuineCheckWebSocketUrl) {
|
|
4971
|
+
builder.addConfig({ webSocketUrl: this._ledgerGenuineCheckWebSocketUrl });
|
|
4972
|
+
}
|
|
4973
|
+
const dmk = builder.build();
|
|
4647
4974
|
this._dmk = dmk;
|
|
4648
4975
|
debugLog("[DMK] _getOrCreateDmk: DMK created");
|
|
4649
4976
|
return dmk;
|
|
@@ -4711,6 +5038,7 @@ var LedgerConnectorBase = class {
|
|
|
4711
5038
|
*/
|
|
4712
5039
|
_resetSignersAndSessions() {
|
|
4713
5040
|
debugLog("[DMK] _resetSignersAndSessions called");
|
|
5041
|
+
this._releaseAllAttestationBridges();
|
|
4714
5042
|
this._signerManager?.clearAll();
|
|
4715
5043
|
this._signerManager = null;
|
|
4716
5044
|
this._deviceAppsManager?.clearAll();
|
|
@@ -4720,6 +5048,7 @@ var LedgerConnectorBase = class {
|
|
|
4720
5048
|
}
|
|
4721
5049
|
_resetAll() {
|
|
4722
5050
|
debugLog("[DMK] _resetAll called");
|
|
5051
|
+
this._releaseAllAttestationBridges();
|
|
4723
5052
|
for (const cancel of this._cancellers.values()) {
|
|
4724
5053
|
try {
|
|
4725
5054
|
cancel();
|
|
@@ -4742,6 +5071,20 @@ var LedgerConnectorBase = class {
|
|
|
4742
5071
|
this._deviceAppsManager = null;
|
|
4743
5072
|
this._dmk = null;
|
|
4744
5073
|
}
|
|
5074
|
+
_releaseAttestationBridge(sessionId) {
|
|
5075
|
+
const release = this._attestationBridgeReleasers.get(sessionId);
|
|
5076
|
+
if (!release) return;
|
|
5077
|
+
this._attestationBridgeReleasers.delete(sessionId);
|
|
5078
|
+
try {
|
|
5079
|
+
release();
|
|
5080
|
+
} catch {
|
|
5081
|
+
}
|
|
5082
|
+
}
|
|
5083
|
+
_releaseAllAttestationBridges() {
|
|
5084
|
+
for (const sessionId of [...this._attestationBridgeReleasers.keys()]) {
|
|
5085
|
+
this._releaseAttestationBridge(sessionId);
|
|
5086
|
+
}
|
|
5087
|
+
}
|
|
4745
5088
|
// ---------------------------------------------------------------------------
|
|
4746
5089
|
// Private -- Events
|
|
4747
5090
|
// ---------------------------------------------------------------------------
|