Пример #1
0
    def add_pairing(self, additional_controller_pairing_identifier, ios_device_ltpk, permissions):
        if not self.session:
            self.session = BleSession(self.pairing_data, self.adapter)
        if permissions == 'User':
            permissions = TlvTypes.Permission_RegularUser
        elif permissions == 'Admin':
            permissions = TlvTypes.Permission_AdminUser
        else:
            print('UNKNOWN')

        request_tlv = tlv8.encode([
            tlv8.Entry(TlvTypes.State, States.M1),
            tlv8.Entry(TlvTypes.Method, Methods.AddPairing),
            tlv8.Entry(TlvTypes.Identifier, additional_controller_pairing_identifier.encode()),
            tlv8.Entry(TlvTypes.PublicKey, bytes.fromhex(ios_device_ltpk)),
            tlv8.Entry(TlvTypes.Permissions, permissions)
        ])

        request_tlv = tlv8.encode([
            tlv8.Entry(AdditionalParameterTypes.ParamReturnResponse, bytearray(b'\x01')),
            tlv8.Entry(AdditionalParameterTypes.Value, request_tlv)
        ])
        body = len(request_tlv).to_bytes(length=2, byteorder='little') + request_tlv

        cid = -1
        for a in self.pairing_data['accessories']:
            for s in a['services']:
                for c in s['characteristics']:
                    if CharacteristicsTypes.get_short_uuid(c['type'].upper()) == CharacteristicsTypes.PAIRING_PAIRINGS:
                        cid = c['iid']
        fc, _ = self.session.find_characteristic_by_iid(cid)
        response = self.session.request(fc, cid, HapBleOpCodes.CHAR_WRITE, body)
        # TODO handle response properly
        print('unhandled response:', response)
Пример #2
0
    def do_char_write(self, tid, value):
        """The value is actually a TLV with a command to perform"""

        request = {
            entry.type_id: entry.data
            for entry in tlv8.decode(
                value, {
                    TlvTypes.State: tlv8.DataType.INTEGER,
                    TlvTypes.Method: tlv8.DataType.INTEGER,
                    TlvTypes.Identifier: tlv8.DataType.BYTES,
                })
        }
        logging.debug('%s', request)

        assert request[TlvTypes.State] == States.M1

        if request[TlvTypes.Method] == Methods.RemovePairing:
            ident = request[TlvTypes.Identifier].decode()
            self.service.device.peers.pop(ident, None)

            # If ident == this session then disconnect it
            # self.service.device.disconnect()

        response = bytearray([0x02, tid, 0x00])

        inner = tlv8.encode([
            tlv8.Entry(TlvTypes.State, States.M2),
        ])

        outer = tlv8.encode(
            [tlv8.Entry(AdditionalParameterTypes.Value, inner)])
        response.extend(len(outer).to_bytes(length=2, byteorder='little'))
        response.extend(outer)

        self.queue_read_response(self.encrypt_value(bytes(response)))
Пример #3
0
    def _send_response_tlv(self, d_res, close=False, status=None):
        result_bytes = tlv8.encode(d_res)

        outer = tlv8.encode([
            tlv8.Entry(AdditionalParameterTypes.Value, result_bytes),
        ])
        self.value += b'\x00' + len(outer).to_bytes(length=2,
                                                    byteorder='little') + outer
Пример #4
0
    def write(request, expected):
        # TODO document me
        body = tlv8.encode(request)
        logger.debug('entering write function %s',
                     tlv8.format_string(tlv8.decode(body)))
        request_tlv = tlv8.encode([
            tlv8.Entry(AdditionalParameterTypes.ParamReturnResponse,
                       bytearray(b'\x01')),
            tlv8.Entry(AdditionalParameterTypes.Value, body)
        ])
        transaction_id = random.randrange(0, 255)
        # construct a hap characteristic write request following chapter 7.3.4.4 page 94 spec R2
        data = bytearray([0x00, HapBleOpCodes.CHAR_WRITE, transaction_id])
        data.extend(characteristic_id.to_bytes(length=2, byteorder='little'))
        data.extend(len(request_tlv).to_bytes(length=2, byteorder='little'))
        data.extend(request_tlv)
        logger.debug('sent %s', bytes(data).hex())

        # write the request to the characteristic
        characteristic.write_value(value=data)

        # reading hap characteristic write response following chapter 7.3.4.5 page 95 spec R2
        data = []
        while len(data) == 0:
            time.sleep(1)
            logger.debug('reading characteristic')
            data = characteristic.read_value()
        resp_data = [b for b in data]

        expected_length = int.from_bytes(bytes(resp_data[3:5]),
                                         byteorder='little')
        logger.debug(
            'control field: {c:x}, tid: {t:x}, status: {s:x}, length: {length}'
            .format(c=resp_data[0],
                    t=resp_data[1],
                    s=resp_data[2],
                    length=expected_length))
        while len(resp_data[3:]) < expected_length:
            time.sleep(1)
            logger.debug('reading characteristic')
            data = characteristic.read_value()
            resp_data.extend([b for b in data])
            logger.debug('data %s of %s', len(resp_data[3:]), expected_length)

        logger.debug('received %s', bytes(resp_data).hex())
        logger.debug('decode %s', bytes(resp_data[5:]).hex())
        resp_tlv = tlv8.decode(
            bytes([int(a) for a in resp_data[5:]]),
            expected={AdditionalParameterTypes.Value: tlv8.DataType.BYTES})
        result = tlv8.decode(
            resp_tlv.first_by_id(AdditionalParameterTypes.Value).data,
            expected)
        logger.debug('leaving write function %s', tlv8.format_string(result))
        return result
Пример #5
0
    def add_pairing(self, additional_controller_pairing_identifier,
                    ios_device_ltpk, permissions):
        if not self.session:
            self.session = IpSession(self.pairing_data)
        if permissions == 'User':
            permissions = TlvTypes.Permission_RegularUser
        elif permissions == 'Admin':
            permissions = TlvTypes.Permission_AdminUser
        else:
            print('UNKNOWN')

        request_tlv = tlv8.encode([
            tlv8.Entry(TlvTypes.State, States.M1),
            tlv8.Entry(TlvTypes.Method, Methods.AddPairing),
            tlv8.Entry(TlvTypes.Identifier,
                       additional_controller_pairing_identifier.encode()),
            tlv8.Entry(TlvTypes.PublicKey, bytes.fromhex(ios_device_ltpk)),
            tlv8.Entry(TlvTypes.Permissions, permissions)
        ])

        response = self.session.sec_http.post('/pairings', request_tlv)
        data = response.read()
        data = tlv8.decode(
            data, {
                TlvTypes.State: tlv8.DataType.INTEGER,
                TlvTypes.Error: tlv8.DataType.BYTES,
            })
        # TODO handle the response properly
        self.session.close()
Пример #6
0
 def test_encode_same_set_sep_type(self):
     data = [
         tlv8.Entry(23, b'23', tlv8.DataType.BYTES),
         tlv8.Entry(23, '23', tlv8.DataType.STRING)
     ]
     result = tlv8.encode(data, 0)
     expected_data = data[0].encode() + b'\x00\x00' + data[1].encode()
     self.assertEqual(result, expected_data)
Пример #7
0
    def test_encode_key(self):
        class TestKeys(enum.IntEnum):
            KEY_1 = 1
            KEY_2 = 2

        data = [tlv8.Entry(TestKeys.KEY_1, 'foo')]
        result = tlv8.encode(data)
        self.assertEqual(b'\x01\x03foo', result)
Пример #8
0
    def test_encode_int_value(self):
        class TestValues(enum.IntEnum):
            VAL_1 = 1
            VAL_2 = 2

        data = [tlv8.Entry(1, TestValues.VAL_2)]
        result = tlv8.encode(data)
        self.assertEqual(b'\x01\x01\x02', result)
Пример #9
0
 def test_encode_supported_rtp_configs(self):
     data = [tlv8.Entry(2, 0), tlv8.Entry(2, 1)]
     result = tlv8.encode(data, separator_type_id=0x00)
     expected_data = \
         b'\x02\x01\x00' + \
         b'\x00\x00' + \
         b'\x02\x01\x01'
     self.assertEqual(result, expected_data)
Пример #10
0
 def test_encode_different(self):
     data = [
         tlv8.Entry(23, b'23', tlv8.DataType.BYTES),
         tlv8.Entry(22, '23', tlv8.DataType.STRING)
     ]
     result = tlv8.encode(data)
     expected_data = data[0].encode() + data[1].encode()
     self.assertEqual(result, expected_data)
Пример #11
0
 def test_decode_misinterpretation(self):
     """This show how data may be misinterpreted by deep_decode"""
     data = tlv8.encode([tlv8.Entry(1, 16843330), tlv8.Entry(2, b'\x01')])
     result = tlv8.deep_decode(data)
     expected_data = tlv8.EntryList([
         tlv8.Entry(1, tlv8.EntryList([tlv8.Entry(66, b'\x01\x01')])),
         tlv8.Entry(2, b'\x01')
     ])
     self.assertEqual(result, expected_data)
Пример #12
0
    def list_pairings(self):
        if not self.session:
            self.session = BleSession(self.pairing_data, self.adapter)
        request_tlv = tlv8.encode([
            tlv8.Entry(TlvTypes.State, States.M1),
            tlv8.Entry(TlvTypes.Method, Methods.ListPairings)
        ])
        request_tlv = tlv8.encode([
            tlv8.Entry(AdditionalParameterTypes.ParamReturnResponse,
                       bytearray(b'\x01')),
            tlv8.Entry(AdditionalParameterTypes.Value, request_tlv)
        ])
        body = len(request_tlv).to_bytes(length=2,
                                         byteorder='little') + request_tlv

        cid = -1
        for a in self.pairing_data['accessories']:
            for s in a['services']:
                for c in s['characteristics']:
                    if CharacteristicsTypes.get_short_uuid(c['type'].upper(
                    )) == CharacteristicsTypes.PAIRING_PAIRINGS:
                        cid = c['iid']
        fc, _ = self.session.find_characteristic_by_iid(cid)
        response = self.session.request(fc, cid, HapBleOpCodes.CHAR_WRITE,
                                        body)
        response = tlv8.decode(
            response.first_by_id(AdditionalParameterTypes.Value).data)
        tmp = []
        r = {}
        for d in response[1:]:
            if d.type_id == TlvTypes.Identifier:
                r = {}
                tmp.append(r)
                r['pairingId'] = d.data.decode()
            if d.type_id == TlvTypes.PublicKey:
                r['publicKey'] = d.data.hex()
            if d.type_id == TlvTypes.Permissions:
                controller_type = 'regular'
                if d.data == b'\x01':
                    controller_type = 'admin'
                r['permissions'] = int.from_bytes(d.data, byteorder='little')
                r['controllerType'] = controller_type
        tmp.sort(key=lambda x: x['pairingId'])
        return tmp
Пример #13
0
 def test_autodetection_of_types(self):
     data = [
         tlv8.Entry(1, 3.141),
         tlv8.Entry(2, [tlv8.Entry(3, 'hello'),
                        tlv8.Entry(4, 'world')]),
         tlv8.Entry(1, 2)
     ]
     result = tlv8.encode(data)
     expected_data = b'\x01\x04%\x06I@\x02\x0e\x03\x05hello\x04\x05world\x01\x01\x02'
     self.assertEqual(result, expected_data)
Пример #14
0
    def identify_ble(accessory_mac, adapter='hci0'):
        """
        This call can be used to trigger the identification of an accessory, that was not yet paired. A successful call
        should cause the accessory to perform some specific action by which it can be distinguished from others (blink a
        LED for example).

        It uses the /identify url as described on page 88 of the spec.

        :param accessory_mac: the accessory's mac address (e.g. retrieved via discover)
        :raises AccessoryNotFoundError: if the accessory could not be looked up via Bonjour
        :param adapter: the bluetooth adapter to be used (defaults to hci0)
        :raises AlreadyPairedError: if the accessory is already paired
        """
        if not BLE_TRANSPORT_SUPPORTED:
            raise TransportNotSupportedError('BLE')
        from .ble_impl.device import DeviceManager
        manager = DeviceManager(adapter)
        device = manager.make_device(accessory_mac)
        device.connect()

        disco_info = device.get_homekit_discovery_data()
        if disco_info.get('flags', 'unknown') == 'paired':
            raise AlreadyPairedError(
                'identify of {mac_address} failed not allowed as device already paired'
                .format(mac_address=accessory_mac), )

        identify, identify_iid = find_characteristic_by_uuid(
            device,
            ServicesTypes.ACCESSORY_INFORMATION_SERVICE,
            CharacteristicsTypes.IDENTIFY,
        )

        if not identify:
            raise AccessoryNotFoundError(
                'Device with address {mac_address} exists but did not find IDENTIFY characteristic'
                .format(mac_address=accessory_mac))

        value = tlv8.encode(
            [tlv8.Entry(AdditionalParameterTypes.Value, b'\x01')])
        body = len(value).to_bytes(length=2, byteorder='little') + value

        tid = random.randrange(0, 255)

        request = bytearray([0x00, HapBleOpCodes.CHAR_WRITE, tid])
        request.extend(identify_iid.to_bytes(length=2, byteorder='little'))
        request.extend(body)

        identify.write_value(request)
        response = bytearray(identify.read_value())

        if not response or not response[2] == 0x00:
            raise UnknownError('Unpaired identify failed')

        return True
Пример #15
0
 def write_http(request, expected):
     body = tlv8.encode(request)
     logging.debug('write message: %s',
                   tlv8.format_string(tlv8.deep_decode(body)))
     connection.putrequest('POST', '/pair-setup', skip_accept_encoding=True)
     connection.putheader('Content-Type', 'application/pairing+tlv8')
     connection.putheader('Content-Length', len(body))
     connection.endheaders(body)
     resp = connection.getresponse()
     response_tlv = tlv8.decode(resp.read(), expected)
     logging.debug('response: %s', tlv8.format_string(response_tlv))
     return response_tlv
Пример #16
0
 def test_encode_3same(self):
     data = [
         tlv8.Entry(23, b'23', tlv8.DataType.BYTES),
         tlv8.Entry(23, '23', tlv8.DataType.STRING),
         tlv8.Entry(23, '23', tlv8.DataType.STRING)
     ]
     result = tlv8.encode(data)
     expected_data = \
         data[0].encode() + b'\xff\x00' + \
         data[1].encode() + b'\xff\x00' + \
         data[2].encode()
     self.assertEqual(result, expected_data)
Пример #17
0
    def put_characteristics(self, characteristics, do_conversion=False):
        """
        Update the values of writable characteristics. The characteristics have to be identified by accessory id (aid),
        instance id (iid). If do_conversion is False (the default), the value must be of proper format for the
        characteristic since no conversion is done. If do_conversion is True, the value is converted.

        :param characteristics: a list of 3-tupels of accessory id, instance id and the value
        :param do_conversion: select if conversion is done (False is default)
        :return: a dict from (aid, iid) onto {status, description}
        :raises FormatError: if the input value could not be converted to the target type and conversion was
                             requested
        """
        if not self.session:
            self.session = BleSession(self.pairing_data, self.adapter)

        results = {}

        for aid, cid, value in characteristics:
            # reply with an error if the characteristic does not exist
            if not self._find_characteristic_in_pairing_data(aid, cid):
                results[(aid, cid)] = {
                    'status':
                    HapBleStatusCodes.INVALID_REQUEST,
                    'description':
                    HapBleStatusCodes[HapBleStatusCodes.INVALID_REQUEST]
                }
                continue

            value = tlv8.encode([
                tlv8.Entry(AdditionalParameterTypes.Value,
                           self._convert_from_python(aid, cid, value))
            ])
            body = len(value).to_bytes(length=2, byteorder='little') + value

            try:
                fc, fc_info = self.session.find_characteristic_by_iid(cid)
                response = self.session.request(fc, cid,
                                                HapBleOpCodes.CHAR_WRITE, body)
                logger.debug('response %s', tlv8.format_string(response))
                # TODO does the response contain useful information here?
            except RequestRejected as e:
                results[(aid, cid)] = {
                    'status': e.status,
                    'description': e.message,
                }
            except Exception as e:
                self.session.close()
                self.session = None
                raise e

        return results
Пример #18
0
def buildTLV8(serverCert, verifyKey, rootCA):
    structure = [
        tlv8.Entry(HAP_KEYSTORE_TYPE.HAP_KEYSTORE_TYPE_CONTAINER_ID.value,
                   CONTAINER_VERSION),
        tlv8.Entry(HAP_KEYSTORE_TYPE.HAP_KEYSTORE_TYPE_ROOT_CA.value,
                   readBinaryFile(rootCA)),
        tlv8.Entry(
            HAP_KEYSTORE_TYPE.HAP_KEYSTORE_TYPE_ROOT_CA_PUBLIC_KEY_SIGNATURE.
            value, readBinaryFile(verifyKey)),
        tlv8.Entry(
            HAP_KEYSTORE_TYPE.HAP_KEYSTORE_TYPE_DEVICE_WEBSERVER_CERT.value,
            readBinaryFile(serverCert))  #,   
        #tlv8.Entry( HAP_KEYSTORE_TYPE.HAP_KEYSTORE_TYPE_DEVICE_PUBLIC_KEY.value,           readBinaryFile("./certs/devices/esp32-cafeec/esp32-cafeec.publicKey.cer") )
    ]

    bytes_data = tlv8.encode(structure)
    #print(bytes_data)
    return bytes_data, structure
Пример #19
0
    def list_pairings(self):
        """
        This method returns all pairings of a HomeKit accessory. This always includes the local controller and can only
        be done by an admin controller.

        The keys in the resulting dicts are:
         * pairingId: the pairing id of the controller
         * publicKey: the ED25519 long-term public key of the controller
         * permissions: bit value for the permissions
         * controllerType: either admin or regular

        :return: a list of dicts
        :raises: UnknownError: if it receives unexpected data
        :raises: UnpairedError: if the polled accessory is not paired
        """
        if not self.session:
            self.session = IpSession(self.pairing_data)
        request_tlv = tlv8.encode([
            tlv8.Entry(TlvTypes.State, States.M1),
            tlv8.Entry(TlvTypes.Method, Methods.ListPairings)
        ])
        try:
            response = self.session.sec_http.post('/pairings', request_tlv)
            data = response.read()
        except (AccessoryDisconnectedError, EncryptionError):
            self.session.close()
            self.session = None
            raise
        data = tlv8.decode(
            data, {
                TlvTypes.State: tlv8.DataType.INTEGER,
                TlvTypes.Error: tlv8.DataType.INTEGER,
                TlvTypes.Identifier: tlv8.DataType.BYTES,
                TlvTypes.PublicKey: tlv8.DataType.BYTES,
                TlvTypes.Permissions: tlv8.DataType.BYTES
            })

        error = data.first_by_id(TlvTypes.Error)
        if not (data.first_by_id(TlvTypes.State).data == States.M2):
            raise UnknownError('unexpected data received: ' +
                               tlv8.format_string(data))
        elif error and error.data == Errors.Authentication:
            raise UnpairedError('Must be paired')
        else:
            tmp = []
            r = {}
            for d in data[1:]:
                if d.type_id == TlvTypes.Identifier:
                    r = {}
                    tmp.append(r)
                    r['pairingId'] = d.data.decode()
                if d.type_id == TlvTypes.PublicKey:
                    r['publicKey'] = d.data.hex()
                if d.type_id == TlvTypes.Permissions:
                    controller_type = 'regular'
                    if d.data == b'\x01':
                        controller_type = 'admin'
                    r['permissions'] = int.from_bytes(d.data,
                                                      byteorder='little')
                    r['controllerType'] = controller_type
            tmp.sort(key=lambda x: x['pairingId'])
            return tmp
Пример #20
0
 def test_encode_64bit_int(self):
     result = tlv8.encode([tlv8.Entry(1, 4611686018427387904)])
     self.assertEqual(b'\x01\x08\x00\x00\x00\x00\x00\x00\x00@', result)
Пример #21
0
 def test_encode_32bit_int(self):
     result = tlv8.encode([tlv8.Entry(1, 1073741824)])
     self.assertEqual(b'\x01\x04\x00\x00\x00@', result)
Пример #22
0
 def test_encode_same_autodetect(self):
     structure = [tlv8.Entry(1, 23), tlv8.Entry(2, 2345)]
     result = tlv8.encode(structure)
     expected_data = b'\x01\x01\x17\x02\x02)\t'
     self.assertEqual(result, expected_data)
Пример #23
0
def get_session_keys(pairing_data):
    """
    HomeKit Controller state machine to perform a pair verify operation as described in chapter 4.8 page 47 ff.
    :param pairing_data: the paring data as returned by perform_pair_setup
    :return: tuple of the session keys (controller_to_accessory_key and  accessory_to_controller_key)
    :raises InvalidAuthTagError: if the auth tag could not be verified,
    :raises IncorrectPairingIdError: if the accessory's LTPK could not be found
    :raises InvalidSignatureError: if the accessory's signature could not be verified
    :raises AuthenticationError: if the secured session could not be established
    """

    #
    # Step #1 ios --> accessory (send verify start Request) (page 47)
    #
    ios_key = x25519.X25519PrivateKey.generate()
    ios_key_pub = ios_key.public_key().public_bytes(
        encoding=serialization.Encoding.Raw,
        format=serialization.PublicFormat.Raw)

    request_tlv = [
        tlv8.Entry(TlvTypes.State, States.M1),
        tlv8.Entry(TlvTypes.PublicKey, ios_key_pub)
    ]

    step2_expectations = {
        TlvTypes.State: tlv8.DataType.INTEGER,
        TlvTypes.PublicKey: tlv8.DataType.BYTES,
        TlvTypes.EncryptedData: tlv8.DataType.BYTES
    }
    response_tlv = yield (request_tlv, step2_expectations)

    #
    # Step #3 ios --> accessory (send SRP verify request)  (page 49)
    #
    state = response_tlv.first_by_id(TlvTypes.State).data
    assert state == States.M2, 'get_session_keys: not M2'
    assert response_tlv.first_by_id(
        TlvTypes.PublicKey), 'get_session_keys: no public key'
    assert response_tlv.first_by_id(
        TlvTypes.EncryptedData), 'get_session_keys: no encrypted data'

    # 1) generate shared secret
    accessorys_session_pub_key_bytes = bytes(
        response_tlv.first_by_id(TlvTypes.PublicKey).data)
    accessorys_session_pub_key = x25519.X25519PublicKey.from_public_bytes(
        accessorys_session_pub_key_bytes)
    shared_secret = ios_key.exchange(accessorys_session_pub_key)

    # 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 auth tag on encrypted data and 4) decrypt
    encrypted = response_tlv.first_by_id(TlvTypes.EncryptedData).data
    decrypted = chacha20_aead_decrypt(bytes(),
                                      session_key, 'PV-Msg02'.encode(),
                                      bytes([0, 0, 0, 0]), encrypted)
    if type(decrypted) == bool and not decrypted:
        raise InvalidAuthTagError('step 3')
    d1 = tlv8.decode(decrypted)
    assert d1.first_by_id(
        TlvTypes.Identifier), 'get_session_keys: no identifier'
    assert d1.first_by_id(TlvTypes.Signature), 'get_session_keys: no signature'

    # 5) look up pairing by accessory name
    accessory_name = d1.first_by_id(TlvTypes.Identifier).data.decode()

    if pairing_data['AccessoryPairingID'] != accessory_name:
        raise IncorrectPairingIdError('step 3')

    accessory_ltpk = ed25519.VerifyingKey(
        bytes.fromhex(pairing_data['AccessoryLTPK']))

    # 6) verify accessory's signature
    accessory_sig = d1.first_by_id(TlvTypes.Signature).data
    accessory_session_pub_key_bytes = response_tlv.first_by_id(
        TlvTypes.PublicKey).data
    accessory_info = accessory_session_pub_key_bytes + accessory_name.encode(
    ) + ios_key_pub
    try:
        accessory_ltpk.verify(bytes(accessory_sig), bytes(accessory_info))
    except ed25519.BadSignatureError:
        raise InvalidSignatureError('step 3')

    # 7) create iOSDeviceInfo
    ios_device_info = ios_key_pub + 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_ltpk_h = pairing_data['iOSDeviceLTPK']
    ios_device_ltsk = ed25519.SigningKey(
        bytes.fromhex(ios_device_ltsk_h) + bytes.fromhex(ios_device_ltpk_h))
    ios_device_signature = ios_device_ltsk.sign(ios_device_info)

    # 9) construct sub tlv
    sub_tlv = tlv8.encode([
        tlv8.Entry(TlvTypes.Identifier, pairing_data['iOSPairingId'].encode()),
        tlv8.Entry(TlvTypes.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 = [
        tlv8.Entry(TlvTypes.State, States.M3),
        tlv8.Entry(TlvTypes.EncryptedData, tmp)
    ]

    step3_expectations = {
        TlvTypes.State: tlv8.DataType.INTEGER,
        TlvTypes.Error: tlv8.DataType.INTEGER
    }
    response_tlv = yield (request_tlv, step3_expectations)

    #
    #   Post Step #4 verification (page 51)
    #
    state = response_tlv.first_by_id(TlvTypes.State).data
    assert state == States.M4, 'get_session_keys: not M4'
    error = response_tlv.first_by_id(TlvTypes.Error)
    if len(response_tlv) == 2 and error:
        error_handler(error.data, 'verification')

    # 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
Пример #24
0
def perform_pair_setup_part2(pin, ios_pairing_id, salt, server_public_key):
    """
    Performs a pair setup operation as described in chapter 4.7 page 39 ff.

    :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
    :raises UnavailableError: if the device is already paired
    :raises MaxTriesError: if the device received more than 100 unsuccessful pairing attempts
    :raises BusyError: if a parallel pairing is ongoing
    :raises AuthenticationError: if the verification of the device's SRP proof fails
    :raises MaxPeersError: if the device cannot accept an additional pairing
    :raises IllegalData: if the verification of the accessory's data fails
    """

    srp_client = SrpClient('Pair-Setup', pin)
    srp_client.set_salt(salt)
    srp_client.set_server_public_key(server_public_key)
    client_pub_key = srp_client.get_public_key()
    client_proof = srp_client.get_proof()

    response_tlv = [
        tlv8.Entry(TlvTypes.State, States.M3),
        tlv8.Entry(TlvTypes.PublicKey,
                   SrpClient.to_byte_array(client_pub_key)),
        tlv8.Entry(TlvTypes.Proof, SrpClient.to_byte_array(client_proof)),
    ]

    step4_expectations = {
        TlvTypes.State: tlv8.DataType.INTEGER,
        TlvTypes.Error: tlv8.DataType.INTEGER,
        TlvTypes.Proof: tlv8.DataType.BYTES
    }
    response_tlv = yield (response_tlv, step4_expectations)

    #
    # Step #5 ios --> accessory (Exchange Request) (see page 43)
    #
    logging.debug('#5 ios -> accessory: send SRP exchange request')

    # M4 Verification (page 43)
    assert response_tlv.first_by_id(
        TlvTypes.State) and response_tlv.first_by_id(
            TlvTypes.State
        ).data == States.M4, 'perform_pair_setup: State not M4'
    error = response_tlv.first_by_id(TlvTypes.Error)
    if error:
        error_handler(error.data, 'step 5')

    assert response_tlv.first_by_id(
        TlvTypes.Proof), 'perform_pair_setup: Not a proof'
    if not srp_client.verify_servers_proof(
            response_tlv.first_by_id(TlvTypes.Proof).data):
        raise AuthenticationError('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 = [
        tlv8.Entry(TlvTypes.Identifier, ios_device_pairing_id),
        tlv8.Entry(TlvTypes.PublicKey, ios_device_ltpk.to_bytes()),
        tlv8.Entry(TlvTypes.Signature, ios_device_signature)
    ]
    sub_tlv_b = tlv8.encode(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 = [
        tlv8.Entry(TlvTypes.State, States.M5),
        tlv8.Entry(TlvTypes.EncryptedData, tmp)
    ]

    step6_expectations = {
        TlvTypes.State: tlv8.DataType.INTEGER,
        TlvTypes.Error: tlv8.DataType.INTEGER,
        TlvTypes.EncryptedData: tlv8.DataType.BYTES
    }
    response_tlv = yield (response_tlv, step6_expectations)

    #
    # Step #7 ios (Verification) (page 47)
    #
    assert response_tlv.first_by_id(
        TlvTypes.State) and response_tlv.first_by_id(
            TlvTypes.State
        ).data == States.M6, 'perform_pair_setup: State not M6'
    error = response_tlv.first_by_id(TlvTypes.Error)
    if error:
        error_handler(error.data, 'step 7')

    assert response_tlv.first_by_id(
        TlvTypes.EncryptedData), 'perform_pair_setup: No encrypted data'
    decrypted_data = chacha20_aead_decrypt(
        bytes(), session_key, 'PS-Msg06'.encode(), bytes([0, 0, 0, 0]),
        response_tlv.first_by_id(TlvTypes.EncryptedData).data)
    if decrypted_data is False:
        raise homekit.exception.IllegalData('step 7')

    response_tlv = tlv8.decode(decrypted_data)

    assert response_tlv.first_by_id(
        TlvTypes.Identifier), 'perform_pair_setup: No identifier'
    accessory_pairing_id = response_tlv.first_by_id(TlvTypes.Identifier).data

    assert response_tlv.first_by_id(
        TlvTypes.PublicKey), 'perform_pair_setup: No public key'
    accessory_ltpk = response_tlv.first_by_id(TlvTypes.PublicKey).data

    assert response_tlv.first_by_id(
        TlvTypes.Signature), 'perform_pair_setup: No signature'
    accessory_sig = response_tlv.first_by_id(TlvTypes.Signature).data

    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(accessory_ltpk))
    try:
        e25519s.verify(bytes(accessory_sig), bytes(accessory_info))
    except AssertionError:
        raise InvalidSignatureError('step #7')

    return {
        'AccessoryPairingID': accessory_pairing_id.decode(),
        'AccessoryLTPK': hexlify(accessory_ltpk).decode(),
        'iOSPairingId': ios_pairing_id,
        'iOSDeviceLTSK':
        ios_device_ltsk.to_ascii(encoding='hex').decode()[:64],
        'iOSDeviceLTPK': hexlify(ios_device_ltpk.to_bytes()).decode()
    }
Пример #25
0
 def test_encode_8bit_signed_int(self):
     result = tlv8.encode([tlv8.Entry(1, -64, tlv8.DataType.INTEGER)])
     self.assertEqual(b'\x01\x01\xc0', result)
Пример #26
0
 def test_encode_8bit_unsigned_int(self):
     result = tlv8.encode(
         [tlv8.Entry(1, 64, tlv8.DataType.UNSIGNED_INTEGER)])
     self.assertEqual(b'\x01\x01@', result)
Пример #27
0
 def test_encode_bytearray_autodetect(self):
     result = tlv8.encode([tlv8.Entry(1, bytearray(b'\x01'))])
     self.assertEqual(b'\x01\x01\x01', result)
Пример #28
0
 def test_encode_16bit_int(self):
     result = tlv8.encode([tlv8.Entry(1, 16384)])
     self.assertEqual(b'\x01\x02\x00@', result)
Пример #29
0
    def process_value(self, value):
        assert value[0] == 0
        opcode = value[1]
        tid = value[2]
        payload = value[7:]

        if opcode == HapBleOpCodes.CHAR_WRITE:
            new_value = {
                entry.type_id: entry.data
                for entry in tlv8.decode(payload)
            }
            self.do_char_write(tid, new_value[1])

        elif opcode == HapBleOpCodes.CHAR_READ:
            value = self.char.get_value_for_ble()
            value = tlv8.encode(
                [tlv8.Entry(AdditionalParameterTypes.Value, value)])

            response = bytearray([0x02, tid, 0x00])
            tlv = len(value).to_bytes(2, byteorder='little') + value
            response.extend(tlv)
            self.queue_read_response(self.encrypt_value(bytes(response)))

        elif opcode == HapBleOpCodes.CHAR_SIG_READ:
            response = bytearray([0x02, tid, 0x00])

            service_type = list(uuid.UUID(self.service.service.type).bytes)
            service_type.reverse()
            service_type = bytes(bytearray(service_type))

            char_type = list(uuid.UUID(self.char.type).bytes)
            char_type.reverse()
            char_type = bytes(bytearray(char_type))

            fmt = BleCharacteristicFormats.get_reverse(
                self.char.format, b'\x00').to_bytes(length=1,
                                                    byteorder='little')
            unit = b'\x00\x00'
            gatt_fmt = fmt + unit

            data = [
                tlv8.Entry(
                    AdditionalParameterTypes.
                    HAPCharacteristicPropertiesDescriptor, b'\x00'),
                tlv8.Entry(
                    AdditionalParameterTypes.GATTPresentationFormatDescriptor,
                    gatt_fmt),
                tlv8.Entry(AdditionalParameterTypes.CharacteristicType,
                           char_type),
                tlv8.Entry(
                    AdditionalParameterTypes.ServiceInstanceId,
                    self.service.service.iid.to_bytes(length=8,
                                                      byteorder='little')),
                tlv8.Entry(AdditionalParameterTypes.ServiceType, service_type),
            ]

            tlv = tlv8.encode(data)
            response.extend(len(tlv).to_bytes(2, byteorder='little') + tlv)
            self.queue_read_response(self.encrypt_value(bytes(response)))
        else:
            raise RuntimeError('Fake does not implement opcode %s' % opcode)
Пример #30
0
 def test_encode_bytearray(self):
     result = tlv8.encode(
         [tlv8.Entry(1, bytearray(b'\x01'), tlv8.DataType.BYTES)])
     self.assertEqual(b'\x01\x01\x01', result)