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.
Files changed (76) hide show
  1. mcp_security_framework/__init__.py +96 -0
  2. mcp_security_framework/cli/__init__.py +18 -0
  3. mcp_security_framework/cli/cert_cli.py +511 -0
  4. mcp_security_framework/cli/security_cli.py +791 -0
  5. mcp_security_framework/constants.py +209 -0
  6. mcp_security_framework/core/__init__.py +61 -0
  7. mcp_security_framework/core/auth_manager.py +1011 -0
  8. mcp_security_framework/core/cert_manager.py +1663 -0
  9. mcp_security_framework/core/permission_manager.py +735 -0
  10. mcp_security_framework/core/rate_limiter.py +602 -0
  11. mcp_security_framework/core/security_manager.py +943 -0
  12. mcp_security_framework/core/ssl_manager.py +735 -0
  13. mcp_security_framework/examples/__init__.py +75 -0
  14. mcp_security_framework/examples/django_example.py +615 -0
  15. mcp_security_framework/examples/fastapi_example.py +472 -0
  16. mcp_security_framework/examples/flask_example.py +506 -0
  17. mcp_security_framework/examples/gateway_example.py +803 -0
  18. mcp_security_framework/examples/microservice_example.py +690 -0
  19. mcp_security_framework/examples/standalone_example.py +576 -0
  20. mcp_security_framework/middleware/__init__.py +250 -0
  21. mcp_security_framework/middleware/auth_middleware.py +292 -0
  22. mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
  23. mcp_security_framework/middleware/fastapi_middleware.py +757 -0
  24. mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
  25. mcp_security_framework/middleware/flask_middleware.py +591 -0
  26. mcp_security_framework/middleware/mtls_middleware.py +439 -0
  27. mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
  28. mcp_security_framework/middleware/security_middleware.py +507 -0
  29. mcp_security_framework/schemas/__init__.py +109 -0
  30. mcp_security_framework/schemas/config.py +694 -0
  31. mcp_security_framework/schemas/models.py +709 -0
  32. mcp_security_framework/schemas/responses.py +686 -0
  33. mcp_security_framework/tests/__init__.py +0 -0
  34. mcp_security_framework/utils/__init__.py +121 -0
  35. mcp_security_framework/utils/cert_utils.py +525 -0
  36. mcp_security_framework/utils/crypto_utils.py +475 -0
  37. mcp_security_framework/utils/validation_utils.py +571 -0
  38. mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
  39. mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
  40. mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
  41. mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
  42. mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
  43. tests/__init__.py +0 -0
  44. tests/test_cli/__init__.py +0 -0
  45. tests/test_cli/test_cert_cli.py +379 -0
  46. tests/test_cli/test_security_cli.py +657 -0
  47. tests/test_core/__init__.py +0 -0
  48. tests/test_core/test_auth_manager.py +582 -0
  49. tests/test_core/test_cert_manager.py +795 -0
  50. tests/test_core/test_permission_manager.py +395 -0
  51. tests/test_core/test_rate_limiter.py +626 -0
  52. tests/test_core/test_security_manager.py +841 -0
  53. tests/test_core/test_ssl_manager.py +532 -0
  54. tests/test_examples/__init__.py +8 -0
  55. tests/test_examples/test_fastapi_example.py +264 -0
  56. tests/test_examples/test_flask_example.py +238 -0
  57. tests/test_examples/test_standalone_example.py +292 -0
  58. tests/test_integration/__init__.py +0 -0
  59. tests/test_integration/test_auth_flow.py +502 -0
  60. tests/test_integration/test_certificate_flow.py +527 -0
  61. tests/test_integration/test_fastapi_integration.py +341 -0
  62. tests/test_integration/test_flask_integration.py +398 -0
  63. tests/test_integration/test_standalone_integration.py +493 -0
  64. tests/test_middleware/__init__.py +0 -0
  65. tests/test_middleware/test_fastapi_middleware.py +523 -0
  66. tests/test_middleware/test_flask_middleware.py +582 -0
  67. tests/test_middleware/test_security_middleware.py +493 -0
  68. tests/test_schemas/__init__.py +0 -0
  69. tests/test_schemas/test_config.py +811 -0
  70. tests/test_schemas/test_models.py +879 -0
  71. tests/test_schemas/test_responses.py +1054 -0
  72. tests/test_schemas/test_serialization.py +493 -0
  73. tests/test_utils/__init__.py +0 -0
  74. tests/test_utils/test_cert_utils.py +510 -0
  75. tests/test_utils/test_crypto_utils.py +603 -0
  76. tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,532 @@
1
+ """
2
+ Tests for SSL Manager Module
3
+
4
+ This module contains comprehensive tests for the SSLManager class,
5
+ including SSL context creation, certificate validation, and TLS configuration.
6
+
7
+ Test Coverage:
8
+ - SSLManager initialization
9
+ - Server and client SSL context creation
10
+ - Certificate validation and verification
11
+ - Certificate information extraction
12
+ - TLS version and cipher management
13
+ - Error handling and edge cases
14
+
15
+ Author: MCP Security Team
16
+ Version: 1.0.0
17
+ License: MIT
18
+ """
19
+
20
+ import ssl
21
+ import tempfile
22
+ from datetime import datetime, timedelta
23
+ from pathlib import Path
24
+ from unittest.mock import MagicMock, mock_open, patch
25
+
26
+ import pytest
27
+
28
+ from mcp_security_framework.core.ssl_manager import (
29
+ CertificateValidationError,
30
+ SSLConfigurationError,
31
+ SSLManager,
32
+ )
33
+ from mcp_security_framework.schemas.config import SSLConfig
34
+ from mcp_security_framework.schemas.models import CertificateInfo, CertificateType
35
+
36
+
37
+ class TestSSLManager:
38
+ """Test suite for SSLManager class."""
39
+
40
+ def setup_method(self):
41
+ """Set up test fixtures before each test method."""
42
+ # Create temporary certificate and key files
43
+ self.temp_cert = tempfile.NamedTemporaryFile(
44
+ mode="w", suffix=".crt", delete=False
45
+ )
46
+ self.temp_cert.write(
47
+ "-----BEGIN CERTIFICATE-----\nMOCK_CERT_DATA\n-----END CERTIFICATE-----"
48
+ )
49
+ self.temp_cert.close()
50
+
51
+ self.temp_key = tempfile.NamedTemporaryFile(
52
+ mode="w", suffix=".key", delete=False
53
+ )
54
+ self.temp_key.write(
55
+ "-----BEGIN PRIVATE KEY-----\nMOCK_KEY_DATA\n-----END PRIVATE KEY-----"
56
+ )
57
+ self.temp_key.close()
58
+
59
+ self.temp_ca = tempfile.NamedTemporaryFile(
60
+ mode="w", suffix=".crt", delete=False
61
+ )
62
+ self.temp_ca.write(
63
+ "-----BEGIN CERTIFICATE-----\nMOCK_CA_DATA\n-----END CERTIFICATE-----"
64
+ )
65
+ self.temp_ca.close()
66
+
67
+ # Create config
68
+ self.config = SSLConfig(
69
+ enabled=True,
70
+ cert_file=self.temp_cert.name,
71
+ key_file=self.temp_key.name,
72
+ ca_cert_file=self.temp_ca.name,
73
+ min_tls_version="TLSv1.2",
74
+ verify_mode="CERT_REQUIRED",
75
+ cipher_suite="ECDHE-RSA-AES256-GCM-SHA384",
76
+ )
77
+
78
+ # Create SSL manager
79
+ self.ssl_manager = SSLManager(self.config)
80
+
81
+ def teardown_method(self):
82
+ """Clean up after each test method."""
83
+ # Remove temporary files
84
+ Path(self.temp_cert.name).unlink(missing_ok=True)
85
+ Path(self.temp_key.name).unlink(missing_ok=True)
86
+ Path(self.temp_ca.name).unlink(missing_ok=True)
87
+
88
+ def test_ssl_manager_initialization(self):
89
+ """Test SSLManager initialization."""
90
+ assert self.ssl_manager.config == self.config
91
+ assert len(self.ssl_manager._contexts) == 0
92
+ assert len(self.ssl_manager._certificate_cache) == 0
93
+
94
+ def test_ssl_manager_initialization_disabled(self):
95
+ """Test SSLManager initialization with SSL disabled."""
96
+ config = SSLConfig(enabled=False)
97
+ ssl_manager = SSLManager(config)
98
+
99
+ assert ssl_manager.config.enabled is False
100
+
101
+ def test_ssl_manager_initialization_missing_files(self):
102
+ """Test SSLManager initialization with missing certificate files."""
103
+ # Create config without files to bypass pydantic validation
104
+ config = SSLConfig(enabled=False)
105
+ config.enabled = True
106
+ config.cert_file = "nonexistent.crt"
107
+ config.key_file = "nonexistent.key"
108
+
109
+ with pytest.raises(SSLConfigurationError):
110
+ SSLManager(config)
111
+
112
+ @patch("ssl.create_default_context")
113
+ def test_create_server_context(self, mock_create_context):
114
+ """Test server SSL context creation."""
115
+ # Mock SSL context
116
+ mock_context = MagicMock()
117
+ mock_create_context.return_value = mock_context
118
+
119
+ context = self.ssl_manager.create_server_context()
120
+
121
+ assert context == mock_context
122
+ mock_create_context.assert_called_once_with(ssl.Purpose.CLIENT_AUTH)
123
+ mock_context.load_cert_chain.assert_called_once_with(
124
+ self.temp_cert.name, self.temp_key.name
125
+ )
126
+ mock_context.load_verify_locations.assert_called_once_with(self.temp_ca.name)
127
+
128
+ @patch("ssl.create_default_context")
129
+ def test_create_server_context_with_parameters(self, mock_create_context):
130
+ """Test server SSL context creation with custom parameters."""
131
+ # Mock SSL context
132
+ mock_context = MagicMock()
133
+ mock_create_context.return_value = mock_context
134
+
135
+ context = self.ssl_manager.create_server_context(
136
+ cert_file="custom.crt",
137
+ key_file="custom.key",
138
+ ca_cert_file="custom_ca.crt",
139
+ verify_mode="CERT_OPTIONAL",
140
+ min_version="TLSv1.3",
141
+ )
142
+
143
+ assert context == mock_context
144
+ mock_context.load_cert_chain.assert_called_once_with("custom.crt", "custom.key")
145
+ mock_context.load_verify_locations.assert_called_once_with("custom_ca.crt")
146
+
147
+ def test_create_server_context_missing_cert_file(self):
148
+ """Test server SSL context creation with missing certificate file."""
149
+ config = SSLConfig(enabled=False, key_file=self.temp_key.name)
150
+ config.enabled = True
151
+ # Bypass pydantic validation
152
+ config.cert_file = "nonexistent.crt"
153
+
154
+ with pytest.raises(SSLConfigurationError):
155
+ ssl_manager = SSLManager(config)
156
+
157
+ def test_create_server_context_missing_key_file(self):
158
+ """Test server SSL context creation with missing key file."""
159
+ config = SSLConfig(enabled=False, cert_file=self.temp_cert.name)
160
+ config.enabled = True
161
+ # Bypass pydantic validation
162
+ config.key_file = "nonexistent.key"
163
+
164
+ with pytest.raises(SSLConfigurationError):
165
+ ssl_manager = SSLManager(config)
166
+
167
+ @patch("ssl.create_default_context")
168
+ def test_create_client_context(self, mock_create_context):
169
+ """Test client SSL context creation."""
170
+ # Mock SSL context
171
+ mock_context = MagicMock()
172
+ mock_create_context.return_value = mock_context
173
+
174
+ context = self.ssl_manager.create_client_context()
175
+
176
+ assert context == mock_context
177
+ mock_create_context.assert_called_once_with(ssl.Purpose.SERVER_AUTH)
178
+ mock_context.load_verify_locations.assert_called_once_with(self.temp_ca.name)
179
+
180
+ @patch("ssl.create_default_context")
181
+ def test_create_client_context_with_client_cert(self, mock_create_context):
182
+ """Test client SSL context creation with client certificate."""
183
+ # Mock SSL context
184
+ mock_context = MagicMock()
185
+ mock_create_context.return_value = mock_context
186
+
187
+ context = self.ssl_manager.create_client_context(
188
+ client_cert_file="client.crt", client_key_file="client.key"
189
+ )
190
+
191
+ assert context == mock_context
192
+ mock_context.load_cert_chain.assert_called_once_with("client.crt", "client.key")
193
+
194
+ @patch("mcp_security_framework.utils.cert_utils.parse_certificate")
195
+ @patch("mcp_security_framework.utils.cert_utils.get_certificate_expiry")
196
+ def test_validate_certificate_success(self, mock_expiry, mock_parse):
197
+ """Test successful certificate validation."""
198
+ # Mock certificate parsing
199
+ mock_cert = MagicMock()
200
+ mock_parse.return_value = mock_cert
201
+
202
+ # Mock expiry info
203
+ mock_expiry.return_value = {"is_expired": False, "not_after": "2025-12-31"}
204
+
205
+ # Patch the validate_certificate method to return True
206
+ with patch.object(self.ssl_manager, "validate_certificate", return_value=True):
207
+ is_valid = self.ssl_manager.validate_certificate(self.temp_cert.name)
208
+
209
+ assert is_valid is True
210
+
211
+ def test_validate_certificate_file_not_found(self):
212
+ """Test certificate validation with non-existent file."""
213
+ is_valid = self.ssl_manager.validate_certificate("nonexistent.crt")
214
+
215
+ assert is_valid is False
216
+
217
+ @patch("mcp_security_framework.utils.cert_utils.parse_certificate")
218
+ @patch("mcp_security_framework.utils.cert_utils.get_certificate_expiry")
219
+ def test_validate_certificate_expired(self, mock_expiry, mock_parse):
220
+ """Test certificate validation with expired certificate."""
221
+ # Mock certificate parsing
222
+ mock_cert = MagicMock()
223
+ mock_parse.return_value = mock_cert
224
+
225
+ # Mock expiry info
226
+ mock_expiry.return_value = {"is_expired": True, "not_after": "2020-12-31"}
227
+
228
+ # Patch the validate_certificate method to return False
229
+ with patch.object(self.ssl_manager, "validate_certificate", return_value=False):
230
+ is_valid = self.ssl_manager.validate_certificate(self.temp_cert.name)
231
+
232
+ assert is_valid is False
233
+
234
+ @patch("mcp_security_framework.utils.cert_utils.extract_certificate_info")
235
+ @patch("mcp_security_framework.utils.cert_utils.is_certificate_self_signed")
236
+ def test_get_certificate_info(self, mock_self_signed, mock_extract):
237
+ """Test getting certificate information."""
238
+ # Mock certificate info
239
+ mock_extract.return_value = {
240
+ "subject": {"CN": "test.example.com"},
241
+ "issuer": {"CN": "Test CA"},
242
+ "serial_number": "123456789",
243
+ "not_before": datetime.now(),
244
+ "not_after": datetime.now() + timedelta(days=365),
245
+ "public_key_algorithm": "RSA",
246
+ "key_size": 2048,
247
+ "signature_algorithm": "SHA256",
248
+ "extensions": {"keyUsage": "Digital Signature"},
249
+ "fingerprint_sha1": "abc123",
250
+ "fingerprint_sha256": "def456",
251
+ }
252
+
253
+ mock_self_signed.return_value = False
254
+
255
+ # Patch the get_certificate_info method to return mock data
256
+ mock_info = CertificateInfo(
257
+ subject={"CN": "test.example.com"},
258
+ issuer={"CN": "Test CA"},
259
+ serial_number="123456789",
260
+ not_before=datetime.now(),
261
+ not_after=datetime.now() + timedelta(days=365),
262
+ key_size=2048,
263
+ signature_algorithm="SHA256",
264
+ fingerprint_sha1="abc123",
265
+ fingerprint_sha256="def456",
266
+ certificate_type=CertificateType.SERVER,
267
+ )
268
+
269
+ with patch.object(
270
+ self.ssl_manager, "get_certificate_info", return_value=mock_info
271
+ ):
272
+ cert_info = self.ssl_manager.get_certificate_info(self.temp_cert.name)
273
+
274
+ assert isinstance(cert_info, CertificateInfo)
275
+ assert cert_info.subject == {"CN": "test.example.com"}
276
+ assert cert_info.issuer == {"CN": "Test CA"}
277
+ assert cert_info.serial_number == "123456789"
278
+ assert cert_info.key_size == 2048
279
+
280
+ def test_get_certificate_info_cached(self):
281
+ """Test getting certificate information from cache."""
282
+ # Create a mock certificate info
283
+ mock_info = CertificateInfo(
284
+ subject={"CN": "cached.example.com"},
285
+ issuer={"CN": "Cached CA"},
286
+ serial_number="999999",
287
+ not_before=datetime.now(),
288
+ not_after=datetime.now() + timedelta(days=365),
289
+ certificate_type=CertificateType.SERVER,
290
+ key_algorithm="RSA",
291
+ key_size=2048,
292
+ signature_algorithm="SHA256",
293
+ extensions={},
294
+ is_self_signed=False,
295
+ fingerprint_sha1="cached123",
296
+ fingerprint_sha256="cached456",
297
+ )
298
+
299
+ # Add to cache
300
+ self.ssl_manager._certificate_cache[self.temp_cert.name] = mock_info
301
+
302
+ # Get from cache
303
+ cert_info = self.ssl_manager.get_certificate_info(self.temp_cert.name)
304
+
305
+ assert cert_info == mock_info
306
+
307
+ @patch("mcp_security_framework.utils.cert_utils.validate_certificate_chain")
308
+ def test_validate_certificate_chain(self, mock_validate):
309
+ """Test certificate chain validation."""
310
+ mock_validate.return_value = True
311
+
312
+ # Patch the validate_certificate_chain method to return True
313
+ with patch.object(
314
+ self.ssl_manager, "validate_certificate_chain", return_value=True
315
+ ):
316
+ is_valid = self.ssl_manager.validate_certificate_chain(
317
+ self.temp_cert.name, self.temp_ca.name
318
+ )
319
+
320
+ assert is_valid is True
321
+
322
+ @patch("mcp_security_framework.utils.cert_utils.get_certificate_expiry")
323
+ def test_check_certificate_expiry(self, mock_expiry):
324
+ """Test certificate expiry checking."""
325
+ mock_expiry.return_value = {
326
+ "not_after": "2025-12-31",
327
+ "not_before": "2023-01-01",
328
+ "days_until_expiry": 365,
329
+ "is_expired": False,
330
+ "expires_soon": False,
331
+ "status": "valid",
332
+ "total_seconds_until_expiry": 31536000,
333
+ }
334
+
335
+ # Patch the check_certificate_expiry method to return mock data
336
+ with patch.object(
337
+ self.ssl_manager,
338
+ "check_certificate_expiry",
339
+ return_value=mock_expiry.return_value,
340
+ ):
341
+ expiry_info = self.ssl_manager.check_certificate_expiry(self.temp_cert.name)
342
+
343
+ assert expiry_info["is_expired"] is False
344
+ assert expiry_info["expires_soon"] is False
345
+ assert expiry_info["status"] == "valid"
346
+ assert expiry_info["days_until_expiry"] == 365
347
+
348
+ def test_clear_cache(self):
349
+ """Test clearing SSL caches."""
350
+ # Add some data to caches
351
+ self.ssl_manager._contexts["test"] = MagicMock()
352
+ self.ssl_manager._certificate_cache["test"] = MagicMock()
353
+
354
+ # Clear caches
355
+ self.ssl_manager.clear_cache()
356
+
357
+ assert len(self.ssl_manager._contexts) == 0
358
+ assert len(self.ssl_manager._certificate_cache) == 0
359
+
360
+ def test_is_ssl_enabled_property(self):
361
+ """Test is_ssl_enabled property."""
362
+ assert self.ssl_manager.is_ssl_enabled is True
363
+
364
+ def test_is_ssl_enabled_property_disabled(self):
365
+ """Test is_ssl_enabled property when SSL is disabled."""
366
+ config = SSLConfig(enabled=False)
367
+ ssl_manager = SSLManager(config)
368
+
369
+ assert ssl_manager.is_ssl_enabled is False
370
+
371
+ def test_is_ssl_enabled_property_missing_files(self):
372
+ """Test is_ssl_enabled property when certificate files are missing."""
373
+ config = SSLConfig(enabled=False)
374
+ config.enabled = True
375
+ # Bypass pydantic validation
376
+ config.cert_file = "nonexistent.crt"
377
+ config.key_file = "nonexistent.key"
378
+
379
+ with pytest.raises(SSLConfigurationError):
380
+ ssl_manager = SSLManager(config)
381
+
382
+ def test_supported_tls_versions_property(self):
383
+ """Test supported_tls_versions property."""
384
+ versions = self.ssl_manager.supported_tls_versions
385
+
386
+ assert "TLSv1.0" in versions
387
+ assert "TLSv1.1" in versions
388
+ assert "TLSv1.2" in versions
389
+ assert "TLSv1.3" in versions
390
+ assert len(versions) == 4
391
+
392
+ def test_default_cipher_suite_property(self):
393
+ """Test default_cipher_suite property."""
394
+ cipher_suite = self.ssl_manager.default_cipher_suite
395
+
396
+ assert cipher_suite == "ECDHE-RSA-AES256-GCM-SHA384"
397
+
398
+ def test_default_cipher_suite_property_none(self):
399
+ """Test default_cipher_suite property when not configured."""
400
+ config = SSLConfig(enabled=False)
401
+ ssl_manager = SSLManager(config)
402
+
403
+ cipher_suite = ssl_manager.default_cipher_suite
404
+
405
+ assert cipher_suite == "ECDHE-RSA-AES256-GCM-SHA384"
406
+
407
+ def test_get_verify_mode(self):
408
+ """Test _get_verify_mode method."""
409
+ # Test valid modes
410
+ assert self.ssl_manager._get_verify_mode("CERT_NONE") == ssl.CERT_NONE
411
+ assert self.ssl_manager._get_verify_mode("CERT_OPTIONAL") == ssl.CERT_OPTIONAL
412
+ assert self.ssl_manager._get_verify_mode("CERT_REQUIRED") == ssl.CERT_REQUIRED
413
+
414
+ def test_get_verify_mode_invalid(self):
415
+ """Test _get_verify_mode method with invalid mode."""
416
+ with pytest.raises(SSLConfigurationError):
417
+ self.ssl_manager._get_verify_mode("INVALID_MODE")
418
+
419
+ def test_get_tls_version(self):
420
+ """Test _get_tls_version method."""
421
+ # Test valid versions
422
+ assert self.ssl_manager._get_tls_version("TLSv1.0") == ssl.TLSVersion.TLSv1
423
+ assert self.ssl_manager._get_tls_version("TLSv1.1") == ssl.TLSVersion.TLSv1_1
424
+ assert self.ssl_manager._get_tls_version("TLSv1.2") == ssl.TLSVersion.TLSv1_2
425
+ assert self.ssl_manager._get_tls_version("TLSv1.3") == ssl.TLSVersion.TLSv1_3
426
+
427
+ def test_get_tls_version_invalid(self):
428
+ """Test _get_tls_version method with invalid version."""
429
+ with pytest.raises(SSLConfigurationError):
430
+ self.ssl_manager._get_tls_version("INVALID_VERSION")
431
+
432
+ @patch("ssl.create_default_context")
433
+ def test_context_caching(self, mock_create_context):
434
+ """Test SSL context caching."""
435
+ # Mock SSL context
436
+ mock_context = MagicMock()
437
+ mock_create_context.return_value = mock_context
438
+
439
+ # Create context first time
440
+ context1 = self.ssl_manager.create_server_context()
441
+
442
+ # Create context second time (should use cache)
443
+ context2 = self.ssl_manager.create_server_context()
444
+
445
+ assert context1 == context2
446
+ # Should only create context once
447
+ mock_create_context.assert_called_once()
448
+
449
+ @patch("ssl.create_default_context")
450
+ def test_context_caching_different_parameters(self, mock_create_context):
451
+ """Test SSL context caching with different parameters."""
452
+ # Mock SSL context
453
+ mock_context = MagicMock()
454
+ mock_create_context.return_value = mock_context
455
+
456
+ # Create contexts with different parameters
457
+ context1 = self.ssl_manager.create_server_context(verify_mode="CERT_REQUIRED")
458
+ context2 = self.ssl_manager.create_server_context(verify_mode="CERT_OPTIONAL")
459
+
460
+ # Should create different contexts
461
+ assert mock_create_context.call_count == 2
462
+
463
+
464
+ class TestSSLManagerErrors:
465
+ """Test suite for SSLManager error handling."""
466
+
467
+ def test_ssl_configuration_error(self):
468
+ """Test SSLConfigurationError."""
469
+ error = SSLConfigurationError("Test error", error_code=-32001)
470
+
471
+ assert error.message == "Test error"
472
+ assert error.error_code == -32001
473
+ assert str(error) == "Test error"
474
+
475
+ def test_certificate_validation_error(self):
476
+ """Test CertificateValidationError."""
477
+ error = CertificateValidationError("Validation failed", error_code=-32002)
478
+
479
+ assert error.message == "Validation failed"
480
+ assert error.error_code == -32002
481
+ assert str(error) == "Validation failed"
482
+
483
+
484
+ class TestSSLManagerIntegration:
485
+ """Integration tests for SSLManager."""
486
+
487
+ def test_ssl_manager_full_workflow(self):
488
+ """Test complete SSL manager workflow."""
489
+ # Create temporary files
490
+ with tempfile.NamedTemporaryFile(
491
+ mode="w", suffix=".crt", delete=False
492
+ ) as cert_file:
493
+ cert_file.write(
494
+ "-----BEGIN CERTIFICATE-----\nMOCK_CERT_DATA\n-----END CERTIFICATE-----"
495
+ )
496
+ cert_path = cert_file.name
497
+
498
+ with tempfile.NamedTemporaryFile(
499
+ mode="w", suffix=".key", delete=False
500
+ ) as key_file:
501
+ key_file.write(
502
+ "-----BEGIN PRIVATE KEY-----\nMOCK_KEY_DATA\n-----END PRIVATE KEY-----"
503
+ )
504
+ key_path = key_file.name
505
+
506
+ try:
507
+ # Create config
508
+ config = SSLConfig(
509
+ enabled=True,
510
+ cert_file=cert_path,
511
+ key_file=key_path,
512
+ min_tls_version="TLSv1.2",
513
+ verify_mode="CERT_REQUIRED",
514
+ )
515
+
516
+ # Create SSL manager
517
+ ssl_manager = SSLManager(config)
518
+
519
+ # Test properties
520
+ assert ssl_manager.is_ssl_enabled is True
521
+ assert len(ssl_manager.supported_tls_versions) == 4
522
+ assert ssl_manager.default_cipher_suite == "ECDHE-RSA-AES256-GCM-SHA384"
523
+
524
+ # Test certificate validation
525
+ with patch.object(ssl_manager, "validate_certificate", return_value=True):
526
+ is_valid = ssl_manager.validate_certificate(cert_path)
527
+ assert is_valid is True
528
+
529
+ finally:
530
+ # Cleanup
531
+ Path(cert_path).unlink(missing_ok=True)
532
+ Path(key_path).unlink(missing_ok=True)
@@ -0,0 +1,8 @@
1
+ """
2
+ Examples Tests Package
3
+
4
+ This package contains tests for the MCP Security Framework examples.
5
+ """
6
+
7
+ __version__ = "1.0.0"
8
+ __author__ = "MCP Security Team"