def test_verify_challenge_transaction_signed_by_client_raise_not_signed(self): server_kp = Keypair.random() client_kp = Keypair.random() timeout = 600 network_passphrase = Network.PUBLIC_NETWORK_PASSPHRASE anchor_name = "SDF" challenge = build_challenge_transaction( server_secret=server_kp.secret, client_account_id=client_kp.public_key, anchor_name=anchor_name, network_passphrase=network_passphrase, timeout=timeout, ) transaction = TransactionEnvelope.from_xdr(challenge, network_passphrase) challenge_tx = transaction.to_xdr() with pytest.raises( InvalidSep10ChallengeError, match="Transaction not signed by client: {}.".format(client_kp.public_key), ): verify_challenge_transaction_signed_by_client_master_key( challenge_tx, server_kp.public_key, network_passphrase )
def test_verify_challenge_transaction_signed_by_client_master_key_raise_unrecognized_signatures( self, ): server_kp = Keypair.random() client_kp_a = Keypair.random() client_kp_unrecognized = Keypair.random() timeout = 600 network_passphrase = Network.PUBLIC_NETWORK_PASSPHRASE anchor_name = "SDF" challenge = build_challenge_transaction( server_secret=server_kp.secret, client_account_id=client_kp_a.public_key, anchor_name=anchor_name, network_passphrase=network_passphrase, timeout=timeout, ) transaction = TransactionEnvelope.from_xdr(challenge, network_passphrase) transaction.sign(client_kp_a) transaction.sign(client_kp_unrecognized) challenge_tx = transaction.to_xdr() with pytest.raises(InvalidSep10ChallengeError, match="Transaction has unrecognized signatures."): verify_challenge_transaction_signed_by_client_master_key( challenge_tx, server_kp.public_key, network_passphrase)
def test_verify_challenge_transaction_signed_by_client_raise_not_signed( self): server_kp = Keypair.random() client_kp = Keypair.random() timeout = 600 network_passphrase = Network.PUBLIC_NETWORK_PASSPHRASE home_domain = "example.com" challenge = build_challenge_transaction( server_secret=server_kp.secret, client_account_id=client_kp.public_key, home_domain=home_domain, network_passphrase=network_passphrase, timeout=timeout, ) transaction = TransactionEnvelope.from_xdr(challenge, network_passphrase) challenge_tx = transaction.to_xdr() with pytest.raises( InvalidSep10ChallengeError, match="Transaction not signed by any client signer.", ): verify_challenge_transaction_signed_by_client_master_key( challenge_tx, server_kp.public_key, home_domain, network_passphrase)
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)
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}")
def test_verify_challenge_transaction_signed_by_client(self): server_kp = Keypair.random() client_kp = Keypair.random() timeout = 600 network_passphrase = Network.PUBLIC_NETWORK_PASSPHRASE anchor_name = "SDF" challenge = build_challenge_transaction( server_secret=server_kp.secret, client_account_id=client_kp.public_key, anchor_name=anchor_name, network_passphrase=network_passphrase, timeout=timeout, ) transaction = TransactionEnvelope.from_xdr(challenge, network_passphrase) transaction.sign(client_kp) challenge_tx = transaction.to_xdr() verify_challenge_transaction_signed_by_client_master_key( challenge_tx, server_kp.public_key, network_passphrase )
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