Beispiel #1
0
def first_login(client: GMatrixClient, signer: Signer, username: str) -> User:
    """Login for the first time.

    This relies on the Matrix server having the `eth_auth_provider` plugin
    installed, the plugin will automatically create the user on the first
    login. The plugin requires the password to be the signature of the server
    hostname, verified by the server to prevent account creation spam.

    Displayname is the signature of the whole user_id (including homeserver),
    to be verified by other peers and prevent impersonation attacks.
    """
    server_url = client.api.base_url
    server_name = urlparse(server_url).netloc

    # The plugin `eth_auth_provider` expects a signature of the server_name as
    # the user's password.
    #
    # For a honest matrix server:
    #
    # - This prevents impersonation attacks / name squatting, since the plugin
    # will validate the username by recovering the adddress from the signature
    # and check the recovered address and the username matches.
    #
    # For a badly configured server (one without the plugin):
    #
    # - An attacker can front run and register the username before the honest
    # user:
    #    - Because the attacker cannot guess the correct password, when the
    #    honest node tries to login it will fail, which tells us the server is
    #    improperly configured and should be blacklisted.
    #    - The attacker cannot forge a signature to use as a display name, so
    #    the partner node can tell there is a malicious node trying to
    #    eavesdrop the conversation and that matrix server should be
    #    blacklisted.
    # - The username is available, but because the plugin is not installed the
    # login will fail since the user is not registered. Here too one can infer
    # the server is improperly configured and blacklist the server.
    password = encode_hex(signer.sign(server_name.encode()))

    # Disabling sync because login is done before the transport is fully
    # initialized, i.e. the inventory rooms don't have the callbacks installed.
    client.login(username, password, sync=False)

    # Because this is the first login, the display name has to be set, this
    # prevents the impersonation metioned above. subsequent calls will reuse
    # the authentication token and the display name will be properly set.
    signature_bytes = signer.sign(client.user_id.encode())
    signature_hex = encode_hex(signature_bytes)

    user = client.get_user(client.user_id)
    user.set_display_name(signature_hex)

    log.debug(
        "Logged to a new server",
        node=to_checksum_address(username),
        homeserver=server_name,
        server_url=server_url,
    )
    return user
Beispiel #2
0
def make_signed_balance_proof_from_unsigned(
        unsigned: BalanceProofUnsignedState,
        signer: Signer) -> BalanceProofSignedState:
    balance_hash = hash_balance_data(
        transferred_amount=unsigned.transferred_amount,
        locked_amount=unsigned.locked_amount,
        locksroot=unsigned.locksroot,
    )

    additional_hash = make_additional_hash()
    data_to_sign = balance_proof.pack_balance_proof(
        balance_hash=balance_hash,
        additional_hash=additional_hash,
        canonical_identifier=unsigned.canonical_identifier,
        nonce=unsigned.nonce,
    )

    signature = signer.sign(data=data_to_sign)
    sender = signer.address

    return BalanceProofSignedState(
        nonce=unsigned.nonce,
        transferred_amount=unsigned.transferred_amount,
        locked_amount=unsigned.locked_amount,
        locksroot=unsigned.locksroot,
        message_hash=additional_hash,
        signature=signature,
        sender=sender,
        canonical_identifier=unsigned.canonical_identifier,
    )
Beispiel #3
0
 def sign(self, signer: Signer) -> None:
     """This method signs twice:
         - the `non_closing_signature` for the balance proof update
         - the `reward_proof_signature` for the monitoring request
     """
     self.non_closing_signature = self.balance_proof._sign(signer)
     message_data = self._data_to_sign()
     self.signature = signer.sign(data=message_data)
Beispiel #4
0
def login_or_register(client: GMatrixClient,
                      signer: Signer,
                      prev_user_id: str = None,
                      prev_access_token: str = None) -> User:
    """Login to a Raiden matrix server with password and displayname proof-of-keys

    - Username is in the format: 0x<eth_address>(.<suffix>)?, where the suffix is not required,
    but a deterministic (per-account) random 8-hex string to prevent DoS by other users registering
    our address
    - Password is the signature of the server hostname, verified by the server to prevent account
    creation spam
    - Displayname currently is the signature of the whole user_id (including homeserver), to be
    verified by other peers. May include in the future other metadata such as protocol version

    Params:
        client: GMatrixClient instance configured with desired homeserver
        signer: raiden.utils.signer.Signer instance for signing password and displayname
        prev_user_id: (optional) previously persisted client.user_id. Must match signer's account
        prev_access_token: (optional) previously persisted client.access_token for prev_user_id
    Returns:
        Own matrix_client.User
    """

    server_url = client.api.base_url
    server_name = urlparse(server_url).netloc

    base_username = to_normalized_address(signer.address)

    user = _check_previous_login(client, prev_user_id, prev_access_token,
                                 base_username, server_name)

    if user is None:
        # password is signed server address
        password = encode_hex(signer.sign(server_name.encode()))
        seed = signer.sign(b"seed")[-32:]

        _try_login_or_register(client, base_username, password, server_name,
                               server_url, seed)

        name = encode_hex(signer.sign(client.user_id.encode()))
        user = client.get_user(client.user_id)
        user.set_display_name(name)

    log.info("Login or register for Hub Node is successfully run")
    return user
Beispiel #5
0
def login_or_register(
    client: GMatrixClient,
    signer: Signer,
    prev_user_id: str = None,
    prev_access_token: str = None,
) -> User:
    """Login to a Raiden matrix server with password and displayname proof-of-keys

    - Username is in the format: 0x<eth_address>(.<suffix>)?, where the suffix is not required,
    but a deterministic (per-account) random 8-hex string to prevent DoS by other users registering
    our address
    - Password is the signature of the server hostname, verified by the server to prevent account
    creation spam
    - Displayname currently is the signature of the whole user_id (including homeserver), to be
    verified by other peers. May include in the future other metadata such as protocol version

    Params:
        client: GMatrixClient instance configured with desired homeserver
        signer: raiden.utils.signer.Signer instance for signing password and displayname
        prev_user_id: (optional) previous persisted client.user_id. Must match signer's account
        prev_access_token: (optional) previous persistend client.access_token for prev_user_id
    Returns:
        Own matrix_client.User
    """
    server_url = client.api.base_url
    server_name = urlparse(server_url).netloc

    base_username = to_normalized_address(signer.address)
    _match_user = re.match(
        f'^@{re.escape(base_username)}.*:{re.escape(server_name)}$',
        prev_user_id or '',
    )
    if _match_user:  # same user as before
        log.debug('Trying previous user login', user_id=prev_user_id)
        client.set_access_token(user_id=prev_user_id, token=prev_access_token)

        try:
            client.api.get_devices()
        except MatrixRequestError as ex:
            log.debug(
                'Couldn\'t use previous login credentials, discarding',
                prev_user_id=prev_user_id,
                _exception=ex,
            )
        else:
            prev_sync_limit = client.set_sync_limit(0)
            client._sync()  # initial_sync
            client.set_sync_limit(prev_sync_limit)
            log.debug('Success. Valid previous credentials',
                      user_id=prev_user_id)
            return client.get_user(client.user_id)
    elif prev_user_id:
        log.debug(
            'Different server or account, discarding',
            prev_user_id=prev_user_id,
            current_address=base_username,
            current_server=server_name,
        )

    # password is signed server address
    password = encode_hex(signer.sign(server_name.encode()))
    rand = None
    # try login and register on first 5 possible accounts
    for i in range(JOIN_RETRIES):
        username = base_username
        if i:
            if not rand:
                rand = Random(
                )  # deterministic, random secret for username suffixes
                # initialize rand for seed (which requires a signature) only if/when needed
                rand.seed(int.from_bytes(signer.sign(b'seed')[-32:], 'big'))
            username = f'{username}.{rand.randint(0, 0xffffffff):08x}'

        try:
            client.login(username, password, sync=False)
            prev_sync_limit = client.set_sync_limit(0)
            client._sync()  # when logging, do initial_sync with limit=0
            client.set_sync_limit(prev_sync_limit)
            log.debug(
                'Login',
                homeserver=server_name,
                server_url=server_url,
                username=username,
            )
            break
        except MatrixRequestError as ex:
            if ex.code != 403:
                raise
            log.debug(
                'Could not login. Trying register',
                homeserver=server_name,
                server_url=server_url,
                username=username,
            )
            try:
                client.register_with_password(username, password)
                log.debug(
                    'Register',
                    homeserver=server_name,
                    server_url=server_url,
                    username=username,
                )
                break
            except MatrixRequestError as ex:
                if ex.code != 400:
                    raise
                log.debug('Username taken. Continuing')
                continue
    else:
        raise ValueError('Could not register or login!')

    name = encode_hex(signer.sign(client.user_id.encode()))
    user = client.get_user(client.user_id)
    user.set_display_name(name)
    return user
Beispiel #6
0
def first_login(client: GMatrixClient, signer: Signer, username: str,
                cap_str: str) -> User:
    """Login within a server.

    There are multiple cases where a previous auth token can become invalid and
    a new login is necessary:

    - The server is configured to automatically invalidate tokens after a while
      (not the default)
    - A server operator may manually wipe or invalidate existing access tokens
    - A node may have roamed to a different server (e.g. because the original
      server was temporarily unavailable) and is now 'returning' to the
      previously used server.

    This relies on the Matrix server having the `eth_auth_provider` plugin
    installed, the plugin will automatically create the user on the first
    login. The plugin requires the password to be the signature of the server
    hostname, verified by the server to prevent account creation spam.

    Displayname is the signature of the whole user_id (including homeserver),
    to be verified by other peers and prevent impersonation attacks.
    """
    server_url = client.api.base_url
    server_name = urlparse(server_url).netloc

    # The plugin `eth_auth_provider` expects a signature of the server_name as
    # the user's password.
    #
    # For a honest matrix server:
    #
    # - This prevents impersonation attacks / name squatting, since the plugin
    # will validate the username by recovering the address from the signature
    # and check the recovered address and the username matches.
    #
    # For a badly configured server (one without the plugin):
    #
    # - An attacker can front run and register the username before the honest
    # user:
    #    - Because the attacker cannot guess the correct password, when the
    #    honest node tries to login it will fail, which tells us the server is
    #    improperly configured and should be blacklisted.
    #    - The attacker cannot forge a signature to use as a display name, so
    #    the partner node can tell there is a malicious node trying to
    #    eavesdrop the conversation and that matrix server should be
    #    blacklisted.
    # - The username is available, but because the plugin is not installed the
    # login will fail since the user is not registered. Here too one can infer
    # the server is improperly configured and blacklist the server.
    password = encode_hex(signer.sign(server_name.encode()))

    # Disabling sync because login is done before the transport is fully
    # initialized, i.e. the inventory rooms don't have the callbacks installed.
    client.login(username, password, sync=False, device_id="raiden")

    # Because this is the first login, the display name has to be set, this
    # prevents the impersonation mentioned above. subsequent calls will reuse
    # the authentication token and the display name will be properly set.
    signature_bytes = signer.sign(client.user_id.encode())
    signature_hex = encode_hex(signature_bytes)

    user = client.get_user(client.user_id)

    try:
        current_display_name = user.get_display_name()
    except MatrixRequestError as ex:
        # calling site
        log.error(
            "Ignoring Matrix error in `get_display_name`",
            exc_info=ex,
            user_id=user.user_id,
        )
        current_display_name = ""

    # Only set the display name if necessary, since this is a slow operation.
    if current_display_name != signature_hex:
        user.set_display_name(signature_hex)

    try:
        current_capabilities = user.get_avatar_url() or ""
    except MatrixRequestError as ex:
        log.error(
            "Ignoring Matrix error in `get_avatar_url`",
            exc_info=ex,
            user_id=user.user_id,
        )
        current_capabilities = ""

    # Only set the capabilities if necessary.
    if current_capabilities != cap_str:
        user.set_avatar_url(cap_str)

    log.debug(
        "Logged in",
        node=to_checksum_address(username),
        homeserver=server_name,
        server_url=server_url,
    )
    return user
Beispiel #7
0
 def sign(self, signer: Signer) -> None:
     """ Sign message using signer. """
     message_data = self._data_to_sign()
     self.signature = signer.sign(data=message_data)
Beispiel #8
0
 def _sign(self, signer: Signer) -> Signature:
     """Internal function for the overall `sign` function of `RequestMonitoring`.
     """
     # Important: we don't write the signature to `.signature`
     data = self._data_to_sign()
     return signer.sign(data)