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 _post_pair_verify(self): d_req = TLV.decode_bytes(self.body) d_res = {} if d_req[TLV.kTLVType_State] == TLV.M1: # step #2 Accessory -> iOS Device Verify Start Response if HomeKitRequestHandler.DEBUG_PAIR_VERIFY: self.log_message('Step #2 /pair-verify') # 1) generate new curve25519 key pair accessory_session_key = py25519.Key25519() accessory_spk = accessory_session_key.public_key().pubkey self.server.sessions[self.session_id]['accessory_pub_key'] = accessory_spk # 2) generate shared secret ios_device_curve25519_pub_key_bytes = d_req[TLV.kTLVType_PublicKey] self.server.sessions[self.session_id]['ios_device_pub_key'] = ios_device_curve25519_pub_key_bytes ios_device_curve25519_pub_key = py25519.Key25519(pubkey=bytes(ios_device_curve25519_pub_key_bytes), verifyingkey=bytes()) shared_secret = accessory_session_key.get_ecdh_key(ios_device_curve25519_pub_key) self.server.sessions[self.session_id]['shared_secret'] = shared_secret # 3) generate accessory info accessory_info = accessory_spk + self.server.data.accessory_pairing_id_bytes + \ ios_device_curve25519_pub_key_bytes # 4) sign accessory info for accessory signature accessory_ltsk = py25519.Key25519(secretkey=self.server.data.accessory_ltsk) accessory_signature = accessory_ltsk.sign(accessory_info) # 5) sub tlv sub_tlv = { TLV.kTLVType_Identifier: self.server.data.accessory_pairing_id_bytes, TLV.kTLVType_Signature: accessory_signature } sub_tlv_b = TLV.encode_dict(sub_tlv) # 6) 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) self.server.sessions[self.session_id]['session_key'] = session_key # 7) encrypt sub tlv encrypted_data_with_auth_tag = chacha20_aead_encrypt(bytes(), session_key, 'PV-Msg02'.encode(), bytes([0, 0, 0, 0]), sub_tlv_b) tmp = bytearray(encrypted_data_with_auth_tag[0]) tmp += encrypted_data_with_auth_tag[1] # 8) construct result tlv d_res[TLV.kTLVType_State] = TLV.M2 d_res[TLV.kTLVType_PublicKey] = accessory_spk d_res[TLV.kTLVType_EncryptedData] = tmp self._send_response_tlv(d_res) if HomeKitRequestHandler.DEBUG_PAIR_VERIFY: 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 Verify Finish Response if HomeKitRequestHandler.DEBUG_PAIR_VERIFY: self.log_message('Step #4 /pair-verify') session_key = self.server.sessions[self.session_id]['session_key'] # 1) verify ios' authtag # 2) decrypt encrypted = d_req[TLV.kTLVType_EncryptedData] decrypted = chacha20_aead_decrypt(bytes(), session_key, 'PV-Msg03'.encode(), bytes([0, 0, 0, 0]), encrypted) if decrypted == False: self.send_error_reply(TLV.M4, TLV.kTLVError_Authentication) print('error in step #4: authtag', d_res, self.server.sessions) return d1 = TLV.decode_bytes(decrypted) assert TLV.kTLVType_Identifier in d1 assert TLV.kTLVType_Signature in d1 # 3) get ios_device_ltpk ios_device_pairing_id = d1[TLV.kTLVType_Identifier] self.server.sessions[self.session_id]['ios_device_pairing_id'] = ios_device_pairing_id ios_device_ltpk_bytes = self.server.data.get_peer_key(ios_device_pairing_id) if ios_device_ltpk_bytes is None: self.send_error_reply(TLV.M4, TLV.kTLVError_Authentication) print('error in step #4: not paired', d_res, self.server.sessions) return ios_device_ltpk = py25519.Key25519(pubkey=bytes(), verifyingkey=ios_device_ltpk_bytes) # 4) verify ios_device_info ios_device_sig = d1[TLV.kTLVType_Signature] ios_device_curve25519_pub_key_bytes = self.server.sessions[self.session_id]['ios_device_pub_key'] accessory_spk = self.server.sessions[self.session_id]['accessory_pub_key'] ios_device_info = ios_device_curve25519_pub_key_bytes + ios_device_pairing_id + accessory_spk if not ios_device_ltpk.verify(bytes(ios_device_sig), bytes(ios_device_info)): self.send_error_reply(TLV.M4, TLV.kTLVError_Authentication) print('error in step #4: signature', d_res, self.server.sessions) return # shared_secret = self.server.sessions[self.session_id]['shared_secret'] 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) self.server.sessions[self.session_id]['controller_to_accessory_key'] = controller_to_accessory_key self.server.sessions[self.session_id]['controller_to_accessory_count'] = 0 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) self.server.sessions[self.session_id]['accessory_to_controller_key'] = accessory_to_controller_key self.server.sessions[self.session_id]['accessory_to_controller_count'] = 0 d_res[TLV.kTLVType_State] = TLV.M4 self._send_response_tlv(d_res) if HomeKitRequestHandler.DEBUG_PAIR_VERIFY: self.log_message('after step #4\n%s', TLV.to_string(d_res)) return self.send_error(HttpStatusCodes.METHOD_NOT_ALLOWED)
def _post_pairings(self): d_req = TLV.decode_bytes(self.body) self.log_message('POST /pairings request body:\n%s', TLV.to_string(d_req)) session = self.server.sessions[self.session_id] server_data = self.server.data d_res = {} if d_req[TLV.kTLVType_State] == TLV.M1 and d_req[TLV.kTLVType_Method] == TLV.AddPairing: self.log_message('Step #2 /pairings add pairing') # see page 51 self.send_error(HttpStatusCodes.METHOD_NOT_ALLOWED) return if d_req[TLV.kTLVType_State] == TLV.M1 and d_req[TLV.kTLVType_Method] == TLV.RemovePairing: # step #2 Accessory -> iOS Device remove pairing response self.log_message('Step #2 /pairings remove pairings') # 1) # 2) verify set admin bit ios_device_pairing_id = session['ios_device_pairing_id'] if not server_data.is_peer_admin(ios_device_pairing_id): self.send_error_reply(TLV.M2, TLV.kTLVError_Authentication) print('error in step #2: admin bit') return # 3) remove pairing and republish device server_data.remove_peer(d_req[TLV.kTLVType_Identifier]) self.server.publish_device() d_res[TLV.kTLVType_State] = TLV.M2 self._send_response_tlv(d_res) self.log_message('after step #2\n%s', TLV.to_string(d_res)) return if d_req[TLV.kTLVType_State] == TLV.M1 and d_req[TLV.kTLVType_Method] == TLV.ListPairings: # step #2 Accessory -> iOS Device list pairing response self.log_message('Step #2 /pairings list pairings') # 1) Validate against session # 2) verify set admin bit ios_device_pairing_id = session['ios_device_pairing_id'] if not server_data.is_peer_admin(ios_device_pairing_id): self.send_error_reply(TLV.M2, TLV.kTLVError_Authentication) print('error in step #2: admin bit') return # 3) construct response TLV tmp = [(TLV.kTLVType_State, TLV.M2)] for index, pairing_id in enumerate(server_data.peers): tmp.append((TLV.kTLVType_Identifier, pairing_id.encode())) tmp.append((TLV.kTLVType_PublicKey, server_data.get_peer_key(pairing_id.encode()))) user = TLV.kTLVType_Permission_RegularUser if server_data.is_peer_admin(pairing_id.encode()): user = TLV.kTLVType_Permission_AdminUser tmp.append((TLV.kTLVType_Permissions, user)) if index + 1 < len(server_data.peers): tmp.append((TLV.kTLVType_Separator, bytes(0))) result_bytes = TLV.encode_list(tmp) # 4) send response self.send_response(HttpStatusCodes.OK) # Send headers self.send_header('Content-Length', len(result_bytes)) self.send_header('Content-Type', 'application/pairing+tlv8') self.send_header('Connection', 'keep-alive') self.end_headers() self.wfile.write(result_bytes) return self.send_error(HttpStatusCodes.METHOD_NOT_ALLOWED)
def _post_pairings(self): d_req = TLV.decode_bytes(self.body) self.log_message('POST /pairings request body:\n%s', TLV.to_string(d_req)) session = self.server.sessions[self.session_id] server_data = self.server.data d_res = {} if d_req[TLV.kTLVType_State] == TLV.M1 and d_req[ TLV.kTLVType_Method] == TLV.AddPairing: self.log_message('Step #2 /pairings add pairing') d_res[TLV.kTLVType_State] = TLV.M2 # see page 51 # 1) # 2) verify admin bit is set ios_device_pairing_id = session['ios_device_pairing_id'] if not server_data.is_peer_admin(ios_device_pairing_id): self.send_error_reply(TLV.M2, TLV.kTLVError_Authentication) self.log_error('error in step #2: admin bit') return additional_controller_pairing_identifier = d_req[ TLV.kTLVType_Identifier] additional_controller_LTPK = d_req[TLV.kTLVType_PublicKey] additional_controller_permissions = d_req[TLV.kTLVType_Permissions] is_admin = additional_controller_permissions == b'\x01' # 3) pairing exists? registered_controller_LTPK = server_data.get_peer_key( additional_controller_pairing_identifier) if registered_controller_LTPK is not None: self.log_message('controller was registered!') if registered_controller_LTPK != additional_controller_LTPK: self.log_message('with different key') # 3.a) d_res[TLV.kTLVType_Error] = TLV.kTLVError_Authentication self._send_response_tlv(d_res) else: self.log_message('with different permissions') # 3.b) update permission server_data.set_peer_permissions( additional_controller_pairing_identifier, is_admin) else: self.log_message('add pairing') # 4) no pairing exists # 4.a) no limit applied to number of pairings # 4.b) add pairing server_data.add_peer(additional_controller_pairing_identifier, additional_controller_LTPK, is_admin) self.log_message('after step #2\n%s', TLV.to_string(d_res)) return if d_req[TLV.kTLVType_State] == TLV.M1 and d_req[ TLV.kTLVType_Method] == TLV.RemovePairing: # step #2 Accessory -> iOS Device remove pairing response self.log_message('Step #2 /pairings remove pairings') # 1) # 2) verify set admin bit ios_device_pairing_id = session['ios_device_pairing_id'] if not server_data.is_peer_admin(ios_device_pairing_id): self.send_error_reply(TLV.M2, TLV.kTLVError_Authentication) print('error in step #2: admin bit') return # 3) remove pairing and republish device server_data.remove_peer(d_req[TLV.kTLVType_Identifier]) self.server.publish_device() d_res[TLV.kTLVType_State] = TLV.M2 self._send_response_tlv(d_res) self.log_message('after step #2\n%s', TLV.to_string(d_res)) return if d_req[TLV.kTLVType_State] == TLV.M1 and d_req[ TLV.kTLVType_Method] == TLV.ListPairings: # step #2 Accessory -> iOS Device list pairing response self.log_message('Step #2 /pairings list pairings') # 1) Validate against session # 2) verify set admin bit ios_device_pairing_id = session['ios_device_pairing_id'] if not server_data.is_peer_admin(ios_device_pairing_id): self.send_error_reply(TLV.M2, TLV.kTLVError_Authentication) print('error in step #2: admin bit') return # 3) construct response TLV tmp = [(TLV.kTLVType_State, TLV.M2)] for index, pairing_id in enumerate(server_data.peers): tmp.append((TLV.kTLVType_Identifier, pairing_id.encode())) tmp.append((TLV.kTLVType_PublicKey, server_data.get_peer_key(pairing_id.encode()))) user = TLV.kTLVType_Permission_RegularUser if server_data.is_peer_admin(pairing_id.encode()): user = TLV.kTLVType_Permission_AdminUser tmp.append((TLV.kTLVType_Permissions, user)) if index + 1 < len(server_data.peers): tmp.append((TLV.kTLVType_Separator, bytes(0))) result_bytes = TLV.encode_list(tmp) # 4) send response self.send_response(HttpStatusCodes.OK) # Send headers self.send_header('Content-Length', len(result_bytes)) self.send_header('Content-Type', 'application/pairing+tlv8') self.send_header('Connection', 'keep-alive') self.end_headers() self.wfile.write(result_bytes) return self.send_error(HttpStatusCodes.METHOD_NOT_ALLOWED)