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,96 @@
|
|
1
|
+
"""
|
2
|
+
MCP Security Framework
|
3
|
+
|
4
|
+
Universal security framework for microservices with SSL/TLS, authentication,
|
5
|
+
authorization, and rate limiting.
|
6
|
+
|
7
|
+
This framework provides comprehensive security capabilities for Python applications,
|
8
|
+
including multi-method authentication, SSL/TLS management, role-based authorization,
|
9
|
+
and rate limiting. It supports multiple web frameworks including FastAPI, Flask,
|
10
|
+
and Django, as well as standalone usage.
|
11
|
+
|
12
|
+
Key Features:
|
13
|
+
- Multi-method Authentication (API keys, JWT tokens, X.509 certificates)
|
14
|
+
- SSL/TLS Management (Server and client certificate handling)
|
15
|
+
- Role-based Authorization (Flexible permission system with role hierarchy)
|
16
|
+
- Rate Limiting (Configurable request rate limiting)
|
17
|
+
- Framework Agnostic (Works with FastAPI, Flask, Django, and standalone)
|
18
|
+
- CLI Tools (Certificate management and security testing)
|
19
|
+
- Comprehensive Logging (Security event logging and monitoring)
|
20
|
+
|
21
|
+
Example Usage:
|
22
|
+
>>> from mcp_security_framework import SecurityManager, SecurityConfig
|
23
|
+
>>> from mcp_security_framework.schemas.config import AuthConfig
|
24
|
+
>>>
|
25
|
+
>>> config = SecurityConfig(
|
26
|
+
... auth=AuthConfig(
|
27
|
+
... enabled=True,
|
28
|
+
... methods=["api_key"],
|
29
|
+
... api_keys={"admin": "admin_key_123"}
|
30
|
+
... )
|
31
|
+
... )
|
32
|
+
>>>
|
33
|
+
>>> security_manager = SecurityManager(config)
|
34
|
+
>>> result = security_manager.validate_request({
|
35
|
+
... "api_key": "admin_key_123",
|
36
|
+
... "required_permissions": ["read", "write"]
|
37
|
+
... })
|
38
|
+
>>>
|
39
|
+
>>> if result.is_valid:
|
40
|
+
... print("Access granted!")
|
41
|
+
>>> else:
|
42
|
+
... print(f"Access denied: {result.error_message}")
|
43
|
+
|
44
|
+
Author: Vasiliy Zdanovskiy
|
45
|
+
Version: 0.1.0
|
46
|
+
License: MIT
|
47
|
+
"""
|
48
|
+
|
49
|
+
from mcp_security_framework.core.security_manager import SecurityManager
|
50
|
+
from mcp_security_framework.core.auth_manager import AuthManager
|
51
|
+
from mcp_security_framework.core.cert_manager import CertificateManager
|
52
|
+
from mcp_security_framework.core.permission_manager import PermissionManager
|
53
|
+
from mcp_security_framework.core.rate_limiter import RateLimiter
|
54
|
+
from mcp_security_framework.schemas.config import SecurityConfig, AuthConfig, SSLConfig, PermissionConfig, RateLimitConfig
|
55
|
+
from mcp_security_framework.schemas.models import AuthResult, ValidationResult, CertificateInfo, CertificatePair
|
56
|
+
from mcp_security_framework.schemas.responses import SecurityResponse, ErrorResponse, SuccessResponse
|
57
|
+
|
58
|
+
# Version information
|
59
|
+
__version__ = "0.1.0"
|
60
|
+
__author__ = "Vasiliy Zdanovskiy"
|
61
|
+
__email__ = "vasilyvz@gmail.com"
|
62
|
+
__license__ = "MIT"
|
63
|
+
|
64
|
+
# Main exports
|
65
|
+
__all__ = [
|
66
|
+
# Core managers
|
67
|
+
"SecurityManager",
|
68
|
+
"AuthManager",
|
69
|
+
"CertificateManager",
|
70
|
+
"PermissionManager",
|
71
|
+
"RateLimiter",
|
72
|
+
|
73
|
+
# Configuration schemas
|
74
|
+
"SecurityConfig",
|
75
|
+
"AuthConfig",
|
76
|
+
"SSLConfig",
|
77
|
+
"PermissionConfig",
|
78
|
+
"RateLimitConfig",
|
79
|
+
|
80
|
+
# Data models
|
81
|
+
"AuthResult",
|
82
|
+
"ValidationResult",
|
83
|
+
"CertificateInfo",
|
84
|
+
"CertificatePair",
|
85
|
+
|
86
|
+
# Response schemas
|
87
|
+
"SecurityResponse",
|
88
|
+
"ErrorResponse",
|
89
|
+
"SuccessResponse",
|
90
|
+
|
91
|
+
# Version info
|
92
|
+
"__version__",
|
93
|
+
"__author__",
|
94
|
+
"__email__",
|
95
|
+
"__license__",
|
96
|
+
]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
"""
|
2
|
+
CLI Module
|
3
|
+
|
4
|
+
This module provides command-line interface tools for the MCP Security Framework.
|
5
|
+
|
6
|
+
Available CLI tools:
|
7
|
+
- cert_cli: Certificate management CLI
|
8
|
+
- security_cli: Security management CLI
|
9
|
+
|
10
|
+
Author: MCP Security Team
|
11
|
+
Version: 1.0.0
|
12
|
+
License: MIT
|
13
|
+
"""
|
14
|
+
|
15
|
+
from .cert_cli import cert_cli
|
16
|
+
from .security_cli import security_cli
|
17
|
+
|
18
|
+
__all__ = ['cert_cli', 'security_cli']
|
@@ -0,0 +1,511 @@
|
|
1
|
+
"""
|
2
|
+
Certificate Management CLI Module
|
3
|
+
|
4
|
+
This module provides command-line interface for certificate management
|
5
|
+
operations including creation, validation, and management of certificates.
|
6
|
+
|
7
|
+
Key Features:
|
8
|
+
- Root CA certificate creation
|
9
|
+
- Server certificate generation
|
10
|
+
- Client certificate generation
|
11
|
+
- Certificate validation and verification
|
12
|
+
- Certificate information display
|
13
|
+
- Certificate revocation
|
14
|
+
|
15
|
+
Commands:
|
16
|
+
create-ca: Create root CA certificate
|
17
|
+
create-server: Create server certificate
|
18
|
+
create-client: Create client certificate
|
19
|
+
validate: Validate certificate
|
20
|
+
info: Display certificate information
|
21
|
+
revoke: Revoke certificate
|
22
|
+
|
23
|
+
Author: MCP Security Team
|
24
|
+
Version: 1.0.0
|
25
|
+
License: MIT
|
26
|
+
"""
|
27
|
+
|
28
|
+
import click
|
29
|
+
import json
|
30
|
+
import os
|
31
|
+
from pathlib import Path
|
32
|
+
from typing import Optional
|
33
|
+
|
34
|
+
from ..core.cert_manager import CertificateManager
|
35
|
+
from ..schemas.config import CertificateConfig, CAConfig, ServerCertConfig, ClientCertConfig
|
36
|
+
from ..schemas.models import CertificateType
|
37
|
+
from ..schemas.config import IntermediateCAConfig
|
38
|
+
|
39
|
+
|
40
|
+
@click.group()
|
41
|
+
@click.option('--config', '-c', 'config_path',
|
42
|
+
help='Path to certificate configuration file')
|
43
|
+
@click.option('--verbose', '-v', is_flag=True,
|
44
|
+
help='Enable verbose output')
|
45
|
+
@click.pass_context
|
46
|
+
def cert_cli(ctx, config_path: Optional[str], verbose: bool):
|
47
|
+
"""
|
48
|
+
Certificate Management CLI
|
49
|
+
|
50
|
+
Manage certificates including creation, validation, and management.
|
51
|
+
"""
|
52
|
+
ctx.ensure_object(dict)
|
53
|
+
ctx.obj['verbose'] = verbose
|
54
|
+
|
55
|
+
# Load configuration
|
56
|
+
if config_path and os.path.exists(config_path):
|
57
|
+
with open(config_path, 'r') as f:
|
58
|
+
config_data = json.load(f)
|
59
|
+
ctx.obj['config'] = CertificateConfig(**config_data)
|
60
|
+
else:
|
61
|
+
# Use default configuration
|
62
|
+
ctx.obj['config'] = CertificateConfig(
|
63
|
+
cert_storage_path="./certs",
|
64
|
+
key_storage_path="./keys",
|
65
|
+
ca_cert_path="./certs/ca_cert.pem",
|
66
|
+
ca_key_path="./keys/ca_key.pem"
|
67
|
+
)
|
68
|
+
|
69
|
+
# Create certificate manager
|
70
|
+
ctx.obj['cert_manager'] = CertificateManager(ctx.obj['config'])
|
71
|
+
|
72
|
+
|
73
|
+
@cert_cli.command()
|
74
|
+
@click.option('--common-name', '-cn', required=True,
|
75
|
+
help='Common name for the CA certificate')
|
76
|
+
@click.option('--organization', '-o', required=True,
|
77
|
+
help='Organization name')
|
78
|
+
@click.option('--country', '-c', required=True,
|
79
|
+
help='Country code (e.g., US, GB)')
|
80
|
+
@click.option('--state', '-s',
|
81
|
+
help='State or province')
|
82
|
+
@click.option('--locality', '-l',
|
83
|
+
help='Locality or city')
|
84
|
+
@click.option('--email', '-e',
|
85
|
+
help='Email address')
|
86
|
+
@click.option('--validity-years', '-y', default=10,
|
87
|
+
help='Certificate validity in years')
|
88
|
+
@click.option('--key-size', '-k', default=2048,
|
89
|
+
help='RSA key size')
|
90
|
+
@click.pass_context
|
91
|
+
def create_ca(ctx, common_name: str, organization: str, country: str,
|
92
|
+
state: Optional[str], locality: Optional[str],
|
93
|
+
email: Optional[str], validity_years: int, key_size: int):
|
94
|
+
"""
|
95
|
+
Create a root CA certificate.
|
96
|
+
|
97
|
+
This command creates a new root Certificate Authority (CA) certificate
|
98
|
+
that can be used to sign other certificates.
|
99
|
+
"""
|
100
|
+
try:
|
101
|
+
config = ctx.obj['config']
|
102
|
+
cert_manager = ctx.obj['cert_manager']
|
103
|
+
verbose = ctx.obj['verbose']
|
104
|
+
|
105
|
+
# Create CA configuration
|
106
|
+
ca_config = CAConfig(
|
107
|
+
common_name=common_name,
|
108
|
+
organization=organization,
|
109
|
+
country=country,
|
110
|
+
state=state,
|
111
|
+
locality=locality,
|
112
|
+
email=email,
|
113
|
+
validity_years=validity_years,
|
114
|
+
key_size=key_size
|
115
|
+
)
|
116
|
+
|
117
|
+
if verbose:
|
118
|
+
click.echo(f"Creating CA certificate with configuration:")
|
119
|
+
click.echo(f" Common Name: {common_name}")
|
120
|
+
click.echo(f" Organization: {organization}")
|
121
|
+
click.echo(f" Country: {country}")
|
122
|
+
click.echo(f" Validity: {validity_years} years")
|
123
|
+
click.echo(f" Key Size: {key_size} bits")
|
124
|
+
|
125
|
+
# Create CA certificate
|
126
|
+
cert_pair = cert_manager.create_root_ca(ca_config)
|
127
|
+
|
128
|
+
click.echo(f"✅ CA certificate created successfully!")
|
129
|
+
click.echo(f" Certificate: {cert_pair.certificate_path}")
|
130
|
+
click.echo(f" Private Key: {cert_pair.private_key_path}")
|
131
|
+
click.echo(f" Serial Number: {cert_pair.serial_number}")
|
132
|
+
click.echo(f" Valid Until: {cert_pair.not_after}")
|
133
|
+
|
134
|
+
except Exception as e:
|
135
|
+
click.echo(f"❌ Failed to create CA certificate: {str(e)}", err=True)
|
136
|
+
raise click.Abort()
|
137
|
+
|
138
|
+
|
139
|
+
@cert_cli.command()
|
140
|
+
@click.option('--common-name', '-cn', required=True,
|
141
|
+
help='Common name for the server certificate')
|
142
|
+
@click.option('--organization', '-o', required=True,
|
143
|
+
help='Organization name')
|
144
|
+
@click.option('--country', '-c', required=True,
|
145
|
+
help='Country code (e.g., US, GB)')
|
146
|
+
@click.option('--state', '-s',
|
147
|
+
help='State or province')
|
148
|
+
@click.option('--locality', '-l',
|
149
|
+
help='Locality or city')
|
150
|
+
@click.option('--email', '-e',
|
151
|
+
help='Email address')
|
152
|
+
@click.option('--validity-days', '-d', default=365,
|
153
|
+
help='Certificate validity in days')
|
154
|
+
@click.option('--key-size', '-k', default=2048,
|
155
|
+
help='RSA key size')
|
156
|
+
@click.option('--san', multiple=True,
|
157
|
+
help='Subject Alternative Names (can be specified multiple times)')
|
158
|
+
@click.pass_context
|
159
|
+
def create_server(ctx, common_name: str, organization: str, country: str,
|
160
|
+
state: Optional[str], locality: Optional[str],
|
161
|
+
email: Optional[str], validity_days: int, key_size: int,
|
162
|
+
san: tuple):
|
163
|
+
"""
|
164
|
+
Create a server certificate.
|
165
|
+
|
166
|
+
This command creates a new server certificate signed by the configured CA.
|
167
|
+
"""
|
168
|
+
try:
|
169
|
+
config = ctx.obj['config']
|
170
|
+
cert_manager = ctx.obj['cert_manager']
|
171
|
+
verbose = ctx.obj['verbose']
|
172
|
+
|
173
|
+
# Create server configuration
|
174
|
+
server_config = ServerCertConfig(
|
175
|
+
common_name=common_name,
|
176
|
+
organization=organization,
|
177
|
+
country=country,
|
178
|
+
state=state,
|
179
|
+
locality=locality,
|
180
|
+
email=email,
|
181
|
+
validity_days=validity_days,
|
182
|
+
key_size=key_size,
|
183
|
+
subject_alt_names=list(san) if san else None
|
184
|
+
)
|
185
|
+
|
186
|
+
if verbose:
|
187
|
+
click.echo(f"Creating server certificate with configuration:")
|
188
|
+
click.echo(f" Common Name: {common_name}")
|
189
|
+
click.echo(f" Organization: {organization}")
|
190
|
+
click.echo(f" Country: {country}")
|
191
|
+
click.echo(f" Validity: {validity_days} days")
|
192
|
+
click.echo(f" Key Size: {key_size} bits")
|
193
|
+
if san:
|
194
|
+
click.echo(f" SAN: {', '.join(san)}")
|
195
|
+
|
196
|
+
# Create server certificate
|
197
|
+
cert_pair = cert_manager.create_server_certificate(server_config)
|
198
|
+
|
199
|
+
click.echo(f"✅ Server certificate created successfully!")
|
200
|
+
click.echo(f" Certificate: {cert_pair.certificate_path}")
|
201
|
+
click.echo(f" Private Key: {cert_pair.private_key_path}")
|
202
|
+
click.echo(f" Serial Number: {cert_pair.serial_number}")
|
203
|
+
click.echo(f" Valid Until: {cert_pair.not_after}")
|
204
|
+
|
205
|
+
except Exception as e:
|
206
|
+
click.echo(f"❌ Failed to create server certificate: {str(e)}", err=True)
|
207
|
+
raise click.Abort()
|
208
|
+
|
209
|
+
|
210
|
+
@cert_cli.command()
|
211
|
+
@click.option('--common-name', '-cn', required=True,
|
212
|
+
help='Common name for the client certificate')
|
213
|
+
@click.option('--organization', '-o', required=True,
|
214
|
+
help='Organization name')
|
215
|
+
@click.option('--country', '-c', required=True,
|
216
|
+
help='Country code (e.g., US, GB)')
|
217
|
+
@click.option('--state', '-s',
|
218
|
+
help='State or province')
|
219
|
+
@click.option('--locality', '-l',
|
220
|
+
help='Locality or city')
|
221
|
+
@click.option('--email', '-e',
|
222
|
+
help='Email address')
|
223
|
+
@click.option('--validity-days', '-d', default=365,
|
224
|
+
help='Certificate validity in days')
|
225
|
+
@click.option('--key-size', '-k', default=2048,
|
226
|
+
help='RSA key size')
|
227
|
+
@click.option('--roles', multiple=True,
|
228
|
+
help='Roles to assign to the client (can be specified multiple times)')
|
229
|
+
@click.option('--permissions', multiple=True,
|
230
|
+
help='Permissions to assign to the client (can be specified multiple times)')
|
231
|
+
@click.pass_context
|
232
|
+
def create_client(ctx, common_name: str, organization: str, country: str,
|
233
|
+
state: Optional[str], locality: Optional[str],
|
234
|
+
email: Optional[str], validity_days: int, key_size: int,
|
235
|
+
roles: tuple, permissions: tuple):
|
236
|
+
"""
|
237
|
+
Create a client certificate.
|
238
|
+
|
239
|
+
This command creates a new client certificate signed by the configured CA.
|
240
|
+
"""
|
241
|
+
try:
|
242
|
+
config = ctx.obj['config']
|
243
|
+
cert_manager = ctx.obj['cert_manager']
|
244
|
+
verbose = ctx.obj['verbose']
|
245
|
+
|
246
|
+
# Create client configuration
|
247
|
+
client_config = ClientCertConfig(
|
248
|
+
common_name=common_name,
|
249
|
+
organization=organization,
|
250
|
+
country=country,
|
251
|
+
state=state,
|
252
|
+
locality=locality,
|
253
|
+
email=email,
|
254
|
+
validity_days=validity_days,
|
255
|
+
key_size=key_size,
|
256
|
+
roles=list(roles) if roles else None,
|
257
|
+
permissions=list(permissions) if permissions else None
|
258
|
+
)
|
259
|
+
|
260
|
+
if verbose:
|
261
|
+
click.echo(f"Creating client certificate with configuration:")
|
262
|
+
click.echo(f" Common Name: {common_name}")
|
263
|
+
click.echo(f" Organization: {organization}")
|
264
|
+
click.echo(f" Country: {country}")
|
265
|
+
click.echo(f" Validity: {validity_days} days")
|
266
|
+
click.echo(f" Key Size: {key_size} bits")
|
267
|
+
if roles:
|
268
|
+
click.echo(f" Roles: {', '.join(roles)}")
|
269
|
+
if permissions:
|
270
|
+
click.echo(f" Permissions: {', '.join(permissions)}")
|
271
|
+
|
272
|
+
# Create client certificate
|
273
|
+
cert_pair = cert_manager.create_client_certificate(client_config)
|
274
|
+
|
275
|
+
click.echo(f"✅ Client certificate created successfully!")
|
276
|
+
click.echo(f" Certificate: {cert_pair.certificate_path}")
|
277
|
+
click.echo(f" Private Key: {cert_pair.private_key_path}")
|
278
|
+
click.echo(f" Serial Number: {cert_pair.serial_number}")
|
279
|
+
click.echo(f" Valid Until: {cert_pair.not_after}")
|
280
|
+
|
281
|
+
except Exception as e:
|
282
|
+
click.echo(f"❌ Failed to create client certificate: {str(e)}", err=True)
|
283
|
+
raise click.Abort()
|
284
|
+
|
285
|
+
|
286
|
+
@cert_cli.command()
|
287
|
+
@click.argument('cert_path', type=click.Path(exists=True))
|
288
|
+
@click.option('--ca-cert', type=click.Path(exists=True),
|
289
|
+
help='Path to CA certificate for validation')
|
290
|
+
@click.pass_context
|
291
|
+
def validate(ctx, cert_path: str, ca_cert: Optional[str]):
|
292
|
+
"""
|
293
|
+
Validate a certificate.
|
294
|
+
|
295
|
+
This command validates a certificate and optionally checks it against a CA.
|
296
|
+
"""
|
297
|
+
try:
|
298
|
+
config = ctx.obj['config']
|
299
|
+
cert_manager = ctx.obj['cert_manager']
|
300
|
+
verbose = ctx.obj['verbose']
|
301
|
+
|
302
|
+
if verbose:
|
303
|
+
click.echo(f"Validating certificate: {cert_path}")
|
304
|
+
if ca_cert:
|
305
|
+
click.echo(f"Using CA certificate: {ca_cert}")
|
306
|
+
|
307
|
+
# Validate certificate
|
308
|
+
is_valid = cert_manager.validate_certificate_chain(cert_path, ca_cert)
|
309
|
+
|
310
|
+
if is_valid:
|
311
|
+
click.echo(f"✅ Certificate is valid!")
|
312
|
+
else:
|
313
|
+
click.echo(f"❌ Certificate validation failed!", err=True)
|
314
|
+
raise click.Abort()
|
315
|
+
|
316
|
+
except Exception as e:
|
317
|
+
click.echo(f"❌ Certificate validation failed: {str(e)}", err=True)
|
318
|
+
raise click.Abort()
|
319
|
+
|
320
|
+
|
321
|
+
@cert_cli.command()
|
322
|
+
@click.argument('cert_path', type=click.Path(exists=True))
|
323
|
+
@click.pass_context
|
324
|
+
def info(ctx, cert_path: str):
|
325
|
+
"""
|
326
|
+
Display certificate information.
|
327
|
+
|
328
|
+
This command displays detailed information about a certificate.
|
329
|
+
"""
|
330
|
+
try:
|
331
|
+
config = ctx.obj['config']
|
332
|
+
cert_manager = ctx.obj['cert_manager']
|
333
|
+
verbose = ctx.obj['verbose']
|
334
|
+
|
335
|
+
if verbose:
|
336
|
+
click.echo(f"Getting certificate information: {cert_path}")
|
337
|
+
|
338
|
+
# Get certificate info
|
339
|
+
cert_info = cert_manager.get_certificate_info(cert_path)
|
340
|
+
|
341
|
+
click.echo(f"Certificate Information:")
|
342
|
+
click.echo(f" Subject: {cert_info.subject}")
|
343
|
+
click.echo(f" Issuer: {cert_info.issuer}")
|
344
|
+
click.echo(f" Serial Number: {cert_info.serial_number}")
|
345
|
+
click.echo(f" Valid From: {cert_info.not_before}")
|
346
|
+
click.echo(f" Valid Until: {cert_info.not_after}")
|
347
|
+
click.echo(f" Key Size: {cert_info.key_size} bits")
|
348
|
+
click.echo(f" Certificate Type: {cert_info.certificate_type}")
|
349
|
+
|
350
|
+
if cert_info.subject_alt_names:
|
351
|
+
click.echo(f" Subject Alternative Names: {', '.join(cert_info.subject_alt_names)}")
|
352
|
+
|
353
|
+
except Exception as e:
|
354
|
+
click.echo(f"❌ Failed to get certificate information: {str(e)}", err=True)
|
355
|
+
raise click.Abort()
|
356
|
+
|
357
|
+
|
358
|
+
@cert_cli.command()
|
359
|
+
@click.option('--common-name', '-cn', required=True,
|
360
|
+
help='Common name for the intermediate CA certificate')
|
361
|
+
@click.option('--organization', '-o', required=True,
|
362
|
+
help='Organization name')
|
363
|
+
@click.option('--country', '-c', required=True,
|
364
|
+
help='Country code (e.g., US, GB)')
|
365
|
+
@click.option('--state', '-s',
|
366
|
+
help='State or province')
|
367
|
+
@click.option('--locality', '-l',
|
368
|
+
help='Locality or city')
|
369
|
+
@click.option('--email', '-e',
|
370
|
+
help='Email address')
|
371
|
+
@click.option('--validity-years', '-y', default=5,
|
372
|
+
help='Certificate validity in years')
|
373
|
+
@click.option('--key-size', '-k', default=2048,
|
374
|
+
help='RSA key size')
|
375
|
+
@click.option('--parent-ca-cert', '-p', required=True,
|
376
|
+
help='Path to parent CA certificate')
|
377
|
+
@click.option('--parent-ca-key', '-pk', required=True,
|
378
|
+
help='Path to parent CA private key')
|
379
|
+
@click.pass_context
|
380
|
+
def create_intermediate_ca(ctx, common_name: str, organization: str, country: str,
|
381
|
+
state: Optional[str], locality: Optional[str],
|
382
|
+
email: Optional[str], validity_years: int, key_size: int,
|
383
|
+
parent_ca_cert: str, parent_ca_key: str):
|
384
|
+
"""
|
385
|
+
Create an intermediate CA certificate.
|
386
|
+
|
387
|
+
This command creates a new intermediate Certificate Authority (CA) certificate
|
388
|
+
signed by a parent CA certificate.
|
389
|
+
"""
|
390
|
+
try:
|
391
|
+
config = ctx.obj['config']
|
392
|
+
cert_manager = ctx.obj['cert_manager']
|
393
|
+
verbose = ctx.obj['verbose']
|
394
|
+
|
395
|
+
# Create intermediate CA configuration
|
396
|
+
intermediate_config = IntermediateCAConfig(
|
397
|
+
common_name=common_name,
|
398
|
+
organization=organization,
|
399
|
+
country=country,
|
400
|
+
state=state,
|
401
|
+
locality=locality,
|
402
|
+
email=email,
|
403
|
+
validity_years=validity_years,
|
404
|
+
key_size=key_size,
|
405
|
+
parent_ca_cert=parent_ca_cert,
|
406
|
+
parent_ca_key=parent_ca_key
|
407
|
+
)
|
408
|
+
|
409
|
+
if verbose:
|
410
|
+
click.echo(f"Creating intermediate CA certificate with configuration:")
|
411
|
+
click.echo(f" Common Name: {common_name}")
|
412
|
+
click.echo(f" Organization: {organization}")
|
413
|
+
click.echo(f" Country: {country}")
|
414
|
+
click.echo(f" Validity: {validity_years} years")
|
415
|
+
click.echo(f" Key Size: {key_size} bits")
|
416
|
+
click.echo(f" Parent CA Cert: {parent_ca_cert}")
|
417
|
+
click.echo(f" Parent CA Key: {parent_ca_key}")
|
418
|
+
|
419
|
+
# Create intermediate CA certificate
|
420
|
+
cert_pair = cert_manager.create_intermediate_ca(intermediate_config)
|
421
|
+
|
422
|
+
click.echo(f"✅ Intermediate CA certificate created successfully!")
|
423
|
+
click.echo(f" Certificate: {cert_pair.certificate_path}")
|
424
|
+
click.echo(f" Private Key: {cert_pair.private_key_path}")
|
425
|
+
click.echo(f" Serial Number: {cert_pair.serial_number}")
|
426
|
+
click.echo(f" Valid Until: {cert_pair.not_after}")
|
427
|
+
|
428
|
+
except Exception as e:
|
429
|
+
click.echo(f"❌ Failed to create intermediate CA certificate: {str(e)}", err=True)
|
430
|
+
raise click.Abort()
|
431
|
+
|
432
|
+
|
433
|
+
@cert_cli.command()
|
434
|
+
@click.option('--ca-cert', '-c', required=True,
|
435
|
+
help='Path to CA certificate')
|
436
|
+
@click.option('--ca-key', '-k', required=True,
|
437
|
+
help='Path to CA private key')
|
438
|
+
@click.option('--output', '-o',
|
439
|
+
help='Output path for CRL file')
|
440
|
+
@click.option('--validity-days', '-d', default=30,
|
441
|
+
help='CRL validity in days')
|
442
|
+
@click.pass_context
|
443
|
+
def create_crl(ctx, ca_cert: str, ca_key: str, output: Optional[str], validity_days: int):
|
444
|
+
"""
|
445
|
+
Create a Certificate Revocation List (CRL).
|
446
|
+
|
447
|
+
This command creates a Certificate Revocation List (CRL) from the CA
|
448
|
+
certificate and private key.
|
449
|
+
"""
|
450
|
+
try:
|
451
|
+
config = ctx.obj['config']
|
452
|
+
cert_manager = ctx.obj['cert_manager']
|
453
|
+
verbose = ctx.obj['verbose']
|
454
|
+
|
455
|
+
if verbose:
|
456
|
+
click.echo(f"Creating CRL with configuration:")
|
457
|
+
click.echo(f" CA Certificate: {ca_cert}")
|
458
|
+
click.echo(f" CA Private Key: {ca_key}")
|
459
|
+
click.echo(f" Validity: {validity_days} days")
|
460
|
+
if output:
|
461
|
+
click.echo(f" Output: {output}")
|
462
|
+
|
463
|
+
# Create CRL
|
464
|
+
crl_path = cert_manager.create_crl(ca_cert, ca_key, output, validity_days)
|
465
|
+
|
466
|
+
click.echo(f"✅ CRL created successfully!")
|
467
|
+
click.echo(f" CRL Path: {crl_path}")
|
468
|
+
click.echo(f" Validity: {validity_days} days")
|
469
|
+
|
470
|
+
except Exception as e:
|
471
|
+
click.echo(f"❌ Failed to create CRL: {str(e)}", err=True)
|
472
|
+
raise click.Abort()
|
473
|
+
|
474
|
+
|
475
|
+
@cert_cli.command()
|
476
|
+
@click.argument('serial_number')
|
477
|
+
@click.option('--reason', '-r', default='unspecified',
|
478
|
+
help='Reason for revocation')
|
479
|
+
@click.pass_context
|
480
|
+
def revoke(ctx, serial_number: str, reason: str):
|
481
|
+
"""
|
482
|
+
Revoke a certificate.
|
483
|
+
|
484
|
+
This command revokes a certificate by adding it to the Certificate
|
485
|
+
Revocation List (CRL).
|
486
|
+
"""
|
487
|
+
try:
|
488
|
+
config = ctx.obj['config']
|
489
|
+
cert_manager = ctx.obj['cert_manager']
|
490
|
+
verbose = ctx.obj['verbose']
|
491
|
+
|
492
|
+
if verbose:
|
493
|
+
click.echo(f"Revoking certificate with serial number: {serial_number}")
|
494
|
+
click.echo(f"Reason: {reason}")
|
495
|
+
|
496
|
+
# Revoke certificate
|
497
|
+
success = cert_manager.revoke_certificate(serial_number, reason)
|
498
|
+
|
499
|
+
if success:
|
500
|
+
click.echo(f"✅ Certificate revoked successfully!")
|
501
|
+
else:
|
502
|
+
click.echo(f"❌ Failed to revoke certificate!", err=True)
|
503
|
+
raise click.Abort()
|
504
|
+
|
505
|
+
except Exception as e:
|
506
|
+
click.echo(f"❌ Failed to revoke certificate: {str(e)}", err=True)
|
507
|
+
raise click.Abort()
|
508
|
+
|
509
|
+
|
510
|
+
if __name__ == '__main__':
|
511
|
+
cert_cli()
|