mcp-security-framework 1.1.0__py3-none-any.whl → 1.1.2__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 +26 -15
- mcp_security_framework/cli/__init__.py +1 -1
- mcp_security_framework/cli/cert_cli.py +233 -197
- mcp_security_framework/cli/security_cli.py +324 -234
- mcp_security_framework/constants.py +21 -27
- mcp_security_framework/core/auth_manager.py +41 -22
- mcp_security_framework/core/cert_manager.py +210 -147
- mcp_security_framework/core/permission_manager.py +9 -9
- mcp_security_framework/core/rate_limiter.py +2 -2
- mcp_security_framework/core/security_manager.py +284 -229
- mcp_security_framework/examples/__init__.py +6 -0
- mcp_security_framework/examples/comprehensive_example.py +349 -279
- mcp_security_framework/examples/django_example.py +247 -206
- mcp_security_framework/examples/fastapi_example.py +315 -283
- mcp_security_framework/examples/flask_example.py +274 -203
- mcp_security_framework/examples/gateway_example.py +304 -237
- mcp_security_framework/examples/microservice_example.py +258 -189
- mcp_security_framework/examples/standalone_example.py +255 -230
- mcp_security_framework/examples/test_all_examples.py +151 -135
- mcp_security_framework/middleware/__init__.py +46 -55
- mcp_security_framework/middleware/auth_middleware.py +62 -63
- mcp_security_framework/middleware/fastapi_auth_middleware.py +119 -118
- mcp_security_framework/middleware/fastapi_middleware.py +156 -148
- mcp_security_framework/middleware/flask_auth_middleware.py +160 -147
- mcp_security_framework/middleware/flask_middleware.py +183 -157
- mcp_security_framework/middleware/mtls_middleware.py +106 -117
- mcp_security_framework/middleware/rate_limit_middleware.py +105 -101
- mcp_security_framework/middleware/security_middleware.py +109 -124
- mcp_security_framework/schemas/config.py +2 -1
- mcp_security_framework/schemas/models.py +18 -6
- mcp_security_framework/utils/cert_utils.py +14 -8
- mcp_security_framework/utils/datetime_compat.py +116 -0
- {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/METADATA +4 -3
- mcp_security_framework-1.1.2.dist-info/RECORD +84 -0
- tests/conftest.py +63 -66
- tests/test_cli/test_cert_cli.py +184 -146
- tests/test_cli/test_security_cli.py +274 -247
- tests/test_core/test_cert_manager.py +24 -10
- tests/test_core/test_security_manager.py +2 -2
- tests/test_examples/test_comprehensive_example.py +190 -137
- tests/test_examples/test_fastapi_example.py +124 -101
- tests/test_examples/test_flask_example.py +124 -101
- tests/test_examples/test_standalone_example.py +73 -80
- tests/test_integration/test_auth_flow.py +213 -197
- tests/test_integration/test_certificate_flow.py +180 -149
- tests/test_integration/test_fastapi_integration.py +108 -111
- tests/test_integration/test_flask_integration.py +141 -140
- tests/test_integration/test_standalone_integration.py +290 -259
- tests/test_middleware/test_fastapi_auth_middleware.py +195 -174
- tests/test_middleware/test_fastapi_middleware.py +147 -132
- tests/test_middleware/test_flask_auth_middleware.py +260 -202
- tests/test_middleware/test_flask_middleware.py +201 -179
- tests/test_middleware/test_security_middleware.py +145 -130
- tests/test_utils/test_datetime_compat.py +147 -0
- mcp_security_framework-1.1.0.dist-info/RECORD +0 -82
- {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/WHEEL +0 -0
- {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/entry_points.txt +0 -0
- {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/top_level.txt +0 -0
@@ -25,83 +25,93 @@ Version: 1.0.0
|
|
25
25
|
License: MIT
|
26
26
|
"""
|
27
27
|
|
28
|
-
import click
|
29
28
|
import json
|
30
29
|
import os
|
31
30
|
from pathlib import Path
|
32
31
|
from typing import Optional
|
33
32
|
|
33
|
+
import click
|
34
|
+
|
34
35
|
from ..core.cert_manager import CertificateManager
|
35
|
-
from ..schemas.config import
|
36
|
+
from ..schemas.config import (
|
37
|
+
CAConfig,
|
38
|
+
CertificateConfig,
|
39
|
+
ClientCertConfig,
|
40
|
+
IntermediateCAConfig,
|
41
|
+
ServerCertConfig,
|
42
|
+
)
|
36
43
|
from ..schemas.models import CertificateType
|
37
|
-
from ..schemas.config import IntermediateCAConfig
|
38
44
|
|
39
45
|
|
40
46
|
@click.group()
|
41
|
-
@click.option(
|
42
|
-
|
43
|
-
|
44
|
-
|
47
|
+
@click.option(
|
48
|
+
"--config", "-c", "config_path", help="Path to certificate configuration file"
|
49
|
+
)
|
50
|
+
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output")
|
45
51
|
@click.pass_context
|
46
52
|
def cert_cli(ctx, config_path: Optional[str], verbose: bool):
|
47
53
|
"""
|
48
54
|
Certificate Management CLI
|
49
|
-
|
55
|
+
|
50
56
|
Manage certificates including creation, validation, and management.
|
51
57
|
"""
|
52
58
|
ctx.ensure_object(dict)
|
53
|
-
ctx.obj[
|
54
|
-
|
59
|
+
ctx.obj["verbose"] = verbose
|
60
|
+
|
55
61
|
# Load configuration
|
56
62
|
if config_path and os.path.exists(config_path):
|
57
|
-
with open(config_path,
|
63
|
+
with open(config_path, "r") as f:
|
58
64
|
config_data = json.load(f)
|
59
|
-
ctx.obj[
|
65
|
+
ctx.obj["config"] = CertificateConfig(**config_data)
|
60
66
|
else:
|
61
67
|
# Use default configuration
|
62
|
-
ctx.obj[
|
68
|
+
ctx.obj["config"] = CertificateConfig(
|
63
69
|
cert_storage_path="./certs",
|
64
70
|
key_storage_path="./keys",
|
65
71
|
ca_cert_path="./certs/ca_cert.pem",
|
66
|
-
ca_key_path="./keys/ca_key.pem"
|
72
|
+
ca_key_path="./keys/ca_key.pem",
|
67
73
|
)
|
68
|
-
|
74
|
+
|
69
75
|
# Create certificate manager
|
70
|
-
ctx.obj[
|
76
|
+
ctx.obj["cert_manager"] = CertificateManager(ctx.obj["config"])
|
71
77
|
|
72
78
|
|
73
79
|
@cert_cli.command()
|
74
|
-
@click.option(
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
@click.option(
|
79
|
-
|
80
|
-
@click.option(
|
81
|
-
|
82
|
-
@click.option(
|
83
|
-
|
84
|
-
|
85
|
-
|
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')
|
80
|
+
@click.option(
|
81
|
+
"--common-name", "-cn", required=True, help="Common name for the CA certificate"
|
82
|
+
)
|
83
|
+
@click.option("--organization", "-o", required=True, help="Organization name")
|
84
|
+
@click.option("--country", "-c", required=True, help="Country code (e.g., US, GB)")
|
85
|
+
@click.option("--state", "-s", help="State or province")
|
86
|
+
@click.option("--locality", "-l", help="Locality or city")
|
87
|
+
@click.option("--email", "-e", help="Email address")
|
88
|
+
@click.option(
|
89
|
+
"--validity-years", "-y", default=10, help="Certificate validity in years"
|
90
|
+
)
|
91
|
+
@click.option("--key-size", "-k", default=2048, help="RSA key size")
|
90
92
|
@click.pass_context
|
91
|
-
def create_ca(
|
92
|
-
|
93
|
-
|
93
|
+
def create_ca(
|
94
|
+
ctx,
|
95
|
+
common_name: str,
|
96
|
+
organization: str,
|
97
|
+
country: str,
|
98
|
+
state: Optional[str],
|
99
|
+
locality: Optional[str],
|
100
|
+
email: Optional[str],
|
101
|
+
validity_years: int,
|
102
|
+
key_size: int,
|
103
|
+
):
|
94
104
|
"""
|
95
105
|
Create a root CA certificate.
|
96
|
-
|
106
|
+
|
97
107
|
This command creates a new root Certificate Authority (CA) certificate
|
98
108
|
that can be used to sign other certificates.
|
99
109
|
"""
|
100
110
|
try:
|
101
|
-
config = ctx.obj[
|
102
|
-
cert_manager = ctx.obj[
|
103
|
-
verbose = ctx.obj[
|
104
|
-
|
111
|
+
config = ctx.obj["config"]
|
112
|
+
cert_manager = ctx.obj["cert_manager"]
|
113
|
+
verbose = ctx.obj["verbose"]
|
114
|
+
|
105
115
|
# Create CA configuration
|
106
116
|
ca_config = CAConfig(
|
107
117
|
common_name=common_name,
|
@@ -111,9 +121,9 @@ def create_ca(ctx, common_name: str, organization: str, country: str,
|
|
111
121
|
locality=locality,
|
112
122
|
email=email,
|
113
123
|
validity_years=validity_years,
|
114
|
-
key_size=key_size
|
124
|
+
key_size=key_size,
|
115
125
|
)
|
116
|
-
|
126
|
+
|
117
127
|
if verbose:
|
118
128
|
click.echo(f"Creating CA certificate with configuration:")
|
119
129
|
click.echo(f" Common Name: {common_name}")
|
@@ -121,55 +131,60 @@ def create_ca(ctx, common_name: str, organization: str, country: str,
|
|
121
131
|
click.echo(f" Country: {country}")
|
122
132
|
click.echo(f" Validity: {validity_years} years")
|
123
133
|
click.echo(f" Key Size: {key_size} bits")
|
124
|
-
|
134
|
+
|
125
135
|
# Create CA certificate
|
126
136
|
cert_pair = cert_manager.create_root_ca(ca_config)
|
127
|
-
|
137
|
+
|
128
138
|
click.echo(f"✅ CA certificate created successfully!")
|
129
139
|
click.echo(f" Certificate: {cert_pair.certificate_path}")
|
130
140
|
click.echo(f" Private Key: {cert_pair.private_key_path}")
|
131
141
|
click.echo(f" Serial Number: {cert_pair.serial_number}")
|
132
142
|
click.echo(f" Valid Until: {cert_pair.not_after}")
|
133
|
-
|
143
|
+
|
134
144
|
except Exception as e:
|
135
145
|
click.echo(f"❌ Failed to create CA certificate: {str(e)}", err=True)
|
136
146
|
raise click.Abort()
|
137
147
|
|
138
148
|
|
139
149
|
@cert_cli.command()
|
140
|
-
@click.option(
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
@click.option(
|
145
|
-
|
146
|
-
@click.option(
|
147
|
-
|
148
|
-
@click.option(
|
149
|
-
|
150
|
-
@click.option(
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
help='RSA key size')
|
156
|
-
@click.option('--san', multiple=True,
|
157
|
-
help='Subject Alternative Names (can be specified multiple times)')
|
150
|
+
@click.option(
|
151
|
+
"--common-name", "-cn", required=True, help="Common name for the server certificate"
|
152
|
+
)
|
153
|
+
@click.option("--organization", "-o", required=True, help="Organization name")
|
154
|
+
@click.option("--country", "-c", required=True, help="Country code (e.g., US, GB)")
|
155
|
+
@click.option("--state", "-s", help="State or province")
|
156
|
+
@click.option("--locality", "-l", help="Locality or city")
|
157
|
+
@click.option("--email", "-e", help="Email address")
|
158
|
+
@click.option("--validity-days", "-d", default=365, help="Certificate validity in days")
|
159
|
+
@click.option("--key-size", "-k", default=2048, help="RSA key size")
|
160
|
+
@click.option(
|
161
|
+
"--san",
|
162
|
+
multiple=True,
|
163
|
+
help="Subject Alternative Names (can be specified multiple times)",
|
164
|
+
)
|
158
165
|
@click.pass_context
|
159
|
-
def create_server(
|
160
|
-
|
161
|
-
|
162
|
-
|
166
|
+
def create_server(
|
167
|
+
ctx,
|
168
|
+
common_name: str,
|
169
|
+
organization: str,
|
170
|
+
country: str,
|
171
|
+
state: Optional[str],
|
172
|
+
locality: Optional[str],
|
173
|
+
email: Optional[str],
|
174
|
+
validity_days: int,
|
175
|
+
key_size: int,
|
176
|
+
san: tuple,
|
177
|
+
):
|
163
178
|
"""
|
164
179
|
Create a server certificate.
|
165
|
-
|
180
|
+
|
166
181
|
This command creates a new server certificate signed by the configured CA.
|
167
182
|
"""
|
168
183
|
try:
|
169
|
-
config = ctx.obj[
|
170
|
-
cert_manager = ctx.obj[
|
171
|
-
verbose = ctx.obj[
|
172
|
-
|
184
|
+
config = ctx.obj["config"]
|
185
|
+
cert_manager = ctx.obj["cert_manager"]
|
186
|
+
verbose = ctx.obj["verbose"]
|
187
|
+
|
173
188
|
# Create server configuration
|
174
189
|
server_config = ServerCertConfig(
|
175
190
|
common_name=common_name,
|
@@ -180,9 +195,9 @@ def create_server(ctx, common_name: str, organization: str, country: str,
|
|
180
195
|
email=email,
|
181
196
|
validity_days=validity_days,
|
182
197
|
key_size=key_size,
|
183
|
-
subject_alt_names=list(san) if san else None
|
198
|
+
subject_alt_names=list(san) if san else None,
|
184
199
|
)
|
185
|
-
|
200
|
+
|
186
201
|
if verbose:
|
187
202
|
click.echo(f"Creating server certificate with configuration:")
|
188
203
|
click.echo(f" Common Name: {common_name}")
|
@@ -192,57 +207,66 @@ def create_server(ctx, common_name: str, organization: str, country: str,
|
|
192
207
|
click.echo(f" Key Size: {key_size} bits")
|
193
208
|
if san:
|
194
209
|
click.echo(f" SAN: {', '.join(san)}")
|
195
|
-
|
210
|
+
|
196
211
|
# Create server certificate
|
197
212
|
cert_pair = cert_manager.create_server_certificate(server_config)
|
198
|
-
|
213
|
+
|
199
214
|
click.echo(f"✅ Server certificate created successfully!")
|
200
215
|
click.echo(f" Certificate: {cert_pair.certificate_path}")
|
201
216
|
click.echo(f" Private Key: {cert_pair.private_key_path}")
|
202
217
|
click.echo(f" Serial Number: {cert_pair.serial_number}")
|
203
218
|
click.echo(f" Valid Until: {cert_pair.not_after}")
|
204
|
-
|
219
|
+
|
205
220
|
except Exception as e:
|
206
221
|
click.echo(f"❌ Failed to create server certificate: {str(e)}", err=True)
|
207
222
|
raise click.Abort()
|
208
223
|
|
209
224
|
|
210
225
|
@cert_cli.command()
|
211
|
-
@click.option(
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
@click.option(
|
216
|
-
|
217
|
-
@click.option(
|
218
|
-
|
219
|
-
@click.option(
|
220
|
-
|
221
|
-
@click.option(
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
226
|
+
@click.option(
|
227
|
+
"--common-name", "-cn", required=True, help="Common name for the client certificate"
|
228
|
+
)
|
229
|
+
@click.option("--organization", "-o", required=True, help="Organization name")
|
230
|
+
@click.option("--country", "-c", required=True, help="Country code (e.g., US, GB)")
|
231
|
+
@click.option("--state", "-s", help="State or province")
|
232
|
+
@click.option("--locality", "-l", help="Locality or city")
|
233
|
+
@click.option("--email", "-e", help="Email address")
|
234
|
+
@click.option("--validity-days", "-d", default=365, help="Certificate validity in days")
|
235
|
+
@click.option("--key-size", "-k", default=2048, help="RSA key size")
|
236
|
+
@click.option(
|
237
|
+
"--roles",
|
238
|
+
multiple=True,
|
239
|
+
help="Roles to assign to the client (can be specified multiple times)",
|
240
|
+
)
|
241
|
+
@click.option(
|
242
|
+
"--permissions",
|
243
|
+
multiple=True,
|
244
|
+
help="Permissions to assign to the client (can be specified multiple times)",
|
245
|
+
)
|
231
246
|
@click.pass_context
|
232
|
-
def create_client(
|
233
|
-
|
234
|
-
|
235
|
-
|
247
|
+
def create_client(
|
248
|
+
ctx,
|
249
|
+
common_name: str,
|
250
|
+
organization: str,
|
251
|
+
country: str,
|
252
|
+
state: Optional[str],
|
253
|
+
locality: Optional[str],
|
254
|
+
email: Optional[str],
|
255
|
+
validity_days: int,
|
256
|
+
key_size: int,
|
257
|
+
roles: tuple,
|
258
|
+
permissions: tuple,
|
259
|
+
):
|
236
260
|
"""
|
237
261
|
Create a client certificate.
|
238
|
-
|
262
|
+
|
239
263
|
This command creates a new client certificate signed by the configured CA.
|
240
264
|
"""
|
241
265
|
try:
|
242
|
-
config = ctx.obj[
|
243
|
-
cert_manager = ctx.obj[
|
244
|
-
verbose = ctx.obj[
|
245
|
-
|
266
|
+
config = ctx.obj["config"]
|
267
|
+
cert_manager = ctx.obj["cert_manager"]
|
268
|
+
verbose = ctx.obj["verbose"]
|
269
|
+
|
246
270
|
# Create client configuration
|
247
271
|
client_config = ClientCertConfig(
|
248
272
|
common_name=common_name,
|
@@ -254,9 +278,9 @@ def create_client(ctx, common_name: str, organization: str, country: str,
|
|
254
278
|
validity_days=validity_days,
|
255
279
|
key_size=key_size,
|
256
280
|
roles=list(roles) if roles else None,
|
257
|
-
permissions=list(permissions) if permissions else None
|
281
|
+
permissions=list(permissions) if permissions else None,
|
258
282
|
)
|
259
|
-
|
283
|
+
|
260
284
|
if verbose:
|
261
285
|
click.echo(f"Creating client certificate with configuration:")
|
262
286
|
click.echo(f" Common Name: {common_name}")
|
@@ -268,76 +292,79 @@ def create_client(ctx, common_name: str, organization: str, country: str,
|
|
268
292
|
click.echo(f" Roles: {', '.join(roles)}")
|
269
293
|
if permissions:
|
270
294
|
click.echo(f" Permissions: {', '.join(permissions)}")
|
271
|
-
|
295
|
+
|
272
296
|
# Create client certificate
|
273
297
|
cert_pair = cert_manager.create_client_certificate(client_config)
|
274
|
-
|
298
|
+
|
275
299
|
click.echo(f"✅ Client certificate created successfully!")
|
276
300
|
click.echo(f" Certificate: {cert_pair.certificate_path}")
|
277
301
|
click.echo(f" Private Key: {cert_pair.private_key_path}")
|
278
302
|
click.echo(f" Serial Number: {cert_pair.serial_number}")
|
279
303
|
click.echo(f" Valid Until: {cert_pair.not_after}")
|
280
|
-
|
304
|
+
|
281
305
|
except Exception as e:
|
282
306
|
click.echo(f"❌ Failed to create client certificate: {str(e)}", err=True)
|
283
307
|
raise click.Abort()
|
284
308
|
|
285
309
|
|
286
310
|
@cert_cli.command()
|
287
|
-
@click.argument(
|
288
|
-
@click.option(
|
289
|
-
|
311
|
+
@click.argument("cert_path", type=click.Path(exists=True))
|
312
|
+
@click.option(
|
313
|
+
"--ca-cert",
|
314
|
+
type=click.Path(exists=True),
|
315
|
+
help="Path to CA certificate for validation",
|
316
|
+
)
|
290
317
|
@click.pass_context
|
291
318
|
def validate(ctx, cert_path: str, ca_cert: Optional[str]):
|
292
319
|
"""
|
293
320
|
Validate a certificate.
|
294
|
-
|
321
|
+
|
295
322
|
This command validates a certificate and optionally checks it against a CA.
|
296
323
|
"""
|
297
324
|
try:
|
298
|
-
config = ctx.obj[
|
299
|
-
cert_manager = ctx.obj[
|
300
|
-
verbose = ctx.obj[
|
301
|
-
|
325
|
+
config = ctx.obj["config"]
|
326
|
+
cert_manager = ctx.obj["cert_manager"]
|
327
|
+
verbose = ctx.obj["verbose"]
|
328
|
+
|
302
329
|
if verbose:
|
303
330
|
click.echo(f"Validating certificate: {cert_path}")
|
304
331
|
if ca_cert:
|
305
332
|
click.echo(f"Using CA certificate: {ca_cert}")
|
306
|
-
|
333
|
+
|
307
334
|
# Validate certificate
|
308
335
|
is_valid = cert_manager.validate_certificate_chain(cert_path, ca_cert)
|
309
|
-
|
336
|
+
|
310
337
|
if is_valid:
|
311
338
|
click.echo(f"✅ Certificate is valid!")
|
312
339
|
else:
|
313
340
|
click.echo(f"❌ Certificate validation failed!", err=True)
|
314
341
|
raise click.Abort()
|
315
|
-
|
342
|
+
|
316
343
|
except Exception as e:
|
317
344
|
click.echo(f"❌ Certificate validation failed: {str(e)}", err=True)
|
318
345
|
raise click.Abort()
|
319
346
|
|
320
347
|
|
321
348
|
@cert_cli.command()
|
322
|
-
@click.argument(
|
349
|
+
@click.argument("cert_path", type=click.Path(exists=True))
|
323
350
|
@click.pass_context
|
324
351
|
def info(ctx, cert_path: str):
|
325
352
|
"""
|
326
353
|
Display certificate information.
|
327
|
-
|
354
|
+
|
328
355
|
This command displays detailed information about a certificate.
|
329
356
|
"""
|
330
357
|
try:
|
331
|
-
config = ctx.obj[
|
332
|
-
cert_manager = ctx.obj[
|
333
|
-
verbose = ctx.obj[
|
334
|
-
|
358
|
+
config = ctx.obj["config"]
|
359
|
+
cert_manager = ctx.obj["cert_manager"]
|
360
|
+
verbose = ctx.obj["verbose"]
|
361
|
+
|
335
362
|
if verbose:
|
336
363
|
click.echo(f"Getting certificate information: {cert_path}")
|
337
|
-
|
364
|
+
|
338
365
|
# Get certificate info
|
339
366
|
cert_info = cert_manager.get_certificate_info(cert_path)
|
340
|
-
|
367
|
+
|
341
368
|
click.echo(f"Certificate Information:")
|
342
369
|
click.echo(f" Subject: {cert_info.subject}")
|
343
370
|
click.echo(f" Issuer: {cert_info.issuer}")
|
@@ -346,52 +373,62 @@ def info(ctx, cert_path: str):
|
|
346
373
|
click.echo(f" Valid Until: {cert_info.not_after}")
|
347
374
|
click.echo(f" Key Size: {cert_info.key_size} bits")
|
348
375
|
click.echo(f" Certificate Type: {cert_info.certificate_type}")
|
349
|
-
|
376
|
+
|
350
377
|
if cert_info.subject_alt_names:
|
351
|
-
click.echo(
|
352
|
-
|
378
|
+
click.echo(
|
379
|
+
f" Subject Alternative Names: {', '.join(cert_info.subject_alt_names)}"
|
380
|
+
)
|
381
|
+
|
353
382
|
except Exception as e:
|
354
383
|
click.echo(f"❌ Failed to get certificate information: {str(e)}", err=True)
|
355
384
|
raise click.Abort()
|
356
385
|
|
357
386
|
|
358
387
|
@cert_cli.command()
|
359
|
-
@click.option(
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
@click.option(
|
366
|
-
|
367
|
-
@click.option(
|
368
|
-
|
369
|
-
@click.option(
|
370
|
-
|
371
|
-
@click.option(
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
@click.option(
|
376
|
-
|
377
|
-
|
378
|
-
help='Path to parent CA private key')
|
388
|
+
@click.option(
|
389
|
+
"--common-name",
|
390
|
+
"-cn",
|
391
|
+
required=True,
|
392
|
+
help="Common name for the intermediate CA certificate",
|
393
|
+
)
|
394
|
+
@click.option("--organization", "-o", required=True, help="Organization name")
|
395
|
+
@click.option("--country", "-c", required=True, help="Country code (e.g., US, GB)")
|
396
|
+
@click.option("--state", "-s", help="State or province")
|
397
|
+
@click.option("--locality", "-l", help="Locality or city")
|
398
|
+
@click.option("--email", "-e", help="Email address")
|
399
|
+
@click.option("--validity-years", "-y", default=5, help="Certificate validity in years")
|
400
|
+
@click.option("--key-size", "-k", default=2048, help="RSA key size")
|
401
|
+
@click.option(
|
402
|
+
"--parent-ca-cert", "-p", required=True, help="Path to parent CA certificate"
|
403
|
+
)
|
404
|
+
@click.option(
|
405
|
+
"--parent-ca-key", "-pk", required=True, help="Path to parent CA private key"
|
406
|
+
)
|
379
407
|
@click.pass_context
|
380
|
-
def create_intermediate_ca(
|
381
|
-
|
382
|
-
|
383
|
-
|
408
|
+
def create_intermediate_ca(
|
409
|
+
ctx,
|
410
|
+
common_name: str,
|
411
|
+
organization: str,
|
412
|
+
country: str,
|
413
|
+
state: Optional[str],
|
414
|
+
locality: Optional[str],
|
415
|
+
email: Optional[str],
|
416
|
+
validity_years: int,
|
417
|
+
key_size: int,
|
418
|
+
parent_ca_cert: str,
|
419
|
+
parent_ca_key: str,
|
420
|
+
):
|
384
421
|
"""
|
385
422
|
Create an intermediate CA certificate.
|
386
|
-
|
423
|
+
|
387
424
|
This command creates a new intermediate Certificate Authority (CA) certificate
|
388
425
|
signed by a parent CA certificate.
|
389
426
|
"""
|
390
427
|
try:
|
391
|
-
config = ctx.obj[
|
392
|
-
cert_manager = ctx.obj[
|
393
|
-
verbose = ctx.obj[
|
394
|
-
|
428
|
+
config = ctx.obj["config"]
|
429
|
+
cert_manager = ctx.obj["cert_manager"]
|
430
|
+
verbose = ctx.obj["verbose"]
|
431
|
+
|
395
432
|
# Create intermediate CA configuration
|
396
433
|
intermediate_config = IntermediateCAConfig(
|
397
434
|
common_name=common_name,
|
@@ -403,9 +440,9 @@ def create_intermediate_ca(ctx, common_name: str, organization: str, country: st
|
|
403
440
|
validity_years=validity_years,
|
404
441
|
key_size=key_size,
|
405
442
|
parent_ca_cert=parent_ca_cert,
|
406
|
-
parent_ca_key=parent_ca_key
|
443
|
+
parent_ca_key=parent_ca_key,
|
407
444
|
)
|
408
|
-
|
445
|
+
|
409
446
|
if verbose:
|
410
447
|
click.echo(f"Creating intermediate CA certificate with configuration:")
|
411
448
|
click.echo(f" Common Name: {common_name}")
|
@@ -415,43 +452,43 @@ def create_intermediate_ca(ctx, common_name: str, organization: str, country: st
|
|
415
452
|
click.echo(f" Key Size: {key_size} bits")
|
416
453
|
click.echo(f" Parent CA Cert: {parent_ca_cert}")
|
417
454
|
click.echo(f" Parent CA Key: {parent_ca_key}")
|
418
|
-
|
455
|
+
|
419
456
|
# Create intermediate CA certificate
|
420
457
|
cert_pair = cert_manager.create_intermediate_ca(intermediate_config)
|
421
|
-
|
458
|
+
|
422
459
|
click.echo(f"✅ Intermediate CA certificate created successfully!")
|
423
460
|
click.echo(f" Certificate: {cert_pair.certificate_path}")
|
424
461
|
click.echo(f" Private Key: {cert_pair.private_key_path}")
|
425
462
|
click.echo(f" Serial Number: {cert_pair.serial_number}")
|
426
463
|
click.echo(f" Valid Until: {cert_pair.not_after}")
|
427
|
-
|
464
|
+
|
428
465
|
except Exception as e:
|
429
|
-
click.echo(
|
466
|
+
click.echo(
|
467
|
+
f"❌ Failed to create intermediate CA certificate: {str(e)}", err=True
|
468
|
+
)
|
430
469
|
raise click.Abort()
|
431
470
|
|
432
471
|
|
433
472
|
@cert_cli.command()
|
434
|
-
@click.option(
|
435
|
-
|
436
|
-
@click.option(
|
437
|
-
|
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')
|
473
|
+
@click.option("--ca-cert", "-c", required=True, help="Path to CA certificate")
|
474
|
+
@click.option("--ca-key", "-k", required=True, help="Path to CA private key")
|
475
|
+
@click.option("--output", "-o", help="Output path for CRL file")
|
476
|
+
@click.option("--validity-days", "-d", default=30, help="CRL validity in days")
|
442
477
|
@click.pass_context
|
443
|
-
def create_crl(
|
478
|
+
def create_crl(
|
479
|
+
ctx, ca_cert: str, ca_key: str, output: Optional[str], validity_days: int
|
480
|
+
):
|
444
481
|
"""
|
445
482
|
Create a Certificate Revocation List (CRL).
|
446
|
-
|
483
|
+
|
447
484
|
This command creates a Certificate Revocation List (CRL) from the CA
|
448
485
|
certificate and private key.
|
449
486
|
"""
|
450
487
|
try:
|
451
|
-
config = ctx.obj[
|
452
|
-
cert_manager = ctx.obj[
|
453
|
-
verbose = ctx.obj[
|
454
|
-
|
488
|
+
config = ctx.obj["config"]
|
489
|
+
cert_manager = ctx.obj["cert_manager"]
|
490
|
+
verbose = ctx.obj["verbose"]
|
491
|
+
|
455
492
|
if verbose:
|
456
493
|
click.echo(f"Creating CRL with configuration:")
|
457
494
|
click.echo(f" CA Certificate: {ca_cert}")
|
@@ -459,53 +496,52 @@ def create_crl(ctx, ca_cert: str, ca_key: str, output: Optional[str], validity_d
|
|
459
496
|
click.echo(f" Validity: {validity_days} days")
|
460
497
|
if output:
|
461
498
|
click.echo(f" Output: {output}")
|
462
|
-
|
499
|
+
|
463
500
|
# Create CRL
|
464
501
|
crl_path = cert_manager.create_crl(ca_cert, ca_key, output, validity_days)
|
465
|
-
|
502
|
+
|
466
503
|
click.echo(f"✅ CRL created successfully!")
|
467
504
|
click.echo(f" CRL Path: {crl_path}")
|
468
505
|
click.echo(f" Validity: {validity_days} days")
|
469
|
-
|
506
|
+
|
470
507
|
except Exception as e:
|
471
508
|
click.echo(f"❌ Failed to create CRL: {str(e)}", err=True)
|
472
509
|
raise click.Abort()
|
473
510
|
|
474
511
|
|
475
512
|
@cert_cli.command()
|
476
|
-
@click.argument(
|
477
|
-
@click.option(
|
478
|
-
help='Reason for revocation')
|
513
|
+
@click.argument("serial_number")
|
514
|
+
@click.option("--reason", "-r", default="unspecified", help="Reason for revocation")
|
479
515
|
@click.pass_context
|
480
516
|
def revoke(ctx, serial_number: str, reason: str):
|
481
517
|
"""
|
482
518
|
Revoke a certificate.
|
483
|
-
|
519
|
+
|
484
520
|
This command revokes a certificate by adding it to the Certificate
|
485
521
|
Revocation List (CRL).
|
486
522
|
"""
|
487
523
|
try:
|
488
|
-
config = ctx.obj[
|
489
|
-
cert_manager = ctx.obj[
|
490
|
-
verbose = ctx.obj[
|
491
|
-
|
524
|
+
config = ctx.obj["config"]
|
525
|
+
cert_manager = ctx.obj["cert_manager"]
|
526
|
+
verbose = ctx.obj["verbose"]
|
527
|
+
|
492
528
|
if verbose:
|
493
529
|
click.echo(f"Revoking certificate with serial number: {serial_number}")
|
494
530
|
click.echo(f"Reason: {reason}")
|
495
|
-
|
531
|
+
|
496
532
|
# Revoke certificate
|
497
533
|
success = cert_manager.revoke_certificate(serial_number, reason)
|
498
|
-
|
534
|
+
|
499
535
|
if success:
|
500
536
|
click.echo(f"✅ Certificate revoked successfully!")
|
501
537
|
else:
|
502
538
|
click.echo(f"❌ Failed to revoke certificate!", err=True)
|
503
539
|
raise click.Abort()
|
504
|
-
|
540
|
+
|
505
541
|
except Exception as e:
|
506
542
|
click.echo(f"❌ Failed to revoke certificate: {str(e)}", err=True)
|
507
543
|
raise click.Abort()
|
508
544
|
|
509
545
|
|
510
|
-
if __name__ ==
|
546
|
+
if __name__ == "__main__":
|
511
547
|
cert_cli()
|