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,795 @@
1
+ """
2
+ Tests for Certificate Manager Module
3
+
4
+ This module contains comprehensive tests for the CertificateManager class,
5
+ covering all certificate creation and management functionality.
6
+
7
+ Test Coverage:
8
+ - Root CA certificate creation
9
+ - Client certificate creation
10
+ - Server certificate creation
11
+ - Certificate revocation
12
+ - Certificate chain validation
13
+ - Certificate information extraction
14
+ - Error handling and edge cases
15
+ - Configuration validation
16
+
17
+ Author: MCP Security Team
18
+ Version: 1.0.0
19
+ License: MIT
20
+ """
21
+
22
+ import os
23
+ import tempfile
24
+ from datetime import datetime, timedelta, timezone
25
+ from unittest.mock import MagicMock, Mock, patch
26
+
27
+ import pytest
28
+ from pydantic import ValidationError
29
+
30
+ from mcp_security_framework.core.cert_manager import (
31
+ CertificateConfigurationError,
32
+ CertificateGenerationError,
33
+ CertificateManager,
34
+ CertificateValidationError,
35
+ )
36
+ from mcp_security_framework.schemas.config import (
37
+ CAConfig,
38
+ CertificateConfig,
39
+ ClientCertConfig,
40
+ ServerCertConfig,
41
+ )
42
+ from mcp_security_framework.schemas.models import (
43
+ CertificateInfo,
44
+ CertificatePair,
45
+ CertificateType,
46
+ )
47
+
48
+
49
+ class TestCertificateManager:
50
+ """Test suite for CertificateManager class."""
51
+
52
+ def setup_method(self):
53
+ """Set up test fixtures before each test method."""
54
+ # Create temporary directory for test certificates
55
+ self.temp_dir = tempfile.mkdtemp()
56
+
57
+ # Create test CA certificate and key files
58
+ self.ca_cert_path = os.path.join(self.temp_dir, "test_ca.crt")
59
+ self.ca_key_path = os.path.join(self.temp_dir, "test_ca.key")
60
+
61
+ # Create dummy CA files for testing
62
+ with open(self.ca_cert_path, "w") as f:
63
+ f.write(
64
+ "-----BEGIN CERTIFICATE-----\nDUMMY CA CERT\n-----END CERTIFICATE-----"
65
+ )
66
+
67
+ with open(self.ca_key_path, "w") as f:
68
+ f.write(
69
+ "-----BEGIN PRIVATE KEY-----\nDUMMY CA KEY\n-----END PRIVATE KEY-----"
70
+ )
71
+
72
+ # Create test configuration
73
+ self.cert_config = CertificateConfig(
74
+ enabled=True,
75
+ ca_cert_path=self.ca_cert_path,
76
+ ca_key_path=self.ca_key_path,
77
+ cert_storage_path=self.temp_dir,
78
+ key_storage_path=self.temp_dir,
79
+ )
80
+
81
+ # Create certificate manager
82
+ self.cert_manager = CertificateManager(self.cert_config)
83
+
84
+ def teardown_method(self):
85
+ """Clean up after each test method."""
86
+ # Remove temporary directory
87
+ import shutil
88
+
89
+ shutil.rmtree(self.temp_dir, ignore_errors=True)
90
+
91
+ def test_init_success(self):
92
+ """Test successful CertificateManager initialization."""
93
+ assert self.cert_manager.config == self.cert_config
94
+ assert self.cert_manager._certificate_cache == {}
95
+ assert self.cert_manager._crl_cache == {}
96
+
97
+ def test_init_missing_ca_cert(self):
98
+ """Test initialization with missing CA certificate."""
99
+ config = CertificateConfig(
100
+ enabled=True,
101
+ ca_cert_path="/nonexistent/ca.crt", ca_key_path=self.ca_key_path
102
+ )
103
+
104
+ with pytest.raises(CertificateConfigurationError) as exc_info:
105
+ CertificateManager(config)
106
+
107
+ assert "CA certificate file not found" in str(exc_info.value)
108
+
109
+ def test_init_missing_ca_key(self):
110
+ """Test initialization with missing CA key."""
111
+ config = CertificateConfig(
112
+ enabled=True,
113
+ ca_cert_path=self.ca_cert_path, ca_key_path="/nonexistent/ca.key"
114
+ )
115
+
116
+ with pytest.raises(CertificateConfigurationError) as exc_info:
117
+ CertificateManager(config)
118
+
119
+ assert "CA private key file not found" in str(exc_info.value)
120
+
121
+ def test_create_root_ca_success(self):
122
+ """Test successful root CA certificate creation."""
123
+ ca_config = CAConfig(
124
+ common_name="Test Root CA",
125
+ organization="Test Organization",
126
+ country="US",
127
+ state="California",
128
+ locality="San Francisco",
129
+ validity_years=10,
130
+ key_size=2048,
131
+ )
132
+
133
+ with patch(
134
+ "cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key"
135
+ ) as mock_rsa:
136
+ with patch("cryptography.x509.CertificateBuilder") as mock_builder:
137
+ with patch("builtins.open", create=True) as mock_open:
138
+ # Mock the certificate building process
139
+ mock_cert = Mock()
140
+ mock_cert.serial_number = 123456789
141
+ mock_cert.not_valid_before = datetime.now(timezone.utc)
142
+ mock_cert.not_valid_after = datetime.now(timezone.utc) + timedelta(
143
+ days=3650
144
+ )
145
+ mock_cert.public_bytes.return_value = b"-----BEGIN CERTIFICATE-----\nMOCK CERT\n-----END CERTIFICATE-----"
146
+
147
+ mock_private_key = Mock()
148
+ mock_public_key = Mock()
149
+ mock_public_key.public_bytes.return_value = b"mock_public_key_data"
150
+ mock_private_key.public_key.return_value = mock_public_key
151
+ mock_private_key.private_bytes.return_value = b"-----BEGIN PRIVATE KEY-----\nMOCK KEY\n-----END PRIVATE KEY-----"
152
+
153
+ mock_rsa.return_value = mock_private_key
154
+
155
+ # Configure the builder mock to return the certificate
156
+ mock_builder_instance = Mock()
157
+ mock_builder_instance.subject_name.return_value = (
158
+ mock_builder_instance
159
+ )
160
+ mock_builder_instance.issuer_name.return_value = (
161
+ mock_builder_instance
162
+ )
163
+ mock_builder_instance.public_key.return_value = (
164
+ mock_builder_instance
165
+ )
166
+ mock_builder_instance.serial_number.return_value = (
167
+ mock_builder_instance
168
+ )
169
+ mock_builder_instance.not_valid_before.return_value = (
170
+ mock_builder_instance
171
+ )
172
+ mock_builder_instance.not_valid_after.return_value = (
173
+ mock_builder_instance
174
+ )
175
+ mock_builder_instance.add_extension.return_value = (
176
+ mock_builder_instance
177
+ )
178
+ mock_builder_instance.sign.return_value = mock_cert
179
+ mock_builder.return_value = mock_builder_instance
180
+
181
+ cert_pair = self.cert_manager.create_root_ca(ca_config)
182
+
183
+ assert isinstance(cert_pair, CertificatePair)
184
+ assert cert_pair.certificate_path.endswith("test_root_ca_ca.crt")
185
+ assert cert_pair.private_key_path.endswith("test_root_ca_ca.key")
186
+ assert cert_pair.serial_number == "123456789"
187
+
188
+ def test_create_root_ca_missing_common_name(self):
189
+ """Test root CA creation with missing common name."""
190
+ ca_config = CAConfig(
191
+ common_name="",
192
+ organization="Test Organization",
193
+ country="US",
194
+ validity_years=10,
195
+ )
196
+
197
+ with pytest.raises(CertificateGenerationError) as exc_info:
198
+ self.cert_manager.create_root_ca(ca_config)
199
+
200
+ assert "Common name is required for CA certificate" in str(exc_info.value)
201
+
202
+ def test_create_client_certificate_success(self):
203
+ """Test successful client certificate creation."""
204
+ client_config = ClientCertConfig(
205
+ common_name="test.client.com",
206
+ organization="Test Organization",
207
+ country="US",
208
+ validity_days=365,
209
+ ca_cert_path=self.ca_cert_path,
210
+ ca_key_path=self.ca_key_path,
211
+ )
212
+
213
+ with patch(
214
+ "cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key"
215
+ ) as mock_rsa:
216
+ with patch("cryptography.x509.CertificateBuilder") as mock_builder_class:
217
+ with patch(
218
+ "cryptography.x509.load_pem_x509_certificate"
219
+ ) as mock_load_cert:
220
+ with patch(
221
+ "cryptography.hazmat.primitives.serialization.load_pem_private_key"
222
+ ) as mock_load_key:
223
+ with patch("builtins.open", create=True) as mock_open:
224
+ with patch("os.chmod") as mock_chmod:
225
+ # Mock file operations
226
+ mock_file = Mock()
227
+ mock_open.return_value.__enter__.return_value = (
228
+ mock_file
229
+ )
230
+
231
+ # Mock CA certificate and key
232
+ mock_ca_cert = Mock()
233
+ mock_ca_cert.subject = Mock()
234
+ mock_load_cert.return_value = mock_ca_cert
235
+
236
+ mock_ca_key = Mock()
237
+ mock_load_key.return_value = mock_ca_key
238
+
239
+ # Mock the certificate building process
240
+ mock_cert = Mock()
241
+ mock_cert.serial_number = 987654321
242
+ mock_cert.not_valid_before = datetime.now(timezone.utc)
243
+ mock_cert.not_valid_after = datetime.now(
244
+ timezone.utc
245
+ ) + timedelta(days=365)
246
+ mock_cert.public_bytes.return_value = b"-----BEGIN CERTIFICATE-----\nMOCK CLIENT CERT\n-----END CERTIFICATE-----"
247
+
248
+ mock_private_key = Mock()
249
+ mock_public_key = Mock()
250
+ mock_public_key.public_bytes.return_value = (
251
+ b"mock_public_key_data"
252
+ )
253
+ mock_private_key.public_key.return_value = (
254
+ mock_public_key
255
+ )
256
+ mock_private_key.private_bytes.return_value = b"-----BEGIN PRIVATE KEY-----\nMOCK CLIENT KEY\n-----END PRIVATE KEY-----"
257
+
258
+ # Mock the builder chain
259
+ mock_builder = Mock()
260
+ mock_builder.subject_name.return_value = mock_builder
261
+ mock_builder.issuer_name.return_value = mock_builder
262
+ mock_builder.public_key.return_value = mock_builder
263
+ mock_builder.serial_number.return_value = mock_builder
264
+ mock_builder.not_valid_before.return_value = (
265
+ mock_builder
266
+ )
267
+ mock_builder.not_valid_after.return_value = mock_builder
268
+ mock_builder.add_extension.return_value = mock_builder
269
+ mock_builder.sign.return_value = mock_cert
270
+
271
+ mock_builder_class.return_value = mock_builder
272
+ mock_rsa.return_value = mock_private_key
273
+
274
+ cert_pair = self.cert_manager.create_client_certificate(
275
+ client_config
276
+ )
277
+
278
+ assert isinstance(cert_pair, CertificatePair)
279
+ assert cert_pair.certificate_path.endswith("test.client.com_client.crt")
280
+ assert cert_pair.private_key_path.endswith("test.client.com_client.key")
281
+ assert cert_pair.serial_number == "987654321"
282
+
283
+ def test_create_client_certificate_missing_common_name(self):
284
+ """Test client certificate creation with missing common name."""
285
+ client_config = ClientCertConfig(
286
+ common_name="",
287
+ organization="Test Organization",
288
+ country="US",
289
+ validity_days=365,
290
+ ca_cert_path=self.ca_cert_path,
291
+ ca_key_path=self.ca_key_path,
292
+ )
293
+
294
+ with pytest.raises(CertificateGenerationError) as exc_info:
295
+ self.cert_manager.create_client_certificate(client_config)
296
+
297
+ assert "Common name is required for client certificate" in str(exc_info.value)
298
+
299
+ def test_create_server_certificate_success(self):
300
+ """Test successful server certificate creation."""
301
+ server_config = ServerCertConfig(
302
+ common_name="api.test.com",
303
+ organization="Test Organization",
304
+ country="US",
305
+ state="California",
306
+ locality="San Francisco",
307
+ validity_days=365,
308
+ key_size=2048,
309
+ subject_alt_names=["api.test.com", "www.test.com"],
310
+ ca_cert_path=self.ca_cert_path,
311
+ ca_key_path=self.ca_key_path,
312
+ )
313
+
314
+ with patch(
315
+ "cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key"
316
+ ) as mock_rsa:
317
+ with patch("cryptography.x509.CertificateBuilder") as mock_builder_class:
318
+ with patch(
319
+ "cryptography.x509.load_pem_x509_certificate"
320
+ ) as mock_load_cert:
321
+ with patch(
322
+ "cryptography.hazmat.primitives.serialization.load_pem_private_key"
323
+ ) as mock_load_key:
324
+ with patch("builtins.open", create=True) as mock_open:
325
+ with patch("os.chmod") as mock_chmod:
326
+ # Mock file operations
327
+ mock_file = Mock()
328
+ mock_open.return_value.__enter__.return_value = (
329
+ mock_file
330
+ )
331
+
332
+ # Mock CA certificate and key
333
+ mock_ca_cert = Mock()
334
+ mock_ca_cert.subject = Mock()
335
+ mock_load_cert.return_value = mock_ca_cert
336
+
337
+ mock_ca_key = Mock()
338
+ mock_load_key.return_value = mock_ca_key
339
+
340
+ # Mock the certificate building process
341
+ mock_cert = Mock()
342
+ mock_cert.serial_number = 555666777
343
+ mock_cert.not_valid_before = datetime.now(timezone.utc)
344
+ mock_cert.not_valid_after = datetime.now(
345
+ timezone.utc
346
+ ) + timedelta(days=365)
347
+ mock_cert.public_bytes.return_value = b"-----BEGIN CERTIFICATE-----\nMOCK SERVER CERT\n-----END CERTIFICATE-----"
348
+
349
+ mock_private_key = Mock()
350
+ mock_public_key = Mock()
351
+ mock_public_key.public_bytes.return_value = (
352
+ b"mock_public_key_data"
353
+ )
354
+ mock_private_key.public_key.return_value = (
355
+ mock_public_key
356
+ )
357
+ mock_private_key.private_bytes.return_value = b"-----BEGIN PRIVATE KEY-----\nMOCK SERVER KEY\n-----END PRIVATE KEY-----"
358
+
359
+ # Mock the builder chain
360
+ mock_builder = Mock()
361
+ mock_builder.subject_name.return_value = mock_builder
362
+ mock_builder.issuer_name.return_value = mock_builder
363
+ mock_builder.public_key.return_value = mock_builder
364
+ mock_builder.serial_number.return_value = mock_builder
365
+ mock_builder.not_valid_before.return_value = (
366
+ mock_builder
367
+ )
368
+ mock_builder.not_valid_after.return_value = mock_builder
369
+ mock_builder.add_extension.return_value = mock_builder
370
+ mock_builder.sign.return_value = mock_cert
371
+
372
+ mock_builder_class.return_value = mock_builder
373
+ mock_rsa.return_value = mock_private_key
374
+
375
+ cert_pair = self.cert_manager.create_server_certificate(
376
+ server_config
377
+ )
378
+
379
+ assert isinstance(cert_pair, CertificatePair)
380
+ assert cert_pair.certificate_path.endswith("api.test.com_server.crt")
381
+ assert cert_pair.private_key_path.endswith("api.test.com_server.key")
382
+ assert cert_pair.serial_number == "555666777"
383
+
384
+ def test_create_server_certificate_missing_common_name(self):
385
+ """Test server certificate creation with missing common name."""
386
+ server_config = ServerCertConfig(
387
+ common_name="",
388
+ organization="Test Organization",
389
+ country="US",
390
+ validity_days=365,
391
+ ca_cert_path=self.ca_cert_path,
392
+ ca_key_path=self.ca_key_path,
393
+ )
394
+
395
+ with pytest.raises(CertificateGenerationError) as exc_info:
396
+ self.cert_manager.create_server_certificate(server_config)
397
+
398
+ assert "Common name is required for server certificate" in str(exc_info.value)
399
+
400
+ def test_revoke_certificate_success(self):
401
+ """Test successful certificate revocation."""
402
+ serial_number = "123456789"
403
+ reason = "key_compromise"
404
+
405
+ with patch("builtins.open", create=True) as mock_open:
406
+ with patch(
407
+ "cryptography.x509.CertificateRevocationListBuilder"
408
+ ) as mock_crl_builder_class:
409
+ with patch(
410
+ "cryptography.x509.RevokedCertificateBuilder"
411
+ ) as mock_revoked_builder_class:
412
+ with patch("cryptography.x509.ReasonFlags") as mock_reason_flags:
413
+ with patch(
414
+ "cryptography.x509.load_pem_x509_certificate"
415
+ ) as mock_load_cert:
416
+ with patch(
417
+ "cryptography.hazmat.primitives.serialization.load_pem_private_key"
418
+ ) as mock_load_key:
419
+ with patch("os.chmod") as mock_chmod:
420
+ # Mock file operations
421
+ mock_file = Mock()
422
+ mock_open.return_value.__enter__.return_value = (
423
+ mock_file
424
+ )
425
+
426
+ # Mock CA certificate and key
427
+ mock_ca_cert = Mock()
428
+ mock_ca_cert.subject = Mock()
429
+ mock_load_cert.return_value = mock_ca_cert
430
+
431
+ mock_ca_key = Mock()
432
+ mock_load_key.return_value = mock_ca_key
433
+
434
+ # Mock CRL building process
435
+ mock_crl = Mock()
436
+ mock_crl.public_bytes.return_value = b"-----BEGIN X509 CRL-----\nMOCK CRL\n-----END X509 CRL-----"
437
+
438
+ # Mock the builder chain
439
+ mock_crl_builder = Mock()
440
+ mock_crl_builder.last_update.return_value = (
441
+ mock_crl_builder
442
+ )
443
+ mock_crl_builder.next_update.return_value = (
444
+ mock_crl_builder
445
+ )
446
+ mock_crl_builder.add_revoked_certificate.return_value = (
447
+ mock_crl_builder
448
+ )
449
+ mock_crl_builder.issuer_name.return_value = (
450
+ mock_crl_builder
451
+ )
452
+ mock_crl_builder.sign.return_value = mock_crl
453
+
454
+ # Mock revoked certificate builder
455
+ mock_revoked_cert = Mock()
456
+ mock_revoked_builder = Mock()
457
+ mock_revoked_builder.serial_number.return_value = (
458
+ mock_revoked_builder
459
+ )
460
+ mock_revoked_builder.revocation_date.return_value = (
461
+ mock_revoked_builder
462
+ )
463
+ mock_revoked_builder.revocation_reason.return_value = (
464
+ mock_revoked_builder
465
+ )
466
+ mock_revoked_builder.build.return_value = (
467
+ mock_revoked_cert
468
+ )
469
+
470
+ # Mock ReasonFlags
471
+ mock_reason_flags.__getitem__.return_value = (
472
+ "KEY_COMPROMISE"
473
+ )
474
+
475
+ mock_crl_builder_class.return_value = (
476
+ mock_crl_builder
477
+ )
478
+ mock_revoked_builder_class.return_value = (
479
+ mock_revoked_builder
480
+ )
481
+
482
+ success = self.cert_manager.revoke_certificate(
483
+ serial_number, reason
484
+ )
485
+
486
+ assert success is True
487
+
488
+ def test_revoke_certificate_missing_serial_number(self):
489
+ """Test certificate revocation with missing serial number."""
490
+ with pytest.raises(ValueError):
491
+ self.cert_manager.revoke_certificate("", "key_compromise")
492
+
493
+ def test_validate_certificate_chain_success(self):
494
+ """Test successful certificate chain validation."""
495
+ cert_path = "/path/to/cert.crt"
496
+
497
+ with patch(
498
+ "mcp_security_framework.core.cert_manager.validate_certificate_chain",
499
+ return_value=True,
500
+ ):
501
+ is_valid = self.cert_manager.validate_certificate_chain(cert_path)
502
+
503
+ assert is_valid is True
504
+
505
+ def test_validate_certificate_chain_failure(self):
506
+ """Test certificate chain validation failure."""
507
+ cert_path = "/path/to/cert.crt"
508
+
509
+ with patch(
510
+ "mcp_security_framework.core.cert_manager.validate_certificate_chain",
511
+ return_value=False,
512
+ ):
513
+ is_valid = self.cert_manager.validate_certificate_chain(cert_path)
514
+
515
+ assert is_valid is False
516
+
517
+ def test_validate_certificate_chain_with_custom_ca(self):
518
+ """Test certificate chain validation with custom CA certificate."""
519
+ cert_path = "/path/to/cert.crt"
520
+ ca_cert_path = "/path/to/custom_ca.crt"
521
+
522
+ with patch(
523
+ "mcp_security_framework.core.cert_manager.validate_certificate_chain",
524
+ return_value=True,
525
+ ):
526
+ is_valid = self.cert_manager.validate_certificate_chain(
527
+ cert_path, ca_cert_path
528
+ )
529
+
530
+ assert is_valid is True
531
+
532
+ def test_get_certificate_info_success(self):
533
+ """Test successful certificate information extraction."""
534
+ cert_path = "/path/to/cert.crt"
535
+
536
+ # Mock certificate data with proper structure
537
+ mock_cert = Mock()
538
+
539
+ # Mock subject and issuer as x509.Name objects
540
+ mock_subject = Mock()
541
+ mock_subject.get_attributes_for_oid.return_value = [
542
+ Mock(value="test.client.com")
543
+ ]
544
+ mock_cert.subject = mock_subject
545
+
546
+ mock_issuer = Mock()
547
+ mock_issuer.get_attributes_for_oid.return_value = [Mock(value="Test Root CA")]
548
+ mock_cert.issuer = mock_issuer
549
+
550
+ mock_cert.serial_number = 123456789
551
+ mock_cert.version.name = "v3"
552
+ mock_cert.not_valid_before = datetime.now(timezone.utc)
553
+ mock_cert.not_valid_after = datetime.now(timezone.utc) + timedelta(days=365)
554
+
555
+ # Mock signature algorithm
556
+ mock_sig_alg = Mock()
557
+ mock_sig_alg._name = "sha256WithRSAEncryption"
558
+ mock_cert.signature_algorithm_oid = mock_sig_alg
559
+
560
+ # Mock public key algorithm
561
+ mock_pub_alg = Mock()
562
+ mock_pub_alg._name = "rsaEncryption"
563
+ mock_cert.public_key_algorithm_oid = mock_pub_alg
564
+
565
+ # Mock fingerprint methods
566
+ mock_cert.fingerprint.side_effect = lambda hash_alg: b"mock_fingerprint"
567
+
568
+ # Mock extensions
569
+ mock_extension = Mock()
570
+ mock_extension.value.ca = False
571
+ mock_cert.extensions.get_extension_for_oid.return_value = mock_extension
572
+
573
+ # Mock all utility functions
574
+ with patch(
575
+ "mcp_security_framework.core.cert_manager.parse_certificate",
576
+ return_value=mock_cert,
577
+ ):
578
+ with patch(
579
+ "mcp_security_framework.core.cert_manager.extract_roles_from_certificate",
580
+ return_value=["user"],
581
+ ):
582
+ with patch(
583
+ "mcp_security_framework.core.cert_manager.extract_permissions_from_certificate",
584
+ return_value=["read:users"],
585
+ ):
586
+ with patch(
587
+ "mcp_security_framework.core.cert_manager.get_certificate_expiry",
588
+ return_value={"key_size": 2048},
589
+ ):
590
+ with patch(
591
+ "mcp_security_framework.core.cert_manager.get_certificate_serial_number",
592
+ return_value="123456789",
593
+ ):
594
+ with patch(
595
+ "mcp_security_framework.core.cert_manager.is_certificate_self_signed",
596
+ return_value=False,
597
+ ):
598
+ cert_info = self.cert_manager.get_certificate_info(
599
+ cert_path
600
+ )
601
+
602
+ assert isinstance(cert_info, CertificateInfo)
603
+ assert cert_info.subject == {"CN": "test.client.com", "C": "test.client.com", "O": "test.client.com"}
604
+ assert cert_info.issuer == {"CN": "Test Root CA"}
605
+ assert cert_info.serial_number == "123456789"
606
+ assert cert_info.roles == ["user"]
607
+ assert cert_info.permissions == ["read:users"]
608
+ assert cert_info.certificate_path == cert_path
609
+
610
+ def test_get_certificate_info_cached(self):
611
+ """Test certificate information retrieval from cache."""
612
+ cert_path = "/path/to/cert.crt"
613
+
614
+ # Create cached certificate info
615
+ cached_info = CertificateInfo(
616
+ subject={"CN": "cached.client.com"},
617
+ issuer={"CN": "Test Root CA"},
618
+ serial_number="987654321",
619
+ not_before=datetime.now(timezone.utc),
620
+ not_after=datetime.now(timezone.utc) + timedelta(days=365),
621
+ certificate_type=CertificateType.CLIENT,
622
+ key_size=2048,
623
+ signature_algorithm="sha256WithRSAEncryption",
624
+ fingerprint_sha1="mock_sha1",
625
+ fingerprint_sha256="mock_sha256",
626
+ is_ca=False,
627
+ roles=["user"],
628
+ permissions=["read:users"],
629
+ certificate_path=cert_path,
630
+ )
631
+
632
+ self.cert_manager._certificate_cache[cert_path] = cached_info
633
+
634
+ cert_info = self.cert_manager.get_certificate_info(cert_path)
635
+
636
+ assert cert_info == cached_info
637
+
638
+ def test_get_certificate_info_parsing_error(self):
639
+ """Test certificate information extraction with parsing error."""
640
+ cert_path = "/path/to/invalid_cert.crt"
641
+
642
+ with patch(
643
+ "mcp_security_framework.utils.cert_utils.parse_certificate",
644
+ side_effect=Exception("Parsing failed"),
645
+ ):
646
+ with pytest.raises(CertificateValidationError) as exc_info:
647
+ self.cert_manager.get_certificate_info(cert_path)
648
+
649
+ assert "Failed to get certificate info" in str(exc_info.value)
650
+
651
+ def test_validate_configuration_missing_ca_cert_path(self):
652
+ """Test configuration validation with missing CA certificate path."""
653
+ with pytest.raises(ValidationError) as exc_info:
654
+ CertificateConfig(enabled=True, ca_cert_path="", ca_key_path=self.ca_key_path)
655
+
656
+ assert "CA certificate and key paths are required" in str(exc_info.value)
657
+
658
+ def test_validate_configuration_missing_ca_key_path(self):
659
+ """Test configuration validation with missing CA key path."""
660
+ with pytest.raises(ValidationError) as exc_info:
661
+ CertificateConfig(enabled=True, ca_cert_path=self.ca_cert_path, ca_key_path="")
662
+
663
+ assert "CA certificate and key paths are required" in str(exc_info.value)
664
+
665
+ def test_create_output_directory(self):
666
+ """Test automatic output directory creation."""
667
+ # Create new temp directory
668
+ new_temp_dir = os.path.join(self.temp_dir, "new_output")
669
+
670
+ config = CertificateConfig(
671
+ enabled=True,
672
+ ca_cert_path=self.ca_cert_path,
673
+ ca_key_path=self.ca_key_path,
674
+ cert_storage_path=new_temp_dir,
675
+ key_storage_path=new_temp_dir,
676
+ )
677
+
678
+ cert_manager = CertificateManager(config)
679
+
680
+ assert os.path.exists(new_temp_dir)
681
+ assert cert_manager.config.cert_storage_path == new_temp_dir
682
+
683
+ def test_ec_key_generation(self):
684
+ """Test EC key generation for certificates."""
685
+ ca_config = CAConfig(
686
+ common_name="Test EC CA",
687
+ organization="Test Organization",
688
+ country="US",
689
+ validity_years=10,
690
+ )
691
+
692
+ with patch(
693
+ "cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key"
694
+ ) as mock_rsa:
695
+ with patch("cryptography.x509.CertificateBuilder") as mock_builder_class:
696
+ with patch("builtins.open", create=True) as mock_open:
697
+ with patch("os.chmod") as mock_chmod:
698
+ # Mock the certificate building process
699
+ mock_cert = Mock()
700
+ mock_cert.serial_number = 111222333
701
+ mock_cert.not_valid_before = datetime.now(timezone.utc)
702
+ mock_cert.not_valid_after = datetime.now(
703
+ timezone.utc
704
+ ) + timedelta(days=3650)
705
+ mock_cert.public_bytes.return_value = b"-----BEGIN CERTIFICATE-----\nMOCK EC CERT\n-----END CERTIFICATE-----"
706
+
707
+ mock_private_key = Mock()
708
+ mock_public_key = Mock()
709
+ mock_public_key.public_bytes.return_value = (
710
+ b"mock_public_key_data"
711
+ )
712
+ mock_private_key.public_key.return_value = mock_public_key
713
+ mock_private_key.private_bytes.return_value = b"-----BEGIN PRIVATE KEY-----\nMOCK EC KEY\n-----END PRIVATE KEY-----"
714
+
715
+ # Mock the builder chain
716
+ mock_builder = Mock()
717
+ mock_builder.subject_name.return_value = mock_builder
718
+ mock_builder.issuer_name.return_value = mock_builder
719
+ mock_builder.public_key.return_value = mock_builder
720
+ mock_builder.serial_number.return_value = mock_builder
721
+ mock_builder.not_valid_before.return_value = mock_builder
722
+ mock_builder.not_valid_after.return_value = mock_builder
723
+ mock_builder.add_extension.return_value = mock_builder
724
+ mock_builder.sign.return_value = mock_cert
725
+
726
+ mock_builder_class.return_value = mock_builder
727
+ mock_rsa.return_value = mock_private_key
728
+
729
+ # Mock file operations
730
+ mock_file = Mock()
731
+ mock_open.return_value.__enter__.return_value = mock_file
732
+
733
+ cert_pair = self.cert_manager.create_root_ca(ca_config)
734
+
735
+ assert isinstance(cert_pair, CertificatePair)
736
+ assert cert_pair.serial_number == "111222333"
737
+ assert cert_pair.common_name == "Test EC CA"
738
+ assert cert_pair.organization == "Test Organization"
739
+
740
+ def test_certificate_permissions(self):
741
+ """Test that generated certificate files have correct permissions."""
742
+ ca_config = CAConfig(
743
+ common_name="Test Permissions CA",
744
+ organization="Test Organization",
745
+ country="US",
746
+ validity_years=10,
747
+ )
748
+
749
+ with patch(
750
+ "cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key"
751
+ ) as mock_rsa:
752
+ with patch("cryptography.x509.CertificateBuilder") as mock_builder_class:
753
+ with patch("builtins.open", create=True) as mock_open:
754
+ with patch("os.chmod") as mock_chmod:
755
+ # Mock the certificate building process
756
+ mock_cert = Mock()
757
+ mock_cert.serial_number = 444555666
758
+ mock_cert.not_valid_before = datetime.now(timezone.utc)
759
+ mock_cert.not_valid_after = datetime.now(
760
+ timezone.utc
761
+ ) + timedelta(days=3650)
762
+ mock_cert.public_bytes.return_value = b"-----BEGIN CERTIFICATE-----\nMOCK CERT\n-----END CERTIFICATE-----"
763
+
764
+ mock_private_key = Mock()
765
+ mock_public_key = Mock()
766
+ mock_public_key.public_bytes.return_value = (
767
+ b"mock_public_key_data"
768
+ )
769
+ mock_private_key.public_key.return_value = mock_public_key
770
+ mock_private_key.private_bytes.return_value = b"-----BEGIN PRIVATE KEY-----\nMOCK KEY\n-----END PRIVATE KEY-----"
771
+
772
+ # Mock the builder chain
773
+ mock_builder = Mock()
774
+ mock_builder.subject_name.return_value = mock_builder
775
+ mock_builder.issuer_name.return_value = mock_builder
776
+ mock_builder.public_key.return_value = mock_builder
777
+ mock_builder.serial_number.return_value = mock_builder
778
+ mock_builder.not_valid_before.return_value = mock_builder
779
+ mock_builder.not_valid_after.return_value = mock_builder
780
+ mock_builder.add_extension.return_value = mock_builder
781
+ mock_builder.sign.return_value = mock_cert
782
+
783
+ mock_builder_class.return_value = mock_builder
784
+ mock_rsa.return_value = mock_private_key
785
+
786
+ # Mock file operations
787
+ mock_file = Mock()
788
+ mock_open.return_value.__enter__.return_value = mock_file
789
+
790
+ cert_pair = self.cert_manager.create_root_ca(ca_config)
791
+
792
+ # Verify that chmod was called with correct permissions
793
+ assert mock_chmod.call_count >= 2 # At least for cert and key files
794
+ # Note: In a real test, we would check actual file permissions
795
+ # but since we're using mocks, we just verify the chmod calls were made