Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
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
Ejemplo n.º 3
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()
    }
Ejemplo n.º 4
0
    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)