def test_verify_challenge_transaction_domain_name_mismatch_raise(self): server_kp = Keypair.random() client_kp = Keypair.random() timeout = 600 network_passphrase = Network.PUBLIC_NETWORK_PASSPHRASE domain_name = "example.com" invalid_domain_name = "invalid_example.com" challenge = build_challenge_transaction( server_secret=server_kp.secret, client_account_id=client_kp.public_key, domain_name=domain_name, network_passphrase=network_passphrase, timeout=timeout, ) transaction = TransactionEnvelope.from_xdr(challenge, network_passphrase) transaction.sign(client_kp) challenge_tx = transaction.to_xdr() with pytest.raises( InvalidSep10ChallengeError, match="The transaction's operation key name " "does not include the expected home domain.", ): verify_challenge_transaction( challenge_tx, server_kp.public_key, invalid_domain_name, network_passphrase, )
def test_post_success_account_doesnt_exist(mock_load_account, client): kp = Keypair.random() challenge_xdr = build_challenge_transaction( server_secret=settings.SIGNING_SEED, client_account_id=kp.public_key, home_domain=settings.SEP10_HOME_DOMAINS[0], web_auth_domain=urlparse(settings.HOST_URL).netloc, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, ) envelope = TransactionEnvelope.from_xdr( challenge_xdr, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE) envelope.sign(kp) signed_challenge_xdr = envelope.to_xdr() mock_load_account.side_effect = NotFoundError(MagicMock()) response = client.post(AUTH_PATH, {"transaction": signed_challenge_xdr}) content = response.json() assert response.status_code == 200, json.dumps(content, indent=2) jwt_contents = jwt.decode(content["token"], settings.SERVER_JWT_KEY, algorithms=["HS256"]) iat = jwt_contents.pop("iat") exp = jwt_contents.pop("exp") assert exp - iat == 24 * 60 * 60 assert jwt_contents == { "iss": os.path.join(settings.HOST_URL, "auth"), "sub": kp.public_key, "jti": envelope.hash().hex(), "client_domain": None, }
def test_verify_challenge_tx_source_is_different_to_server_account_id( self): server_kp = Keypair.random() client_kp = Keypair.random() network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE anchor_name = "SDF" challenge = build_challenge_transaction(server_kp.secret, client_kp.public_key, anchor_name, network_passphrase) transaction = TransactionEnvelope.from_xdr(challenge, Network(network_passphrase)) transaction.sign(client_kp) challenge_tx = transaction.to_xdr() with pytest.raises( InvalidSep10ChallengeError, match= "Transaction source account is not equal to server's account.", ): verify_challenge_transaction(challenge_tx, Keypair.random().public_key, network_passphrase)
def test_post_fails_account_doesnt_exist_no_client_attribution_signature( mock_load_account, client): kp = Keypair.random() client_domain_kp = Keypair.random() challenge_xdr = build_challenge_transaction( server_secret=settings.SIGNING_SEED, client_account_id=kp.public_key, home_domain=settings.SEP10_HOME_DOMAINS[0], web_auth_domain=urlparse(settings.HOST_URL).netloc, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, client_domain="test.com", client_signing_key=client_domain_kp.public_key, ) envelope = TransactionEnvelope.from_xdr( challenge_xdr, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE) envelope.sign(kp) signed_challenge_xdr = envelope.to_xdr() mock_load_account.side_effect = NotFoundError(MagicMock()) response = client.post(AUTH_PATH, {"transaction": signed_challenge_xdr}) content = response.json() assert response.status_code == 400, json.dumps(content, indent=2) assert ( content["error"] == "error while validating challenge: " "Transaction not signed by the source account of the 'client_domain' ManageData operation" )
def test_verify_transaction_signatures(self): server_kp = Keypair.random() client_kp_a = Keypair.random() client_kp_b = Keypair.random() client_kp_c = 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_b) transaction.sign(client_kp_c) signers = [ Ed25519PublicKeySigner(client_kp_a.public_key, 1), Ed25519PublicKeySigner(client_kp_b.public_key, 2), Ed25519PublicKeySigner(client_kp_c.public_key, 3), Ed25519PublicKeySigner(Keypair.random().public_key, 4), ] signers_found = _verify_transaction_signatures(transaction, signers) assert signers_found == [ Ed25519PublicKeySigner(client_kp_a.public_key, 1), Ed25519PublicKeySigner(client_kp_b.public_key, 2), Ed25519PublicKeySigner(client_kp_c.public_key, 3), ]
def test_challenge_transaction(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, ) transaction = TransactionEnvelope.from_xdr( challenge, network_passphrase ).transaction assert len(transaction.operations) == 1 op = transaction.operations[0] assert isinstance(op, ManageData) assert op.data_name == "SDF auth" assert len(op.data_value) == 64 assert len(base64.b64decode(op.data_value)) == 48 assert op.source == client_account_id now = int(time.time()) assert now - 3 < transaction.time_bounds.min_time < now + 3 assert ( transaction.time_bounds.max_time - transaction.time_bounds.min_time == timeout ) assert transaction.source.public_key == server_kp.public_key assert transaction.sequence == 0
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 test_verify_challenge_transaction_signers_raise_no_signers(self): server_kp = Keypair.random() client_kp_a = Keypair.random() client_kp_b = Keypair.random() client_kp_c = 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_b) transaction.sign(client_kp_c) challenge_tx = transaction.to_xdr() signers = [] with pytest.raises(InvalidSep10ChallengeError, match="No signers provided."): verify_challenge_transaction_signers( challenge_tx, server_kp.public_key, network_passphrase, signers )
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 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 test_challenge_transaction_mux_client_account_id_raise(self): server_kp = Keypair.random() client_account_id = ( "MAAAAAAAAAAAJURAAB2X52XFQP6FBXLGT6LWOOWMEXWHEWBDVRZ7V5WH34Y22MPFBHUHY" ) timeout = 600 network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE anchor_name = "SDF" with pytest.raises( ValueError, match= "Invalid client_account_id, multiplexed account are not supported.", ): build_challenge_transaction( server_secret=server_kp.secret, client_account_id=client_account_id, anchor_name=anchor_name, network_passphrase=network_passphrase, timeout=timeout, )
def _challenge_transaction(client_account): """ Generate the challenge transaction for a client account. This is used in `GET <auth>`, as per SEP 10. Returns the XDR encoding of that transaction. """ challenge_tx_xdr = build_challenge_transaction(server_secret=settings.STELLAR_DISTRIBUTION_ACCOUNT_SEED, client_account_id=client_account, anchor_name=ANCHOR_NAME, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE) return challenge_tx_xdr
def _challenge_transaction(client_account, home_domain): """ Generate the challenge transaction for a client account. This is used in `GET <auth>`, as per SEP 10. Returns the XDR encoding of that transaction. """ return build_challenge_transaction( server_secret=settings.SIGNING_SEED, client_account_id=client_account, home_domain=home_domain, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, timeout=900, )
def test_verify_challenge_transaction_signers_raise_no_server_signature( self): server_kp = Keypair.random() client_kp_a = Keypair.random() client_kp_b = Keypair.random() client_kp_c = Keypair.random() timeout = 600 network_passphrase = Network.PUBLIC_NETWORK_PASSPHRASE domain_name = "example.com" challenge = build_challenge_transaction( server_secret=server_kp.secret, client_account_id=client_kp_a.public_key, domain_name=domain_name, network_passphrase=network_passphrase, timeout=timeout, ) transaction = TransactionEnvelope.from_xdr(challenge, network_passphrase) transaction.signatures = [] transaction.sign(client_kp_a) transaction.sign(client_kp_b) transaction.sign(client_kp_c) challenge_tx = transaction.to_xdr() signers = [ Ed25519PublicKeySigner(client_kp_a.public_key, 1), Ed25519PublicKeySigner(client_kp_b.public_key, 2), Ed25519PublicKeySigner(client_kp_c.public_key, 4), Ed25519PublicKeySigner(Keypair.random().public_key, 255), ] with pytest.raises( InvalidSep10ChallengeError, match="Transaction not signed by server: {}.".format( server_kp.public_key), ): verify_challenge_transaction_signers( challenge_tx, server_kp.public_key, domain_name, network_passphrase, signers, )
def test_verify_challenge_transaction_threshold(self): server_kp = Keypair.random() client_kp_a = Keypair.random() client_kp_b = Keypair.random() client_kp_c = Keypair.random() timeout = 600 network_passphrase = Network.PUBLIC_NETWORK_PASSPHRASE domain_name = "example.com" challenge = build_challenge_transaction( server_secret=server_kp.secret, client_account_id=client_kp_a.public_key, domain_name=domain_name, network_passphrase=network_passphrase, timeout=timeout, ) transaction = TransactionEnvelope.from_xdr(challenge, network_passphrase) transaction.sign(client_kp_a) transaction.sign(client_kp_b) transaction.sign(client_kp_c) challenge_tx = transaction.to_xdr() signers = [ Ed25519PublicKeySigner(client_kp_a.public_key, 1), Ed25519PublicKeySigner(client_kp_b.public_key, 2), Ed25519PublicKeySigner(client_kp_c.public_key, 4), Ed25519PublicKeySigner(Keypair.random().public_key, 255), ] med_threshold = 7 signers_found = verify_challenge_transaction_threshold( challenge_tx, server_kp.public_key, domain_name, network_passphrase, med_threshold, signers, ) assert signers_found == [ Ed25519PublicKeySigner(client_kp_a.public_key, 1), Ed25519PublicKeySigner(client_kp_b.public_key, 2), Ed25519PublicKeySigner(client_kp_c.public_key, 4), ]
def test_verify_challenge_transaction_threshold_raise_not_meet_threshold( self): server_kp = Keypair.random() client_kp_a = Keypair.random() client_kp_b = Keypair.random() client_kp_c = Keypair.random() timeout = 600 network_passphrase = Network.PUBLIC_NETWORK_PASSPHRASE domain_name = "example.com" challenge = build_challenge_transaction( server_secret=server_kp.secret, client_account_id=client_kp_a.public_key, domain_name=domain_name, network_passphrase=network_passphrase, timeout=timeout, ) transaction = TransactionEnvelope.from_xdr(challenge, network_passphrase) transaction.sign(client_kp_a) transaction.sign(client_kp_b) transaction.sign(client_kp_c) challenge_tx = transaction.to_xdr() signers = [ Ed25519PublicKeySigner(client_kp_a.public_key, 1), Ed25519PublicKeySigner(client_kp_b.public_key, 2), Ed25519PublicKeySigner(client_kp_c.public_key, 4), Ed25519PublicKeySigner(Keypair.random().public_key, 255), ] med_threshold = 10 with pytest.raises( InvalidSep10ChallengeError, match="signers with weight 7 do not meet threshold 10.", ): verify_challenge_transaction_threshold( challenge_tx, server_kp.public_key, domain_name, network_passphrase, med_threshold, signers, )
def test_verify_challenge_transaction_signers_raise_unrecognized_signatures( self): server_kp = Keypair.random() client_kp_a = Keypair.random() client_kp_b = Keypair.random() client_kp_c = Keypair.random() client_kp_unrecognized = 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_a.public_key, home_domain=home_domain, network_passphrase=network_passphrase, timeout=timeout, ) transaction = TransactionEnvelope.from_xdr(challenge, network_passphrase) transaction.sign(client_kp_a) transaction.sign(client_kp_b) transaction.sign(client_kp_c) transaction.sign(client_kp_unrecognized) challenge_tx = transaction.to_xdr() signers = [ Ed25519PublicKeySigner(client_kp_a.public_key, 1), Ed25519PublicKeySigner(client_kp_b.public_key, 2), Ed25519PublicKeySigner(client_kp_c.public_key, 4), Ed25519PublicKeySigner(Keypair.random().public_key, 255), ] with pytest.raises(InvalidSep10ChallengeError, match="Transaction has unrecognized signatures."): verify_challenge_transaction_signers( challenge_tx, server_kp.public_key, home_domain, network_passphrase, signers, )
def _challenge_transaction(client_account, home_domain, client_domain=None, client_signing_key=None): """ Generate the challenge transaction for a client account. This is used in `GET <auth>`, as per SEP 10. Returns the XDR encoding of that transaction. """ return build_challenge_transaction( server_secret=settings.SIGNING_SEED, client_account_id=client_account, home_domain=home_domain, web_auth_domain=urlparse(settings.HOST_URL).netloc, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, timeout=900, client_domain=client_domain, client_signing_key=client_signing_key, )
def test_verify_challenge_transaction(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( challenge_tx, server_kp.public_key, network_passphrase )
def test_verify_challenge_tx_transaction_is_not_signed_by_the_client(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( InvalidSep10ChallengeError, match="Transaction not signed by any client signer.", ): verify_challenge_transaction(challenge, server_kp.public_key, network_passphrase)
def test_post_success_account_exists_client_attribution( mock_load_account, client): kp = Keypair.random() client_domain_kp = Keypair.random() challenge_xdr = build_challenge_transaction( server_secret=settings.SIGNING_SEED, client_account_id=kp.public_key, home_domain=settings.SEP10_HOME_DOMAINS[0], web_auth_domain=urlparse(settings.HOST_URL).netloc, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, client_domain="test.com", client_signing_key=client_domain_kp.public_key, ) envelope = TransactionEnvelope.from_xdr( challenge_xdr, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE) envelope.sign(kp) envelope.sign(client_domain_kp) signed_challenge_xdr = envelope.to_xdr() mock_load_account.return_value = Mock( load_ed25519_public_key_signers=Mock( return_value=[Ed25519PublicKeySigner(kp.public_key, 0)]), thresholds=Mock(med_threshold=0), ) response = client.post(AUTH_PATH, {"transaction": signed_challenge_xdr}) content = response.json() assert response.status_code == 200, json.dumps(content, indent=2) jwt_contents = jwt.decode(content["token"], settings.SERVER_JWT_KEY, algorithms=["HS256"]) iat = jwt_contents.pop("iat") exp = jwt_contents.pop("exp") assert exp - iat == 24 * 60 * 60 assert jwt_contents == { "iss": os.path.join(settings.HOST_URL, "auth"), "sub": kp.public_key, "jti": envelope.hash().hex(), "client_domain": "test.com", }
def test_verify_challenge_transaction_with_multi_domain_names(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) transaction.sign(client_kp) challenge_tx = transaction.to_xdr() verify_challenge_transaction( challenge_tx, server_kp.public_key, ["example.com2", "example.com1", home_domain], 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, )