mcp-security-framework 0.1.0__py3-none-any.whl → 1.1.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.
Files changed (58) hide show
  1. mcp_security_framework/__init__.py +26 -15
  2. mcp_security_framework/cli/__init__.py +1 -1
  3. mcp_security_framework/cli/cert_cli.py +233 -197
  4. mcp_security_framework/cli/security_cli.py +324 -234
  5. mcp_security_framework/constants.py +21 -27
  6. mcp_security_framework/core/auth_manager.py +49 -20
  7. mcp_security_framework/core/cert_manager.py +398 -104
  8. mcp_security_framework/core/permission_manager.py +13 -9
  9. mcp_security_framework/core/rate_limiter.py +10 -0
  10. mcp_security_framework/core/security_manager.py +286 -229
  11. mcp_security_framework/examples/__init__.py +6 -0
  12. mcp_security_framework/examples/comprehensive_example.py +954 -0
  13. mcp_security_framework/examples/django_example.py +276 -202
  14. mcp_security_framework/examples/fastapi_example.py +897 -393
  15. mcp_security_framework/examples/flask_example.py +311 -200
  16. mcp_security_framework/examples/gateway_example.py +373 -214
  17. mcp_security_framework/examples/microservice_example.py +337 -172
  18. mcp_security_framework/examples/standalone_example.py +719 -478
  19. mcp_security_framework/examples/test_all_examples.py +572 -0
  20. mcp_security_framework/middleware/__init__.py +46 -55
  21. mcp_security_framework/middleware/auth_middleware.py +62 -63
  22. mcp_security_framework/middleware/fastapi_auth_middleware.py +179 -110
  23. mcp_security_framework/middleware/fastapi_middleware.py +156 -148
  24. mcp_security_framework/middleware/flask_auth_middleware.py +267 -107
  25. mcp_security_framework/middleware/flask_middleware.py +183 -157
  26. mcp_security_framework/middleware/mtls_middleware.py +106 -117
  27. mcp_security_framework/middleware/rate_limit_middleware.py +105 -101
  28. mcp_security_framework/middleware/security_middleware.py +109 -124
  29. mcp_security_framework/schemas/config.py +2 -1
  30. mcp_security_framework/schemas/models.py +19 -6
  31. mcp_security_framework/utils/cert_utils.py +14 -8
  32. mcp_security_framework/utils/datetime_compat.py +116 -0
  33. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/METADATA +2 -1
  34. mcp_security_framework-1.1.1.dist-info/RECORD +84 -0
  35. tests/conftest.py +303 -0
  36. tests/test_cli/test_cert_cli.py +194 -174
  37. tests/test_cli/test_security_cli.py +274 -247
  38. tests/test_core/test_cert_manager.py +33 -19
  39. tests/test_core/test_security_manager.py +2 -2
  40. tests/test_examples/test_comprehensive_example.py +613 -0
  41. tests/test_examples/test_fastapi_example.py +290 -169
  42. tests/test_examples/test_flask_example.py +304 -162
  43. tests/test_examples/test_standalone_example.py +106 -168
  44. tests/test_integration/test_auth_flow.py +214 -198
  45. tests/test_integration/test_certificate_flow.py +181 -150
  46. tests/test_integration/test_fastapi_integration.py +140 -149
  47. tests/test_integration/test_flask_integration.py +144 -141
  48. tests/test_integration/test_standalone_integration.py +331 -300
  49. tests/test_middleware/test_fastapi_auth_middleware.py +745 -0
  50. tests/test_middleware/test_fastapi_middleware.py +147 -132
  51. tests/test_middleware/test_flask_auth_middleware.py +696 -0
  52. tests/test_middleware/test_flask_middleware.py +201 -179
  53. tests/test_middleware/test_security_middleware.py +151 -130
  54. tests/test_utils/test_datetime_compat.py +147 -0
  55. mcp_security_framework-0.1.0.dist-info/RECORD +0 -76
  56. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/WHEEL +0 -0
  57. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/entry_points.txt +0 -0
  58. {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.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 CertificateConfig, CAConfig, ServerCertConfig, ClientCertConfig
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('--config', '-c', 'config_path',
42
- help='Path to certificate configuration file')
43
- @click.option('--verbose', '-v', is_flag=True,
44
- help='Enable verbose output')
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['verbose'] = verbose
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, 'r') as f:
63
+ with open(config_path, "r") as f:
58
64
  config_data = json.load(f)
59
- ctx.obj['config'] = CertificateConfig(**config_data)
65
+ ctx.obj["config"] = CertificateConfig(**config_data)
60
66
  else:
61
67
  # Use default configuration
62
- ctx.obj['config'] = CertificateConfig(
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['cert_manager'] = CertificateManager(ctx.obj['config'])
76
+ ctx.obj["cert_manager"] = CertificateManager(ctx.obj["config"])
71
77
 
72
78
 
73
79
  @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')
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(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):
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['config']
102
- cert_manager = ctx.obj['cert_manager']
103
- verbose = ctx.obj['verbose']
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('--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)')
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(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):
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['config']
170
- cert_manager = ctx.obj['cert_manager']
171
- verbose = ctx.obj['verbose']
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('--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)')
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(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):
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['config']
243
- cert_manager = ctx.obj['cert_manager']
244
- verbose = ctx.obj['verbose']
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('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')
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['config']
299
- cert_manager = ctx.obj['cert_manager']
300
- verbose = ctx.obj['verbose']
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('cert_path', type=click.Path(exists=True))
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['config']
332
- cert_manager = ctx.obj['cert_manager']
333
- verbose = ctx.obj['verbose']
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(f" Subject Alternative Names: {', '.join(cert_info.subject_alt_names)}")
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('--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')
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(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):
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['config']
392
- cert_manager = ctx.obj['cert_manager']
393
- verbose = ctx.obj['verbose']
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(f"❌ Failed to create intermediate CA certificate: {str(e)}", err=True)
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('--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')
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(ctx, ca_cert: str, ca_key: str, output: Optional[str], validity_days: int):
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['config']
452
- cert_manager = ctx.obj['cert_manager']
453
- verbose = ctx.obj['verbose']
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('serial_number')
477
- @click.option('--reason', '-r', default='unspecified',
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['config']
489
- cert_manager = ctx.obj['cert_manager']
490
- verbose = ctx.obj['verbose']
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__ == '__main__':
546
+ if __name__ == "__main__":
511
547
  cert_cli()