UNPKG

@hazae41/ledger

Version:

Private and supply-chain hardened Ledger controller for TypeScript

113 lines (109 loc) 4.03 kB
'use strict'; var apdu = require('@hazae41/apdu'); var binary = require('@hazae41/binary'); var bytes = require('@hazae41/bytes'); var index = require('../hid/index.cjs'); const VENDOR_ID = 0x2c97; const PACKET_SIZE = 64; class DeviceInterfaceNotFoundError extends Error { #class = DeviceInterfaceNotFoundError; name = this.#class.name; constructor(options) { super(`Could not find device interface`, options); } static from(cause) { return new DeviceInterfaceNotFoundError({ cause }); } } class DeviceTransferOutError extends Error { #class = DeviceTransferOutError; name = this.#class.name; constructor(options) { super(`Could not transfer data to device`, options); } static from(cause) { return new DeviceTransferOutError({ cause }); } } class DeviceTransferInError extends Error { #class = DeviceTransferInError; name = this.#class.name; constructor(options) { super(`Could not transfer data from device`, options); } static from(cause) { return new DeviceTransferInError({ cause }); } } async function getDevicesOrThrow() { const devices = await navigator.usb.getDevices(); return devices.filter(x => x.vendorId === VENDOR_ID); } async function getOrRequestDeviceOrThrow() { const devices = await getDevicesOrThrow(); const device = devices[0]; if (device != null) return device; return await navigator.usb.requestDevice({ filters: [{ vendorId: VENDOR_ID }] }); } async function connectOrThrow(device) { await device.open(); if (device.configuration == null) await device.selectConfiguration(1); await device.reset(); const iface = device.configurations[0].interfaces.find(({ alternates }) => alternates.some(x => x.interfaceClass === 255)); if (iface == null) throw new DeviceInterfaceNotFoundError(); await device.claimInterface(iface.interfaceNumber); return new Connector(device, iface); } class Connector { device; iface; #channel = Math.floor(Math.random() * 0xffff); constructor(device, iface) { this.device = device; this.iface = iface; } async #transferOutOrThrow(frame) { await this.device.transferOut(3, binary.Writable.writeToBytesOrThrow(frame)); } async #transferInOrThrow(length) { const result = await this.device.transferIn(3, length); if (result.data == null) throw new DeviceTransferInError(); const bytes$1 = bytes.Bytes.fromView(result.data); const frame = binary.Readable.readFromBytesOrThrow(index.HidFrame, bytes$1); return frame; } async #sendOrThrow(fragment) { const container = index.HidContainer.newOrThrow(fragment); const bytes = binary.Writable.writeToBytesOrThrow(container); const frames = index.HidFrame.splitOrThrow(this.#channel, bytes); let frame = frames.next(); for (; !frame.done; frame = frames.next()) await this.#transferOutOrThrow(frame.value); return frame.value; } async *#receiveOrThrow() { while (true) yield await this.#transferInOrThrow(64); } async requestOrThrow(init) { const request = apdu.ApduRequest.fromOrThrow(init); await this.#sendOrThrow(request); const bytes = await index.HidFrame.unsplitOrThrow(this.#channel, this.#receiveOrThrow()); const response = binary.Readable.readFromBytesOrThrow(apdu.ApduResponse, bytes); return response; } } exports.Connector = Connector; exports.DeviceInterfaceNotFoundError = DeviceInterfaceNotFoundError; exports.DeviceTransferInError = DeviceTransferInError; exports.DeviceTransferOutError = DeviceTransferOutError; exports.PACKET_SIZE = PACKET_SIZE; exports.VENDOR_ID = VENDOR_ID; exports.connectOrThrow = connectOrThrow; exports.getDevicesOrThrow = getDevicesOrThrow; exports.getOrRequestDeviceOrThrow = getOrRequestDeviceOrThrow; //# sourceMappingURL=index.cjs.map