mcp-security-framework 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_security_framework/__init__.py +96 -0
- mcp_security_framework/cli/__init__.py +18 -0
- mcp_security_framework/cli/cert_cli.py +511 -0
- mcp_security_framework/cli/security_cli.py +791 -0
- mcp_security_framework/constants.py +209 -0
- mcp_security_framework/core/__init__.py +61 -0
- mcp_security_framework/core/auth_manager.py +1011 -0
- mcp_security_framework/core/cert_manager.py +1663 -0
- mcp_security_framework/core/permission_manager.py +735 -0
- mcp_security_framework/core/rate_limiter.py +602 -0
- mcp_security_framework/core/security_manager.py +943 -0
- mcp_security_framework/core/ssl_manager.py +735 -0
- mcp_security_framework/examples/__init__.py +75 -0
- mcp_security_framework/examples/django_example.py +615 -0
- mcp_security_framework/examples/fastapi_example.py +472 -0
- mcp_security_framework/examples/flask_example.py +506 -0
- mcp_security_framework/examples/gateway_example.py +803 -0
- mcp_security_framework/examples/microservice_example.py +690 -0
- mcp_security_framework/examples/standalone_example.py +576 -0
- mcp_security_framework/middleware/__init__.py +250 -0
- mcp_security_framework/middleware/auth_middleware.py +292 -0
- mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
- mcp_security_framework/middleware/fastapi_middleware.py +757 -0
- mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
- mcp_security_framework/middleware/flask_middleware.py +591 -0
- mcp_security_framework/middleware/mtls_middleware.py +439 -0
- mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
- mcp_security_framework/middleware/security_middleware.py +507 -0
- mcp_security_framework/schemas/__init__.py +109 -0
- mcp_security_framework/schemas/config.py +694 -0
- mcp_security_framework/schemas/models.py +709 -0
- mcp_security_framework/schemas/responses.py +686 -0
- mcp_security_framework/tests/__init__.py +0 -0
- mcp_security_framework/utils/__init__.py +121 -0
- mcp_security_framework/utils/cert_utils.py +525 -0
- mcp_security_framework/utils/crypto_utils.py +475 -0
- mcp_security_framework/utils/validation_utils.py +571 -0
- mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
- mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
- mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
- mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_cli/__init__.py +0 -0
- tests/test_cli/test_cert_cli.py +379 -0
- tests/test_cli/test_security_cli.py +657 -0
- tests/test_core/__init__.py +0 -0
- tests/test_core/test_auth_manager.py +582 -0
- tests/test_core/test_cert_manager.py +795 -0
- tests/test_core/test_permission_manager.py +395 -0
- tests/test_core/test_rate_limiter.py +626 -0
- tests/test_core/test_security_manager.py +841 -0
- tests/test_core/test_ssl_manager.py +532 -0
- tests/test_examples/__init__.py +8 -0
- tests/test_examples/test_fastapi_example.py +264 -0
- tests/test_examples/test_flask_example.py +238 -0
- tests/test_examples/test_standalone_example.py +292 -0
- tests/test_integration/__init__.py +0 -0
- tests/test_integration/test_auth_flow.py +502 -0
- tests/test_integration/test_certificate_flow.py +527 -0
- tests/test_integration/test_fastapi_integration.py +341 -0
- tests/test_integration/test_flask_integration.py +398 -0
- tests/test_integration/test_standalone_integration.py +493 -0
- tests/test_middleware/__init__.py +0 -0
- tests/test_middleware/test_fastapi_middleware.py +523 -0
- tests/test_middleware/test_flask_middleware.py +582 -0
- tests/test_middleware/test_security_middleware.py +493 -0
- tests/test_schemas/__init__.py +0 -0
- tests/test_schemas/test_config.py +811 -0
- tests/test_schemas/test_models.py +879 -0
- tests/test_schemas/test_responses.py +1054 -0
- tests/test_schemas/test_serialization.py +493 -0
- tests/test_utils/__init__.py +0 -0
- tests/test_utils/test_cert_utils.py +510 -0
- tests/test_utils/test_crypto_utils.py +603 -0
- tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,735 @@
|
|
1
|
+
"""
|
2
|
+
SSL/TLS Manager Module
|
3
|
+
|
4
|
+
This module provides comprehensive SSL/TLS management for the MCP Security
|
5
|
+
Framework. It handles SSL context creation, certificate validation, and
|
6
|
+
TLS configuration management.
|
7
|
+
|
8
|
+
Key Features:
|
9
|
+
- SSL context creation for servers and clients
|
10
|
+
- Certificate validation and verification
|
11
|
+
- TLS version and cipher management
|
12
|
+
- Certificate information extraction
|
13
|
+
- SSL configuration management
|
14
|
+
- Certificate chain validation
|
15
|
+
|
16
|
+
Classes:
|
17
|
+
SSLManager: Main SSL/TLS management class
|
18
|
+
SSLContextBuilder: Builder for SSL contexts
|
19
|
+
CertificateValidator: Certificate validation utilities
|
20
|
+
|
21
|
+
Author: MCP Security Team
|
22
|
+
Version: 1.0.0
|
23
|
+
License: MIT
|
24
|
+
"""
|
25
|
+
|
26
|
+
import logging
|
27
|
+
import ssl
|
28
|
+
from pathlib import Path
|
29
|
+
from typing import Dict, List, Optional, Union
|
30
|
+
|
31
|
+
from cryptography import x509
|
32
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
33
|
+
from cryptography.hazmat.primitives.asymmetric import rsa
|
34
|
+
|
35
|
+
from ..schemas.config import SSLConfig
|
36
|
+
from ..schemas.models import CertificateInfo
|
37
|
+
from ..utils.cert_utils import (
|
38
|
+
extract_certificate_info,
|
39
|
+
get_certificate_expiry,
|
40
|
+
is_certificate_self_signed,
|
41
|
+
parse_certificate,
|
42
|
+
validate_certificate_chain,
|
43
|
+
)
|
44
|
+
|
45
|
+
|
46
|
+
class SSLManager:
|
47
|
+
"""
|
48
|
+
SSL/TLS Management Class
|
49
|
+
|
50
|
+
This class provides comprehensive SSL/TLS management capabilities including
|
51
|
+
SSL context creation, certificate validation, and TLS configuration.
|
52
|
+
|
53
|
+
The SSLManager handles:
|
54
|
+
- Server and client SSL context creation
|
55
|
+
- Certificate validation and verification
|
56
|
+
- TLS version and cipher management
|
57
|
+
- Certificate information extraction
|
58
|
+
- SSL configuration management
|
59
|
+
- Certificate chain building and validation
|
60
|
+
|
61
|
+
Attributes:
|
62
|
+
config (SSLConfig): SSL configuration settings
|
63
|
+
logger (Logger): Logger instance for SSL operations
|
64
|
+
_contexts (Dict): Cache of created SSL contexts
|
65
|
+
_certificate_cache (Dict): Cache of certificate information
|
66
|
+
|
67
|
+
Example:
|
68
|
+
>>> config = SSLConfig(enabled=True, cert_file="server.crt")
|
69
|
+
>>> ssl_manager = SSLManager(config)
|
70
|
+
>>> context = ssl_manager.create_server_context()
|
71
|
+
|
72
|
+
Raises:
|
73
|
+
SSLConfigurationError: When SSL configuration is invalid
|
74
|
+
CertificateValidationError: When certificate validation fails
|
75
|
+
"""
|
76
|
+
|
77
|
+
def __init__(self, config: SSLConfig):
|
78
|
+
"""
|
79
|
+
Initialize SSL Manager.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
config (SSLConfig): SSL configuration settings containing
|
83
|
+
certificate paths, TLS versions, and verification settings.
|
84
|
+
Must be a valid SSLConfig instance with proper certificate
|
85
|
+
file paths and TLS configuration.
|
86
|
+
|
87
|
+
Raises:
|
88
|
+
SSLConfigurationError: If configuration is invalid or certificate
|
89
|
+
files are not accessible.
|
90
|
+
|
91
|
+
Example:
|
92
|
+
>>> config = SSLConfig(enabled=True, cert_file="server.crt")
|
93
|
+
>>> ssl_manager = SSLManager(config)
|
94
|
+
"""
|
95
|
+
self.config = config
|
96
|
+
self.logger = logging.getLogger(__name__)
|
97
|
+
self._contexts: Dict[str, ssl.SSLContext] = {}
|
98
|
+
self._certificate_cache: Dict[str, CertificateInfo] = {}
|
99
|
+
|
100
|
+
# Validate configuration
|
101
|
+
self._validate_configuration()
|
102
|
+
|
103
|
+
self.logger.info(
|
104
|
+
"SSLManager initialized successfully",
|
105
|
+
extra={
|
106
|
+
"enabled": config.enabled,
|
107
|
+
"min_tls_version": config.min_tls_version,
|
108
|
+
"verify_mode": config.verify_mode,
|
109
|
+
},
|
110
|
+
)
|
111
|
+
|
112
|
+
def create_server_context(
|
113
|
+
self,
|
114
|
+
cert_file: Optional[str] = None,
|
115
|
+
key_file: Optional[str] = None,
|
116
|
+
ca_cert_file: Optional[str] = None,
|
117
|
+
verify_mode: Optional[str] = None,
|
118
|
+
min_version: Optional[str] = None,
|
119
|
+
) -> ssl.SSLContext:
|
120
|
+
"""
|
121
|
+
Create SSL context for server operations.
|
122
|
+
|
123
|
+
This method creates and configures an SSL context suitable for server
|
124
|
+
operations. It handles certificate loading, key management, and TLS
|
125
|
+
configuration according to the provided parameters.
|
126
|
+
|
127
|
+
Args:
|
128
|
+
cert_file (Optional[str]): Path to server certificate file.
|
129
|
+
If None, uses certificate from config. Must be a valid PEM
|
130
|
+
or DER certificate file path.
|
131
|
+
key_file (Optional[str]): Path to server private key file.
|
132
|
+
If None, uses key from config. Must be a valid PEM or DER
|
133
|
+
private key file path.
|
134
|
+
ca_cert_file (Optional[str]): Path to CA certificate file for
|
135
|
+
client certificate verification. If None, uses CA from config.
|
136
|
+
Must be a valid PEM certificate file path.
|
137
|
+
verify_mode (Optional[str]): SSL verification mode. Valid values:
|
138
|
+
- "CERT_NONE": No certificate verification
|
139
|
+
- "CERT_OPTIONAL": Certificate verification optional
|
140
|
+
- "CERT_REQUIRED": Certificate verification required
|
141
|
+
If None, uses verify_mode from config.
|
142
|
+
min_version (Optional[str]): Minimum TLS version. Valid values:
|
143
|
+
- "TLSv1.0": TLS 1.0
|
144
|
+
- "TLSv1.1": TLS 1.1
|
145
|
+
- "TLSv1.2": TLS 1.2
|
146
|
+
- "TLSv1.3": TLS 1.3
|
147
|
+
If None, uses min_tls_version from config.
|
148
|
+
|
149
|
+
Returns:
|
150
|
+
ssl.SSLContext: Configured SSL context for server operations.
|
151
|
+
The context is properly configured with certificates, keys,
|
152
|
+
and TLS settings for secure server communication.
|
153
|
+
|
154
|
+
Raises:
|
155
|
+
SSLConfigurationError: If SSL configuration is invalid or
|
156
|
+
certificate/key files cannot be loaded.
|
157
|
+
CertificateValidationError: If certificate validation fails.
|
158
|
+
FileNotFoundError: If certificate or key files are not found.
|
159
|
+
PermissionError: If certificate or key files are not readable.
|
160
|
+
|
161
|
+
Example:
|
162
|
+
>>> ssl_manager = SSLManager(config)
|
163
|
+
>>> context = ssl_manager.create_server_context(
|
164
|
+
... cert_file="server.crt",
|
165
|
+
... key_file="server.key",
|
166
|
+
... verify_mode="CERT_REQUIRED"
|
167
|
+
... )
|
168
|
+
>>> # Use context for HTTPS server
|
169
|
+
"""
|
170
|
+
try:
|
171
|
+
# Use config values if not provided
|
172
|
+
cert_file = cert_file or self.config.cert_file
|
173
|
+
key_file = key_file or self.config.key_file
|
174
|
+
ca_cert_file = ca_cert_file or self.config.ca_cert_file
|
175
|
+
verify_mode = verify_mode or self.config.verify_mode
|
176
|
+
min_version = min_version or self.config.min_tls_version
|
177
|
+
|
178
|
+
# Validate required files
|
179
|
+
if not cert_file:
|
180
|
+
raise SSLConfigurationError("Server certificate file is required")
|
181
|
+
if not key_file:
|
182
|
+
raise SSLConfigurationError("Server private key file is required")
|
183
|
+
|
184
|
+
# Check cache
|
185
|
+
cache_key = f"server_{cert_file}_{key_file}_{ca_cert_file}_{verify_mode}_{min_version}"
|
186
|
+
if cache_key in self._contexts:
|
187
|
+
return self._contexts[cache_key]
|
188
|
+
|
189
|
+
# Create SSL context
|
190
|
+
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
191
|
+
|
192
|
+
# Configure verification mode
|
193
|
+
context.verify_mode = self._get_verify_mode(verify_mode)
|
194
|
+
|
195
|
+
# Set minimum TLS version
|
196
|
+
context.minimum_version = self._get_tls_version(min_version)
|
197
|
+
|
198
|
+
# Load certificate and key
|
199
|
+
context.load_cert_chain(cert_file, key_file)
|
200
|
+
|
201
|
+
# Load CA certificate if provided
|
202
|
+
if ca_cert_file:
|
203
|
+
context.load_verify_locations(ca_cert_file)
|
204
|
+
|
205
|
+
# Configure cipher suites
|
206
|
+
if self.config.cipher_suite:
|
207
|
+
context.set_ciphers(self.config.cipher_suite)
|
208
|
+
|
209
|
+
# Cache context
|
210
|
+
self._contexts[cache_key] = context
|
211
|
+
|
212
|
+
self.logger.info(
|
213
|
+
"Server SSL context created successfully",
|
214
|
+
extra={
|
215
|
+
"cert_file": cert_file,
|
216
|
+
"key_file": key_file,
|
217
|
+
"verify_mode": verify_mode,
|
218
|
+
"min_version": min_version,
|
219
|
+
},
|
220
|
+
)
|
221
|
+
|
222
|
+
return context
|
223
|
+
|
224
|
+
except Exception as e:
|
225
|
+
self.logger.error(
|
226
|
+
"Failed to create server SSL context",
|
227
|
+
extra={"cert_file": cert_file, "key_file": key_file, "error": str(e)},
|
228
|
+
)
|
229
|
+
raise SSLConfigurationError(
|
230
|
+
f"Failed to create server SSL context: {str(e)}"
|
231
|
+
)
|
232
|
+
|
233
|
+
def create_client_context(
|
234
|
+
self,
|
235
|
+
ca_cert_file: Optional[str] = None,
|
236
|
+
client_cert_file: Optional[str] = None,
|
237
|
+
client_key_file: Optional[str] = None,
|
238
|
+
verify_mode: Optional[str] = None,
|
239
|
+
min_version: Optional[str] = None,
|
240
|
+
) -> ssl.SSLContext:
|
241
|
+
"""
|
242
|
+
Create SSL context for client operations.
|
243
|
+
|
244
|
+
This method creates and configures an SSL context suitable for client
|
245
|
+
operations. It handles certificate loading and TLS configuration for
|
246
|
+
secure client connections.
|
247
|
+
|
248
|
+
Args:
|
249
|
+
ca_cert_file (Optional[str]): Path to CA certificate file for
|
250
|
+
server certificate verification. If None, uses CA from config.
|
251
|
+
Must be a valid PEM certificate file path.
|
252
|
+
client_cert_file (Optional[str]): Path to client certificate file
|
253
|
+
for client authentication. If None, uses client cert from config.
|
254
|
+
Must be a valid PEM certificate file path.
|
255
|
+
client_key_file (Optional[str]): Path to client private key file.
|
256
|
+
If None, uses client key from config. Must be a valid PEM
|
257
|
+
private key file path.
|
258
|
+
verify_mode (Optional[str]): SSL verification mode. Valid values:
|
259
|
+
- "CERT_NONE": No certificate verification
|
260
|
+
- "CERT_OPTIONAL": Certificate verification optional
|
261
|
+
- "CERT_REQUIRED": Certificate verification required
|
262
|
+
If None, uses verify_mode from config.
|
263
|
+
min_version (Optional[str]): Minimum TLS version. Valid values:
|
264
|
+
- "TLSv1.0": TLS 1.0
|
265
|
+
- "TLSv1.1": TLS 1.1
|
266
|
+
- "TLSv1.2": TLS 1.2
|
267
|
+
- "TLSv1.3": TLS 1.3
|
268
|
+
If None, uses min_tls_version from config.
|
269
|
+
|
270
|
+
Returns:
|
271
|
+
ssl.SSLContext: Configured SSL context for client operations.
|
272
|
+
The context is properly configured for secure client communication.
|
273
|
+
|
274
|
+
Raises:
|
275
|
+
SSLConfigurationError: If SSL configuration is invalid or
|
276
|
+
certificate files cannot be loaded.
|
277
|
+
CertificateValidationError: If certificate validation fails.
|
278
|
+
FileNotFoundError: If certificate files are not found.
|
279
|
+
PermissionError: If certificate files are not readable.
|
280
|
+
|
281
|
+
Example:
|
282
|
+
>>> ssl_manager = SSLManager(config)
|
283
|
+
>>> context = ssl_manager.create_client_context(
|
284
|
+
... ca_cert_file="ca.crt",
|
285
|
+
... client_cert_file="client.crt",
|
286
|
+
... client_key_file="client.key"
|
287
|
+
... )
|
288
|
+
>>> # Use context for HTTPS client
|
289
|
+
"""
|
290
|
+
try:
|
291
|
+
# Use config values if not provided
|
292
|
+
ca_cert_file = ca_cert_file or self.config.ca_cert_file
|
293
|
+
client_cert_file = client_cert_file or self.config.client_cert_file
|
294
|
+
client_key_file = client_key_file or self.config.client_key_file
|
295
|
+
verify_mode = verify_mode or self.config.verify_mode
|
296
|
+
min_version = min_version or self.config.min_tls_version
|
297
|
+
|
298
|
+
# Check cache
|
299
|
+
cache_key = f"client_{ca_cert_file}_{client_cert_file}_{client_key_file}_{verify_mode}_{min_version}"
|
300
|
+
if cache_key in self._contexts:
|
301
|
+
return self._contexts[cache_key]
|
302
|
+
|
303
|
+
# Create SSL context
|
304
|
+
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
305
|
+
|
306
|
+
# Configure verification mode
|
307
|
+
context.verify_mode = self._get_verify_mode(verify_mode)
|
308
|
+
|
309
|
+
# Set minimum TLS version
|
310
|
+
context.minimum_version = self._get_tls_version(min_version)
|
311
|
+
|
312
|
+
# Load CA certificate if provided
|
313
|
+
if ca_cert_file:
|
314
|
+
context.load_verify_locations(ca_cert_file)
|
315
|
+
|
316
|
+
# Load client certificate and key if provided
|
317
|
+
if client_cert_file and client_key_file:
|
318
|
+
context.load_cert_chain(client_cert_file, client_key_file)
|
319
|
+
|
320
|
+
# Configure cipher suites
|
321
|
+
if self.config.cipher_suite:
|
322
|
+
context.set_ciphers(self.config.cipher_suite)
|
323
|
+
|
324
|
+
# Cache context
|
325
|
+
self._contexts[cache_key] = context
|
326
|
+
|
327
|
+
self.logger.info(
|
328
|
+
"Client SSL context created successfully",
|
329
|
+
extra={
|
330
|
+
"ca_cert_file": ca_cert_file,
|
331
|
+
"client_cert_file": client_cert_file,
|
332
|
+
"verify_mode": verify_mode,
|
333
|
+
"min_version": min_version,
|
334
|
+
},
|
335
|
+
)
|
336
|
+
|
337
|
+
return context
|
338
|
+
|
339
|
+
except Exception as e:
|
340
|
+
self.logger.error(
|
341
|
+
"Failed to create client SSL context",
|
342
|
+
extra={
|
343
|
+
"ca_cert_file": ca_cert_file,
|
344
|
+
"client_cert_file": client_cert_file,
|
345
|
+
"error": str(e),
|
346
|
+
},
|
347
|
+
)
|
348
|
+
raise SSLConfigurationError(
|
349
|
+
f"Failed to create client SSL context: {str(e)}"
|
350
|
+
)
|
351
|
+
|
352
|
+
def validate_certificate(self, cert_path: str) -> bool:
|
353
|
+
"""
|
354
|
+
Validate certificate file.
|
355
|
+
|
356
|
+
This method validates a certificate file by checking its format,
|
357
|
+
parsing it, and verifying basic certificate properties.
|
358
|
+
|
359
|
+
Args:
|
360
|
+
cert_path (str): Path to certificate file to validate.
|
361
|
+
Must be a valid PEM or DER certificate file path.
|
362
|
+
|
363
|
+
Returns:
|
364
|
+
bool: True if certificate is valid, False otherwise.
|
365
|
+
Returns True when certificate can be parsed and has
|
366
|
+
valid basic properties.
|
367
|
+
|
368
|
+
Raises:
|
369
|
+
FileNotFoundError: If certificate file is not found.
|
370
|
+
PermissionError: If certificate file is not readable.
|
371
|
+
|
372
|
+
Example:
|
373
|
+
>>> ssl_manager = SSLManager(config)
|
374
|
+
>>> is_valid = ssl_manager.validate_certificate("server.crt")
|
375
|
+
>>> if is_valid:
|
376
|
+
... print("Certificate is valid")
|
377
|
+
>>> else:
|
378
|
+
... print("Certificate is invalid")
|
379
|
+
"""
|
380
|
+
try:
|
381
|
+
# Check if file exists
|
382
|
+
if not Path(cert_path).exists():
|
383
|
+
self.logger.error(f"Certificate file not found: {cert_path}")
|
384
|
+
return False
|
385
|
+
|
386
|
+
# Parse certificate
|
387
|
+
cert = parse_certificate(cert_path)
|
388
|
+
|
389
|
+
# Basic validation
|
390
|
+
if not cert:
|
391
|
+
return False
|
392
|
+
|
393
|
+
# Check if certificate is expired
|
394
|
+
expiry_info = get_certificate_expiry(cert_path)
|
395
|
+
if expiry_info["is_expired"]:
|
396
|
+
self.logger.warning(
|
397
|
+
"Certificate is expired",
|
398
|
+
extra={
|
399
|
+
"cert_path": cert_path,
|
400
|
+
"expiry_date": expiry_info["not_after"],
|
401
|
+
},
|
402
|
+
)
|
403
|
+
return False
|
404
|
+
|
405
|
+
self.logger.info(
|
406
|
+
"Certificate validation successful", extra={"cert_path": cert_path}
|
407
|
+
)
|
408
|
+
|
409
|
+
return True
|
410
|
+
|
411
|
+
except Exception as e:
|
412
|
+
self.logger.error(
|
413
|
+
"Certificate validation failed",
|
414
|
+
extra={"cert_path": cert_path, "error": str(e)},
|
415
|
+
)
|
416
|
+
return False
|
417
|
+
|
418
|
+
def get_certificate_info(self, cert_path: str) -> CertificateInfo:
|
419
|
+
"""
|
420
|
+
Get detailed certificate information.
|
421
|
+
|
422
|
+
This method extracts comprehensive information from a certificate
|
423
|
+
including subject, issuer, validity dates, and extensions.
|
424
|
+
|
425
|
+
Args:
|
426
|
+
cert_path (str): Path to certificate file. Must be a valid
|
427
|
+
PEM or DER certificate file path.
|
428
|
+
|
429
|
+
Returns:
|
430
|
+
CertificateInfo: Certificate information object containing:
|
431
|
+
- subject: Certificate subject
|
432
|
+
- issuer: Certificate issuer
|
433
|
+
- serial_number: Certificate serial number
|
434
|
+
- not_before: Certificate validity start date
|
435
|
+
- not_after: Certificate validity end date
|
436
|
+
- key_algorithm: Public key algorithm
|
437
|
+
- key_size: Public key size in bits
|
438
|
+
- signature_algorithm: Signature algorithm
|
439
|
+
- extensions: Certificate extensions
|
440
|
+
- is_self_signed: Whether certificate is self-signed
|
441
|
+
- fingerprint_sha1: SHA1 fingerprint
|
442
|
+
- fingerprint_sha256: SHA256 fingerprint
|
443
|
+
|
444
|
+
Raises:
|
445
|
+
CertificateValidationError: If certificate cannot be parsed
|
446
|
+
or information extraction fails.
|
447
|
+
FileNotFoundError: If certificate file is not found.
|
448
|
+
PermissionError: If certificate file is not readable.
|
449
|
+
|
450
|
+
Example:
|
451
|
+
>>> ssl_manager = SSLManager(config)
|
452
|
+
>>> info = ssl_manager.get_certificate_info("server.crt")
|
453
|
+
>>> print(f"Subject: {info.subject}")
|
454
|
+
>>> print(f"Expires: {info.not_after}")
|
455
|
+
>>> print(f"Key size: {info.key_size} bits")
|
456
|
+
"""
|
457
|
+
try:
|
458
|
+
# Check cache first
|
459
|
+
if cert_path in self._certificate_cache:
|
460
|
+
return self._certificate_cache[cert_path]
|
461
|
+
|
462
|
+
# Extract certificate information
|
463
|
+
cert_data = extract_certificate_info(cert_path)
|
464
|
+
|
465
|
+
# Create CertificateInfo object
|
466
|
+
cert_info = CertificateInfo(
|
467
|
+
subject=cert_data.get("subject", ""),
|
468
|
+
issuer=cert_data.get("issuer", ""),
|
469
|
+
serial_number=cert_data.get("serial_number", ""),
|
470
|
+
not_before=cert_data.get("not_before"),
|
471
|
+
not_after=cert_data.get("not_after"),
|
472
|
+
key_algorithm=cert_data.get("public_key_algorithm", ""),
|
473
|
+
key_size=cert_data.get("key_size", 0),
|
474
|
+
signature_algorithm=cert_data.get("signature_algorithm", ""),
|
475
|
+
extensions=cert_data.get("extensions", {}),
|
476
|
+
is_self_signed=is_certificate_self_signed(cert_path),
|
477
|
+
fingerprint_sha1=cert_data.get("fingerprint_sha1", ""),
|
478
|
+
fingerprint_sha256=cert_data.get("fingerprint_sha256", ""),
|
479
|
+
)
|
480
|
+
|
481
|
+
# Cache the result
|
482
|
+
self._certificate_cache[cert_path] = cert_info
|
483
|
+
|
484
|
+
self.logger.info(
|
485
|
+
"Certificate information extracted successfully",
|
486
|
+
extra={"cert_path": cert_path},
|
487
|
+
)
|
488
|
+
|
489
|
+
return cert_info
|
490
|
+
|
491
|
+
except Exception as e:
|
492
|
+
self.logger.error(
|
493
|
+
"Failed to get certificate information",
|
494
|
+
extra={"cert_path": cert_path, "error": str(e)},
|
495
|
+
)
|
496
|
+
raise CertificateValidationError(
|
497
|
+
f"Failed to get certificate information: {str(e)}"
|
498
|
+
)
|
499
|
+
|
500
|
+
def validate_certificate_chain(self, cert_path: str, ca_cert_path: str) -> bool:
|
501
|
+
"""
|
502
|
+
Validate certificate chain against CA certificate.
|
503
|
+
|
504
|
+
This method validates a certificate chain by checking if the
|
505
|
+
certificate is signed by the provided CA certificate.
|
506
|
+
|
507
|
+
Args:
|
508
|
+
cert_path (str): Path to certificate to validate. Must be a
|
509
|
+
valid PEM or DER certificate file path.
|
510
|
+
ca_cert_path (str): Path to CA certificate. Must be a valid
|
511
|
+
PEM or DER certificate file path.
|
512
|
+
|
513
|
+
Returns:
|
514
|
+
bool: True if certificate chain is valid, False otherwise.
|
515
|
+
Returns True when certificate is properly signed by the
|
516
|
+
CA certificate.
|
517
|
+
|
518
|
+
Raises:
|
519
|
+
FileNotFoundError: If certificate files are not found.
|
520
|
+
PermissionError: If certificate files are not readable.
|
521
|
+
|
522
|
+
Example:
|
523
|
+
>>> ssl_manager = SSLManager(config)
|
524
|
+
>>> is_valid = ssl_manager.validate_certificate_chain(
|
525
|
+
... "server.crt", "ca.crt"
|
526
|
+
... )
|
527
|
+
>>> if is_valid:
|
528
|
+
... print("Certificate chain is valid")
|
529
|
+
>>> else:
|
530
|
+
... print("Certificate chain is invalid")
|
531
|
+
"""
|
532
|
+
try:
|
533
|
+
# Validate certificate chain
|
534
|
+
is_valid = validate_certificate_chain(cert_path, ca_cert_path)
|
535
|
+
|
536
|
+
self.logger.info(
|
537
|
+
"Certificate chain validation completed",
|
538
|
+
extra={
|
539
|
+
"cert_path": cert_path,
|
540
|
+
"ca_cert_path": ca_cert_path,
|
541
|
+
"is_valid": is_valid,
|
542
|
+
},
|
543
|
+
)
|
544
|
+
|
545
|
+
return is_valid
|
546
|
+
|
547
|
+
except Exception as e:
|
548
|
+
self.logger.error(
|
549
|
+
"Certificate chain validation failed",
|
550
|
+
extra={
|
551
|
+
"cert_path": cert_path,
|
552
|
+
"ca_cert_path": ca_cert_path,
|
553
|
+
"error": str(e),
|
554
|
+
},
|
555
|
+
)
|
556
|
+
return False
|
557
|
+
|
558
|
+
def check_certificate_expiry(self, cert_path: str) -> Dict:
|
559
|
+
"""
|
560
|
+
Check certificate expiry information.
|
561
|
+
|
562
|
+
This method provides detailed information about certificate
|
563
|
+
expiry including days until expiry and expiry status.
|
564
|
+
|
565
|
+
Args:
|
566
|
+
cert_path (str): Path to certificate file. Must be a valid
|
567
|
+
PEM or DER certificate file path.
|
568
|
+
|
569
|
+
Returns:
|
570
|
+
Dict: Certificate expiry information containing:
|
571
|
+
- not_after: Certificate expiry date
|
572
|
+
- not_before: Certificate validity start date
|
573
|
+
- days_until_expiry: Days until certificate expires
|
574
|
+
- is_expired: Whether certificate is expired
|
575
|
+
- expires_soon: Whether certificate expires within 30 days
|
576
|
+
- status: Expiry status (valid, expires_soon, expired)
|
577
|
+
- total_seconds_until_expiry: Seconds until expiry
|
578
|
+
|
579
|
+
Raises:
|
580
|
+
CertificateValidationError: If certificate cannot be parsed
|
581
|
+
or expiry information extraction fails.
|
582
|
+
FileNotFoundError: If certificate file is not found.
|
583
|
+
PermissionError: If certificate file is not readable.
|
584
|
+
|
585
|
+
Example:
|
586
|
+
>>> ssl_manager = SSLManager(config)
|
587
|
+
>>> expiry_info = ssl_manager.check_certificate_expiry("server.crt")
|
588
|
+
>>> if expiry_info["is_expired"]:
|
589
|
+
... print("Certificate is expired!")
|
590
|
+
>>> elif expiry_info["expires_soon"]:
|
591
|
+
... print(f"Certificate expires in {expiry_info['days_until_expiry']} days")
|
592
|
+
"""
|
593
|
+
try:
|
594
|
+
expiry_info = get_certificate_expiry(cert_path)
|
595
|
+
|
596
|
+
self.logger.info(
|
597
|
+
"Certificate expiry check completed",
|
598
|
+
extra={
|
599
|
+
"cert_path": cert_path,
|
600
|
+
"days_until_expiry": expiry_info["days_until_expiry"],
|
601
|
+
"status": expiry_info["status"],
|
602
|
+
},
|
603
|
+
)
|
604
|
+
|
605
|
+
return expiry_info
|
606
|
+
|
607
|
+
except Exception as e:
|
608
|
+
self.logger.error(
|
609
|
+
"Certificate expiry check failed",
|
610
|
+
extra={"cert_path": cert_path, "error": str(e)},
|
611
|
+
)
|
612
|
+
raise CertificateValidationError(
|
613
|
+
f"Certificate expiry check failed: {str(e)}"
|
614
|
+
)
|
615
|
+
|
616
|
+
def clear_cache(self) -> None:
|
617
|
+
"""Clear SSL context and certificate caches."""
|
618
|
+
self._contexts.clear()
|
619
|
+
self._certificate_cache.clear()
|
620
|
+
self.logger.info("SSL caches cleared")
|
621
|
+
|
622
|
+
@property
|
623
|
+
def is_ssl_enabled(self) -> bool:
|
624
|
+
"""
|
625
|
+
Check if SSL/TLS is enabled in the configuration.
|
626
|
+
|
627
|
+
This property indicates whether SSL/TLS functionality is enabled
|
628
|
+
based on the current configuration settings.
|
629
|
+
|
630
|
+
Returns:
|
631
|
+
bool: True if SSL/TLS is enabled, False otherwise.
|
632
|
+
Returns True when config.enabled is True and valid
|
633
|
+
certificate/key files are configured.
|
634
|
+
|
635
|
+
Example:
|
636
|
+
>>> ssl_manager = SSLManager(config)
|
637
|
+
>>> if ssl_manager.is_ssl_enabled:
|
638
|
+
... context = ssl_manager.create_server_context()
|
639
|
+
... print("SSL context created successfully")
|
640
|
+
>>> else:
|
641
|
+
... print("SSL is not properly configured")
|
642
|
+
"""
|
643
|
+
return (
|
644
|
+
self.config.enabled
|
645
|
+
and self.config.cert_file
|
646
|
+
and self.config.key_file
|
647
|
+
and Path(self.config.cert_file).exists()
|
648
|
+
and Path(self.config.key_file).exists()
|
649
|
+
)
|
650
|
+
|
651
|
+
@property
|
652
|
+
def supported_tls_versions(self) -> List[str]:
|
653
|
+
"""
|
654
|
+
Get list of supported TLS versions.
|
655
|
+
|
656
|
+
Returns:
|
657
|
+
List[str]: List of supported TLS version strings.
|
658
|
+
"""
|
659
|
+
return ["TLSv1.0", "TLSv1.1", "TLSv1.2", "TLSv1.3"]
|
660
|
+
|
661
|
+
@property
|
662
|
+
def default_cipher_suite(self) -> str:
|
663
|
+
"""
|
664
|
+
Get default cipher suite configuration.
|
665
|
+
|
666
|
+
Returns:
|
667
|
+
str: Default cipher suite string.
|
668
|
+
"""
|
669
|
+
return self.config.cipher_suite or "ECDHE-RSA-AES256-GCM-SHA384"
|
670
|
+
|
671
|
+
def _validate_configuration(self) -> None:
|
672
|
+
"""Validate SSL configuration."""
|
673
|
+
if not self.config.enabled:
|
674
|
+
return
|
675
|
+
|
676
|
+
# Check certificate files if SSL is enabled
|
677
|
+
if self.config.cert_file and not Path(self.config.cert_file).exists():
|
678
|
+
raise SSLConfigurationError(
|
679
|
+
f"Certificate file not found: {self.config.cert_file}"
|
680
|
+
)
|
681
|
+
|
682
|
+
if self.config.key_file and not Path(self.config.key_file).exists():
|
683
|
+
raise SSLConfigurationError(
|
684
|
+
f"Private key file not found: {self.config.key_file}"
|
685
|
+
)
|
686
|
+
|
687
|
+
if self.config.ca_cert_file and not Path(self.config.ca_cert_file).exists():
|
688
|
+
raise SSLConfigurationError(
|
689
|
+
f"CA certificate file not found: {self.config.ca_cert_file}"
|
690
|
+
)
|
691
|
+
|
692
|
+
def _get_verify_mode(self, verify_mode: str) -> int:
|
693
|
+
"""Convert verify mode string to SSL constant."""
|
694
|
+
verify_modes = {
|
695
|
+
"CERT_NONE": ssl.CERT_NONE,
|
696
|
+
"CERT_OPTIONAL": ssl.CERT_OPTIONAL,
|
697
|
+
"CERT_REQUIRED": ssl.CERT_REQUIRED,
|
698
|
+
}
|
699
|
+
|
700
|
+
if verify_mode not in verify_modes:
|
701
|
+
raise SSLConfigurationError(f"Invalid verify mode: {verify_mode}")
|
702
|
+
|
703
|
+
return verify_modes[verify_mode]
|
704
|
+
|
705
|
+
def _get_tls_version(self, version: str) -> int:
|
706
|
+
"""Convert TLS version string to SSL constant."""
|
707
|
+
tls_versions = {
|
708
|
+
"TLSv1.0": ssl.TLSVersion.TLSv1,
|
709
|
+
"TLSv1.1": ssl.TLSVersion.TLSv1_1,
|
710
|
+
"TLSv1.2": ssl.TLSVersion.TLSv1_2,
|
711
|
+
"TLSv1.3": ssl.TLSVersion.TLSv1_3,
|
712
|
+
}
|
713
|
+
|
714
|
+
if version not in tls_versions:
|
715
|
+
raise SSLConfigurationError(f"Invalid TLS version: {version}")
|
716
|
+
|
717
|
+
return tls_versions[version]
|
718
|
+
|
719
|
+
|
720
|
+
class SSLConfigurationError(Exception):
|
721
|
+
"""Raised when SSL configuration is invalid."""
|
722
|
+
|
723
|
+
def __init__(self, message: str, error_code: int = -32001):
|
724
|
+
self.message = message
|
725
|
+
self.error_code = error_code
|
726
|
+
super().__init__(self.message)
|
727
|
+
|
728
|
+
|
729
|
+
class CertificateValidationError(Exception):
|
730
|
+
"""Raised when certificate validation fails."""
|
731
|
+
|
732
|
+
def __init__(self, message: str, error_code: int = -32002):
|
733
|
+
self.message = message
|
734
|
+
self.error_code = error_code
|
735
|
+
super().__init__(self.message)
|