mcp-security-framework 0.1.0__tar.gz → 1.1.0__tar.gz

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 (91) hide show
  1. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/PKG-INFO +1 -1
  2. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/core/auth_manager.py +12 -2
  3. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/core/cert_manager.py +247 -16
  4. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/core/permission_manager.py +4 -0
  5. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/core/rate_limiter.py +10 -0
  6. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/core/security_manager.py +2 -0
  7. mcp_security_framework-1.1.0/mcp_security_framework/examples/comprehensive_example.py +884 -0
  8. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/examples/django_example.py +45 -12
  9. mcp_security_framework-1.1.0/mcp_security_framework/examples/fastapi_example.py +944 -0
  10. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/examples/flask_example.py +51 -11
  11. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/examples/gateway_example.py +109 -17
  12. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/examples/microservice_example.py +112 -16
  13. mcp_security_framework-1.1.0/mcp_security_framework/examples/standalone_example.py +792 -0
  14. mcp_security_framework-1.1.0/mcp_security_framework/examples/test_all_examples.py +556 -0
  15. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/middleware/auth_middleware.py +1 -1
  16. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/middleware/fastapi_auth_middleware.py +82 -14
  17. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/middleware/flask_auth_middleware.py +154 -7
  18. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/schemas/models.py +1 -0
  19. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/utils/cert_utils.py +5 -5
  20. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework.egg-info/PKG-INFO +1 -1
  21. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework.egg-info/SOURCES.txt +6 -0
  22. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework.egg-info/top_level.txt +2 -0
  23. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/pyproject.toml +1 -1
  24. mcp_security_framework-1.1.0/tests/conftest.py +306 -0
  25. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_cli/test_cert_cli.py +13 -31
  26. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_core/test_cert_manager.py +12 -12
  27. mcp_security_framework-1.1.0/tests/test_examples/test_comprehensive_example.py +560 -0
  28. mcp_security_framework-1.1.0/tests/test_examples/test_fastapi_example.py +362 -0
  29. mcp_security_framework-1.1.0/tests/test_examples/test_flask_example.py +357 -0
  30. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_examples/test_standalone_example.py +44 -99
  31. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_integration/test_auth_flow.py +4 -4
  32. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_integration/test_certificate_flow.py +1 -1
  33. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_integration/test_fastapi_integration.py +39 -45
  34. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_integration/test_flask_integration.py +4 -2
  35. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_integration/test_standalone_integration.py +48 -48
  36. mcp_security_framework-1.1.0/tests/test_middleware/test_fastapi_auth_middleware.py +724 -0
  37. mcp_security_framework-1.1.0/tests/test_middleware/test_flask_auth_middleware.py +638 -0
  38. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_middleware/test_security_middleware.py +9 -3
  39. mcp_security_framework-0.1.0/mcp_security_framework/examples/fastapi_example.py +0 -472
  40. mcp_security_framework-0.1.0/mcp_security_framework/examples/standalone_example.py +0 -576
  41. mcp_security_framework-0.1.0/tests/test_examples/test_fastapi_example.py +0 -264
  42. mcp_security_framework-0.1.0/tests/test_examples/test_flask_example.py +0 -238
  43. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/README.md +0 -0
  44. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/__init__.py +0 -0
  45. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/cli/__init__.py +0 -0
  46. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/cli/cert_cli.py +0 -0
  47. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/cli/security_cli.py +0 -0
  48. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/constants.py +0 -0
  49. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/core/__init__.py +0 -0
  50. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/core/ssl_manager.py +0 -0
  51. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/examples/__init__.py +0 -0
  52. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/middleware/__init__.py +0 -0
  53. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/middleware/fastapi_middleware.py +0 -0
  54. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/middleware/flask_middleware.py +0 -0
  55. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/middleware/mtls_middleware.py +0 -0
  56. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/middleware/rate_limit_middleware.py +0 -0
  57. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/middleware/security_middleware.py +0 -0
  58. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/schemas/__init__.py +0 -0
  59. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/schemas/config.py +0 -0
  60. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/schemas/responses.py +0 -0
  61. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/tests/__init__.py +0 -0
  62. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/utils/__init__.py +0 -0
  63. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/utils/crypto_utils.py +0 -0
  64. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework/utils/validation_utils.py +0 -0
  65. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework.egg-info/dependency_links.txt +0 -0
  66. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework.egg-info/entry_points.txt +0 -0
  67. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/mcp_security_framework.egg-info/requires.txt +0 -0
  68. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/setup.cfg +0 -0
  69. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/__init__.py +0 -0
  70. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_cli/__init__.py +0 -0
  71. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_cli/test_security_cli.py +0 -0
  72. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_core/__init__.py +0 -0
  73. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_core/test_auth_manager.py +0 -0
  74. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_core/test_permission_manager.py +0 -0
  75. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_core/test_rate_limiter.py +0 -0
  76. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_core/test_security_manager.py +0 -0
  77. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_core/test_ssl_manager.py +0 -0
  78. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_examples/__init__.py +0 -0
  79. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_integration/__init__.py +0 -0
  80. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_middleware/__init__.py +0 -0
  81. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_middleware/test_fastapi_middleware.py +0 -0
  82. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_middleware/test_flask_middleware.py +0 -0
  83. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_schemas/__init__.py +0 -0
  84. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_schemas/test_config.py +0 -0
  85. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_schemas/test_models.py +0 -0
  86. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_schemas/test_responses.py +0 -0
  87. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_schemas/test_serialization.py +0 -0
  88. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_utils/__init__.py +0 -0
  89. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_utils/test_cert_utils.py +0 -0
  90. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_utils/test_crypto_utils.py +0 -0
  91. {mcp_security_framework-0.1.0 → mcp_security_framework-1.1.0}/tests/test_utils/test_validation_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-security-framework
3
- Version: 0.1.0
3
+ Version: 1.1.0
4
4
  Summary: Universal security framework for microservices with SSL/TLS, authentication, authorization, and rate limiting
5
5
  Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
6
6
  Maintainer-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
@@ -159,6 +159,16 @@ class AuthManager:
159
159
  "jwt_expiry_hours": config.jwt_expiry_hours,
160
160
  },
161
161
  )
162
+
163
+ @property
164
+ def is_auth_enabled(self) -> bool:
165
+ """
166
+ Check if authentication is enabled.
167
+
168
+ Returns:
169
+ bool: True if authentication is enabled, False otherwise
170
+ """
171
+ return self.config.enabled
162
172
 
163
173
  def authenticate_api_key(self, api_key: str) -> AuthResult:
164
174
  """
@@ -568,7 +578,7 @@ class AuthManager:
568
578
 
569
579
  # Check certificate expiration
570
580
  now = datetime.now(timezone.utc)
571
- if cert.not_valid_after.replace(tzinfo=timezone.utc) < now:
581
+ if cert.not_valid_after_utc.replace(tzinfo=timezone.utc) < now:
572
582
  return AuthResult(
573
583
  is_valid=False,
574
584
  status=AuthStatus.EXPIRED,
@@ -626,7 +636,7 @@ class AuthManager:
626
636
  roles=roles,
627
637
  auth_method="certificate",
628
638
  auth_timestamp=datetime.now(timezone.utc),
629
- token_expiry=cert.not_valid_after.replace(tzinfo=timezone.utc),
639
+ token_expiry=cert.not_valid_after_utc.replace(tzinfo=timezone.utc),
630
640
  )
631
641
 
632
642
  self.logger.info(
@@ -313,8 +313,8 @@ class CertificateManager:
313
313
  serial_number=str(certificate.serial_number),
314
314
  common_name=ca_config.common_name,
315
315
  organization=ca_config.organization,
316
- not_before=certificate.not_valid_before,
317
- not_after=certificate.not_valid_after.replace(tzinfo=timezone.utc),
316
+ not_before=certificate.not_valid_before_utc,
317
+ not_after=certificate.not_valid_after_utc.replace(tzinfo=timezone.utc),
318
318
  certificate_type=CertificateType.ROOT_CA,
319
319
  key_size=ca_config.key_size,
320
320
  )
@@ -534,8 +534,8 @@ class CertificateManager:
534
534
  encryption_algorithm=serialization.NoEncryption(),
535
535
  ).decode(),
536
536
  serial_number=str(certificate.serial_number),
537
- not_before=certificate.not_valid_before.replace(tzinfo=timezone.utc),
538
- not_after=certificate.not_valid_after.replace(tzinfo=timezone.utc),
537
+ not_before=certificate.not_valid_before_utc.replace(tzinfo=timezone.utc),
538
+ not_after=certificate.not_valid_after_utc.replace(tzinfo=timezone.utc),
539
539
  common_name=intermediate_config.common_name,
540
540
  organization=intermediate_config.organization,
541
541
  certificate_type=CertificateType.INTERMEDIATE_CA,
@@ -775,8 +775,8 @@ class CertificateManager:
775
775
  serial_number=str(certificate.serial_number),
776
776
  common_name=client_config.common_name,
777
777
  organization=client_config.organization,
778
- not_before=certificate.not_valid_before,
779
- not_after=certificate.not_valid_after.replace(tzinfo=timezone.utc),
778
+ not_before=certificate.not_valid_before_utc,
779
+ not_after=certificate.not_valid_after_utc.replace(tzinfo=timezone.utc),
780
780
  certificate_type=CertificateType.CLIENT,
781
781
  key_size=client_config.key_size,
782
782
  )
@@ -1009,8 +1009,8 @@ class CertificateManager:
1009
1009
  serial_number=str(certificate.serial_number),
1010
1010
  common_name=server_config.common_name,
1011
1011
  organization=server_config.organization,
1012
- not_before=certificate.not_valid_before,
1013
- not_after=certificate.not_valid_after.replace(tzinfo=timezone.utc),
1012
+ not_before=certificate.not_valid_before_utc,
1013
+ not_after=certificate.not_valid_after_utc.replace(tzinfo=timezone.utc),
1014
1014
  certificate_type=CertificateType.SERVER,
1015
1015
  key_size=server_config.key_size,
1016
1016
  )
@@ -1144,8 +1144,8 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
1144
1144
  serial_number=str(new_cert.serial_number),
1145
1145
  common_name="", # Will be extracted from subject
1146
1146
  organization="", # Will be extracted from subject
1147
- not_before=new_cert.not_valid_before,
1148
- not_after=new_cert.not_valid_after.replace(tzinfo=timezone.utc),
1147
+ not_before=new_cert.not_valid_before_utc,
1148
+ not_after=new_cert.not_valid_after_utc.replace(tzinfo=timezone.utc),
1149
1149
  certificate_type=CertificateType.CLIENT, # Default
1150
1150
  key_size=0, # Will be extracted
1151
1151
  )
@@ -1411,8 +1411,8 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
1411
1411
  )
1412
1412
  },
1413
1413
  serial_number=serial_number,
1414
- not_before=cert.not_valid_before,
1415
- not_after=cert.not_valid_after,
1414
+ not_before=cert.not_valid_before_utc,
1415
+ not_after=cert.not_valid_after_utc,
1416
1416
  certificate_type=CertificateType.CLIENT, # Default to client, could be enhanced
1417
1417
  key_size=expiry_info.get("key_size", 2048), # Default to 2048 bits
1418
1418
  signature_algorithm=cert.signature_algorithm_oid._name,
@@ -1446,18 +1446,199 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
1446
1446
  f"Failed to get certificate info: {str(e)}"
1447
1447
  )
1448
1448
 
1449
+ def create_certificate_signing_request(self,
1450
+ common_name: str,
1451
+ organization: str,
1452
+ country: str,
1453
+ state: Optional[str] = None,
1454
+ locality: Optional[str] = None,
1455
+ organizational_unit: Optional[str] = None,
1456
+ email: Optional[str] = None,
1457
+ key_size: int = 2048,
1458
+ key_type: str = "rsa",
1459
+ output_path: Optional[str] = None) -> Tuple[str, str]:
1460
+ """
1461
+ Create a Certificate Signing Request (CSR).
1462
+
1463
+ This method creates a Certificate Signing Request (CSR) that can be
1464
+ submitted to a Certificate Authority (CA) for signing.
1465
+
1466
+ Args:
1467
+ common_name (str): Common name for the certificate (e.g., domain name)
1468
+ organization (str): Organization name
1469
+ country (str): Country code (e.g., "US", "GB")
1470
+ state (Optional[str]): State or province name
1471
+ locality (Optional[str]): Locality or city name
1472
+ organizational_unit (Optional[str]): Organizational unit name
1473
+ email (Optional[str]): Email address
1474
+ key_size (int): Key size in bits. Defaults to 2048
1475
+ key_type (str): Key type ("rsa" or "ec"). Defaults to "rsa"
1476
+ output_path (Optional[str]): Output directory for CSR and key files.
1477
+ If None, uses default path from configuration
1478
+
1479
+ Returns:
1480
+ Tuple[str, str]: Paths to the created CSR file and private key file
1481
+
1482
+ Raises:
1483
+ ValueError: When required parameters are invalid
1484
+ CertificateGenerationError: When CSR creation fails
1485
+ FileNotFoundError: When output directory is not accessible
1486
+ PermissionError: When output directory is not writable
1487
+
1488
+ Example:
1489
+ >>> cert_manager = CertificateManager(config)
1490
+ >>> csr_path, key_path = cert_manager.create_certificate_signing_request(
1491
+ ... common_name="api.example.com",
1492
+ ... organization="Example Corp",
1493
+ ... country="US",
1494
+ ... state="California"
1495
+ ... )
1496
+ >>> print(f"CSR created: {csr_path}")
1497
+ >>> print(f"Private key created: {key_path}")
1498
+ """
1499
+ try:
1500
+ # Validate required parameters
1501
+ if not common_name:
1502
+ raise ValueError("Common name is required")
1503
+ if not organization:
1504
+ raise ValueError("Organization is required")
1505
+ if not country:
1506
+ raise ValueError("Country is required")
1507
+
1508
+ # Generate private key
1509
+ if key_type.lower() == "rsa":
1510
+ private_key = rsa.generate_private_key(
1511
+ public_exponent=65537, key_size=key_size, backend=None
1512
+ )
1513
+ elif key_type.lower() == "ec":
1514
+ private_key = ec.generate_private_key(
1515
+ ec.SECP256R1(), backend=None
1516
+ )
1517
+ else:
1518
+ raise ValueError(f"Unsupported key type: {key_type}")
1519
+
1520
+ # Create CSR subject
1521
+ subject_attributes = [
1522
+ x509.NameAttribute(NameOID.COMMON_NAME, common_name),
1523
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization),
1524
+ x509.NameAttribute(NameOID.COUNTRY_NAME, country),
1525
+ ]
1526
+
1527
+ # Add optional attributes
1528
+ if state:
1529
+ subject_attributes.append(
1530
+ x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, state)
1531
+ )
1532
+ if locality:
1533
+ subject_attributes.append(
1534
+ x509.NameAttribute(NameOID.LOCALITY_NAME, locality)
1535
+ )
1536
+ if organizational_unit:
1537
+ subject_attributes.append(
1538
+ x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, organizational_unit)
1539
+ )
1540
+ if email:
1541
+ subject_attributes.append(
1542
+ x509.NameAttribute(NameOID.EMAIL_ADDRESS, email)
1543
+ )
1544
+
1545
+ subject = x509.Name(subject_attributes)
1546
+
1547
+ # Create CSR builder
1548
+ csr_builder = x509.CertificateSigningRequestBuilder()
1549
+ csr_builder = csr_builder.subject_name(subject)
1550
+ csr_builder = csr_builder.add_extension(
1551
+ x509.BasicConstraints(ca=False, path_length=None),
1552
+ critical=True
1553
+ )
1554
+
1555
+ # Add Subject Alternative Name extension if common name looks like a domain
1556
+ if "." in common_name and not common_name.startswith("*"):
1557
+ san_extension = x509.SubjectAlternativeName([
1558
+ x509.DNSName(common_name)
1559
+ ])
1560
+ csr_builder = csr_builder.add_extension(san_extension, critical=False)
1561
+
1562
+ # Build and sign CSR
1563
+ csr = csr_builder.sign(private_key, hashes.SHA256())
1564
+
1565
+ # Determine output paths
1566
+ if output_path is None:
1567
+ output_dir = Path(self.config.cert_storage_path) / "csr"
1568
+ output_dir.mkdir(parents=True, exist_ok=True)
1569
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
1570
+ csr_filename = f"{common_name.replace('.', '_')}_{timestamp}.csr"
1571
+ key_filename = f"{common_name.replace('.', '_')}_{timestamp}.key"
1572
+ csr_path = str(output_dir / csr_filename)
1573
+ key_path = str(output_dir / key_filename)
1574
+ else:
1575
+ output_dir = Path(output_path)
1576
+ output_dir.mkdir(parents=True, exist_ok=True)
1577
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
1578
+ csr_filename = f"{common_name.replace('.', '_')}_{timestamp}.csr"
1579
+ key_filename = f"{common_name.replace('.', '_')}_{timestamp}.key"
1580
+ csr_path = str(output_dir / csr_filename)
1581
+ key_path = str(output_dir / key_filename)
1582
+
1583
+ # Write CSR to file
1584
+ with open(csr_path, 'wb') as f:
1585
+ f.write(csr.public_bytes(serialization.Encoding.PEM))
1586
+
1587
+ # Write private key to file
1588
+ with open(key_path, 'wb') as f:
1589
+ f.write(private_key.private_bytes(
1590
+ encoding=serialization.Encoding.PEM,
1591
+ format=serialization.PrivateFormat.PKCS8,
1592
+ encryption_algorithm=serialization.NoEncryption()
1593
+ ))
1594
+
1595
+ # Set proper permissions
1596
+ os.chmod(key_path, 0o600) # Read/write for owner only
1597
+ os.chmod(csr_path, 0o644) # Read for all, write for owner
1598
+
1599
+ self.logger.info(
1600
+ "CSR created successfully",
1601
+ extra={
1602
+ "csr_path": csr_path,
1603
+ "key_path": key_path,
1604
+ "common_name": common_name,
1605
+ "organization": organization,
1606
+ "key_type": key_type,
1607
+ "key_size": key_size
1608
+ }
1609
+ )
1610
+
1611
+ return csr_path, key_path
1612
+
1613
+ except Exception as e:
1614
+ self.logger.error(
1615
+ "Failed to create CSR",
1616
+ extra={
1617
+ "common_name": common_name,
1618
+ "organization": organization,
1619
+ "error": str(e)
1620
+ }
1621
+ )
1622
+ raise CertificateGenerationError(f"Failed to create CSR: {str(e)}")
1623
+
1449
1624
  def create_crl(self, ca_cert_path: str, ca_key_path: str,
1625
+ revoked_serials: Optional[List[Dict[str, Union[str, int]]]] = None,
1450
1626
  output_path: Optional[str] = None, validity_days: int = 30) -> str:
1451
1627
  """
1452
1628
  Create a Certificate Revocation List (CRL).
1453
1629
 
1454
1630
  This method creates a Certificate Revocation List (CRL) from the CA
1455
1631
  certificate and private key. The CRL contains information about
1456
- revoked certificates.
1632
+ revoked certificates with revocation reasons.
1457
1633
 
1458
1634
  Args:
1459
1635
  ca_cert_path (str): Path to CA certificate file
1460
1636
  ca_key_path (str): Path to CA private key file
1637
+ revoked_serials (Optional[List[Dict]]): List of revoked certificate serials.
1638
+ Each dict should contain:
1639
+ - "serial": Serial number as string or int
1640
+ - "reason": Revocation reason (optional)
1641
+ - "revocation_date": Revocation date (optional, defaults to now)
1461
1642
  output_path (Optional[str]): Output path for CRL file.
1462
1643
  If None, uses default path from configuration
1463
1644
  validity_days (int): CRL validity period in days. Defaults to 30
@@ -1471,9 +1652,14 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
1471
1652
 
1472
1653
  Example:
1473
1654
  >>> cert_manager = CertificateManager(config)
1655
+ >>> revoked_serials = [
1656
+ ... {"serial": "123456789", "reason": "key_compromise"},
1657
+ ... {"serial": "987654321", "reason": "certificate_hold"}
1658
+ ... ]
1474
1659
  >>> crl_path = cert_manager.create_crl(
1475
1660
  ... ca_cert_path="/path/to/ca.crt",
1476
1661
  ... ca_key_path="/path/to/ca.key",
1662
+ ... revoked_serials=revoked_serials,
1477
1663
  ... validity_days=30
1478
1664
  ... )
1479
1665
  >>> print(f"CRL created: {crl_path}")
@@ -1508,10 +1694,31 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
1508
1694
  crl_builder = crl_builder.next_update(next_update)
1509
1695
  crl_builder = crl_builder.issuer_name(ca_cert.subject)
1510
1696
 
1511
- # Add revoked certificates (empty for now, can be enhanced)
1512
- # This is a basic implementation - in a real scenario, you would
1513
- # load revoked certificates from a database or file
1697
+ # Add revoked certificates
1514
1698
  revoked_certificates = []
1699
+ if revoked_serials:
1700
+ for revoked_info in revoked_serials:
1701
+ serial = revoked_info.get("serial")
1702
+ reason = revoked_info.get("reason", "unspecified")
1703
+ revocation_date = revoked_info.get("revocation_date", now)
1704
+
1705
+ # Convert serial to int if it's a string
1706
+ if isinstance(serial, str):
1707
+ serial = int(serial, 16) if serial.startswith("0x") else int(serial)
1708
+
1709
+ # Map reason string to x509 enum
1710
+ reason_enum = self._get_revocation_reason(reason)
1711
+
1712
+ # Create revoked certificate entry
1713
+ revoked_cert = x509.RevokedCertificateBuilder().serial_number(
1714
+ serial
1715
+ ).revocation_date(
1716
+ revocation_date
1717
+ ).add_extension(
1718
+ x509.CRLReason(reason_enum), critical=False
1719
+ ).build()
1720
+
1721
+ revoked_certificates.append(revoked_cert)
1515
1722
 
1516
1723
  # Build CRL
1517
1724
  crl = crl_builder.sign(ca_private_key, hashes.SHA256())
@@ -1551,6 +1758,30 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
1551
1758
  )
1552
1759
  raise CertificateGenerationError(f"Failed to create CRL: {str(e)}")
1553
1760
 
1761
+ def _get_revocation_reason(self, reason: str) -> x509.ReasonFlags:
1762
+ """
1763
+ Map reason string to x509.ReasonFlags enum.
1764
+
1765
+ Args:
1766
+ reason (str): Reason string
1767
+
1768
+ Returns:
1769
+ x509.ReasonFlags: Corresponding reason enum
1770
+ """
1771
+ reason_map = {
1772
+ "unspecified": x509.ReasonFlags.unspecified,
1773
+ "key_compromise": x509.ReasonFlags.key_compromise,
1774
+ "ca_compromise": x509.ReasonFlags.ca_compromise,
1775
+ "affiliation_changed": x509.ReasonFlags.affiliation_changed,
1776
+ "superseded": x509.ReasonFlags.superseded,
1777
+ "cessation_of_operation": x509.ReasonFlags.cessation_of_operation,
1778
+ "certificate_hold": x509.ReasonFlags.certificate_hold,
1779
+ "privilege_withdrawn": x509.ReasonFlags.privilege_withdrawn,
1780
+ "aa_compromise": x509.ReasonFlags.aa_compromise,
1781
+ }
1782
+
1783
+ return reason_map.get(reason.lower(), x509.ReasonFlags.unspecified)
1784
+
1554
1785
  def export_certificate(self, cert_path: str, format: str = "pem") -> Union[str, bytes]:
1555
1786
  """
1556
1787
  Export certificate to different formats.
@@ -660,6 +660,10 @@ class PermissionManager:
660
660
  if required_perm in available_permissions:
661
661
  return True
662
662
 
663
+ # Check for universal permission "*" (all permissions)
664
+ if "*" in available_permissions:
665
+ return True
666
+
663
667
  # Wildcard match
664
668
  if "*" in required_perm:
665
669
  perm_parts = required_perm.split(":")
@@ -182,6 +182,16 @@ class RateLimiter:
182
182
  "storage_backend": config.storage_backend,
183
183
  },
184
184
  )
185
+
186
+ @property
187
+ def is_rate_limiting_enabled(self) -> bool:
188
+ """
189
+ Check if rate limiting is enabled.
190
+
191
+ Returns:
192
+ bool: True if rate limiting is enabled, False otherwise
193
+ """
194
+ return self.config.enabled
185
195
 
186
196
  def check_rate_limit(self, identifier: str, limit: Optional[int] = None) -> bool:
187
197
  """
@@ -25,6 +25,7 @@ License: MIT
25
25
 
26
26
  import logging
27
27
  from typing import Dict, List, Optional, Union, Any
28
+ from datetime import datetime, timezone
28
29
 
29
30
  from ..schemas.config import SecurityConfig, AuthConfig, PermissionConfig, CertificateConfig
30
31
  from ..schemas.models import AuthResult, ValidationResult, ValidationStatus, CertificatePair, CertificateInfo, AuthStatus
@@ -129,6 +130,7 @@ class SecurityManager:
129
130
  self.logger = logging.getLogger(__name__)
130
131
  self._component_status: Dict[str, bool] = {}
131
132
  self._security_events: List[Dict[str, Any]] = []
133
+ self._start_time = datetime.now(timezone.utc)
132
134
 
133
135
  # Initialize all core components
134
136
  self._initialize_components()