def get_session_keys(pairing_data): """ HomeKit Controller state machine to perform a pair verify operation as described in chapter 4.8 page 47 ff. :param pairing_data: the paring data as returned by perform_pair_setup :return: tuple of the session keys (controller_to_accessory_key and accessory_to_controller_key) :raises InvalidAuthTagError: if the auth tag could not be verified, :raises IncorrectPairingIdError: if the accessory's LTPK could not be found :raises InvalidSignatureError: if the accessory's signature could not be verified :raises AuthenticationError: if the secured session could not be established """ # # Step #1 ios --> accessory (send verify start Request) (page 47) # ios_key = x25519.X25519PrivateKey.generate() ios_key_pub = ios_key.public_key().public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) request_tlv = [(TLV.kTLVType_State, TLV.M1), (TLV.kTLVType_PublicKey, ios_key_pub)] step2_expectations = [ TLV.kTLVType_State, TLV.kTLVType_PublicKey, TLV.kTLVType_EncryptedData ] response_tlv = yield (request_tlv, step2_expectations) # # Step #3 ios --> accessory (send SRP verify request) (page 49) # response_tlv = TLV.reorder(response_tlv, step2_expectations) assert response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][ 1] == TLV.M2, 'get_session_keys: not M2' assert response_tlv[1][ 0] == TLV.kTLVType_PublicKey, 'get_session_keys: no public key' assert response_tlv[2][ 0] == TLV.kTLVType_EncryptedData, 'get_session_keys: no encrypted data' # 1) generate shared secret accessorys_session_pub_key_bytes = bytes(response_tlv[1][1]) accessorys_session_pub_key = x25519.X25519PublicKey.from_public_bytes( accessorys_session_pub_key_bytes) shared_secret = ios_key.exchange(accessorys_session_pub_key) # 2) derive session key hkdf_inst = hkdf.Hkdf('Pair-Verify-Encrypt-Salt'.encode(), shared_secret, hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Verify-Encrypt-Info'.encode(), 32) # 3) verify auth tag on encrypted data and 4) decrypt encrypted = response_tlv[2][1] decrypted = chacha20_aead_decrypt(bytes(), session_key, 'PV-Msg02'.encode(), bytes([0, 0, 0, 0]), encrypted) if type(decrypted) == bool and not decrypted: raise InvalidAuthTagError('step 3') d1 = TLV.decode_bytes(decrypted) d1 = TLV.reorder(d1, [TLV.kTLVType_Identifier, TLV.kTLVType_Signature]) assert d1[0][ 0] == TLV.kTLVType_Identifier, 'get_session_keys: no identifier' assert d1[1][0] == TLV.kTLVType_Signature, 'get_session_keys: no signature' # 5) look up pairing by accessory name accessory_name = d1[0][1].decode() if pairing_data['AccessoryPairingID'] != accessory_name: raise IncorrectPairingIdError('step 3') accessory_ltpk = ed25519.VerifyingKey( bytes.fromhex(pairing_data['AccessoryLTPK'])) # 6) verify accessory's signature accessory_sig = d1[1][1] accessory_session_pub_key_bytes = response_tlv[1][1] accessory_info = accessory_session_pub_key_bytes + accessory_name.encode( ) + ios_key_pub try: accessory_ltpk.verify(bytes(accessory_sig), bytes(accessory_info)) except ed25519.BadSignatureError: raise InvalidSignatureError('step 3') # 7) create iOSDeviceInfo ios_device_info = ios_key_pub + pairing_data['iOSPairingId'].encode( ) + accessorys_session_pub_key_bytes # 8) sign iOSDeviceInfo with long term secret key ios_device_ltsk_h = pairing_data['iOSDeviceLTSK'] ios_device_ltpk_h = pairing_data['iOSDeviceLTPK'] ios_device_ltsk = ed25519.SigningKey( bytes.fromhex(ios_device_ltsk_h) + bytes.fromhex(ios_device_ltpk_h)) ios_device_signature = ios_device_ltsk.sign(ios_device_info) # 9) construct sub tlv sub_tlv = TLV.encode_list([(TLV.kTLVType_Identifier, pairing_data['iOSPairingId'].encode()), (TLV.kTLVType_Signature, ios_device_signature)]) # 10) encrypt and sign encrypted_data_with_auth_tag = chacha20_aead_encrypt( bytes(), session_key, 'PV-Msg03'.encode(), bytes([0, 0, 0, 0]), sub_tlv) tmp = bytearray(encrypted_data_with_auth_tag[0]) tmp += encrypted_data_with_auth_tag[1] # 11) create tlv request_tlv = [(TLV.kTLVType_State, TLV.M3), (TLV.kTLVType_EncryptedData, tmp)] step3_expectations = [TLV.kTLVType_State, TLV.kTLVType_Error] response_tlv = yield (request_tlv, step3_expectations) # # Post Step #4 verification (page 51) # response_tlv = TLV.reorder(response_tlv, step3_expectations) assert response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][ 1] == TLV.M4, 'get_session_keys: not M4' if len(response_tlv) == 2 and response_tlv[1][0] == TLV.kTLVType_Error: error_handler(response_tlv[1][1], 'verification') # calculate session keys hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) controller_to_accessory_key = hkdf_inst.expand( 'Control-Write-Encryption-Key'.encode(), 32) hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) accessory_to_controller_key = hkdf_inst.expand( 'Control-Read-Encryption-Key'.encode(), 32) return controller_to_accessory_key, accessory_to_controller_key
def get_session_keys(conn, pairing_data): """ HomeKit Controller side call to perform a pair verify operation as described in chapter 4.8 page 47 ff. :param conn: the http_impl connection to the target accessory :param pairing_data: the paring data as returned by perform_pair_setup :return: tuple of the session keys (controller_to_accessory_key and accessory_to_controller_key) :raises InvalidAuthTagError: if the auth tag could not be verified, :raises IncorrectPairingIdError: if the accessory's LTPK could not be found :raises InvalidSignatureError: if the accessory's signature could not be verified :raises AuthenticationError: if the secured session could not be established """ headers = {'Content-Type': 'application/pairing+tlv8'} # # Step #1 ios --> accessory (send verify start Request) (page 47) # ios_key = py25519.Key25519() request_tlv = TLV.encode_list([(TLV.kTLVType_State, TLV.M1), (TLV.kTLVType_PublicKey, ios_key.pubkey)]) conn.request('POST', '/pair-verify', request_tlv, headers) resp = conn.getresponse() response_tlv = TLV.decode_bytes(resp.read()) # # Step #3 ios --> accessory (send SRP verify request) (page 49) # response_tlv = TLV.reorder(response_tlv, [ TLV.kTLVType_State, TLV.kTLVType_PublicKey, TLV.kTLVType_EncryptedData ]) assert response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][ 1] == TLV.M2, 'get_session_keys: not M2' assert response_tlv[1][ 0] == TLV.kTLVType_PublicKey, 'get_session_keys: no public key' assert response_tlv[2][ 0] == TLV.kTLVType_EncryptedData, 'get_session_keys: no encrypted data' # 1) generate shared secret accessorys_session_pub_key_bytes = response_tlv[1][1] shared_secret = ios_key.get_ecdh_key( py25519.Key25519(pubkey=bytes(accessorys_session_pub_key_bytes), verifyingkey=bytes())) # 2) derive session key hkdf_inst = hkdf.Hkdf('Pair-Verify-Encrypt-Salt'.encode(), shared_secret, hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Verify-Encrypt-Info'.encode(), 32) # 3) verify auth tag on encrypted data and 4) decrypt encrypted = response_tlv[2][1] decrypted = chacha20_aead_decrypt(bytes(), session_key, 'PV-Msg02'.encode(), bytes([0, 0, 0, 0]), encrypted) if type(decrypted) == bool and not decrypted: raise InvalidAuthTagError('step 3') d1 = TLV.decode_bytes(decrypted) d1 = TLV.reorder(d1, [TLV.kTLVType_Identifier, TLV.kTLVType_Signature]) assert d1[0][ 0] == TLV.kTLVType_Identifier, 'get_session_keys: no identifier' assert d1[1][0] == TLV.kTLVType_Signature, 'get_session_keys: no signature' # 5) look up pairing by accessory name accessory_name = d1[0][1].decode() if pairing_data['AccessoryPairingID'] != accessory_name: raise IncorrectPairingIdError('step 3') accessory_ltpk = py25519.Key25519(pubkey=bytes(), verifyingkey=bytes.fromhex( pairing_data['AccessoryLTPK'])) # 6) verify accessory's signature accessory_sig = d1[1][1] accessory_session_pub_key_bytes = response_tlv[1][1] accessory_info = accessory_session_pub_key_bytes + accessory_name.encode( ) + ios_key.pubkey if not accessory_ltpk.verify(bytes(accessory_sig), bytes(accessory_info)): raise InvalidSignatureError('step 3') # 7) create iOSDeviceInfo ios_device_info = ios_key.pubkey + pairing_data['iOSPairingId'].encode( ) + accessorys_session_pub_key_bytes # 8) sign iOSDeviceInfo with long term secret key ios_device_ltsk_h = pairing_data['iOSDeviceLTSK'] ios_device_ltsk = py25519.Key25519( secretkey=bytes.fromhex(ios_device_ltsk_h)) ios_device_signature = ios_device_ltsk.sign(ios_device_info) # 9) construct sub tlv sub_tlv = TLV.encode_list([(TLV.kTLVType_Identifier, pairing_data['iOSPairingId'].encode()), (TLV.kTLVType_Signature, ios_device_signature)]) # 10) encrypt and sign encrypted_data_with_auth_tag = chacha20_aead_encrypt( bytes(), session_key, 'PV-Msg03'.encode(), bytes([0, 0, 0, 0]), sub_tlv) tmp = bytearray(encrypted_data_with_auth_tag[0]) tmp += encrypted_data_with_auth_tag[1] # 11) create tlv request_tlv = TLV.encode_list([(TLV.kTLVType_State, TLV.M3), (TLV.kTLVType_EncryptedData, tmp)]) # 12) send to accessory conn.request('POST', '/pair-verify', request_tlv, headers) resp = conn.getresponse() response_tlv = TLV.decode_bytes(resp.read()) # # Post Step #4 verification (page 51) # response_tlv = TLV.reorder(response_tlv, [TLV.kTLVType_State, TLV.kTLVType_Error]) assert response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][ 1] == TLV.M4, 'get_session_keys: not M4' if len(response_tlv) == 2 and response_tlv[1][0] == TLV.kTLVType_Error: error_handler(response_tlv[1][1], 'verification') # calculate session keys hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) controller_to_accessory_key = hkdf_inst.expand( 'Control-Write-Encryption-Key'.encode(), 32) hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) accessory_to_controller_key = hkdf_inst.expand( 'Control-Read-Encryption-Key'.encode(), 32) return controller_to_accessory_key, accessory_to_controller_key