Skip to content
This repository was archived by the owner on Jun 9, 2022. It is now read-only.

Commit 0dbaef2

Browse files
committed
Merge branch 'capabilities'
2 parents 2fdbadf + e65d7b3 commit 0dbaef2

8 files changed

Lines changed: 125 additions & 6 deletions

File tree

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ addons:
2222

2323
install:
2424
- pip install "pip>=7.0.2" wheel
25-
- pip install -r dev-requirements.txt
25+
- $TRAVIS_BUILD_DIR/dev-requirements.py
2626
- pip install -e .
2727

2828
script:

dev-requirements-2.7.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
coverage
2+
coveralls
3+
Cython # For building hidapi in Tox and on Travis
4+
cryptography>=1.0
5+
mock

dev-requirements.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env python
2+
3+
import os
4+
import sys
5+
6+
if sys.version_info.major == 2:
7+
os.system('pip install -r dev-requirements-2.7.txt')
8+
elif sys.version_info.major == 3:
9+
os.system('pip install -r dev-requirements-3.x.txt')
10+
else:
11+
raise Exception('Unsupported python version!')

setup.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@
2525
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2626
# POSSIBILITY OF SUCH DAMAGE.
2727

28+
import sys
2829
from u2flib_host.yubicommon.setup import setup
2930

31+
tests_require = ['cryptography>=1.0']
32+
if (sys.version_info < (3, 3)):
33+
tests_require.append('mock')
3034

3135
setup(
3236
name='python-u2flib-host',
@@ -44,7 +48,7 @@
4448
'u2f-authenticate=u2flib_host.authenticate:main',
4549
],
4650
},
47-
tests_require=['cryptography>=1.0'],
51+
tests_require=tests_require,
4852
extras_require={
4953
'soft_device': ['cryptography>=1.0'],
5054
},

test/test_hid_transport.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import os
2+
import unittest
3+
from u2flib_host import hid_transport
4+
from u2flib_host import exc
5+
from u2flib_host.yubicommon.compat import byte2int, int2byte
6+
7+
try:
8+
from unittest.mock import patch
9+
except ImportError:
10+
from mock import patch
11+
12+
class TestHIDDevice(object):
13+
def write(self, payload):
14+
self.cid = payload[1:5]
15+
self.cmd = payload[5] ^ hid_transport.TYPE_INIT
16+
self.size = (payload[6] << 8) + payload[7]
17+
self.data = list(map(int2byte, payload[8:(8 + self.size)]))
18+
19+
def read(self, size):
20+
self.response += [0] * (hid_transport.HID_RPT_SIZE - len(self.response) + 1)
21+
types = list(map(type, self.response))
22+
return self.response
23+
24+
def close(self):
25+
return None
26+
27+
28+
class HIDDeviceTest(unittest.TestCase):
29+
@classmethod
30+
def build_response(cls, cid, cmd, data):
31+
size = len(data)
32+
size_low = size & 0xff
33+
size_high = (size >> 8) & 0xff
34+
response = list(map(byte2int, cid)) + list(map(byte2int, cmd)) + [size_high, size_low]
35+
response += list(map(byte2int, data))
36+
return response
37+
38+
39+
def test_init(self):
40+
with patch.object(os, 'urandom', return_value=(b'\xab'*8)) as mock_method:
41+
hid_device = TestHIDDevice()
42+
hid_device.response = HIDDeviceTest.build_response(
43+
b'\xff'*4,
44+
b'\x86',
45+
b'\xab'*8 + b'\x01\x02\x03\x04' + b'\x01\x02\x03\x04\x05'
46+
)
47+
48+
dev = hid_transport.HIDDevice('/dev/null')
49+
dev.handle = hid_device
50+
dev.init()
51+
self.assertEqual(dev.capabilities, 0x05)
52+
53+
def test_init_invalid_nonce(self):
54+
with patch.object(os, 'urandom', return_value=(b'\xab'*8)) as mock_method:
55+
hid_device = TestHIDDevice()
56+
hid_device.response = HIDDeviceTest.build_response(
57+
b'\xff'*4,
58+
b'\x86',
59+
b'\x00'*8 + b'\x01\x02\x03\x04' + b'\x01\x02\x03\x04\x05'
60+
)
61+
62+
dev = hid_transport.HIDDevice('/dev/null')
63+
dev.handle = hid_device
64+
with self.assertRaises(exc.DeviceError) as context:
65+
dev.init()
66+
self.assertTrue('Wrong INIT response from device' in context.exception)
67+
68+
def test_init_invalid_length(self):
69+
with patch.object(os, 'urandom', return_value=(b'\xab'*8)) as mock_method:
70+
hid_device = TestHIDDevice()
71+
hid_device.response = HIDDeviceTest.build_response(
72+
b'\xff'*4,
73+
b'\x86',
74+
b'\xab'*8 + b'\x01\x02\x03\x04' + b'\x01\x02\x03\x04'
75+
)
76+
77+
dev = hid_transport.HIDDevice('/dev/null')
78+
dev.handle = hid_device
79+
80+
with self.assertRaises(exc.DeviceError) as context:
81+
dev.init()
82+
self.assertTrue('Wrong INIT response from device' in context.exception)
83+
84+
def test_ctap2_enabled(self):
85+
dev = hid_transport.HIDDevice('/dev/null')
86+
dev.capabilities = 0x01
87+
self.assertFalse(dev.ctap2_enabled())
88+
dev.capabilities = 0x04
89+
self.assertTrue(dev.ctap2_enabled())

u2flib_host/hid_transport.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import hidraw as hid # Prefer hidraw
3333
except ImportError:
3434
import hid
35-
from time import time
35+
from time import time, sleep
3636
from u2flib_host.device import U2FDevice
3737
from u2flib_host.yubicommon.compat import byte2int, int2byte
3838
from u2flib_host import exc
@@ -110,6 +110,7 @@ class HIDDevice(U2FDevice):
110110
def __init__(self, path):
111111
self.path = path
112112
self.cid = b"\xff\xff\xff\xff"
113+
self.capabilities = 0x00
113114

114115
def open(self):
115116
self.handle = hid.device()
@@ -125,10 +126,19 @@ def close(self):
125126
def init(self):
126127
nonce = os.urandom(8)
127128
resp = self.call(CMD_INIT, nonce)
128-
while resp[:8] != nonce:
129-
print("Wrong nonce, read again...")
129+
130+
timeout = time() + 2.0
131+
while (len(resp) != 17 or resp[:8] != nonce):
132+
if timeout < time():
133+
raise exc.DeviceError('Wrong INIT response from device')
134+
sleep(0.1)
130135
resp = self._read_resp(self.cid, CMD_INIT)
136+
131137
self.cid = resp[8:12]
138+
self.capabilities = byte2int(resp[16])
139+
140+
def ctap2_enabled(self):
141+
return (self.capabilities >> 2) & 0x01
132142

133143
def set_mode(self, mode):
134144
data = mode + b"\x0f\x00\x00"

0 commit comments

Comments
 (0)