Exemplo n.º 1
0
    def test_example1(self):
        example_1 = bytearray.fromhex('060103010568656c6c6f')
        dict_1_1 = TLV.decode_bytearray(example_1)

        bytearray_1 = TLV.encode_dict(dict_1_1)
        dict_1_2 = TLV.decode_bytearray(bytearray_1)
        self.assertEqual(dict_1_1, dict_1_2)
Exemplo n.º 2
0
    def test_example2(self):
        example_2 = bytearray.fromhex('060103' + ('09FF' + 255 * '61' +
                                                  '092D' + 45 * '61') +
                                      '010568656c6c6f')
        dict_2_1 = TLV.decode_bytearray(example_2)

        bytearray_2 = TLV.encode_dict(dict_2_1)
        dict_2_2 = TLV.decode_bytearray(bytearray_2)
        self.assertEqual(dict_2_1, dict_2_2)
Exemplo n.º 3
0
    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_to_list(example)
        self.assertListEqual(data, expected)
Exemplo n.º 4
0
    def _send_response_tlv(self, d_res, close=False, status=HttpStatusCodes.OK):
        result_bytes = TLV.encode_dict(d_res)

        self.send_response(status)
        # 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)
Exemplo n.º 5
0
    def test_long_values_decode_bytearray(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.assertDictEqual(data, expected)
Exemplo n.º 6
0
    def send_error_reply(self, state, error):
        """
        Send an error reply encoded as TLV.
        :param state: The state as in TLV.M1, TLV.M2, ...
        :param error: The error code as in TLV.kTLVError_*
        :return: None
        """
        d_res = dict()
        d_res[TLV.kTLVType_State] = state
        d_res[TLV.kTLVType_Error] = error
        result_bytes = TLV.encode_dict(d_res)

        self.send_response(HttpStatusCodes.METHOD_NOT_ALLOWED)
        # Send headers
        self.send_header('Content-Length', len(result_bytes))
        self.send_header('Content-Type', 'application/pairing+tlv8')
        self.end_headers()

        self.wfile.write(result_bytes)
Exemplo n.º 7
0
def setup_args_parser():
    parser = argparse.ArgumentParser(description='HomeKit list pairings app')
    parser.add_argument('-f', action='store', required=True, dest='file', help='File with the pairing data')
    parser.add_argument('-d', action='store_true', dest='delete', help='Delete file with the pairing data')
    return parser.parse_args()


if __name__ == '__main__':
    args = setup_args_parser()

    conn, controllerToAccessoryKey, accessoryToControllerKey = create_session(args.file)
    sec_http = SecureHttp(conn.sock, accessoryToControllerKey, controllerToAccessoryKey)
    pairing_data = load_pairing(args.file)
    pairingId = pairing_data['iOSPairingId']

    request_tlv = TLV.encode_dict({
        TLV.kTLVType_State: TLV.M1,
        TLV.kTLVType_Method: TLV.RemovePairing,
        TLV.kTLVType_Identifier: pairingId.encode()
    })

    response = sec_http.post('/pairings', request_tlv.decode())
    data = response.read()
    data = TLV.decode_bytes_to_list(data)
    if data[0][0] == TLV.kTLVType_State and data[0][1] == TLV.M2:
        print('Pairing removed')
        if args.delete:
            os.remove(args.file)
    else:
        print('Remove pairing failed')
Exemplo n.º 8
0
        if characteristic_type == CharacteristicFormats.float:
            try:
                value = float(value)
            except ValueError:
                print('{v} is no valid float!'.format(v=value))
                sys.exit(-1)
        if characteristic_type == CharacteristicFormats.data:
            try:
                base64.decodebytes(value.encode())
            except binascii.Error:
                print('{v} is no valid base64 encoded data!'.format(v=value))
                sys.exit(-1)
        if characteristic_type == CharacteristicFormats.tlv8:
            try:
                b = base64.decodebytes(value.encode())
                tlv = TLV.decode_bytes(b)
            except (binascii.Error, TlvParseException):
                print('{v} is no valid base64 encoded tlv!'.format(v=value))
                sys.exit(-1)

        # Nothing to do for CharacteristicFormats.string!

        characteristics.append({'aid': aid, 'iid': iid, 'value': value})
        characteristics_set.add('{a}.{i}'.format(a=aid, i=iid))

    body = json.dumps({'characteristics': characteristics})
    response = sec_http.put('/characteristics', body)
    if response.code != 204:
        data = response.read().decode()
        data = json.loads(data)
        for characteristic in data['characteristics']:
Exemplo n.º 9
0

def setup_args_parser():
    parser = argparse.ArgumentParser(description='HomeKit list pairings app')
    parser.add_argument('-f', action='store', required=True, dest='file', help='File with the pairing data')
    return parser.parse_args()


if __name__ == '__main__':
    args = setup_args_parser()

    conn, controllerToAccessoryKey, accessoryToControllerKey = create_session(args.file)
    sec_http = SecureHttp(conn.sock, accessoryToControllerKey, controllerToAccessoryKey)

    request_tlv = TLV.encode_dict({
        TLV.kTLVType_State: TLV.M1,
        TLV.kTLVType_Method: TLV.ListPairings
    })

    response = sec_http.post('/pairings', request_tlv.decode())
    data = response.read()
    data = TLV.decode_bytes_to_list(data)

    if not (data[0][0] == TLV.kTLVType_State and data[0][1] == TLV.M2):
        print('Illegal reply.')
    elif data[1][0] == TLV.kTLVType_Error and data[1][1] == TLV.kTLVError_Authentication:
        print('Device not paired.')
    else:
        for d in data[1:]:
            if d[0] == TLV.kTLVType_Identifier:
                print('Pairing Id: {id}'.format(id=d[1].decode()))
            if d[0] == TLV.kTLVType_PublicKey:
Exemplo n.º 10
0
    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)
Exemplo n.º 11
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)
Exemplo n.º 12
0
    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)
Exemplo n.º 13
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()
    }
Exemplo n.º 14
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
Exemplo n.º 15
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)