mcp-security-framework 1.1.2__py3-none-any.whl → 1.2.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.
- mcp_security_framework/__init__.py +1 -1
- mcp_security_framework/cli/cert_cli.py +167 -3
- mcp_security_framework/core/auth_manager.py +32 -10
- mcp_security_framework/core/cert_manager.py +261 -6
- mcp_security_framework/core/ssl_manager.py +41 -9
- mcp_security_framework/middleware/mtls_middleware.py +10 -2
- mcp_security_framework/schemas/config.py +31 -0
- mcp_security_framework/schemas/models.py +46 -0
- mcp_security_framework/utils/cert_utils.py +309 -8
- {mcp_security_framework-1.1.2.dist-info → mcp_security_framework-1.2.1.dist-info}/METADATA +1 -1
- {mcp_security_framework-1.1.2.dist-info → mcp_security_framework-1.2.1.dist-info}/RECORD +17 -16
- tests/test_core/test_auth_manager.py +6 -6
- tests/test_utils/test_cert_utils.py +168 -0
- tests/test_utils/test_unitid_compat.py +550 -0
- {mcp_security_framework-1.1.2.dist-info → mcp_security_framework-1.2.1.dist-info}/WHEEL +0 -0
- {mcp_security_framework-1.1.2.dist-info → mcp_security_framework-1.2.1.dist-info}/entry_points.txt +0 -0
- {mcp_security_framework-1.1.2.dist-info → mcp_security_framework-1.2.1.dist-info}/top_level.txt +0 -0
@@ -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()
|
@@ -38,6 +38,7 @@ from ..schemas.models import AuthResult, AuthStatus, ValidationResult
|
|
38
38
|
from ..utils.cert_utils import (
|
39
39
|
extract_permissions_from_certificate,
|
40
40
|
extract_roles_from_certificate,
|
41
|
+
extract_unitid_from_certificate,
|
41
42
|
parse_certificate,
|
42
43
|
validate_certificate_chain,
|
43
44
|
)
|
@@ -124,20 +125,21 @@ class AuthManager:
|
|
124
125
|
self.logger = logging.getLogger(__name__)
|
125
126
|
|
126
127
|
# Initialize storage
|
127
|
-
#
|
128
|
+
# Store API keys in format: {"api_key": "username"}
|
128
129
|
# or handle new format "key": {"username": "user", "roles": ["role1", "role2"]}
|
129
130
|
if config.api_keys:
|
130
131
|
self._api_keys = {}
|
131
132
|
self._api_key_metadata = {}
|
132
133
|
for key, value in config.api_keys.items():
|
133
134
|
if isinstance(value, str):
|
134
|
-
# Old format: "
|
135
|
+
# Old format: "username": "api_key" -> store as {"api_key": "username"}
|
135
136
|
self._api_keys[value] = key
|
136
137
|
elif isinstance(value, dict):
|
137
|
-
# New format: "
|
138
|
+
# New format: "username": {"api_key": "key", "roles": ["role1", "role2"]}
|
139
|
+
api_key = value.get("api_key", key)
|
138
140
|
username = value.get("username", key)
|
139
|
-
self._api_keys[
|
140
|
-
self._api_key_metadata[
|
141
|
+
self._api_keys[api_key] = username
|
142
|
+
self._api_key_metadata[api_key] = value
|
141
143
|
else:
|
142
144
|
self.logger.warning(
|
143
145
|
f"Invalid API key format for key {key}: {value}"
|
@@ -253,7 +255,7 @@ class AuthManager:
|
|
253
255
|
# Find user by API key
|
254
256
|
username = None
|
255
257
|
user_roles = []
|
256
|
-
for
|
258
|
+
for api_key_in_config, user in self._api_keys.items():
|
257
259
|
if api_key_in_config == api_key:
|
258
260
|
username = user
|
259
261
|
# Check if we have metadata for this API key
|
@@ -625,11 +627,23 @@ class AuthManager:
|
|
625
627
|
)
|
626
628
|
roles = []
|
627
629
|
|
630
|
+
# Extract unitid from certificate
|
631
|
+
unitid = None
|
632
|
+
try:
|
633
|
+
unitid = extract_unitid_from_certificate(cert_pem)
|
634
|
+
except Exception as e:
|
635
|
+
self.logger.warning(
|
636
|
+
"Failed to extract unitid from certificate",
|
637
|
+
extra={"username": username, "error": str(e)},
|
638
|
+
)
|
639
|
+
|
628
640
|
# Validate certificate chain if CA is configured
|
629
641
|
if self.config.ca_cert_file:
|
630
642
|
try:
|
643
|
+
# Check if CRL is configured for certificate validation
|
644
|
+
crl_file = getattr(self.config, 'crl_file', None)
|
631
645
|
is_valid_chain = validate_certificate_chain(
|
632
|
-
cert_pem, self.config.ca_cert_file
|
646
|
+
cert_pem, self.config.ca_cert_file, crl_file
|
633
647
|
)
|
634
648
|
if not is_valid_chain:
|
635
649
|
return AuthResult(
|
@@ -654,6 +668,7 @@ class AuthManager:
|
|
654
668
|
auth_method="certificate",
|
655
669
|
auth_timestamp=datetime.now(timezone.utc),
|
656
670
|
token_expiry=get_not_valid_after_utc(cert),
|
671
|
+
unitid=unitid,
|
657
672
|
)
|
658
673
|
|
659
674
|
self.logger.info(
|
@@ -859,7 +874,7 @@ class AuthManager:
|
|
859
874
|
if not validate_api_key_format(api_key):
|
860
875
|
return False
|
861
876
|
|
862
|
-
self._api_keys[
|
877
|
+
self._api_keys[api_key] = username
|
863
878
|
|
864
879
|
self.logger.info("API key added for user", extra={"username": username})
|
865
880
|
|
@@ -882,8 +897,15 @@ class AuthManager:
|
|
882
897
|
bool: True if API key was removed successfully, False otherwise
|
883
898
|
"""
|
884
899
|
try:
|
885
|
-
|
886
|
-
|
900
|
+
# Find API key for the username and remove it
|
901
|
+
api_key_to_remove = None
|
902
|
+
for api_key, user in self._api_keys.items():
|
903
|
+
if user == username:
|
904
|
+
api_key_to_remove = api_key
|
905
|
+
break
|
906
|
+
|
907
|
+
if api_key_to_remove:
|
908
|
+
del self._api_keys[api_key_to_remove]
|
887
909
|
|
888
910
|
self.logger.info(
|
889
911
|
"API key removed for user", extra={"username": username}
|
@@ -29,6 +29,7 @@ License: MIT
|
|
29
29
|
|
30
30
|
import logging
|
31
31
|
import os
|
32
|
+
import uuid
|
32
33
|
from datetime import datetime, timedelta, timezone
|
33
34
|
from pathlib import Path
|
34
35
|
from typing import Dict, List, Optional, Tuple, Union
|
@@ -54,10 +55,15 @@ from mcp_security_framework.schemas.models import (
|
|
54
55
|
from mcp_security_framework.utils.cert_utils import (
|
55
56
|
extract_permissions_from_certificate,
|
56
57
|
extract_roles_from_certificate,
|
58
|
+
extract_unitid_from_certificate,
|
57
59
|
get_certificate_expiry,
|
58
60
|
get_certificate_serial_number,
|
61
|
+
get_crl_info,
|
62
|
+
is_certificate_revoked,
|
59
63
|
is_certificate_self_signed,
|
64
|
+
is_crl_valid,
|
60
65
|
parse_certificate,
|
66
|
+
validate_certificate_against_crl,
|
61
67
|
validate_certificate_chain,
|
62
68
|
)
|
63
69
|
from mcp_security_framework.utils.datetime_compat import (
|
@@ -250,6 +256,14 @@ class CertificateManager:
|
|
250
256
|
x509.BasicConstraints(ca=True, path_length=None), critical=True
|
251
257
|
)
|
252
258
|
|
259
|
+
# Add unitid extension if provided
|
260
|
+
if ca_config.unitid:
|
261
|
+
unitid_extension = x509.UnrecognizedExtension(
|
262
|
+
oid=x509.ObjectIdentifier("1.3.6.1.4.1.99999.1.3"),
|
263
|
+
value=ca_config.unitid.encode(),
|
264
|
+
)
|
265
|
+
builder = builder.add_extension(unitid_extension, critical=False)
|
266
|
+
|
253
267
|
builder = builder.add_extension(
|
254
268
|
x509.KeyUsage(
|
255
269
|
digital_signature=True,
|
@@ -321,6 +335,7 @@ class CertificateManager:
|
|
321
335
|
not_after=get_not_valid_after_utc(certificate),
|
322
336
|
certificate_type=CertificateType.ROOT_CA,
|
323
337
|
key_size=ca_config.key_size,
|
338
|
+
unitid=ca_config.unitid,
|
324
339
|
)
|
325
340
|
|
326
341
|
self.logger.info(
|
@@ -762,6 +777,14 @@ class CertificateManager:
|
|
762
777
|
)
|
763
778
|
builder = builder.add_extension(permissions_extension, critical=False)
|
764
779
|
|
780
|
+
# Add unitid extension if provided
|
781
|
+
if client_config.unitid:
|
782
|
+
unitid_extension = x509.UnrecognizedExtension(
|
783
|
+
oid=x509.ObjectIdentifier("1.3.6.1.4.1.99999.1.3"),
|
784
|
+
value=client_config.unitid.encode(),
|
785
|
+
)
|
786
|
+
builder = builder.add_extension(unitid_extension, critical=False)
|
787
|
+
|
765
788
|
# Create certificate
|
766
789
|
certificate = builder.sign(ca_key, hashes.SHA256())
|
767
790
|
|
@@ -812,6 +835,7 @@ class CertificateManager:
|
|
812
835
|
not_after=get_not_valid_after_utc(certificate),
|
813
836
|
certificate_type=CertificateType.CLIENT,
|
814
837
|
key_size=client_config.key_size,
|
838
|
+
unitid=client_config.unitid,
|
815
839
|
)
|
816
840
|
|
817
841
|
self.logger.info(
|
@@ -1327,21 +1351,27 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1327
1351
|
return False
|
1328
1352
|
|
1329
1353
|
def validate_certificate_chain(
|
1330
|
-
self,
|
1354
|
+
self,
|
1355
|
+
cert_path: str,
|
1356
|
+
ca_cert_path: Optional[str] = None,
|
1357
|
+
crl_path: Optional[str] = None
|
1331
1358
|
) -> bool:
|
1332
1359
|
"""
|
1333
|
-
Validate certificate chain against CA.
|
1360
|
+
Validate certificate chain against CA and optionally check CRL.
|
1334
1361
|
|
1335
1362
|
This method validates a certificate chain by checking the certificate
|
1336
|
-
against the CA certificate and verifying the chain of trust.
|
1363
|
+
against the CA certificate and verifying the chain of trust. If CRL
|
1364
|
+
is provided, it also checks if the certificate is revoked.
|
1337
1365
|
|
1338
1366
|
Args:
|
1339
1367
|
cert_path (str): Path to certificate to validate
|
1340
1368
|
ca_cert_path (Optional[str]): Path to CA certificate. If None,
|
1341
1369
|
uses CA certificate from configuration.
|
1370
|
+
crl_path (Optional[str]): Path to CRL file. If None, CRL check
|
1371
|
+
is skipped. If provided, certificate revocation is checked.
|
1342
1372
|
|
1343
1373
|
Returns:
|
1344
|
-
bool: True if certificate chain is valid, False otherwise
|
1374
|
+
bool: True if certificate chain is valid and not revoked, False otherwise
|
1345
1375
|
|
1346
1376
|
Raises:
|
1347
1377
|
FileNotFoundError: When certificate files are not found
|
@@ -1352,6 +1382,11 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1352
1382
|
>>> is_valid = cert_manager.validate_certificate_chain("client.crt")
|
1353
1383
|
>>> if is_valid:
|
1354
1384
|
... print("Certificate chain is valid")
|
1385
|
+
>>>
|
1386
|
+
>>> # With CRL check
|
1387
|
+
>>> is_valid = cert_manager.validate_certificate_chain(
|
1388
|
+
... "client.crt", crl_path="crl.pem"
|
1389
|
+
... )
|
1355
1390
|
"""
|
1356
1391
|
try:
|
1357
1392
|
# Use configured CA certificate if not provided
|
@@ -1361,8 +1396,12 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1361
1396
|
if not ca_cert_path:
|
1362
1397
|
raise CertificateConfigurationError("CA certificate path is required")
|
1363
1398
|
|
1364
|
-
#
|
1365
|
-
|
1399
|
+
# Use configured CRL path if not provided
|
1400
|
+
if not crl_path and self.config.crl_enabled:
|
1401
|
+
crl_path = self.config.crl_path
|
1402
|
+
|
1403
|
+
# Validate certificate chain with optional CRL check
|
1404
|
+
return validate_certificate_chain(cert_path, ca_cert_path, crl_path)
|
1366
1405
|
|
1367
1406
|
except Exception as e:
|
1368
1407
|
self.logger.error(
|
@@ -1370,6 +1409,7 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1370
1409
|
extra={
|
1371
1410
|
"cert_path": cert_path,
|
1372
1411
|
"ca_cert_path": ca_cert_path,
|
1412
|
+
"crl_path": crl_path,
|
1373
1413
|
"error": str(e),
|
1374
1414
|
},
|
1375
1415
|
)
|
@@ -1929,6 +1969,221 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1929
1969
|
f"CA private key file not found: {self.config.ca_key_path}"
|
1930
1970
|
)
|
1931
1971
|
|
1972
|
+
def validate_certificate_against_crl(
|
1973
|
+
self,
|
1974
|
+
cert_path: str,
|
1975
|
+
crl_path: Optional[str] = None
|
1976
|
+
) -> Dict[str, any]:
|
1977
|
+
"""
|
1978
|
+
Validate certificate against CRL and return detailed revocation status.
|
1979
|
+
|
1980
|
+
This method checks if a certificate is revoked according to the
|
1981
|
+
provided CRL and returns detailed revocation information.
|
1982
|
+
|
1983
|
+
Args:
|
1984
|
+
cert_path (str): Path to certificate to validate
|
1985
|
+
crl_path (Optional[str]): Path to CRL file. If None, uses CRL
|
1986
|
+
from configuration if CRL is enabled.
|
1987
|
+
|
1988
|
+
Returns:
|
1989
|
+
Dict[str, any]: Dictionary containing revocation status and details:
|
1990
|
+
- is_revoked (bool): True if certificate is revoked
|
1991
|
+
- serial_number (str): Certificate serial number
|
1992
|
+
- revocation_date (datetime): Date of revocation (if revoked)
|
1993
|
+
- revocation_reason (str): Reason for revocation (if revoked)
|
1994
|
+
- crl_issuer (str): CRL issuer information
|
1995
|
+
- crl_last_update (datetime): CRL last update time
|
1996
|
+
- crl_next_update (datetime): CRL next update time
|
1997
|
+
|
1998
|
+
Raises:
|
1999
|
+
CertificateConfigurationError: When CRL configuration is invalid
|
2000
|
+
CertificateValidationError: When CRL validation fails
|
2001
|
+
|
2002
|
+
Example:
|
2003
|
+
>>> cert_manager = CertificateManager(config)
|
2004
|
+
>>> result = cert_manager.validate_certificate_against_crl("client.crt")
|
2005
|
+
>>> if result["is_revoked"]:
|
2006
|
+
... print(f"Certificate revoked: {result['revocation_reason']}")
|
2007
|
+
"""
|
2008
|
+
try:
|
2009
|
+
# Use configured CRL path if not provided
|
2010
|
+
if not crl_path:
|
2011
|
+
if not self.config.crl_enabled:
|
2012
|
+
raise CertificateConfigurationError("CRL is not enabled in configuration")
|
2013
|
+
crl_path = self.config.crl_path
|
2014
|
+
|
2015
|
+
if not crl_path:
|
2016
|
+
raise CertificateConfigurationError("CRL path is required")
|
2017
|
+
|
2018
|
+
# Validate certificate against CRL
|
2019
|
+
return validate_certificate_against_crl(cert_path, crl_path)
|
2020
|
+
|
2021
|
+
except Exception as e:
|
2022
|
+
self.logger.error(
|
2023
|
+
"Certificate CRL validation failed",
|
2024
|
+
extra={
|
2025
|
+
"cert_path": cert_path,
|
2026
|
+
"crl_path": crl_path,
|
2027
|
+
"error": str(e),
|
2028
|
+
},
|
2029
|
+
)
|
2030
|
+
raise CertificateValidationError(f"CRL validation failed: {str(e)}")
|
2031
|
+
|
2032
|
+
def is_certificate_revoked(
|
2033
|
+
self,
|
2034
|
+
cert_path: str,
|
2035
|
+
crl_path: Optional[str] = None
|
2036
|
+
) -> bool:
|
2037
|
+
"""
|
2038
|
+
Check if certificate is revoked according to CRL.
|
2039
|
+
|
2040
|
+
This method provides a simple boolean check for certificate revocation
|
2041
|
+
without detailed revocation information.
|
2042
|
+
|
2043
|
+
Args:
|
2044
|
+
cert_path (str): Path to certificate to check
|
2045
|
+
crl_path (Optional[str]): Path to CRL file. If None, uses CRL
|
2046
|
+
from configuration if CRL is enabled.
|
2047
|
+
|
2048
|
+
Returns:
|
2049
|
+
bool: True if certificate is revoked, False otherwise
|
2050
|
+
|
2051
|
+
Raises:
|
2052
|
+
CertificateConfigurationError: When CRL configuration is invalid
|
2053
|
+
CertificateValidationError: When CRL validation fails
|
2054
|
+
|
2055
|
+
Example:
|
2056
|
+
>>> cert_manager = CertificateManager(config)
|
2057
|
+
>>> if cert_manager.is_certificate_revoked("client.crt"):
|
2058
|
+
... print("Certificate is revoked")
|
2059
|
+
"""
|
2060
|
+
try:
|
2061
|
+
# Use configured CRL path if not provided
|
2062
|
+
if not crl_path:
|
2063
|
+
if not self.config.crl_enabled:
|
2064
|
+
raise CertificateConfigurationError("CRL is not enabled in configuration")
|
2065
|
+
crl_path = self.config.crl_path
|
2066
|
+
|
2067
|
+
if not crl_path:
|
2068
|
+
raise CertificateConfigurationError("CRL path is required")
|
2069
|
+
|
2070
|
+
# Check if certificate is revoked
|
2071
|
+
return is_certificate_revoked(cert_path, crl_path)
|
2072
|
+
|
2073
|
+
except Exception as e:
|
2074
|
+
self.logger.error(
|
2075
|
+
"Certificate revocation check failed",
|
2076
|
+
extra={
|
2077
|
+
"cert_path": cert_path,
|
2078
|
+
"crl_path": crl_path,
|
2079
|
+
"error": str(e),
|
2080
|
+
},
|
2081
|
+
)
|
2082
|
+
raise CertificateValidationError(f"Revocation check failed: {str(e)}")
|
2083
|
+
|
2084
|
+
def get_crl_info(self, crl_path: Optional[str] = None) -> Dict:
|
2085
|
+
"""
|
2086
|
+
Get detailed information from CRL.
|
2087
|
+
|
2088
|
+
This method extracts comprehensive information from a CRL including
|
2089
|
+
issuer details, validity period, and revoked certificate count.
|
2090
|
+
|
2091
|
+
Args:
|
2092
|
+
crl_path (Optional[str]): Path to CRL file. If None, uses CRL
|
2093
|
+
from configuration if CRL is enabled.
|
2094
|
+
|
2095
|
+
Returns:
|
2096
|
+
Dict: Dictionary containing CRL information:
|
2097
|
+
- issuer (str): CRL issuer information
|
2098
|
+
- last_update (datetime): CRL last update time
|
2099
|
+
- next_update (datetime): CRL next update time
|
2100
|
+
- revoked_certificates_count (int): Number of revoked certificates
|
2101
|
+
- days_until_expiry (int): Days until CRL expires
|
2102
|
+
- is_expired (bool): True if CRL is expired
|
2103
|
+
- expires_soon (bool): True if CRL expires within 7 days
|
2104
|
+
- status (str): CRL status (valid, expires_soon, expired)
|
2105
|
+
- version (str): CRL version
|
2106
|
+
- signature_algorithm (str): Signature algorithm used
|
2107
|
+
- signature (str): CRL signature in hex format
|
2108
|
+
|
2109
|
+
Raises:
|
2110
|
+
CertificateConfigurationError: When CRL configuration is invalid
|
2111
|
+
CertificateValidationError: When CRL information extraction fails
|
2112
|
+
|
2113
|
+
Example:
|
2114
|
+
>>> cert_manager = CertificateManager(config)
|
2115
|
+
>>> crl_info = cert_manager.get_crl_info()
|
2116
|
+
>>> print(f"CRL has {crl_info['revoked_certificates_count']} revoked certificates")
|
2117
|
+
"""
|
2118
|
+
try:
|
2119
|
+
# Use configured CRL path if not provided
|
2120
|
+
if not crl_path:
|
2121
|
+
if not self.config.crl_enabled:
|
2122
|
+
raise CertificateConfigurationError("CRL is not enabled in configuration")
|
2123
|
+
crl_path = self.config.crl_path
|
2124
|
+
|
2125
|
+
if not crl_path:
|
2126
|
+
raise CertificateConfigurationError("CRL path is required")
|
2127
|
+
|
2128
|
+
# Get CRL information
|
2129
|
+
return get_crl_info(crl_path)
|
2130
|
+
|
2131
|
+
except Exception as e:
|
2132
|
+
self.logger.error(
|
2133
|
+
"CRL information extraction failed",
|
2134
|
+
extra={
|
2135
|
+
"crl_path": crl_path,
|
2136
|
+
"error": str(e),
|
2137
|
+
},
|
2138
|
+
)
|
2139
|
+
raise CertificateValidationError(f"CRL information extraction failed: {str(e)}")
|
2140
|
+
|
2141
|
+
def is_crl_valid(self, crl_path: Optional[str] = None) -> bool:
|
2142
|
+
"""
|
2143
|
+
Check if CRL is valid (not expired and properly formatted).
|
2144
|
+
|
2145
|
+
This method validates CRL format and checks if it's within its
|
2146
|
+
validity period.
|
2147
|
+
|
2148
|
+
Args:
|
2149
|
+
crl_path (Optional[str]): Path to CRL file. If None, uses CRL
|
2150
|
+
from configuration if CRL is enabled.
|
2151
|
+
|
2152
|
+
Returns:
|
2153
|
+
bool: True if CRL is valid, False otherwise
|
2154
|
+
|
2155
|
+
Raises:
|
2156
|
+
CertificateConfigurationError: When CRL configuration is invalid
|
2157
|
+
CertificateValidationError: When CRL validation fails
|
2158
|
+
|
2159
|
+
Example:
|
2160
|
+
>>> cert_manager = CertificateManager(config)
|
2161
|
+
>>> if cert_manager.is_crl_valid():
|
2162
|
+
... print("CRL is valid")
|
2163
|
+
"""
|
2164
|
+
try:
|
2165
|
+
# Use configured CRL path if not provided
|
2166
|
+
if not crl_path:
|
2167
|
+
if not self.config.crl_enabled:
|
2168
|
+
raise CertificateConfigurationError("CRL is not enabled in configuration")
|
2169
|
+
crl_path = self.config.crl_path
|
2170
|
+
|
2171
|
+
if not crl_path:
|
2172
|
+
raise CertificateConfigurationError("CRL path is required")
|
2173
|
+
|
2174
|
+
# Check if CRL is valid
|
2175
|
+
return is_crl_valid(crl_path)
|
2176
|
+
|
2177
|
+
except Exception as e:
|
2178
|
+
self.logger.error(
|
2179
|
+
"CRL validation failed",
|
2180
|
+
extra={
|
2181
|
+
"crl_path": crl_path,
|
2182
|
+
"error": str(e),
|
2183
|
+
},
|
2184
|
+
)
|
2185
|
+
raise CertificateValidationError(f"CRL validation failed: {str(e)}")
|
2186
|
+
|
1932
2187
|
|
1933
2188
|
class CertificateConfigurationError(Exception):
|
1934
2189
|
"""Raised when certificate configuration is invalid."""
|