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

Commit df35142

Browse files
committed
Fixed Python 3 incompatibilities in u2f_v2 and added test.
1 parent 4396b95 commit df35142

2 files changed

Lines changed: 100 additions & 6 deletions

File tree

test/test_u2f_v2.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
2+
from u2flib_host.constants import INS_ENROLL, INS_SIGN, APDU_USE_NOT_SATISFIED
3+
from u2flib_host.utils import websafe_decode, websafe_encode
4+
from u2flib_host.exc import APDUError
5+
from u2flib_host import u2f_v2
6+
import unittest
7+
import json
8+
9+
VERSION = 'U2F_V2'
10+
FACET = 'https://example.com'
11+
CHALLENGE = 'challenge'
12+
KEY_HANDLE = websafe_encode(b'\0' * 64)
13+
14+
REG_DATA = json.dumps({
15+
'version': VERSION,
16+
'challenge': CHALLENGE,
17+
'appId': FACET
18+
})
19+
20+
AUTH_DATA = json.dumps({
21+
'version': VERSION,
22+
'challenge': CHALLENGE,
23+
'appId': FACET,
24+
'keyHandle': KEY_HANDLE
25+
})
26+
27+
DUMMY_RESP = b'a_dummy_response'
28+
29+
30+
class MockDevice(object):
31+
32+
def __init__(self, response):
33+
self._response = response
34+
35+
def send_apdu(self, ins, p1, p2, request):
36+
self.ins = ins
37+
self.p1 = p1
38+
self.p2 = p2
39+
self.request = request
40+
41+
if isinstance(self._response, Exception):
42+
raise self._response
43+
return self._response
44+
45+
46+
class TestU2FV2(unittest.TestCase):
47+
48+
def test_register(self):
49+
device = MockDevice(DUMMY_RESP)
50+
response = u2f_v2.register(device, REG_DATA, FACET)
51+
52+
self.assertEqual(device.ins, INS_ENROLL)
53+
self.assertEqual(device.p1, 0x03)
54+
self.assertEqual(device.p2, 0x00)
55+
self.assertEqual(len(device.request), 64)
56+
57+
self.assertEqual(websafe_decode(response['registrationData']),
58+
DUMMY_RESP)
59+
60+
client_data = json.loads(websafe_decode(response['clientData'])
61+
.decode('utf8'))
62+
self.assertEqual(client_data['typ'], 'navigator.id.finishEnrollment')
63+
self.assertEqual(client_data['origin'], FACET)
64+
self.assertEqual(client_data['challenge'], CHALLENGE)
65+
66+
def test_authenticate(self):
67+
device = MockDevice(DUMMY_RESP)
68+
response = u2f_v2.authenticate(device, AUTH_DATA, FACET, False)
69+
70+
self.assertEqual(device.ins, INS_SIGN)
71+
self.assertEqual(device.p1, 0x03)
72+
self.assertEqual(device.p2, 0x00)
73+
self.assertEqual(len(device.request), 64 + 1 + 64)
74+
self.assertEqual(device.request[-64:], websafe_decode(KEY_HANDLE))
75+
76+
self.assertEqual(response['keyHandle'], KEY_HANDLE)
77+
self.assertEqual(websafe_decode(response['signatureData']), DUMMY_RESP)
78+
79+
client_data = json.loads(websafe_decode(response['clientData'])
80+
.decode('utf8'))
81+
self.assertEqual(client_data['typ'], 'navigator.id.getAssertion')
82+
self.assertEqual(client_data['origin'], FACET)
83+
self.assertEqual(client_data['challenge'], CHALLENGE)
84+
85+
def test_authenticate_check_only(self):
86+
device = MockDevice(APDUError(APDU_USE_NOT_SATISFIED))
87+
88+
try:
89+
u2f_v2.authenticate(device, AUTH_DATA, FACET, True)
90+
self.fail('authenticate should throw USE_NOT_SATISIFIED')
91+
except APDUError as e:
92+
self.assertEqual(device.ins, INS_SIGN)
93+
self.assertEqual(device.p1, 0x07)
94+
self.assertEqual(e.code, APDU_USE_NOT_SATISFIED)

u2flib_host/u2f_v2.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from u2flib_host.constants import INS_ENROLL, INS_SIGN
2929
from u2flib_host.utils import websafe_decode, websafe_encode
3030
from u2flib_host.appid import verify_facet
31-
from u2flib_host.yubicommon.compat import string_types
31+
from u2flib_host.yubicommon.compat import string_types, int2byte
3232

3333
from hashlib import sha256
3434
import json
@@ -56,15 +56,15 @@ def register(device, data, facet):
5656

5757
app_id = data.get('appId', facet)
5858
verify_facet(app_id, facet)
59-
app_param = sha256(app_id).digest()
59+
app_param = sha256(app_id.encode('utf8')).digest()
6060

6161
client_data = {
6262
'typ': 'navigator.id.finishEnrollment',
6363
'challenge': data['challenge'],
6464
'origin': facet
6565
}
6666
client_data = json.dumps(client_data)
67-
client_param = sha256(client_data).digest()
67+
client_param = sha256(client_data.encode('utf8')).digest()
6868

6969
request = client_param + app_param
7070

@@ -99,7 +99,7 @@ def authenticate(device, data, facet, check_only=False):
9999

100100
app_id = data.get('appId', facet)
101101
verify_facet(app_id, facet)
102-
app_param = sha256(app_id).digest()
102+
app_param = sha256(app_id.encode('utf8')).digest()
103103

104104
key_handle = websafe_decode(data['keyHandle'])
105105

@@ -110,9 +110,9 @@ def authenticate(device, data, facet, check_only=False):
110110
'origin': facet
111111
}
112112
client_data = json.dumps(client_data)
113-
client_param = sha256(client_data).digest()
113+
client_param = sha256(client_data.encode('utf8')).digest()
114114

115-
request = client_param + app_param + chr(
115+
request = client_param + app_param + int2byte(
116116
len(key_handle)) + key_handle
117117

118118
p1 = 0x07 if check_only else 0x03

0 commit comments

Comments
 (0)