1
0
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:
Pavol Rusnak 2019-03-28 19:16:15 +01:00
parent 6a54839acb
commit fb8d6fe820
No known key found for this signature in database
GPG Key ID: 91F3B339B9A02A3D
7 changed files with 339 additions and 0 deletions

View 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"

View 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
View 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)

View 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()),
)

View 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))

View 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
View 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