def _read_resp(self, cid, cmd): resp = b'.' header = cid + int2byte(TYPE_INIT | cmd) while resp and resp[:5] != header: resp_vals = _read_timeout(self.handle, HID_RPT_SIZE) resp = b''.join(int2byte(v) for v in resp_vals) if resp[:5] == cid + int2byte(STAT_ERR): raise U2FHIDError(byte2int(resp[7])) if not resp: raise exc.DeviceError("Invalid response from device!") data_len = (byte2int(resp[5]) << 8) + byte2int(resp[6]) data = resp[7:min(7 + data_len, HID_RPT_SIZE)] data_len -= len(data) seq = 0 while data_len > 0: resp_vals = _read_timeout(self.handle, HID_RPT_SIZE) resp = b''.join(int2byte(v) for v in resp_vals) if resp[:4] != cid: raise exc.DeviceError("Wrong CID from device!") if byte2int(resp[4]) != seq & 0x7f: raise exc.DeviceError("Wrong SEQ from device!") seq += 1 new_data = resp[5:min(5 + data_len, HID_RPT_SIZE)] data_len -= len(new_data) data += new_data return data
def _read_resp(self, cid, cmd): resp = b'.' header = cid + int2byte(TYPE_INIT | cmd) while resp and resp[:5] != header: resp_vals = _read_timeout(self.handle, HID_RPT_SIZE) resp = b''.join(int2byte(v) for v in resp_vals) if resp[:5] == cid + int2byte(STAT_ERR): raise U2FHIDError(byte2int(resp[6])) if not resp: raise exc.DeviceError("Invalid response from device!") data_len = (byte2int(resp[5]) << 8) + byte2int(resp[6]) data = resp[7:min(7 + data_len, HID_RPT_SIZE)] data_len -= len(data) seq = 0 while data_len > 0: resp_vals = _read_timeout(self.handle, HID_RPT_SIZE) resp = b''.join(int2byte(v) for v in resp_vals) if resp[:4] != cid: raise exc.DeviceError("Wrong CID from device!") if byte2int(resp[4:5]) != seq & 0x7f: raise exc.DeviceError("Wrong SEQ from device!") seq += 1 new_data = resp[5:min(5 + data_len, HID_RPT_SIZE)] data_len -= len(new_data) data += new_data return data
def exchange(self, apdu, timeout=TIMEOUT): if self.debug: print("U2F => %s" % apdu.hex()) if len(apdu) >= 256: raise CommException("Too long APDU to transport") # wrap apdu i = 0 keyHandle = b'' while i < len(apdu): val = apdu[i:i + 1] if len(self.scrambleKey) > 0: val = b'' + int2byte( ord(val) ^ ord(self.scrambleKey[i % len(self.scrambleKey)])) keyHandle += val i += 1 client_param = sha256("u2f_tunnel".encode('utf8')).digest() app_param = sha256("u2f_tunnel".encode('utf8')).digest() request = client_param + app_param + int2byte( len(keyHandle)) + keyHandle start = time.time() while time.time() - start < timeout: #p1 = 0x07 if check_only else 0x03 p1 = 0x03 p2 = 0 try: response = self.device.send_apdu(INS_SIGN, p1, p2, request) except exc.APDUError as e: if e.code == 0x6985: time.sleep(0.25) continue raise e if self.debug: print("U2F <= %s%.2x" % (response.hex(), 0x9000)) # check replied status words of the command (within the APDU tunnel) if response[-2:] != b"\x90\x00": raise CommException("Invalid status words received: " + response[-2:].hex()) else: break # api expect a byte array, remove the appended status words, remove the user presence and counter return bytearray(response[5:-2])
def _send_req(self, cid, cmd, data): size = len(data) bc_l = int2byte(size & 0xff) bc_h = int2byte(size >> 8 & 0xff) payload = cid + int2byte(TYPE_INIT | cmd) + bc_h + bc_l + \ data[:HID_RPT_SIZE - 7] payload += b'\0' * (HID_RPT_SIZE - len(payload)) self.handle.write([0] + [byte2int(c) for c in payload]) data = data[HID_RPT_SIZE - 7:] seq = 0 while len(data) > 0: payload = cid + int2byte(0x7f & seq) + data[:HID_RPT_SIZE - 5] payload += b'\0' * (HID_RPT_SIZE - len(payload)) self.handle.write([0] + [byte2int(c) for c in payload]) data = data[HID_RPT_SIZE - 5:] seq += 1
def send_apdu(self, ins, p1=0, p2=0, data=b''): """ Sends an APDU to the device, and waits for a response. """ if data is None: data = b'' elif isinstance(data, int): data = int2byte(data) size = len(data) l0 = size >> 16 & 0xff l1 = size >> 8 & 0xff l2 = size & 0xff apdu_data = struct.pack('B B B B B B B %is B B' % size, 0, ins, p1, p2, l0, l1, l2, data, 0x04, 0x00) try: resp = self._do_send_apdu(apdu_data) except Exception as e: # TODO Use six.reraise if/when Six becomes an agreed dependency. raise exc.DeviceError(e) status = struct.unpack('>H', resp[-2:])[0] data = resp[:-2] if status != APDU_OK: raise exc.APDUError(status) return data
def send_apdu(self, ins, p1=0, p2=0, data=b''): """ Sends an APDU to the device, and waits for a response. """ if data is None: data = b'' elif isinstance(data, int): data = int2byte(data) size = len(data) l0 = size >> 16 & 0xff l1 = size >> 8 & 0xff l2 = size & 0xff apdu_data = struct.pack('B B B B B B B %is B B' % size, 0, ins, p1, p2, l0, l1, l2, data, 0x00, 0x00) try: resp = self._do_send_apdu(apdu_data) except Exception as e: # TODO Use six.reraise if/when Six becomes an agreed dependency. raise exc.DeviceError(e) status = struct.unpack('>H', resp[-2:])[0] data = resp[:-2] if status != APDU_OK: raise exc.APDUError(status) return data
def authenticate(device, data, facet, check_only=False): """ Signs an authentication challenge data = { 'version': "U2F_V2", 'challenge': websafe_encode(self.challenge), 'appId': self.binding.app_id, 'keyHandle': websafe_encode(self.binding.key_handle) } """ if isinstance(data, string_types): data = json.loads(data) if data['version'] != VERSION: raise ValueError('Unsupported U2F version: %s' % data['version']) app_id = data.get('appId', facet) verify_facet(app_id, facet) app_param = sha256(app_id.encode('utf8')).digest() key_handle = websafe_decode(data['keyHandle']) # Client data client_data = { 'typ': 'navigator.id.getAssertion', 'challenge': data['challenge'], 'origin': facet } client_data = json.dumps(client_data) client_param = sha256(client_data.encode('utf8')).digest() request = client_param + app_param + int2byte( len(key_handle)) + key_handle p1 = 0x07 if check_only else 0x03 p2 = 0 response = device.send_apdu(INS_SIGN, p1, p2, request) return { 'clientData': websafe_encode(client_data), 'signatureData': websafe_encode(response), 'keyHandle': data['keyHandle'] }
def authenticate(device, data, facet, check_only=False): """ Signs an authentication challenge data = { 'version': "U2F_V2", 'challenge': websafe_encode(self.challenge), 'appId': self.binding.app_id, 'keyHandle': websafe_encode(self.binding.key_handle) } """ if isinstance(data, string_types): data = json.loads(data) if data['version'] != VERSION: raise ValueError('Unsupported U2F version: %s' % data['version']) app_id = data.get('appId', facet) verify_facet(app_id, facet) app_param = sha256(app_id.encode('utf8')).digest() key_handle = websafe_decode(data['keyHandle']) # Client data client_data = { 'typ': 'navigator.id.getAssertion', 'challenge': data['challenge'], 'origin': facet } client_data = json.dumps(client_data) client_param = sha256(client_data.encode('utf8')).digest() request = client_param + app_param + int2byte(len(key_handle)) + key_handle p1 = 0x07 if check_only else 0x03 p2 = 0 response = device.send_apdu(INS_SIGN, p1, p2, request) return { 'clientData': websafe_encode(client_data), 'signatureData': websafe_encode(response), 'keyHandle': data['keyHandle'] }
def _register(self, data): client_param = data[:32] app_param = data[32:] # ECC key generation privu = ec.generate_private_key(CURVE(), default_backend()) pubu = privu.public_key() pub_key_der = pubu.public_bytes( serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo, ) pub_key = pub_key_der[-65:] # Store key_handle = os.urandom(64) priv_key_pem = privu.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption(), ) self.data['keys'][_b16text(key_handle)] = { 'priv_key': priv_key_pem.decode('ascii'), 'app_param': _b16text(app_param), } self._persist() # Attestation signature cert = CERT cert_priv = serialization.load_pem_private_key( CERT_PRIV, password=None, backend=default_backend(), ) signer = cert_priv.signer(ec.ECDSA(hashes.SHA256())) signer.update(b'\x00' + app_param + client_param + key_handle + pub_key) signature = signer.finalize() raw_response = b'\x05' + pub_key + int2byte(len(key_handle)) + \ key_handle + cert + signature return raw_response
def _register(self, data): client_param = data[:32] app_param = data[32:] # ECC key generation privu = ec.generate_private_key(CURVE(), default_backend()) pubu = privu.public_key() pub_key_der = pubu.public_bytes( serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo, ) pub_key = pub_key_der[-65:] # Store key_handle = os.urandom(64) priv_key_pem = privu.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption(), ) self.data['keys'][_b16text(key_handle)] = { 'priv_key': priv_key_pem.decode('ascii'), 'app_param': _b16text(app_param), } self._persist() # Attestation signature cert = CERT cert_priv = serialization.load_pem_private_key( CERT_PRIV, password=None, backend=default_backend(), ) signer = cert_priv.signer(ec.ECDSA(hashes.SHA256())) signer.update( b'\x00' + app_param + client_param + key_handle + pub_key ) signature = signer.finalize() raw_response = b'\x05' + pub_key + int2byte(len(key_handle)) + \ key_handle + cert + signature return raw_response
def exchange(self, apdu, timeout=TIMEOUT): if self.debug: print("U2F => %s" % hexstr(apdu)) if (len(apdu) >= 256): raise CommException("Too long APDU to transport") # wrap apdu i = 0 keyHandle = "" while i < len(apdu): val = apdu[i:i + 1] if len(self.scrambleKey) > 0: val = chr( ord(val) ^ ord(self.scrambleKey[i % len(self.scrambleKey)])) keyHandle += val i += 1 client_param = sha256("u2f_tunnel".encode('utf8')).digest() app_param = sha256("u2f_tunnel".encode('utf8')).digest() request = client_param + app_param + int2byte( len(keyHandle)) + keyHandle #p1 = 0x07 if check_only else 0x03 p1 = 0x03 p2 = 0 response = self.device.send_apdu(INS_SIGN, p1, p2, request) if self.debug: print("U2F <= %s%.2x" % (hexstr(response), 0x9000)) # check replied status words of the command (within the APDU tunnel) if hexstr(response[-2:]) != "9000": raise CommException("Invalid status words received: " + hexstr(response[-2:])) # api expect a byte array, remove the appended status words return bytearray(response[:-2])
def exchange(self, apdu, timeout=TIMEOUT): if self.debug: print("U2F => %s" % hexstr(apdu)) if (len(apdu)>=256): raise CommException("Too long APDU to transport") # wrap apdu i=0 keyHandle = "" while i < len(apdu): val = apdu[i:i+1] if len(self.scrambleKey) > 0: val = chr(ord(val) ^ ord(self.scrambleKey[i % len(self.scrambleKey)])) keyHandle += val i+=1 client_param = sha256("u2f_tunnel".encode('utf8')).digest() app_param = sha256("u2f_tunnel".encode('utf8')).digest() request = client_param + app_param + int2byte(len(keyHandle)) + keyHandle #p1 = 0x07 if check_only else 0x03 p1 = 0x03 p2 = 0 response = self.device.send_apdu(INS_SIGN, p1, p2, request) if self.debug: print("U2F <= %s%.2x" % (hexstr(response), 0x9000)) # check replied status words of the command (within the APDU tunnel) if hexstr(response[-2:]) != "9000": raise CommException("Invalid status words received: " + hexstr(response[-2:])); # api expect a byte array, remove the appended status words return bytearray(response[:-2])
def call(self, cmd, data=b''): if isinstance(data, int): data = int2byte(data) self._send_req(self.cid, cmd, data) return self._read_resp(self.cid, cmd)