def _handle_request(self, data):
     data = data.replace("\n", "\r\n")
     assert len(data) < 1024
     len_bytes = len(data).to_bytes(2, byteorder='little')
     cnt_bytes = self.c2a_counter.to_bytes(8, byteorder='little')
     self.c2a_counter += 1
     ciper_and_mac = chacha20_aead_encrypt(len_bytes, self.c2a_key,
                                           cnt_bytes, bytes([0, 0, 0, 0]),
                                           data.encode())
     self.sock.send(len_bytes + ciper_and_mac[0] + ciper_and_mac[1])
     return self._handle_response()
    def handle_one_request(self):
        """
        This is used to determine wether the request is encrypted or not. This is done by looking at the first bytes of
        the request. To be valid unencrypted HTTP call, it must be one of the methods defined in RFC7231 Section 4
        "Request Methods".
        :return:
        """
        try:
            # make connection non blocking so the select can work
            self.connection.setblocking(0)
            ready = select.select([self.connection], [], [], 1)

            # no data was to be received, so we count up to track how many seconds in total this happened
            if not ready[0]:
                self.timeout_counter += 1

                # if this is above our configured timeout the connection gets closed
                if self.timeout_counter >= self.timeout:
                    self.close_connection = True
                return

            raw_peeked_data = self.rfile.peek(10)
            if len(raw_peeked_data) == 0:
                # since select says ready but no data is there, close the connection to prevent hidden busy waiting to
                # rise load
                self.close_connection = True
                return

            # data was received so reset the timeout handler
            self.timeout_counter = 0

            # RFC7230 Section 3 tells us, that US-ASCII is fine
            peeked_data = raw_peeked_data[:10]
            peeked_data = peeked_data.decode(encoding='ASCII')
            # self.log_message('deciding over: >%s<', peeked_data)
            # If the request line starts with a known HTTP verb, then use handle_one_request from super class
            if ' ' in peeked_data:
                method = peeked_data.split(' ', 1)[0]
                if method in self.VALID_METHODS:
                    self.server.sessions[self.session_id]['enrypted_connection'] = False
                    BaseHTTPRequestHandler.handle_one_request(self)
                    return
        except (socket.timeout, OSError) as e:
            # a read or a write timed out.  Discard this connection
            self.log_error(' %r', e)
            self.close_connection = True
            return
        except UnicodeDecodeError as e:
            self.log_error('Unicode exception %s' % e)
            pass

        # the first 2 bytes are the length of the encrypted data to follow
        len_bytes = self.rfile.read(2)
        data_len = int.from_bytes(len_bytes, byteorder='little')

        # the authtag is not counted, so add its length
        data = self.rfile.read(data_len + 16)
        if HomeKitRequestHandler.DEBUG_CRYPT:
            self.log_message('data >%i< >%s<', len(data), binascii.hexlify(data))

        # get the crypto key from the session
        c2a_key = self.server.sessions[self.session_id]['controller_to_accessory_key']

        # verify & decrypt the read data
        cnt_bytes = self.server.sessions[self.session_id]['controller_to_accessory_count'].to_bytes(8,
                                                                                                    byteorder='little')
        decrypted = chacha20_aead_decrypt(len_bytes, c2a_key, cnt_bytes, bytes([0, 0, 0, 0]),
                                          data)
        if decrypted == False:
            self.log_error('Could not decrypt %s', binascii.hexlify(data))
            # TODO: handle errors
            pass

        if HomeKitRequestHandler.DEBUG_CRYPT:
            self.log_message('crypted request >%s<', decrypted)

        self.server.sessions[self.session_id]['controller_to_accessory_count'] += 1

        # replace the original rfile with a fake with the decrypted stuff
        old_rfile = self.rfile
        self.rfile = io.BytesIO(decrypted)

        # replace writefile to pass on encrypted data
        old_wfile = self.wfile
        self.wfile = io.BytesIO()

        # call known function
        self.server.sessions[self.session_id]['enrypted_connection'] = True
        BaseHTTPRequestHandler.handle_one_request(self)

        # read the plaintext and send it out encrypted
        self.wfile.seek(0)
        in_data = self.wfile.read(65537)

        if HomeKitRequestHandler.DEBUG_CRYPT:
            self.log_message('response >%s<', in_data)
            self.log_message('len(response) %s', len(in_data))

        block_size = 1024
        out_data = bytearray()
        while len(in_data) > 0:
            block = in_data[:block_size]
            if HomeKitRequestHandler.DEBUG_CRYPT:
                self.log_message('==> BLOCK: len %s', len(block))
            in_data = in_data[block_size:]

            len_bytes = len(block).to_bytes(2, byteorder='little')
            a2c_key = self.server.sessions[self.session_id]['accessory_to_controller_key']
            cnt_bytes = self.server.sessions[self.session_id]['accessory_to_controller_count'].to_bytes(8,
                                                                                                        byteorder='little')
            ciper_and_mac = chacha20_aead_encrypt(len_bytes, a2c_key, cnt_bytes, bytes([0, 0, 0, 0]), block)
            self.server.sessions[self.session_id]['accessory_to_controller_count'] += 1
            out_data += len_bytes + ciper_and_mac[0] + ciper_and_mac[1]

        # change back to originals to handle multiple calls
        self.rfile = old_rfile
        self.wfile = old_wfile

        # send data to original requester
        self.wfile.write(out_data)
        self.wfile.flush()
    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)
Exemple #5
0
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()
    }
Exemple #6
0
def get_session_keys(conn, pairing_data):
    """
    Performs a pair verify operation as described in chapter 4.8 page 47 ff.

    :param conn: the http 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)
    """
    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)
    #
    assert TLV.kTLVType_State in response_tlv, response_tlv
    assert response_tlv[TLV.kTLVType_State] == TLV.M2
    assert TLV.kTLVType_PublicKey in response_tlv, response_tlv
    assert TLV.kTLVType_EncryptedData in response_tlv, response_tlv

    # 1) generate shared secret
    accessorys_session_pub_key_bytes = response_tlv[TLV.kTLVType_PublicKey]
    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 authtag on encrypted data and 4) decrypt
    encrypted = response_tlv[TLV.kTLVType_EncryptedData]
    decrypted = chacha20_aead_decrypt(bytes(),
                                      session_key, 'PV-Msg02'.encode(),
                                      bytes([0, 0, 0, 0]), encrypted)
    if decrypted == False:
        raise homekit.exception.InvalidAuth("step 3")
    d1 = TLV.decode_bytes(decrypted)
    assert TLV.kTLVType_Identifier in d1
    assert TLV.kTLVType_Signature in d1

    # 5) look up pairing by accessory name
    accessory_name = d1[TLV.kTLVType_Identifier].decode()

    if pairing_data['AccessoryPairingID'] != accessory_name:
        raise homekit.exception.IncorrectPairingID("step 3")

    accessory_ltpk = py25519.Key25519(pubkey=bytes(),
                                      verifyingkey=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.pubkey
    if not accessory_ltpk.verify(bytes(accessory_sig), bytes(accessory_info)):
        raise homekit.exception.InvalidSignature("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)
    #
    if TLV.kTLVType_Error in response_tlv:
        error_handler(response_tlv[TLV.kTLVType_Error], "verification")
    assert TLV.kTLVType_State in response_tlv
    assert response_tlv[TLV.kTLVType_State] == TLV.M4

    # 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