def test_long_values_2(self): val = [ [TLV.kTLVType_State, TLV.M3], [TLV.kTLVType_Certificate, (150 * "a" + 150 * "b").encode()], [TLV.kTLVType_Identifier, "hello".encode()], ] res = TLV.decode_bytearray(TLV.encode_list(val)) self.assertEqual(val, res)
def test_separator_list(self): val = [ [TLV.kTLVType_State, TLV.M3], TLV.kTLVType_Separator_Pair, [TLV.kTLVType_State, TLV.M4], ] res = TLV.decode_bytearray(TLV.encode_list(val)) self.assertEqual(val, res)
def test_to_string_for_dict(self): example = {1: "hello"} res = TLV.to_string(example) self.assertEqual(res, "{\n 1: (5 bytes/<class 'str'>) hello\n}\n") example = {1: "hello", 2: "world"} res = TLV.to_string(example) self.assertEqual( res, "{\n 1: (5 bytes/<class 'str'>) hello\n 2: (5 bytes/<class 'str'>) world\n}\n", )
async def post_tlv(self, target, body, expected=None): try: response = await self.post( target, TLV.encode_list(body), content_type=HttpContentTypes.TLV, ) except HttpErrorResponse as e: self.transport.close() response = e.response body = TLV.decode_bytes(response.body, expected=expected) return body
def write_http(request, expected): body = TLV.encode_list(request) logging.debug("write message: %s", TLV.to_string(TLV.decode_bytes(body))) connection.putrequest("POST", "/pair-setup", skip_accept_encoding=True) connection.putheader("Content-Type", "application/pairing+tlv8") connection.putheader("Content-Length", len(body)) connection.endheaders(body) resp = connection.getresponse() response_tlv = TLV.decode_bytes(resp.read(), expected) logging.debug("response: %s", TLV.to_string(response_tlv)) return response_tlv
def test_filter(self): example = bytes(bytearray.fromhex("060103" + "010203")) expected = [ [6, bytearray(b"\x03")], ] data = TLV.decode_bytes(example, expected=[6]) self.assertListEqual(data, expected)
def perform_pair_setup_part1( with_auth: bool = True, ) -> Generator[Tuple[List[Tuple[int, bytearray]], List[int]], None, Tuple[ bytearray, bytearray]]: """ Performs a pair setup operation as described in chapter 4.7 page 39 ff. :return: a tuple of salt and server's public key :raises UnavailableError: if the device is already paired :raises MaxTriesError: if the device received more than 100 unsuccessful pairing attempts :raises BusyError: if a parallel pairing is ongoing :raises AuthenticationError: if the verification of the device's SRP proof fails :raises MaxPeersError: if the device cannot accept an additional pairing :raises IllegalData: if the verification of the accessory's data fails """ # # Step #1 ios --> accessory (send SRP start Request) (see page 39) # logging.debug("#1 ios -> accessory: send SRP start request") request_tlv = [ (TLV.kTLVType_State, TLV.M1), (TLV.kTLVType_Method, TLV.PairSetupWithAuth if with_auth else TLV.PairSetup), ] step2_expectations = [ TLV.kTLVType_State, TLV.kTLVType_Error, TLV.kTLVType_PublicKey, TLV.kTLVType_Salt, ] response_tlv = yield (request_tlv, step2_expectations) # # Step #3 ios --> accessory (send SRP verify request) (see page 41) # logging.debug("#3 ios -> accessory: send SRP verify request") response_tlv = TLV.reorder(response_tlv, step2_expectations) assert (response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][1] == TLV.M2), "perform_pair_setup: State not M2" # the errors here can be: # * kTLVError_Unavailable: Device is paired # * kTLVError_MaxTries: More than 100 unsuccessful attempts # * kTLVError_Busy: There is already a pairing going on if response_tlv[1][0] == TLV.kTLVType_Error: error_handler(response_tlv[1][1], "step 3") assert (response_tlv[1][0] == TLV.kTLVType_PublicKey ), "perform_pair_setup: Not a public key" assert response_tlv[2][ 0] == TLV.kTLVType_Salt, "perform_pair_setup: Not a salt" return response_tlv[2][1], response_tlv[1][1]
def test_reorder_2(self): val = [ [TLV.kTLVType_State, TLV.M3], [TLV.kTLVType_Salt, (16 * "a").encode()], [TLV.kTLVType_PublicKey, (384 * "b").encode()], ] tmp = TLV.reorder(val, [TLV.kTLVType_State, TLV.kTLVType_Salt]) self.assertEqual(tmp[0][0], TLV.kTLVType_State) self.assertEqual(tmp[0][1], TLV.M3) self.assertEqual(tmp[1][0], TLV.kTLVType_Salt) self.assertEqual(tmp[1][1], (16 * "a").encode())
def test_long_values_decode_bytearray_to_list(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.assertListEqual(data, expected)
def test_to_string_for_list(self): example = [ ( 1, "hello", ), ] res = TLV.to_string(example) self.assertEqual(res, "[\n 1: (5 bytes/<class 'str'>) hello\n]\n") example = [ ( 1, "hello", ), ( 2, "world", ), ] res = TLV.to_string(example) self.assertEqual( res, "[\n 1: (5 bytes/<class 'str'>) hello\n 2: (5 bytes/<class 'str'>) world\n]\n", )
def validate_mfi(session_key, response_tlv): # If pairing method is PairSetupWithAuth there should be an EncryptedData TLV in M4 # It should have a signature and a certificate from an Apple secure co-processor. decrypted = ChaCha20Poly1305Decryptor(session_key).decrypt( bytes(), b"PS-Msg04", bytes([0, 0, 0, 0]), response_tlv[TLV.kTLVType_EncryptedData], ) if not decrypted: logger.debug( "Device returned kTLVType_EncryptedData during M4 but could not decrypt" ) return sub_tlv = TLV.decode_bytes(decrypted) if TLV.kTLVType_Signature not in sub_tlv: logger.debug( "QUIRK: M4: Device returned kTLVType_EncryptedData, but did not contain kTLVType_Signature" ) return if TLV.kTLVType_Certificate not in sub_tlv: logger.debug( "QUIRK: M4: Device returned kTLVType_Signature but not kTLVType_Certificate" ) return # Certificate appears to be X509 in DER format but with some sort of PKCS7 pre-amble. # cryptography doesn't seem to support that yet. logger.debug( "Found seemingly valid MFI kTLVType_Signature; we don't validate this yet" )
def check_convert_value(val: str, char: Characteristic) -> Any: """ Checks if the given value is of the given type or is convertible into the type. If the value is not convertible, a HomeKitTypeException is thrown. :param val: the original value :param char: the characteristic :return: the converted value :raises FormatError: if the input value could not be converted to the target type """ if char.format == CharacteristicFormats.bool: try: val = strtobool(str(val)) except ValueError: raise FormatError('"{v}" is no valid "{t}"!'.format(v=val, t=char.format)) # We have seen iPhone's sending 1 and 0 for True and False # This is in spec # It is also *required* for Ecobee Switch+ devices (as at Mar 2020) return 1 if val else 0 if char.format in NUMBER_TYPES: try: val = Decimal(val) except ValueError: raise FormatError('"{v}" is no valid "{t}"!'.format(v=val, t=char.format)) if char.minValue is not None: val = max(Decimal(char.minValue), val) if char.maxValue is not None: val = min(Decimal(char.maxValue), val) # Honeywell T6 Pro cannot handle arbritary precision, the values we send # *must* respect minStep # See https://github.com/home-assistant/core/issues/37083 if char.minStep is not None: with localcontext() as ctx: ctx.prec = 6 # Python3 uses bankers rounding by default, so 28.5 rounds to 28, not 29. # This is surprising for most people ctx.rounding = ROUND_HALF_UP val = Decimal(val) offset = Decimal( char.minValue if char.minValue is not None else 0) min_step = Decimal(char.minStep) # We use to_integral_value() here rather than round as it respsects # ctx.rounding val = offset + (( (val - offset) / min_step).to_integral_value() * min_step) if char.format in INTEGER_TYPES: val = int(val.to_integral_value()) else: val = float(val) if char.format == CharacteristicFormats.data: try: base64.decodebytes(val.encode()) except binascii.Error: raise FormatError('"{v}" is no valid "{t}"!'.format(v=val, t=char.format)) if char.format == CharacteristicFormats.tlv8: try: tmp_bytes = base64.decodebytes(val.encode()) TLV.decode_bytes(tmp_bytes) except (binascii.Error, TlvParseException): raise FormatError('"{v}" is no valid "{t}"!'.format(v=val, t=char.format)) return val
def get_session_keys( pairing_data: dict[str, str | int | list[Any]] ) -> Generator[ ( tuple[list[tuple[int, bytearray] | tuple[int, bytes]], list[int]] | tuple[list[tuple[int, bytearray]], list[int]] ), None, Callable[[str, str], bytes], ]: """ 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 = dict(response_tlv) handle_state_step(response_tlv, TLV.M2) if TLV.kTLVType_PublicKey not in response_tlv: raise InvalidError("M2: Missing public key") if TLV.kTLVType_EncryptedData not in response_tlv: raise InvalidError("M2: Missing encrypted data") # 1) generate shared secret accessorys_session_pub_key_bytes = bytes(response_tlv[TLV.kTLVType_PublicKey]) 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 session_key = hkdf_derive( shared_secret, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info" ) # 3) verify auth tag on encrypted data and 4) decrypt encrypted = response_tlv[TLV.kTLVType_EncryptedData] decrypted = ChaCha20Poly1305Decryptor(session_key).decrypt( bytes(), b"PV-Msg02", bytes([0, 0, 0, 0]), encrypted ) if type(decrypted) == bool and not decrypted: raise InvalidAuthTagError("step 3") d1 = dict(TLV.decode_bytes(decrypted)) if TLV.kTLVType_Identifier not in d1: raise InvalidError("M2: Encrypted data did not contain identifier") if TLV.kTLVType_Signature not in d1: raise InvalidError("M2: Encrypted data did not contain signature") # 5) look up pairing by accessory name accessory_name = d1[TLV.kTLVType_Identifier].decode() if pairing_data["AccessoryPairingID"] != accessory_name: raise IncorrectPairingIdError("step 3") accessory_ltpk = ed25519.Ed25519PublicKey.from_public_bytes( bytes.fromhex(pairing_data["AccessoryLTPK"]) ) # 6) verify accessory's signature accessory_sig = d1[TLV.kTLVType_Signature] accessory_session_pub_key_bytes = response_tlv[TLV.kTLVType_PublicKey] accessory_info = ( accessory_session_pub_key_bytes + accessory_name.encode() + ios_key_pub ) try: accessory_ltpk.verify(bytes(accessory_sig), bytes(accessory_info)) except cryptography_exceptions.InvalidSignature: 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.Ed25519PrivateKey.from_private_bytes( bytes.fromhex(ios_device_ltsk_h) ) # 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 = ChaCha20Poly1305Encryptor(session_key).encrypt( bytes(), b"PV-Msg03", bytes([0, 0, 0, 0]), sub_tlv ) # 11) create tlv request_tlv = [ (TLV.kTLVType_State, TLV.M3), (TLV.kTLVType_EncryptedData, encrypted_data_with_auth_tag), ] step3_expectations = [TLV.kTLVType_State, TLV.kTLVType_Error] response_tlv = yield (request_tlv, step3_expectations) # # Post Step #4 verification (page 51) # response_tlv = dict(response_tlv) handle_state_step(response_tlv, TLV.M4) # return function to calculate session keys def derive(salt, info): return hkdf_derive(shared_secret, salt, info) return derive
def perform_pair_setup_part2( pin: str, ios_pairing_id: str, salt: bytearray, server_public_key: bytearray ) -> Generator[Tuple[List[Tuple[int, bytearray]], List[int]], None, Dict[str, str]]: """ Performs a pair setup operation as described in chapter 4.7 page 39 ff. :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 :raises UnavailableError: if the device is already paired :raises MaxTriesError: if the device received more than 100 unsuccessful pairing attempts :raises BusyError: if a parallel pairing is ongoing :raises AuthenticationError: if the verification of the device's SRP proof fails :raises MaxPeersError: if the device cannot accept an additional pairing :raises IllegalData: if the verification of the accessory's data fails """ srp_client = SrpClient("Pair-Setup", pin) srp_client.set_salt(salt) srp_client.set_server_public_key(server_public_key) client_pub_key = srp_client.get_public_key() client_proof = srp_client.get_proof() response_tlv = [ (TLV.kTLVType_State, TLV.M3), (TLV.kTLVType_PublicKey, SrpClient.to_byte_array(client_pub_key)), (TLV.kTLVType_Proof, SrpClient.to_byte_array(client_proof)), ] step4_expectations = [ TLV.kTLVType_State, TLV.kTLVType_Error, TLV.kTLVType_Proof ] response_tlv = yield (response_tlv, step4_expectations) # # Step #5 ios --> accessory (Exchange Request) (see page 43) # logging.debug("#5 ios -> accessory: send SRP exchange request") # M4 Verification (page 43) response_tlv = TLV.reorder(response_tlv, step4_expectations) assert (response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][1] == TLV.M4), "perform_pair_setup: State not M4" if response_tlv[1][0] == TLV.kTLVType_Error: error_handler(response_tlv[1][1], "step 5") assert response_tlv[1][ 0] == TLV.kTLVType_Proof, "perform_pair_setup: Not a proof" if not srp_client.verify_servers_proof(response_tlv[1][1]): raise AuthenticationError("Step #5: wrong proof!") # M5 Request generation (page 44) session_key = srp_client.get_session_key() ios_device_ltsk = ed25519.Ed25519PrivateKey.generate() ios_device_ltpk = ios_device_ltsk.public_key() ios_device_public_bytes = ios_device_ltpk.public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) # reversed: # Pair-Setup-Encrypt-Salt instead of Pair-Setup-Controller-Sign-Salt # Pair-Setup-Encrypt-Info instead of Pair-Setup-Controller-Sign-Info ios_device_x = hkdf_derive( SrpClient.to_byte_array(session_key), "Pair-Setup-Controller-Sign-Salt", "Pair-Setup-Controller-Sign-Info", ) session_key = hkdf_derive( SrpClient.to_byte_array(session_key), "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info", ) ios_device_pairing_id = ios_pairing_id.encode() ios_device_info = ios_device_x + ios_device_pairing_id + ios_device_public_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_public_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) response_tlv = [ (TLV.kTLVType_State, TLV.M5), (TLV.kTLVType_EncryptedData, encrypted_data_with_auth_tag), ] step6_expectations = [ TLV.kTLVType_State, TLV.kTLVType_Error, TLV.kTLVType_EncryptedData, ] response_tlv = yield (response_tlv, step6_expectations) # # Step #7 ios (Verification) (page 47) # response_tlv = TLV.reorder(response_tlv, step6_expectations) assert (response_tlv[0][0] == TLV.kTLVType_State and response_tlv[0][1] == TLV.M6), "perform_pair_setup: State not M6" if response_tlv[1][0] == TLV.kTLVType_Error: error_handler(response_tlv[1][1], "step 7") assert (response_tlv[1][0] == TLV.kTLVType_EncryptedData ), "perform_pair_setup: No encrypted data" decrypted_data = chacha20_aead_decrypt( bytes(), session_key, "PS-Msg06".encode(), bytes([0, 0, 0, 0]), response_tlv[1][1], ) if decrypted_data is False: raise IllegalData("step 7") response_tlv = TLV.decode_bytearray(decrypted_data) response_tlv = TLV.reorder( response_tlv, [ TLV.kTLVType_Identifier, TLV.kTLVType_PublicKey, TLV.kTLVType_Signature ], ) assert (response_tlv[2][0] == TLV.kTLVType_Signature ), "perform_pair_setup: No signature" accessory_sig = response_tlv[2][1] assert (response_tlv[0][0] == TLV.kTLVType_Identifier ), "perform_pair_setup: No identifier" accessory_pairing_id = response_tlv[0][1] assert (response_tlv[1][0] == TLV.kTLVType_PublicKey ), "perform_pair_setup: No public key" accessory_ltpk = response_tlv[1][1] accessory_x = hkdf_derive( SrpClient.to_byte_array(srp_client.get_session_key()), "Pair-Setup-Accessory-Sign-Salt", "Pair-Setup-Accessory-Sign-Info", ) accessory_info = accessory_x + accessory_pairing_id + accessory_ltpk e25519s = ed25519.Ed25519PublicKey.from_public_bytes( bytes(response_tlv[1][1])) try: e25519s.verify(bytes(accessory_sig), bytes(accessory_info)) except cryptography_exceptions.InvalidSignature: raise InvalidSignatureError("step #7") ios_device_ltsk_private_bytes = ios_device_ltsk.private_bytes( encoding=serialization.Encoding.Raw, format=serialization.PrivateFormat.Raw, encryption_algorithm=serialization.NoEncryption(), ) return { "AccessoryPairingID": response_tlv[0][1].decode(), "AccessoryLTPK": hexlify(response_tlv[1][1]).decode(), "iOSPairingId": ios_pairing_id, "iOSDeviceLTSK": ios_device_ltsk_private_bytes.hex(), "iOSDeviceLTPK": ios_device_public_bytes.hex(), }
def get_session_keys( pairing_data: Dict[str, Union[str, int, List[Any]]] ) -> Generator[Union[Tuple[List[Union[Tuple[int, bytearray], Tuple[ int, bytes]]], List[int]], Tuple[List[Tuple[ int, bytearray]], List[int]], ], None, Tuple[bytes, bytes], ]: """ 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 session_key = hkdf_derive(shared_secret, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info") # 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.Ed25519PublicKey.from_public_bytes( 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 cryptography_exceptions.InvalidSignature: 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.Ed25519PrivateKey.from_private_bytes( bytes.fromhex(ios_device_ltsk_h)) # 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) # 11) create tlv request_tlv = [ (TLV.kTLVType_State, TLV.M3), (TLV.kTLVType_EncryptedData, encrypted_data_with_auth_tag), ] 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 controller_to_accessory_key = hkdf_derive(shared_secret, "Control-Salt", "Control-Write-Encryption-Key") accessory_to_controller_key = hkdf_derive(shared_secret, "Control-Salt", "Control-Read-Encryption-Key") return controller_to_accessory_key, accessory_to_controller_key
def test_to_string_for_list_bytearray(self): example = [[1, bytearray([0x42, 0x23])]] res = TLV.to_string(example) self.assertEqual(res, "[\n 1: (2 bytes/<class 'bytearray'>) 0x4223\n]\n")
def test_to_string_for_dict_bytearray(self): example = {1: bytearray([0x42, 0x23])} res = TLV.to_string(example) self.assertEqual(res, "{\n 1: (2 bytes/<class 'bytearray'>) 0x4223\n}\n")