-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcertificate_handler.py
More file actions
131 lines (109 loc) · 4.94 KB
/
certificate_handler.py
File metadata and controls
131 lines (109 loc) · 4.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""Generate self-signed SSL certificate for local development."""
import ipaddress
import logging
import sys
from datetime import UTC, datetime, timedelta
from pathlib import Path
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
from python_template_server.logging_setup import setup_logging
from python_template_server.models import CertificateConfigModel
setup_logging()
logger = logging.getLogger(__name__)
class CertificateHandler:
"""Handles SSL certificate generation and management."""
def __init__(self, certificate_config: CertificateConfigModel) -> None:
"""Initialize the CertificateHandler."""
self.cert_dir = certificate_config.directory
self.cert_file = certificate_config.ssl_cert_file_path
self.key_file = certificate_config.ssl_key_file_path
self.days_valid = certificate_config.days_valid
@property
def certificate_subject(self) -> x509.Name:
"""Define the subject for the self-signed certificate."""
return x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, "UK"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Local"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "Local"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Development"),
x509.NameAttribute(NameOID.COMMON_NAME, "localhost"),
]
)
@staticmethod
def new_private_key() -> rsa.RSAPrivateKey:
"""Generate a new RSA private key."""
return rsa.generate_private_key(
public_exponent=65537,
key_size=4096,
)
@staticmethod
def _write_to_file(file_path: Path, data: bytes) -> None:
"""Write data to a file."""
with file_path.open("wb") as f:
f.write(data)
def write_to_key_file(self, data: bytes) -> None:
"""Write data to the key file."""
self._write_to_file(self.key_file, data)
def write_to_cert_file(self, data: bytes) -> None:
"""Write data to the certificate file."""
self._write_to_file(self.cert_file, data)
def generate_self_signed_cert(self) -> None:
"""Generate a self-signed certificate and private key.
:raise SystemExit: If certificate directory cannot be created
:raise OSError: If certificate files cannot be written
:raise PermissionError: If insufficient permissions to write certificate files
"""
try:
# Ensure certificate directory exists and is writable
self.cert_file.parent.mkdir(parents=True, exist_ok=True)
# Test write permissions
if not self.cert_file.parent.exists():
logger.error("Failed to create certificate directory: %s", self.cert_file.parent)
sys.exit(1)
# Generate private key
private_key = self.new_private_key()
# Create certificate subject and issuer (self-signed, so they're the same)
subject = issuer = self.certificate_subject
# Build certificate
certificate = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(private_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.now(UTC))
.not_valid_after(datetime.now(UTC) + timedelta(days=self.days_valid))
.add_extension(
x509.SubjectAlternativeName(
[
x509.DNSName("localhost"),
x509.DNSName("127.0.0.1"),
x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")),
]
),
critical=False,
)
.sign(private_key, hashes.SHA256())
)
# Write private key to file
self.write_to_key_file(
private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
)
# Write certificate to file
self.write_to_cert_file(certificate.public_bytes(serialization.Encoding.PEM))
logger.info("Certificate generated successfully!")
logger.info("Saved in directory: %s", self.cert_dir)
logger.info("Valid for: %d days", self.days_valid)
except PermissionError:
logger.exception("Permission denied when writing certificate files: %s", self.cert_file.parent)
raise
except OSError:
logger.exception("Failed to generate certificate files!")
raise