mcp-security-framework 2.1.0__tar.gz → 2.2.1__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 (57) hide show
  1. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/PKG-INFO +1 -1
  2. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/__init__.py +1 -1
  3. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/core/cert_manager.py +109 -18
  4. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/core/ssl_manager.py +273 -43
  5. mcp_security_framework-2.2.1/mcp_security_framework/examples/cert_inspection_example.py +391 -0
  6. mcp_security_framework-2.2.1/mcp_security_framework/examples/crl_retrieval_example.py +385 -0
  7. mcp_security_framework-2.2.1/mcp_security_framework/examples/protected_keys_example.py +314 -0
  8. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/schemas/config.py +45 -6
  9. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/utils/__init__.py +27 -0
  10. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/utils/cert_utils.py +206 -7
  11. mcp_security_framework-2.2.1/mcp_security_framework/utils/crl_client.py +408 -0
  12. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework.egg-info/PKG-INFO +1 -1
  13. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework.egg-info/SOURCES.txt +4 -0
  14. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/pyproject.toml +1 -1
  15. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/LICENSE +0 -0
  16. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/README.md +0 -0
  17. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/cli/__init__.py +0 -0
  18. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/cli/cert_cli.py +0 -0
  19. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/cli/security_cli.py +0 -0
  20. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/constants.py +0 -0
  21. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/core/__init__.py +0 -0
  22. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/core/adapter_wrapper.py +0 -0
  23. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/core/audit_logger.py +0 -0
  24. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/core/auth_manager.py +0 -0
  25. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/core/permission_manager.py +0 -0
  26. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/core/rate_limiter.py +0 -0
  27. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/core/security_adapter.py +0 -0
  28. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/core/security_manager.py +0 -0
  29. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/examples/__init__.py +0 -0
  30. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/examples/adapter_example.py +0 -0
  31. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/examples/comprehensive_example.py +0 -0
  32. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/examples/fastapi_example.py +0 -0
  33. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/examples/flask_example.py +0 -0
  34. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/examples/gateway_example.py +0 -0
  35. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/examples/microservice_example.py +0 -0
  36. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/examples/standalone_example.py +0 -0
  37. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/middleware/__init__.py +0 -0
  38. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/middleware/auth_middleware.py +0 -0
  39. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/middleware/fastapi_auth_middleware.py +0 -0
  40. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/middleware/fastapi_middleware.py +0 -0
  41. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/middleware/flask_auth_middleware.py +0 -0
  42. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/middleware/flask_middleware.py +0 -0
  43. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/middleware/mtls_middleware.py +0 -0
  44. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/middleware/rate_limit_middleware.py +0 -0
  45. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/middleware/security_middleware.py +0 -0
  46. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/schemas/__init__.py +0 -0
  47. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/schemas/models.py +0 -0
  48. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/schemas/operation_context.py +0 -0
  49. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/schemas/responses.py +0 -0
  50. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/utils/crypto_utils.py +0 -0
  51. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/utils/datetime_compat.py +0 -0
  52. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework/utils/validation_utils.py +0 -0
  53. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework.egg-info/dependency_links.txt +0 -0
  54. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework.egg-info/entry_points.txt +0 -0
  55. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework.egg-info/requires.txt +0 -0
  56. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/mcp_security_framework.egg-info/top_level.txt +0 -0
  57. {mcp_security_framework-2.1.0 → mcp_security_framework-2.2.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-security-framework
3
- Version: 2.1.0
3
+ Version: 2.2.1
4
4
  Summary: Universal security framework for microservices with SSL/TLS, authentication, authorization, and rate limiting. Added extensible security adapter system for custom operation validation, structured audit logging, and operation context support. Comprehensive test coverage with 1104+ tests. Fully backward compatible. Requires cryptography>=42.0.0 for certificate operations.
5
5
  Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
6
6
  Maintainer-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
@@ -79,7 +79,7 @@ from mcp_security_framework.schemas.responses import (
79
79
  )
80
80
 
81
81
  # Version information
82
- __version__ = "2.1.0"
82
+ __version__ = "2.2.1"
83
83
  __author__ = "Vasiliy Zdanovskiy"
84
84
  __email__ = "vasilyvz@gmail.com"
85
85
  __license__ = "MIT"
@@ -36,6 +36,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union
36
36
  from cryptography import x509
37
37
  from cryptography.hazmat.primitives import hashes, serialization
38
38
  from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, rsa
39
+ from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes
39
40
  from cryptography.x509.oid import ExtendedKeyUsageOID, ExtensionOID, NameOID
40
41
 
41
42
  from mcp_security_framework.schemas.config import (
@@ -53,6 +54,7 @@ from mcp_security_framework.schemas.models import (
53
54
  UnknownRoleError,
54
55
  )
55
56
  from mcp_security_framework.utils.cert_utils import (
57
+ CertificateError,
56
58
  extract_permissions_from_certificate,
57
59
  extract_roles_from_certificate,
58
60
  get_certificate_expiry,
@@ -61,6 +63,7 @@ from mcp_security_framework.utils.cert_utils import (
61
63
  is_certificate_revoked,
62
64
  is_crl_valid,
63
65
  parse_certificate,
66
+ parse_private_key,
64
67
  validate_certificate_against_crl,
65
68
  validate_certificate_chain,
66
69
  )
@@ -123,6 +126,39 @@ def _get_signing_algorithm(private_key):
123
126
  return hashes.SHA256()
124
127
 
125
128
 
129
+ def _load_private_key(
130
+ key_bytes: bytes,
131
+ password: Optional[bytes],
132
+ source: str,
133
+ ) -> PrivateKeyTypes:
134
+ """
135
+ Load a private key from PEM or DER bytes with optional passphrase.
136
+
137
+ This helper wraps parse_private_key to attach a source label and convert
138
+ from CertificateError (raised by parse_private_key) to CertificateValidationError
139
+ (expected by callers in this module). The classification of whether the key
140
+ is encrypted, whether a password was supplied, and whether the password is
141
+ correct all lives in parse_private_key; this wrapper's only job is to add
142
+ the source context and bridge the exception type.
143
+
144
+ Args:
145
+ key_bytes: The PEM or DER-encoded private key
146
+ password: Optional passphrase as bytes, or None
147
+ source: Human-readable label for error messages (e.g. "CA private key",
148
+ "Parent CA private key"). Included in all error messages.
149
+
150
+ Returns:
151
+ Loaded private key object
152
+
153
+ Raises:
154
+ CertificateValidationError: When the key cannot be loaded or decrypted
155
+ """
156
+ try:
157
+ return parse_private_key(key_bytes, password=password)
158
+ except CertificateError as exc:
159
+ raise CertificateValidationError(f"{source}: {exc}", error_code=-32003) from exc
160
+
161
+
126
162
  def _subject_attribute(certificate, oid) -> str:
127
163
  """
128
164
  Return one subject attribute of a certificate, or "" when it carries none.
@@ -616,8 +652,15 @@ class CertificateManager:
616
652
  parent_ca_cert = x509.load_pem_x509_certificate(f.read())
617
653
 
618
654
  with open(intermediate_config.parent_ca_key, "rb") as f:
619
- parent_ca_key = serialization.load_pem_private_key(
620
- f.read(), password=None
655
+ password = (
656
+ intermediate_config.parent_ca_key_password.encode()
657
+ if intermediate_config.parent_ca_key_password
658
+ else None
659
+ )
660
+ parent_ca_key = _load_private_key(
661
+ f.read(),
662
+ password=password,
663
+ source="Parent CA private key",
621
664
  )
622
665
 
623
666
  # Generate intermediate CA private key
@@ -878,7 +921,16 @@ class CertificateManager:
878
921
  ca_cert = x509.load_pem_x509_certificate(f.read())
879
922
 
880
923
  with open(client_config.ca_key_path, "rb") as f:
881
- ca_key = serialization.load_pem_private_key(f.read(), password=None)
924
+ password = (
925
+ client_config.ca_key_password.encode()
926
+ if client_config.ca_key_password
927
+ else None
928
+ )
929
+ ca_key = _load_private_key(
930
+ f.read(),
931
+ password=password,
932
+ source="CA private key",
933
+ )
882
934
 
883
935
  # Verify that CA private key matches CA certificate
884
936
  _verify_ca_key_matches_cert(
@@ -1104,8 +1156,8 @@ class CertificateManager:
1104
1156
  )
1105
1157
  except (FileNotFoundError, PermissionError, ValueError):
1106
1158
  raise
1107
- except CertificateGenerationError:
1108
- # Re-raise CertificateGenerationError without wrapping
1159
+ except (CertificateGenerationError, CertificateValidationError):
1160
+ # Re-raise these without wrapping
1109
1161
  raise
1110
1162
  except Exception as e:
1111
1163
  self.logger.error(
@@ -1189,7 +1241,16 @@ class CertificateManager:
1189
1241
  ca_cert = x509.load_pem_x509_certificate(f.read())
1190
1242
 
1191
1243
  with open(server_config.ca_key_path, "rb") as f:
1192
- ca_key = serialization.load_pem_private_key(f.read(), password=None)
1244
+ password = (
1245
+ server_config.ca_key_password.encode()
1246
+ if server_config.ca_key_password
1247
+ else None
1248
+ )
1249
+ ca_key = _load_private_key(
1250
+ f.read(),
1251
+ password=password,
1252
+ source="CA private key",
1253
+ )
1193
1254
 
1194
1255
  # Verify that CA private key matches CA certificate
1195
1256
  _verify_ca_key_matches_cert(
@@ -1406,8 +1467,8 @@ class CertificateManager:
1406
1467
  )
1407
1468
  except (FileNotFoundError, PermissionError, ValueError):
1408
1469
  raise
1409
- except CertificateGenerationError:
1410
- # Re-raise CertificateGenerationError without wrapping
1470
+ except (CertificateGenerationError, CertificateValidationError):
1471
+ # Re-raise these without wrapping
1411
1472
  raise
1412
1473
  except Exception as e:
1413
1474
  self.logger.error(
@@ -1493,7 +1554,16 @@ class CertificateManager:
1493
1554
  ca_cert = x509.load_pem_x509_certificate(f.read())
1494
1555
 
1495
1556
  with open(ca_key_file, "rb") as f:
1496
- ca_key = serialization.load_pem_private_key(f.read(), password=None)
1557
+ password = (
1558
+ self.config.ca_key_password.encode()
1559
+ if self.config.ca_key_password
1560
+ else None
1561
+ )
1562
+ ca_key = _load_private_key(
1563
+ f.read(),
1564
+ password=password,
1565
+ source="CA private key",
1566
+ )
1497
1567
 
1498
1568
  # Create new certificate with extended validity
1499
1569
  builder = x509.CertificateBuilder()
@@ -1651,7 +1721,16 @@ class CertificateManager:
1651
1721
  ca_cert = x509.load_pem_x509_certificate(f.read())
1652
1722
 
1653
1723
  with open(ca_key_file, "rb") as f:
1654
- ca_key = serialization.load_pem_private_key(f.read(), password=None)
1724
+ password = (
1725
+ self.config.ca_key_password.encode()
1726
+ if self.config.ca_key_password
1727
+ else None
1728
+ )
1729
+ ca_key = _load_private_key(
1730
+ f.read(),
1731
+ password=password,
1732
+ source="CA private key",
1733
+ )
1655
1734
 
1656
1735
  # Create CRL builder
1657
1736
  builder = x509.CertificateRevocationListBuilder()
@@ -2165,8 +2244,15 @@ class CertificateManager:
2165
2244
  # Load CA private key
2166
2245
  with open(ca_key_path, "rb") as f:
2167
2246
  ca_key_data = f.read()
2168
- ca_private_key = serialization.load_pem_private_key(
2169
- ca_key_data, password=None
2247
+ password = (
2248
+ self.config.ca_key_password.encode()
2249
+ if self.config.ca_key_password
2250
+ else None
2251
+ )
2252
+ ca_private_key = _load_private_key(
2253
+ ca_key_data,
2254
+ password=password,
2255
+ source="CA private key",
2170
2256
  )
2171
2257
 
2172
2258
  # Verify that CA private key matches CA certificate
@@ -2249,8 +2335,8 @@ class CertificateManager:
2249
2335
 
2250
2336
  except (FileNotFoundError, PermissionError, ValueError):
2251
2337
  raise
2252
- except CertificateGenerationError:
2253
- # Re-raise CertificateGenerationError without wrapping
2338
+ except (CertificateGenerationError, CertificateValidationError):
2339
+ # Re-raise these without wrapping
2254
2340
  raise
2255
2341
  except Exception as e:
2256
2342
  self.logger.error(
@@ -2495,11 +2581,16 @@ class CertificateManager:
2495
2581
  return key_data.decode("utf-8")
2496
2582
  elif format.lower() == "der":
2497
2583
  # Convert PEM to DER
2498
- from cryptography.hazmat.primitives.serialization import (
2499
- load_pem_private_key,
2584
+ password = (
2585
+ self.config.ca_key_password.encode()
2586
+ if self.config.ca_key_password
2587
+ else None
2588
+ )
2589
+ key = _load_private_key(
2590
+ key_data,
2591
+ password=password,
2592
+ source="Private key",
2500
2593
  )
2501
-
2502
- key = load_pem_private_key(key_data, password=None)
2503
2594
  return key.private_bytes(
2504
2595
  encoding=serialization.Encoding.DER,
2505
2596
  format=serialization.PrivateFormat.PKCS8,