"""Security tests for timing attack resistance.""" import os import secrets import time from statistics import mean, stdev import pytest @pytest.mark.security @pytest.mark.slow class TestTimingAttackResistance: """Test timing attack resistance in token validation.""" @pytest.mark.skip(reason="Requires database fixture - will be implemented with full DB test fixtures") def test_token_verification_constant_time(self, db_session): """ Test that token verification takes similar time for valid and invalid tokens. Timing attacks exploit differences in processing time to guess secrets. This test verifies that token verification uses constant-time comparison. """ from gondulf.services.token_service import TokenService token_service = TokenService(db_session) # Generate valid token me = "https://user.example.com" client_id = "https://client.example.com" token = token_service.generate_access_token(me=me, client_id=client_id, scope="") # Measure time for valid token (hits database, passes validation) valid_times = [] # Use more samples in CI for better statistics samples = 200 if os.getenv("CI") == "true" else 100 for _ in range(samples): start = time.perf_counter() result = token_service.verify_access_token(token) end = time.perf_counter() valid_times.append(end - start) assert result is not None # Valid token # Measure time for invalid token (misses database, fails validation) invalid_token = secrets.token_urlsafe(32) invalid_times = [] for _ in range(samples): start = time.perf_counter() result = token_service.verify_access_token(invalid_token) end = time.perf_counter() invalid_times.append(end - start) assert result is None # Invalid token # Statistical analysis: times should be similar valid_mean = mean(valid_times) invalid_mean = mean(invalid_times) valid_stdev = stdev(valid_times) invalid_stdev = stdev(invalid_times) # Difference in means should be small relative to standard deviations # Allow 3x stdev difference (99.7% confidence interval) # Use relaxed threshold in CI (30% vs 20% coefficient of variation) max_cv = 0.30 if os.getenv("CI") == "true" else 0.20 valid_cv = valid_stdev / valid_mean if valid_mean > 0 else 0 invalid_cv = invalid_stdev / invalid_mean if invalid_mean > 0 else 0 # Check coefficient of variation is reasonable assert valid_cv < max_cv, f"Valid timing variation too high: {valid_cv:.2%} (max: {max_cv:.2%})" assert invalid_cv < max_cv, f"Invalid timing variation too high: {invalid_cv:.2%} (max: {max_cv:.2%})" def test_hash_comparison_uses_constant_time(self): """ Test that hash comparison uses secrets.compare_digest or SQL lookup. This is a code inspection test. """ import inspect from gondulf.services.token_service import TokenService # The method is validate_token source = inspect.getsource(TokenService.validate_token) # Verify that constant-time comparison is used # Either via secrets.compare_digest or SQL lookup (which is also constant-time) assert "SELECT" in source or "select" in source or "execute" in source, ( "Token verification should use SQL lookup for constant-time behavior" )