xpra icon
Bug tracker and wiki

Ticket #1789: u2flib_auth.py

File u2flib_auth.py, 3.2 KB (added by Antoine Martin, 20 months ago)

alternative example using u2flib_host

Line 
1# This file is part of Xpra.
2# Copyright (C) 2018 Antoine Martin <antoine@devloop.org.uk>
3
4import time
5import json
6import struct
7import binascii
8from hashlib import sha256
9from u2flib_host.utils import websafe_decode, websafe_encode
10from u2flib_host import u2f, exc, u2f_v2
11
12VERSION = 'U2F_V2'
13APP_ID = "Xpra"
14
15def h(s):
16    try:
17        s = s.encode()
18    except:
19        pass
20    return binascii.hexlify(s)
21
22#lazy: pick first device:
23devices = u2f.list_devices()
24device = devices[0]
25
26def may_retry(counter, fn, *args, **kwargs):
27    retry = False
28    with device:
29        try:
30            return fn(*args)
31        except exc.APDUError as e:
32            if e.code==0x6985:
33                print("please activate the device")
34                retry = True
35            else:
36                print("APDUError: %s" % (e,))
37                raise
38    if retry and counter>0:
39        time.sleep(0.5)
40        return may_retry(counter-1, fn, *args, **kwargs)
41
42CHALLENGE = '0'*64
43REG_DATA = json.dumps({
44    'version'   : VERSION,
45    'challenge' : CHALLENGE,
46    'appId'     : APP_ID,
47    })
48response = may_retry(5, u2f_v2.register, device, REG_DATA, APP_ID)
49client_data = json.loads(websafe_decode(response['clientData']).decode('utf8'))
50print("register client_data=%s" % (client_data,))
51registration_data = websafe_decode(response['registrationData'])
52#parse registration data to extract key handle and public key:
53b = bytearray(registration_data)
54assert b[0]==5
55pubkey = bytes(b[1:66])
56khl = b[66]
57key_handle = bytes(b[67:67 + khl])
58print("key_handle=%s (len=%i)" % (h(key_handle), len(key_handle)))
59print("public key=%s (len=%i)" % (h(pubkey), len(pubkey)))
60
61#now try to authenticate:
62CHALLENGE = '0123456789012345678901234567890101234567890123456789012345678901'
63AUTH_DATA = json.dumps({
64    'version'   : VERSION,
65    'challenge' : CHALLENGE,
66    'appId'     : APP_ID,
67    'keyHandle' : websafe_encode(key_handle),
68})
69response = may_retry(5, u2f_v2.authenticate, device, AUTH_DATA, APP_ID, check_only=False)
70client_data_json = websafe_decode(response['clientData'])
71client_data = json.loads(client_data_json)
72print("client_data=%s (%s)" % (client_data, type(client_data),))
73sig  = websafe_decode(response["signatureData"])
74print("signature_data=%s (%s len=%i)" % (h(sig), type(sig), len(sig)))
75
76touch = bool(sig[0])
77counter = struct.unpack('>I', sig[1:5])[0]
78print("touch=%s, counter=%i" % (touch, counter))
79assert sig[5] in (0x30, chr(0x30)), "expected sequence got %#x" % sig[5]
80
81def verify(sig):
82    from cryptography.hazmat.primitives import hashes
83    from cryptography.hazmat.primitives.asymmetric import ec
84    from cryptography.hazmat.primitives.serialization import load_der_public_key
85    from cryptography.hazmat.backends import default_backend
86    PUB_KEY_DER_PREFIX = binascii.a2b_hex('3059301306072a8648ce3d020106082a8648ce3d030107034200')
87    der_pubkey = PUB_KEY_DER_PREFIX+pubkey
88    key = load_der_public_key(der_pubkey, default_backend())
89    verifier = key.verifier(sig, ec.ECDSA(hashes.SHA256()))
90    app_param = sha256(APP_ID.encode('utf8')).digest()
91    client_param = sha256(client_data_json).digest()
92    verifier.update(app_param +
93                    struct.pack('>B', touch) +
94                    struct.pack('>I', counter) +
95                    client_param)
96    verifier.verify()
97
98verify(sig[5:])