def test_read_challenge_transaction_fee_bump_transaction_raise(self):
     inner_keypair = Keypair.from_secret(
         "SBKTIFHJSS3JJWEZO2W74DZSA45WZU56LOL3AY7GAW63BXPEJQFYV53E"
     )
     inner_source = Account(inner_keypair.public_key, 7)
     destination = "GDQERENWDDSQZS7R7WKHZI3BSOYMV3FSWR7TFUYFTKQ447PIX6NREOJM"
     amount = "2000.0000000"
     inner_tx = (
         TransactionBuilder(
             inner_source, Network.TESTNET_NETWORK_PASSPHRASE, 200, v1=True
         )
         .append_payment_op(destination=destination, amount=amount, asset_code="XLM")
         .add_time_bounds(0, 0)
         .build()
     )
     inner_tx.sign(inner_keypair)
     fee_source = Keypair.from_secret(
         "SB7ZMPZB3YMMK5CUWENXVLZWBK4KYX4YU5JBXQNZSK2DP2Q7V3LVTO5V"
     )
     base_fee = 200
     fee_bump_tx = TransactionBuilder.build_fee_bump_transaction(
         fee_source.public_key,
         base_fee,
         inner_tx,
         Network.TESTNET_NETWORK_PASSPHRASE,
     )
     fee_bump_tx.sign(fee_source)
     challenge = fee_bump_tx.to_xdr()
     with pytest.raises(
         ValueError,
         match="Invalid challenge, expected a TransactionEnvelope but received a FeeBumpTransactionEnvelope.",
     ):
         read_challenge_transaction(
             challenge, inner_keypair.public_key, Network.TESTNET_NETWORK_PASSPHRASE
         )
Beispiel #2
0
def example_verify_challenge_tx_threshold():
    # Server builds challenge transaction
    challenge_tx = build_challenge_transaction(
        server_keypair.secret, client_master_keypair.public_key, home_domain,
        web_auth_domain, network_passphrase, 300)

    # Client reads and signs challenge transaction
    tx, tx_client_account_id, _ = read_challenge_transaction(
        challenge_tx, server_keypair.public_key, home_domain, web_auth_domain,
        network_passphrase)
    if tx_client_account_id != client_master_keypair.public_key:
        print("Error: challenge tx is not for expected client account")
        return
    tx.sign(client_signer_keypair1)
    tx.sign(client_signer_keypair2)
    signed_challenge_tx = tx.to_xdr()

    # Server verifies signed challenge transaction
    _, tx_client_account_id, _ = read_challenge_transaction(
        challenge_tx, server_keypair.public_key, home_domain, web_auth_domain,
        network_passphrase)
    client_account_exists = False
    horizon_client_account = None
    try:
        horizon_client_account = server.load_account(
            client_master_keypair.public_key)
        client_account_exists = True
    except NotFoundError:
        print("Account does not exist, use master key to verify")

    if client_account_exists:
        # gets list of signers from account
        signers = horizon_client_account.load_ed25519_public_key_signers()
        # chooses the threshold to require: low, med or high
        threshold = horizon_client_account.thresholds.med_threshold
        try:
            signers_found = verify_challenge_transaction_threshold(
                signed_challenge_tx, server_keypair.public_key, home_domain,
                web_auth_domain, network_passphrase, threshold, signers)
        except InvalidSep10ChallengeError as e:
            print("You should handle possible exceptions:")
            print(e)
            return

        print("Client Signers Verified:")
        for signer in signers_found:
            print("Signer: {}, weight: {}".format(signer.account_id,
                                                  signer.weight))
    else:
        # verifies that master key has signed challenge transaction
        try:
            verify_challenge_transaction_signed_by_client_master_key(
                signed_challenge_tx, server_keypair.public_key, home_domain,
                web_auth_domain, network_passphrase)
            print("Client Master Key Verified.")
        except InvalidSep10ChallengeError as e:
            print("You should handle possible exceptions:")
            print(e)
Beispiel #3
0
    def _generate_jwt(envelope_xdr: str) -> str:
        """
        Generates the JSON web token from the challenge transaction XDR.

        See: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md#token
        """
        issued_at = time.time()
        transaction_envelope, source_account, _ = read_challenge_transaction(
            challenge_transaction=envelope_xdr,
            server_account_id=settings.SIGNING_KEY,
            home_domains=settings.SEP10_HOME_DOMAINS,
            network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
        )
        logger.info(
            f"Challenge verified, generating SEP-10 token for account {source_account}"
        )
        hash_hex = binascii.hexlify(transaction_envelope.hash()).decode()
        jwt_dict = {
            "iss": os.path.join(settings.HOST_URL, "auth"),
            "sub": source_account,
            "iat": issued_at,
            "exp": issued_at + 24 * 60 * 60,
            "jti": hash_hex,
        }
        encoded_jwt = jwt.encode(jwt_dict,
                                 settings.SERVER_JWT_KEY,
                                 algorithm="HS256")
        return encoded_jwt.decode("ascii")
Beispiel #4
0
def test_get_client_attribution_optional_not_allowed_client_domain(
        mock_fetch_stellar_toml, client):
    kp = Keypair.random()
    response = client.get(AUTH_PATH, {
        "account": kp.public_key,
        "client_domain": "test.com"
    })

    content = response.json()
    assert response.status_code == 200, json.dumps(content, indent=2)

    challenge, _, _ = read_challenge_transaction(
        challenge_transaction=content["transaction"],
        server_account_id=settings.SIGNING_KEY,
        home_domains=urlparse(settings.HOST_URL).netloc,
        web_auth_domain=urlparse(settings.HOST_URL).netloc,
        network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
    )
    assert content["network_passphrase"] == settings.STELLAR_NETWORK_PASSPHRASE

    mock_fetch_stellar_toml.assert_not_called()

    client_domain = None
    client_domain_signing_key = None
    for op in challenge.transaction.operations:
        if isinstance(op, ManageData) and op.data_name == "client_domain":
            client_domain = op.data_value.decode()
            client_domain_signing_key = op.source

    assert client_domain is None
    assert client_domain_signing_key is None
Beispiel #5
0
    def _generate_jwt(envelope_xdr: str, client_domain: str = None) -> str:
        """
        Generates the JSON web token from the challenge transaction XDR.

        See: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md#token
        """
        transaction_envelope, source_account, _ = read_challenge_transaction(
            challenge_transaction=envelope_xdr,
            server_account_id=settings.SIGNING_KEY,
            home_domains=settings.SEP10_HOME_DOMAINS,
            web_auth_domain=urlparse(settings.HOST_URL).netloc,
            network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
        )
        logger.info(
            f"Challenge verified, generating SEP-10 token for account {source_account}"
        )
        # set iat value to minimum timebound of the challenge so that the JWT returned
        # for a given challenge is always the same.
        # https://github.com/stellar/stellar-protocol/pull/982
        issued_at = transaction_envelope.transaction.time_bounds.min_time
        jwt_dict = {
            "iss": os.path.join(settings.HOST_URL, "auth"),
            "sub": source_account,
            "iat": issued_at,
            "exp": issued_at + 24 * 60 * 60,
            "jti": transaction_envelope.hash().hex(),
            "client_domain": client_domain,
        }
        encoded_jwt = jwt.encode(jwt_dict,
                                 settings.SERVER_JWT_KEY,
                                 algorithm="HS256")
        return encoded_jwt.decode("ascii")
Beispiel #6
0
    def _validate_challenge_xdr(envelope_xdr: str):
        """
        Validate the provided TransactionEnvelope XDR (base64 string).

        If the source account of the challenge transaction exists, verify the weight
        of the signers on the challenge are signers for the account and the medium
        threshold on the account is met by those signers.

        If the source account does not exist, verify that the keypair used as the
        source for the challenge transaction has signed the challenge. This is
        sufficient because newly created accounts have their own keypair as signer
        with a weight greater than the default thresholds.
        """
        server_key = settings.SIGNING_KEY
        net = settings.STELLAR_NETWORK_PASSPHRASE

        logger.info("Validating challenge transaction")
        try:
            tx_envelope, account_id = read_challenge_transaction(
                envelope_xdr, server_key, DOMAIN_NAME, net)
        except InvalidSep10ChallengeError as e:
            err_msg = f"Error while validating challenge: {str(e)}"
            logger.error(err_msg)
            raise ValueError(err_msg)

        try:
            account = settings.HORIZON_SERVER.load_account(account_id)
        except NotFoundError:
            logger.warning(
                "Account does not exist, using client's master key to verify")
            try:
                verify_challenge_transaction_signed_by_client_master_key(
                    envelope_xdr, server_key, DOMAIN_NAME, net)
                if len(tx_envelope.signatures) != 2:
                    raise InvalidSep10ChallengeError(
                        "There is more than one client signer on a challenge "
                        "transaction for an account that doesn't exist")
            except InvalidSep10ChallengeError as e:
                logger.info(
                    f"Missing or invalid signature(s) for {account_id}: {str(e)}"
                )
                raise ValueError(str(e))
            else:
                logger.info("Challenge verified using client's master key")
                return

        signers = account.load_ed25519_public_key_signers()
        threshold = account.thresholds.med_threshold
        try:
            signers_found = verify_challenge_transaction_threshold(
                envelope_xdr, server_key, DOMAIN_NAME, net, threshold, signers)
        except InvalidSep10ChallengeError as e:
            logger.info(str(e))
            raise ValueError(str(e))

        logger.info(
            f"Challenge verified using account signers: {signers_found}")
Beispiel #7
0
def _check_server_transaction(transaction_xdr, anchor_name):
    LOGGER.info("Validating challenge transaction")
    try:
        transaction_envelope, account_id = read_challenge_transaction(
            transaction_xdr, 
            anchor_name['SIGNING_KEY'], 
            _get_network_passphrase(),
        )
    except InvalidSep10ChallengeError as error:
        raise ValueError(error)

    return transaction_envelope
Beispiel #8
0
def test_get_success(client):
    kp = Keypair.random()
    response = client.get(AUTH_PATH, {"account": kp.public_key})

    content = response.json()
    assert response.status_code == 200, json.dumps(content, indent=2)

    challenge, _, _ = read_challenge_transaction(
        challenge_transaction=content["transaction"],
        server_account_id=settings.SIGNING_KEY,
        home_domains=urlparse(settings.HOST_URL).netloc,
        web_auth_domain=urlparse(settings.HOST_URL).netloc,
        network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
    )
    assert content["network_passphrase"] == settings.STELLAR_NETWORK_PASSPHRASE
    def test_read_challenge_transaction_mux_server_id_raise(self):
        server_kp = Keypair.random()
        client_account_id = "GBDIT5GUJ7R5BXO3GJHFXJ6AZ5UQK6MNOIDMPQUSMXLIHTUNR2Q5CFNF"
        timeout = 600
        network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE
        anchor_name = "SDF"

        challenge = build_challenge_transaction(
            server_secret=server_kp.secret,
            client_account_id=client_account_id,
            anchor_name=anchor_name,
            network_passphrase=network_passphrase,
            timeout=timeout,
        )
        with pytest.raises(
                ValueError,
                match=
                "Invalid server_account_id, multiplexed account are not supported.",
        ):
            read_challenge_transaction(
                challenge,
                "MAAAAAAAAAAAJURAAB2X52XFQP6FBXLGT6LWOOWMEXWHEWBDVRZ7V5WH34Y22MPFBHUHY",
                network_passphrase,
            )
Beispiel #10
0
def test_get_success_client_attribution_is_allowed(mock_fetch_stellar_toml,
                                                   client):
    client_kp = Keypair.random()
    client_domain_kp = Keypair.random()
    request_client_domain = "test.com"
    mock_fetch_stellar_toml.return_value = {
        "SIGNING_KEY": client_domain_kp.public_key
    }
    response = client.get(
        AUTH_PATH,
        {
            "account": client_kp.public_key,
            "client_domain": request_client_domain
        },
    )

    content = response.json()
    assert response.status_code == 200, json.dumps(content, indent=2)

    challenge, _, _ = read_challenge_transaction(
        challenge_transaction=content["transaction"],
        server_account_id=settings.SIGNING_KEY,
        home_domains=urlparse(settings.HOST_URL).netloc,
        web_auth_domain=urlparse(settings.HOST_URL).netloc,
        network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
    )
    assert content["network_passphrase"] == settings.STELLAR_NETWORK_PASSPHRASE

    mock_fetch_stellar_toml.assert_called_once()
    args, kwargs = mock_fetch_stellar_toml.call_args
    assert args[0] == request_client_domain
    assert isinstance(kwargs.get("client"), RequestsClient)
    assert (kwargs["client"].request_timeout ==
            settings.SEP10_CLIENT_ATTRIBUTION_REQUEST_TIMEOUT)

    client_domain = None
    client_domain_signing_key = None
    for op in challenge.transaction.operations:
        if isinstance(op, ManageData) and op.data_name == "client_domain":
            client_domain = op.data_value.decode()
            client_domain_signing_key = op.source

    assert client_domain == request_client_domain
    assert client_domain_signing_key == client_domain_kp.public_key
Beispiel #11
0
    def _validate_challenge_xdr(envelope_xdr: str):
        """
        Validate the provided TransactionEnvelope XDR (base64 string).

        If the source account of the challenge transaction exists, verify the weight
        of the signers on the challenge are signers for the account and the medium
        threshold on the account is met by those signers.

        If the source account does not exist, verify that the keypair used as the
        source for the challenge transaction has signed the challenge. This is
        sufficient because newly created accounts have their own keypair as signer
        with a weight greater than the default thresholds.
        """
        logger.info("Validating challenge transaction")
        generic_err_msg = gettext("error while validating challenge: %s")
        try:
            tx_envelope, account_id, _ = read_challenge_transaction(
                challenge_transaction=envelope_xdr,
                server_account_id=settings.SIGNING_KEY,
                home_domains=settings.SEP10_HOME_DOMAINS,
                web_auth_domain=urlparse(settings.HOST_URL).netloc,
                network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
            )
        except InvalidSep10ChallengeError as e:
            return None, render_error_response(generic_err_msg % (str(e)))

        client_domain = None
        for operation in tx_envelope.transaction.operations:
            if (isinstance(operation, ManageData)
                    and operation.data_name == "client_domain"):
                client_domain = operation.data_value.decode()
                break

        try:
            account = settings.HORIZON_SERVER.load_account(account_id)
        except NotFoundError:
            logger.info(
                "Account does not exist, using client's master key to verify")
            try:
                verify_challenge_transaction_signed_by_client_master_key(
                    challenge_transaction=envelope_xdr,
                    server_account_id=settings.SIGNING_KEY,
                    home_domains=settings.SEP10_HOME_DOMAINS,
                    web_auth_domain=urlparse(settings.HOST_URL).netloc,
                    network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
                )
                if (client_domain and len(tx_envelope.signatures) != 3) or (
                        not client_domain
                        and len(tx_envelope.signatures) != 2):
                    raise InvalidSep10ChallengeError(
                        gettext(
                            "There is more than one client signer on a challenge "
                            "transaction for an account that doesn't exist"))
            except InvalidSep10ChallengeError as e:
                logger.info(
                    f"Missing or invalid signature(s) for {account_id}: {str(e)}"
                )
                return None, render_error_response(generic_err_msg % (str(e)))
            else:
                logger.info("Challenge verified using client's master key")
                return client_domain, None

        signers = account.load_ed25519_public_key_signers()
        threshold = account.thresholds.med_threshold
        try:
            signers_found = verify_challenge_transaction_threshold(
                challenge_transaction=envelope_xdr,
                server_account_id=settings.SIGNING_KEY,
                home_domains=settings.SEP10_HOME_DOMAINS,
                web_auth_domain=urlparse(settings.HOST_URL).netloc,
                network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
                threshold=threshold,
                signers=signers,
            )
        except InvalidSep10ChallengeError as e:
            return None, render_error_response(generic_err_msg % (str(e)))

        logger.info(
            f"Challenge verified using account signers: {[s.account_id for s in signers_found]}"
        )
        return client_domain, None