mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-26 01:18:28 +00:00
tools: add hid-bridge by Ondrej Vejpustek
This commit is contained in:
parent
6a54839acb
commit
fb8d6fe820
2
tools/hid-bridge/50-hid-bridge.rules
Normal file
2
tools/hid-bridge/50-hid-bridge.rules
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
KERNELS=="uhid", KERNEL=="hidraw*", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
|
||||||
|
KERNEL=="uhid", MODE="0660", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
|
21
tools/hid-bridge/README.md
Normal file
21
tools/hid-bridge/README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# hid-bridge
|
||||||
|
|
||||||
|
Creates a virtual hid device which can be controlled by a user driver via a UDP port.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
You need Python 3.5 or higher.
|
||||||
|
|
||||||
|
The uhid driver is required. If it is build as a module and not loaded, load it by `modprobe uhid`.
|
||||||
|
|
||||||
|
You must have read/write permission to the `/dev/uhid/` device as same as to the newly created `/dev/hidraw*` device. This may be accomplished by copying `50-hid-bridge.rules` into `/dev/udev/rules.d/`. You may need to reload the driver.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Run [TREZOR emulator](https://github.com/trezor/trezor-core/blob/master/docs/emulator.md) and `./hid-bridge`.
|
||||||
|
|
||||||
|
## Known issues
|
||||||
|
|
||||||
|
Does not work with Firefox. Firefox closes hid devices on the lost of the focus.
|
||||||
|
|
||||||
|
Does not work with the emulator in the debug mode sice emulator doesn't start hid interface.
|
62
tools/hid-bridge/hid-bridge
Executable file
62
tools/hid-bridge/hid-bridge
Executable file
@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import select
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from udp_interface import UDPInterface
|
||||||
|
from hid_interface import HIDInterface
|
||||||
|
import uhid
|
||||||
|
import logger
|
||||||
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"-e",
|
||||||
|
default=21325,
|
||||||
|
metavar="port",
|
||||||
|
type=int,
|
||||||
|
help="UDP port the utility communicates with, 21325 by default.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-l",
|
||||||
|
"--log-level",
|
||||||
|
choices=["none", "raw", "uhid-event", "hid-packet"],
|
||||||
|
default="none",
|
||||||
|
help="Do not log at all (none); log everything sent to and received by the uhid device and the UDP socket (raw); log uhid events written to and read by the uhid device (uhid-event), log hid packets send to and received by the virtual hid device (hid-packet).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-t",
|
||||||
|
"--log-timestams",
|
||||||
|
dest="log_timestamps",
|
||||||
|
action="store_true",
|
||||||
|
default="False",
|
||||||
|
help="Do include timestamps in the log, this is the default option.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-log-timestams",
|
||||||
|
dest="log_timestamps",
|
||||||
|
action="store_false",
|
||||||
|
help="Do not include timestamps in the log.",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
logger.log_level = args.log_level
|
||||||
|
logger.log_timestamps = args.log_timestamps
|
||||||
|
|
||||||
|
udp_interface = UDPInterface(args.e)
|
||||||
|
hid_interface = HIDInterface()
|
||||||
|
|
||||||
|
poller = select.poll()
|
||||||
|
poller.register(udp_interface.file_descriptor, select.POLLIN | select.POLLPRI)
|
||||||
|
poller.register(hid_interface.file_descriptor, select.POLLIN | select.POLLPRI)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
events = poller.poll()
|
||||||
|
|
||||||
|
for descriptor, event in events:
|
||||||
|
if descriptor == hid_interface.file_descriptor:
|
||||||
|
data = hid_interface.process_event()
|
||||||
|
if data:
|
||||||
|
udp_interface.write(data)
|
||||||
|
if descriptor == udp_interface.file_descriptor:
|
||||||
|
data = udp_interface.read(uhid.EVENT_LENGTH)
|
||||||
|
hid_interface.write_data(data)
|
109
tools/hid-bridge/hid_interface.py
Normal file
109
tools/hid-bridge/hid_interface.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import logger
|
||||||
|
import uhid
|
||||||
|
|
||||||
|
|
||||||
|
def random_bytes(length):
|
||||||
|
return os.urandom(length)
|
||||||
|
|
||||||
|
|
||||||
|
class HIDInterface:
|
||||||
|
uhid_device = "/dev/uhid"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.file_descriptor = os.open(HIDInterface.uhid_device, os.O_RDWR)
|
||||||
|
self.create_device()
|
||||||
|
|
||||||
|
def __uhid_read(self, length):
|
||||||
|
data = os.read(self.file_descriptor, length)
|
||||||
|
logger.log_raw("{} >".format(HIDInterface.uhid_device), data.hex())
|
||||||
|
return data
|
||||||
|
|
||||||
|
def __uhid_write(self, data):
|
||||||
|
bytes_written = os.write(self.file_descriptor, data)
|
||||||
|
assert bytes_written == len(data)
|
||||||
|
logger.log_raw("{} <".format(HIDInterface.uhid_device), data.hex())
|
||||||
|
|
||||||
|
def create_device(self):
|
||||||
|
name = b"Virtual TREZOR"
|
||||||
|
phys = b""
|
||||||
|
uniq = random_bytes(64)
|
||||||
|
bus = 0
|
||||||
|
vendor = 0x1209
|
||||||
|
product = 0x53C1
|
||||||
|
version = 0x0200
|
||||||
|
country = 0
|
||||||
|
# fmt: off
|
||||||
|
rd_data = bytes([
|
||||||
|
0x06, 0xD0, 0xF1, # USAGE_PAGE (FIDO Alliance)
|
||||||
|
0x09, 0x01, # USAGE (U2F HID Authenticator Device)
|
||||||
|
0xA1, 0x01, # COLLECTION (Application)
|
||||||
|
0x09, 0x20, # USAGE (Input Report Data)
|
||||||
|
0x15, 0x00, # LOGICAL_MINIMUM (0)
|
||||||
|
0x26, 0xFF, 0x00, # LOGICAL_MAXIMUM (255)
|
||||||
|
0x75, 0x08, # REPORT_SIZE (8)
|
||||||
|
0x95, 0x40, # REPORT_COUNT (64)
|
||||||
|
0x81, 0x02, # INPUT (Data,Var,Abs)
|
||||||
|
0x09, 0x21, # USAGE (Output Report Data)
|
||||||
|
0x15, 0x00, # LOGICAL_MINIMUM (0)
|
||||||
|
0x26, 0xFF, 0x00, # LOGICAL_MAXIMUM (255)
|
||||||
|
0x75, 0x08, # REPORT_SIZE (8)
|
||||||
|
0x95, 0x40, # REPORT_COUNT (64)
|
||||||
|
0x91, 0x02, # OUTPUT (Data,Var,Abs)
|
||||||
|
0xC0, # END_COLLECTION
|
||||||
|
])
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
buf = uhid.create_create2_event(
|
||||||
|
name, phys, uniq, bus, vendor, product, version, country, rd_data
|
||||||
|
)
|
||||||
|
self.__uhid_write(buf)
|
||||||
|
logger.log_uhid_event(
|
||||||
|
"UHID_CREATE2",
|
||||||
|
"name='{}' phys='{}' uniq=0x{} rd_size={} bus=0x{:04x} vendor=0x{:04x} product=0x{:04x} version=0x{:04x} country=0x{:04x} rd_data=0x{}".format(
|
||||||
|
name.decode("ascii"),
|
||||||
|
phys.decode("ascii"),
|
||||||
|
uniq.hex(),
|
||||||
|
len(rd_data),
|
||||||
|
bus,
|
||||||
|
vendor,
|
||||||
|
product,
|
||||||
|
version,
|
||||||
|
country,
|
||||||
|
rd_data.hex(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def write_data(self, data):
|
||||||
|
buf = uhid.create_input2_event(data)
|
||||||
|
self.__uhid_write(buf)
|
||||||
|
logger.log_uhid_event(
|
||||||
|
"UHID_INPUT2", "data=0x{} size={}".format(data.hex(), len(data))
|
||||||
|
)
|
||||||
|
logger.log_hid_packet("DEVICE_OUTPUT", "0x{}".format(data.hex()))
|
||||||
|
|
||||||
|
def process_event(self):
|
||||||
|
ev_type, request = uhid.parse_event(self.__uhid_read(uhid.EVENT_LENGTH))
|
||||||
|
if ev_type == uhid.EVENT_TYPE_START:
|
||||||
|
dev_flags, = request
|
||||||
|
logger.log_uhid_event("UHID_START", "dev_flags=0b{:08b}".format(dev_flags))
|
||||||
|
elif ev_type == uhid.EVENT_TYPE_STOP:
|
||||||
|
logger.log_uhid_event("UHID_STOP")
|
||||||
|
elif ev_type == uhid.EVENT_TYPE_OPEN:
|
||||||
|
logger.log_uhid_event("UHID_OPEN")
|
||||||
|
elif ev_type == uhid.EVENT_TYPE_CLOSE:
|
||||||
|
logger.log_uhid_event("UHID_CLOSE")
|
||||||
|
elif ev_type == uhid.EVENT_TYPE_OUTPUT:
|
||||||
|
data, size, rtype = request
|
||||||
|
logger.log_uhid_event(
|
||||||
|
"UHID_OUTPUT",
|
||||||
|
"data=0x{} size={} rtype={}".format(data.hex(), size, rtype),
|
||||||
|
)
|
||||||
|
logger.log_hid_packet("DEVICE_INPUT", "0x{}".format(data[1:].hex()))
|
||||||
|
return data[1:]
|
||||||
|
else:
|
||||||
|
logger.log_uhid_event(
|
||||||
|
"UNKNOWN_EVENT",
|
||||||
|
"ev_type={} request=0x{}".format(ev_type, request.hex()),
|
||||||
|
)
|
33
tools/hid-bridge/logger.py
Normal file
33
tools/hid-bridge/logger.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
log_level = "None"
|
||||||
|
log_timestamps = False
|
||||||
|
|
||||||
|
|
||||||
|
def __get_timestamp():
|
||||||
|
return str(datetime.datetime.now())
|
||||||
|
|
||||||
|
|
||||||
|
def __log_message(message):
|
||||||
|
if log_timestamps == True:
|
||||||
|
print("{}\t{}".format(__get_timestamp(), message))
|
||||||
|
else:
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
|
def log_uhid_event(event_name, params=None):
|
||||||
|
if log_level == "uhid-event":
|
||||||
|
if params:
|
||||||
|
__log_message("{}\t{}".format(event_name, params))
|
||||||
|
else:
|
||||||
|
__log_message(event_name)
|
||||||
|
|
||||||
|
|
||||||
|
def log_hid_packet(packet_name, payload):
|
||||||
|
if log_level == "hid-packet":
|
||||||
|
__log_message("{}\t{}".format(packet_name, payload))
|
||||||
|
|
||||||
|
|
||||||
|
def log_raw(direction, payload):
|
||||||
|
if log_level == "raw":
|
||||||
|
__log_message("{}\t{}".format(direction, payload))
|
39
tools/hid-bridge/udp_interface.py
Normal file
39
tools/hid-bridge/udp_interface.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import socket
|
||||||
|
|
||||||
|
import logger
|
||||||
|
|
||||||
|
|
||||||
|
class UDPInterface:
|
||||||
|
def __init__(self, destination_port):
|
||||||
|
self.bind_ip = "127.0.0.1"
|
||||||
|
self.bind_port = 21423
|
||||||
|
|
||||||
|
self.destination_ip = "127.0.0.1"
|
||||||
|
self.destination_port = destination_port
|
||||||
|
|
||||||
|
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.socket.bind((self.bind_ip, self.bind_port))
|
||||||
|
|
||||||
|
self.file_descriptor = self.socket.fileno()
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
bytes_sent = self.socket.sendto(
|
||||||
|
data, ((self.destination_ip, self.destination_port))
|
||||||
|
)
|
||||||
|
assert bytes_sent == len(data)
|
||||||
|
logger.log_raw(
|
||||||
|
"{}:{} < {}:{}".format(
|
||||||
|
self.destination_ip, self.destination_port, self.bind_ip, self.bind_port
|
||||||
|
),
|
||||||
|
data.hex(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def read(self, length):
|
||||||
|
data, address = self.socket.recvfrom(length)
|
||||||
|
logger.log_raw(
|
||||||
|
"{}:{} < {}:{}".format(
|
||||||
|
self.bind_ip, self.bind_port, address[0], address[1]
|
||||||
|
),
|
||||||
|
data.hex(),
|
||||||
|
)
|
||||||
|
return data
|
73
tools/hid-bridge/uhid.py
Normal file
73
tools/hid-bridge/uhid.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import struct
|
||||||
|
|
||||||
|
EVENT_TYPE_START = 2
|
||||||
|
EVENT_TYPE_STOP = 3
|
||||||
|
EVENT_TYPE_OPEN = 4
|
||||||
|
EVENT_TYPE_CLOSE = 5
|
||||||
|
EVENT_TYPE_OUTPUT = 6
|
||||||
|
EVENT_TYPE_CREATE2 = 11
|
||||||
|
EVENT_TYPE_INTPUT2 = 12
|
||||||
|
|
||||||
|
DATA_MAX = 4096
|
||||||
|
EVENT_LENGTH = 4380
|
||||||
|
|
||||||
|
INPUT2_REQ_FMT = "< H {}s".format(DATA_MAX)
|
||||||
|
CREATE2_REQ_FMT = "< 128s 64s 64s H H L L L L {}s".format(DATA_MAX)
|
||||||
|
START_REQ_FMT = "< Q"
|
||||||
|
OUTPUT_REQ_FMT = "< {}s H B".format(DATA_MAX)
|
||||||
|
|
||||||
|
|
||||||
|
def pack_event(ev_type, request):
|
||||||
|
return ev_type.to_bytes(4, byteorder="little") + request
|
||||||
|
|
||||||
|
|
||||||
|
def unpack_event(buf):
|
||||||
|
return int.from_bytes(buf[:4], byteorder="little"), buf[4:]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_event(event):
|
||||||
|
assert len(event) == EVENT_LENGTH
|
||||||
|
|
||||||
|
ev_type, request = unpack_event(event)
|
||||||
|
|
||||||
|
if ev_type == EVENT_TYPE_START:
|
||||||
|
request = struct.unpack_from(START_REQ_FMT, request)
|
||||||
|
elif ev_type == EVENT_TYPE_STOP:
|
||||||
|
request = []
|
||||||
|
elif ev_type == EVENT_TYPE_OPEN:
|
||||||
|
request = []
|
||||||
|
elif ev_type == EVENT_TYPE_CLOSE:
|
||||||
|
request = []
|
||||||
|
elif ev_type == EVENT_TYPE_OUTPUT:
|
||||||
|
data, size, rtype = struct.unpack_from(OUTPUT_REQ_FMT, request)
|
||||||
|
data = data[:size]
|
||||||
|
request = [data, size, rtype]
|
||||||
|
|
||||||
|
return ev_type, request
|
||||||
|
|
||||||
|
|
||||||
|
def create_create2_event(
|
||||||
|
name, phys, uniq, bus, vendor, product, version, country, rd_data
|
||||||
|
):
|
||||||
|
uhid_create2_req = struct.pack(
|
||||||
|
CREATE2_REQ_FMT,
|
||||||
|
name,
|
||||||
|
phys,
|
||||||
|
uniq,
|
||||||
|
len(rd_data),
|
||||||
|
bus,
|
||||||
|
vendor,
|
||||||
|
product,
|
||||||
|
version,
|
||||||
|
country,
|
||||||
|
rd_data,
|
||||||
|
)
|
||||||
|
event = pack_event(EVENT_TYPE_CREATE2, uhid_create2_req)
|
||||||
|
|
||||||
|
return event
|
||||||
|
|
||||||
|
|
||||||
|
def create_input2_event(data):
|
||||||
|
uhid_input2_req = struct.pack(INPUT2_REQ_FMT, len(data), data)
|
||||||
|
event = pack_event(EVENT_TYPE_INTPUT2, uhid_input2_req)
|
||||||
|
return event
|
Loading…
Reference in New Issue
Block a user