mcp-proxy-adapter 6.9.28__py3-none-any.whl → 6.9.30__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.

Potentially problematic release.


This version of mcp-proxy-adapter might be problematic. Click here for more details.

Files changed (212) hide show
  1. mcp_proxy_adapter/__init__.py +10 -0
  2. mcp_proxy_adapter/__main__.py +8 -21
  3. mcp_proxy_adapter/api/app.py +10 -913
  4. mcp_proxy_adapter/api/core/__init__.py +18 -0
  5. mcp_proxy_adapter/api/core/app_factory.py +243 -0
  6. mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
  7. mcp_proxy_adapter/api/core/registration_manager.py +166 -0
  8. mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
  9. mcp_proxy_adapter/api/handlers.py +78 -199
  10. mcp_proxy_adapter/api/middleware/__init__.py +1 -44
  11. mcp_proxy_adapter/api/middleware/base.py +0 -42
  12. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +0 -85
  13. mcp_proxy_adapter/api/middleware/error_handling.py +1 -127
  14. mcp_proxy_adapter/api/middleware/factory.py +0 -94
  15. mcp_proxy_adapter/api/middleware/logging.py +0 -112
  16. mcp_proxy_adapter/api/middleware/performance.py +0 -35
  17. mcp_proxy_adapter/api/middleware/protocol_middleware.py +2 -98
  18. mcp_proxy_adapter/api/middleware/transport_middleware.py +0 -37
  19. mcp_proxy_adapter/api/middleware/unified_security.py +10 -10
  20. mcp_proxy_adapter/api/middleware/user_info_middleware.py +0 -118
  21. mcp_proxy_adapter/api/openapi/__init__.py +21 -0
  22. mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
  23. mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
  24. mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
  25. mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
  26. mcp_proxy_adapter/api/schemas.py +0 -61
  27. mcp_proxy_adapter/api/tool_integration.py +0 -117
  28. mcp_proxy_adapter/api/tools.py +0 -46
  29. mcp_proxy_adapter/cli/__init__.py +12 -0
  30. mcp_proxy_adapter/cli/commands/__init__.py +15 -0
  31. mcp_proxy_adapter/cli/commands/client.py +100 -0
  32. mcp_proxy_adapter/cli/commands/config_generate.py +21 -0
  33. mcp_proxy_adapter/cli/commands/config_validate.py +36 -0
  34. mcp_proxy_adapter/cli/commands/generate.py +259 -0
  35. mcp_proxy_adapter/cli/commands/server.py +174 -0
  36. mcp_proxy_adapter/cli/commands/sets.py +128 -0
  37. mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
  38. mcp_proxy_adapter/cli/examples/__init__.py +8 -0
  39. mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
  40. mcp_proxy_adapter/cli/examples/https_token.py +96 -0
  41. mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
  42. mcp_proxy_adapter/cli/main.py +63 -0
  43. mcp_proxy_adapter/cli/parser.py +324 -0
  44. mcp_proxy_adapter/cli/validators.py +231 -0
  45. mcp_proxy_adapter/client/jsonrpc_client.py +406 -0
  46. mcp_proxy_adapter/client/proxy.py +45 -0
  47. mcp_proxy_adapter/commands/__init__.py +44 -28
  48. mcp_proxy_adapter/commands/auth_validation_command.py +7 -344
  49. mcp_proxy_adapter/commands/base.py +19 -43
  50. mcp_proxy_adapter/commands/builtin_commands.py +0 -75
  51. mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
  52. mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
  53. mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
  54. mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
  55. mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
  56. mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
  57. mcp_proxy_adapter/commands/catalog_manager.py +58 -928
  58. mcp_proxy_adapter/commands/cert_monitor_command.py +0 -88
  59. mcp_proxy_adapter/commands/certificate_management_command.py +0 -45
  60. mcp_proxy_adapter/commands/command_registry.py +172 -904
  61. mcp_proxy_adapter/commands/config_command.py +0 -28
  62. mcp_proxy_adapter/commands/dependency_container.py +1 -70
  63. mcp_proxy_adapter/commands/dependency_manager.py +0 -128
  64. mcp_proxy_adapter/commands/echo_command.py +0 -34
  65. mcp_proxy_adapter/commands/health_command.py +0 -3
  66. mcp_proxy_adapter/commands/help_command.py +0 -159
  67. mcp_proxy_adapter/commands/hooks.py +0 -137
  68. mcp_proxy_adapter/commands/key_management_command.py +0 -25
  69. mcp_proxy_adapter/commands/load_command.py +7 -78
  70. mcp_proxy_adapter/commands/plugins_command.py +0 -16
  71. mcp_proxy_adapter/commands/protocol_management_command.py +0 -28
  72. mcp_proxy_adapter/commands/proxy_registration_command.py +0 -88
  73. mcp_proxy_adapter/commands/queue_commands.py +750 -0
  74. mcp_proxy_adapter/commands/registration_status_command.py +0 -43
  75. mcp_proxy_adapter/commands/registry/__init__.py +18 -0
  76. mcp_proxy_adapter/commands/registry/command_info.py +103 -0
  77. mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
  78. mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
  79. mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
  80. mcp_proxy_adapter/commands/reload_command.py +0 -80
  81. mcp_proxy_adapter/commands/result.py +25 -77
  82. mcp_proxy_adapter/commands/role_test_command.py +0 -44
  83. mcp_proxy_adapter/commands/roles_management_command.py +0 -199
  84. mcp_proxy_adapter/commands/security_command.py +0 -30
  85. mcp_proxy_adapter/commands/settings_command.py +0 -68
  86. mcp_proxy_adapter/commands/ssl_setup_command.py +0 -42
  87. mcp_proxy_adapter/commands/token_management_command.py +0 -1
  88. mcp_proxy_adapter/commands/transport_management_command.py +0 -20
  89. mcp_proxy_adapter/commands/unload_command.py +0 -71
  90. mcp_proxy_adapter/config.py +15 -626
  91. mcp_proxy_adapter/core/__init__.py +5 -39
  92. mcp_proxy_adapter/core/app_factory.py +14 -36
  93. mcp_proxy_adapter/core/app_runner.py +0 -27
  94. mcp_proxy_adapter/core/auth_validator.py +1 -93
  95. mcp_proxy_adapter/core/certificate/__init__.py +20 -0
  96. mcp_proxy_adapter/core/certificate/certificate_creator.py +371 -0
  97. mcp_proxy_adapter/core/certificate/certificate_extractor.py +183 -0
  98. mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
  99. mcp_proxy_adapter/core/certificate/certificate_validator.py +110 -0
  100. mcp_proxy_adapter/core/certificate/ssl_context_manager.py +70 -0
  101. mcp_proxy_adapter/core/certificate_utils.py +64 -903
  102. mcp_proxy_adapter/core/client.py +10 -9
  103. mcp_proxy_adapter/core/client_manager.py +0 -19
  104. mcp_proxy_adapter/core/client_security.py +0 -2
  105. mcp_proxy_adapter/core/config/__init__.py +18 -0
  106. mcp_proxy_adapter/core/config/config.py +195 -0
  107. mcp_proxy_adapter/core/config/config_factory.py +22 -0
  108. mcp_proxy_adapter/core/config/config_loader.py +66 -0
  109. mcp_proxy_adapter/core/config/feature_manager.py +31 -0
  110. mcp_proxy_adapter/core/config/simple_config.py +112 -0
  111. mcp_proxy_adapter/core/config/simple_config_generator.py +50 -0
  112. mcp_proxy_adapter/core/config/simple_config_validator.py +96 -0
  113. mcp_proxy_adapter/core/config_converter.py +0 -186
  114. mcp_proxy_adapter/core/config_validator.py +96 -1238
  115. mcp_proxy_adapter/core/errors.py +7 -42
  116. mcp_proxy_adapter/core/job_manager.py +54 -0
  117. mcp_proxy_adapter/core/logging.py +2 -22
  118. mcp_proxy_adapter/core/mtls_asgi.py +0 -20
  119. mcp_proxy_adapter/core/mtls_asgi_app.py +0 -12
  120. mcp_proxy_adapter/core/mtls_proxy.py +0 -80
  121. mcp_proxy_adapter/core/mtls_server.py +3 -173
  122. mcp_proxy_adapter/core/protocol_manager.py +1 -191
  123. mcp_proxy_adapter/core/proxy/__init__.py +22 -0
  124. mcp_proxy_adapter/core/proxy/auth_manager.py +27 -0
  125. mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +137 -0
  126. mcp_proxy_adapter/core/proxy/registration_client.py +60 -0
  127. mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
  128. mcp_proxy_adapter/core/proxy_client.py +0 -1
  129. mcp_proxy_adapter/core/proxy_registration.py +36 -913
  130. mcp_proxy_adapter/core/role_utils.py +0 -308
  131. mcp_proxy_adapter/core/security_adapter.py +1 -36
  132. mcp_proxy_adapter/core/security_factory.py +1 -150
  133. mcp_proxy_adapter/core/security_integration.py +0 -33
  134. mcp_proxy_adapter/core/server_adapter.py +1 -40
  135. mcp_proxy_adapter/core/server_engine.py +2 -173
  136. mcp_proxy_adapter/core/settings.py +0 -127
  137. mcp_proxy_adapter/core/signal_handler.py +0 -65
  138. mcp_proxy_adapter/core/ssl_utils.py +19 -137
  139. mcp_proxy_adapter/core/transport_manager.py +0 -151
  140. mcp_proxy_adapter/core/unified_config_adapter.py +1 -193
  141. mcp_proxy_adapter/core/utils.py +1 -182
  142. mcp_proxy_adapter/core/validation/__init__.py +21 -0
  143. mcp_proxy_adapter/core/validation/config_validator.py +211 -0
  144. mcp_proxy_adapter/core/validation/file_validator.py +73 -0
  145. mcp_proxy_adapter/core/validation/protocol_validator.py +191 -0
  146. mcp_proxy_adapter/core/validation/security_validator.py +58 -0
  147. mcp_proxy_adapter/core/validation/validation_result.py +27 -0
  148. mcp_proxy_adapter/custom_openapi.py +33 -652
  149. mcp_proxy_adapter/examples/bugfix_certificate_config.py +0 -23
  150. mcp_proxy_adapter/examples/check_config.py +0 -2
  151. mcp_proxy_adapter/examples/client_usage_example.py +164 -0
  152. mcp_proxy_adapter/examples/config_builder.py +13 -2
  153. mcp_proxy_adapter/examples/config_cli.py +0 -1
  154. mcp_proxy_adapter/examples/create_test_configs.py +0 -46
  155. mcp_proxy_adapter/examples/debug_request_state.py +0 -1
  156. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -47
  157. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -45
  158. mcp_proxy_adapter/examples/full_application/commands/echo_command.py +0 -12
  159. mcp_proxy_adapter/examples/full_application/commands/help_command.py +0 -12
  160. mcp_proxy_adapter/examples/full_application/commands/list_command.py +0 -7
  161. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +0 -2
  162. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -59
  163. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -54
  164. mcp_proxy_adapter/examples/full_application/main.py +186 -150
  165. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +0 -107
  166. mcp_proxy_adapter/examples/full_application/test_minimal_server.py +0 -24
  167. mcp_proxy_adapter/examples/full_application/test_server.py +0 -58
  168. mcp_proxy_adapter/examples/generate_config.py +65 -11
  169. mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
  170. mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
  171. mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
  172. mcp_proxy_adapter/examples/queue_server_example.py +85 -0
  173. mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
  174. mcp_proxy_adapter/examples/required_certificates.py +0 -2
  175. mcp_proxy_adapter/examples/run_full_test_suite.py +0 -29
  176. mcp_proxy_adapter/examples/run_proxy_server.py +31 -71
  177. mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -27
  178. mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
  179. mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
  180. mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
  181. mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
  182. mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
  183. mcp_proxy_adapter/examples/security_test_client.py +24 -1075
  184. mcp_proxy_adapter/examples/setup/__init__.py +24 -0
  185. mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
  186. mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
  187. mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
  188. mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
  189. mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
  190. mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
  191. mcp_proxy_adapter/examples/setup_test_environment.py +133 -1425
  192. mcp_proxy_adapter/examples/test_config.py +0 -3
  193. mcp_proxy_adapter/examples/test_config_builder.py +25 -405
  194. mcp_proxy_adapter/examples/test_examples.py +0 -1
  195. mcp_proxy_adapter/examples/test_framework_complete.py +0 -2
  196. mcp_proxy_adapter/examples/test_mcp_server.py +0 -1
  197. mcp_proxy_adapter/examples/test_protocol_examples.py +0 -1
  198. mcp_proxy_adapter/examples/universal_client.py +0 -6
  199. mcp_proxy_adapter/examples/update_config_certificates.py +0 -1
  200. mcp_proxy_adapter/examples/validate_generator_compatibility.py +0 -1
  201. mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +0 -187
  202. mcp_proxy_adapter/integrations/__init__.py +25 -0
  203. mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
  204. mcp_proxy_adapter/main.py +70 -62
  205. mcp_proxy_adapter/openapi.py +0 -22
  206. mcp_proxy_adapter/version.py +1 -1
  207. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/METADATA +2 -1
  208. mcp_proxy_adapter-6.9.30.dist-info/RECORD +235 -0
  209. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/entry_points.txt +1 -1
  210. mcp_proxy_adapter-6.9.28.dist-info/RECORD +0 -149
  211. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/WHEEL +0 -0
  212. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/top_level.txt +0 -0
@@ -1,56 +1,26 @@
1
1
  """
2
- Certificate Utilities
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
3
4
 
4
- This module provides utilities for working with certificates including creation,
5
- validation, and role extraction. Integrates with mcp_security_framework.
6
-
7
- Author: MCP Proxy Adapter Team
8
- Version: 1.0.0
5
+ Main certificate utilities for MCP Proxy Adapter.
9
6
  """
10
7
 
11
8
  import logging
12
- import os
13
- import ipaddress
14
- from datetime import datetime, timedelta, timezone
15
- from typing import Dict, List, Optional, Any
9
+ from datetime import datetime
16
10
  from pathlib import Path
11
+ from typing import Dict, List, Optional, Any
17
12
 
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
-
36
- SECURITY_FRAMEWORK_AVAILABLE = True
37
- except ImportError:
38
- SECURITY_FRAMEWORK_AVAILABLE = False
39
- # Fallback to cryptography if mcp_security_framework is not available
40
- from cryptography import x509
41
- from cryptography.hazmat.primitives import hashes, serialization
42
- from cryptography.hazmat.primitives.asymmetric import rsa
43
- from cryptography.x509.oid import NameOID
44
-
45
- from .auth_validator import AuthValidator
46
- from .role_utils import RoleUtils
13
+ from .certificate_creator import CertificateCreator
14
+ from .certificate_validator import CertificateValidator
15
+ from .certificate_extractor import CertificateExtractor
16
+ from .ssl_context_manager import SSLContextManager
47
17
 
48
18
  logger = logging.getLogger(__name__)
49
19
 
50
20
 
51
21
  class CertificateUtils:
52
22
  """
53
- Utilities for working with certificates.
23
+ Main utilities for working with certificates.
54
24
 
55
25
  Provides methods for creating CA, server, and client certificates,
56
26
  as well as validation and role extraction using mcp_security_framework.
@@ -73,7 +43,7 @@ class CertificateUtils:
73
43
  key_size: int = DEFAULT_KEY_SIZE,
74
44
  ) -> Dict[str, str]:
75
45
  """
76
- Create a CA certificate and private key using mcp_security_framework.
46
+ Create a CA certificate and private key.
77
47
 
78
48
  Args:
79
49
  common_name: Common name for the CA certificate
@@ -83,542 +53,75 @@ class CertificateUtils:
83
53
 
84
54
  Returns:
85
55
  Dictionary with paths to created files
86
-
87
- Raises:
88
- ValueError: If parameters are invalid
89
- OSError: If files cannot be created
90
56
  """
91
- if not SECURITY_FRAMEWORK_AVAILABLE:
92
- get_global_logger().warning(
93
- "mcp_security_framework not available, using fallback method"
94
- )
95
- return CertificateUtils._create_ca_certificate_fallback(
96
- common_name, output_dir, validity_days, key_size
97
- )
98
-
99
- try:
100
- # Validate parameters
101
- if not common_name or not common_name.strip():
102
- raise ValueError("Common name cannot be empty")
103
-
104
- if validity_days <= 0:
105
- raise ValueError("Validity days must be positive")
106
-
107
- if key_size < 1024:
108
- raise ValueError("Key size must be at least 1024 bits")
109
-
110
- # Create output directory if it doesn't exist
111
- Path(output_dir).mkdir(parents=True, exist_ok=True)
112
-
113
- # Configure CA using mcp_security_framework
114
- ca_config = CAConfig(
115
- common_name=common_name,
116
- organization="MCP Proxy Adapter CA",
117
- organizational_unit="Certificate Authority",
118
- country="US",
119
- state="Default State",
120
- locality="Default City",
121
- validity_days=validity_days,
122
- key_size=key_size,
123
- key_type="RSA",
124
- )
125
-
126
- # Create certificate manager
127
- cert_config = CertificateConfig(
128
- output_dir=output_dir,
129
- ca_cert_path=str(Path(output_dir) / f"{common_name}.crt"),
130
- ca_key_path=str(Path(output_dir) / f"{common_name}.key"),
131
- )
132
-
133
- cert_manager = CertificateManager(cert_config)
134
-
135
- # Generate CA certificate
136
- ca_pair = cert_manager.create_ca_certificate(ca_config)
137
-
138
- return {
139
- "cert_path": str(ca_pair.cert_path),
140
- "key_path": str(ca_pair.key_path),
141
- }
142
-
143
- except Exception as e:
144
- get_global_logger().error(f"Failed to create CA certificate: {e}")
145
- raise
146
-
147
- @staticmethod
148
- def _create_ca_certificate_fallback(
149
- common_name: str, output_dir: str, validity_days: int, key_size: int
150
- ) -> Dict[str, str]:
151
- """Fallback method using cryptography directly."""
152
- try:
153
- # Validate parameters
154
- if not common_name or not common_name.strip():
155
- raise ValueError("Common name cannot be empty")
156
-
157
- if validity_days <= 0:
158
- raise ValueError("Validity days must be positive")
159
-
160
- if key_size < 1024:
161
- raise ValueError("Key size must be at least 1024 bits")
162
-
163
- # Create output directory if it doesn't exist
164
- Path(output_dir).mkdir(parents=True, exist_ok=True)
165
-
166
- # Generate private key
167
- private_key = rsa.generate_private_key(
168
- public_exponent=65537, key_size=key_size
169
- )
170
-
171
- # Create certificate subject
172
- subject = issuer = x509.Name(
173
- [
174
- x509.NameAttribute(NameOID.COMMON_NAME, common_name),
175
- x509.NameAttribute(
176
- NameOID.ORGANIZATION_NAME, "MCP Proxy Adapter CA"
177
- ),
178
- x509.NameAttribute(
179
- NameOID.ORGANIZATIONAL_UNIT_NAME, "Certificate Authority"
180
- ),
181
- x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
182
- ]
183
- )
184
-
185
- # Create certificate
186
- cert = (
187
- x509.CertificateBuilder()
188
- .subject_name(subject)
189
- .issuer_name(issuer)
190
- .public_key(private_key.public_key())
191
- .serial_number(x509.random_serial_number())
192
- .not_valid_before(datetime.now(timezone.utc))
193
- .not_valid_after(
194
- datetime.now(timezone.utc) + timedelta(days=validity_days)
195
- )
196
- .add_extension(
197
- x509.BasicConstraints(ca=True, path_length=None), critical=True
198
- )
199
- .add_extension(
200
- x509.KeyUsage(
201
- key_cert_sign=True,
202
- crl_sign=True,
203
- digital_signature=True,
204
- key_encipherment=False,
205
- data_encipherment=False,
206
- key_agreement=False,
207
- encipher_only=False,
208
- decipher_only=False,
209
- ),
210
- critical=True,
211
- )
212
- .sign(private_key, hashes.SHA256())
213
- )
214
-
215
- # Save certificate and key
216
- cert_path = Path(output_dir) / f"{common_name}.crt"
217
- key_path = Path(output_dir) / f"{common_name}.key"
218
-
219
- with open(cert_path, "wb") as f:
220
- f.write(cert.public_bytes(serialization.Encoding.PEM))
221
-
222
- with open(key_path, "wb") as f:
223
- f.write(
224
- private_key.private_bytes(
225
- encoding=serialization.Encoding.PEM,
226
- format=serialization.PrivateFormat.PKCS8,
227
- encryption_algorithm=serialization.NoEncryption(),
228
- )
229
- )
230
-
231
- return {"cert_path": str(cert_path), "key_path": str(key_path)}
232
-
233
- except Exception as e:
234
- get_global_logger().error(f"Failed to create CA certificate (fallback): {e}")
235
- raise
57
+ return CertificateCreator.create_ca_certificate(
58
+ common_name, output_dir, validity_days, key_size
59
+ )
236
60
 
237
61
  @staticmethod
238
62
  def create_server_certificate(
239
63
  common_name: str,
240
- roles: List[str],
64
+ output_dir: str,
241
65
  ca_cert_path: str,
242
66
  ca_key_path: str,
243
- output_dir: str,
244
67
  validity_days: int = DEFAULT_VALIDITY_DAYS,
245
68
  key_size: int = DEFAULT_KEY_SIZE,
69
+ san_dns: Optional[List[str]] = None,
70
+ san_ip: Optional[List[str]] = None,
246
71
  ) -> Dict[str, str]:
247
72
  """
248
73
  Create a server certificate signed by CA.
249
74
 
250
75
  Args:
251
76
  common_name: Common name for the server certificate
252
- roles: List of roles to include in certificate
77
+ output_dir: Directory to save certificate and key files
253
78
  ca_cert_path: Path to CA certificate
254
79
  ca_key_path: Path to CA private key
255
- output_dir: Directory to save certificate and key files
256
80
  validity_days: Certificate validity period in days
257
81
  key_size: RSA key size in bits
82
+ san_dns: List of DNS names for SAN extension
83
+ san_ip: List of IP addresses for SAN extension
258
84
 
259
85
  Returns:
260
86
  Dictionary with paths to created files
261
-
262
- Raises:
263
- ValueError: If parameters are invalid
264
- FileNotFoundError: If CA files not found
265
- OSError: If files cannot be created
266
87
  """
267
- try:
268
- # Validate parameters
269
- if not common_name or not common_name.strip():
270
- raise ValueError("Common name cannot be empty")
271
-
272
- if not roles:
273
- roles = ["server"]
274
-
275
- if not Path(ca_cert_path).exists():
276
- raise FileNotFoundError(f"CA certificate not found: {ca_cert_path}")
277
-
278
- if not Path(ca_key_path).exists():
279
- raise FileNotFoundError(f"CA key not found: {ca_key_path}")
280
-
281
- # Create output directory if it doesn't exist
282
- Path(output_dir).mkdir(parents=True, exist_ok=True)
283
-
284
- # Load CA certificate and key
285
- with open(ca_cert_path, "rb") as f:
286
- ca_cert = x509.load_pem_x509_certificate(f.read())
287
-
288
- with open(ca_key_path, "rb") as f:
289
- ca_key = serialization.load_pem_private_key(f.read(), password=None)
290
-
291
- # Generate server private key
292
- private_key = rsa.generate_private_key(
293
- public_exponent=65537, key_size=key_size
294
- )
295
-
296
- # Create certificate subject
297
- subject = x509.Name(
298
- [
299
- x509.NameAttribute(NameOID.COMMON_NAME, common_name),
300
- x509.NameAttribute(NameOID.ORGANIZATION_NAME, "MCP Proxy Adapter"),
301
- x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "Server"),
302
- x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
303
- ]
304
- )
305
-
306
- # Create certificate
307
- cert_builder = (
308
- x509.CertificateBuilder()
309
- .subject_name(subject)
310
- .issuer_name(ca_cert.subject)
311
- .public_key(private_key.public_key())
312
- .serial_number(x509.random_serial_number())
313
- .not_valid_before(datetime.now(timezone.utc))
314
- .not_valid_after(
315
- datetime.now(timezone.utc) + timedelta(days=validity_days)
316
- )
317
- .add_extension(
318
- x509.BasicConstraints(ca=False, path_length=None), critical=True
319
- )
320
- .add_extension(
321
- x509.KeyUsage(
322
- key_cert_sign=False,
323
- crl_sign=False,
324
- digital_signature=True,
325
- key_encipherment=True,
326
- data_encipherment=False,
327
- key_agreement=False,
328
- encipher_only=False,
329
- decipher_only=False,
330
- content_commitment=False,
331
- ),
332
- critical=True,
333
- )
334
- .add_extension(
335
- x509.SubjectKeyIdentifier.from_public_key(private_key.public_key()),
336
- critical=False,
337
- )
338
- .add_extension(
339
- x509.AuthorityKeyIdentifier.from_issuer_public_key(
340
- ca_key.public_key()
341
- ),
342
- critical=False,
343
- )
344
- .add_extension(
345
- x509.SubjectAlternativeName(
346
- [
347
- x509.DNSName(common_name),
348
- x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")),
349
- x509.IPAddress(ipaddress.IPv6Address("::1")),
350
- ]
351
- ),
352
- critical=False,
353
- )
354
- )
355
-
356
- # Add roles extension
357
- if roles:
358
- roles_data = ",".join(roles).encode("utf-8")
359
- roles_oid = x509.ObjectIdentifier(CertificateUtils.ROLE_EXTENSION_OID)
360
- cert_builder = cert_builder.add_extension(
361
- x509.UnrecognizedExtension(roles_oid, roles_data), critical=False
362
- )
363
-
364
- cert = cert_builder.sign(ca_key, hashes.SHA256())
365
-
366
- # Save certificate and key
367
- cert_path = os.path.join(output_dir, "server.crt")
368
- key_path = os.path.join(output_dir, "server.key")
369
-
370
- with open(cert_path, "wb") as f:
371
- f.write(cert.public_bytes(serialization.Encoding.PEM))
372
-
373
- with open(key_path, "wb") as f:
374
- f.write(
375
- private_key.private_bytes(
376
- encoding=serialization.Encoding.PEM,
377
- format=serialization.PrivateFormat.PKCS8,
378
- encryption_algorithm=serialization.NoEncryption(),
379
- )
380
- )
381
-
382
- get_global_logger().info(f"Server certificate created: {cert_path}")
383
-
384
- return {
385
- "cert_path": cert_path,
386
- "key_path": key_path,
387
- "common_name": common_name,
388
- "roles": roles,
389
- "validity_days": validity_days,
390
- }
391
-
392
- except Exception as e:
393
- get_global_logger().error(f"Failed to create server certificate: {e}")
394
- raise
88
+ return CertificateCreator.create_server_certificate(
89
+ common_name, output_dir, ca_cert_path, ca_key_path,
90
+ validity_days, key_size, san_dns, san_ip
91
+ )
395
92
 
396
93
  @staticmethod
397
94
  def create_client_certificate(
398
95
  common_name: str,
96
+ output_dir: str,
399
97
  ca_cert_path: str,
400
98
  ca_key_path: str,
401
- output_dir: str,
402
- roles: List[str] = None,
403
99
  validity_days: int = DEFAULT_VALIDITY_DAYS,
404
100
  key_size: int = DEFAULT_KEY_SIZE,
405
101
  ) -> Dict[str, str]:
406
102
  """
407
- Create a client certificate and private key using mcp_security_framework.
103
+ Create a client certificate signed by CA.
408
104
 
409
105
  Args:
410
106
  common_name: Common name for the client certificate
107
+ output_dir: Directory to save certificate and key files
411
108
  ca_cert_path: Path to CA certificate
412
109
  ca_key_path: Path to CA private key
413
- output_dir: Directory to save certificate and key files
414
- roles: List of roles to include in certificate
415
110
  validity_days: Certificate validity period in days
416
111
  key_size: RSA key size in bits
417
112
 
418
113
  Returns:
419
114
  Dictionary with paths to created files
420
-
421
- Raises:
422
- ValueError: If parameters are invalid
423
- OSError: If files cannot be created
424
115
  """
425
- if not SECURITY_FRAMEWORK_AVAILABLE:
426
- get_global_logger().warning(
427
- "mcp_security_framework not available, using fallback method"
428
- )
429
- return CertificateUtils._create_client_certificate_fallback(
430
- common_name,
431
- ca_cert_path,
432
- ca_key_path,
433
- output_dir,
434
- roles,
435
- validity_days,
436
- key_size,
437
- )
438
-
439
- try:
440
- # Validate parameters
441
- if not common_name or not common_name.strip():
442
- raise ValueError("Common name cannot be empty")
443
-
444
- if not Path(ca_cert_path).exists():
445
- raise ValueError(f"CA certificate not found: {ca_cert_path}")
446
-
447
- if not Path(ca_key_path).exists():
448
- raise ValueError(f"CA private key not found: {ca_key_path}")
449
-
450
- if validity_days <= 0:
451
- raise ValueError("Validity days must be positive")
452
-
453
- if key_size < 1024:
454
- raise ValueError("Key size must be at least 1024 bits")
455
-
456
- # Create output directory if it doesn't exist
457
- Path(output_dir).mkdir(parents=True, exist_ok=True)
458
-
459
- # Configure client certificate using mcp_security_framework
460
- client_config = ClientCertConfig(
461
- common_name=common_name,
462
- organization="MCP Proxy Adapter Client",
463
- organizational_unit="Client Certificates",
464
- country="US",
465
- state="Default State",
466
- locality="Default City",
467
- validity_days=validity_days,
468
- key_size=key_size,
469
- key_type="RSA",
470
- roles=roles or [],
471
- permissions=[], # Permissions can be added later if needed
472
- )
473
-
474
- # Create certificate manager
475
- cert_config = CertificateConfig(
476
- output_dir=output_dir,
477
- ca_cert_path=ca_cert_path,
478
- ca_key_path=ca_key_path,
479
- )
480
-
481
- cert_manager = CertificateManager(cert_config)
482
-
483
- # Generate client certificate
484
- client_pair = cert_manager.create_client_certificate(client_config)
485
-
486
- return {
487
- "cert_path": str(client_pair.cert_path),
488
- "key_path": str(client_pair.key_path),
489
- }
490
-
491
- except Exception as e:
492
- get_global_logger().error(f"Failed to create client certificate: {e}")
493
- raise
494
-
495
- @staticmethod
496
- def _create_client_certificate_fallback(
497
- common_name: str,
498
- ca_cert_path: str,
499
- ca_key_path: str,
500
- output_dir: str,
501
- roles: List[str] = None,
502
- validity_days: int = DEFAULT_VALIDITY_DAYS,
503
- key_size: int = DEFAULT_KEY_SIZE,
504
- ) -> Dict[str, str]:
505
- """Fallback method using cryptography directly."""
506
- try:
507
- # Validate parameters
508
- if not common_name or not common_name.strip():
509
- raise ValueError("Common name cannot be empty")
510
-
511
- if not Path(ca_cert_path).exists():
512
- raise ValueError(f"CA certificate not found: {ca_cert_path}")
513
-
514
- if not Path(ca_key_path).exists():
515
- raise ValueError(f"CA private key not found: {ca_key_path}")
516
-
517
- if validity_days <= 0:
518
- raise ValueError("Validity days must be positive")
519
-
520
- if key_size < 1024:
521
- raise ValueError("Key size must be at least 1024 bits")
522
-
523
- # Create output directory if it doesn't exist
524
- Path(output_dir).mkdir(parents=True, exist_ok=True)
525
-
526
- # Load CA certificate and key
527
- with open(ca_cert_path, "rb") as f:
528
- ca_cert = x509.load_pem_x509_certificate(f.read())
529
-
530
- with open(ca_key_path, "rb") as f:
531
- ca_key = serialization.load_pem_private_key(f.read(), password=None)
532
-
533
- # Generate client private key
534
- private_key = rsa.generate_private_key(
535
- public_exponent=65537, key_size=key_size
536
- )
537
-
538
- # Create certificate subject
539
- subject = x509.Name(
540
- [
541
- x509.NameAttribute(NameOID.COMMON_NAME, common_name),
542
- x509.NameAttribute(
543
- NameOID.ORGANIZATION_NAME, "MCP Proxy Adapter Client"
544
- ),
545
- x509.NameAttribute(
546
- NameOID.ORGANIZATIONAL_UNIT_NAME, "Client Certificates"
547
- ),
548
- x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
549
- ]
550
- )
551
-
552
- # Create certificate
553
- cert_builder = (
554
- x509.CertificateBuilder()
555
- .subject_name(subject)
556
- .issuer_name(ca_cert.subject)
557
- .public_key(private_key.public_key())
558
- .serial_number(x509.random_serial_number())
559
- .not_valid_before(datetime.now(timezone.utc))
560
- .not_valid_after(
561
- datetime.now(timezone.utc) + timedelta(days=validity_days)
562
- )
563
- .add_extension(
564
- x509.BasicConstraints(ca=False, path_length=None), critical=True
565
- )
566
- .add_extension(
567
- x509.KeyUsage(
568
- key_cert_sign=False,
569
- crl_sign=False,
570
- digital_signature=True,
571
- key_encipherment=True,
572
- data_encipherment=False,
573
- key_agreement=False,
574
- encipher_only=False,
575
- decipher_only=False,
576
- ),
577
- critical=True,
578
- )
579
- .add_extension(
580
- x509.ExtendedKeyUsage([x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH]),
581
- critical=False,
582
- )
583
- )
584
-
585
- # Add roles extension if provided
586
- if roles:
587
- roles_str = ",".join(roles)
588
- roles_oid = x509.ObjectIdentifier(CertificateUtils.ROLE_EXTENSION_OID)
589
- cert_builder = cert_builder.add_extension(
590
- x509.UnrecognizedExtension(roles_oid, roles_str.encode()),
591
- critical=False,
592
- )
593
-
594
- cert = cert_builder.sign(ca_key, hashes.SHA256())
595
-
596
- # Save certificate and key
597
- cert_path = Path(output_dir) / f"{common_name}.crt"
598
- key_path = Path(output_dir) / f"{common_name}.key"
599
-
600
- with open(cert_path, "wb") as f:
601
- f.write(cert.public_bytes(serialization.Encoding.PEM))
602
-
603
- with open(key_path, "wb") as f:
604
- f.write(
605
- private_key.private_bytes(
606
- encoding=serialization.Encoding.PEM,
607
- format=serialization.PrivateFormat.PKCS8,
608
- encryption_algorithm=serialization.NoEncryption(),
609
- )
610
- )
611
-
612
- return {"cert_path": str(cert_path), "key_path": str(key_path)}
613
-
614
- except Exception as e:
615
- get_global_logger().error(f"Failed to create client certificate (fallback): {e}")
616
- raise
116
+ return CertificateCreator.create_client_certificate(
117
+ common_name, output_dir, ca_cert_path, ca_key_path,
118
+ validity_days, key_size
119
+ )
617
120
 
618
121
  @staticmethod
619
122
  def extract_roles_from_certificate(cert_path: str) -> List[str]:
620
123
  """
621
- Extract roles from certificate using mcp_security_framework.
124
+ Extract roles from certificate.
622
125
 
623
126
  Args:
624
127
  cert_path: Path to certificate file
@@ -626,22 +129,12 @@ class CertificateUtils:
626
129
  Returns:
627
130
  List of roles found in certificate
628
131
  """
629
- if not SECURITY_FRAMEWORK_AVAILABLE:
630
- get_global_logger().warning(
631
- "mcp_security_framework not available, using fallback method"
632
- )
633
- return RoleUtils.extract_roles_from_certificate(cert_path)
634
-
635
- try:
636
- return extract_roles_from_certificate(cert_path)
637
- except Exception as e:
638
- get_global_logger().error(f"Failed to extract roles from certificate: {e}")
639
- return []
132
+ return CertificateExtractor.extract_roles_from_certificate(cert_path)
640
133
 
641
134
  @staticmethod
642
135
  def extract_roles_from_certificate_object(cert) -> List[str]:
643
136
  """
644
- Extract roles from certificate object using mcp_security_framework.
137
+ Extract roles from certificate object.
645
138
 
646
139
  Args:
647
140
  cert: Certificate object
@@ -649,24 +142,12 @@ class CertificateUtils:
649
142
  Returns:
650
143
  List of roles found in certificate
651
144
  """
652
- if not SECURITY_FRAMEWORK_AVAILABLE:
653
- get_global_logger().warning(
654
- "mcp_security_framework not available, using fallback method"
655
- )
656
- return RoleUtils.extract_roles_from_certificate_object(cert)
657
-
658
- try:
659
- # Convert certificate object to PEM format for mcp_security_framework
660
- cert_pem = cert.public_bytes(serialization.Encoding.PEM)
661
- return extract_roles_from_certificate(cert_pem)
662
- except Exception as e:
663
- get_global_logger().error(f"Failed to extract roles from certificate object: {e}")
664
- return []
145
+ return CertificateExtractor.extract_roles_from_certificate_object(cert)
665
146
 
666
147
  @staticmethod
667
148
  def extract_permissions_from_certificate(cert_path: str) -> List[str]:
668
149
  """
669
- Extract permissions from certificate using mcp_security_framework.
150
+ Extract permissions from certificate.
670
151
 
671
152
  Args:
672
153
  cert_path: Path to certificate file
@@ -674,119 +155,47 @@ class CertificateUtils:
674
155
  Returns:
675
156
  List of permissions found in certificate
676
157
  """
677
- if not SECURITY_FRAMEWORK_AVAILABLE:
678
- get_global_logger().warning(
679
- "mcp_security_framework not available, permissions extraction not supported"
680
- )
681
- return []
682
-
683
- try:
684
- return extract_permissions_from_certificate(cert_path)
685
- except Exception as e:
686
- get_global_logger().error(f"Failed to extract permissions from certificate: {e}")
687
- return []
158
+ return CertificateExtractor.extract_permissions_from_certificate(cert_path)
688
159
 
689
160
  @staticmethod
690
161
  def validate_certificate_chain(cert_path: str, ca_cert_path: str) -> bool:
691
162
  """
692
- Validate certificate chain using mcp_security_framework.
163
+ Validate certificate chain.
693
164
 
694
165
  Args:
695
- cert_path: Path to certificate to validate
696
- ca_cert_path: Path to CA certificate
166
+ cert_path: Path to certificate file
167
+ ca_cert_path: Path to CA certificate file
697
168
 
698
169
  Returns:
699
- True if chain is valid, False otherwise
170
+ True if certificate chain is valid, False otherwise
700
171
  """
701
- if not SECURITY_FRAMEWORK_AVAILABLE:
702
- get_global_logger().warning(
703
- "mcp_security_framework not available, using fallback validation"
704
- )
705
- return CertificateUtils._validate_certificate_chain_fallback(
706
- cert_path, ca_cert_path
707
- )
708
-
709
- try:
710
- return validate_certificate_chain(cert_path, ca_cert_path)
711
- except Exception as e:
712
- get_global_logger().error(f"Failed to validate certificate chain: {e}")
713
- return False
714
-
715
- @staticmethod
716
- def _validate_certificate_chain_fallback(cert_path: str, ca_cert_path: str) -> bool:
717
- """Fallback certificate chain validation using cryptography."""
718
- try:
719
- # Load certificates
720
- with open(cert_path, "rb") as f:
721
- cert = x509.load_pem_x509_certificate(f.read())
722
-
723
- with open(ca_cert_path, "rb") as f:
724
- ca_cert = x509.load_pem_x509_certificate(f.read())
725
-
726
- # Simple validation: check if certificate is issued by CA
727
- return cert.issuer == ca_cert.subject
728
-
729
- except Exception as e:
730
- get_global_logger().error(f"Failed to validate certificate chain (fallback): {e}")
731
- return False
172
+ return CertificateValidator.validate_certificate_chain(cert_path, ca_cert_path)
732
173
 
733
174
  @staticmethod
734
175
  def get_certificate_expiry(cert_path: str) -> Optional[datetime]:
735
176
  """
736
- Get certificate expiry date using mcp_security_framework.
177
+ Get certificate expiry date.
737
178
 
738
179
  Args:
739
180
  cert_path: Path to certificate file
740
181
 
741
182
  Returns:
742
- Certificate expiry date or None if not available
183
+ Certificate expiry date or None if error
743
184
  """
744
- if not SECURITY_FRAMEWORK_AVAILABLE:
745
- get_global_logger().warning(
746
- "mcp_security_framework not available, using fallback method"
747
- )
748
- return CertificateUtils._get_certificate_expiry_fallback(cert_path)
749
-
750
- try:
751
- expiry_info = get_certificate_expiry(cert_path)
752
- if isinstance(expiry_info, dict) and "expiry_date" in expiry_info:
753
- return expiry_info["expiry_date"]
754
- return None
755
- except Exception as e:
756
- get_global_logger().error(f"Failed to get certificate expiry: {e}")
757
- return None
758
-
759
- @staticmethod
760
- def _get_certificate_expiry_fallback(cert_path: str) -> Optional[datetime]:
761
- """Fallback method to get certificate expiry using cryptography."""
762
- try:
763
- with open(cert_path, "rb") as f:
764
- cert = x509.load_pem_x509_certificate(f.read())
765
-
766
- return cert.not_valid_after
767
-
768
- except Exception as e:
769
- get_global_logger().error(f"Failed to get certificate expiry (fallback): {e}")
770
- return None
185
+ return CertificateValidator.get_certificate_expiry(cert_path)
771
186
 
772
187
  @staticmethod
773
188
  def validate_certificate(cert_path: str) -> bool:
774
189
  """
775
- Validate certificate using AuthValidator.
190
+ Validate certificate file.
776
191
 
777
192
  Args:
778
- cert_path: Path to certificate to validate
193
+ cert_path: Path to certificate file
779
194
 
780
195
  Returns:
781
196
  True if certificate is valid, False otherwise
782
197
  """
783
- try:
784
- validator = AuthValidator()
785
- result = validator.validate_certificate(cert_path)
786
- return result.is_valid
787
- except Exception as e:
788
- get_global_logger().error(f"Failed to validate certificate: {e}")
789
- return False
198
+ return CertificateValidator.validate_certificate(cert_path)
790
199
 
791
200
  @staticmethod
792
201
  def get_certificate_info(cert_path: str) -> Dict[str, Any]:
@@ -799,260 +208,28 @@ class CertificateUtils:
799
208
  Returns:
800
209
  Dictionary with certificate information
801
210
  """
802
- try:
803
- with open(cert_path, "rb") as f:
804
- cert_data = f.read()
805
-
806
- cert = x509.load_pem_x509_certificate(cert_data)
807
-
808
- # Extract roles
809
- roles = CertificateUtils.extract_roles_from_certificate_object(cert)
810
-
811
- # Convert subject and issuer to dictionaries
812
- subject_dict = {}
813
- for name_attribute in cert.subject:
814
- subject_dict[str(name_attribute.oid)] = str(name_attribute.value)
815
-
816
- issuer_dict = {}
817
- for name_attribute in cert.issuer:
818
- issuer_dict[str(name_attribute.oid)] = str(name_attribute.value)
819
-
820
- return {
821
- "subject": subject_dict,
822
- "issuer": issuer_dict,
823
- "serial_number": str(cert.serial_number),
824
- "not_valid_before": cert.not_valid_before.isoformat(),
825
- "not_valid_after": cert.not_valid_after.isoformat(),
826
- "roles": roles,
827
- "is_ca": (
828
- cert.extensions.get_extension_for_oid(
829
- x509.oid.ExtensionOID.BASIC_CONSTRAINTS
830
- ).value.ca
831
- if cert.extensions.get_extension_for_oid(
832
- x509.oid.ExtensionOID.BASIC_CONSTRAINTS
833
- )
834
- else False
835
- ),
836
- }
837
-
838
- except Exception as e:
839
- get_global_logger().error(f"Failed to get certificate info: {e}")
840
- return {}
841
-
842
- @staticmethod
843
- def generate_private_key(
844
- key_type: str, key_size: int, output_path: str
845
- ) -> Dict[str, Any]:
846
- """
847
- Generate a private key.
848
-
849
- Args:
850
- key_type: Type of key (RSA, ECDSA)
851
- key_size: Key size in bits
852
- output_path: Path to save the private key
853
-
854
- Returns:
855
- Dictionary with generation result
856
- """
857
- try:
858
- # Validate key type
859
- if key_type not in ["RSA", "ECDSA"]:
860
- return {"success": False, "error": "Key type must be RSA or ECDSA"}
861
-
862
- # Validate key size
863
- if key_type == "RSA" and key_size < 1024:
864
- return {
865
- "success": False,
866
- "error": "Key size must be at least 1024 bits",
867
- }
868
-
869
- if key_type == "ECDSA" and key_size not in [256, 384, 521]:
870
- return {
871
- "success": False,
872
- "error": "ECDSA key size must be 256, 384, or 521 bits",
873
- }
874
-
875
- # Create output directory if it doesn't exist
876
- output_dir = os.path.dirname(output_path)
877
- if output_dir:
878
- Path(output_dir).mkdir(parents=True, exist_ok=True)
879
-
880
- # Generate private key
881
- if key_type == "RSA":
882
- private_key = rsa.generate_private_key(
883
- public_exponent=65537, key_size=key_size
884
- )
885
- else: # ECDSA
886
- from cryptography.hazmat.primitives.asymmetric import ec
887
-
888
- if key_size == 256:
889
- curve = ec.SECP256R1()
890
- elif key_size == 384:
891
- curve = ec.SECP384R1()
892
- else: # 521
893
- curve = ec.SECP521R1()
894
-
895
- private_key = ec.generate_private_key(curve)
896
-
897
- # Save private key
898
- with open(output_path, "wb") as f:
899
- f.write(
900
- private_key.private_bytes(
901
- encoding=serialization.Encoding.PEM,
902
- format=serialization.PrivateFormat.PKCS8,
903
- encryption_algorithm=serialization.NoEncryption(),
904
- )
905
- )
906
-
907
- return {
908
- "success": True,
909
- "key_type": key_type,
910
- "key_size": key_size,
911
- "key_path": output_path,
912
- }
913
-
914
- except Exception as e:
915
- get_global_logger().error(f"Failed to generate private key: {e}")
916
- return {"success": False, "error": f"Key generation failed: {str(e)}"}
211
+ return CertificateValidator.get_certificate_info(cert_path)
917
212
 
918
213
  @staticmethod
919
214
  def validate_private_key(key_path: str) -> Dict[str, Any]:
920
215
  """
921
- Validate a private key.
216
+ Validate private key file.
922
217
 
923
218
  Args:
924
219
  key_path: Path to private key file
925
220
 
926
221
  Returns:
927
- Dictionary with validation result
222
+ Dictionary with validation results
928
223
  """
929
- try:
930
- if not os.path.exists(key_path):
931
- return {"success": False, "error": "Key file not found"}
932
-
933
- with open(key_path, "rb") as f:
934
- key_data = f.read()
935
-
936
- # Try to load the private key
937
- try:
938
- private_key = serialization.load_pem_private_key(
939
- key_data, password=None
940
- )
941
-
942
- # Get key info
943
- if isinstance(private_key, rsa.RSAPrivateKey):
944
- key_type = "RSA"
945
- key_size = private_key.key_size
946
- else:
947
- key_type = "ECDSA"
948
- key_size = private_key.key_size
949
-
950
- return {
951
- "success": True,
952
- "key_type": key_type,
953
- "key_size": key_size,
954
- "created_date": datetime.now().isoformat(),
955
- }
956
-
957
- except Exception as e:
958
- return {"success": False, "error": f"Invalid private key: {str(e)}"}
959
-
960
- except Exception as e:
961
- get_global_logger().error(f"Failed to validate private key: {e}")
962
- return {"success": False, "error": f"Key validation failed: {str(e)}"}
963
-
964
- @staticmethod
965
- def create_encrypted_backup(
966
- key_path: str, backup_path: str, password: str
967
- ) -> Dict[str, Any]:
968
- """
969
- Create an encrypted backup of a private key.
970
-
971
- Args:
972
- key_path: Path to private key file
973
- backup_path: Path to save encrypted backup
974
- password: Password for encryption
975
-
976
- Returns:
977
- Dictionary with backup result
978
- """
979
- try:
980
- if not os.path.exists(key_path):
981
- return {"success": False, "error": "Key file not found"}
982
-
983
- # Read the private key
984
- with open(key_path, "rb") as f:
985
- key_data = f.read()
986
-
987
- # Load the private key
988
- private_key = serialization.load_pem_private_key(key_data, password=None)
989
-
990
- # Create encrypted backup
991
- encrypted_key = private_key.private_bytes(
992
- encoding=serialization.Encoding.PEM,
993
- format=serialization.PrivateFormat.PKCS8,
994
- encryption_algorithm=serialization.BestAvailableEncryption(
995
- password.encode()
996
- ),
997
- )
998
-
999
- # Save encrypted backup
1000
- with open(backup_path, "wb") as f:
1001
- f.write(encrypted_key)
1002
-
1003
- return {"success": True, "backup_path": backup_path}
1004
-
1005
- except Exception as e:
1006
- get_global_logger().error(f"Failed to create encrypted backup: {e}")
1007
- return {"success": False, "error": f"Encryption failed: {str(e)}"}
1008
-
1009
- @staticmethod
1010
- def restore_encrypted_backup(
1011
- backup_path: str, key_path: str, password: str
1012
- ) -> Dict[str, Any]:
1013
- """
1014
- Restore a private key from encrypted backup.
1015
-
1016
- Args:
1017
- backup_path: Path to encrypted backup file
1018
- key_path: Path to save restored key
1019
- password: Password for decryption
1020
-
1021
- Returns:
1022
- Dictionary with restore result
1023
- """
1024
- try:
1025
- if not os.path.exists(backup_path):
1026
- return {"success": False, "error": "Backup file not found"}
1027
-
1028
- # Read the encrypted backup
1029
- with open(backup_path, "rb") as f:
1030
- encrypted_data = f.read()
1031
-
1032
- # Load the encrypted private key
1033
- private_key = serialization.load_pem_private_key(
1034
- encrypted_data, password=password.encode()
1035
- )
1036
-
1037
- # Save the decrypted key
1038
- with open(key_path, "wb") as f:
1039
- f.write(
1040
- private_key.private_bytes(
1041
- encoding=serialization.Encoding.PEM,
1042
- format=serialization.PrivateFormat.PKCS8,
1043
- encryption_algorithm=serialization.NoEncryption(),
1044
- )
1045
- )
1046
-
1047
- return {"success": True, "key_path": key_path}
1048
-
1049
- except Exception as e:
1050
- get_global_logger().error(f"Failed to restore encrypted backup: {e}")
1051
- return {"success": False, "error": f"Decryption failed: {str(e)}"}
224
+ return CertificateValidator.validate_private_key(key_path)
1052
225
 
1053
226
  @staticmethod
1054
227
  def create_ssl_context(
1055
- cert_file: str, key_file: str, ca_file: Optional[str] = None
228
+ cert_file: Optional[str] = None,
229
+ key_file: Optional[str] = None,
230
+ ca_cert_file: Optional[str] = None,
231
+ verify_mode: int = 0, # ssl.CERT_NONE
232
+ check_hostname: bool = False,
1056
233
  ) -> Any:
1057
234
  """
1058
235
  Create SSL context for server or client.
@@ -1060,29 +237,13 @@ class CertificateUtils:
1060
237
  Args:
1061
238
  cert_file: Path to certificate file
1062
239
  key_file: Path to private key file
1063
- ca_file: Path to CA certificate file (optional)
240
+ ca_cert_file: Path to CA certificate file
241
+ verify_mode: SSL verification mode
242
+ check_hostname: Whether to check hostname
1064
243
 
1065
244
  Returns:
1066
- SSL context object
245
+ SSL context
1067
246
  """
1068
- try:
1069
- import ssl
1070
-
1071
- # Create SSL context
1072
- context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
1073
- context.check_hostname = False
1074
- context.verify_mode = ssl.CERT_NONE
1075
-
1076
- # Load certificate and key
1077
- context.load_cert_chain(cert_file, key_file)
1078
-
1079
- # Load CA certificate if provided
1080
- if ca_file and os.path.exists(ca_file):
1081
- context.load_verify_locations(ca_file)
1082
- context.verify_mode = ssl.CERT_REQUIRED
1083
-
1084
- return context
1085
-
1086
- except Exception as e:
1087
- get_global_logger().error(f"Failed to create SSL context: {e}")
1088
- raise
247
+ return SSLContextManager.create_ssl_context(
248
+ cert_file, key_file, ca_cert_file, verify_mode, check_hostname
249
+ )