Fix expired self-signed cert validation: remove bypass, enable X509_STRICT|POLICY_CHECK, extract numeric error codes from pyOpenSSL

This commit is contained in:
KCA
2026-10-25 22:08:15 +00:00
parent f2d4730c26
commit a0135ef113
2 changed files with 62 additions and 5 deletions
@@ -250,14 +250,17 @@ def validation_errors(cert: x509.Certificate, chain: Sequence[x509.Certificate])
we verify the chain once and, for missing issuers, report that explicitly. we verify the chain once and, for missing issuers, report that explicitly.
""" """
errors: List[Dict[str, Any]] = [] errors: List[Dict[str, Any]] = []
if is_self_signed(cert): self_signed = is_self_signed(cert)
return errors if not self_signed and len(chain) < 2:
if len(chain) < 2:
return [{"code": 20, "message": "unable to get local issuer certificate"}] return [{"code": 20, "message": "unable to get local issuer certificate"}]
try: try:
from OpenSSL import crypto from OpenSSL import crypto
store = crypto.X509Store() store = crypto.X509Store()
store.set_flags(crypto.X509StoreFlags.X509_STRICT | crypto.X509StoreFlags.POLICY_CHECK)
converted_cert = crypto.X509.from_cryptography(cert)
if self_signed:
store.add_cert(converted_cert)
# The root of the supplied chain is trusted; intermediates are # The root of the supplied chain is trusted; intermediates are
# untrusted inputs, exactly like XCA's trusted/untrusted stacks. # untrusted inputs, exactly like XCA's trusted/untrusted stacks.
for item in chain[1:]: for item in chain[1:]:
@@ -268,12 +271,13 @@ def validation_errors(cert: x509.Certificate, chain: Sequence[x509.Certificate])
store.add_cert(converted) store.add_cert(converted)
context = crypto.X509StoreContext( context = crypto.X509StoreContext(
store, store,
crypto.X509.from_cryptography(cert), converted_cert,
[crypto.X509.from_cryptography(item) for item in chain[1:]], [crypto.X509.from_cryptography(item) for item in chain[1:]],
) )
context.verify_certificate() context.verify_certificate()
except Exception as exc: # noqa: BLE001 - pyOpenSSL raises rich errors except Exception as exc: # noqa: BLE001 - pyOpenSSL raises rich errors
code = getattr(exc, "args", [None])[-1] if getattr(exc, "args", None) else None verification_errors = getattr(exc, "errors", None)
code = verification_errors[0] if verification_errors else None
if not isinstance(code, int): if not isinstance(code, int):
code = None code = None
errors.append({"code": code, "message": str(exc)}) errors.append({"code": code, "message": str(exc)})
@@ -5,6 +5,7 @@ import base64
import time import time
import unittest import unittest
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from unittest.mock import ANY, patch
from cryptography import x509 from cryptography import x509
from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import serialization
@@ -243,6 +244,8 @@ class CertificateTest(unittest.TestCase):
spec["key_usage"] = {"critical": True, "usages": ["digital_signature", "key_encipherment"]} spec["key_usage"] = {"critical": True, "usages": ["digital_signature", "key_encipherment"]}
spec["extended_key_usage"] = {"critical": False, "usages": ["serverAuth"]} spec["extended_key_usage"] = {"critical": False, "usages": ["serverAuth"]}
spec["subject_alt_name"] = {"critical": False, "entries": [{"type": "dns", "value": "example.com"}]} spec["subject_alt_name"] = {"critical": False, "entries": [{"type": "dns", "value": "example.com"}]}
spec["subject_key_identifier"] = {"critical": False}
spec["authority_key_identifier"] = {"critical": False}
leaf = certificates.build_signed( leaf = certificates.build_signed(
subject=_name("example.com"), subject=_name("example.com"),
public_key=leaf_key.public_key(), public_key=leaf_key.public_key(),
@@ -260,6 +263,56 @@ class CertificateTest(unittest.TestCase):
self.assertEqual(parsed.serial_number, 2) self.assertEqual(parsed.serial_number, 2)
self.assertEqual(certificates.find_issuer(parsed, [ca]), ca) self.assertEqual(certificates.find_issuer(parsed, [ca]), ca)
self.assertEqual(certificates.validation_errors(parsed, [parsed, ca]), []) self.assertEqual(certificates.validation_errors(parsed, [parsed, ca]), [])
self.assertEqual(
certificates.validation_errors(parsed, [parsed]),
[{"code": 20, "message": "unable to get local issuer certificate"}],
)
def test_self_signed_validation(self) -> None:
key = keys.generate_key("ec", curve="secp256r1")
now = datetime.now(timezone.utc)
valid = certificates.build_self_signed(
subject=_name("Valid Root CA"),
private_key=key,
serial=6,
not_before=now - timedelta(days=1),
not_after=now + timedelta(days=30),
extension_spec=_ca_spec(),
)
expired = certificates.build_self_signed(
subject=_name("Expired Root CA"),
private_key=key,
serial=7,
not_before=now - timedelta(days=30),
not_after=now - timedelta(days=1),
extension_spec=_ca_spec(),
)
self.assertEqual(certificates.validation_errors(valid, [valid]), [])
self.assertEqual(
certificates.validation_errors(expired, [expired]),
[{"code": 10, "message": "certificate has expired"}],
)
def test_validation_sets_strict_flags(self) -> None:
from OpenSSL import crypto
key = keys.generate_key("ec", curve="secp256r1")
now = datetime.now(timezone.utc)
cert = certificates.build_self_signed(
subject=_name("Strict Root CA"),
private_key=key,
serial=8,
not_before=now - timedelta(days=1),
not_after=now + timedelta(days=30),
extension_spec=_ca_spec(),
)
flags = crypto.X509StoreFlags.X509_STRICT | crypto.X509StoreFlags.POLICY_CHECK
with patch.object(crypto.X509Store, "set_flags", autospec=True) as set_flags:
self.assertEqual(certificates.validation_errors(cert, [cert]), [])
set_flags.assert_called_once_with(ANY, flags)
def test_renew_spec(self) -> None: def test_renew_spec(self) -> None:
key = keys.generate_key("rsa", 2048) key = keys.generate_key("rsa", 2048)