Esempio n. 1
0
async def pair_setup(request: Request) -> Response:
    global_context = request.global_context
    config = global_context['config']

    parsed_body = tlv_parser.decode(await request.read())[0]
    requested_state = parsed_body.get(TlvCode.state)
    expected_state = global_context['pair_setup_expected_state']

    logger.debug(f'Requested pair_setup state: {requested_state}')

    if requested_state == TlvState.m1 and parsed_body.get(TlvCode.method) == TlvMethod.reserved:
        result = srp_start(config, request.context, expected_state)
        global_context['pair_setup_expected_state'] = TlvState.m3
    elif requested_state == TlvState.m3:
        result = srp_verify(request.context, expected_state, parsed_body[TlvCode.public_key],
                            parsed_body[TlvCode.proof])
        global_context['pair_setup_expected_state'] = TlvState.m5
    elif requested_state == TlvState.m5:
        result = exchange(config, request.context, expected_state, parsed_body[TlvCode.encrypted_data])
        global_context['pair_setup_expected_state'] = TlvState.m1
    else:
        raise ValueError('Unknown data received: {}'.format(parsed_body))

    if TlvCode.error in result[0]:
        config.pair_setup_mode = False
        global_context['pair_setup_expected_state'] = TlvState.m1

    return Response(PAIRING_CONTENT_TYPE, data=tlv_parser.encode(result))
Esempio n. 2
0
    def test_tlv_decode_separated(self):
        data = bytes([
            0x01,  # identifier
            0x05,  # 5 byte value size
            0x68,  # ASCII 'h'
            0x65,  # ASCII 'e'
            0x6c,  # ASCII 'l'
            0x6c,  # ASCII 'l'
            0x6f,  # ASCII 'o'
            0x0b,  # permissions
            0x01,  # 1 byte value size
            0x00,  # user permission
            0xff,  # separator
            0x00,  # 0 byte value size
            0x01,  # identifier
            0x05,  # 5 byte value size
            0x77,  # ASCII 'w'
            0x6f,  # ASCII 'o'
            0x72,  # ASCII 'r'
            0x6c,  # ASCII 'l'
            0x64,  # ASCII 'd'
            0x0b,  # permissions
            0x01,  # 1 byte value size
            0x01,  # admin permission
        ])

        result = tlv_parser.decode(data)
        expected_result = [{
            TlvCode.identifier: 'hello',
            TlvCode.permissions: 0
        }, {
            TlvCode.identifier: 'world',
            TlvCode.permissions: 1
        }]
        self.assertEqual(result, expected_result)
Esempio n. 3
0
    def test_tlv_decode_merge(self):
        data = [
            0x06,  # state
            0x01,  # 1 byte value size
            0x03,  # M3
            0x09,  # certificate
            0xff,  # 255 byte value size
            0x61,  # ASCII 'a'
        ]
        data.extend([0x61] * 254)  # 254 more bytes containing 0x61 (ASCII 'a')
        data.extend([
            0x09,  # certificate, continuation of previous TLV
            0x2d,  # 45 byte value size
            0x61,  # ASCII 'a'
        ])
        data.extend([0x61] * 44)  # 44 more bytes containing 0x61 (ASCII 'a')
        data.extend([
            0x01,  # identifier, new TLV item
            0x05,  # 5 byte value size
            0x68,  # ASCII 'h'
            0x65,  # ASCII 'e'
            0x6c,  # ASCII 'l'
            0x6c,  # ASCII 'l'
            0x6f,  # ASCII 'o'
        ])

        result = tlv_parser.decode(bytes(data))[0]
        expected_result = {
            TlvCode.state: 3,
            TlvCode.certificate: b'a' * 300,
            TlvCode.identifier: 'hello'
        }
        self.assertEqual(result, expected_result)
Esempio n. 4
0
def exchange(config: Config, context: dict, expected_tlv_state: TlvState, encrypted_data: bytes) -> List[dict]:
    """pair_setup M5 and M6"""
    srp = context.get('srp')

    if expected_tlv_state != TlvState.m5 or not srp:
        return _error(TlvState.m6, TlvError.unknown, 'Unexpected pair_setup state')

    hkdf = HKDF(algorithm=SHA512(), length=32, salt=SALT_ENCRYPT, info=INFO_ENCRYPT, backend=default_backend())
    decrypt_key = hkdf.derive(srp.session_key)

    chacha = ChaCha20Poly1305(decrypt_key)

    try:
        data = chacha.decrypt(NONCE_SETUP_M5, encrypted_data, None)
    except InvalidTag:
        return _error(TlvState.m6, TlvError.authentication, 'pair_setup M5: invalid auth tag during chacha decryption')

    try:
        tlv = tlv_parser.decode(data)[0]
    except ValueError:
        return _error(TlvState.m6, TlvError.authentication, 'unable to decode decrypted tlv data')

    hkdf = HKDF(algorithm=SHA512(), length=32, salt=SALT_CONTROLLER, info=INFO_CONTROLLER, backend=default_backend())
    ios_device_x = hkdf.derive(srp.session_key)
    ios_device_info = ios_device_x + tlv[TlvCode.identifier].encode() + tlv[TlvCode.public_key]

    if not _verify_ed25519(key=tlv[TlvCode.public_key], message=ios_device_info, signature=tlv[TlvCode.signature]):
        return _error(TlvState.m6, TlvError.authentication, 'ios_device_info ed25519 signature verification is failed')

    config.add_pairing(tlv[TlvCode.identifier], tlv[TlvCode.public_key], ControllerPermission.admin)  # save pairing

    # M6 response generation
    hkdf = HKDF(algorithm=SHA512(), length=32, salt=SALT_ACCESSORY, info=INFO_ACCESSORY, backend=default_backend())
    accessory_x = hkdf.derive(srp.session_key)

    signing_key = ed25519.SigningKey(config.accessory_ltsk)
    public_key = signing_key.get_verifying_key().to_bytes()
    accessory_info = accessory_x + config.device_id.encode() + public_key
    accessory_signature = signing_key.sign(accessory_info)

    sub_tlv = tlv_parser.encode([{
        TlvCode.identifier: config.device_id,
        TlvCode.public_key: public_key,
        TlvCode.signature: accessory_signature,
    }])

    encrypted_data = chacha.encrypt(NONCE_SETUP_M6, sub_tlv, None)

    config.pair_setup_mode = False
    return [{
        TlvCode.state: TlvState.m6,
        TlvCode.encrypted_data: encrypted_data,
    }]
Esempio n. 5
0
def verify_finish(config: Config, context: dict, encrypted_data: bytes) -> List[dict]:
    """pair_verify M3 and M4"""
    session_key = context.get('session_key')
    accessory_curve25519_public_key = context.get('accessory_curve25519_public_key')
    ios_device_curve25519_public_key = context.get('ios_device_curve25519_public_key')

    if not session_key or not accessory_curve25519_public_key or not ios_device_curve25519_public_key:
        return _error(TlvState.m4, TlvError.authentication,
                      'verify_finished call before successful verify_start')

    chacha = ChaCha20Poly1305(session_key)

    try:
        data = chacha.decrypt(NONCE_VERIFY_M3, encrypted_data, None)
    except InvalidTag:
        return _error(TlvState.m4, TlvError.authentication, 'invalid auth tag during chacha decryption')

    try:
        tlv = tlv_parser.decode(data)[0]
    except ValueError:
        return _error(TlvState.m4, TlvError.authentication, 'unable to decode decrypted tlv data')

    ios_device_ltpk = config.get_pairing(tlv[TlvCode.identifier])[1]

    if not ios_device_ltpk:
        return _error(TlvState.m4, TlvError.authentication,
                      'unable to find requested ios device in config file')

    ios_device_info = ios_device_curve25519_public_key + tlv[TlvCode.identifier].encode() + \
        accessory_curve25519_public_key

    if not _verify_ed25519(ios_device_ltpk, message=ios_device_info, signature=tlv[TlvCode.signature]):
        return _error(TlvState.m4, TlvError.authentication,
                      'ios_device_info ed25519 signature verification is failed')

    context['paired'] = True
    context['ios_device_pairing_id'] = tlv[TlvCode.identifier]

    hkdf = HKDF(algorithm=SHA512(), length=32, salt=SALT_CONTROL, info=INFO_CONTROL_WRITE, backend=default_backend())
    context['decrypt_key'] = hkdf.derive(context['shared_secret'])

    hkdf = HKDF(algorithm=SHA512(), length=32, salt=SALT_CONTROL, info=INFO_CONTROL_READ, backend=default_backend())
    context['encrypt_key'] = hkdf.derive(context['shared_secret'])

    return [{
        TlvCode.state: TlvState.m4,
    }]
Esempio n. 6
0
async def pair_verify(request: Request) -> Response:
    config = request.global_context['config']
    upgrade = False

    parsed_body = tlv_parser.decode(await request.read())[0]
    requested_state = parsed_body.get(TlvCode.state)

    logger.debug(f'Requested pair_verify state: {requested_state}')

    if requested_state == TlvState.m1:
        result = verify_start(config, request.context, parsed_body[TlvCode.public_key])
    elif requested_state == TlvState.m3:
        result = verify_finish(config, request.context, parsed_body[TlvCode.encrypted_data])
        if request.context.get('paired'):
            upgrade = True  # verify_finish end up without errors, upgrade to fully encrypted communication
    else:
        raise ValueError('Unknown data received: {}'.format(parsed_body))

    return Response(PAIRING_CONTENT_TYPE, data=tlv_parser.encode(result), upgrade=upgrade)
Esempio n. 7
0
    def test_tlv_decode(self):
        data = bytes([
            0x06,  # state
            0x01,  # 1 byte value size
            0x03,  # M3
            0x01,  # identifier
            0x05,  # 5 byte value size
            0x68,  # ASCII 'h'
            0x65,  # ASCII 'e'
            0x6c,  # ASCII 'l'
            0x6c,  # ASCII 'l'
            0x6f,  # ASCII 'o'
        ])

        result = tlv_parser.decode(data)[0]
        expected_result = {TlvCode.state: 3, TlvCode.identifier: 'hello'}
        self.assertEqual(result, expected_result)

        with self.assertRaises(ValueError):
            tlv_parser.decode(bytes([
                0xfa,  # unknown TlvCode
            ]))

        with self.assertRaises(ValueError):
            tlv_parser.decode(
                bytes([
                    0x01,  # identifier (string type)
                    0x01,  # 1 byte value size
                    0xf0,  # invalid unicode symbol
                ]))

        with self.assertRaises(ValueError):
            tlv_parser.decode(
                bytes([
                    0x00,  # method (integer type)
                    0x02,  # 2 byte value size
                    0x00,  # first integer byte
                    0x00,  # second integer byte (only 1-byte length integers is supported)
                ]))
Esempio n. 8
0
async def pairings(request: Request) -> Response:
    logger.debug('/pairings called')

    config = request.global_context['config']

    if config.get_pairing(request.context['ios_device_pairing_id'])[2] != ControllerPermission.admin:
        logger.error('Controller without admin permission is trying to call /pairings')
        return Response(PAIRING_CONTENT_TYPE, data=tlv_parser.encode([{
            TlvCode.state: TlvState.m2,
            TlvCode.error: TlvError.authentication,
        }]))

    parsed_body = tlv_parser.decode(await request.read())[0]
    method = parsed_body.get(TlvCode.method)
    requested_state = parsed_body.get(TlvCode.state)
    keep_alive = True

    if method == TlvMethod.list_pairings and requested_state == TlvState.m1:
        logger.debug('/pairings list_pairings called')
        result = list_pairings(config)
    elif method == TlvMethod.add_pairing and requested_state == TlvState.m1:
        logger.debug('/pairings add_pairing called')
        ios_device_pairing_id = parsed_body[TlvCode.identifier]
        ios_device_public_key = parsed_body[TlvCode.public_key]
        permission = parsed_body[TlvCode.permissions]
        result = add_pairing(config, ios_device_pairing_id, ios_device_public_key, ControllerPermission(permission))
    elif method == TlvMethod.remove_pairing and requested_state == TlvState.m1:
        logger.debug('/pairings remove_pairing called')
        ios_device_pairing_id = parsed_body[TlvCode.identifier]
        result = remove_pairing(config, ios_device_pairing_id)
        if not config.get_pairing(ios_device_pairing_id)[0]:
            keep_alive = False
    else:
        raise ValueError('Unknown data received: {}'.format(parsed_body))

    return Response(PAIRING_CONTENT_TYPE, data=tlv_parser.encode(result), keep_alive=keep_alive)