mcp-proxy-adapter 6.3.3__py3-none-any.whl → 6.3.5__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 (129) hide show
  1. mcp_proxy_adapter/__init__.py +9 -5
  2. mcp_proxy_adapter/__main__.py +1 -1
  3. mcp_proxy_adapter/api/app.py +227 -176
  4. mcp_proxy_adapter/api/handlers.py +68 -60
  5. mcp_proxy_adapter/api/middleware/__init__.py +7 -5
  6. mcp_proxy_adapter/api/middleware/base.py +19 -16
  7. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +44 -34
  8. mcp_proxy_adapter/api/middleware/error_handling.py +57 -67
  9. mcp_proxy_adapter/api/middleware/factory.py +50 -52
  10. mcp_proxy_adapter/api/middleware/logging.py +46 -30
  11. mcp_proxy_adapter/api/middleware/performance.py +19 -16
  12. mcp_proxy_adapter/api/middleware/protocol_middleware.py +80 -50
  13. mcp_proxy_adapter/api/middleware/transport_middleware.py +26 -24
  14. mcp_proxy_adapter/api/middleware/unified_security.py +70 -51
  15. mcp_proxy_adapter/api/middleware/user_info_middleware.py +43 -34
  16. mcp_proxy_adapter/api/schemas.py +69 -43
  17. mcp_proxy_adapter/api/tool_integration.py +83 -63
  18. mcp_proxy_adapter/api/tools.py +60 -50
  19. mcp_proxy_adapter/commands/__init__.py +15 -6
  20. mcp_proxy_adapter/commands/auth_validation_command.py +107 -110
  21. mcp_proxy_adapter/commands/base.py +108 -112
  22. mcp_proxy_adapter/commands/builtin_commands.py +28 -18
  23. mcp_proxy_adapter/commands/catalog_manager.py +394 -265
  24. mcp_proxy_adapter/commands/cert_monitor_command.py +222 -204
  25. mcp_proxy_adapter/commands/certificate_management_command.py +210 -213
  26. mcp_proxy_adapter/commands/command_registry.py +275 -226
  27. mcp_proxy_adapter/commands/config_command.py +48 -33
  28. mcp_proxy_adapter/commands/dependency_container.py +22 -23
  29. mcp_proxy_adapter/commands/dependency_manager.py +65 -56
  30. mcp_proxy_adapter/commands/echo_command.py +15 -15
  31. mcp_proxy_adapter/commands/health_command.py +31 -29
  32. mcp_proxy_adapter/commands/help_command.py +97 -61
  33. mcp_proxy_adapter/commands/hooks.py +65 -49
  34. mcp_proxy_adapter/commands/key_management_command.py +148 -147
  35. mcp_proxy_adapter/commands/load_command.py +58 -40
  36. mcp_proxy_adapter/commands/plugins_command.py +80 -54
  37. mcp_proxy_adapter/commands/protocol_management_command.py +60 -48
  38. mcp_proxy_adapter/commands/proxy_registration_command.py +107 -115
  39. mcp_proxy_adapter/commands/reload_command.py +43 -37
  40. mcp_proxy_adapter/commands/result.py +26 -33
  41. mcp_proxy_adapter/commands/role_test_command.py +26 -26
  42. mcp_proxy_adapter/commands/roles_management_command.py +176 -173
  43. mcp_proxy_adapter/commands/security_command.py +134 -122
  44. mcp_proxy_adapter/commands/settings_command.py +47 -56
  45. mcp_proxy_adapter/commands/ssl_setup_command.py +109 -129
  46. mcp_proxy_adapter/commands/token_management_command.py +129 -158
  47. mcp_proxy_adapter/commands/transport_management_command.py +41 -36
  48. mcp_proxy_adapter/commands/unload_command.py +42 -37
  49. mcp_proxy_adapter/config.py +36 -35
  50. mcp_proxy_adapter/core/__init__.py +19 -21
  51. mcp_proxy_adapter/core/app_factory.py +30 -9
  52. mcp_proxy_adapter/core/app_runner.py +81 -64
  53. mcp_proxy_adapter/core/auth_validator.py +176 -182
  54. mcp_proxy_adapter/core/certificate_utils.py +469 -426
  55. mcp_proxy_adapter/core/client.py +155 -126
  56. mcp_proxy_adapter/core/client_manager.py +60 -54
  57. mcp_proxy_adapter/core/client_security.py +108 -88
  58. mcp_proxy_adapter/core/config_converter.py +176 -143
  59. mcp_proxy_adapter/core/config_validator.py +12 -4
  60. mcp_proxy_adapter/core/crl_utils.py +21 -7
  61. mcp_proxy_adapter/core/errors.py +64 -20
  62. mcp_proxy_adapter/core/logging.py +34 -29
  63. mcp_proxy_adapter/core/mtls_asgi.py +29 -25
  64. mcp_proxy_adapter/core/mtls_asgi_app.py +66 -54
  65. mcp_proxy_adapter/core/protocol_manager.py +154 -104
  66. mcp_proxy_adapter/core/proxy_client.py +202 -144
  67. mcp_proxy_adapter/core/proxy_registration.py +12 -2
  68. mcp_proxy_adapter/core/role_utils.py +139 -125
  69. mcp_proxy_adapter/core/security_adapter.py +88 -77
  70. mcp_proxy_adapter/core/security_factory.py +50 -44
  71. mcp_proxy_adapter/core/security_integration.py +72 -24
  72. mcp_proxy_adapter/core/server_adapter.py +68 -64
  73. mcp_proxy_adapter/core/server_engine.py +71 -53
  74. mcp_proxy_adapter/core/settings.py +68 -58
  75. mcp_proxy_adapter/core/ssl_utils.py +69 -56
  76. mcp_proxy_adapter/core/transport_manager.py +72 -60
  77. mcp_proxy_adapter/core/unified_config_adapter.py +201 -150
  78. mcp_proxy_adapter/core/utils.py +4 -2
  79. mcp_proxy_adapter/custom_openapi.py +107 -99
  80. mcp_proxy_adapter/examples/basic_framework/main.py +9 -2
  81. mcp_proxy_adapter/examples/commands/__init__.py +1 -1
  82. mcp_proxy_adapter/examples/create_certificates_simple.py +182 -71
  83. mcp_proxy_adapter/examples/debug_request_state.py +38 -19
  84. mcp_proxy_adapter/examples/debug_role_chain.py +53 -20
  85. mcp_proxy_adapter/examples/demo_client.py +48 -36
  86. mcp_proxy_adapter/examples/examples/basic_framework/main.py +9 -2
  87. mcp_proxy_adapter/examples/examples/full_application/__init__.py +1 -0
  88. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +22 -10
  89. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +24 -17
  90. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +16 -3
  91. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +13 -3
  92. mcp_proxy_adapter/examples/examples/full_application/main.py +27 -2
  93. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +48 -14
  94. mcp_proxy_adapter/examples/full_application/__init__.py +1 -0
  95. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +22 -10
  96. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +24 -17
  97. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +16 -3
  98. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +13 -3
  99. mcp_proxy_adapter/examples/full_application/main.py +27 -2
  100. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +48 -14
  101. mcp_proxy_adapter/examples/generate_all_certificates.py +198 -73
  102. mcp_proxy_adapter/examples/generate_certificates.py +31 -16
  103. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +220 -74
  104. mcp_proxy_adapter/examples/generate_test_configs.py +68 -91
  105. mcp_proxy_adapter/examples/proxy_registration_example.py +76 -75
  106. mcp_proxy_adapter/examples/run_example.py +23 -5
  107. mcp_proxy_adapter/examples/run_full_test_suite.py +109 -71
  108. mcp_proxy_adapter/examples/run_proxy_server.py +22 -9
  109. mcp_proxy_adapter/examples/run_security_tests.py +103 -41
  110. mcp_proxy_adapter/examples/run_security_tests_fixed.py +72 -36
  111. mcp_proxy_adapter/examples/scripts/config_generator.py +288 -187
  112. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +185 -72
  113. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +220 -74
  114. mcp_proxy_adapter/examples/security_test_client.py +196 -127
  115. mcp_proxy_adapter/examples/setup_test_environment.py +17 -29
  116. mcp_proxy_adapter/examples/test_config.py +19 -4
  117. mcp_proxy_adapter/examples/test_config_generator.py +23 -7
  118. mcp_proxy_adapter/examples/test_examples.py +84 -56
  119. mcp_proxy_adapter/examples/universal_client.py +119 -62
  120. mcp_proxy_adapter/openapi.py +108 -115
  121. mcp_proxy_adapter/utils/config_generator.py +429 -274
  122. mcp_proxy_adapter/version.py +1 -2
  123. {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/METADATA +1 -1
  124. mcp_proxy_adapter-6.3.5.dist-info/RECORD +143 -0
  125. mcp_proxy_adapter-6.3.3.dist-info/RECORD +0 -143
  126. {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/WHEEL +0 -0
  127. {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/entry_points.txt +0 -0
  128. {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/licenses/LICENSE +0 -0
  129. {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/top_level.txt +0 -0
@@ -25,16 +25,23 @@ logger = logging.getLogger(__name__)
25
25
  class KeyResult:
26
26
  """
27
27
  Result class for key operations.
28
-
28
+
29
29
  Contains key information and operation status.
30
30
  """
31
-
32
- def __init__(self, key_path: str, key_type: str, key_size: int,
33
- created_date: Optional[str] = None, expiry_date: Optional[str] = None,
34
- status: str = "valid", error: Optional[str] = None):
31
+
32
+ def __init__(
33
+ self,
34
+ key_path: str,
35
+ key_type: str,
36
+ key_size: int,
37
+ created_date: Optional[str] = None,
38
+ expiry_date: Optional[str] = None,
39
+ status: str = "valid",
40
+ error: Optional[str] = None,
41
+ ):
35
42
  """
36
43
  Initialize key result.
37
-
44
+
38
45
  Args:
39
46
  key_path: Path to key file
40
47
  key_type: Type of key (RSA, ECDSA, etc.)
@@ -51,11 +58,11 @@ class KeyResult:
51
58
  self.expiry_date = expiry_date
52
59
  self.status = status
53
60
  self.error = error
54
-
61
+
55
62
  def to_dict(self) -> Dict[str, Any]:
56
63
  """
57
64
  Convert to dictionary format.
58
-
65
+
59
66
  Returns:
60
67
  Dictionary representation
61
68
  """
@@ -66,9 +73,9 @@ class KeyResult:
66
73
  "created_date": self.created_date,
67
74
  "expiry_date": self.expiry_date,
68
75
  "status": self.status,
69
- "error": self.error
76
+ "error": self.error,
70
77
  }
71
-
78
+
72
79
  @classmethod
73
80
  def get_schema(cls) -> Dict[str, Any]:
74
81
  """
@@ -85,21 +92,24 @@ class KeyResult:
85
92
  "key_size": {"type": "integer", "description": "Key size in bits"},
86
93
  "created_date": {"type": "string", "description": "Key creation date"},
87
94
  "expiry_date": {"type": "string", "description": "Key expiry date"},
88
- "status": {"type": "string", "enum": ["valid", "expired", "error"],
89
- "description": "Key status"},
90
- "error": {"type": "string", "description": "Error message if any"}
95
+ "status": {
96
+ "type": "string",
97
+ "enum": ["valid", "expired", "error"],
98
+ "description": "Key status",
99
+ },
100
+ "error": {"type": "string", "description": "Error message if any"},
91
101
  },
92
- "required": ["key_path", "key_type", "key_size", "status"]
102
+ "required": ["key_path", "key_type", "key_size", "status"],
93
103
  }
94
104
 
95
105
 
96
106
  class KeyManagementCommand(Command):
97
107
  """
98
108
  Command for key management.
99
-
109
+
100
110
  Provides methods for generating, validating, rotating, backing up, and restoring keys.
101
111
  """
102
-
112
+
103
113
  # Command metadata
104
114
  name = "key_management"
105
115
  version = "1.0.0"
@@ -109,16 +119,16 @@ class KeyManagementCommand(Command):
109
119
  email = "team@mcp-proxy-adapter.com"
110
120
  source_url = "https://github.com/mcp-proxy-adapter"
111
121
  result_class = KeyResult
112
-
122
+
113
123
  def __init__(self):
114
124
  """Initialize key management command."""
115
125
  super().__init__()
116
126
  self.certificate_utils = CertificateUtils()
117
-
127
+
118
128
  async def execute(self, **kwargs) -> CommandResult:
119
129
  """
120
130
  Execute key management command.
121
-
131
+
122
132
  Args:
123
133
  **kwargs: Command parameters including:
124
134
  - action: Action to perform (key_generate, key_validate, key_rotate, key_backup, key_restore)
@@ -133,12 +143,12 @@ class KeyManagementCommand(Command):
133
143
  - backup_old: Whether to backup old key during rotation
134
144
  - backup_path: Backup path for key backup/restore
135
145
  - encrypt_backup: Whether to encrypt backup
136
-
146
+
137
147
  Returns:
138
148
  CommandResult with key operation status
139
149
  """
140
150
  action = kwargs.get("action", "key_validate")
141
-
151
+
142
152
  if action == "key_generate":
143
153
  key_type = kwargs.get("key_type")
144
154
  key_size = kwargs.get("key_size")
@@ -154,13 +164,17 @@ class KeyManagementCommand(Command):
154
164
  new_key_path = kwargs.get("new_key_path")
155
165
  cert_path = kwargs.get("cert_path")
156
166
  backup_old = kwargs.get("backup_old", True)
157
- return await self.key_rotate(old_key_path, new_key_path, cert_path, backup_old)
167
+ return await self.key_rotate(
168
+ old_key_path, new_key_path, cert_path, backup_old
169
+ )
158
170
  elif action == "key_backup":
159
171
  key_path = kwargs.get("key_path")
160
172
  backup_path = kwargs.get("backup_path")
161
173
  encrypt_backup = kwargs.get("encrypt_backup", True)
162
174
  password = kwargs.get("password")
163
- return await self.key_backup(key_path, backup_path, encrypt_backup, password)
175
+ return await self.key_backup(
176
+ key_path, backup_path, encrypt_backup, password
177
+ )
164
178
  elif action == "key_restore":
165
179
  backup_path = kwargs.get("backup_path")
166
180
  key_path = kwargs.get("key_path")
@@ -170,175 +184,162 @@ class KeyManagementCommand(Command):
170
184
  return ErrorResult(
171
185
  message=f"Unknown action: {action}. Supported actions: key_generate, key_validate, key_rotate, key_backup, key_restore"
172
186
  )
173
-
174
- async def key_generate(self, key_type: str, key_size: int, output_path: str,
175
- password: Optional[str] = None) -> CommandResult:
187
+
188
+ async def key_generate(
189
+ self,
190
+ key_type: str,
191
+ key_size: int,
192
+ output_path: str,
193
+ password: Optional[str] = None,
194
+ ) -> CommandResult:
176
195
  """
177
196
  Generate a new private key.
178
-
197
+
179
198
  Args:
180
199
  key_type: Type of key to generate (RSA, ECDSA)
181
200
  key_size: Key size in bits
182
201
  output_path: Path to save the generated key
183
202
  password: Optional password to encrypt the key
184
-
203
+
185
204
  Returns:
186
205
  CommandResult with key generation status
187
206
  """
188
207
  try:
189
208
  logger.info(f"Generating {key_type} key with size {key_size} bits")
190
-
209
+
191
210
  # Validate parameters
192
211
  if key_type not in ["RSA", "ECDSA"]:
193
- return ErrorResult(
194
- message="Key type must be RSA or ECDSA"
195
- )
196
-
212
+ return ErrorResult(message="Key type must be RSA or ECDSA")
213
+
197
214
  if key_type == "ECDSA":
198
215
  if key_size not in [256, 384, 521]:
199
216
  return ErrorResult(
200
217
  message="ECDSA key size must be 256, 384, or 521 bits"
201
218
  )
202
219
  elif key_size < 1024:
203
- return ErrorResult(
204
- message="Key size must be at least 1024 bits"
205
- )
206
-
220
+ return ErrorResult(message="Key size must be at least 1024 bits")
221
+
207
222
  # Create output directory if it doesn't exist
208
223
  output_dir = os.path.dirname(output_path)
209
224
  if output_dir:
210
225
  Path(output_dir).mkdir(parents=True, exist_ok=True)
211
-
226
+
212
227
  # Generate key
213
228
  result = self.certificate_utils.generate_private_key(
214
229
  key_type, key_size, output_path
215
230
  )
216
-
231
+
217
232
  if not result["success"]:
218
- return ErrorResult(
219
- message=f"Key generation failed: {result['error']}"
220
- )
221
-
233
+ return ErrorResult(message=f"Key generation failed: {result['error']}")
234
+
222
235
  key_result = KeyResult(
223
236
  key_path=output_path,
224
237
  key_type=key_type,
225
238
  key_size=key_size,
226
239
  created_date=datetime.now().isoformat(),
227
- status="valid"
240
+ status="valid",
228
241
  )
229
-
242
+
230
243
  logger.info(f"Key generated successfully: {output_path}")
231
- return SuccessResult(
232
- data={
233
- "key": key_result.to_dict(),
234
- "details": result
235
- }
236
- )
237
-
244
+ return SuccessResult(data={"key": key_result.to_dict(), "details": result})
245
+
238
246
  except Exception as e:
239
247
  logger.error(f"Key generation failed: {e}")
240
- return ErrorResult(
241
- message=f"Key generation failed: {str(e)}"
242
- )
243
-
244
- async def key_validate(self, key_path: str, password: Optional[str] = None) -> CommandResult:
248
+ return ErrorResult(message=f"Key generation failed: {str(e)}")
249
+
250
+ async def key_validate(
251
+ self, key_path: str, password: Optional[str] = None
252
+ ) -> CommandResult:
245
253
  """
246
254
  Validate a private key.
247
-
255
+
248
256
  Args:
249
257
  key_path: Path to key file to validate
250
258
  password: Optional password if key is encrypted
251
-
259
+
252
260
  Returns:
253
261
  CommandResult with key validation status
254
262
  """
255
263
  try:
256
264
  logger.info(f"Validating key: {key_path}")
257
-
265
+
258
266
  # Validate parameters
259
267
  if not key_path or not os.path.exists(key_path):
260
- return ErrorResult(
261
- message=f"Key file not found: {key_path}"
262
- )
263
-
268
+ return ErrorResult(message=f"Key file not found: {key_path}")
269
+
264
270
  # Validate key
265
271
  result = self.certificate_utils.validate_private_key(key_path)
266
-
272
+
267
273
  if not result["success"]:
268
- return ErrorResult(
269
- message=f"Key validation failed: {result['error']}"
270
- )
271
-
274
+ return ErrorResult(message=f"Key validation failed: {result['error']}")
275
+
272
276
  key_result = KeyResult(
273
277
  key_path=key_path,
274
278
  key_type=result.get("key_type", "unknown"),
275
279
  key_size=result.get("key_size", 0),
276
280
  created_date=result.get("created_date"),
277
- status="valid"
281
+ status="valid",
278
282
  )
279
-
283
+
280
284
  logger.info(f"Key validation completed: {key_path}")
281
- return SuccessResult(
282
- data={
283
- "key": key_result.to_dict()
284
- }
285
- )
286
-
285
+ return SuccessResult(data={"key": key_result.to_dict()})
286
+
287
287
  except Exception as e:
288
288
  logger.error(f"Key validation failed: {e}")
289
- return ErrorResult(
290
- message=f"Key validation failed: {str(e)}"
291
- )
292
-
293
- async def key_rotate(self, old_key_path: str, new_key_path: str,
294
- cert_path: Optional[str] = None, backup_old: bool = True) -> CommandResult:
289
+ return ErrorResult(message=f"Key validation failed: {str(e)}")
290
+
291
+ async def key_rotate(
292
+ self,
293
+ old_key_path: str,
294
+ new_key_path: str,
295
+ cert_path: Optional[str] = None,
296
+ backup_old: bool = True,
297
+ ) -> CommandResult:
295
298
  """
296
299
  Rotate a private key.
297
-
300
+
298
301
  Args:
299
302
  old_key_path: Path to old key file
300
303
  new_key_path: Path to new key file
301
304
  cert_path: Optional certificate path to update with new key
302
305
  backup_old: Whether to backup the old key
303
-
306
+
304
307
  Returns:
305
308
  CommandResult with key rotation status
306
309
  """
307
310
  try:
308
311
  logger.info(f"Rotating key from {old_key_path} to {new_key_path}")
309
-
312
+
310
313
  # Validate parameters
311
314
  if not old_key_path or not os.path.exists(old_key_path):
312
- return ErrorResult(
313
- message=f"Old key file not found: {old_key_path}"
314
- )
315
-
315
+ return ErrorResult(message=f"Old key file not found: {old_key_path}")
316
+
316
317
  if not new_key_path or not os.path.exists(new_key_path):
317
- return ErrorResult(
318
- message=f"New key file not found: {new_key_path}"
319
- )
320
-
318
+ return ErrorResult(message=f"New key file not found: {new_key_path}")
319
+
321
320
  # Validate both keys
322
321
  old_key_validation = await self.key_validate(old_key_path)
323
322
  new_key_validation = await self.key_validate(new_key_path)
324
-
323
+
325
324
  if not old_key_validation.to_dict()["success"]:
326
325
  return ErrorResult(
327
326
  message=f"Old key validation failed: {old_key_validation.to_dict()['error']['message']}"
328
327
  )
329
-
328
+
330
329
  if not new_key_validation.to_dict()["success"]:
331
330
  return ErrorResult(
332
331
  message=f"New key validation failed: {new_key_validation.to_dict()['error']['message']}"
333
332
  )
334
-
333
+
335
334
  # Backup old key if requested
336
335
  backup_path = None
337
336
  if backup_old:
338
- backup_path = f"{old_key_path}.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}"
337
+ backup_path = (
338
+ f"{old_key_path}.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}"
339
+ )
339
340
  shutil.copy2(old_key_path, backup_path)
340
341
  logger.info(f"Old key backed up to: {backup_path}")
341
-
342
+
342
343
  # Update certificate if provided
343
344
  cert_updated = False
344
345
  if cert_path and os.path.exists(cert_path):
@@ -346,13 +347,15 @@ class KeyManagementCommand(Command):
346
347
  # This would require implementing certificate re-signing
347
348
  # For now, we'll just note that it needs to be done
348
349
  cert_updated = True
349
- logger.info(f"Certificate {cert_path} needs to be re-signed with new key")
350
+ logger.info(
351
+ f"Certificate {cert_path} needs to be re-signed with new key"
352
+ )
350
353
  except Exception as e:
351
354
  logger.warning(f"Could not update certificate {cert_path}: {e}")
352
-
355
+
353
356
  # Replace old key with new key
354
357
  shutil.copy2(new_key_path, old_key_path)
355
-
358
+
356
359
  logger.info(f"Key rotation completed successfully")
357
360
  return SuccessResult(
358
361
  data={
@@ -360,51 +363,52 @@ class KeyManagementCommand(Command):
360
363
  "new_key_path": new_key_path,
361
364
  "backup_path": backup_path,
362
365
  "cert_updated": cert_updated,
363
- "message": "Key rotation completed successfully"
366
+ "message": "Key rotation completed successfully",
364
367
  }
365
368
  )
366
-
369
+
367
370
  except Exception as e:
368
371
  logger.error(f"Key rotation failed: {e}")
369
- return ErrorResult(
370
- message=f"Key rotation failed: {str(e)}"
371
- )
372
-
373
- async def key_backup(self, key_path: str, backup_path: str,
374
- encrypt_backup: bool = True, password: Optional[str] = None) -> CommandResult:
372
+ return ErrorResult(message=f"Key rotation failed: {str(e)}")
373
+
374
+ async def key_backup(
375
+ self,
376
+ key_path: str,
377
+ backup_path: str,
378
+ encrypt_backup: bool = True,
379
+ password: Optional[str] = None,
380
+ ) -> CommandResult:
375
381
  """
376
382
  Backup a private key.
377
-
383
+
378
384
  Args:
379
385
  key_path: Path to key file to backup
380
386
  backup_path: Path to save the backup
381
387
  encrypt_backup: Whether to encrypt the backup
382
388
  password: Password for backup encryption
383
-
389
+
384
390
  Returns:
385
391
  CommandResult with backup status
386
392
  """
387
393
  try:
388
394
  logger.info(f"Backing up key: {key_path}")
389
-
395
+
390
396
  # Validate parameters
391
397
  if not key_path or not os.path.exists(key_path):
392
- return ErrorResult(
393
- message=f"Key file not found: {key_path}"
394
- )
395
-
398
+ return ErrorResult(message=f"Key file not found: {key_path}")
399
+
396
400
  # Validate key before backup
397
401
  key_validation = await self.key_validate(key_path)
398
402
  if not key_validation.to_dict()["success"]:
399
403
  return ErrorResult(
400
404
  message=f"Key validation failed before backup: {key_validation.to_dict()['error']['message']}"
401
405
  )
402
-
406
+
403
407
  # Create backup directory if it doesn't exist
404
408
  backup_dir = os.path.dirname(backup_path)
405
409
  if backup_dir:
406
410
  Path(backup_dir).mkdir(parents=True, exist_ok=True)
407
-
411
+
408
412
  # Create backup
409
413
  if encrypt_backup and password:
410
414
  # Encrypted backup
@@ -418,13 +422,11 @@ class KeyManagementCommand(Command):
418
422
  else:
419
423
  # Simple file copy
420
424
  shutil.copy2(key_path, backup_path)
421
-
425
+
422
426
  # Verify backup
423
427
  if not os.path.exists(backup_path):
424
- return ErrorResult(
425
- message="Backup file was not created"
426
- )
427
-
428
+ return ErrorResult(message="Backup file was not created")
429
+
428
430
  logger.info(f"Key backup completed successfully: {backup_path}")
429
431
  return SuccessResult(
430
432
  data={
@@ -432,43 +434,40 @@ class KeyManagementCommand(Command):
432
434
  "backup_path": backup_path,
433
435
  "encrypted": encrypt_backup,
434
436
  "backup_size": os.path.getsize(backup_path),
435
- "backup_date": datetime.now().isoformat()
437
+ "backup_date": datetime.now().isoformat(),
436
438
  }
437
439
  )
438
-
440
+
439
441
  except Exception as e:
440
442
  logger.error(f"Key backup failed: {e}")
441
- return ErrorResult(
442
- message=f"Key backup failed: {str(e)}"
443
- )
444
-
445
- async def key_restore(self, backup_path: str, key_path: str,
446
- password: Optional[str] = None) -> CommandResult:
443
+ return ErrorResult(message=f"Key backup failed: {str(e)}")
444
+
445
+ async def key_restore(
446
+ self, backup_path: str, key_path: str, password: Optional[str] = None
447
+ ) -> CommandResult:
447
448
  """
448
449
  Restore a private key from backup.
449
-
450
+
450
451
  Args:
451
452
  backup_path: Path to backup file
452
453
  key_path: Path to restore the key to
453
454
  password: Password if backup is encrypted
454
-
455
+
455
456
  Returns:
456
457
  CommandResult with restore status
457
458
  """
458
459
  try:
459
460
  logger.info(f"Restoring key from backup: {backup_path}")
460
-
461
+
461
462
  # Validate parameters
462
463
  if not backup_path or not os.path.exists(backup_path):
463
- return ErrorResult(
464
- message=f"Backup file not found: {backup_path}"
465
- )
466
-
464
+ return ErrorResult(message=f"Backup file not found: {backup_path}")
465
+
467
466
  # Create target directory if it doesn't exist
468
467
  key_dir = os.path.dirname(key_path)
469
468
  if key_dir:
470
469
  Path(key_dir).mkdir(parents=True, exist_ok=True)
471
-
470
+
472
471
  # Restore key
473
472
  if password:
474
473
  # Try encrypted restore first
@@ -482,26 +481,28 @@ class KeyManagementCommand(Command):
482
481
  else:
483
482
  # Simple file copy
484
483
  shutil.copy2(backup_path, key_path)
485
-
484
+
486
485
  # Validate restored key
487
486
  key_validation = await self.key_validate(key_path)
488
487
  if not key_validation.to_dict()["success"]:
489
488
  return ErrorResult(
490
489
  message=f"Restored key validation failed: {key_validation.to_dict()['error']['message']}"
491
490
  )
492
-
491
+
493
492
  logger.info(f"Key restore completed successfully: {key_path}")
494
493
  return SuccessResult(
495
494
  data={
496
495
  "backup_path": backup_path,
497
496
  "key_path": key_path,
498
497
  "restore_date": datetime.now().isoformat(),
499
- "key_info": key_validation.to_dict().get("data", {}).get("key") if key_validation.to_dict().get("success") else None
498
+ "key_info": (
499
+ key_validation.to_dict().get("data", {}).get("key")
500
+ if key_validation.to_dict().get("success")
501
+ else None
502
+ ),
500
503
  }
501
504
  )
502
-
505
+
503
506
  except Exception as e:
504
507
  logger.error(f"Key restore failed: {e}")
505
- return ErrorResult(
506
- message=f"Key restore failed: {str(e)}"
507
- )
508
+ return ErrorResult(message=f"Key restore failed: {str(e)}")