mcp-proxy-adapter 6.1.1__py3-none-any.whl → 6.2.1__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.
- mcp_proxy_adapter/__main__.py +27 -7
- mcp_proxy_adapter/api/app.py +18 -7
- mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
- mcp_proxy_adapter/core/app_factory.py +87 -3
- mcp_proxy_adapter/core/app_runner.py +272 -0
- mcp_proxy_adapter/core/certificate_utils.py +291 -73
- mcp_proxy_adapter/core/client.py +574 -0
- mcp_proxy_adapter/core/client_manager.py +284 -0
- mcp_proxy_adapter/core/server_adapter.py +17 -80
- mcp_proxy_adapter/core/server_engine.py +5 -99
- mcp_proxy_adapter/core/ssl_utils.py +13 -12
- mcp_proxy_adapter/core/transport_manager.py +5 -5
- mcp_proxy_adapter/examples/__init__.py +16 -0
- mcp_proxy_adapter/examples/basic_framework/__init__.py +7 -0
- mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/main.py +21 -40
- mcp_proxy_adapter/examples/commands/__init__.py +5 -1
- mcp_proxy_adapter/examples/create_certificates_simple.py +260 -75
- mcp_proxy_adapter/examples/debug_request_state.py +4 -36
- mcp_proxy_adapter/examples/debug_role_chain.py +2 -49
- mcp_proxy_adapter/examples/demo_client.py +0 -66
- mcp_proxy_adapter/examples/full_application/__init__.py +11 -0
- mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -19
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -16
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -22
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -24
- mcp_proxy_adapter/examples/full_application/main.py +65 -44
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/generate_all_certificates.py +0 -67
- mcp_proxy_adapter/examples/generate_certificates.py +0 -15
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/examples/generate_test_configs.py +204 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +3 -70
- mcp_proxy_adapter/examples/run_example.py +1 -23
- mcp_proxy_adapter/examples/run_security_tests.py +2 -60
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -53
- mcp_proxy_adapter/examples/security_test_client.py +18 -123
- mcp_proxy_adapter/examples/setup_test_environment.py +179 -0
- mcp_proxy_adapter/examples/test_config.py +148 -0
- mcp_proxy_adapter/examples/test_config_generator.py +1 -25
- mcp_proxy_adapter/examples/test_examples.py +4 -67
- mcp_proxy_adapter/examples/universal_client.py +154 -162
- mcp_proxy_adapter/main.py +51 -161
- mcp_proxy_adapter/version.py +1 -1
- mcp_proxy_adapter-6.2.1.dist-info/METADATA +676 -0
- mcp_proxy_adapter-6.2.1.dist-info/RECORD +119 -0
- mcp_proxy_adapter/docs/EN/TROUBLESHOOTING.md +0 -285
- mcp_proxy_adapter/docs/RU/TROUBLESHOOTING.md +0 -285
- mcp_proxy_adapter/examples/README.md +0 -257
- mcp_proxy_adapter/examples/README_EN.md +0 -258
- mcp_proxy_adapter/examples/SECURITY_TESTING.md +0 -455
- mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +0 -37
- mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +0 -23
- mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +0 -43
- mcp_proxy_adapter/examples/basic_framework/configs/https_no_protocol_middleware.json +0 -36
- mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +0 -29
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_protocol_middleware.json +0 -34
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +0 -39
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_simple.json +0 -35
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +0 -45
- mcp_proxy_adapter/examples/basic_framework/roles.json +0 -21
- mcp_proxy_adapter/examples/cert_config.json +0 -9
- mcp_proxy_adapter/examples/certs/admin.crt +0 -32
- mcp_proxy_adapter/examples/certs/admin.key +0 -52
- mcp_proxy_adapter/examples/certs/admin_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/admin_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/ca_cert.pem +0 -23
- mcp_proxy_adapter/examples/certs/ca_cert.srl +0 -1
- mcp_proxy_adapter/examples/certs/ca_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/cert_config.json +0 -9
- mcp_proxy_adapter/examples/certs/client.crt +0 -32
- mcp_proxy_adapter/examples/certs/client.key +0 -52
- mcp_proxy_adapter/examples/certs/client_admin.crt +0 -32
- mcp_proxy_adapter/examples/certs/client_admin.key +0 -52
- mcp_proxy_adapter/examples/certs/client_user.crt +0 -32
- mcp_proxy_adapter/examples/certs/client_user.key +0 -52
- mcp_proxy_adapter/examples/certs/guest_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/guest_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +0 -23
- mcp_proxy_adapter/examples/certs/proxy_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/proxy_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/readonly.crt +0 -32
- mcp_proxy_adapter/examples/certs/readonly.key +0 -52
- mcp_proxy_adapter/examples/certs/readonly_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/readonly_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/server.crt +0 -32
- mcp_proxy_adapter/examples/certs/server.key +0 -52
- mcp_proxy_adapter/examples/certs/server_cert.pem +0 -32
- mcp_proxy_adapter/examples/certs/server_key.pem +0 -52
- mcp_proxy_adapter/examples/certs/test_ca_ca.crt +0 -20
- mcp_proxy_adapter/examples/certs/user.crt +0 -32
- mcp_proxy_adapter/examples/certs/user.key +0 -52
- mcp_proxy_adapter/examples/certs/user_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/user_key.pem +0 -28
- mcp_proxy_adapter/examples/client_configs/api_key_client.json +0 -13
- mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +0 -13
- mcp_proxy_adapter/examples/client_configs/certificate_client.json +0 -22
- mcp_proxy_adapter/examples/client_configs/jwt_client.json +0 -15
- mcp_proxy_adapter/examples/client_configs/no_auth_client.json +0 -9
- mcp_proxy_adapter/examples/full_application/configs/http_auth.json +0 -37
- mcp_proxy_adapter/examples/full_application/configs/http_simple.json +0 -23
- mcp_proxy_adapter/examples/full_application/configs/https_auth.json +0 -39
- mcp_proxy_adapter/examples/full_application/configs/https_simple.json +0 -25
- mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +0 -39
- mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +0 -45
- mcp_proxy_adapter/examples/full_application/roles.json +0 -21
- mcp_proxy_adapter/examples/keys/ca_key.pem +0 -28
- mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +0 -28
- mcp_proxy_adapter/examples/keys/test_ca_ca.key +0 -28
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +0 -220
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +0 -220
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +0 -2
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +0 -1
- mcp_proxy_adapter/examples/roles.json +0 -38
- mcp_proxy_adapter/examples/server_configs/config_basic_http.json +0 -204
- mcp_proxy_adapter/examples/server_configs/config_http_token.json +0 -238
- mcp_proxy_adapter/examples/server_configs/config_https.json +0 -215
- mcp_proxy_adapter/examples/server_configs/config_https_token.json +0 -231
- mcp_proxy_adapter/examples/server_configs/config_mtls.json +0 -215
- mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +0 -250
- mcp_proxy_adapter/examples/server_configs/config_simple.json +0 -46
- mcp_proxy_adapter/examples/server_configs/roles.json +0 -38
- mcp_proxy_adapter/utils/config_generator.py +0 -727
- mcp_proxy_adapter-6.1.1.dist-info/METADATA +0 -205
- mcp_proxy_adapter-6.1.1.dist-info/RECORD +0 -197
- mcp_proxy_adapter-6.1.1.dist-info/entry_points.txt +0 -2
- mcp_proxy_adapter-6.1.1.dist-info/licenses/LICENSE +0 -21
- {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.1.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
|
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
|
-
|
20
|
-
|
21
|
-
from
|
22
|
-
from
|
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 =
|
134
|
-
key_path =
|
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,
|
308
|
-
|
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
|
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
|
338
|
-
|
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
|
471
|
+
raise ValueError(f"CA certificate not found: {ca_cert_path}")
|
342
472
|
|
343
473
|
if not Path(ca_key_path).exists():
|
344
|
-
raise
|
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.
|
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
|
-
|
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,
|
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 =
|
425
|
-
key_path =
|
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
|
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
|
582
|
+
List of roles found in certificate
|
461
583
|
"""
|
462
|
-
|
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
|
595
|
+
def extract_roles_from_certificate_object(cert) -> List[str]:
|
466
596
|
"""
|
467
|
-
Extract roles from certificate object using
|
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
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
"""
|