Fix expired self-signed cert validation: remove bypass, enable X509_STRICT|POLICY_CHECK, extract numeric error codes from pyOpenSSL
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user