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.
- 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 +49 -20
- mcp_security_framework/core/cert_manager.py +398 -104
- mcp_security_framework/core/permission_manager.py +13 -9
- mcp_security_framework/core/rate_limiter.py +10 -0
- mcp_security_framework/core/security_manager.py +286 -229
- mcp_security_framework/examples/__init__.py +6 -0
- mcp_security_framework/examples/comprehensive_example.py +954 -0
- mcp_security_framework/examples/django_example.py +276 -202
- mcp_security_framework/examples/fastapi_example.py +897 -393
- mcp_security_framework/examples/flask_example.py +311 -200
- mcp_security_framework/examples/gateway_example.py +373 -214
- mcp_security_framework/examples/microservice_example.py +337 -172
- mcp_security_framework/examples/standalone_example.py +719 -478
- mcp_security_framework/examples/test_all_examples.py +572 -0
- 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 +179 -110
- mcp_security_framework/middleware/fastapi_middleware.py +156 -148
- mcp_security_framework/middleware/flask_auth_middleware.py +267 -107
- 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 +19 -6
- mcp_security_framework/utils/cert_utils.py +14 -8
- mcp_security_framework/utils/datetime_compat.py +116 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/METADATA +2 -1
- mcp_security_framework-1.1.1.dist-info/RECORD +84 -0
- tests/conftest.py +303 -0
- tests/test_cli/test_cert_cli.py +194 -174
- tests/test_cli/test_security_cli.py +274 -247
- tests/test_core/test_cert_manager.py +33 -19
- tests/test_core/test_security_manager.py +2 -2
- tests/test_examples/test_comprehensive_example.py +613 -0
- tests/test_examples/test_fastapi_example.py +290 -169
- tests/test_examples/test_flask_example.py +304 -162
- tests/test_examples/test_standalone_example.py +106 -168
- tests/test_integration/test_auth_flow.py +214 -198
- tests/test_integration/test_certificate_flow.py +181 -150
- tests/test_integration/test_fastapi_integration.py +140 -149
- tests/test_integration/test_flask_integration.py +144 -141
- tests/test_integration/test_standalone_integration.py +331 -300
- tests/test_middleware/test_fastapi_auth_middleware.py +745 -0
- tests/test_middleware/test_fastapi_middleware.py +147 -132
- tests/test_middleware/test_flask_auth_middleware.py +696 -0
- tests/test_middleware/test_flask_middleware.py +201 -179
- tests/test_middleware/test_security_middleware.py +151 -130
- tests/test_utils/test_datetime_compat.py +147 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +0 -76
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/WHEEL +0 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/entry_points.txt +0 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/top_level.txt +0 -0
@@ -43,8 +43,8 @@ from mcp_security_framework.schemas.config import (
|
|
43
43
|
CAConfig,
|
44
44
|
CertificateConfig,
|
45
45
|
ClientCertConfig,
|
46
|
-
ServerCertConfig,
|
47
46
|
IntermediateCAConfig,
|
47
|
+
ServerCertConfig,
|
48
48
|
)
|
49
49
|
from mcp_security_framework.schemas.models import (
|
50
50
|
CertificateInfo,
|
@@ -60,6 +60,10 @@ from mcp_security_framework.utils.cert_utils import (
|
|
60
60
|
parse_certificate,
|
61
61
|
validate_certificate_chain,
|
62
62
|
)
|
63
|
+
from mcp_security_framework.utils.datetime_compat import (
|
64
|
+
get_not_valid_after_utc,
|
65
|
+
get_not_valid_before_utc,
|
66
|
+
)
|
63
67
|
|
64
68
|
|
65
69
|
class CertificateManager:
|
@@ -313,8 +317,8 @@ class CertificateManager:
|
|
313
317
|
serial_number=str(certificate.serial_number),
|
314
318
|
common_name=ca_config.common_name,
|
315
319
|
organization=ca_config.organization,
|
316
|
-
not_before=certificate
|
317
|
-
not_after=certificate
|
320
|
+
not_before=get_not_valid_before_utc(certificate),
|
321
|
+
not_after=get_not_valid_after_utc(certificate),
|
318
322
|
certificate_type=CertificateType.ROOT_CA,
|
319
323
|
key_size=ca_config.key_size,
|
320
324
|
)
|
@@ -340,7 +344,9 @@ class CertificateManager:
|
|
340
344
|
f"Failed to create root CA certificate: {str(e)}"
|
341
345
|
)
|
342
346
|
|
343
|
-
def create_intermediate_ca(
|
347
|
+
def create_intermediate_ca(
|
348
|
+
self, intermediate_config: IntermediateCAConfig
|
349
|
+
) -> CertificatePair:
|
344
350
|
"""
|
345
351
|
Create intermediate CA certificate signed by parent CA.
|
346
352
|
|
@@ -400,32 +406,47 @@ class CertificateManager:
|
|
400
406
|
try:
|
401
407
|
# Validate intermediate configuration
|
402
408
|
if not intermediate_config.common_name:
|
403
|
-
raise ValueError(
|
409
|
+
raise ValueError(
|
410
|
+
"Common name is required for intermediate CA certificate"
|
411
|
+
)
|
404
412
|
|
405
|
-
if
|
413
|
+
if (
|
414
|
+
not intermediate_config.parent_ca_cert
|
415
|
+
or not intermediate_config.parent_ca_key
|
416
|
+
):
|
406
417
|
raise ValueError("Parent CA certificate and key paths are required")
|
407
418
|
|
408
419
|
# Load parent CA certificate and private key
|
409
420
|
if not os.path.exists(intermediate_config.parent_ca_cert):
|
410
|
-
raise FileNotFoundError(
|
421
|
+
raise FileNotFoundError(
|
422
|
+
f"Parent CA certificate not found: {intermediate_config.parent_ca_cert}"
|
423
|
+
)
|
411
424
|
|
412
425
|
if not os.path.exists(intermediate_config.parent_ca_key):
|
413
|
-
raise FileNotFoundError(
|
426
|
+
raise FileNotFoundError(
|
427
|
+
f"Parent CA private key not found: {intermediate_config.parent_ca_key}"
|
428
|
+
)
|
414
429
|
|
415
430
|
with open(intermediate_config.parent_ca_cert, "rb") as f:
|
416
431
|
parent_ca_cert = x509.load_pem_x509_certificate(f.read())
|
417
432
|
|
418
433
|
with open(intermediate_config.parent_ca_key, "rb") as f:
|
419
|
-
parent_ca_key = serialization.load_pem_private_key(
|
434
|
+
parent_ca_key = serialization.load_pem_private_key(
|
435
|
+
f.read(), password=None
|
436
|
+
)
|
420
437
|
|
421
438
|
# Generate intermediate CA private key
|
422
439
|
private_key = rsa.generate_private_key(
|
423
|
-
public_exponent=65537,
|
440
|
+
public_exponent=65537,
|
441
|
+
key_size=intermediate_config.key_size,
|
442
|
+
backend=None,
|
424
443
|
)
|
425
444
|
|
426
445
|
# Create certificate subject
|
427
446
|
subject_attributes = [
|
428
|
-
x509.NameAttribute(
|
447
|
+
x509.NameAttribute(
|
448
|
+
NameOID.COMMON_NAME, intermediate_config.common_name
|
449
|
+
),
|
429
450
|
x509.NameAttribute(
|
430
451
|
NameOID.ORGANIZATION_NAME, intermediate_config.organization
|
431
452
|
),
|
@@ -434,12 +455,16 @@ class CertificateManager:
|
|
434
455
|
|
435
456
|
if intermediate_config.state:
|
436
457
|
subject_attributes.append(
|
437
|
-
x509.NameAttribute(
|
458
|
+
x509.NameAttribute(
|
459
|
+
NameOID.STATE_OR_PROVINCE_NAME, intermediate_config.state
|
460
|
+
)
|
438
461
|
)
|
439
462
|
|
440
463
|
if intermediate_config.locality:
|
441
464
|
subject_attributes.append(
|
442
|
-
x509.NameAttribute(
|
465
|
+
x509.NameAttribute(
|
466
|
+
NameOID.LOCALITY_NAME, intermediate_config.locality
|
467
|
+
)
|
443
468
|
)
|
444
469
|
|
445
470
|
if intermediate_config.email:
|
@@ -457,7 +482,8 @@ class CertificateManager:
|
|
457
482
|
builder = builder.serial_number(x509.random_serial_number())
|
458
483
|
builder = builder.not_valid_before(datetime.now(timezone.utc))
|
459
484
|
builder = builder.not_valid_after(
|
460
|
-
datetime.now(timezone.utc)
|
485
|
+
datetime.now(timezone.utc)
|
486
|
+
+ timedelta(days=intermediate_config.validity_years * 365)
|
461
487
|
)
|
462
488
|
|
463
489
|
# Add CA extensions
|
@@ -487,7 +513,9 @@ class CertificateManager:
|
|
487
513
|
|
488
514
|
# Add Authority Key Identifier
|
489
515
|
builder = builder.add_extension(
|
490
|
-
x509.AuthorityKeyIdentifier.from_issuer_public_key(
|
516
|
+
x509.AuthorityKeyIdentifier.from_issuer_public_key(
|
517
|
+
parent_ca_key.public_key()
|
518
|
+
),
|
491
519
|
critical=False,
|
492
520
|
)
|
493
521
|
|
@@ -527,15 +555,17 @@ class CertificateManager:
|
|
527
555
|
cert_pair = CertificatePair(
|
528
556
|
certificate_path=cert_path,
|
529
557
|
private_key_path=key_path,
|
530
|
-
certificate_pem=certificate.public_bytes(
|
558
|
+
certificate_pem=certificate.public_bytes(
|
559
|
+
serialization.Encoding.PEM
|
560
|
+
).decode(),
|
531
561
|
private_key_pem=private_key.private_bytes(
|
532
562
|
encoding=serialization.Encoding.PEM,
|
533
563
|
format=serialization.PrivateFormat.PKCS8,
|
534
564
|
encryption_algorithm=serialization.NoEncryption(),
|
535
565
|
).decode(),
|
536
566
|
serial_number=str(certificate.serial_number),
|
537
|
-
not_before=certificate
|
538
|
-
not_after=certificate
|
567
|
+
not_before=get_not_valid_before_utc(certificate),
|
568
|
+
not_after=get_not_valid_after_utc(certificate),
|
539
569
|
common_name=intermediate_config.common_name,
|
540
570
|
organization=intermediate_config.organization,
|
541
571
|
certificate_type=CertificateType.INTERMEDIATE_CA,
|
@@ -558,7 +588,10 @@ class CertificateManager:
|
|
558
588
|
except Exception as e:
|
559
589
|
self.logger.error(
|
560
590
|
"Failed to create intermediate CA certificate",
|
561
|
-
extra={
|
591
|
+
extra={
|
592
|
+
"intermediate_config": intermediate_config.model_dump(),
|
593
|
+
"error": str(e),
|
594
|
+
},
|
562
595
|
)
|
563
596
|
raise CertificateGenerationError(
|
564
597
|
f"Failed to create intermediate CA certificate: {str(e)}"
|
@@ -775,8 +808,8 @@ class CertificateManager:
|
|
775
808
|
serial_number=str(certificate.serial_number),
|
776
809
|
common_name=client_config.common_name,
|
777
810
|
organization=client_config.organization,
|
778
|
-
not_before=certificate
|
779
|
-
not_after=certificate
|
811
|
+
not_before=get_not_valid_before_utc(certificate),
|
812
|
+
not_after=get_not_valid_after_utc(certificate),
|
780
813
|
certificate_type=CertificateType.CLIENT,
|
781
814
|
key_size=client_config.key_size,
|
782
815
|
)
|
@@ -1009,8 +1042,8 @@ class CertificateManager:
|
|
1009
1042
|
serial_number=str(certificate.serial_number),
|
1010
1043
|
common_name=server_config.common_name,
|
1011
1044
|
organization=server_config.organization,
|
1012
|
-
not_before=certificate
|
1013
|
-
not_after=certificate
|
1045
|
+
not_before=get_not_valid_before_utc(certificate),
|
1046
|
+
not_after=get_not_valid_after_utc(certificate),
|
1014
1047
|
certificate_type=CertificateType.SERVER,
|
1015
1048
|
key_size=server_config.key_size,
|
1016
1049
|
)
|
@@ -1038,52 +1071,54 @@ class CertificateManager:
|
|
1038
1071
|
)
|
1039
1072
|
|
1040
1073
|
def renew_certificate(
|
1041
|
-
self,
|
1042
|
-
cert_path: str,
|
1074
|
+
self,
|
1075
|
+
cert_path: str,
|
1043
1076
|
ca_cert_path: Optional[str] = None,
|
1044
1077
|
ca_key_path: Optional[str] = None,
|
1045
|
-
validity_years: int = 1
|
1078
|
+
validity_years: int = 1,
|
1046
1079
|
) -> CertificatePair:
|
1047
1080
|
"""
|
1048
1081
|
Renew an existing certificate with new validity period.
|
1049
|
-
|
1082
|
+
|
1050
1083
|
This method renews a certificate by creating a new certificate
|
1051
1084
|
with the same subject and key but extended validity period.
|
1052
|
-
|
1085
|
+
|
1053
1086
|
Args:
|
1054
1087
|
cert_path (str): Path to existing certificate to renew
|
1055
1088
|
ca_cert_path (Optional[str]): Path to CA certificate for signing
|
1056
1089
|
ca_key_path (Optional[str]): Path to CA private key for signing
|
1057
1090
|
validity_years (int): New validity period in years
|
1058
|
-
|
1091
|
+
|
1059
1092
|
Returns:
|
1060
1093
|
CertificatePair: New certificate pair with extended validity
|
1061
|
-
|
1094
|
+
|
1062
1095
|
Raises:
|
1063
1096
|
CertificateValidationError: When certificate validation fails
|
1064
1097
|
CertificateGenerationError: When renewal fails
|
1065
1098
|
"""
|
1066
1099
|
try:
|
1067
1100
|
# Load existing certificate
|
1068
|
-
with open(cert_path,
|
1101
|
+
with open(cert_path, "rb") as f:
|
1069
1102
|
cert_data = f.read()
|
1070
|
-
|
1103
|
+
|
1071
1104
|
cert = x509.load_pem_x509_certificate(cert_data)
|
1072
|
-
|
1105
|
+
|
1073
1106
|
# Use provided CA paths or default from config
|
1074
1107
|
ca_cert_file = ca_cert_path or self.config.ca_cert_path
|
1075
1108
|
ca_key_file = ca_key_path or self.config.ca_key_path
|
1076
|
-
|
1109
|
+
|
1077
1110
|
if not ca_cert_file or not ca_key_file:
|
1078
|
-
raise CertificateConfigurationError(
|
1079
|
-
|
1111
|
+
raise CertificateConfigurationError(
|
1112
|
+
"CA certificate and key paths are required"
|
1113
|
+
)
|
1114
|
+
|
1080
1115
|
# Load CA certificate and key
|
1081
|
-
with open(ca_cert_file,
|
1116
|
+
with open(ca_cert_file, "rb") as f:
|
1082
1117
|
ca_cert = x509.load_pem_x509_certificate(f.read())
|
1083
|
-
|
1084
|
-
with open(ca_key_file,
|
1118
|
+
|
1119
|
+
with open(ca_key_file, "rb") as f:
|
1085
1120
|
ca_key = serialization.load_pem_private_key(f.read(), password=None)
|
1086
|
-
|
1121
|
+
|
1087
1122
|
# Create new certificate with extended validity
|
1088
1123
|
builder = x509.CertificateBuilder()
|
1089
1124
|
builder = builder.subject_name(cert.subject)
|
@@ -1094,34 +1129,37 @@ class CertificateManager:
|
|
1094
1129
|
builder = builder.not_valid_after(
|
1095
1130
|
datetime.now(timezone.utc) + timedelta(days=365 * validity_years)
|
1096
1131
|
)
|
1097
|
-
|
1132
|
+
|
1098
1133
|
# Copy extensions from original certificate
|
1099
1134
|
for extension in cert.extensions:
|
1100
1135
|
if extension.oid not in [x509.ExtensionOID.AUTHORITY_KEY_IDENTIFIER]:
|
1101
|
-
builder = builder.add_extension(
|
1102
|
-
|
1136
|
+
builder = builder.add_extension(
|
1137
|
+
extension.value, critical=extension.critical
|
1138
|
+
)
|
1139
|
+
|
1103
1140
|
# Sign the certificate
|
1104
1141
|
new_cert = builder.sign(ca_key, hashes.SHA256())
|
1105
|
-
|
1142
|
+
|
1106
1143
|
# Generate new file paths
|
1107
1144
|
cert_dir = os.path.dirname(cert_path)
|
1108
1145
|
cert_name = os.path.splitext(os.path.basename(cert_path))[0]
|
1109
1146
|
new_cert_path = os.path.join(cert_dir, f"{cert_name}_renewed.crt")
|
1110
1147
|
new_key_path = os.path.join(cert_dir, f"{cert_name}_renewed.key")
|
1111
|
-
|
1148
|
+
|
1112
1149
|
# Save new certificate
|
1113
|
-
with open(new_cert_path,
|
1150
|
+
with open(new_cert_path, "wb") as f:
|
1114
1151
|
f.write(new_cert.public_bytes(serialization.Encoding.PEM))
|
1115
|
-
|
1152
|
+
|
1116
1153
|
# For renewal, we typically keep the same private key
|
1117
1154
|
# Copy the original private key if it exists
|
1118
|
-
key_path = cert_path.replace(
|
1155
|
+
key_path = cert_path.replace(".crt", ".key").replace(".pem", ".key")
|
1119
1156
|
private_key_pem = ""
|
1120
1157
|
if os.path.exists(key_path):
|
1121
1158
|
import shutil
|
1159
|
+
|
1122
1160
|
shutil.copy2(key_path, new_key_path)
|
1123
1161
|
# Read the private key content
|
1124
|
-
with open(key_path,
|
1162
|
+
with open(key_path, "r") as f:
|
1125
1163
|
private_key_pem = f.read()
|
1126
1164
|
else:
|
1127
1165
|
# Create a placeholder key file
|
@@ -1131,46 +1169,51 @@ gVdaZKJR+ym6h3Za4ryK42qlz8Rb5lQICyFJi+h5xqkk71B2ELzu2nzmoafs9OTJ
|
|
1131
1169
|
LjL4Cwf+OLlI1eRybbU8eqBk8i+B6ALB2FuGjZJplP99fejLMM0L5XNwxJt3OwCx
|
1132
1170
|
WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
1133
1171
|
-----END PRIVATE KEY-----"""
|
1134
|
-
with open(new_key_path,
|
1172
|
+
with open(new_key_path, "w") as f:
|
1135
1173
|
f.write(placeholder_key)
|
1136
1174
|
private_key_pem = placeholder_key
|
1137
|
-
|
1175
|
+
|
1138
1176
|
# Create certificate pair
|
1139
1177
|
cert_pair = CertificatePair(
|
1140
1178
|
certificate_path=new_cert_path,
|
1141
1179
|
private_key_path=new_key_path,
|
1142
|
-
certificate_pem=new_cert.public_bytes(
|
1180
|
+
certificate_pem=new_cert.public_bytes(
|
1181
|
+
serialization.Encoding.PEM
|
1182
|
+
).decode(),
|
1143
1183
|
private_key_pem=private_key_pem,
|
1144
1184
|
serial_number=str(new_cert.serial_number),
|
1145
1185
|
common_name="", # Will be extracted from subject
|
1146
1186
|
organization="", # Will be extracted from subject
|
1147
|
-
not_before=new_cert
|
1148
|
-
not_after=new_cert
|
1187
|
+
not_before=get_not_valid_before_utc(new_cert),
|
1188
|
+
not_after=get_not_valid_after_utc(new_cert),
|
1149
1189
|
certificate_type=CertificateType.CLIENT, # Default
|
1150
1190
|
key_size=0, # Will be extracted
|
1151
1191
|
)
|
1152
|
-
|
1192
|
+
|
1153
1193
|
self.logger.info(
|
1154
1194
|
"Certificate renewed successfully",
|
1155
1195
|
extra={
|
1156
1196
|
"original_cert": cert_path,
|
1157
1197
|
"new_cert": new_cert_path,
|
1158
|
-
"validity_years": validity_years
|
1159
|
-
}
|
1198
|
+
"validity_years": validity_years,
|
1199
|
+
},
|
1160
1200
|
)
|
1161
|
-
|
1201
|
+
|
1162
1202
|
return cert_pair
|
1163
|
-
|
1203
|
+
|
1164
1204
|
except Exception as e:
|
1165
1205
|
self.logger.error(
|
1166
1206
|
"Failed to renew certificate",
|
1167
|
-
extra={"cert_path": cert_path, "error": str(e)}
|
1207
|
+
extra={"cert_path": cert_path, "error": str(e)},
|
1168
1208
|
)
|
1169
1209
|
raise CertificateGenerationError(f"Failed to renew certificate: {str(e)}")
|
1170
1210
|
|
1171
1211
|
def revoke_certificate(
|
1172
|
-
self,
|
1173
|
-
|
1212
|
+
self,
|
1213
|
+
serial_number: str,
|
1214
|
+
reason: str = "unspecified",
|
1215
|
+
ca_cert_path: Optional[str] = None,
|
1216
|
+
ca_key_path: Optional[str] = None,
|
1174
1217
|
) -> bool:
|
1175
1218
|
"""
|
1176
1219
|
Revoke certificate by serial number.
|
@@ -1211,7 +1254,7 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1211
1254
|
# Use provided CA paths or default from config
|
1212
1255
|
ca_cert_file = ca_cert_path or self.config.ca_cert_path
|
1213
1256
|
ca_key_file = ca_key_path or self.config.ca_key_path
|
1214
|
-
|
1257
|
+
|
1215
1258
|
if not ca_cert_file or not ca_key_file:
|
1216
1259
|
raise CertificateConfigurationError(
|
1217
1260
|
"CA certificate and key paths are required"
|
@@ -1235,7 +1278,7 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1235
1278
|
revoked_cert = x509.RevokedCertificateBuilder()
|
1236
1279
|
revoked_cert = revoked_cert.serial_number(int(serial_number))
|
1237
1280
|
revoked_cert = revoked_cert.revocation_date(datetime.now(timezone.utc))
|
1238
|
-
|
1281
|
+
|
1239
1282
|
# Build the revoked certificate
|
1240
1283
|
revoked_cert_built = revoked_cert.build()
|
1241
1284
|
|
@@ -1247,7 +1290,7 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1247
1290
|
# Save CRL
|
1248
1291
|
crl_filename = "ca_crl.pem"
|
1249
1292
|
crl_path = os.path.join(self.config.cert_storage_path, crl_filename)
|
1250
|
-
|
1293
|
+
|
1251
1294
|
# Ensure directory exists
|
1252
1295
|
os.makedirs(os.path.dirname(crl_path), exist_ok=True)
|
1253
1296
|
|
@@ -1378,25 +1421,27 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1378
1421
|
|
1379
1422
|
# Create certificate info
|
1380
1423
|
subject_dict = {}
|
1381
|
-
|
1424
|
+
|
1382
1425
|
# Add Common Name
|
1383
1426
|
if cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME):
|
1384
1427
|
subject_dict["CN"] = str(
|
1385
1428
|
cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
|
1386
1429
|
)
|
1387
|
-
|
1430
|
+
|
1388
1431
|
# Add Organization
|
1389
1432
|
if cert.subject.get_attributes_for_oid(NameOID.ORGANIZATION_NAME):
|
1390
1433
|
subject_dict["O"] = str(
|
1391
|
-
cert.subject.get_attributes_for_oid(NameOID.ORGANIZATION_NAME)[
|
1434
|
+
cert.subject.get_attributes_for_oid(NameOID.ORGANIZATION_NAME)[
|
1435
|
+
0
|
1436
|
+
].value
|
1392
1437
|
)
|
1393
|
-
|
1438
|
+
|
1394
1439
|
# Add Country
|
1395
1440
|
if cert.subject.get_attributes_for_oid(NameOID.COUNTRY_NAME):
|
1396
1441
|
subject_dict["C"] = str(
|
1397
1442
|
cert.subject.get_attributes_for_oid(NameOID.COUNTRY_NAME)[0].value
|
1398
1443
|
)
|
1399
|
-
|
1444
|
+
|
1400
1445
|
cert_info = CertificateInfo(
|
1401
1446
|
subject=subject_dict,
|
1402
1447
|
issuer={
|
@@ -1411,8 +1456,8 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1411
1456
|
)
|
1412
1457
|
},
|
1413
1458
|
serial_number=serial_number,
|
1414
|
-
not_before=cert
|
1415
|
-
not_after=cert
|
1459
|
+
not_before=get_not_valid_before_utc(cert),
|
1460
|
+
not_after=get_not_valid_after_utc(cert),
|
1416
1461
|
certificate_type=CertificateType.CLIENT, # Default to client, could be enhanced
|
1417
1462
|
key_size=expiry_info.get("key_size", 2048), # Default to 2048 bits
|
1418
1463
|
signature_algorithm=cert.signature_algorithm_oid._name,
|
@@ -1446,18 +1491,205 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1446
1491
|
f"Failed to get certificate info: {str(e)}"
|
1447
1492
|
)
|
1448
1493
|
|
1449
|
-
def
|
1450
|
-
|
1494
|
+
def create_certificate_signing_request(
|
1495
|
+
self,
|
1496
|
+
common_name: str,
|
1497
|
+
organization: str,
|
1498
|
+
country: str,
|
1499
|
+
state: Optional[str] = None,
|
1500
|
+
locality: Optional[str] = None,
|
1501
|
+
organizational_unit: Optional[str] = None,
|
1502
|
+
email: Optional[str] = None,
|
1503
|
+
key_size: int = 2048,
|
1504
|
+
key_type: str = "rsa",
|
1505
|
+
output_path: Optional[str] = None,
|
1506
|
+
) -> Tuple[str, str]:
|
1507
|
+
"""
|
1508
|
+
Create a Certificate Signing Request (CSR).
|
1509
|
+
|
1510
|
+
This method creates a Certificate Signing Request (CSR) that can be
|
1511
|
+
submitted to a Certificate Authority (CA) for signing.
|
1512
|
+
|
1513
|
+
Args:
|
1514
|
+
common_name (str): Common name for the certificate (e.g., domain name)
|
1515
|
+
organization (str): Organization name
|
1516
|
+
country (str): Country code (e.g., "US", "GB")
|
1517
|
+
state (Optional[str]): State or province name
|
1518
|
+
locality (Optional[str]): Locality or city name
|
1519
|
+
organizational_unit (Optional[str]): Organizational unit name
|
1520
|
+
email (Optional[str]): Email address
|
1521
|
+
key_size (int): Key size in bits. Defaults to 2048
|
1522
|
+
key_type (str): Key type ("rsa" or "ec"). Defaults to "rsa"
|
1523
|
+
output_path (Optional[str]): Output directory for CSR and key files.
|
1524
|
+
If None, uses default path from configuration
|
1525
|
+
|
1526
|
+
Returns:
|
1527
|
+
Tuple[str, str]: Paths to the created CSR file and private key file
|
1528
|
+
|
1529
|
+
Raises:
|
1530
|
+
ValueError: When required parameters are invalid
|
1531
|
+
CertificateGenerationError: When CSR creation fails
|
1532
|
+
FileNotFoundError: When output directory is not accessible
|
1533
|
+
PermissionError: When output directory is not writable
|
1534
|
+
|
1535
|
+
Example:
|
1536
|
+
>>> cert_manager = CertificateManager(config)
|
1537
|
+
>>> csr_path, key_path = cert_manager.create_certificate_signing_request(
|
1538
|
+
... common_name="api.example.com",
|
1539
|
+
... organization="Example Corp",
|
1540
|
+
... country="US",
|
1541
|
+
... state="California"
|
1542
|
+
... )
|
1543
|
+
>>> print(f"CSR created: {csr_path}")
|
1544
|
+
>>> print(f"Private key created: {key_path}")
|
1545
|
+
"""
|
1546
|
+
try:
|
1547
|
+
# Validate required parameters
|
1548
|
+
if not common_name:
|
1549
|
+
raise ValueError("Common name is required")
|
1550
|
+
if not organization:
|
1551
|
+
raise ValueError("Organization is required")
|
1552
|
+
if not country:
|
1553
|
+
raise ValueError("Country is required")
|
1554
|
+
|
1555
|
+
# Generate private key
|
1556
|
+
if key_type.lower() == "rsa":
|
1557
|
+
private_key = rsa.generate_private_key(
|
1558
|
+
public_exponent=65537, key_size=key_size, backend=None
|
1559
|
+
)
|
1560
|
+
elif key_type.lower() == "ec":
|
1561
|
+
private_key = ec.generate_private_key(ec.SECP256R1(), backend=None)
|
1562
|
+
else:
|
1563
|
+
raise ValueError(f"Unsupported key type: {key_type}")
|
1564
|
+
|
1565
|
+
# Create CSR subject
|
1566
|
+
subject_attributes = [
|
1567
|
+
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
|
1568
|
+
x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization),
|
1569
|
+
x509.NameAttribute(NameOID.COUNTRY_NAME, country),
|
1570
|
+
]
|
1571
|
+
|
1572
|
+
# Add optional attributes
|
1573
|
+
if state:
|
1574
|
+
subject_attributes.append(
|
1575
|
+
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, state)
|
1576
|
+
)
|
1577
|
+
if locality:
|
1578
|
+
subject_attributes.append(
|
1579
|
+
x509.NameAttribute(NameOID.LOCALITY_NAME, locality)
|
1580
|
+
)
|
1581
|
+
if organizational_unit:
|
1582
|
+
subject_attributes.append(
|
1583
|
+
x509.NameAttribute(
|
1584
|
+
NameOID.ORGANIZATIONAL_UNIT_NAME, organizational_unit
|
1585
|
+
)
|
1586
|
+
)
|
1587
|
+
if email:
|
1588
|
+
subject_attributes.append(
|
1589
|
+
x509.NameAttribute(NameOID.EMAIL_ADDRESS, email)
|
1590
|
+
)
|
1591
|
+
|
1592
|
+
subject = x509.Name(subject_attributes)
|
1593
|
+
|
1594
|
+
# Create CSR builder
|
1595
|
+
csr_builder = x509.CertificateSigningRequestBuilder()
|
1596
|
+
csr_builder = csr_builder.subject_name(subject)
|
1597
|
+
csr_builder = csr_builder.add_extension(
|
1598
|
+
x509.BasicConstraints(ca=False, path_length=None), critical=True
|
1599
|
+
)
|
1600
|
+
|
1601
|
+
# Add Subject Alternative Name extension if common name looks like a domain
|
1602
|
+
if "." in common_name and not common_name.startswith("*"):
|
1603
|
+
san_extension = x509.SubjectAlternativeName([x509.DNSName(common_name)])
|
1604
|
+
csr_builder = csr_builder.add_extension(san_extension, critical=False)
|
1605
|
+
|
1606
|
+
# Build and sign CSR
|
1607
|
+
csr = csr_builder.sign(private_key, hashes.SHA256())
|
1608
|
+
|
1609
|
+
# Determine output paths
|
1610
|
+
if output_path is None:
|
1611
|
+
output_dir = Path(self.config.cert_storage_path) / "csr"
|
1612
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
1613
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
1614
|
+
csr_filename = f"{common_name.replace('.', '_')}_{timestamp}.csr"
|
1615
|
+
key_filename = f"{common_name.replace('.', '_')}_{timestamp}.key"
|
1616
|
+
csr_path = str(output_dir / csr_filename)
|
1617
|
+
key_path = str(output_dir / key_filename)
|
1618
|
+
else:
|
1619
|
+
output_dir = Path(output_path)
|
1620
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
1621
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
1622
|
+
csr_filename = f"{common_name.replace('.', '_')}_{timestamp}.csr"
|
1623
|
+
key_filename = f"{common_name.replace('.', '_')}_{timestamp}.key"
|
1624
|
+
csr_path = str(output_dir / csr_filename)
|
1625
|
+
key_path = str(output_dir / key_filename)
|
1626
|
+
|
1627
|
+
# Write CSR to file
|
1628
|
+
with open(csr_path, "wb") as f:
|
1629
|
+
f.write(csr.public_bytes(serialization.Encoding.PEM))
|
1630
|
+
|
1631
|
+
# Write private key to file
|
1632
|
+
with open(key_path, "wb") as f:
|
1633
|
+
f.write(
|
1634
|
+
private_key.private_bytes(
|
1635
|
+
encoding=serialization.Encoding.PEM,
|
1636
|
+
format=serialization.PrivateFormat.PKCS8,
|
1637
|
+
encryption_algorithm=serialization.NoEncryption(),
|
1638
|
+
)
|
1639
|
+
)
|
1640
|
+
|
1641
|
+
# Set proper permissions
|
1642
|
+
os.chmod(key_path, 0o600) # Read/write for owner only
|
1643
|
+
os.chmod(csr_path, 0o644) # Read for all, write for owner
|
1644
|
+
|
1645
|
+
self.logger.info(
|
1646
|
+
"CSR created successfully",
|
1647
|
+
extra={
|
1648
|
+
"csr_path": csr_path,
|
1649
|
+
"key_path": key_path,
|
1650
|
+
"common_name": common_name,
|
1651
|
+
"organization": organization,
|
1652
|
+
"key_type": key_type,
|
1653
|
+
"key_size": key_size,
|
1654
|
+
},
|
1655
|
+
)
|
1656
|
+
|
1657
|
+
return csr_path, key_path
|
1658
|
+
|
1659
|
+
except Exception as e:
|
1660
|
+
self.logger.error(
|
1661
|
+
"Failed to create CSR",
|
1662
|
+
extra={
|
1663
|
+
"common_name": common_name,
|
1664
|
+
"organization": organization,
|
1665
|
+
"error": str(e),
|
1666
|
+
},
|
1667
|
+
)
|
1668
|
+
raise CertificateGenerationError(f"Failed to create CSR: {str(e)}")
|
1669
|
+
|
1670
|
+
def create_crl(
|
1671
|
+
self,
|
1672
|
+
ca_cert_path: str,
|
1673
|
+
ca_key_path: str,
|
1674
|
+
revoked_serials: Optional[List[Dict[str, Union[str, int]]]] = None,
|
1675
|
+
output_path: Optional[str] = None,
|
1676
|
+
validity_days: int = 30,
|
1677
|
+
) -> str:
|
1451
1678
|
"""
|
1452
1679
|
Create a Certificate Revocation List (CRL).
|
1453
1680
|
|
1454
1681
|
This method creates a Certificate Revocation List (CRL) from the CA
|
1455
1682
|
certificate and private key. The CRL contains information about
|
1456
|
-
revoked certificates.
|
1683
|
+
revoked certificates with revocation reasons.
|
1457
1684
|
|
1458
1685
|
Args:
|
1459
1686
|
ca_cert_path (str): Path to CA certificate file
|
1460
1687
|
ca_key_path (str): Path to CA private key file
|
1688
|
+
revoked_serials (Optional[List[Dict]]): List of revoked certificate serials.
|
1689
|
+
Each dict should contain:
|
1690
|
+
- "serial": Serial number as string or int
|
1691
|
+
- "reason": Revocation reason (optional)
|
1692
|
+
- "revocation_date": Revocation date (optional, defaults to now)
|
1461
1693
|
output_path (Optional[str]): Output path for CRL file.
|
1462
1694
|
If None, uses default path from configuration
|
1463
1695
|
validity_days (int): CRL validity period in days. Defaults to 30
|
@@ -1471,9 +1703,14 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1471
1703
|
|
1472
1704
|
Example:
|
1473
1705
|
>>> cert_manager = CertificateManager(config)
|
1706
|
+
>>> revoked_serials = [
|
1707
|
+
... {"serial": "123456789", "reason": "key_compromise"},
|
1708
|
+
... {"serial": "987654321", "reason": "certificate_hold"}
|
1709
|
+
... ]
|
1474
1710
|
>>> crl_path = cert_manager.create_crl(
|
1475
1711
|
... ca_cert_path="/path/to/ca.crt",
|
1476
1712
|
... ca_key_path="/path/to/ca.key",
|
1713
|
+
... revoked_serials=revoked_serials,
|
1477
1714
|
... validity_days=30
|
1478
1715
|
... )
|
1479
1716
|
>>> print(f"CRL created: {crl_path}")
|
@@ -1482,17 +1719,17 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1482
1719
|
# Validate input files
|
1483
1720
|
if not os.path.exists(ca_cert_path):
|
1484
1721
|
raise FileNotFoundError(f"CA certificate not found: {ca_cert_path}")
|
1485
|
-
|
1722
|
+
|
1486
1723
|
if not os.path.exists(ca_key_path):
|
1487
1724
|
raise FileNotFoundError(f"CA private key not found: {ca_key_path}")
|
1488
1725
|
|
1489
1726
|
# Load CA certificate
|
1490
|
-
with open(ca_cert_path,
|
1727
|
+
with open(ca_cert_path, "rb") as f:
|
1491
1728
|
ca_cert_data = f.read()
|
1492
1729
|
ca_cert = x509.load_pem_x509_certificate(ca_cert_data)
|
1493
1730
|
|
1494
1731
|
# Load CA private key
|
1495
|
-
with open(ca_key_path,
|
1732
|
+
with open(ca_key_path, "rb") as f:
|
1496
1733
|
ca_key_data = f.read()
|
1497
1734
|
ca_private_key = serialization.load_pem_private_key(
|
1498
1735
|
ca_key_data, password=None
|
@@ -1508,10 +1745,33 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1508
1745
|
crl_builder = crl_builder.next_update(next_update)
|
1509
1746
|
crl_builder = crl_builder.issuer_name(ca_cert.subject)
|
1510
1747
|
|
1511
|
-
# Add revoked certificates
|
1512
|
-
# This is a basic implementation - in a real scenario, you would
|
1513
|
-
# load revoked certificates from a database or file
|
1748
|
+
# Add revoked certificates
|
1514
1749
|
revoked_certificates = []
|
1750
|
+
if revoked_serials:
|
1751
|
+
for revoked_info in revoked_serials:
|
1752
|
+
serial = revoked_info.get("serial")
|
1753
|
+
reason = revoked_info.get("reason", "unspecified")
|
1754
|
+
revocation_date = revoked_info.get("revocation_date", now)
|
1755
|
+
|
1756
|
+
# Convert serial to int if it's a string
|
1757
|
+
if isinstance(serial, str):
|
1758
|
+
serial = (
|
1759
|
+
int(serial, 16) if serial.startswith("0x") else int(serial)
|
1760
|
+
)
|
1761
|
+
|
1762
|
+
# Map reason string to x509 enum
|
1763
|
+
reason_enum = self._get_revocation_reason(reason)
|
1764
|
+
|
1765
|
+
# Create revoked certificate entry
|
1766
|
+
revoked_cert = (
|
1767
|
+
x509.RevokedCertificateBuilder()
|
1768
|
+
.serial_number(serial)
|
1769
|
+
.revocation_date(revocation_date)
|
1770
|
+
.add_extension(x509.CRLReason(reason_enum), critical=False)
|
1771
|
+
.build()
|
1772
|
+
)
|
1773
|
+
|
1774
|
+
revoked_certificates.append(revoked_cert)
|
1515
1775
|
|
1516
1776
|
# Build CRL
|
1517
1777
|
crl = crl_builder.sign(ca_private_key, hashes.SHA256())
|
@@ -1520,13 +1780,15 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1520
1780
|
if output_path is None:
|
1521
1781
|
output_dir = Path(self.config.cert_storage_path) / "crl"
|
1522
1782
|
output_dir.mkdir(parents=True, exist_ok=True)
|
1523
|
-
output_path = str(
|
1783
|
+
output_path = str(
|
1784
|
+
output_dir / f"crl_{now.strftime('%Y%m%d_%H%M%S')}.pem"
|
1785
|
+
)
|
1524
1786
|
else:
|
1525
1787
|
output_dir = Path(output_path).parent
|
1526
1788
|
output_dir.mkdir(parents=True, exist_ok=True)
|
1527
1789
|
|
1528
1790
|
# Write CRL to file
|
1529
|
-
with open(output_path,
|
1791
|
+
with open(output_path, "wb") as f:
|
1530
1792
|
f.write(crl.public_bytes(serialization.Encoding.PEM))
|
1531
1793
|
|
1532
1794
|
self.logger.info(
|
@@ -1534,8 +1796,8 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1534
1796
|
extra={
|
1535
1797
|
"crl_path": output_path,
|
1536
1798
|
"validity_days": validity_days,
|
1537
|
-
"revoked_count": len(revoked_certificates)
|
1538
|
-
}
|
1799
|
+
"revoked_count": len(revoked_certificates),
|
1800
|
+
},
|
1539
1801
|
)
|
1540
1802
|
|
1541
1803
|
return output_path
|
@@ -1546,69 +1808,101 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1546
1808
|
extra={
|
1547
1809
|
"ca_cert_path": ca_cert_path,
|
1548
1810
|
"ca_key_path": ca_key_path,
|
1549
|
-
"error": str(e)
|
1550
|
-
}
|
1811
|
+
"error": str(e),
|
1812
|
+
},
|
1551
1813
|
)
|
1552
1814
|
raise CertificateGenerationError(f"Failed to create CRL: {str(e)}")
|
1553
1815
|
|
1554
|
-
def
|
1816
|
+
def _get_revocation_reason(self, reason: str) -> x509.ReasonFlags:
|
1817
|
+
"""
|
1818
|
+
Map reason string to x509.ReasonFlags enum.
|
1819
|
+
|
1820
|
+
Args:
|
1821
|
+
reason (str): Reason string
|
1822
|
+
|
1823
|
+
Returns:
|
1824
|
+
x509.ReasonFlags: Corresponding reason enum
|
1825
|
+
"""
|
1826
|
+
reason_map = {
|
1827
|
+
"unspecified": x509.ReasonFlags.unspecified,
|
1828
|
+
"key_compromise": x509.ReasonFlags.key_compromise,
|
1829
|
+
"ca_compromise": x509.ReasonFlags.ca_compromise,
|
1830
|
+
"affiliation_changed": x509.ReasonFlags.affiliation_changed,
|
1831
|
+
"superseded": x509.ReasonFlags.superseded,
|
1832
|
+
"cessation_of_operation": x509.ReasonFlags.cessation_of_operation,
|
1833
|
+
"certificate_hold": x509.ReasonFlags.certificate_hold,
|
1834
|
+
"privilege_withdrawn": x509.ReasonFlags.privilege_withdrawn,
|
1835
|
+
"aa_compromise": x509.ReasonFlags.aa_compromise,
|
1836
|
+
}
|
1837
|
+
|
1838
|
+
return reason_map.get(reason.lower(), x509.ReasonFlags.unspecified)
|
1839
|
+
|
1840
|
+
def export_certificate(
|
1841
|
+
self, cert_path: str, format: str = "pem"
|
1842
|
+
) -> Union[str, bytes]:
|
1555
1843
|
"""
|
1556
1844
|
Export certificate to different formats.
|
1557
|
-
|
1845
|
+
|
1558
1846
|
Args:
|
1559
1847
|
cert_path: Path to certificate file
|
1560
1848
|
format: Export format ("pem" or "der")
|
1561
|
-
|
1849
|
+
|
1562
1850
|
Returns:
|
1563
1851
|
Certificate content in specified format
|
1564
1852
|
"""
|
1565
1853
|
try:
|
1566
|
-
with open(cert_path,
|
1854
|
+
with open(cert_path, "rb") as f:
|
1567
1855
|
cert_data = f.read()
|
1568
|
-
|
1856
|
+
|
1569
1857
|
if format.lower() == "pem":
|
1570
|
-
return cert_data.decode(
|
1858
|
+
return cert_data.decode("utf-8")
|
1571
1859
|
elif format.lower() == "der":
|
1572
1860
|
# Convert PEM to DER
|
1573
1861
|
from cryptography import x509
|
1862
|
+
|
1574
1863
|
cert = x509.load_pem_x509_certificate(cert_data)
|
1575
1864
|
return cert.public_bytes(serialization.Encoding.DER)
|
1576
1865
|
else:
|
1577
1866
|
raise ValueError(f"Unsupported format: {format}")
|
1578
|
-
|
1867
|
+
|
1579
1868
|
except Exception as e:
|
1580
1869
|
self.logger.error(f"Failed to export certificate: {str(e)}")
|
1581
1870
|
raise CertificateGenerationError(f"Failed to export certificate: {str(e)}")
|
1582
1871
|
|
1583
|
-
def export_private_key(
|
1872
|
+
def export_private_key(
|
1873
|
+
self, key_path: str, format: str = "pem"
|
1874
|
+
) -> Union[str, bytes]:
|
1584
1875
|
"""
|
1585
1876
|
Export private key to different formats.
|
1586
|
-
|
1877
|
+
|
1587
1878
|
Args:
|
1588
1879
|
key_path: Path to private key file
|
1589
1880
|
format: Export format ("pem" or "der")
|
1590
|
-
|
1881
|
+
|
1591
1882
|
Returns:
|
1592
1883
|
Private key content in specified format
|
1593
1884
|
"""
|
1594
1885
|
try:
|
1595
|
-
with open(key_path,
|
1886
|
+
with open(key_path, "rb") as f:
|
1596
1887
|
key_data = f.read()
|
1597
|
-
|
1888
|
+
|
1598
1889
|
if format.lower() == "pem":
|
1599
|
-
return key_data.decode(
|
1890
|
+
return key_data.decode("utf-8")
|
1600
1891
|
elif format.lower() == "der":
|
1601
1892
|
# Convert PEM to DER
|
1602
|
-
from cryptography.hazmat.primitives.serialization import
|
1893
|
+
from cryptography.hazmat.primitives.serialization import (
|
1894
|
+
load_pem_private_key,
|
1895
|
+
)
|
1896
|
+
|
1603
1897
|
key = load_pem_private_key(key_data, password=None)
|
1604
1898
|
return key.private_bytes(
|
1605
1899
|
encoding=serialization.Encoding.DER,
|
1606
1900
|
format=serialization.PrivateFormat.PKCS8,
|
1607
|
-
encryption_algorithm=serialization.NoEncryption()
|
1901
|
+
encryption_algorithm=serialization.NoEncryption(),
|
1608
1902
|
)
|
1609
1903
|
else:
|
1610
1904
|
raise ValueError(f"Unsupported format: {format}")
|
1611
|
-
|
1905
|
+
|
1612
1906
|
except Exception as e:
|
1613
1907
|
self.logger.error(f"Failed to export private key: {str(e)}")
|
1614
1908
|
raise CertificateGenerationError(f"Failed to export private key: {str(e)}")
|
@@ -1618,7 +1912,7 @@ WvWwM6xqxW0Sf6s5AxJmTn3amZ0G+aP4Y2AEojlbQR7g5aigKbFQqGDFW07egp6
|
|
1618
1912
|
# Skip validation if certificate management is disabled
|
1619
1913
|
if not self.config.enabled:
|
1620
1914
|
return
|
1621
|
-
|
1915
|
+
|
1622
1916
|
if not self.config.ca_cert_path:
|
1623
1917
|
raise CertificateConfigurationError("CA certificate path is required")
|
1624
1918
|
|