mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-22 07:28:10 +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