Closed VitGottwald closed 7 years ago
Thanks for reporting this issue! It looks very similar to https://github.com/romanz/trezor-agent/issues/53, which was fixed by https://github.com/trezor/python-trezor/pull/77.
Could you please update your trezor package to the latest version (currently 0.7.12)?
Could you also run trezorctl get_features
command a few times, to see if this bug can be reproduced without trezor-agent
?
It looks like I already have the latest version
$ pip list
Cython (0.25.2)
ecdsa (0.13)
ECPy (0.8.1)
ed25519 (1.4)
future (0.16.0)
hidapi (0.7.99.post15)
keepkey (0.7.3)
ledgerblue (0.1.12)
mnemonic (0.17)
olefile (0.44)
pbkdf2 (1.3)
Pillow (4.0.0)
pip (9.0.1)
protobuf (3.2.0)
pycrypto (2.6.1)
semver (2.7.6)
setuptools (32.1.0)
six (1.10.0)
trezor (0.7.12)
trezor-agent (0.8.2)
wheel (0.29.0)
running trezorctl has the same problem
$ trezorctl get_features
Traceback (most recent call last):
File "/usr/local/bin/trezorctl", line 598, in <module>
main()
File "/usr/local/bin/trezorctl", line 581, in main
client = TrezorClient(transport)
File "/usr/local/lib/python2.7/site-packages/trezorlib/client.py", line 431, in __init__
self.init_device()
File "/usr/local/lib/python2.7/site-packages/trezorlib/client.py", line 438, in init_device
self.features = expect(proto.Features)(self.call)(proto.Initialize())
File "/usr/local/lib/python2.7/site-packages/trezorlib/client.py", line 127, in wrapped_f
ret = f(*args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/trezorlib/client.py", line 140, in wrapped_f
return f(*args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/trezorlib/client.py", line 176, in call
resp = self.call_raw(msg)
File "/usr/local/lib/python2.7/site-packages/trezorlib/client.py", line 140, in wrapped_f
return f(*args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/trezorlib/client.py", line 172, in call_raw
return self.transport.read_blocking()
File "/usr/local/lib/python2.7/site-packages/trezorlib/transport.py", line 86, in read_blocking
data = self._read()
File "/usr/local/lib/python2.7/site-packages/trezorlib/transport.py", line 146, in _read
(msg_type, datalen, data) = self.parse_first(chunk)
File "/usr/local/lib/python2.7/site-packages/trezorlib/transport.py", line 158, in parse_first
raise Exception("Unexpected magic characters")
Exception: Unexpected magic characters
It seems that there is an issue with the USB link with the device itself... Could you please retry with another USB cable?
@prusnak - what do you think?
I use trezor with this cable for TREZOR password manager without any problem. I just tried another cable anyway, but the result is still the same.
The really weird part is that the command sometimes succeeds and sometimes not and it looks totally random.
Duplicate of https://github.com/trezor/python-trezor/issues/76 and https://github.com/romanz/trezor-agent/issues/50
Wonder why it is still present.
Any ideas how to turn on some diagnostics to help you find the root cause?
@baremetalfreak , can you please attach your /usr/local/lib/python2.7/site-packages/trezorlib/transport_hid.py
file, to make sure that its enumerate()
function returns sorted(devices.values())
?
(that was the issue solved at https://github.com/trezor/python-trezor/pull/77)
# This file is part of the TREZOR project.
#
# Copyright (C) 2012-2016 Marek Palatinus <slush@satoshilabs.com>
# Copyright (C) 2012-2016 Pavol Rusnak <stick@satoshilabs.com>
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see <http://www.gnu.org/licenses/>.
'''USB HID implementation of Transport.'''
import hid
import time
from .transport import TransportV1, TransportV2, ConnectionError
def enumerate():
"""
Return a list of available TREZOR devices.
"""
devices = {}
for d in hid.enumerate(0, 0):
vendor_id = d['vendor_id']
product_id = d['product_id']
serial_number = d['serial_number']
interface_number = d['interface_number']
usage_page = d['usage_page']
path = d['path']
if (vendor_id, product_id) in DEVICE_IDS:
devices.setdefault(serial_number, [None, None])
# first match by usage_page, then try interface number
if usage_page == 0xFF00 or interface_number == 0: # normal link
devices[serial_number][0] = path
elif usage_page == 0xFF01 or interface_number == 1: # debug link
devices[serial_number][1] = path
# List of two-tuples (path_normal, path_debuglink)
return sorted(devices.values())
def path_to_transport(path):
try:
device = [ d for d in hid.enumerate(0, 0) if d['path'] == path ][0]
except IndexError:
raise ConnectionError("Connection failed")
# VID/PID found, let's find proper transport
try:
transport = DEVICE_TRANSPORTS[(device['vendor_id'], device['product_id'])]
except IndexError:
raise Exception("Unknown transport for VID:PID %04x:%04x" % (vid, pid))
return transport
class _HidTransport(object):
def __init__(self, device, *args, **kwargs):
self.hid = None
self.hid_version = None
device = device[int(bool(kwargs.get('debug_link')))]
super(_HidTransport, self).__init__(device, *args, **kwargs)
def is_connected(self):
"""
Check if the device is still connected.
"""
for d in hid.enumerate(0, 0):
if d['path'] == self.device:
return True
return False
def _open(self):
self.hid = hid.device()
self.hid.open_path(self.device)
self.hid.set_nonblocking(True)
# determine hid_version
if isinstance(self, HidTransportV2):
self.hid_version = 2
else:
r = self.hid.write([0, 63, ] + [0xFF] * 63)
if r == 65:
self.hid_version = 2
return
r = self.hid.write([63, ] + [0xFF] * 63)
if r == 64:
self.hid_version = 1
return
raise ConnectionError("Unknown HID version")
def _close(self):
self.hid.close()
self.hid = None
def _write_chunk(self, chunk):
if len(chunk) != 64:
raise Exception("Unexpected data length")
if self.hid_version == 2:
self.hid.write(b'\0' + chunk)
else:
self.hid.write(chunk)
def _read_chunk(self):
start = time.time()
while True:
data = self.hid.read(64)
if not len(data):
if time.time() - start > 10:
# Over 10 s of no response, let's check if
# device is still alive
if not self.is_connected():
raise ConnectionError("Connection failed")
# Restart timer
start = time.time()
time.sleep(0.001)
continue
break
if len(data) != 64:
raise Exception("Unexpected chunk size: %d" % len(data))
return bytearray(data)
class HidTransportV1(_HidTransport, TransportV1):
pass
class HidTransportV2(_HidTransport, TransportV2):
pass
DEVICE_IDS = [
(0x534c, 0x0001), # TREZOR
(0x1209, 0x53C0), # TREZORv2 Bootloader
(0x1209, 0x53C1), # TREZORv2
]
DEVICE_TRANSPORTS = {
(0x534c, 0x0001): HidTransportV1, # TREZOR
(0x1209, 0x53C0): HidTransportV2, # TREZORv2 Bootloader
(0x1209, 0x53C1): HidTransportV2, # TREZORv2
}
# Backward compatible wrapper, decides for proper transport
# based on VID/PID of given path
def HidTransport(device, *args, **kwargs):
transport = path_to_transport(device[0])
return transport(device, *args, **kwargs)
# Backward compatibility hack; HidTransport is a function, not a class like before
HidTransport.enumerate = enumerate
What does the result of running the following in Python
import hid
hid.enumerate()
look like? (while TREZOR is connected?)
$ python
Python 2.7.13 (default, Dec 18 2016, 07:03:39)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import hid
>>> hid.enumerate()
[{'interface_number': -1,
'manufacturer_string': u'Apple Inc.',
'path': 'USB_05ac_0274_14400000',
'product_id': 628,
'product_string': u'Apple Internal Keyboard / Trackpad',
'release_number': 1572,
'serial_number': u'D3H62661FU1GHMFAF2FS',
'usage': 11,
'usage_page': 65280,
'vendor_id': 1452},
{'interface_number': -1,
'manufacturer_string': u'Apple Inc.',
'path': 'USB_05ac_0274_14400000',
'product_id': 628,
'product_string': u'Apple Internal Keyboard / Trackpad',
'release_number': 1572,
'serial_number': u'D3H62661FU1GHMFAF2FS',
'usage': 6,
'usage_page': 1,
'vendor_id': 1452},
{'interface_number': -1,
'manufacturer_string': u'SatoshiLabs',
'path': 'USB_534c_0001_14200000',
'product_id': 1,
'product_string': u'TREZOR',
'release_number': 256,
'serial_number': u'25EF44E0D2C99827292CC0AE',
'usage': 1,
'usage_page': 61904,
'vendor_id': 21324},
{'interface_number': -1,
'manufacturer_string': u'Apple Inc.',
'path': 'USB_05ac_0274_14400000',
'product_id': 628,
'product_string': u'Apple Internal Keyboard / Trackpad',
'release_number': 1572,
'serial_number': u'D3H62661FU1GHMFAF2FS',
'usage': 2,
'usage_page': 1,
'vendor_id': 1452},
{'interface_number': -1,
'manufacturer_string': u'SatoshiLabs',
'path': 'USB_534c_0001_14200000',
'product_id': 1,
'product_string': u'TREZOR',
'release_number': 256,
'serial_number': u'25EF44E0D2C99827292CC0AE',
'usage': 1,
'usage_page': 65280,
'vendor_id': 21324},
{'interface_number': -1,
'manufacturer_string': u'Apple Inc.',
'path': 'USB_05ac_0274_14400000',
'product_id': 628,
'product_string': u'Apple Internal Keyboard / Trackpad',
'release_number': 1572,
'serial_number': u'D3H62661FU1GHMFAF2FS',
'usage': 13,
'usage_page': 65280,
'vendor_id': 1452},
{'interface_number': -1,
'manufacturer_string': u'Apple Inc.',
'path': 'USB_05ac_0274_14400000',
'product_id': 628,
'product_string': u'Apple Internal Keyboard / Trackpad',
'release_number': 1572,
'serial_number': u'D3H62661FU1GHMFAF2FS',
'usage': 3,
'usage_page': 65280,
'vendor_id': 1452}]
>>>
Crappy OSX USB stack. :-/ Both interfaces (normal and U2F) report the same path (USB_534c_0001_14200000 in your case). And it also seems that random interface is opened each time.
I wonder if that changed recently or was always broken like this.
I have a big rewrite of all transport-related stuff in python-trezor, but this might take some time to finish it. Without it this issue is not fixable unfortunately (because our Python stack uses path to identify the correct interface it should talk to, not its native USB handle).
Any idea why Chrome plugin for TREZOR password manager works flawlessly over the same USB port and cable?
Because they use device handles, not paths to distinguish between the interfaces, while our Python stack uses strings to identify the interfaces.
It seems that you already know the exact problem and what it would take to fix it.
So just for my better understanding - is the problem caused by the python code using https://github.com/signal11/hidapi while the chrome plugin using https://developer.chrome.com/apps/hid to deal with USB devices?
I found the culprit ...
Older versions of hidapi
used custom paths which had collisions I described above. Latest release of python-hidapi (0.7.99.post20) used updated version of hidapi from master, which uses IOKit paths, which do not have these collisions:
new hidapi:
IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/XHC1@14/XHC1@14000000/PRT1@14100000/TREZOR@14100000/IOUSBHostInterface@0/IOUSBHostHIDDevice@14100000,0
IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/XHC1@14/XHC1@14000000/PRT1@14100000/TREZOR@14100000/IOUSBHostInterface@1/IOUSBHostHIDDevice@14100000,1
vs old hidapi:
USB_534c_0001_14200000
USB_534c_0001_14200000
Please update to latest hidapi. I also added requirement for hidapi>=0.7.99.post20
to python-trezor.
Upgrading to hidapi (0.7.99.post20) fixed the problem. Thank you!!!
Thanks @prusnak :)
Can confirm. I had the same issue. Solved with sudo pip install -I hidapi
After installation of trezor_agent on macOS Sierra (12.10.3) , I get this:
the funny thing is that if I try to run the same trezor-agent command a couple of times, sometimes it actually outputs the expected thing
in this case the display blinks with some text that is immediately replaced back by logo