mcp-security-framework 1.1.2__tar.gz → 1.2.0__tar.gz

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 (89) hide show
  1. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/PKG-INFO +1 -1
  2. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/cli/cert_cli.py +167 -3
  3. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/core/auth_manager.py +3 -1
  4. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/core/cert_manager.py +241 -6
  5. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/core/ssl_manager.py +41 -9
  6. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/middleware/mtls_middleware.py +10 -2
  7. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/utils/cert_utils.py +255 -8
  8. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework.egg-info/PKG-INFO +1 -1
  9. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework.egg-info/top_level.txt +0 -1
  10. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/pyproject.toml +1 -1
  11. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_utils/test_cert_utils.py +168 -0
  12. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/README.md +0 -0
  13. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/__init__.py +0 -0
  14. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/cli/__init__.py +0 -0
  15. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/cli/security_cli.py +0 -0
  16. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/constants.py +0 -0
  17. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/core/__init__.py +0 -0
  18. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/core/permission_manager.py +0 -0
  19. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/core/rate_limiter.py +0 -0
  20. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/core/security_manager.py +0 -0
  21. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/examples/__init__.py +0 -0
  22. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/examples/comprehensive_example.py +0 -0
  23. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/examples/django_example.py +0 -0
  24. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/examples/fastapi_example.py +0 -0
  25. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/examples/flask_example.py +0 -0
  26. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/examples/gateway_example.py +0 -0
  27. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/examples/microservice_example.py +0 -0
  28. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/examples/standalone_example.py +0 -0
  29. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/examples/test_all_examples.py +0 -0
  30. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/middleware/__init__.py +0 -0
  31. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/middleware/auth_middleware.py +0 -0
  32. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/middleware/fastapi_auth_middleware.py +0 -0
  33. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/middleware/fastapi_middleware.py +0 -0
  34. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/middleware/flask_auth_middleware.py +0 -0
  35. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/middleware/flask_middleware.py +0 -0
  36. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/middleware/rate_limit_middleware.py +0 -0
  37. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/middleware/security_middleware.py +0 -0
  38. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/schemas/__init__.py +0 -0
  39. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/schemas/config.py +0 -0
  40. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/schemas/models.py +0 -0
  41. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/schemas/responses.py +0 -0
  42. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/tests/__init__.py +0 -0
  43. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/utils/__init__.py +0 -0
  44. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/utils/crypto_utils.py +0 -0
  45. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/utils/datetime_compat.py +0 -0
  46. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework/utils/validation_utils.py +0 -0
  47. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework.egg-info/SOURCES.txt +0 -0
  48. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework.egg-info/dependency_links.txt +0 -0
  49. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework.egg-info/entry_points.txt +0 -0
  50. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/mcp_security_framework.egg-info/requires.txt +0 -0
  51. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/setup.cfg +0 -0
  52. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/__init__.py +0 -0
  53. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/conftest.py +0 -0
  54. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_cli/__init__.py +0 -0
  55. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_cli/test_cert_cli.py +0 -0
  56. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_cli/test_security_cli.py +0 -0
  57. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_core/__init__.py +0 -0
  58. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_core/test_auth_manager.py +0 -0
  59. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_core/test_cert_manager.py +0 -0
  60. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_core/test_permission_manager.py +0 -0
  61. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_core/test_rate_limiter.py +0 -0
  62. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_core/test_security_manager.py +0 -0
  63. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_core/test_ssl_manager.py +0 -0
  64. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_examples/__init__.py +0 -0
  65. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_examples/test_comprehensive_example.py +0 -0
  66. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_examples/test_fastapi_example.py +0 -0
  67. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_examples/test_flask_example.py +0 -0
  68. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_examples/test_standalone_example.py +0 -0
  69. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_integration/__init__.py +0 -0
  70. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_integration/test_auth_flow.py +0 -0
  71. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_integration/test_certificate_flow.py +0 -0
  72. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_integration/test_fastapi_integration.py +0 -0
  73. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_integration/test_flask_integration.py +0 -0
  74. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_integration/test_standalone_integration.py +0 -0
  75. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_middleware/__init__.py +0 -0
  76. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_middleware/test_fastapi_auth_middleware.py +0 -0
  77. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_middleware/test_fastapi_middleware.py +0 -0
  78. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_middleware/test_flask_auth_middleware.py +0 -0
  79. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_middleware/test_flask_middleware.py +0 -0
  80. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_middleware/test_security_middleware.py +0 -0
  81. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_schemas/__init__.py +0 -0
  82. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_schemas/test_config.py +0 -0
  83. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_schemas/test_models.py +0 -0
  84. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_schemas/test_responses.py +0 -0
  85. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_schemas/test_serialization.py +0 -0
  86. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_utils/__init__.py +0 -0
  87. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_utils/test_crypto_utils.py +0 -0
  88. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_utils/test_datetime_compat.py +0 -0
  89. {mcp_security_framework-1.1.2 → mcp_security_framework-1.2.0}/tests/test_utils/test_validation_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-security-framework
3
- Version: 1.1.2
3
+ Version: 1.2.0
4
4
  Summary: Universal security framework for microservices with SSL/TLS, authentication, authorization, and rate limiting. Requires cryptography>=42.0.0 for certificate operations.
5
5
  Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
6
6
  Maintainer-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
@@ -314,12 +314,18 @@ def create_client(
314
314
  type=click.Path(exists=True),
315
315
  help="Path to CA certificate for validation",
316
316
  )
317
+ @click.option(
318
+ "--crl",
319
+ type=click.Path(exists=True),
320
+ help="Path to CRL file for revocation check",
321
+ )
317
322
  @click.pass_context
318
- def validate(ctx, cert_path: str, ca_cert: Optional[str]):
323
+ def validate(ctx, cert_path: str, ca_cert: Optional[str], crl: Optional[str]):
319
324
  """
320
325
  Validate a certificate.
321
326
 
322
- This command validates a certificate and optionally checks it against a CA.
327
+ This command validates a certificate and optionally checks it against a CA
328
+ and CRL for revocation status.
323
329
  """
324
330
  try:
325
331
  config = ctx.obj["config"]
@@ -330,12 +336,16 @@ def validate(ctx, cert_path: str, ca_cert: Optional[str]):
330
336
  click.echo(f"Validating certificate: {cert_path}")
331
337
  if ca_cert:
332
338
  click.echo(f"Using CA certificate: {ca_cert}")
339
+ if crl:
340
+ click.echo(f"Using CRL file: {crl}")
333
341
 
334
342
  # Validate certificate
335
- is_valid = cert_manager.validate_certificate_chain(cert_path, ca_cert)
343
+ is_valid = cert_manager.validate_certificate_chain(cert_path, ca_cert, crl)
336
344
 
337
345
  if is_valid:
338
346
  click.echo(f"✅ Certificate is valid!")
347
+ if crl:
348
+ click.echo(f"✅ Certificate is not revoked according to CRL")
339
349
  else:
340
350
  click.echo(f"❌ Certificate validation failed!", err=True)
341
351
  raise click.Abort()
@@ -543,5 +553,159 @@ def revoke(ctx, serial_number: str, reason: str):
543
553
  raise click.Abort()
544
554
 
545
555
 
556
+ @cert_cli.command()
557
+ @click.argument("cert_path", type=click.Path(exists=True))
558
+ @click.option(
559
+ "--crl",
560
+ type=click.Path(exists=True),
561
+ help="Path to CRL file for revocation check",
562
+ )
563
+ @click.pass_context
564
+ def check_revocation(ctx, cert_path: str, crl: Optional[str]):
565
+ """
566
+ Check if certificate is revoked according to CRL.
567
+
568
+ This command checks if a certificate is revoked according to the provided CRL.
569
+ """
570
+ try:
571
+ config = ctx.obj["config"]
572
+ cert_manager = ctx.obj["cert_manager"]
573
+ verbose = ctx.obj["verbose"]
574
+
575
+ if verbose:
576
+ click.echo(f"Checking revocation status for certificate: {cert_path}")
577
+ if crl:
578
+ click.echo(f"Using CRL file: {crl}")
579
+
580
+ # Check if certificate is revoked
581
+ is_revoked = cert_manager.is_certificate_revoked(cert_path, crl)
582
+
583
+ if is_revoked:
584
+ click.echo(f"❌ Certificate is REVOKED!", err=True)
585
+ else:
586
+ click.echo(f"✅ Certificate is NOT revoked")
587
+
588
+ except Exception as e:
589
+ click.echo(f"❌ Failed to check revocation status: {str(e)}", err=True)
590
+ raise click.Abort()
591
+
592
+
593
+ @cert_cli.command()
594
+ @click.argument("cert_path", type=click.Path(exists=True))
595
+ @click.option(
596
+ "--crl",
597
+ type=click.Path(exists=True),
598
+ help="Path to CRL file for detailed revocation check",
599
+ )
600
+ @click.pass_context
601
+ def revocation_info(ctx, cert_path: str, crl: Optional[str]):
602
+ """
603
+ Get detailed revocation information for certificate.
604
+
605
+ This command provides detailed revocation information including
606
+ revocation date, reason, and CRL details.
607
+ """
608
+ try:
609
+ config = ctx.obj["config"]
610
+ cert_manager = ctx.obj["cert_manager"]
611
+ verbose = ctx.obj["verbose"]
612
+
613
+ if verbose:
614
+ click.echo(f"Getting revocation information for certificate: {cert_path}")
615
+ if crl:
616
+ click.echo(f"Using CRL file: {crl}")
617
+
618
+ # Get detailed revocation information
619
+ revocation_info = cert_manager.validate_certificate_against_crl(cert_path, crl)
620
+
621
+ click.echo(f"Certificate Serial Number: {revocation_info['serial_number']}")
622
+ click.echo(f"CRL Issuer: {revocation_info['crl_issuer']}")
623
+ click.echo(f"CRL Last Update: {revocation_info['crl_last_update']}")
624
+ click.echo(f"CRL Next Update: {revocation_info['crl_next_update']}")
625
+
626
+ if revocation_info["is_revoked"]:
627
+ click.echo(f"❌ Certificate is REVOKED!", err=True)
628
+ click.echo(f"Revocation Date: {revocation_info['revocation_date']}")
629
+ click.echo(f"Revocation Reason: {revocation_info['revocation_reason']}")
630
+ else:
631
+ click.echo(f"✅ Certificate is NOT revoked")
632
+
633
+ except Exception as e:
634
+ click.echo(f"❌ Failed to get revocation information: {str(e)}", err=True)
635
+ raise click.Abort()
636
+
637
+
638
+ @cert_cli.command()
639
+ @click.argument("crl_path", type=click.Path(exists=True))
640
+ @click.pass_context
641
+ def crl_info(ctx, crl_path: str):
642
+ """
643
+ Display CRL information.
644
+
645
+ This command displays detailed information about a CRL including
646
+ issuer, validity period, and revoked certificate count.
647
+ """
648
+ try:
649
+ config = ctx.obj["config"]
650
+ cert_manager = ctx.obj["cert_manager"]
651
+ verbose = ctx.obj["verbose"]
652
+
653
+ if verbose:
654
+ click.echo(f"Getting CRL information: {crl_path}")
655
+
656
+ # Get CRL information
657
+ crl_info = cert_manager.get_crl_info(crl_path)
658
+
659
+ click.echo(f"CRL Issuer: {crl_info['issuer']}")
660
+ click.echo(f"Last Update: {crl_info['last_update']}")
661
+ click.echo(f"Next Update: {crl_info['next_update']}")
662
+ click.echo(f"Revoked Certificates: {crl_info['revoked_certificates_count']}")
663
+ click.echo(f"Status: {crl_info['status']}")
664
+ click.echo(f"Version: {crl_info['version']}")
665
+ click.echo(f"Signature Algorithm: {crl_info['signature_algorithm']}")
666
+
667
+ if crl_info["is_expired"]:
668
+ click.echo(f"❌ CRL is EXPIRED!", err=True)
669
+ elif crl_info["expires_soon"]:
670
+ click.echo(f"⚠️ CRL expires soon ({crl_info['days_until_expiry']} days)", err=True)
671
+ else:
672
+ click.echo(f"✅ CRL is valid")
673
+
674
+ except Exception as e:
675
+ click.echo(f"❌ Failed to get CRL information: {str(e)}", err=True)
676
+ raise click.Abort()
677
+
678
+
679
+ @cert_cli.command()
680
+ @click.argument("crl_path", type=click.Path(exists=True))
681
+ @click.pass_context
682
+ def validate_crl(ctx, crl_path: str):
683
+ """
684
+ Validate CRL file.
685
+
686
+ This command validates a CRL file for format and validity period.
687
+ """
688
+ try:
689
+ config = ctx.obj["config"]
690
+ cert_manager = ctx.obj["cert_manager"]
691
+ verbose = ctx.obj["verbose"]
692
+
693
+ if verbose:
694
+ click.echo(f"Validating CRL: {crl_path}")
695
+
696
+ # Validate CRL
697
+ is_valid = cert_manager.is_crl_valid(crl_path)
698
+
699
+ if is_valid:
700
+ click.echo(f"✅ CRL is valid!")
701
+ else:
702
+ click.echo(f"❌ CRL validation failed!", err=True)
703
+ raise click.Abort()
704
+
705
+ except Exception as e:
706
+ click.echo(f"❌ CRL validation failed: {str(e)}", err=True)
707
+ raise click.Abort()
708
+
709
+
546
710
  if __name__ == "__main__":
547
711
  cert_cli()
@@ -628,8 +628,10 @@ class AuthManager:
628
628
  # Validate certificate chain if CA is configured
629
629
  if self.config.ca_cert_file:
630
630
  try:
631
+ # Check if CRL is configured for certificate validation
632
+ crl_file = getattr(self.config, 'crl_file', None)
631
633
  is_valid_chain = validate_certificate_chain(
632
- cert_pem, self.config.ca_cert_file
634
+ cert_pem, self.config.ca_cert_file, crl_file
633
635
  )
634
636
  if not is_valid_chain:
635
637
  return AuthResult(
@@ -56,8 +56,12 @@ from mcp_security_framework.utils.cert_utils import (
56
56
  extract_roles_from_certificate,
57
57
  get_certificate_expiry,
58
58
  get_certificate_serial_number,
59
+ get_crl_info,
60
+ is_certificate_revoked,
59
61
  is_certificate_self_signed,
62
+ is_crl_valid,
60
63
  parse_certificate,
64
+ validate_certificate_against_crl,
61
65
  validate_certificate_chain,
62
66
  )
63
67
  from mcp_security_framework.utils.datetime_compat import (
@@ -1327,21 +1331,27 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
1327
1331
  return False
1328
1332
 
1329
1333
  def validate_certificate_chain(
1330
- self, cert_path: str, ca_cert_path: Optional[str] = None
1334
+ self,
1335
+ cert_path: str,
1336
+ ca_cert_path: Optional[str] = None,
1337
+ crl_path: Optional[str] = None
1331
1338
  ) -> bool:
1332
1339
  """
1333
- Validate certificate chain against CA.
1340
+ Validate certificate chain against CA and optionally check CRL.
1334
1341
 
1335
1342
  This method validates a certificate chain by checking the certificate
1336
- against the CA certificate and verifying the chain of trust.
1343
+ against the CA certificate and verifying the chain of trust. If CRL
1344
+ is provided, it also checks if the certificate is revoked.
1337
1345
 
1338
1346
  Args:
1339
1347
  cert_path (str): Path to certificate to validate
1340
1348
  ca_cert_path (Optional[str]): Path to CA certificate. If None,
1341
1349
  uses CA certificate from configuration.
1350
+ crl_path (Optional[str]): Path to CRL file. If None, CRL check
1351
+ is skipped. If provided, certificate revocation is checked.
1342
1352
 
1343
1353
  Returns:
1344
- bool: True if certificate chain is valid, False otherwise
1354
+ bool: True if certificate chain is valid and not revoked, False otherwise
1345
1355
 
1346
1356
  Raises:
1347
1357
  FileNotFoundError: When certificate files are not found
@@ -1352,6 +1362,11 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
1352
1362
  >>> is_valid = cert_manager.validate_certificate_chain("client.crt")
1353
1363
  >>> if is_valid:
1354
1364
  ... print("Certificate chain is valid")
1365
+ >>>
1366
+ >>> # With CRL check
1367
+ >>> is_valid = cert_manager.validate_certificate_chain(
1368
+ ... "client.crt", crl_path="crl.pem"
1369
+ ... )
1355
1370
  """
1356
1371
  try:
1357
1372
  # Use configured CA certificate if not provided
@@ -1361,8 +1376,12 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
1361
1376
  if not ca_cert_path:
1362
1377
  raise CertificateConfigurationError("CA certificate path is required")
1363
1378
 
1364
- # Validate certificate chain
1365
- return validate_certificate_chain(cert_path, ca_cert_path)
1379
+ # Use configured CRL path if not provided
1380
+ if not crl_path and self.config.crl_enabled:
1381
+ crl_path = self.config.crl_path
1382
+
1383
+ # Validate certificate chain with optional CRL check
1384
+ return validate_certificate_chain(cert_path, ca_cert_path, crl_path)
1366
1385
 
1367
1386
  except Exception as e:
1368
1387
  self.logger.error(
@@ -1370,6 +1389,7 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
1370
1389
  extra={
1371
1390
  "cert_path": cert_path,
1372
1391
  "ca_cert_path": ca_cert_path,
1392
+ "crl_path": crl_path,
1373
1393
  "error": str(e),
1374
1394
  },
1375
1395
  )
@@ -1929,6 +1949,221 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
1929
1949
  f"CA private key file not found: {self.config.ca_key_path}"
1930
1950
  )
1931
1951
 
1952
+ def validate_certificate_against_crl(
1953
+ self,
1954
+ cert_path: str,
1955
+ crl_path: Optional[str] = None
1956
+ ) -> Dict[str, any]:
1957
+ """
1958
+ Validate certificate against CRL and return detailed revocation status.
1959
+
1960
+ This method checks if a certificate is revoked according to the
1961
+ provided CRL and returns detailed revocation information.
1962
+
1963
+ Args:
1964
+ cert_path (str): Path to certificate to validate
1965
+ crl_path (Optional[str]): Path to CRL file. If None, uses CRL
1966
+ from configuration if CRL is enabled.
1967
+
1968
+ Returns:
1969
+ Dict[str, any]: Dictionary containing revocation status and details:
1970
+ - is_revoked (bool): True if certificate is revoked
1971
+ - serial_number (str): Certificate serial number
1972
+ - revocation_date (datetime): Date of revocation (if revoked)
1973
+ - revocation_reason (str): Reason for revocation (if revoked)
1974
+ - crl_issuer (str): CRL issuer information
1975
+ - crl_last_update (datetime): CRL last update time
1976
+ - crl_next_update (datetime): CRL next update time
1977
+
1978
+ Raises:
1979
+ CertificateConfigurationError: When CRL configuration is invalid
1980
+ CertificateValidationError: When CRL validation fails
1981
+
1982
+ Example:
1983
+ >>> cert_manager = CertificateManager(config)
1984
+ >>> result = cert_manager.validate_certificate_against_crl("client.crt")
1985
+ >>> if result["is_revoked"]:
1986
+ ... print(f"Certificate revoked: {result['revocation_reason']}")
1987
+ """
1988
+ try:
1989
+ # Use configured CRL path if not provided
1990
+ if not crl_path:
1991
+ if not self.config.crl_enabled:
1992
+ raise CertificateConfigurationError("CRL is not enabled in configuration")
1993
+ crl_path = self.config.crl_path
1994
+
1995
+ if not crl_path:
1996
+ raise CertificateConfigurationError("CRL path is required")
1997
+
1998
+ # Validate certificate against CRL
1999
+ return validate_certificate_against_crl(cert_path, crl_path)
2000
+
2001
+ except Exception as e:
2002
+ self.logger.error(
2003
+ "Certificate CRL validation failed",
2004
+ extra={
2005
+ "cert_path": cert_path,
2006
+ "crl_path": crl_path,
2007
+ "error": str(e),
2008
+ },
2009
+ )
2010
+ raise CertificateValidationError(f"CRL validation failed: {str(e)}")
2011
+
2012
+ def is_certificate_revoked(
2013
+ self,
2014
+ cert_path: str,
2015
+ crl_path: Optional[str] = None
2016
+ ) -> bool:
2017
+ """
2018
+ Check if certificate is revoked according to CRL.
2019
+
2020
+ This method provides a simple boolean check for certificate revocation
2021
+ without detailed revocation information.
2022
+
2023
+ Args:
2024
+ cert_path (str): Path to certificate to check
2025
+ crl_path (Optional[str]): Path to CRL file. If None, uses CRL
2026
+ from configuration if CRL is enabled.
2027
+
2028
+ Returns:
2029
+ bool: True if certificate is revoked, False otherwise
2030
+
2031
+ Raises:
2032
+ CertificateConfigurationError: When CRL configuration is invalid
2033
+ CertificateValidationError: When CRL validation fails
2034
+
2035
+ Example:
2036
+ >>> cert_manager = CertificateManager(config)
2037
+ >>> if cert_manager.is_certificate_revoked("client.crt"):
2038
+ ... print("Certificate is revoked")
2039
+ """
2040
+ try:
2041
+ # Use configured CRL path if not provided
2042
+ if not crl_path:
2043
+ if not self.config.crl_enabled:
2044
+ raise CertificateConfigurationError("CRL is not enabled in configuration")
2045
+ crl_path = self.config.crl_path
2046
+
2047
+ if not crl_path:
2048
+ raise CertificateConfigurationError("CRL path is required")
2049
+
2050
+ # Check if certificate is revoked
2051
+ return is_certificate_revoked(cert_path, crl_path)
2052
+
2053
+ except Exception as e:
2054
+ self.logger.error(
2055
+ "Certificate revocation check failed",
2056
+ extra={
2057
+ "cert_path": cert_path,
2058
+ "crl_path": crl_path,
2059
+ "error": str(e),
2060
+ },
2061
+ )
2062
+ raise CertificateValidationError(f"Revocation check failed: {str(e)}")
2063
+
2064
+ def get_crl_info(self, crl_path: Optional[str] = None) -> Dict:
2065
+ """
2066
+ Get detailed information from CRL.
2067
+
2068
+ This method extracts comprehensive information from a CRL including
2069
+ issuer details, validity period, and revoked certificate count.
2070
+
2071
+ Args:
2072
+ crl_path (Optional[str]): Path to CRL file. If None, uses CRL
2073
+ from configuration if CRL is enabled.
2074
+
2075
+ Returns:
2076
+ Dict: Dictionary containing CRL information:
2077
+ - issuer (str): CRL issuer information
2078
+ - last_update (datetime): CRL last update time
2079
+ - next_update (datetime): CRL next update time
2080
+ - revoked_certificates_count (int): Number of revoked certificates
2081
+ - days_until_expiry (int): Days until CRL expires
2082
+ - is_expired (bool): True if CRL is expired
2083
+ - expires_soon (bool): True if CRL expires within 7 days
2084
+ - status (str): CRL status (valid, expires_soon, expired)
2085
+ - version (str): CRL version
2086
+ - signature_algorithm (str): Signature algorithm used
2087
+ - signature (str): CRL signature in hex format
2088
+
2089
+ Raises:
2090
+ CertificateConfigurationError: When CRL configuration is invalid
2091
+ CertificateValidationError: When CRL information extraction fails
2092
+
2093
+ Example:
2094
+ >>> cert_manager = CertificateManager(config)
2095
+ >>> crl_info = cert_manager.get_crl_info()
2096
+ >>> print(f"CRL has {crl_info['revoked_certificates_count']} revoked certificates")
2097
+ """
2098
+ try:
2099
+ # Use configured CRL path if not provided
2100
+ if not crl_path:
2101
+ if not self.config.crl_enabled:
2102
+ raise CertificateConfigurationError("CRL is not enabled in configuration")
2103
+ crl_path = self.config.crl_path
2104
+
2105
+ if not crl_path:
2106
+ raise CertificateConfigurationError("CRL path is required")
2107
+
2108
+ # Get CRL information
2109
+ return get_crl_info(crl_path)
2110
+
2111
+ except Exception as e:
2112
+ self.logger.error(
2113
+ "CRL information extraction failed",
2114
+ extra={
2115
+ "crl_path": crl_path,
2116
+ "error": str(e),
2117
+ },
2118
+ )
2119
+ raise CertificateValidationError(f"CRL information extraction failed: {str(e)}")
2120
+
2121
+ def is_crl_valid(self, crl_path: Optional[str] = None) -> bool:
2122
+ """
2123
+ Check if CRL is valid (not expired and properly formatted).
2124
+
2125
+ This method validates CRL format and checks if it's within its
2126
+ validity period.
2127
+
2128
+ Args:
2129
+ crl_path (Optional[str]): Path to CRL file. If None, uses CRL
2130
+ from configuration if CRL is enabled.
2131
+
2132
+ Returns:
2133
+ bool: True if CRL is valid, False otherwise
2134
+
2135
+ Raises:
2136
+ CertificateConfigurationError: When CRL configuration is invalid
2137
+ CertificateValidationError: When CRL validation fails
2138
+
2139
+ Example:
2140
+ >>> cert_manager = CertificateManager(config)
2141
+ >>> if cert_manager.is_crl_valid():
2142
+ ... print("CRL is valid")
2143
+ """
2144
+ try:
2145
+ # Use configured CRL path if not provided
2146
+ if not crl_path:
2147
+ if not self.config.crl_enabled:
2148
+ raise CertificateConfigurationError("CRL is not enabled in configuration")
2149
+ crl_path = self.config.crl_path
2150
+
2151
+ if not crl_path:
2152
+ raise CertificateConfigurationError("CRL path is required")
2153
+
2154
+ # Check if CRL is valid
2155
+ return is_crl_valid(crl_path)
2156
+
2157
+ except Exception as e:
2158
+ self.logger.error(
2159
+ "CRL validation failed",
2160
+ extra={
2161
+ "crl_path": crl_path,
2162
+ "error": str(e),
2163
+ },
2164
+ )
2165
+ raise CertificateValidationError(f"CRL validation failed: {str(e)}")
2166
+
1932
2167
 
1933
2168
  class CertificateConfigurationError(Exception):
1934
2169
  """Raised when certificate configuration is invalid."""
@@ -37,6 +37,7 @@ from ..schemas.models import CertificateInfo
37
37
  from ..utils.cert_utils import (
38
38
  extract_certificate_info,
39
39
  get_certificate_expiry,
40
+ is_certificate_revoked,
40
41
  is_certificate_self_signed,
41
42
  parse_certificate,
42
43
  validate_certificate_chain,
@@ -349,21 +350,24 @@ class SSLManager:
349
350
  f"Failed to create client SSL context: {str(e)}"
350
351
  )
351
352
 
352
- def validate_certificate(self, cert_path: str) -> bool:
353
+ def validate_certificate(self, cert_path: str, crl_path: Optional[str] = None) -> bool:
353
354
  """
354
- Validate certificate file.
355
+ Validate certificate file with optional CRL check.
355
356
 
356
357
  This method validates a certificate file by checking its format,
357
- parsing it, and verifying basic certificate properties.
358
+ parsing it, and verifying basic certificate properties. If CRL
359
+ is provided, it also checks if the certificate is revoked.
358
360
 
359
361
  Args:
360
362
  cert_path (str): Path to certificate file to validate.
361
363
  Must be a valid PEM or DER certificate file path.
364
+ crl_path (Optional[str]): Path to CRL file. If None, CRL check
365
+ is skipped. If provided, certificate revocation is checked.
362
366
 
363
367
  Returns:
364
- bool: True if certificate is valid, False otherwise.
365
- Returns True when certificate can be parsed and has
366
- valid basic properties.
368
+ bool: True if certificate is valid and not revoked, False otherwise.
369
+ Returns True when certificate can be parsed, has valid basic
370
+ properties, and is not revoked (if CRL is provided).
367
371
 
368
372
  Raises:
369
373
  FileNotFoundError: If certificate file is not found.
@@ -374,8 +378,9 @@ class SSLManager:
374
378
  >>> is_valid = ssl_manager.validate_certificate("server.crt")
375
379
  >>> if is_valid:
376
380
  ... print("Certificate is valid")
377
- >>> else:
378
- ... print("Certificate is invalid")
381
+ >>>
382
+ >>> # With CRL check
383
+ >>> is_valid = ssl_manager.validate_certificate("server.crt", "crl.pem")
379
384
  """
380
385
  try:
381
386
  # Check if file exists
@@ -402,8 +407,35 @@ class SSLManager:
402
407
  )
403
408
  return False
404
409
 
410
+ # Check CRL if provided
411
+ if crl_path:
412
+ try:
413
+ if is_certificate_revoked(cert_path, crl_path):
414
+ self.logger.warning(
415
+ "Certificate is revoked",
416
+ extra={
417
+ "cert_path": cert_path,
418
+ "crl_path": crl_path,
419
+ },
420
+ )
421
+ return False
422
+ except Exception as e:
423
+ self.logger.error(
424
+ "CRL validation failed",
425
+ extra={
426
+ "cert_path": cert_path,
427
+ "crl_path": crl_path,
428
+ "error": str(e),
429
+ },
430
+ )
431
+ return False
432
+
405
433
  self.logger.info(
406
- "Certificate validation successful", extra={"cert_path": cert_path}
434
+ "Certificate validation successful",
435
+ extra={
436
+ "cert_path": cert_path,
437
+ "crl_checked": crl_path is not None,
438
+ }
407
439
  )
408
440
 
409
441
  return True
@@ -223,9 +223,17 @@ class MTLSMiddleware(SecurityMiddleware):
223
223
  Dict[str, Any]: Validation result with status and details
224
224
  """
225
225
  try:
226
- # Use security manager's certificate validation
226
+ # Get CA certificate file
227
+ ca_cert_file = self.config.ssl.ca_cert_file if self.config.ssl else None
228
+
229
+ # Get CRL file if configured
230
+ crl_file = None
231
+ if self.config.ssl and hasattr(self.config.ssl, 'crl_file'):
232
+ crl_file = self.config.ssl.crl_file
233
+
234
+ # Use security manager's certificate validation with optional CRL check
227
235
  is_valid = self.security_manager.cert_manager.validate_certificate_chain(
228
- cert_pem, self.config.ssl.ca_cert_file if self.config.ssl else None
236
+ cert_pem, ca_cert_file, crl_file
229
237
  )
230
238
 
231
239
  if is_valid: