Skip to content

Commit 82e79c9

Browse files
committed
Refactor CSR signing to include explicit role parameter
Updated the `sign_csr` method to accept an explicit `role` parameter, replacing the previous heuristic using `is_server`. This improves clarity and flexibility, ensuring correct certificate attributes based on the provided role ("server" or "client"). Also updated related invocations and adjusted the builder logic accordingly.
1 parent f83e3fd commit 82e79c9

File tree

4 files changed

+45
-29
lines changed

4 files changed

+45
-29
lines changed

chaski/ca.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ async def sign_csr(
9191
f"{self.name}: Starting the signing process for CSR data for node '{node_id}'"
9292
)
9393
# Sign the provided Certificate Signing Request (CSR) using the Certificate Authority (CA)
94-
signed_csr_client = self.ca.sign_csr(csr_data_client)
95-
signed_csr_server = self.ca.sign_csr(csr_data_server)
94+
signed_csr_client = self.ca.sign_csr(csr_data_client, role="client")
95+
signed_csr_server = self.ca.sign_csr(csr_data_server, role="server")
9696

9797
# Prepare the dictionary containing the signed certificates for client and server,
9898
# and the path to the CA certificate. This dictionary will be returned as the result

chaski/utils/certificate_authority.py

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import ssl
33
import datetime
44
from ipaddress import ip_address
5+
from typing import Literal, Optional
56

67
# Importing cryptography modules for X509 certificates, private key generation,
78
# and serialization, including RSA key generation and PEM encoding without encryption.
@@ -241,7 +242,9 @@ def ca_certificate_path(self, path: str) -> None:
241242
"""
242243
self.ca_cert_path_ = path
243244

244-
def sign_csr(self, csr_data: bytes, is_server: bool | None = None) -> bytes:
245+
def sign_csr(
246+
self, csr_data: bytes, role: Optional[Literal["server", "client"]] = None
247+
) -> bytes:
245248
"""
246249
Sign a Certificate Signing Request (CSR) with the Certificate Authority (CA) key.
247250
@@ -252,9 +255,9 @@ def sign_csr(self, csr_data: bytes, is_server: bool | None = None) -> bytes:
252255
----------
253256
csr_data : bytes
254257
The certificate signing request data in PEM format.
255-
is_server : bool, optional
256-
If True, indicates this is a server certificate. If False, indicates this is a client certificate.
257-
If None, will be inferred from the Common Name (CN) in the CSR by checking if it contains "server".
258+
role : Optional[Literal["server", "client"]], optional
259+
The role of the certificate, either "server" or "client". If not provided,
260+
it will be inferred from the Common Name in the CSR.
258261
259262
Returns
260263
-------
@@ -282,10 +285,15 @@ def sign_csr(self, csr_data: bytes, is_server: bool | None = None) -> bytes:
282285
# Load client CSR
283286
csr = x509.load_pem_x509_csr(csr_data)
284287

285-
# infer by CN if not provided
286-
if is_server is None:
287-
cn = csr.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
288-
is_server = "server" in cn.lower()
288+
# Heuristic if role not explicitly provided
289+
if role is None:
290+
try:
291+
cn = csr.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[
292+
0
293+
].value.lower()
294+
role = "server" if "server" in cn else "client"
295+
except Exception:
296+
role = "client"
289297

290298
ku = x509.KeyUsage(
291299
digital_signature=True,
@@ -300,12 +308,12 @@ def sign_csr(self, csr_data: bytes, is_server: bool | None = None) -> bytes:
300308
)
301309
eku = x509.ExtendedKeyUsage(
302310
[ExtendedKeyUsageOID.SERVER_AUTH]
303-
if is_server
311+
if role == "server"
304312
else [ExtendedKeyUsageOID.CLIENT_AUTH]
305313
)
306314

307315
# Generate client certificate
308-
certificate = (
316+
builder = (
309317
x509.CertificateBuilder()
310318
# Set the subject name for the certificate to the subject specified in the CSR (Certificate Signing Request)
311319
.subject_name(csr.subject)
@@ -332,16 +340,6 @@ def sign_csr(self, csr_data: bytes, is_server: bool | None = None) -> bytes:
332340
# Add the subject alternative name extension with the IP address
333341
.add_extension(ku, critical=True)
334342
.add_extension(eku, critical=False)
335-
.add_extension(
336-
(
337-
x509.SubjectAlternativeName(
338-
[x509.IPAddress(ip_address(self.ip_address))]
339-
)
340-
if is_server
341-
else x509.SubjectAlternativeName([])
342-
), # no SAN-IP en client por defecto
343-
critical=False,
344-
)
345343
.add_extension(
346344
x509.SubjectKeyIdentifier.from_public_key(csr.public_key()),
347345
critical=False,
@@ -350,10 +348,19 @@ def sign_csr(self, csr_data: bytes, is_server: bool | None = None) -> bytes:
350348
x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()),
351349
critical=False,
352350
)
353-
# Sign the certificate using the CA's private key with SHA256 as the hashing algorithm
354-
.sign(ca_key, hashes.SHA256())
355351
)
356352

353+
if role == "server":
354+
builder = builder.add_extension(
355+
x509.SubjectAlternativeName(
356+
[x509.IPAddress(ip_address(self.ip_address))]
357+
),
358+
critical=False,
359+
)
360+
361+
# Sign the certificate using the CA's private key with SHA256 as the hashing algorithm
362+
certificate = builder.sign(ca_key, hashes.SHA256())
363+
357364
return certificate.public_bytes(serialization.Encoding.PEM)
358365

359366
def _key_and_csr(self, name="client") -> tuple:
@@ -683,7 +690,7 @@ def get_context(self) -> tuple[ssl.SSLContext, ssl.SSLContext]:
683690

684691
return ssl_context_client, ssl_context_server
685692

686-
def sign_and_store(self, name: str) -> str:
693+
def sign_and_store(self, name: Literal["server", "client"]) -> str:
687694
"""
688695
Sign a Certificate Signing Request (CSR) and store the resulting certificate.
689696
@@ -709,6 +716,7 @@ def sign_and_store(self, name: str) -> str:
709716
"""
710717
csr_path = self.certificate_paths[name]
711718
cert_path = self.certificate_signed_paths[name]
712-
cert_pem = self.sign_csr(self.load_certificate(csr_path))
719+
cert_pem = self.sign_csr(self.load_certificate(csr_path), role=name)
713720
self.write_certificate(cert_path, cert_pem)
721+
os.chmod(cert_path, 0o644)
714722
return cert_path

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "chaski-confluent"
7-
version = "0.1a6"
7+
version = "0.1a8"
88
description = "Chaski Confluent"
99
readme = "README.md"
1010
requires-python = ">=3.10"

test/test_certificate_authority.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,10 +271,18 @@ async def test_sign(self, certificate_authority: CertificateAuthority) -> None:
271271
)
272272

273273
with open(ca.certificate_signed_paths["client"], "wb") as file:
274-
file.write(ca.sign_csr(ca.load_certificate(ca.certificate_paths["client"])))
274+
file.write(
275+
ca.sign_csr(
276+
ca.load_certificate(ca.certificate_paths["client"]), role="client"
277+
)
278+
)
275279

276280
with open(ca.certificate_signed_paths["server"], "wb") as file:
277-
file.write(ca.sign_csr(ca.load_certificate(ca.certificate_paths["server"])))
281+
file.write(
282+
ca.sign_csr(
283+
ca.load_certificate(ca.certificate_paths["server"]), role="server"
284+
)
285+
)
278286

279287
ca_cert: Certificate = x509.load_pem_x509_certificate(
280288
ca.load_certificate(ca.ca_certificate_path), default_backend()

0 commit comments

Comments
 (0)