mcp-proxy-adapter 6.1.0__py3-none-any.whl → 6.2.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 (148) hide show
  1. mcp_proxy_adapter/__main__.py +27 -7
  2. mcp_proxy_adapter/api/app.py +18 -7
  3. mcp_proxy_adapter/api/middleware/__init__.py +2 -2
  4. mcp_proxy_adapter/api/middleware/protocol_middleware.py +32 -13
  5. mcp_proxy_adapter/api/middleware/unified_security.py +12 -4
  6. mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
  7. mcp_proxy_adapter/core/app_factory.py +87 -3
  8. mcp_proxy_adapter/core/app_runner.py +272 -0
  9. mcp_proxy_adapter/core/certificate_utils.py +291 -73
  10. mcp_proxy_adapter/core/client.py +574 -0
  11. mcp_proxy_adapter/core/client_manager.py +284 -0
  12. mcp_proxy_adapter/core/protocol_manager.py +132 -10
  13. mcp_proxy_adapter/core/security_integration.py +19 -11
  14. mcp_proxy_adapter/core/server_adapter.py +17 -80
  15. mcp_proxy_adapter/core/server_engine.py +5 -99
  16. mcp_proxy_adapter/core/ssl_utils.py +13 -12
  17. mcp_proxy_adapter/core/transport_manager.py +5 -5
  18. mcp_proxy_adapter/examples/__init__.py +16 -0
  19. mcp_proxy_adapter/examples/basic_framework/__init__.py +7 -0
  20. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  21. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  22. mcp_proxy_adapter/examples/basic_framework/main.py +21 -40
  23. mcp_proxy_adapter/examples/commands/__init__.py +5 -1
  24. mcp_proxy_adapter/examples/create_certificates_simple.py +260 -75
  25. mcp_proxy_adapter/examples/debug_request_state.py +4 -36
  26. mcp_proxy_adapter/examples/debug_role_chain.py +2 -49
  27. mcp_proxy_adapter/examples/demo_client.py +0 -66
  28. mcp_proxy_adapter/examples/full_application/__init__.py +11 -0
  29. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  30. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -19
  31. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -16
  32. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  33. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -22
  34. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -24
  35. mcp_proxy_adapter/examples/full_application/main.py +65 -44
  36. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  37. mcp_proxy_adapter/examples/generate_all_certificates.py +0 -67
  38. mcp_proxy_adapter/examples/generate_certificates.py +0 -15
  39. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  40. mcp_proxy_adapter/examples/generate_test_configs.py +204 -0
  41. mcp_proxy_adapter/examples/proxy_registration_example.py +3 -70
  42. mcp_proxy_adapter/examples/run_example.py +1 -23
  43. mcp_proxy_adapter/examples/run_security_tests.py +2 -60
  44. mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -53
  45. mcp_proxy_adapter/examples/security_test_client.py +18 -123
  46. mcp_proxy_adapter/examples/setup_test_environment.py +179 -0
  47. mcp_proxy_adapter/examples/test_config.py +148 -0
  48. mcp_proxy_adapter/examples/test_config_generator.py +86 -0
  49. mcp_proxy_adapter/examples/test_examples.py +4 -67
  50. mcp_proxy_adapter/examples/universal_client.py +154 -162
  51. mcp_proxy_adapter/main.py +51 -161
  52. mcp_proxy_adapter/utils/config_generator.py +90 -2
  53. mcp_proxy_adapter/version.py +4 -2
  54. mcp_proxy_adapter-6.2.0.dist-info/METADATA +687 -0
  55. mcp_proxy_adapter-6.2.0.dist-info/RECORD +122 -0
  56. mcp_proxy_adapter/examples/README.md +0 -257
  57. mcp_proxy_adapter/examples/README_EN.md +0 -258
  58. mcp_proxy_adapter/examples/SECURITY_TESTING.md +0 -455
  59. mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
  60. mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
  61. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +0 -37
  62. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +0 -23
  63. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +0 -39
  64. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +0 -25
  65. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +0 -39
  66. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +0 -45
  67. mcp_proxy_adapter/examples/basic_framework/roles.json +0 -21
  68. mcp_proxy_adapter/examples/cert_config.json +0 -9
  69. mcp_proxy_adapter/examples/certs/admin.crt +0 -32
  70. mcp_proxy_adapter/examples/certs/admin.key +0 -52
  71. mcp_proxy_adapter/examples/certs/admin_cert.pem +0 -21
  72. mcp_proxy_adapter/examples/certs/admin_key.pem +0 -28
  73. mcp_proxy_adapter/examples/certs/ca_cert.pem +0 -23
  74. mcp_proxy_adapter/examples/certs/ca_cert.srl +0 -1
  75. mcp_proxy_adapter/examples/certs/ca_key.pem +0 -28
  76. mcp_proxy_adapter/examples/certs/cert_config.json +0 -9
  77. mcp_proxy_adapter/examples/certs/client.crt +0 -32
  78. mcp_proxy_adapter/examples/certs/client.key +0 -52
  79. mcp_proxy_adapter/examples/certs/client_admin.crt +0 -32
  80. mcp_proxy_adapter/examples/certs/client_admin.key +0 -52
  81. mcp_proxy_adapter/examples/certs/client_user.crt +0 -32
  82. mcp_proxy_adapter/examples/certs/client_user.key +0 -52
  83. mcp_proxy_adapter/examples/certs/guest_cert.pem +0 -21
  84. mcp_proxy_adapter/examples/certs/guest_key.pem +0 -28
  85. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +0 -23
  86. mcp_proxy_adapter/examples/certs/proxy_cert.pem +0 -21
  87. mcp_proxy_adapter/examples/certs/proxy_key.pem +0 -28
  88. mcp_proxy_adapter/examples/certs/readonly.crt +0 -32
  89. mcp_proxy_adapter/examples/certs/readonly.key +0 -52
  90. mcp_proxy_adapter/examples/certs/readonly_cert.pem +0 -21
  91. mcp_proxy_adapter/examples/certs/readonly_key.pem +0 -28
  92. mcp_proxy_adapter/examples/certs/server.crt +0 -32
  93. mcp_proxy_adapter/examples/certs/server.key +0 -52
  94. mcp_proxy_adapter/examples/certs/server_cert.pem +0 -32
  95. mcp_proxy_adapter/examples/certs/server_key.pem +0 -52
  96. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +0 -20
  97. mcp_proxy_adapter/examples/certs/user.crt +0 -32
  98. mcp_proxy_adapter/examples/certs/user.key +0 -52
  99. mcp_proxy_adapter/examples/certs/user_cert.pem +0 -21
  100. mcp_proxy_adapter/examples/certs/user_key.pem +0 -28
  101. mcp_proxy_adapter/examples/client_configs/api_key_client.json +0 -13
  102. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +0 -13
  103. mcp_proxy_adapter/examples/client_configs/certificate_client.json +0 -22
  104. mcp_proxy_adapter/examples/client_configs/jwt_client.json +0 -15
  105. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +0 -9
  106. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +0 -37
  107. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +0 -23
  108. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +0 -39
  109. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +0 -25
  110. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +0 -39
  111. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +0 -45
  112. mcp_proxy_adapter/examples/full_application/roles.json +0 -21
  113. mcp_proxy_adapter/examples/keys/ca_key.pem +0 -28
  114. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +0 -28
  115. mcp_proxy_adapter/examples/keys/test_ca_ca.key +0 -28
  116. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +0 -220
  117. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +0 -1
  118. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +0 -1
  119. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +0 -1
  120. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +0 -1
  121. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +0 -1
  122. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +0 -220
  123. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +0 -1
  124. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +0 -1
  125. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +0 -1
  126. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +0 -1
  127. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +0 -1
  128. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +0 -2
  129. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +0 -1
  130. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +0 -1
  131. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +0 -1
  132. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +0 -1
  133. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +0 -1
  134. mcp_proxy_adapter/examples/roles.json +0 -38
  135. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +0 -204
  136. mcp_proxy_adapter/examples/server_configs/config_http_token.json +0 -238
  137. mcp_proxy_adapter/examples/server_configs/config_https.json +0 -215
  138. mcp_proxy_adapter/examples/server_configs/config_https_token.json +0 -231
  139. mcp_proxy_adapter/examples/server_configs/config_mtls.json +0 -215
  140. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +0 -250
  141. mcp_proxy_adapter/examples/server_configs/config_simple.json +0 -46
  142. mcp_proxy_adapter/examples/server_configs/roles.json +0 -38
  143. mcp_proxy_adapter-6.1.0.dist-info/METADATA +0 -205
  144. mcp_proxy_adapter-6.1.0.dist-info/RECORD +0 -193
  145. {mcp_proxy_adapter-6.1.0.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/WHEEL +0 -0
  146. {mcp_proxy_adapter-6.1.0.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/entry_points.txt +0 -0
  147. {mcp_proxy_adapter-6.1.0.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/licenses/LICENSE +0 -0
  148. {mcp_proxy_adapter-6.1.0.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/top_level.txt +0 -0
@@ -2,8 +2,7 @@
2
2
  Certificate Utilities
3
3
 
4
4
  This module provides utilities for working with certificates including creation,
5
- validation, and role extraction. Integrates with AuthValidator and RoleUtils
6
- from previous phases.
5
+ validation, and role extraction. Integrates with mcp_security_framework.
7
6
 
8
7
  Author: MCP Proxy Adapter Team
9
8
  Version: 1.0.0
@@ -16,10 +15,31 @@ from datetime import datetime, timedelta, timezone
16
15
  from typing import Dict, List, Optional, Any
17
16
  from pathlib import Path
18
17
 
19
- from cryptography import x509
20
- from cryptography.hazmat.primitives import hashes, serialization
21
- from cryptography.hazmat.primitives.asymmetric import rsa
22
- from cryptography.x509.oid import NameOID
18
+ # Import mcp_security_framework
19
+ try:
20
+ from mcp_security_framework.core.cert_manager import CertificateManager
21
+ from mcp_security_framework.schemas.config import (
22
+ CertificateConfig,
23
+ CAConfig,
24
+ ClientCertConfig,
25
+ ServerCertConfig
26
+ )
27
+ from mcp_security_framework.schemas.models import CertificateType
28
+ from mcp_security_framework.utils.cert_utils import (
29
+ parse_certificate,
30
+ extract_roles_from_certificate,
31
+ extract_permissions_from_certificate,
32
+ validate_certificate_chain,
33
+ get_certificate_expiry
34
+ )
35
+ SECURITY_FRAMEWORK_AVAILABLE = True
36
+ except ImportError:
37
+ SECURITY_FRAMEWORK_AVAILABLE = False
38
+ # Fallback to cryptography if mcp_security_framework is not available
39
+ from cryptography import x509
40
+ from cryptography.hazmat.primitives import hashes, serialization
41
+ from cryptography.hazmat.primitives.asymmetric import rsa
42
+ from cryptography.x509.oid import NameOID
23
43
 
24
44
  from .auth_validator import AuthValidator
25
45
  from .role_utils import RoleUtils
@@ -32,7 +52,7 @@ class CertificateUtils:
32
52
  Utilities for working with certificates.
33
53
 
34
54
  Provides methods for creating CA, server, and client certificates,
35
- as well as validation and role extraction.
55
+ as well as validation and role extraction using mcp_security_framework.
36
56
  """
37
57
 
38
58
  # Default certificate validity period (1 year)
@@ -49,7 +69,7 @@ class CertificateUtils:
49
69
  validity_days: int = DEFAULT_VALIDITY_DAYS,
50
70
  key_size: int = DEFAULT_KEY_SIZE) -> Dict[str, str]:
51
71
  """
52
- Create a CA certificate and private key.
72
+ Create a CA certificate and private key using mcp_security_framework.
53
73
 
54
74
  Args:
55
75
  common_name: Common name for the CA certificate
@@ -64,6 +84,64 @@ class CertificateUtils:
64
84
  ValueError: If parameters are invalid
65
85
  OSError: If files cannot be created
66
86
  """
87
+ if not SECURITY_FRAMEWORK_AVAILABLE:
88
+ logger.warning("mcp_security_framework not available, using fallback method")
89
+ return CertificateUtils._create_ca_certificate_fallback(
90
+ common_name, output_dir, validity_days, key_size
91
+ )
92
+
93
+ try:
94
+ # Validate parameters
95
+ if not common_name or not common_name.strip():
96
+ raise ValueError("Common name cannot be empty")
97
+
98
+ if validity_days <= 0:
99
+ raise ValueError("Validity days must be positive")
100
+
101
+ if key_size < 1024:
102
+ raise ValueError("Key size must be at least 1024 bits")
103
+
104
+ # Create output directory if it doesn't exist
105
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
106
+
107
+ # Configure CA using mcp_security_framework
108
+ ca_config = CAConfig(
109
+ common_name=common_name,
110
+ organization="MCP Proxy Adapter CA",
111
+ organizational_unit="Certificate Authority",
112
+ country="US",
113
+ state="Default State",
114
+ locality="Default City",
115
+ validity_days=validity_days,
116
+ key_size=key_size,
117
+ key_type="RSA"
118
+ )
119
+
120
+ # Create certificate manager
121
+ cert_config = CertificateConfig(
122
+ output_dir=output_dir,
123
+ ca_cert_path=str(Path(output_dir) / f"{common_name}.crt"),
124
+ ca_key_path=str(Path(output_dir) / f"{common_name}.key")
125
+ )
126
+
127
+ cert_manager = CertificateManager(cert_config)
128
+
129
+ # Generate CA certificate
130
+ ca_pair = cert_manager.create_ca_certificate(ca_config)
131
+
132
+ return {
133
+ "cert_path": str(ca_pair.cert_path),
134
+ "key_path": str(ca_pair.key_path)
135
+ }
136
+
137
+ except Exception as e:
138
+ logger.error(f"Failed to create CA certificate: {e}")
139
+ raise
140
+
141
+ @staticmethod
142
+ def _create_ca_certificate_fallback(common_name: str, output_dir: str,
143
+ validity_days: int, key_size: int) -> Dict[str, str]:
144
+ """Fallback method using cryptography directly."""
67
145
  try:
68
146
  # Validate parameters
69
147
  if not common_name or not common_name.strip():
@@ -117,21 +195,14 @@ class CertificateUtils:
117
195
  data_encipherment=False,
118
196
  key_agreement=False,
119
197
  encipher_only=False,
120
- decipher_only=False,
121
- content_commitment=False
198
+ decipher_only=False
122
199
  ),
123
200
  critical=True
124
- ).add_extension(
125
- x509.SubjectKeyIdentifier.from_public_key(private_key.public_key()),
126
- critical=False
127
- ).add_extension(
128
- x509.AuthorityKeyIdentifier.from_issuer_public_key(private_key.public_key()),
129
- critical=False
130
201
  ).sign(private_key, hashes.SHA256())
131
202
 
132
203
  # Save certificate and key
133
- cert_path = os.path.join(output_dir, "ca.crt")
134
- key_path = os.path.join(output_dir, "ca.key")
204
+ cert_path = Path(output_dir) / f"{common_name}.crt"
205
+ key_path = Path(output_dir) / f"{common_name}.key"
135
206
 
136
207
  with open(cert_path, "wb") as f:
137
208
  f.write(cert.public_bytes(serialization.Encoding.PEM))
@@ -143,17 +214,13 @@ class CertificateUtils:
143
214
  encryption_algorithm=serialization.NoEncryption()
144
215
  ))
145
216
 
146
- logger.info(f"CA certificate created: {cert_path}")
147
-
148
217
  return {
149
- "cert_path": cert_path,
150
- "key_path": key_path,
151
- "common_name": common_name,
152
- "validity_days": validity_days
218
+ "cert_path": str(cert_path),
219
+ "key_path": str(key_path)
153
220
  }
154
221
 
155
222
  except Exception as e:
156
- logger.error(f"Failed to create CA certificate: {e}")
223
+ logger.error(f"Failed to create CA certificate (fallback): {e}")
157
224
  raise
158
225
 
159
226
  @staticmethod
@@ -304,20 +371,19 @@ class CertificateUtils:
304
371
  raise
305
372
 
306
373
  @staticmethod
307
- def create_client_certificate(common_name: str, roles: List[str],
308
- ca_cert_path: str, ca_key_path: str,
309
- output_dir: str,
374
+ def create_client_certificate(common_name: str, ca_cert_path: str, ca_key_path: str,
375
+ output_dir: str, roles: List[str] = None,
310
376
  validity_days: int = DEFAULT_VALIDITY_DAYS,
311
377
  key_size: int = DEFAULT_KEY_SIZE) -> Dict[str, str]:
312
378
  """
313
- Create a client certificate signed by CA.
379
+ Create a client certificate and private key using mcp_security_framework.
314
380
 
315
381
  Args:
316
382
  common_name: Common name for the client certificate
317
- roles: List of roles to include in certificate
318
383
  ca_cert_path: Path to CA certificate
319
384
  ca_key_path: Path to CA private key
320
385
  output_dir: Directory to save certificate and key files
386
+ roles: List of roles to include in certificate
321
387
  validity_days: Certificate validity period in days
322
388
  key_size: RSA key size in bits
323
389
 
@@ -326,22 +392,92 @@ class CertificateUtils:
326
392
 
327
393
  Raises:
328
394
  ValueError: If parameters are invalid
329
- FileNotFoundError: If CA files not found
330
395
  OSError: If files cannot be created
331
396
  """
397
+ if not SECURITY_FRAMEWORK_AVAILABLE:
398
+ logger.warning("mcp_security_framework not available, using fallback method")
399
+ return CertificateUtils._create_client_certificate_fallback(
400
+ common_name, ca_cert_path, ca_key_path, output_dir, roles, validity_days, key_size
401
+ )
402
+
332
403
  try:
333
404
  # Validate parameters
334
405
  if not common_name or not common_name.strip():
335
406
  raise ValueError("Common name cannot be empty")
336
407
 
337
- if not roles:
338
- roles = ["client"]
408
+ if not Path(ca_cert_path).exists():
409
+ raise ValueError(f"CA certificate not found: {ca_cert_path}")
410
+
411
+ if not Path(ca_key_path).exists():
412
+ raise ValueError(f"CA private key not found: {ca_key_path}")
413
+
414
+ if validity_days <= 0:
415
+ raise ValueError("Validity days must be positive")
416
+
417
+ if key_size < 1024:
418
+ raise ValueError("Key size must be at least 1024 bits")
419
+
420
+ # Create output directory if it doesn't exist
421
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
422
+
423
+ # Configure client certificate using mcp_security_framework
424
+ client_config = ClientCertConfig(
425
+ common_name=common_name,
426
+ organization="MCP Proxy Adapter Client",
427
+ organizational_unit="Client Certificates",
428
+ country="US",
429
+ state="Default State",
430
+ locality="Default City",
431
+ validity_days=validity_days,
432
+ key_size=key_size,
433
+ key_type="RSA",
434
+ roles=roles or [],
435
+ permissions=[] # Permissions can be added later if needed
436
+ )
437
+
438
+ # Create certificate manager
439
+ cert_config = CertificateConfig(
440
+ output_dir=output_dir,
441
+ ca_cert_path=ca_cert_path,
442
+ ca_key_path=ca_key_path
443
+ )
444
+
445
+ cert_manager = CertificateManager(cert_config)
446
+
447
+ # Generate client certificate
448
+ client_pair = cert_manager.create_client_certificate(client_config)
449
+
450
+ return {
451
+ "cert_path": str(client_pair.cert_path),
452
+ "key_path": str(client_pair.key_path)
453
+ }
454
+
455
+ except Exception as e:
456
+ logger.error(f"Failed to create client certificate: {e}")
457
+ raise
458
+
459
+ @staticmethod
460
+ def _create_client_certificate_fallback(common_name: str, ca_cert_path: str, ca_key_path: str,
461
+ output_dir: str, roles: List[str] = None,
462
+ validity_days: int = DEFAULT_VALIDITY_DAYS,
463
+ key_size: int = DEFAULT_KEY_SIZE) -> Dict[str, str]:
464
+ """Fallback method using cryptography directly."""
465
+ try:
466
+ # Validate parameters
467
+ if not common_name or not common_name.strip():
468
+ raise ValueError("Common name cannot be empty")
339
469
 
340
470
  if not Path(ca_cert_path).exists():
341
- raise FileNotFoundError(f"CA certificate not found: {ca_cert_path}")
471
+ raise ValueError(f"CA certificate not found: {ca_cert_path}")
342
472
 
343
473
  if not Path(ca_key_path).exists():
344
- raise FileNotFoundError(f"CA key not found: {ca_key_path}")
474
+ raise ValueError(f"CA private key not found: {ca_key_path}")
475
+
476
+ if validity_days <= 0:
477
+ raise ValueError("Validity days must be positive")
478
+
479
+ if key_size < 1024:
480
+ raise ValueError("Key size must be at least 1024 bits")
345
481
 
346
482
  # Create output directory if it doesn't exist
347
483
  Path(output_dir).mkdir(parents=True, exist_ok=True)
@@ -362,8 +498,8 @@ class CertificateUtils:
362
498
  # Create certificate subject
363
499
  subject = x509.Name([
364
500
  x509.NameAttribute(NameOID.COMMON_NAME, common_name),
365
- x509.NameAttribute(NameOID.ORGANIZATION_NAME, "MCP Proxy Adapter"),
366
- x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "Client"),
501
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "MCP Proxy Adapter Client"),
502
+ x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "Client Certificates"),
367
503
  x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
368
504
  ])
369
505
 
@@ -392,37 +528,28 @@ class CertificateUtils:
392
528
  data_encipherment=False,
393
529
  key_agreement=False,
394
530
  encipher_only=False,
395
- decipher_only=False,
396
- content_commitment=False
531
+ decipher_only=False
397
532
  ),
398
533
  critical=True
399
534
  ).add_extension(
400
- x509.SubjectKeyIdentifier.from_public_key(private_key.public_key()),
401
- critical=False
402
- ).add_extension(
403
- x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()),
404
- critical=False
405
- ).add_extension(
406
- x509.ExtendedKeyUsage([
407
- x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH
408
- ]),
535
+ x509.ExtendedKeyUsage([x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH]),
409
536
  critical=False
410
537
  )
411
538
 
412
- # Add roles extension
539
+ # Add roles extension if provided
413
540
  if roles:
414
- roles_data = ",".join(roles).encode('utf-8')
541
+ roles_str = ",".join(roles)
415
542
  roles_oid = x509.ObjectIdentifier(CertificateUtils.ROLE_EXTENSION_OID)
416
543
  cert_builder = cert_builder.add_extension(
417
- x509.UnrecognizedExtension(roles_oid, roles_data),
544
+ x509.UnrecognizedExtension(roles_oid, roles_str.encode()),
418
545
  critical=False
419
546
  )
420
547
 
421
548
  cert = cert_builder.sign(ca_key, hashes.SHA256())
422
549
 
423
550
  # Save certificate and key
424
- cert_path = os.path.join(output_dir, f"{common_name}.crt")
425
- key_path = os.path.join(output_dir, f"{common_name}.key")
551
+ cert_path = Path(output_dir) / f"{common_name}.crt"
552
+ key_path = Path(output_dir) / f"{common_name}.key"
426
553
 
427
554
  with open(cert_path, "wb") as f:
428
555
  f.write(cert.public_bytes(serialization.Encoding.PEM))
@@ -434,66 +561,157 @@ class CertificateUtils:
434
561
  encryption_algorithm=serialization.NoEncryption()
435
562
  ))
436
563
 
437
- logger.info(f"Client certificate created: {cert_path}")
438
-
439
564
  return {
440
- "cert_path": cert_path,
441
- "key_path": key_path,
442
- "common_name": common_name,
443
- "roles": roles,
444
- "validity_days": validity_days
565
+ "cert_path": str(cert_path),
566
+ "key_path": str(key_path)
445
567
  }
446
568
 
447
569
  except Exception as e:
448
- logger.error(f"Failed to create client certificate: {e}")
570
+ logger.error(f"Failed to create client certificate (fallback): {e}")
449
571
  raise
450
572
 
451
573
  @staticmethod
452
574
  def extract_roles_from_certificate(cert_path: str) -> List[str]:
453
575
  """
454
- Extract roles from certificate file using RoleUtils.
576
+ Extract roles from certificate using mcp_security_framework.
455
577
 
456
578
  Args:
457
579
  cert_path: Path to certificate file
458
580
 
459
581
  Returns:
460
- List of roles extracted from certificate
582
+ List of roles found in certificate
461
583
  """
462
- return RoleUtils.extract_roles_from_certificate(cert_path)
584
+ if not SECURITY_FRAMEWORK_AVAILABLE:
585
+ logger.warning("mcp_security_framework not available, using fallback method")
586
+ return RoleUtils.extract_roles_from_certificate(cert_path)
587
+
588
+ try:
589
+ return extract_roles_from_certificate(cert_path)
590
+ except Exception as e:
591
+ logger.error(f"Failed to extract roles from certificate: {e}")
592
+ return []
463
593
 
464
594
  @staticmethod
465
- def extract_roles_from_certificate_object(cert: x509.Certificate) -> List[str]:
595
+ def extract_roles_from_certificate_object(cert) -> List[str]:
466
596
  """
467
- Extract roles from certificate object using RoleUtils.
597
+ Extract roles from certificate object using mcp_security_framework.
468
598
 
469
599
  Args:
470
600
  cert: Certificate object
471
601
 
472
602
  Returns:
473
- List of roles extracted from certificate
603
+ List of roles found in certificate
604
+ """
605
+ if not SECURITY_FRAMEWORK_AVAILABLE:
606
+ logger.warning("mcp_security_framework not available, using fallback method")
607
+ return RoleUtils.extract_roles_from_certificate_object(cert)
608
+
609
+ try:
610
+ # Convert certificate object to PEM format for mcp_security_framework
611
+ cert_pem = cert.public_bytes(serialization.Encoding.PEM)
612
+ return extract_roles_from_certificate(cert_pem)
613
+ except Exception as e:
614
+ logger.error(f"Failed to extract roles from certificate object: {e}")
615
+ return []
616
+
617
+ @staticmethod
618
+ def extract_permissions_from_certificate(cert_path: str) -> List[str]:
619
+ """
620
+ Extract permissions from certificate using mcp_security_framework.
621
+
622
+ Args:
623
+ cert_path: Path to certificate file
624
+
625
+ Returns:
626
+ List of permissions found in certificate
474
627
  """
475
- return RoleUtils.extract_roles_from_certificate_object(cert)
628
+ if not SECURITY_FRAMEWORK_AVAILABLE:
629
+ logger.warning("mcp_security_framework not available, permissions extraction not supported")
630
+ return []
631
+
632
+ try:
633
+ return extract_permissions_from_certificate(cert_path)
634
+ except Exception as e:
635
+ logger.error(f"Failed to extract permissions from certificate: {e}")
636
+ return []
476
637
 
477
638
  @staticmethod
478
639
  def validate_certificate_chain(cert_path: str, ca_cert_path: str) -> bool:
479
640
  """
480
- Validate certificate chain using AuthValidator.
641
+ Validate certificate chain using mcp_security_framework.
481
642
 
482
643
  Args:
483
644
  cert_path: Path to certificate to validate
484
645
  ca_cert_path: Path to CA certificate
485
646
 
486
647
  Returns:
487
- True if certificate chain is valid, False otherwise
648
+ True if chain is valid, False otherwise
488
649
  """
650
+ if not SECURITY_FRAMEWORK_AVAILABLE:
651
+ logger.warning("mcp_security_framework not available, using fallback validation")
652
+ return CertificateUtils._validate_certificate_chain_fallback(cert_path, ca_cert_path)
653
+
489
654
  try:
490
- validator = AuthValidator()
491
- result = validator.validate_certificate_chain(cert_path, ca_cert_path)
492
- return result.is_valid
655
+ return validate_certificate_chain(cert_path, ca_cert_path)
493
656
  except Exception as e:
494
657
  logger.error(f"Failed to validate certificate chain: {e}")
495
658
  return False
496
659
 
660
+ @staticmethod
661
+ def _validate_certificate_chain_fallback(cert_path: str, ca_cert_path: str) -> bool:
662
+ """Fallback certificate chain validation using cryptography."""
663
+ try:
664
+ # Load certificates
665
+ with open(cert_path, "rb") as f:
666
+ cert = x509.load_pem_x509_certificate(f.read())
667
+
668
+ with open(ca_cert_path, "rb") as f:
669
+ ca_cert = x509.load_pem_x509_certificate(f.read())
670
+
671
+ # Simple validation: check if certificate is issued by CA
672
+ return cert.issuer == ca_cert.subject
673
+
674
+ except Exception as e:
675
+ logger.error(f"Failed to validate certificate chain (fallback): {e}")
676
+ return False
677
+
678
+ @staticmethod
679
+ def get_certificate_expiry(cert_path: str) -> Optional[datetime]:
680
+ """
681
+ Get certificate expiry date using mcp_security_framework.
682
+
683
+ Args:
684
+ cert_path: Path to certificate file
685
+
686
+ Returns:
687
+ Certificate expiry date or None if not available
688
+ """
689
+ if not SECURITY_FRAMEWORK_AVAILABLE:
690
+ logger.warning("mcp_security_framework not available, using fallback method")
691
+ return CertificateUtils._get_certificate_expiry_fallback(cert_path)
692
+
693
+ try:
694
+ expiry_info = get_certificate_expiry(cert_path)
695
+ if isinstance(expiry_info, dict) and 'expiry_date' in expiry_info:
696
+ return expiry_info['expiry_date']
697
+ return None
698
+ except Exception as e:
699
+ logger.error(f"Failed to get certificate expiry: {e}")
700
+ return None
701
+
702
+ @staticmethod
703
+ def _get_certificate_expiry_fallback(cert_path: str) -> Optional[datetime]:
704
+ """Fallback method to get certificate expiry using cryptography."""
705
+ try:
706
+ with open(cert_path, "rb") as f:
707
+ cert = x509.load_pem_x509_certificate(f.read())
708
+
709
+ return cert.not_valid_after
710
+
711
+ except Exception as e:
712
+ logger.error(f"Failed to get certificate expiry (fallback): {e}")
713
+ return None
714
+
497
715
  @staticmethod
498
716
  def validate_certificate(cert_path: str) -> bool:
499
717
  """