mcp-security-framework 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_security_framework/__init__.py +96 -0
- mcp_security_framework/cli/__init__.py +18 -0
- mcp_security_framework/cli/cert_cli.py +511 -0
- mcp_security_framework/cli/security_cli.py +791 -0
- mcp_security_framework/constants.py +209 -0
- mcp_security_framework/core/__init__.py +61 -0
- mcp_security_framework/core/auth_manager.py +1011 -0
- mcp_security_framework/core/cert_manager.py +1663 -0
- mcp_security_framework/core/permission_manager.py +735 -0
- mcp_security_framework/core/rate_limiter.py +602 -0
- mcp_security_framework/core/security_manager.py +943 -0
- mcp_security_framework/core/ssl_manager.py +735 -0
- mcp_security_framework/examples/__init__.py +75 -0
- mcp_security_framework/examples/django_example.py +615 -0
- mcp_security_framework/examples/fastapi_example.py +472 -0
- mcp_security_framework/examples/flask_example.py +506 -0
- mcp_security_framework/examples/gateway_example.py +803 -0
- mcp_security_framework/examples/microservice_example.py +690 -0
- mcp_security_framework/examples/standalone_example.py +576 -0
- mcp_security_framework/middleware/__init__.py +250 -0
- mcp_security_framework/middleware/auth_middleware.py +292 -0
- mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
- mcp_security_framework/middleware/fastapi_middleware.py +757 -0
- mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
- mcp_security_framework/middleware/flask_middleware.py +591 -0
- mcp_security_framework/middleware/mtls_middleware.py +439 -0
- mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
- mcp_security_framework/middleware/security_middleware.py +507 -0
- mcp_security_framework/schemas/__init__.py +109 -0
- mcp_security_framework/schemas/config.py +694 -0
- mcp_security_framework/schemas/models.py +709 -0
- mcp_security_framework/schemas/responses.py +686 -0
- mcp_security_framework/tests/__init__.py +0 -0
- mcp_security_framework/utils/__init__.py +121 -0
- mcp_security_framework/utils/cert_utils.py +525 -0
- mcp_security_framework/utils/crypto_utils.py +475 -0
- mcp_security_framework/utils/validation_utils.py +571 -0
- mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
- mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
- mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
- mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_cli/__init__.py +0 -0
- tests/test_cli/test_cert_cli.py +379 -0
- tests/test_cli/test_security_cli.py +657 -0
- tests/test_core/__init__.py +0 -0
- tests/test_core/test_auth_manager.py +582 -0
- tests/test_core/test_cert_manager.py +795 -0
- tests/test_core/test_permission_manager.py +395 -0
- tests/test_core/test_rate_limiter.py +626 -0
- tests/test_core/test_security_manager.py +841 -0
- tests/test_core/test_ssl_manager.py +532 -0
- tests/test_examples/__init__.py +8 -0
- tests/test_examples/test_fastapi_example.py +264 -0
- tests/test_examples/test_flask_example.py +238 -0
- tests/test_examples/test_standalone_example.py +292 -0
- tests/test_integration/__init__.py +0 -0
- tests/test_integration/test_auth_flow.py +502 -0
- tests/test_integration/test_certificate_flow.py +527 -0
- tests/test_integration/test_fastapi_integration.py +341 -0
- tests/test_integration/test_flask_integration.py +398 -0
- tests/test_integration/test_standalone_integration.py +493 -0
- tests/test_middleware/__init__.py +0 -0
- tests/test_middleware/test_fastapi_middleware.py +523 -0
- tests/test_middleware/test_flask_middleware.py +582 -0
- tests/test_middleware/test_security_middleware.py +493 -0
- tests/test_schemas/__init__.py +0 -0
- tests/test_schemas/test_config.py +811 -0
- tests/test_schemas/test_models.py +879 -0
- tests/test_schemas/test_responses.py +1054 -0
- tests/test_schemas/test_serialization.py +493 -0
- tests/test_utils/__init__.py +0 -0
- tests/test_utils/test_cert_utils.py +510 -0
- tests/test_utils/test_crypto_utils.py +603 -0
- tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,603 @@
|
|
1
|
+
"""
|
2
|
+
Cryptographic Utilities Test Module
|
3
|
+
|
4
|
+
This module provides comprehensive unit tests for all cryptographic
|
5
|
+
utilities in the MCP Security Framework.
|
6
|
+
|
7
|
+
Test Classes:
|
8
|
+
TestPasswordHashing: Tests for password hashing and verification
|
9
|
+
TestRandomGeneration: Tests for random data generation
|
10
|
+
TestJWTOperations: Tests for JWT token creation and verification
|
11
|
+
TestDataHashing: Tests for data hashing functions
|
12
|
+
TestDigitalSignatures: Tests for digital signature operations
|
13
|
+
TestHMAC: Tests for HMAC generation and verification
|
14
|
+
|
15
|
+
Author: MCP Security Team
|
16
|
+
Version: 1.0.0
|
17
|
+
License: MIT
|
18
|
+
"""
|
19
|
+
|
20
|
+
from unittest.mock import patch
|
21
|
+
|
22
|
+
import pytest
|
23
|
+
|
24
|
+
from mcp_security_framework.utils.crypto_utils import (
|
25
|
+
CryptoError,
|
26
|
+
create_jwt_token,
|
27
|
+
generate_api_key,
|
28
|
+
generate_hmac,
|
29
|
+
generate_random_bytes,
|
30
|
+
generate_rsa_key_pair,
|
31
|
+
hash_data,
|
32
|
+
hash_password,
|
33
|
+
sign_data,
|
34
|
+
verify_hmac,
|
35
|
+
verify_jwt_token,
|
36
|
+
verify_password,
|
37
|
+
verify_signature,
|
38
|
+
)
|
39
|
+
|
40
|
+
|
41
|
+
class TestPasswordHashing:
|
42
|
+
"""Test suite for password hashing functions."""
|
43
|
+
|
44
|
+
def test_hash_password_success(self):
|
45
|
+
"""Test successful password hashing."""
|
46
|
+
password = "test_password_123"
|
47
|
+
result = hash_password(password)
|
48
|
+
|
49
|
+
assert "hash" in result
|
50
|
+
assert "salt" in result
|
51
|
+
assert isinstance(result["hash"], str)
|
52
|
+
assert isinstance(result["salt"], str)
|
53
|
+
assert len(result["hash"]) > 0
|
54
|
+
assert len(result["salt"]) > 0
|
55
|
+
|
56
|
+
def test_hash_password_with_salt(self):
|
57
|
+
"""Test password hashing with provided salt."""
|
58
|
+
password = "test_password_123"
|
59
|
+
salt = "test_salt_123"
|
60
|
+
result = hash_password(password, salt)
|
61
|
+
|
62
|
+
assert result["salt"] == salt
|
63
|
+
assert result["hash"] != password
|
64
|
+
|
65
|
+
def test_hash_password_empty_password(self):
|
66
|
+
"""Test password hashing with empty password."""
|
67
|
+
with pytest.raises(CryptoError) as exc_info:
|
68
|
+
hash_password("")
|
69
|
+
|
70
|
+
assert "Password cannot be empty" in str(exc_info.value)
|
71
|
+
|
72
|
+
def test_hash_password_none_password(self):
|
73
|
+
"""Test password hashing with None password."""
|
74
|
+
with pytest.raises(CryptoError) as exc_info:
|
75
|
+
hash_password(None)
|
76
|
+
|
77
|
+
assert "Password cannot be empty" in str(exc_info.value)
|
78
|
+
|
79
|
+
def test_hash_password_whitespace_only(self):
|
80
|
+
"""Test password hashing with whitespace-only password."""
|
81
|
+
with pytest.raises(CryptoError) as exc_info:
|
82
|
+
hash_password(" ")
|
83
|
+
|
84
|
+
assert "Password cannot be empty" in str(exc_info.value)
|
85
|
+
|
86
|
+
def test_hash_password_exception(self):
|
87
|
+
"""Test password hashing with exception."""
|
88
|
+
# Test with invalid salt that will cause an exception
|
89
|
+
with patch(
|
90
|
+
"cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC.derive",
|
91
|
+
side_effect=Exception("Test exception"),
|
92
|
+
):
|
93
|
+
with pytest.raises(CryptoError) as exc_info:
|
94
|
+
hash_password("password", "salt")
|
95
|
+
|
96
|
+
assert "Password hashing failed" in str(exc_info.value)
|
97
|
+
|
98
|
+
def test_verify_password_success(self):
|
99
|
+
"""Test successful password verification."""
|
100
|
+
password = "test_password_123"
|
101
|
+
hashed = hash_password(password)
|
102
|
+
|
103
|
+
result = verify_password(password, hashed["hash"], hashed["salt"])
|
104
|
+
assert result is True
|
105
|
+
|
106
|
+
def test_verify_password_wrong_password(self):
|
107
|
+
"""Test password verification with wrong password."""
|
108
|
+
password = "test_password_123"
|
109
|
+
hashed = hash_password(password)
|
110
|
+
|
111
|
+
result = verify_password("wrong_password", hashed["hash"], hashed["salt"])
|
112
|
+
assert result is False
|
113
|
+
|
114
|
+
def test_verify_password_wrong_salt(self):
|
115
|
+
"""Test password verification with wrong salt."""
|
116
|
+
password = "test_password_123"
|
117
|
+
hashed = hash_password(password)
|
118
|
+
|
119
|
+
result = verify_password(password, hashed["hash"], "wrong_salt")
|
120
|
+
assert result is False
|
121
|
+
|
122
|
+
def test_verify_password_exception(self):
|
123
|
+
"""Test password verification with exception."""
|
124
|
+
# Test with invalid hash that will cause an exception
|
125
|
+
with patch(
|
126
|
+
"mcp_security_framework.utils.crypto_utils.hash_password",
|
127
|
+
side_effect=Exception("Test exception"),
|
128
|
+
):
|
129
|
+
with pytest.raises(CryptoError) as exc_info:
|
130
|
+
verify_password("password", "invalid_hash", "invalid_salt")
|
131
|
+
|
132
|
+
assert "Password verification failed" in str(exc_info.value)
|
133
|
+
|
134
|
+
|
135
|
+
class TestRandomGeneration:
|
136
|
+
"""Test suite for random data generation."""
|
137
|
+
|
138
|
+
def test_generate_random_bytes_success(self):
|
139
|
+
"""Test successful random bytes generation."""
|
140
|
+
length = 32
|
141
|
+
result = generate_random_bytes(length)
|
142
|
+
|
143
|
+
assert isinstance(result, bytes)
|
144
|
+
assert len(result) == length
|
145
|
+
|
146
|
+
def test_generate_random_bytes_different_lengths(self):
|
147
|
+
"""Test random bytes generation with different lengths."""
|
148
|
+
lengths = [16, 32, 64, 128]
|
149
|
+
|
150
|
+
for length in lengths:
|
151
|
+
result = generate_random_bytes(length)
|
152
|
+
assert len(result) == length
|
153
|
+
|
154
|
+
def test_generate_random_bytes_unique(self):
|
155
|
+
"""Test that generated random bytes are unique."""
|
156
|
+
results = []
|
157
|
+
for _ in range(10):
|
158
|
+
result = generate_random_bytes(32)
|
159
|
+
assert result not in results
|
160
|
+
results.append(result)
|
161
|
+
|
162
|
+
def test_generate_random_bytes_invalid_length(self):
|
163
|
+
"""Test random bytes generation with invalid length."""
|
164
|
+
with pytest.raises(CryptoError) as exc_info:
|
165
|
+
generate_random_bytes(0)
|
166
|
+
|
167
|
+
assert "Length must be positive" in str(exc_info.value)
|
168
|
+
|
169
|
+
def test_generate_random_bytes_negative_length(self):
|
170
|
+
"""Test random bytes generation with negative length."""
|
171
|
+
with pytest.raises(CryptoError) as exc_info:
|
172
|
+
generate_random_bytes(-1)
|
173
|
+
|
174
|
+
assert "Length must be positive" in str(exc_info.value)
|
175
|
+
|
176
|
+
def test_generate_api_key_success(self):
|
177
|
+
"""Test successful API key generation."""
|
178
|
+
length = 32
|
179
|
+
result = generate_api_key(length)
|
180
|
+
|
181
|
+
assert isinstance(result, str)
|
182
|
+
assert len(result) > 0
|
183
|
+
# API key should be URL-safe base64
|
184
|
+
assert "=" not in result
|
185
|
+
assert "/" not in result
|
186
|
+
assert "+" not in result
|
187
|
+
|
188
|
+
def test_generate_api_key_different_lengths(self):
|
189
|
+
"""Test API key generation with different lengths."""
|
190
|
+
lengths = [16, 32, 64]
|
191
|
+
|
192
|
+
for length in lengths:
|
193
|
+
result = generate_api_key(length)
|
194
|
+
assert isinstance(result, str)
|
195
|
+
assert len(result) > 0
|
196
|
+
|
197
|
+
def test_generate_api_key_invalid_length(self):
|
198
|
+
"""Test API key generation with invalid length."""
|
199
|
+
with pytest.raises(CryptoError) as exc_info:
|
200
|
+
generate_api_key(0)
|
201
|
+
|
202
|
+
assert "Length must be positive" in str(exc_info.value)
|
203
|
+
|
204
|
+
def test_generate_random_bytes_exception(self):
|
205
|
+
"""Test random bytes generation with exception."""
|
206
|
+
# This is hard to trigger in practice, but we can test the exception path
|
207
|
+
# by mocking secrets.token_bytes to raise an exception
|
208
|
+
with patch("secrets.token_bytes", side_effect=Exception("Test exception")):
|
209
|
+
with pytest.raises(CryptoError) as exc_info:
|
210
|
+
generate_random_bytes(32)
|
211
|
+
|
212
|
+
assert "Random bytes generation failed" in str(exc_info.value)
|
213
|
+
|
214
|
+
|
215
|
+
class TestJWTOperations:
|
216
|
+
"""Test suite for JWT operations."""
|
217
|
+
|
218
|
+
def test_create_jwt_token_success(self):
|
219
|
+
"""Test successful JWT token creation."""
|
220
|
+
payload = {"user_id": "123", "username": "testuser"}
|
221
|
+
secret = "test_secret_key"
|
222
|
+
|
223
|
+
token = create_jwt_token(payload, secret)
|
224
|
+
|
225
|
+
assert isinstance(token, str)
|
226
|
+
assert len(token) > 0
|
227
|
+
|
228
|
+
def test_create_jwt_token_with_expiry(self):
|
229
|
+
"""Test JWT token creation with expiry."""
|
230
|
+
payload = {"user_id": "123"}
|
231
|
+
secret = "test_secret_key"
|
232
|
+
expires_in = 3600 # 1 hour
|
233
|
+
|
234
|
+
token = create_jwt_token(payload, secret, expires_in=expires_in)
|
235
|
+
|
236
|
+
assert isinstance(token, str)
|
237
|
+
assert len(token) > 0
|
238
|
+
|
239
|
+
def test_create_jwt_token_with_algorithm(self):
|
240
|
+
"""Test JWT token creation with specific algorithm."""
|
241
|
+
payload = {"user_id": "123"}
|
242
|
+
secret = "test_secret_key"
|
243
|
+
|
244
|
+
token = create_jwt_token(payload, secret, algorithm="HS512")
|
245
|
+
|
246
|
+
assert isinstance(token, str)
|
247
|
+
assert len(token) > 0
|
248
|
+
|
249
|
+
def test_create_jwt_token_exception(self):
|
250
|
+
"""Test JWT token creation with exception."""
|
251
|
+
# Test with invalid payload that will cause an exception
|
252
|
+
with pytest.raises(CryptoError) as exc_info:
|
253
|
+
create_jwt_token({"invalid": object()}, "secret")
|
254
|
+
|
255
|
+
assert "JWT token creation failed" in str(exc_info.value)
|
256
|
+
|
257
|
+
def test_verify_jwt_token_success(self):
|
258
|
+
"""Test successful JWT token verification."""
|
259
|
+
payload = {"user_id": "123", "username": "testuser"}
|
260
|
+
secret = "test_secret_key"
|
261
|
+
|
262
|
+
token = create_jwt_token(payload, secret)
|
263
|
+
decoded = verify_jwt_token(token, secret)
|
264
|
+
|
265
|
+
assert decoded["user_id"] == "123"
|
266
|
+
assert decoded["username"] == "testuser"
|
267
|
+
assert "iat" in decoded
|
268
|
+
|
269
|
+
def test_verify_jwt_token_invalid_token(self):
|
270
|
+
"""Test JWT token verification with invalid token."""
|
271
|
+
secret = "test_secret_key"
|
272
|
+
|
273
|
+
with pytest.raises(CryptoError) as exc_info:
|
274
|
+
verify_jwt_token("invalid_token", secret)
|
275
|
+
|
276
|
+
assert "Invalid JWT token" in str(exc_info.value)
|
277
|
+
|
278
|
+
def test_verify_jwt_token_wrong_secret(self):
|
279
|
+
"""Test JWT token verification with wrong secret."""
|
280
|
+
payload = {"user_id": "123"}
|
281
|
+
secret = "test_secret_key"
|
282
|
+
wrong_secret = "wrong_secret"
|
283
|
+
|
284
|
+
token = create_jwt_token(payload, secret)
|
285
|
+
|
286
|
+
with pytest.raises(CryptoError) as exc_info:
|
287
|
+
verify_jwt_token(token, wrong_secret)
|
288
|
+
|
289
|
+
assert "Invalid JWT token" in str(exc_info.value)
|
290
|
+
|
291
|
+
def test_verify_jwt_token_expired(self):
|
292
|
+
"""Test JWT token verification with expired token."""
|
293
|
+
payload = {"user_id": "123"}
|
294
|
+
secret = "test_secret_key"
|
295
|
+
expires_in = -1 # Expired immediately
|
296
|
+
|
297
|
+
token = create_jwt_token(payload, secret, expires_in=expires_in)
|
298
|
+
|
299
|
+
with pytest.raises(CryptoError) as exc_info:
|
300
|
+
verify_jwt_token(token, secret)
|
301
|
+
|
302
|
+
assert "JWT token has expired" in str(exc_info.value)
|
303
|
+
|
304
|
+
def test_verify_jwt_token_with_custom_algorithms(self):
|
305
|
+
"""Test JWT token verification with custom algorithms."""
|
306
|
+
payload = {"user_id": "123"}
|
307
|
+
secret = "test_secret_key"
|
308
|
+
|
309
|
+
token = create_jwt_token(payload, secret)
|
310
|
+
decoded = verify_jwt_token(token, secret, algorithms=["HS256", "HS512"])
|
311
|
+
|
312
|
+
assert decoded["user_id"] == "123"
|
313
|
+
|
314
|
+
def test_verify_jwt_token_exception(self):
|
315
|
+
"""Test JWT token verification with exception."""
|
316
|
+
# Test with invalid token that will cause an exception
|
317
|
+
with pytest.raises(CryptoError) as exc_info:
|
318
|
+
verify_jwt_token("invalid_token", "secret")
|
319
|
+
|
320
|
+
assert "Invalid JWT token" in str(exc_info.value)
|
321
|
+
|
322
|
+
|
323
|
+
class TestDataHashing:
|
324
|
+
"""Test suite for data hashing functions."""
|
325
|
+
|
326
|
+
def test_hash_data_sha256(self):
|
327
|
+
"""Test data hashing with SHA-256."""
|
328
|
+
data = "test_data"
|
329
|
+
result = hash_data(data, "sha256")
|
330
|
+
|
331
|
+
assert isinstance(result, str)
|
332
|
+
assert len(result) == 64 # SHA-256 produces 64 hex characters
|
333
|
+
|
334
|
+
def test_hash_data_sha512(self):
|
335
|
+
"""Test data hashing with SHA-512."""
|
336
|
+
data = "test_data"
|
337
|
+
result = hash_data(data, "sha512")
|
338
|
+
|
339
|
+
assert isinstance(result, str)
|
340
|
+
assert len(result) == 128 # SHA-512 produces 128 hex characters
|
341
|
+
|
342
|
+
def test_hash_data_md5(self):
|
343
|
+
"""Test data hashing with MD5."""
|
344
|
+
data = "test_data"
|
345
|
+
result = hash_data(data, "md5")
|
346
|
+
|
347
|
+
assert isinstance(result, str)
|
348
|
+
assert len(result) == 32 # MD5 produces 32 hex characters
|
349
|
+
|
350
|
+
def test_hash_data_bytes_input(self):
|
351
|
+
"""Test data hashing with bytes input."""
|
352
|
+
data = b"test_data"
|
353
|
+
result = hash_data(data, "sha256")
|
354
|
+
|
355
|
+
assert isinstance(result, str)
|
356
|
+
assert len(result) == 64
|
357
|
+
|
358
|
+
def test_hash_data_unsupported_algorithm(self):
|
359
|
+
"""Test data hashing with unsupported algorithm."""
|
360
|
+
data = "test_data"
|
361
|
+
|
362
|
+
with pytest.raises(CryptoError) as exc_info:
|
363
|
+
hash_data(data, "unsupported")
|
364
|
+
|
365
|
+
assert "Unsupported hash algorithm" in str(exc_info.value)
|
366
|
+
|
367
|
+
def test_hash_data_consistent(self):
|
368
|
+
"""Test that hashing the same data produces consistent results."""
|
369
|
+
data = "test_data"
|
370
|
+
result1 = hash_data(data, "sha256")
|
371
|
+
result2 = hash_data(data, "sha256")
|
372
|
+
|
373
|
+
assert result1 == result2
|
374
|
+
|
375
|
+
def test_hash_data_case_insensitive_algorithm(self):
|
376
|
+
"""Test data hashing with case insensitive algorithm names."""
|
377
|
+
data = "test_data"
|
378
|
+
result1 = hash_data(data, "SHA256")
|
379
|
+
result2 = hash_data(data, "sha256")
|
380
|
+
|
381
|
+
assert result1 == result2
|
382
|
+
|
383
|
+
|
384
|
+
class TestDigitalSignatures:
|
385
|
+
"""Test suite for digital signature operations."""
|
386
|
+
|
387
|
+
def test_generate_rsa_key_pair_success(self):
|
388
|
+
"""Test successful RSA key pair generation."""
|
389
|
+
result = generate_rsa_key_pair(2048)
|
390
|
+
|
391
|
+
assert "private_key" in result
|
392
|
+
assert "public_key" in result
|
393
|
+
assert isinstance(result["private_key"], str)
|
394
|
+
assert isinstance(result["public_key"], str)
|
395
|
+
assert "-----BEGIN PRIVATE KEY-----" in result["private_key"]
|
396
|
+
assert "-----BEGIN PUBLIC KEY-----" in result["public_key"]
|
397
|
+
|
398
|
+
def test_generate_rsa_key_pair_4096(self):
|
399
|
+
"""Test RSA key pair generation with 4096 bits."""
|
400
|
+
result = generate_rsa_key_pair(4096)
|
401
|
+
|
402
|
+
assert "private_key" in result
|
403
|
+
assert "public_key" in result
|
404
|
+
|
405
|
+
def test_generate_rsa_key_pair_invalid_size(self):
|
406
|
+
"""Test RSA key pair generation with invalid key size."""
|
407
|
+
with pytest.raises(CryptoError) as exc_info:
|
408
|
+
generate_rsa_key_pair(1024)
|
409
|
+
|
410
|
+
assert "Key size must be 2048 or 4096 bits" in str(exc_info.value)
|
411
|
+
|
412
|
+
def test_generate_rsa_key_pair_another_invalid_size(self):
|
413
|
+
"""Test RSA key pair generation with another invalid key size."""
|
414
|
+
with pytest.raises(CryptoError) as exc_info:
|
415
|
+
generate_rsa_key_pair(3072)
|
416
|
+
|
417
|
+
assert "Key size must be 2048 or 4096 bits" in str(exc_info.value)
|
418
|
+
|
419
|
+
def test_generate_rsa_key_pair_exception(self):
|
420
|
+
"""Test RSA key pair generation with exception."""
|
421
|
+
# This is hard to trigger in practice, but we can test the exception path
|
422
|
+
# by mocking rsa.generate_private_key to raise an exception
|
423
|
+
with patch(
|
424
|
+
"cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key",
|
425
|
+
side_effect=Exception("Test exception"),
|
426
|
+
):
|
427
|
+
with pytest.raises(CryptoError) as exc_info:
|
428
|
+
generate_rsa_key_pair(2048)
|
429
|
+
|
430
|
+
assert "RSA key pair generation failed" in str(exc_info.value)
|
431
|
+
|
432
|
+
def test_sign_data_success(self):
|
433
|
+
"""Test successful data signing."""
|
434
|
+
key_pair = generate_rsa_key_pair(2048)
|
435
|
+
data = "test_data_to_sign"
|
436
|
+
|
437
|
+
signature = sign_data(data, key_pair["private_key"])
|
438
|
+
|
439
|
+
assert isinstance(signature, str)
|
440
|
+
assert len(signature) > 0
|
441
|
+
|
442
|
+
def test_sign_data_bytes_input(self):
|
443
|
+
"""Test data signing with bytes input."""
|
444
|
+
key_pair = generate_rsa_key_pair(2048)
|
445
|
+
data = b"test_data_to_sign"
|
446
|
+
|
447
|
+
signature = sign_data(data, key_pair["private_key"])
|
448
|
+
|
449
|
+
assert isinstance(signature, str)
|
450
|
+
assert len(signature) > 0
|
451
|
+
|
452
|
+
def test_sign_data_exception(self):
|
453
|
+
"""Test data signing with exception."""
|
454
|
+
# Test with invalid private key that will cause an exception
|
455
|
+
with pytest.raises(CryptoError) as exc_info:
|
456
|
+
sign_data("test_data", "invalid_private_key")
|
457
|
+
|
458
|
+
assert "Data signing failed" in str(exc_info.value)
|
459
|
+
|
460
|
+
def test_verify_signature_success(self):
|
461
|
+
"""Test successful signature verification."""
|
462
|
+
key_pair = generate_rsa_key_pair(2048)
|
463
|
+
data = "test_data_to_sign"
|
464
|
+
|
465
|
+
signature = sign_data(data, key_pair["private_key"])
|
466
|
+
result = verify_signature(data, signature, key_pair["public_key"])
|
467
|
+
|
468
|
+
assert result is True
|
469
|
+
|
470
|
+
def test_verify_signature_wrong_data(self):
|
471
|
+
"""Test signature verification with wrong data."""
|
472
|
+
key_pair = generate_rsa_key_pair(2048)
|
473
|
+
data = "test_data_to_sign"
|
474
|
+
wrong_data = "wrong_data"
|
475
|
+
|
476
|
+
signature = sign_data(data, key_pair["private_key"])
|
477
|
+
result = verify_signature(wrong_data, signature, key_pair["public_key"])
|
478
|
+
|
479
|
+
assert result is False
|
480
|
+
|
481
|
+
def test_verify_signature_wrong_signature(self):
|
482
|
+
"""Test signature verification with wrong signature."""
|
483
|
+
key_pair = generate_rsa_key_pair(2048)
|
484
|
+
data = "test_data_to_sign"
|
485
|
+
|
486
|
+
result = verify_signature(data, "wrong_signature", key_pair["public_key"])
|
487
|
+
|
488
|
+
assert result is False
|
489
|
+
|
490
|
+
def test_verify_signature_bytes_input(self):
|
491
|
+
"""Test signature verification with bytes input."""
|
492
|
+
key_pair = generate_rsa_key_pair(2048)
|
493
|
+
data = b"test_data_to_sign"
|
494
|
+
|
495
|
+
signature = sign_data(data, key_pair["private_key"])
|
496
|
+
result = verify_signature(data, signature, key_pair["public_key"])
|
497
|
+
|
498
|
+
assert result is True
|
499
|
+
|
500
|
+
|
501
|
+
class TestHMAC:
|
502
|
+
"""Test suite for HMAC operations."""
|
503
|
+
|
504
|
+
def test_generate_hmac_success(self):
|
505
|
+
"""Test successful HMAC generation."""
|
506
|
+
data = "test_data"
|
507
|
+
key = "test_key"
|
508
|
+
|
509
|
+
hmac_value = generate_hmac(data, key)
|
510
|
+
|
511
|
+
assert isinstance(hmac_value, str)
|
512
|
+
assert len(hmac_value) == 64 # SHA-256 HMAC produces 64 hex characters
|
513
|
+
|
514
|
+
def test_generate_hmac_bytes_input(self):
|
515
|
+
"""Test HMAC generation with bytes input."""
|
516
|
+
data = b"test_data"
|
517
|
+
key = b"test_key"
|
518
|
+
|
519
|
+
hmac_value = generate_hmac(data, key)
|
520
|
+
|
521
|
+
assert isinstance(hmac_value, str)
|
522
|
+
assert len(hmac_value) == 64
|
523
|
+
|
524
|
+
def test_generate_hmac_mixed_input(self):
|
525
|
+
"""Test HMAC generation with mixed string/bytes input."""
|
526
|
+
data = "test_data"
|
527
|
+
key = b"test_key"
|
528
|
+
|
529
|
+
hmac_value = generate_hmac(data, key)
|
530
|
+
|
531
|
+
assert isinstance(hmac_value, str)
|
532
|
+
assert len(hmac_value) == 64
|
533
|
+
|
534
|
+
def test_verify_hmac_success(self):
|
535
|
+
"""Test successful HMAC verification."""
|
536
|
+
data = "test_data"
|
537
|
+
key = "test_key"
|
538
|
+
|
539
|
+
hmac_value = generate_hmac(data, key)
|
540
|
+
result = verify_hmac(data, key, hmac_value)
|
541
|
+
|
542
|
+
assert result is True
|
543
|
+
|
544
|
+
def test_verify_hmac_wrong_data(self):
|
545
|
+
"""Test HMAC verification with wrong data."""
|
546
|
+
data = "test_data"
|
547
|
+
wrong_data = "wrong_data"
|
548
|
+
key = "test_key"
|
549
|
+
|
550
|
+
hmac_value = generate_hmac(data, key)
|
551
|
+
result = verify_hmac(wrong_data, key, hmac_value)
|
552
|
+
|
553
|
+
assert result is False
|
554
|
+
|
555
|
+
def test_verify_hmac_wrong_key(self):
|
556
|
+
"""Test HMAC verification with wrong key."""
|
557
|
+
data = "test_data"
|
558
|
+
key = "test_key"
|
559
|
+
wrong_key = "wrong_key"
|
560
|
+
|
561
|
+
hmac_value = generate_hmac(data, key)
|
562
|
+
result = verify_hmac(data, wrong_key, hmac_value)
|
563
|
+
|
564
|
+
assert result is False
|
565
|
+
|
566
|
+
def test_verify_hmac_wrong_hmac(self):
|
567
|
+
"""Test HMAC verification with wrong HMAC value."""
|
568
|
+
data = "test_data"
|
569
|
+
key = "test_key"
|
570
|
+
|
571
|
+
result = verify_hmac(data, key, "wrong_hmac")
|
572
|
+
|
573
|
+
assert result is False
|
574
|
+
|
575
|
+
def test_verify_hmac_bytes_input(self):
|
576
|
+
"""Test HMAC verification with bytes input."""
|
577
|
+
data = b"test_data"
|
578
|
+
key = b"test_key"
|
579
|
+
|
580
|
+
hmac_value = generate_hmac(data, key)
|
581
|
+
result = verify_hmac(data, key, hmac_value)
|
582
|
+
|
583
|
+
assert result is True
|
584
|
+
|
585
|
+
def test_generate_hmac_exception(self):
|
586
|
+
"""Test HMAC generation with exception."""
|
587
|
+
# This is hard to trigger in practice, but we can test the exception path
|
588
|
+
# by mocking hmac.new to raise an exception
|
589
|
+
with patch("hmac.new", side_effect=Exception("Test exception")):
|
590
|
+
with pytest.raises(CryptoError) as exc_info:
|
591
|
+
generate_hmac("test_data", "test_key")
|
592
|
+
|
593
|
+
assert "HMAC generation failed" in str(exc_info.value)
|
594
|
+
|
595
|
+
def test_verify_hmac_exception(self):
|
596
|
+
"""Test HMAC verification with exception."""
|
597
|
+
# Test with invalid data that will cause an exception in generate_hmac
|
598
|
+
with patch(
|
599
|
+
"mcp_security_framework.utils.crypto_utils.generate_hmac",
|
600
|
+
side_effect=Exception("Test exception"),
|
601
|
+
):
|
602
|
+
result = verify_hmac("test_data", "test_key", "expected_hmac")
|
603
|
+
assert result is False
|