diff --git a/apps/sample/src/providers/SignerEthProvider/index.tsx b/apps/sample/src/providers/SignerEthProvider/index.tsx index 9aa0c52d6..5299cec57 100644 --- a/apps/sample/src/providers/SignerEthProvider/index.tsx +++ b/apps/sample/src/providers/SignerEthProvider/index.tsx @@ -32,7 +32,6 @@ const initialState: SignerEthContextType = { web3checksUrl: "https://api.blockaid.io/v0/ledger/transaction/scan", mode: "prod", branch: "main", - web3checksUrl: "https://web3-checks.api.ledger.com/v1", }, setCalConfig: () => {}, }; diff --git a/packages/device-management-kit/src/internal/device-session/model/DeviceSession.ts b/packages/device-management-kit/src/internal/device-session/model/DeviceSession.ts index f4debfd0c..ab12acd51 100644 --- a/packages/device-management-kit/src/internal/device-session/model/DeviceSession.ts +++ b/packages/device-management-kit/src/internal/device-session/model/DeviceSession.ts @@ -119,13 +119,17 @@ export class DeviceSession { options.triggersDisconnection, ); - return errorOrResponse.ifRight((response: ApduResponse) => { - if (CommandUtils.isLockedDeviceResponse(response)) { - this.updateDeviceStatus(DeviceStatus.LOCKED); - } else { + return errorOrResponse + .ifRight((response: ApduResponse) => { + if (CommandUtils.isLockedDeviceResponse(response)) { + this.updateDeviceStatus(DeviceStatus.LOCKED); + } else { + this.updateDeviceStatus(DeviceStatus.CONNECTED); + } + }) + .ifLeft(() => { this.updateDeviceStatus(DeviceStatus.CONNECTED); - } - }); + }); } async sendCommand( diff --git a/packages/device-management-kit/src/internal/discovery/use-case/DisconnectUseCase.ts b/packages/device-management-kit/src/internal/discovery/use-case/DisconnectUseCase.ts index 441e70af1..8da03e149 100644 --- a/packages/device-management-kit/src/internal/discovery/use-case/DisconnectUseCase.ts +++ b/packages/device-management-kit/src/internal/discovery/use-case/DisconnectUseCase.ts @@ -57,7 +57,11 @@ export class DisconnectUseCase { this._transportService .getTransport(transportIdentifier) .toEither( - new TransportNotSupportedError(new Error("Unknown transport")), + new TransportNotSupportedError( + new Error( + `Unknown transport with identifier: ${transportIdentifier}`, + ), + ), ), ); diff --git a/packages/signer/context-module/src/di.ts b/packages/signer/context-module/src/di.ts index 791d2ef45..efa3e169f 100644 --- a/packages/signer/context-module/src/di.ts +++ b/packages/signer/context-module/src/di.ts @@ -4,6 +4,7 @@ import { configModuleFactory } from "@/config/di/configModuleFactory"; import { type ContextModuleConfig } from "@/config/model/ContextModuleConfig"; import { externalPluginModuleFactory } from "@/external-plugin/di/externalPluginModuleFactory"; import { nftModuleFactory } from "@/nft/di/nftModuleFactory"; +import { nanoPkiModuleFactory } from "@/pki/di/pkiModuleFactory"; import { tokenModuleFactory } from "@/token/di/tokenModuleFactory"; import { transactionModuleFactory } from "@/transaction/di/transactionModuleFactory"; import { trustedNameModuleFactory } from "@/trusted-name/di/trustedNameModuleFactory"; @@ -26,6 +27,7 @@ export const makeContainer = ({ config }: MakeContainerArgs) => { trustedNameModuleFactory(), typedDataModuleFactory(), web3CheckModuleFactory(), + nanoPkiModuleFactory(), ); return container; diff --git a/packages/signer/context-module/src/pki/data/HttpPkiCertificateDataSource.ts b/packages/signer/context-module/src/pki/data/HttpPkiCertificateDataSource.ts index 85bc49039..12551b876 100644 --- a/packages/signer/context-module/src/pki/data/HttpPkiCertificateDataSource.ts +++ b/packages/signer/context-module/src/pki/data/HttpPkiCertificateDataSource.ts @@ -8,6 +8,8 @@ import { PkiCertificate, PkiCertificateInfo, } from "@/pki/domain/pkiCertificateTypes"; +import { HexStringUtils } from "@/shared/utils/HexStringUtils"; +import { KeyUsageMapper } from "@/shared/utils/KeyUsageMapper"; import PACKAGE from "@root/package.json"; import { type PkiCertificateDataSource } from "./PkiCertificateDataSource"; @@ -15,7 +17,10 @@ import { type PkiCertificateRequestDto, type PkiCertificateResponseDto, } from "./pkiDataSourceTypes"; +import { hexaStringToBuffer } from "@ledgerhq/device-management-kit"; +const KEY_USAGE = "trusted_name"; +const SIGNATURE_TAG = "15"; @injectable() export class HttpPkiCertificateDataSource implements PkiCertificateDataSource { constructor( @@ -26,12 +31,11 @@ export class HttpPkiCertificateDataSource implements PkiCertificateDataSource { pkiCertificateInfo: PkiCertificateInfo, ): Promise> { const requestDto: PkiCertificateRequestDto = { - output: - "id,target_device,not_valid_after,public_key_usage,certificate_version,descriptor", - target_device: "nanox", //TODO add in input - not_valid_after: "1.3.0", //TODO add current OS_version + output: "descriptor", + target_device: pkiCertificateInfo.targetDevice, latest: true, - descriptor: pkiCertificateInfo.descriptor, + key_id: pkiCertificateInfo.keyId, + key_usage: pkiCertificateInfo.keyUsage, }; try { @@ -46,7 +50,25 @@ export class HttpPkiCertificateDataSource implements PkiCertificateDataSource { }); if (pkiCertificateResponse.status == 200) { - return Right(pkiCertificateResponse.data); + const payload = hexaStringToBuffer( + HexStringUtils.appendSignatureToPayload( + pkiCertificateResponse.data.descriptor.data, + pkiCertificateResponse.data.descriptor.signatures[ + this.config.cal.mode + ], + SIGNATURE_TAG, + ), + ); + if (!payload) { + return Left( + Error("Error encountered: Cannot convert Hex String to UInt8Array"), + ); + } + const pkiCertificate: PkiCertificate = { + payload: payload, + keyUsageNumber: KeyUsageMapper.mapKeyUsageForFirmware(KEY_USAGE), + }; + return Right(pkiCertificate); } else { return Left( Error( diff --git a/packages/signer/context-module/src/pki/data/pkiDataSourceTypes.ts b/packages/signer/context-module/src/pki/data/pkiDataSourceTypes.ts index 9b5af9977..24134addd 100644 --- a/packages/signer/context-module/src/pki/data/pkiDataSourceTypes.ts +++ b/packages/signer/context-module/src/pki/data/pkiDataSourceTypes.ts @@ -1,9 +1,9 @@ export type PkiCertificateRequestDto = { + key_id: string | undefined; + key_usage: string; output: string; target_device: string; - not_valid_after: string; latest: boolean; - descriptor: string; }; export type PkiCertificateResponseDto = { @@ -12,5 +12,12 @@ export type PkiCertificateResponseDto = { not_valid_after: string; public_key_usage: string; certificate_version: string; - descriptor: string; + descriptor: { + data: string; + descriptorType: string; + signatures: { + prod: string; + test: string; + }; + }; }; diff --git a/packages/signer/context-module/src/pki/di/pkiModuleFactory.ts b/packages/signer/context-module/src/pki/di/pkiModuleFactory.ts index e69de29bb..ebacc2cf4 100644 --- a/packages/signer/context-module/src/pki/di/pkiModuleFactory.ts +++ b/packages/signer/context-module/src/pki/di/pkiModuleFactory.ts @@ -0,0 +1,12 @@ +import { ContainerModule } from "inversify"; + +import { HttpPkiCertificateDataSource } from "@/pki/data/HttpPkiCertificateDataSource"; +import { DefaultPkiCertificateLoader } from "@/pki/domain/DefaultPkiCertificateLoader"; + +import { pkiTypes } from "./pkiDiTypes"; + +export const nanoPkiModuleFactory = () => + new ContainerModule((bind, _unbind, _isBound, _rebind) => { + bind(pkiTypes.PkiCertificateDataSource).to(HttpPkiCertificateDataSource); + bind(pkiTypes.PkiCertificateLoader).to(DefaultPkiCertificateLoader); + }); diff --git a/packages/signer/context-module/src/pki/domain/DefaultPkiCertificateLoader.ts b/packages/signer/context-module/src/pki/domain/DefaultPkiCertificateLoader.ts index d071cf3ec..6544ed605 100644 --- a/packages/signer/context-module/src/pki/domain/DefaultPkiCertificateLoader.ts +++ b/packages/signer/context-module/src/pki/domain/DefaultPkiCertificateLoader.ts @@ -7,7 +7,7 @@ import { PkiCertificateLoader } from "./PkiCertificateLoader"; import { PkiCertificate, PkiCertificateInfo } from "./pkiCertificateTypes"; @injectable() -export class DefaultPkiLoader implements PkiCertificateLoader { +export class DefaultPkiCertificateLoader implements PkiCertificateLoader { private _dataSource: PkiCertificateDataSource; constructor( @@ -19,9 +19,10 @@ export class DefaultPkiLoader implements PkiCertificateLoader { async loadCertificate( certificateInfos: PkiCertificateInfo, - ): Promise { - await this._dataSource.fetchCertificate(certificateInfos); + ): Promise { + const certificate = + await this._dataSource.fetchCertificate(certificateInfos); - return null; + return certificate.orDefault(undefined); } } diff --git a/packages/signer/context-module/src/pki/domain/PkiCertificateLoader.ts b/packages/signer/context-module/src/pki/domain/PkiCertificateLoader.ts index 2029c5b29..86b2968f1 100644 --- a/packages/signer/context-module/src/pki/domain/PkiCertificateLoader.ts +++ b/packages/signer/context-module/src/pki/domain/PkiCertificateLoader.ts @@ -6,5 +6,5 @@ import { export interface PkiCertificateLoader { loadCertificate( certificateInfos: PkiCertificateInfo, - ): Promise; + ): Promise; } diff --git a/packages/signer/context-module/src/pki/domain/model/KeyUsage.ts b/packages/signer/context-module/src/pki/domain/model/KeyUsage.ts new file mode 100644 index 000000000..60f4237c8 --- /dev/null +++ b/packages/signer/context-module/src/pki/domain/model/KeyUsage.ts @@ -0,0 +1,5 @@ +export enum KeyUsage { + TrustedName = "trusted_name", + Web3Checks = "web3checks", + //TODO add following Docs https://ledgerhq.atlassian.net/wiki/spaces/BE/pages/4863033373/CAL+-+Raw+signing+NanoPKI+migration +} diff --git a/packages/signer/context-module/src/pki/domain/pkiCertificateTypes.ts b/packages/signer/context-module/src/pki/domain/pkiCertificateTypes.ts index f7e41456a..a1cc09a1d 100644 --- a/packages/signer/context-module/src/pki/domain/pkiCertificateTypes.ts +++ b/packages/signer/context-module/src/pki/domain/pkiCertificateTypes.ts @@ -1,10 +1,17 @@ +import type { KeyUsage } from "./model/KeyUsage"; + export type PkiCertificate = { - id: string; - descriptor: string; + keyUsageNumber: number; + payload: Uint8Array; }; export type PkiCertificateInfo = { - descriptor: string; targetDevice: string; - osVersion: string; + keyUsage: KeyUsage; + keyId?: string | undefined; }; + +/* +Do we re add it to PkiCertificateInfo? +osVersion: string; +*/ diff --git a/packages/signer/context-module/src/shared/model/ClearSignContext.ts b/packages/signer/context-module/src/shared/model/ClearSignContext.ts index 9feff369a..727288f18 100644 --- a/packages/signer/context-module/src/shared/model/ClearSignContext.ts +++ b/packages/signer/context-module/src/shared/model/ClearSignContext.ts @@ -1,3 +1,4 @@ +import { PkiCertificate } from "@/pki/domain/pkiCertificateTypes"; import { type GenericPath } from "./GenericPath"; export enum ClearSignContextType { @@ -71,6 +72,7 @@ export type ClearSignContextSuccess< id: number; payload: string; value: number; + certificate?: PkiCertificate | undefined; } : { type: Exclude< @@ -79,7 +81,7 @@ export type ClearSignContextSuccess< >; payload: string; reference?: ClearSignContextReference; - certificate?: string; + certificate?: PkiCertificate | undefined; }; export type ClearSignContextError = { diff --git a/packages/signer/context-module/src/shared/model/TransactionContext.ts b/packages/signer/context-module/src/shared/model/TransactionContext.ts index 1271380a1..564360528 100644 --- a/packages/signer/context-module/src/shared/model/TransactionContext.ts +++ b/packages/signer/context-module/src/shared/model/TransactionContext.ts @@ -19,4 +19,5 @@ export type TransactionFieldContext = export type TransactionContext = TransactionSubset & { challenge: string; domain?: string; + deviceModel?: string; }; diff --git a/packages/signer/context-module/src/shared/utils/HexStringUtils.ts b/packages/signer/context-module/src/shared/utils/HexStringUtils.ts index e89432f82..868eb3d68 100644 --- a/packages/signer/context-module/src/shared/utils/HexStringUtils.ts +++ b/packages/signer/context-module/src/shared/utils/HexStringUtils.ts @@ -7,4 +7,18 @@ export class HexStringUtils { } return hexString; } + + static appendSignatureToPayload( + payload: string, + signature: string, + tag: string, + ): string { + // Ensure correct padding + if (signature.length % 2 !== 0) { + signature = "0" + signature; + } + // TLV encoding as according to trusted name documentation + const signatureLength = (signature.length / 2).toString(16); + return `${payload}${tag}${signatureLength}${signature}`; + } } diff --git a/packages/signer/context-module/src/shared/utils/KeyUsageMapper.ts b/packages/signer/context-module/src/shared/utils/KeyUsageMapper.ts new file mode 100644 index 000000000..94cb1fedf --- /dev/null +++ b/packages/signer/context-module/src/shared/utils/KeyUsageMapper.ts @@ -0,0 +1,42 @@ +export class KeyUsageMapper { + static mapKeyUsageForFirmware(keyUsageString: string): number { + let keyUsageNumber = -1; + + switch (keyUsageString) { + case "genuine_check": + keyUsageNumber = 1; + break; + case "exchange_payload": + keyUsageNumber = 2; + break; + case "nft_meta": + keyUsageNumber = 3; + break; + case "trusted_name": + keyUsageNumber = 4; + break; + case "backup_provider": + keyUsageNumber = 5; + break; + case "protect_orchestrator": + keyUsageNumber = 6; + break; + case "plugin_meta": + keyUsageNumber = 7; + break; + case "coin_meta": + keyUsageNumber = 8; + break; + case "seed_id_auth": + keyUsageNumber = 9; + break; + case "web3checks": + keyUsageNumber = 10; + break; + default: + break; + } + + return keyUsageNumber; + } +} diff --git a/packages/signer/context-module/src/transaction/data/HttpTransactionDataSource.ts b/packages/signer/context-module/src/transaction/data/HttpTransactionDataSource.ts index e9f4bb02f..e5f850924 100644 --- a/packages/signer/context-module/src/transaction/data/HttpTransactionDataSource.ts +++ b/packages/signer/context-module/src/transaction/data/HttpTransactionDataSource.ts @@ -13,6 +13,7 @@ import { ClearSignContextType, } from "@/shared/model/ClearSignContext"; import { GenericPath } from "@/shared/model/GenericPath"; +import { HexStringUtils } from "@/shared/utils/HexStringUtils"; import PACKAGE from "@root/package.json"; import { @@ -37,6 +38,7 @@ import { TransactionDataSource, } from "./TransactionDataSource"; +const INFO_SIGNATURE_TAG = "81ff"; @injectable() export class HttpTransactionDataSource implements TransactionDataSource { constructor( @@ -110,7 +112,11 @@ export class HttpTransactionDataSource implements TransactionDataSource { ]; const info: ClearSignContextSuccess = { type: ClearSignContextType.TRANSACTION_INFO, - payload: this.formatTransactionInfo(infoData, infoSignature), + payload: HexStringUtils.appendSignatureToPayload( + infoData, + infoSignature, + INFO_SIGNATURE_TAG, + ), }; const enums: ClearSignContextSuccess[] = []; for (const [id, values] of Object.entries(calldataDescriptor.enums)) { @@ -122,9 +128,10 @@ export class HttpTransactionDataSource implements TransactionDataSource { type: ClearSignContextType.ENUM, id: Number(id), value: Number(value), - payload: this.formatTransactionInfo( + payload: HexStringUtils.appendSignatureToPayload( data, - signatures[this.config.cal.mode]!, // the enum is validated by isCalldataDescriptorV1 + signatures[this.config.cal.mode]!, + INFO_SIGNATURE_TAG, ), }); } @@ -147,20 +154,6 @@ export class HttpTransactionDataSource implements TransactionDataSource { ); } - private formatTransactionInfo( - infoData: string, - infoSignature: string, - ): string { - // Ensure correct padding - if (infoSignature.length % 2 !== 0) { - infoSignature = "0" + infoSignature; - } - // TLV encoding as according to generic parser documentation - const infoSignatureTag = "81ff"; - const infoSignatureLength = (infoSignature.length / 2).toString(16); - return `${infoData}${infoSignatureTag}${infoSignatureLength}${infoSignature}`; - } - private getReference( param: CalldataDescriptorParam, ): ClearSignContextReference | undefined { diff --git a/packages/signer/context-module/src/trusted-name/data/HttpTrustedNameDataSource.ts b/packages/signer/context-module/src/trusted-name/data/HttpTrustedNameDataSource.ts index ba9816428..0030a65d2 100644 --- a/packages/signer/context-module/src/trusted-name/data/HttpTrustedNameDataSource.ts +++ b/packages/signer/context-module/src/trusted-name/data/HttpTrustedNameDataSource.ts @@ -4,6 +4,7 @@ import { Either, Left, Right } from "purify-ts"; import { configTypes } from "@/config/di/configTypes"; import type { ContextModuleConfig } from "@/config/model/ContextModuleConfig"; +import { HexStringUtils } from "@/shared/utils/HexStringUtils"; import { GetDomainNameInfosParams, GetTrustedNameInfosParams, @@ -13,6 +14,7 @@ import PACKAGE from "@root/package.json"; import { TrustedNameDto } from "./TrustedNameDto"; +const SIGNATURE_TAG = "15"; @injectable() export class HttpTrustedNameDataSource implements TrustedNameDataSource { constructor( @@ -92,7 +94,13 @@ export class HttpTrustedNameDataSource implements TrustedNameDataSource { const signature = trustedName.signedDescriptor.signatures[this.config.cal.mode]!; - return Right(this.formatTrustedName(payload, signature)); + return Right( + HexStringUtils.appendSignatureToPayload( + payload, + signature, + SIGNATURE_TAG, + ), + ); } catch (_error) { return Left( new Error( @@ -101,15 +109,4 @@ export class HttpTrustedNameDataSource implements TrustedNameDataSource { ); } } - - private formatTrustedName(payload: string, signature: string): string { - // Ensure correct padding - if (signature.length % 2 !== 0) { - signature = "0" + signature; - } - // TLV encoding as according to trusted name documentation - const signatureTag = "15"; - const signatureLength = (signature.length / 2).toString(16); - return `${payload}${signatureTag}${signatureLength}${signature}`; - } } diff --git a/packages/signer/context-module/src/trusted-name/domain/TrustedNameContextLoader.test.ts b/packages/signer/context-module/src/trusted-name/domain/TrustedNameContextLoader.test.ts index 661723ba6..43ff9f3c2 100644 --- a/packages/signer/context-module/src/trusted-name/domain/TrustedNameContextLoader.test.ts +++ b/packages/signer/context-module/src/trusted-name/domain/TrustedNameContextLoader.test.ts @@ -1,5 +1,6 @@ import { Left, Right } from "purify-ts"; +import { type PkiCertificateLoader } from "@/pki/domain/PkiCertificateLoader"; import { ClearSignContextType } from "@/shared/model/ClearSignContext"; import { type TransactionContext, @@ -14,17 +15,28 @@ describe("TrustedNameContextLoader", () => { getTrustedNamePayload: jest.fn(), }; + const mockCertificateLoader: PkiCertificateLoader = { + loadCertificate: jest.fn(), + }; + beforeEach(() => { jest.restoreAllMocks(); jest .spyOn(mockTrustedNameDataSource, "getDomainNamePayload") .mockResolvedValue(Right("payload")); + + jest + .spyOn(mockCertificateLoader, "loadCertificate") + .mockResolvedValue(undefined); }); describe("load function", () => { it("should return an empty array when no domain or registry", () => { const transaction = {} as TransactionContext; - const loader = new TrustedNameContextLoader(mockTrustedNameDataSource); + const loader = new TrustedNameContextLoader( + mockTrustedNameDataSource, + mockCertificateLoader, + ); const promise = () => loader.load(transaction); expect(promise()).resolves.toEqual([]); @@ -35,7 +47,10 @@ describe("TrustedNameContextLoader", () => { domain: "maxlength-maxlength-maxlength-maxlength-maxlength-maxlength", } as TransactionContext; - const loader = new TrustedNameContextLoader(mockTrustedNameDataSource); + const loader = new TrustedNameContextLoader( + mockTrustedNameDataSource, + mockCertificateLoader, + ); const result = await loader.load(transaction); expect(result).toEqual([ @@ -51,7 +66,10 @@ describe("TrustedNameContextLoader", () => { domain: "hellođź‘‹", } as TransactionContext; - const loader = new TrustedNameContextLoader(mockTrustedNameDataSource); + const loader = new TrustedNameContextLoader( + mockTrustedNameDataSource, + mockCertificateLoader, + ); const result = await loader.load(transaction); expect(result).toEqual([ @@ -68,7 +86,10 @@ describe("TrustedNameContextLoader", () => { challenge: "challenge", } as TransactionContext; - const loader = new TrustedNameContextLoader(mockTrustedNameDataSource); + const loader = new TrustedNameContextLoader( + mockTrustedNameDataSource, + mockCertificateLoader, + ); const result = await loader.load(transaction); expect(result).toEqual([ @@ -90,7 +111,10 @@ describe("TrustedNameContextLoader", () => { jest .spyOn(mockTrustedNameDataSource, "getDomainNamePayload") .mockResolvedValue(Left(new Error("error"))); - const loader = new TrustedNameContextLoader(mockTrustedNameDataSource); + const loader = new TrustedNameContextLoader( + mockTrustedNameDataSource, + mockCertificateLoader, + ); const result = await loader.load(transaction); // THEN @@ -108,7 +132,10 @@ describe("TrustedNameContextLoader", () => { address: "0x1234", }; - const loader = new TrustedNameContextLoader(mockTrustedNameDataSource); + const loader = new TrustedNameContextLoader( + mockTrustedNameDataSource, + mockCertificateLoader, + ); const result = await loader.loadField(field); expect(result).toEqual(null); @@ -129,7 +156,10 @@ describe("TrustedNameContextLoader", () => { jest .spyOn(mockTrustedNameDataSource, "getTrustedNamePayload") .mockResolvedValue(Right("payload")); - const loader = new TrustedNameContextLoader(mockTrustedNameDataSource); + const loader = new TrustedNameContextLoader( + mockTrustedNameDataSource, + mockCertificateLoader, + ); const result = await loader.loadField(field); // THEN @@ -154,7 +184,10 @@ describe("TrustedNameContextLoader", () => { jest .spyOn(mockTrustedNameDataSource, "getTrustedNamePayload") .mockResolvedValue(Left(new Error("error"))); - const loader = new TrustedNameContextLoader(mockTrustedNameDataSource); + const loader = new TrustedNameContextLoader( + mockTrustedNameDataSource, + mockCertificateLoader, + ); const result = await loader.loadField(field); // THEN diff --git a/packages/signer/context-module/src/trusted-name/domain/TrustedNameContextLoader.ts b/packages/signer/context-module/src/trusted-name/domain/TrustedNameContextLoader.ts index 5e8a483ca..e8f967981 100644 --- a/packages/signer/context-module/src/trusted-name/domain/TrustedNameContextLoader.ts +++ b/packages/signer/context-module/src/trusted-name/domain/TrustedNameContextLoader.ts @@ -11,16 +11,23 @@ import { } from "@/shared/model/TransactionContext"; import type { TrustedNameDataSource } from "@/trusted-name/data/TrustedNameDataSource"; import { trustedNameTypes } from "@/trusted-name/di/trustedNameTypes"; +import { type PkiCertificateLoader } from "@/pki/domain/PkiCertificateLoader"; +import { pkiTypes } from "@/pki/di/pkiDiTypes"; +import { KeyUsage } from "@/pki/domain/model/KeyUsage"; @injectable() export class TrustedNameContextLoader implements ContextLoader { private _dataSource: TrustedNameDataSource; + private _certificateLoader: PkiCertificateLoader; constructor( @inject(trustedNameTypes.TrustedNameDataSource) dataSource: TrustedNameDataSource, + @inject(pkiTypes.PkiCertificateLoader) + certificateLoader: PkiCertificateLoader, ) { this._dataSource = dataSource; + this._certificateLoader = certificateLoader; } async load( @@ -46,7 +53,11 @@ export class TrustedNameContextLoader implements ContextLoader { challenge: challenge, }); - //TODO Add certificate fetch here + //Try to fetch Nano PKI certificate + const certificate = await this._certificateLoader.loadCertificate({ + targetDevice: "flex", + keyUsage: KeyUsage.TrustedName, + }); return [ payload.caseOf({ @@ -57,6 +68,7 @@ export class TrustedNameContextLoader implements ContextLoader { Right: (value): ClearSignContext => ({ type: ClearSignContextType.TRUSTED_NAME, payload: value, + certificate: certificate ? certificate : undefined, }), }), ]; diff --git a/packages/signer/signer-eth/src/internal/app-binder/task/ProvideTransactionContextTask.ts b/packages/signer/signer-eth/src/internal/app-binder/task/ProvideTransactionContextTask.ts index 9e270068a..d3b8ea940 100644 --- a/packages/signer/signer-eth/src/internal/app-binder/task/ProvideTransactionContextTask.ts +++ b/packages/signer/signer-eth/src/internal/app-binder/task/ProvideTransactionContextTask.ts @@ -10,6 +10,7 @@ import { InvalidStatusWordError, isSuccessCommandResult, } from "@ledgerhq/device-management-kit"; +import { LoadCertificateCommand } from "@ledgerhq/device-management-kit/src/api/command/os/ProvidePkiCertificateCommand.js"; import { Just, type Maybe, Nothing } from "purify-ts"; import { @@ -92,12 +93,23 @@ export class ProvideTransactionContextTask { async provideContext({ type, payload, + certificate, }: ClearSignContextSuccess): Promise< CommandResult< void | ProvideTokenInformationCommandResponse, ProvideTransactionContextTaskErrorCodes > > { + //if a certificate is provided, we load it before sending the command + if (certificate) { + await this.api.sendCommand( + new LoadCertificateCommand({ + keyUsage: certificate.keyUsageNumber, + certificate: certificate.payload, + }), + ); + } + switch (type) { case ClearSignContextType.PLUGIN: { return await this.api.sendCommand(new SetPluginCommand({ payload })); diff --git a/packages/transport/speculos/src/api/SpeculosTransport.ts b/packages/transport/speculos/src/api/SpeculosTransport.ts index 4783e16c3..a2abc6e32 100644 --- a/packages/transport/speculos/src/api/SpeculosTransport.ts +++ b/packages/transport/speculos/src/api/SpeculosTransport.ts @@ -1,4 +1,5 @@ import { + ApduParser, type ApduResponse, bufferToHexaString, type ConnectError, @@ -17,7 +18,7 @@ import { type TransportIdentifier, } from "@ledgerhq/device-management-kit"; import { type Either, Left, Right } from "purify-ts"; -import { delay, from, type Observable } from "rxjs"; +import { from, type Observable } from "rxjs"; import { HttpSpeculosDatasource } from "@internal/datasource/HttpSpeculosDatasource"; import { type SpeculosDatasource } from "@internal/datasource/SpeculosDatasource"; @@ -83,10 +84,22 @@ export class SpeculosTransport implements Transport { onDisconnect: DisconnectHandler; }): Promise> { this.logger.debug("connect"); - await delay(500); + + const hexResponse = await this._speculosDataSource.postAdpu("B0010000"); + this.logger.debug(`Hex Response: ${hexResponse}`); + const apduResponse = this.createApduResponse(hexResponse); + const parser = new ApduParser(apduResponse); + + //Copy paste from GetAppAndVersionCommand + parser.extract8BitUInt(); //Need otherwise parser is not in the right position + const appName = parser.encodeToString(parser.extractFieldLVEncoded()); + const appVersion = parser.encodeToString(parser.extractFieldLVEncoded()); + + this.logger.debug(`App Name: ${appName} and version ${appVersion}`); + const sessionId: string = params.deviceId; try { - const connectedDevice = { + const connectedDevice: TransportConnectedDevice = { sendApdu: (apdu) => { return this.sendApdu( sessionId, @@ -95,8 +108,17 @@ export class SpeculosTransport implements Transport { apdu, ); }, - deviceModel: this.speculosDevice.deviceModel, - } as TransportConnectedDevice; + deviceModel: { + ...this.speculosDevice.deviceModel, + productName: `Speculos - ${appName} - ${appVersion}`, + getBlockSize() { + return 32; + }, + }, + transport: this.identifier, + id: "SpeculosID", //TODO make it dynamic at creation + type: "USB", + }; return Right(connectedDevice); } catch (error) { return Left(new OpeningConnectionError(error as Error)); @@ -117,23 +139,32 @@ export class SpeculosTransport implements Transport { apdu: Uint8Array, ): Promise> { try { - this.logger.debug("send"); const hexApdu = bufferToHexaString(apdu).substring(2); + this.logger.debug(`send APDU: ${hexApdu}`); const hexResponse: string = await this._speculosDataSource.postAdpu(hexApdu); - return Right({ - statusCode: this.fromHexString( - hexResponse.substring(hexResponse.length - 4, hexResponse.length), - ), - data: this.fromHexString( - hexResponse.substring(0, hexResponse.length - 4), - ), - } as ApduResponse); + const apduResponse = this.createApduResponse(hexResponse); + return Right(apduResponse); } catch (error) { return Left(new GeneralDmkError(error)); } } + private createApduResponse(hexApdu: string): ApduResponse { + this.logger.debug( + `Status code hex: ${hexApdu.substring(hexApdu.length - 4, hexApdu.length)}`, + ); + this.logger.debug(`data hex: ${hexApdu.substring(0, hexApdu.length - 4)}`); + + const apduResponse = { + statusCode: this.fromHexString( + hexApdu.substring(hexApdu.length - 4, hexApdu.length), + ), + data: this.fromHexString(hexApdu.substring(0, hexApdu.length - 4)), + }; + return apduResponse; + } + //TODO: Move this to a helper private fromHexString(hexString: string): Uint8Array { if (!hexString) {