Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions homekit/controller/ble_impl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,18 @@ def __init__(self, pairing_data, adapter):
# TODO Have exception here
sys.exit(-1)

write_fun = create_ble_pair_setup_write(pair_verify_char, pair_verify_char_info['iid'])
self.c2a_key, self.a2c_key = get_session_keys(None, self.pairing_data, write_fun)
write_fun = create_ble_pair_verify_write(pair_verify_char, pair_verify_char_info['iid'])
state_machine = get_session_keys(self.pairing_data)

request, expected = state_machine.send(None)
while True:
try:
response = write_fun(request, expected)
request, expected = state_machine.send(response)
except StopIteration as result:
self.c2a_key, self.a2c_key = result.value
break

logger.debug('pair_verified, keys: \n\t\tc2a: %s\n\t\ta2c: %s', self.c2a_key.hex(), self.a2c_key.hex())

self.c2a_counter = 0
Expand Down Expand Up @@ -644,6 +654,17 @@ def write(request, expected):
return write


def create_ble_pair_verify_write(characteristic, characteristic_id):
# This is a temporary wrapper until the pairing functions are also migrated to
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what needs to be done to handle the temporary?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this PR is merged I will raise a similar one for the perform_pair_setup_part1 and perform_pair_setup_part2 functions. At that point this wrapper will go away. It's a fairly mechanical change - if this is merged tomorrow the i will raise the follow up PR Sun evening or Mon morning.

(It's just because the transport is calling TLV.encode_list now, rather than the state machine doing it, and the BLE code path uses the same function for pair_verify and pair_setup. So using the state machine approach for both of them will mean i can get rid of the temporary function, if that makes sense).

# the new state machine approach
pair_setup_write = create_ble_pair_setup_write(characteristic, characteristic_id)

def write(request, expected):
return pair_setup_write(TLV.encode_list(request), expected)

return write


class ResolvingManager(DeviceManager):
"""
DeviceManager implementation that stops running after a given device was discovered.
Expand Down
48 changes: 31 additions & 17 deletions homekit/controller/ip_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,38 +415,52 @@ def __init__(self, pairing_data):
"""
logging.debug('init session')
connected = False
self.pairing_data = pairing_data

if 'AccessoryIP' in pairing_data and 'AccessoryPort' in pairing_data:
# if it is known, try it
accessory_ip = pairing_data['AccessoryIP']
accessory_port = pairing_data['AccessoryPort']
conn = HomeKitHTTPConnection(accessory_ip, port=accessory_port)
try:
conn.connect()
write_fun = create_ip_pair_verify_write(conn)
c2a_key, a2c_key = get_session_keys(conn, pairing_data, write_fun)
connected = True
except Exception:
connected = False

connected = self._connect(accessory_ip, accessory_port)

if not connected:
# no connection yet, so ip / port might have changed and we need to fall back to slow zeroconf lookup
device_id = pairing_data['AccessoryPairingID']
connection_data = find_device_ip_and_port(device_id)
if connection_data is None:
raise AccessoryNotFoundError(
'Device {id} not found'.format(id=pairing_data['AccessoryPairingID']))
conn = HomeKitHTTPConnection(connection_data['ip'], port=connection_data['port'])
pairing_data['AccessoryIP'] = connection_data['ip']
pairing_data['AccessoryPort'] = connection_data['port']
write_fun = create_ip_pair_verify_write(conn)
c2a_key, a2c_key = get_session_keys(conn, pairing_data, write_fun)

if not self._connect(connection_data['ip'], connection_data['port']):
return

logging.debug('session established')
self.sock = conn.sock
self.c2a_key = c2a_key
self.a2c_key = a2c_key
self.pairing_data = pairing_data

self.sec_http = SecureHttp(self)

def _connect(self, accessory_ip, accessory_port):
conn = HomeKitHTTPConnection(accessory_ip, port=accessory_port)
try:
conn.connect()
write_fun = create_ip_pair_verify_write(conn)

state_machine = get_session_keys(self.pairing_data)

request, expected = state_machine.send(None)
while True:
try:
response = write_fun(request, expected)
request, expected = state_machine.send(response)
except StopIteration as result:
self.c2a_key, self.a2c_key = result.value
self.sock = conn.sock
return True
except Exception:
logging.exception("Failed to connect to accessory")

return False

def close(self):
"""
Close the session. This closes the socket.
Expand Down
37 changes: 12 additions & 25 deletions homekit/protocol/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,12 @@ def write_http(request, expected):

def create_ip_pair_verify_write(connection):
def write_http(request, expected):
logging.debug('write message: %s', TLV.to_string(TLV.decode_bytes(request)))
body = TLV.encode_list(request)
logging.debug('write message: %s', TLV.to_string(TLV.decode_bytes(body)))
connection.putrequest('POST', '/pair-verify', skip_accept_encoding=True)
connection.putheader('Content-Type', 'application/pairing+tlv8')
connection.putheader('Content-Length', len(request))
connection.endheaders(request)
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))
Expand Down Expand Up @@ -272,23 +273,16 @@ def perform_pair_setup_part2(pin, ios_pairing_id, write_fun, salt, server_public
}


def get_session_keys(conn, pairing_data, write_fun):
def get_session_keys(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
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
:param write_fun: a function that takes a bytes representation of a TLV, the expected keys as list and returns
decoded TLV as list
: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)
Expand All @@ -299,16 +293,13 @@ def get_session_keys(conn, pairing_data, write_fun):
format=serialization.PublicFormat.Raw
)

request_tlv = TLV.encode_list([
request_tlv = [
(TLV.kTLVType_State, TLV.M1),
(TLV.kTLVType_PublicKey, ios_key_pub)
])
]

# conn.request('POST', '/pair-verify', request_tlv, headers)
# resp = conn.getresponse()
# response_tlv = TLV.decode_bytes(resp.read())
step2_expectations = [TLV.kTLVType_State, TLV.kTLVType_PublicKey, TLV.kTLVType_EncryptedData]
response_tlv = write_fun(request_tlv, step2_expectations)
response_tlv = yield (request_tlv, step2_expectations)

#
# Step #3 ios --> accessory (send SRP verify request) (page 49)
Expand Down Expand Up @@ -379,17 +370,13 @@ def get_session_keys(conn, pairing_data, write_fun):
tmp += encrypted_data_with_auth_tag[1]

# 11) create tlv
request_tlv = TLV.encode_list([
request_tlv = [
(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())
step3_expectations = [TLV.kTLVType_State, TLV.kTLVType_Error]
response_tlv = write_fun(request_tlv, step3_expectations)
response_tlv = yield (request_tlv, step3_expectations)

#
# Post Step #4 verification (page 51)
Expand Down