From 0d92180d4ab43100444bc8e8e02389ec44c0de10 Mon Sep 17 00:00:00 2001 From: amalcaraz Date: Thu, 30 Oct 2025 15:58:02 +0100 Subject: [PATCH 1/2] fix: ecdsa token verify --- src/aleph/toolkit/ecdsa.py | 2 +- tests/toolkit/test_ecdsa.py | 200 ++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 tests/toolkit/test_ecdsa.py diff --git a/src/aleph/toolkit/ecdsa.py b/src/aleph/toolkit/ecdsa.py index a0c2cd999..3aebcf053 100644 --- a/src/aleph/toolkit/ecdsa.py +++ b/src/aleph/toolkit/ecdsa.py @@ -64,7 +64,7 @@ def verify_signature(message: str, signature_b64: str, public_key_hex: str) -> b True if signature is valid, False otherwise """ try: - public_key = PublicKey.from_hex(public_key_hex) + public_key = PublicKey(bytes.fromhex(public_key_hex)) signature = base64.b64decode(signature_b64) message_bytes = message.encode("utf-8") return public_key.verify(signature, message_bytes) diff --git a/tests/toolkit/test_ecdsa.py b/tests/toolkit/test_ecdsa.py new file mode 100644 index 000000000..87b84c3e9 --- /dev/null +++ b/tests/toolkit/test_ecdsa.py @@ -0,0 +1,200 @@ +import base64 +import time +import pytest +from unittest.mock import patch + +from aleph.toolkit.ecdsa import ( + generate_key_pair, + generate_key_pair_from_private_key, + sign_message, + verify_signature, + create_auth_token, + verify_auth_token, +) + + +def test_generate_key_pair(): + """Test key pair generation.""" + private_key, public_key = generate_key_pair() + + # Keys should be hex strings + assert isinstance(private_key, str) + assert isinstance(public_key, str) + + # Private key should be 64 hex characters (32 bytes) + assert len(private_key) == 64 + + # Public key should be 66 hex characters (33 bytes compressed) + assert len(public_key) == 66 + + # Public key should start with 02 or 03 (compressed format) + assert public_key.startswith(('02', '03')) + + +def test_generate_key_pair_from_private_key(): + """Test key pair generation from existing private key.""" + test_private_key = "646fa150ca94320b8eca3bd28d106703f3602dfb13b6b982cc17d17fd1182f85" + + private_key, public_key = generate_key_pair_from_private_key(test_private_key) + + # Should return the same private key + assert private_key == test_private_key + + # Public key should be correctly derived + assert isinstance(public_key, str) + assert len(public_key) == 66 + assert public_key.startswith(('02', '03')) + + +def test_sign_and_verify_message(): + """Test message signing and verification.""" + private_key, public_key = generate_key_pair() + message = "test message" + + # Sign the message + signature = sign_message(message, private_key) + + # Signature should be base64 encoded + assert isinstance(signature, str) + # Should be valid base64 + base64.b64decode(signature) + + # Verify the signature + is_valid = verify_signature(message, signature, public_key) + assert is_valid is True + + # Verification should fail with wrong message + is_valid_wrong = verify_signature("wrong message", signature, public_key) + assert is_valid_wrong is False + + # Verification should fail with wrong public key + _, wrong_public_key = generate_key_pair() + is_valid_wrong_key = verify_signature(message, signature, wrong_public_key) + assert is_valid_wrong_key is False + + +def test_create_and_verify_auth_token(): + """Test authentication token creation and verification.""" + private_key, public_key = generate_key_pair() + + # Create a token + token = create_auth_token(private_key) + + # Token should be base64 encoded + assert isinstance(token, str) + # Should be valid base64 + token_data = base64.b64decode(token).decode("utf-8") + + # Token should contain timestamp and signature separated by colon + parts = token_data.split(":", 1) + assert len(parts) == 2 + + timestamp_str, signature = parts + # Timestamp should be a valid integer + timestamp = int(timestamp_str) + assert timestamp > 0 + + # Signature should be valid base64 + base64.b64decode(signature) + + # Verify the token + is_valid = verify_auth_token(token, public_key) + assert is_valid is True + + +def test_verify_auth_token_with_wrong_public_key(): + """Test token verification fails with wrong public key.""" + private_key, _ = generate_key_pair() + _, wrong_public_key = generate_key_pair() + + token = create_auth_token(private_key) + + # Should fail with wrong public key + is_valid = verify_auth_token(token, wrong_public_key) + assert is_valid is False + + +def test_verify_auth_token_expired(): + """Test token verification fails when token is expired.""" + private_key, public_key = generate_key_pair() + + # Mock time to create an old token + old_timestamp = int(time.time()) - 600 # 10 minutes ago + + with patch('aleph.toolkit.ecdsa.time.time', return_value=old_timestamp): + token = create_auth_token(private_key) + + # Should fail with default max_age (5 minutes) + is_valid = verify_auth_token(token, public_key) + assert is_valid is False + + # Should pass with larger max_age + is_valid_long = verify_auth_token(token, public_key, max_age_seconds=700) + assert is_valid_long is True + + +def test_verify_auth_token_future_timestamp(): + """Test token verification handles future timestamps within tolerance.""" + private_key, public_key = generate_key_pair() + + # Mock time to create a future token + future_timestamp = int(time.time()) + 60 # 1 minute in future + + with patch('aleph.toolkit.ecdsa.time.time', return_value=future_timestamp): + token = create_auth_token(private_key) + + # Should pass as it uses abs() for time difference + is_valid = verify_auth_token(token, public_key) + assert is_valid is True + + +def test_verify_auth_token_malformed(): + """Test token verification fails with malformed tokens.""" + _, public_key = generate_key_pair() + + # Invalid base64 + is_valid = verify_auth_token("invalid_base64!", public_key) + assert is_valid is False + + # Valid base64 but wrong format (no colon) + malformed_token = base64.b64encode("no_colon_here".encode()).decode() + is_valid = verify_auth_token(malformed_token, public_key) + assert is_valid is False + + # Valid base64 but invalid timestamp + malformed_data = "not_a_number:MEQCIHFHKoBPyY3pCMY9x5gS4P1" + malformed_token = base64.b64encode(malformed_data.encode()).decode() + is_valid = verify_auth_token(malformed_token, public_key) + assert is_valid is False + + +def test_verify_signature_with_invalid_inputs(): + """Test verify_signature handles invalid inputs gracefully.""" + _, public_key = generate_key_pair() + + # Invalid base64 signature + is_valid = verify_signature("message", "invalid_base64!", public_key) + assert is_valid is False + + # Invalid public key + is_valid = verify_signature("message", "dGVzdA==", "invalid_public_key") + assert is_valid is False + + +def test_token_roundtrip_with_known_values(): + """Test token creation and verification with known test values.""" + # Use known values for reproducible testing + test_private_key = "50b44756efbcb9266d974af8a8bcecb97d960fd8ddaadd31ecf2082c757fcaad" + test_public_key = "023d3b6f2e92e5d30b8d75291087051f6ef9425abbb626bebc3a5b358bce6007ee" + + # Create token + token = create_auth_token(test_private_key) + + # Verify it works + is_valid = verify_auth_token(token, test_public_key) + assert is_valid is True + + # Verify with wrong key fails + _, wrong_key = generate_key_pair() + is_valid_wrong = verify_auth_token(token, wrong_key) + assert is_valid_wrong is False \ No newline at end of file From 252b1c1f5ca4c9b40da5989398267825f1c13764 Mon Sep 17 00:00:00 2001 From: amalcaraz Date: Thu, 30 Oct 2025 15:04:26 +0000 Subject: [PATCH 2/2] fix: linting errors --- tests/toolkit/test_ecdsa.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/tests/toolkit/test_ecdsa.py b/tests/toolkit/test_ecdsa.py index 87b84c3e9..2b1048ff3 100644 --- a/tests/toolkit/test_ecdsa.py +++ b/tests/toolkit/test_ecdsa.py @@ -1,15 +1,14 @@ import base64 import time -import pytest from unittest.mock import patch from aleph.toolkit.ecdsa import ( + create_auth_token, generate_key_pair, generate_key_pair_from_private_key, sign_message, - verify_signature, - create_auth_token, verify_auth_token, + verify_signature, ) @@ -28,12 +27,14 @@ def test_generate_key_pair(): assert len(public_key) == 66 # Public key should start with 02 or 03 (compressed format) - assert public_key.startswith(('02', '03')) + assert public_key.startswith(("02", "03")) def test_generate_key_pair_from_private_key(): """Test key pair generation from existing private key.""" - test_private_key = "646fa150ca94320b8eca3bd28d106703f3602dfb13b6b982cc17d17fd1182f85" + test_private_key = ( + "646fa150ca94320b8eca3bd28d106703f3602dfb13b6b982cc17d17fd1182f85" + ) private_key, public_key = generate_key_pair_from_private_key(test_private_key) @@ -43,7 +44,7 @@ def test_generate_key_pair_from_private_key(): # Public key should be correctly derived assert isinstance(public_key, str) assert len(public_key) == 66 - assert public_key.startswith(('02', '03')) + assert public_key.startswith(("02", "03")) def test_sign_and_verify_message(): @@ -121,7 +122,7 @@ def test_verify_auth_token_expired(): # Mock time to create an old token old_timestamp = int(time.time()) - 600 # 10 minutes ago - with patch('aleph.toolkit.ecdsa.time.time', return_value=old_timestamp): + with patch("aleph.toolkit.ecdsa.time.time", return_value=old_timestamp): token = create_auth_token(private_key) # Should fail with default max_age (5 minutes) @@ -140,7 +141,7 @@ def test_verify_auth_token_future_timestamp(): # Mock time to create a future token future_timestamp = int(time.time()) + 60 # 1 minute in future - with patch('aleph.toolkit.ecdsa.time.time', return_value=future_timestamp): + with patch("aleph.toolkit.ecdsa.time.time", return_value=future_timestamp): token = create_auth_token(private_key) # Should pass as it uses abs() for time difference @@ -184,8 +185,12 @@ def test_verify_signature_with_invalid_inputs(): def test_token_roundtrip_with_known_values(): """Test token creation and verification with known test values.""" # Use known values for reproducible testing - test_private_key = "50b44756efbcb9266d974af8a8bcecb97d960fd8ddaadd31ecf2082c757fcaad" - test_public_key = "023d3b6f2e92e5d30b8d75291087051f6ef9425abbb626bebc3a5b358bce6007ee" + test_private_key = ( + "50b44756efbcb9266d974af8a8bcecb97d960fd8ddaadd31ecf2082c757fcaad" + ) + test_public_key = ( + "023d3b6f2e92e5d30b8d75291087051f6ef9425abbb626bebc3a5b358bce6007ee" + ) # Create token token = create_auth_token(test_private_key) @@ -197,4 +202,4 @@ def test_token_roundtrip_with_known_values(): # Verify with wrong key fails _, wrong_key = generate_key_pair() is_valid_wrong = verify_auth_token(token, wrong_key) - assert is_valid_wrong is False \ No newline at end of file + assert is_valid_wrong is False