def test_example1(self): example_1 = bytearray.fromhex('060103010568656c6c6f') dict_1_1 = TLV.decode_bytearray(example_1) bytearray_1 = TLV.encode_dict(dict_1_1) dict_1_2 = TLV.decode_bytearray(bytearray_1) self.assertEqual(dict_1_1, dict_1_2)
def test_example2(self): example_2 = bytearray.fromhex('060103' + ('09FF' + 255 * '61' + '092D' + 45 * '61') + '010568656c6c6f') dict_2_1 = TLV.decode_bytearray(example_2) bytearray_2 = TLV.encode_dict(dict_2_1) dict_2_2 = TLV.decode_bytearray(bytearray_2) self.assertEqual(dict_2_1, dict_2_2)
def test_long_values_decode_bytearray(self): example = bytearray.fromhex('060103' + ('09FF' + 255 * '61' + '092D' + 45 * '61') + '010568656c6c6f') expected = { 6: bytearray(b'\x03'), 9: bytearray(300 * b'a'), 1: bytearray(b'hello') } data = TLV.decode_bytearray(example) self.assertDictEqual(data, expected)
def _post_pair_setup(self): d_req = TLV.decode_bytes(self.body) self.log_message('POST /pair-setup request body:\n%s', TLV.to_string(d_req)) d_res = {} if d_req[TLV.kTLVType_State] == TLV.M1: # step #2 Accessory -> iOS Device SRP Start Response self.log_message('Step #2 /pair-setup') # 1) Check if paired if self.server.data.is_paired: self.send_error_reply(TLV.M2, TLV.kTLVError_Unavailable) return # 2) Check if over 100 attempts if self.server.data.unsuccessful_tries > 100: self.log_error('to many failed attempts') self.send_error_reply(TLV.M2, TLV.kTLVError_MaxTries) return # 3) Check if already in pairing if False: self.send_error_reply(TLV.M2, TLV.kTLVError_Busy) return # 4) 5) 7) Create in SRP Session, set username and password server = SrpServer('Pair-Setup', self.server.data.setup_code) # 6) create salt salt = server.get_salt() # 8) show setup code to user sc = self.server.data.setup_code sc_str = 'Setup Code\n┌─' + '─' * len(sc) + '─┐\n│ ' + sc + ' │\n└─' + '─' * len(sc) + '─┘' self.log_message(sc_str) # 9) create public key public_key = server.get_public_key() # 10) create response tlv and send response d_res[TLV.kTLVType_State] = TLV.M2 d_res[TLV.kTLVType_PublicKey] = SrpServer.to_byte_array(public_key) d_res[TLV.kTLVType_Salt] = SrpServer.to_byte_array(salt) self._send_response_tlv(d_res) # store session self.server.sessions[self.session_id]['srp'] = server self.log_message('after step #2:\n%s', TLV.to_string(d_res)) return if d_req[TLV.kTLVType_State] == TLV.M3: # step #4 Accessory -> iOS Device SRP Verify Response self.log_message('Step #4 /pair-setup') # 1) use ios pub key to compute shared secret key ios_pub_key = bytes_to_mpz(d_req[TLV.kTLVType_PublicKey]) server = self.server.sessions[self.session_id]['srp'] server.set_client_public_key(ios_pub_key) hkdf_inst = hkdf.Hkdf('Pair-Setup-Encrypt-Salt'.encode(), SrpServer.to_byte_array(server.get_session_key()), hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Setup-Encrypt-Info'.encode(), 32) self.server.sessions[self.session_id]['session_key'] = session_key # 2) verify ios proof ios_proof = bytes_to_mpz(d_req[TLV.kTLVType_Proof]) if not server.verify_clients_proof(ios_proof): d_res[TLV.kTLVType_State] = TLV.M4 d_res[TLV.kTLVType_Error] = TLV.kTLVError_Authentication self._send_response_tlv(d_res) print('error in step #4', d_res, self.server.sessions) return else: self.log_message('ios proof was verified') # 3) generate accessory proof accessory_proof = server.get_proof(ios_proof) # 4) create response tlv d_res[TLV.kTLVType_State] = TLV.M4 d_res[TLV.kTLVType_Proof] = SrpServer.to_byte_array(accessory_proof) # 5) send response tlv self._send_response_tlv(d_res) self.log_message('after step #4:\n%s', TLV.to_string(d_res)) return if d_req[TLV.kTLVType_State] == TLV.M5: # step #6 Accessory -> iOS Device Exchange Response self.log_message('Step #6 /pair-setup') # 1) Verify the iOS device's authTag # done by chacha20_aead_decrypt # 2) decrypt and test encrypted_data = d_req[TLV.kTLVType_EncryptedData] decrypted_data = chacha20_aead_decrypt(bytes(), self.server.sessions[self.session_id]['session_key'], 'PS-Msg05'.encode(), bytes([0, 0, 0, 0]), encrypted_data) if decrypted_data == False: d_res[TLV.kTLVType_State] = TLV.M6 d_res[TLV.kTLVType_Error] = TLV.kTLVError_Authentication self.send_error_reply(TLV.M6, TLV.kTLVError_Authentication) print('error in step #6', d_res, self.server.sessions) return d_req_2 = TLV.decode_bytearray(decrypted_data) # 3) Derive ios_device_x shared_secret = self.server.sessions[self.session_id]['srp'].get_session_key() hkdf_inst = hkdf.Hkdf('Pair-Setup-Controller-Sign-Salt'.encode(), SrpServer.to_byte_array(shared_secret), hash=hashlib.sha512) ios_device_x = hkdf_inst.expand('Pair-Setup-Controller-Sign-Info'.encode(), 32) # 4) construct ios_device_info ios_device_pairing_id = d_req_2[TLV.kTLVType_Identifier] ios_device_ltpk = d_req_2[TLV.kTLVType_PublicKey] ios_device_info = ios_device_x + ios_device_pairing_id + ios_device_ltpk # 5) verify signature ios_device_sig = d_req_2[TLV.kTLVType_Signature] verify_key = py25519.Key25519(pubkey=bytes(), verifyingkey=bytes(ios_device_ltpk)) if not verify_key.verify(bytes(ios_device_sig), bytes(ios_device_info)): self.send_error_reply(TLV.M6, TLV.kTLVError_Authentication) print('error in step #6', d_res, self.server.sessions) return # 6) save ios_device_pairing_id and ios_device_ltpk self.server.data.add_peer(ios_device_pairing_id, ios_device_ltpk) # Response Generation # 1) generate accessoryLTPK if not existing if self.server.data.accessory_ltsk is None or self.server.data.accessory_ltpk is None: accessory_ltsk = py25519.Key25519() accessory_ltpk = accessory_ltsk.verifyingkey self.server.data.set_accessory_keys(accessory_ltpk, accessory_ltsk.secretkey) else: accessory_ltsk = py25519.Key25519(self.server.data.accessory_ltsk) accessory_ltpk = accessory_ltsk.verifyingkey # 2) derive AccessoryX hkdf_inst = hkdf.Hkdf('Pair-Setup-Accessory-Sign-Salt'.encode(), SrpServer.to_byte_array(shared_secret), hash=hashlib.sha512) accessory_x = hkdf_inst.expand('Pair-Setup-Accessory-Sign-Info'.encode(), 32) # 3) accessory_info = accessory_x + self.server.data.accessory_pairing_id_bytes + accessory_ltpk # 4) generate signature accessory_signature = accessory_ltsk.sign(accessory_info) # 5) construct sub_tlv sub_tlv = { TLV.kTLVType_Identifier: self.server.data.accessory_pairing_id_bytes, TLV.kTLVType_PublicKey: accessory_ltpk, TLV.kTLVType_Signature: accessory_signature } sub_tlv_b = TLV.encode_dict(sub_tlv) # 6) encrypt sub_tlv encrypted_data_with_auth_tag = chacha20_aead_encrypt(bytes(), self.server.sessions[self.session_id]['session_key'], 'PS-Msg06'.encode(), bytes([0, 0, 0, 0]), sub_tlv_b) tmp = bytearray(encrypted_data_with_auth_tag[0]) tmp += encrypted_data_with_auth_tag[1] # 7) send response self.server.publish_device() d_res = dict() d_res[TLV.kTLVType_State] = TLV.M6 d_res[TLV.kTLVType_EncryptedData] = tmp self._send_response_tlv(d_res) self.log_message('after step #6:\n%s', TLV.to_string(d_res)) return self.send_error(HttpStatusCodes.METHOD_NOT_ALLOWED)
def perform_pair_setup(connection, pin, ios_pairing_id): """ Performs a pair setup operation as described in chapter 4.7 page 39 ff. :param connection: the http connection to the target accessory :param pin: the setup code from the accessory :param ios_pairing_id: the id of the simulated ios device :return: a dict with the ios device's part of the pairing information """ headers = {'Content-Type': 'application/pairing+tlv8'} # # Step #1 ios --> accessory (send SRP start Request) (see page 39) # request_tlv = TLV.encode_list([(TLV.kTLVType_State, TLV.M1), (TLV.kTLVType_Method, TLV.PairSetup)]) connection.request('POST', '/pair-setup', request_tlv, headers) resp = connection.getresponse() response_tlv = TLV.decode_bytes(resp.read()) # # Step #3 ios --> accessory (send SRP verify request) (see page 41) # assert TLV.kTLVType_State in response_tlv, response_tlv assert response_tlv[TLV.kTLVType_State] == TLV.M2 if TLV.kTLVType_Error in response_tlv: error_handler(response_tlv[TLV.kTLVType_Error], "step 3") srp_client = SrpClient('Pair-Setup', pin) srp_client.set_salt(response_tlv[TLV.kTLVType_Salt]) srp_client.set_server_public_key(response_tlv[TLV.kTLVType_PublicKey]) client_pub_key = srp_client.get_public_key() client_proof = srp_client.get_proof() response_tlv = TLV.encode_list([ (TLV.kTLVType_State, TLV.M3), (TLV.kTLVType_PublicKey, SrpClient.to_byte_array(client_pub_key)), (TLV.kTLVType_Proof, SrpClient.to_byte_array(client_proof)), ]) connection.request('POST', '/pair-setup', response_tlv, headers) resp = connection.getresponse() response_tlv = TLV.decode_bytes(resp.read()) # # Step #5 ios --> accessory (Exchange Request) (see page 43) # # M4 Verification (page 43) assert TLV.kTLVType_State in response_tlv, response_tlv assert response_tlv[TLV.kTLVType_State] == TLV.M4 if TLV.kTLVType_Error in response_tlv: error_handler(response_tlv[TLV.kTLVType_Error], "step 5") assert TLV.kTLVType_Proof in response_tlv if not srp_client.verify_servers_proof(response_tlv[TLV.kTLVType_Proof]): print('Step #5: wrong proof!') # M5 Request generation (page 44) session_key = srp_client.get_session_key() ios_device_ltsk, ios_device_ltpk = ed25519.create_keypair() # reversed: # Pair-Setup-Encrypt-Salt instead of Pair-Setup-Controller-Sign-Salt # Pair-Setup-Encrypt-Info instead of Pair-Setup-Controller-Sign-Info hkdf_inst = hkdf.Hkdf('Pair-Setup-Controller-Sign-Salt'.encode(), SrpClient.to_byte_array(session_key), hash=hashlib.sha512) ios_device_x = hkdf_inst.expand('Pair-Setup-Controller-Sign-Info'.encode(), 32) hkdf_inst = hkdf.Hkdf('Pair-Setup-Encrypt-Salt'.encode(), SrpClient.to_byte_array(session_key), hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Setup-Encrypt-Info'.encode(), 32) ios_device_pairing_id = ios_pairing_id.encode() ios_device_info = ios_device_x + ios_device_pairing_id + ios_device_ltpk.to_bytes( ) ios_device_signature = ios_device_ltsk.sign(ios_device_info) sub_tlv = [(TLV.kTLVType_Identifier, ios_device_pairing_id), (TLV.kTLVType_PublicKey, ios_device_ltpk.to_bytes()), (TLV.kTLVType_Signature, ios_device_signature)] sub_tlv_b = TLV.encode_list(sub_tlv) # taking tge iOSDeviceX as key was reversed from # https://github.com/KhaosT/HAP-NodeJS/blob/2ea9d761d9bd7593dd1949fec621ab085af5e567/lib/HAPServer.js # function handlePairStepFive calling encryption.encryptAndSeal encrypted_data_with_auth_tag = chacha20_aead_encrypt( bytes(), session_key, 'PS-Msg05'.encode(), bytes([0, 0, 0, 0]), sub_tlv_b) tmp = bytearray(encrypted_data_with_auth_tag[0]) tmp += encrypted_data_with_auth_tag[1] response_tlv = [(TLV.kTLVType_State, TLV.M5), (TLV.kTLVType_EncryptedData, tmp)] body = TLV.encode_list(response_tlv) connection.request('POST', '/pair-setup', body, headers) resp = connection.getresponse() response_tlv = TLV.decode_bytes(resp.read()) # # Step #7 ios (Verification) (page 47) # assert response_tlv[TLV.kTLVType_State] == TLV.M6 if TLV.kTLVType_Error in response_tlv: error_handler(response_tlv[TLV.kTLVType_Error], "step 7") assert TLV.kTLVType_EncryptedData in response_tlv decrypted_data = chacha20_aead_decrypt( bytes(), session_key, 'PS-Msg06'.encode(), bytes([0, 0, 0, 0]), response_tlv[TLV.kTLVType_EncryptedData]) if decrypted_data == False: raise homekit.exception.IllegalData("step 7") response_tlv = TLV.decode_bytearray(decrypted_data) assert TLV.kTLVType_Signature in response_tlv accessory_sig = response_tlv[TLV.kTLVType_Signature] assert TLV.kTLVType_Identifier in response_tlv accessory_pairing_id = response_tlv[TLV.kTLVType_Identifier] assert TLV.kTLVType_PublicKey in response_tlv accessory_ltpk = response_tlv[TLV.kTLVType_PublicKey] hkdf_inst = hkdf.Hkdf('Pair-Setup-Accessory-Sign-Salt'.encode(), SrpClient.to_byte_array( srp_client.get_session_key()), hash=hashlib.sha512) accessory_x = hkdf_inst.expand('Pair-Setup-Accessory-Sign-Info'.encode(), 32) accessory_info = accessory_x + accessory_pairing_id + accessory_ltpk e25519s = ed25519.VerifyingKey(bytes(response_tlv[TLV.kTLVType_PublicKey])) e25519s.verify(bytes(accessory_sig), bytes(accessory_info)) return { 'AccessoryPairingID': response_tlv[TLV.kTLVType_Identifier].decode(), 'AccessoryLTPK': hexlify(response_tlv[TLV.kTLVType_PublicKey]).decode(), 'iOSPairingId': ios_pairing_id, 'iOSDeviceLTSK': ios_device_ltsk.to_ascii(encoding='hex').decode(), 'iOSDeviceLTPK': hexlify(ios_device_ltpk.to_bytes()).decode() }