diff --git a/ca/django_ca/extensions/serialize.py b/ca/django_ca/extensions/serialize.py index 830829075..709e31bd2 100644 --- a/ca/django_ca/extensions/serialize.py +++ b/ca/django_ca/extensions/serialize.py @@ -11,7 +11,15 @@ # You should have received a copy of the GNU General Public License along with django-ca. If not, see # . -"""``django_ca.extensions.serialize`` contains functions to serialize extensions.""" +"""``django_ca.extensions.serialize`` contains functions to serialize extensions. + +NOTE:: + + Functions in this module return values in the order as they appear in the extensions, so that the original + extension can be reconstructed exactly from this serialized form. + +TODO:: Make sure the above is actually true. +""" import binascii from typing import Any, Dict, List, Optional @@ -191,7 +199,7 @@ def _serialize_extension( # pylint: disable=too-many-return-statements if isinstance(value, x509.CertificatePolicies): return _certificate_policies_serialized(value) if isinstance(value, x509.ExtendedKeyUsage): - return sorted([EXTENDED_KEY_USAGE_NAMES[usage] for usage in value]) + return [EXTENDED_KEY_USAGE_NAMES[usage] for usage in value] if isinstance(value, x509.InhibitAnyPolicy): return value.skip_certs if isinstance(value, x509.KeyUsage): diff --git a/ca/django_ca/tests/acme/views/base.py b/ca/django_ca/tests/acme/views/base.py index 32c6d5bd2..22b261628 100644 --- a/ca/django_ca/tests/acme/views/base.py +++ b/ca/django_ca/tests/acme/views/base.py @@ -34,8 +34,9 @@ from django_ca.acme.responses import AcmeResponseUnauthorized from django_ca.models import AcmeAccount, CertificateAuthority, acme_slug -from django_ca.tests.base import certs, override_tmpcadir +from django_ca.tests.base.constants import CERT_DATA from django_ca.tests.base.mixins import TestCaseMixin +from django_ca.tests.base.utils import override_tmpcadir MessageTypeVar = typing.TypeVar("MessageTypeVar", bound=jose.json_util.JSONObjectWithFields) @@ -51,7 +52,7 @@ class AcmeTestCaseMixin(TestCaseMixin): # NOTE: PEM here is the same as AcmeAccount.pem when this cert is used for account registration PEM = ( - certs["root-cert"]["key"]["parsed"] + CERT_DATA["root-cert"]["key"]["parsed"] .public_key() .public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo) .decode("utf-8") @@ -60,7 +61,7 @@ class AcmeTestCaseMixin(TestCaseMixin): thumbprint = "kqtZjXqX07HbrRg220VoINzqF9QXsfIkQava3PdWM8o" ACCOUNT_ONE_CONTACT = "mailto:one@example.com" CHILD_PEM = ( - certs["child-cert"]["key"]["parsed"] + CERT_DATA["child-cert"]["key"]["parsed"] .public_key() .public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo) .decode("utf-8") @@ -238,7 +239,9 @@ def acme( if nonce is None: nonce = self.get_nonce() if cert is None: - cert = typing.cast(CertificateIssuerPrivateKeyTypes, certs[self.load_certs[0]]["key"]["parsed"]) + cert = typing.cast( + CertificateIssuerPrivateKeyTypes, CERT_DATA[self.load_certs[0]]["key"]["parsed"] + ) if post_kwargs is None: post_kwargs = {} diff --git a/ca/django_ca/tests/acme/views/test_authorization.py b/ca/django_ca/tests/acme/views/test_authorization.py index 4868b8175..0ead6b1f6 100644 --- a/ca/django_ca/tests/acme/views/test_authorization.py +++ b/ca/django_ca/tests/acme/views/test_authorization.py @@ -29,10 +29,11 @@ from django_ca import ca_settings from django_ca.models import AcmeAuthorization, AcmeChallenge, AcmeOrder from django_ca.tests.acme.views.base import AcmeWithAccountViewTestCaseMixin -from django_ca.tests.base import override_tmpcadir, timestamps +from django_ca.tests.base.constants import TIMESTAMPS +from django_ca.tests.base.utils import override_tmpcadir -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeAuthorizationViewTestCase( AcmeWithAccountViewTestCaseMixin[jose.json_util.JSONObjectWithFields], TestCase ): diff --git a/ca/django_ca/tests/acme/views/test_challenge.py b/ca/django_ca/tests/acme/views/test_challenge.py index 4cb4243ad..bcd2e86be 100644 --- a/ca/django_ca/tests/acme/views/test_challenge.py +++ b/ca/django_ca/tests/acme/views/test_challenge.py @@ -27,10 +27,11 @@ from django_ca.models import AcmeAuthorization, AcmeChallenge, AcmeOrder from django_ca.tasks import acme_validate_challenge from django_ca.tests.acme.views.base import AcmeWithAccountViewTestCaseMixin -from django_ca.tests.base import override_tmpcadir, timestamps +from django_ca.tests.base.constants import TIMESTAMPS +from django_ca.tests.base.utils import override_tmpcadir -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeChallengeViewTestCase( AcmeWithAccountViewTestCaseMixin[jose.json_util.JSONObjectWithFields], TransactionTestCase ): diff --git a/ca/django_ca/tests/acme/views/test_directory.py b/ca/django_ca/tests/acme/views/test_directory.py index 547732668..2551473e1 100644 --- a/ca/django_ca/tests/acme/views/test_directory.py +++ b/ca/django_ca/tests/acme/views/test_directory.py @@ -23,7 +23,7 @@ from freezegun import freeze_time from django_ca.models import CertificateAuthority -from django_ca.tests.base import timestamps +from django_ca.tests.base.constants import TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin @@ -39,7 +39,7 @@ def setUp(self) -> None: self.ca.acme_enabled = True self.ca.save() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_default(self) -> None: """Test the default directory view.""" with mock.patch("secrets.token_bytes", return_value=b"foobar"): @@ -58,7 +58,7 @@ def test_default(self) -> None: }, ) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_named_ca(self) -> None: """Test getting directory for named CA.""" url = reverse("django_ca:acme-directory", kwargs={"serial": self.ca.serial}) @@ -79,7 +79,7 @@ def test_named_ca(self) -> None: }, ) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_meta(self) -> None: """Test the meta property.""" self.ca.website = "http://ca.example.com" @@ -112,7 +112,7 @@ def test_meta(self) -> None: }, ) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_acme_default_disabled(self) -> None: """Test that fetching the default CA with ACME disabled doesn't work.""" self.ca.acme_enabled = False @@ -130,7 +130,7 @@ def test_acme_default_disabled(self) -> None: }, ) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_acme_disabled(self) -> None: """Test that fetching the default CA with ACME disabled doesn't work.""" self.ca.acme_enabled = False @@ -164,7 +164,7 @@ def test_no_ca(self) -> None: }, ) - @freeze_time(timestamps["everything_expired"]) + @freeze_time(TIMESTAMPS["everything_expired"]) def test_expired_ca(self) -> None: """Test using default CA when all CAs are expired.""" response = self.client.get(self.url) diff --git a/ca/django_ca/tests/acme/views/test_new_account.py b/ca/django_ca/tests/acme/views/test_new_account.py index 284dbab26..902999bdb 100644 --- a/ca/django_ca/tests/acme/views/test_new_account.py +++ b/ca/django_ca/tests/acme/views/test_new_account.py @@ -26,15 +26,16 @@ from django_ca.models import AcmeAccount from django_ca.tests.acme.views.base import AcmeBaseViewTestCaseMixin -from django_ca.tests.base import certs, override_tmpcadir, timestamps +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS +from django_ca.tests.base.utils import override_tmpcadir -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeNewAccountViewTestCase(AcmeBaseViewTestCaseMixin[acme.messages.Registration], TestCase): """Test creating a new account.""" contact = "mailto:user@example.com" - url = reverse_lazy("django_ca:acme-new-account", kwargs={"serial": certs["root"]["serial"]}) + url = reverse_lazy("django_ca:acme-new-account", kwargs={"serial": CERT_DATA["root"]["serial"]}) message = acme.messages.Registration(contact=(contact,), terms_of_service_agreed=True) message_cls = acme.messages.Registration requires_kid = False diff --git a/ca/django_ca/tests/acme/views/test_new_order.py b/ca/django_ca/tests/acme/views/test_new_order.py index 73d3cd647..23054dae8 100644 --- a/ca/django_ca/tests/acme/views/test_new_order.py +++ b/ca/django_ca/tests/acme/views/test_new_order.py @@ -33,14 +33,15 @@ from django_ca.acme.messages import NewOrder from django_ca.models import AcmeAuthorization, AcmeOrder from django_ca.tests.acme.views.base import AcmeWithAccountViewTestCaseMixin -from django_ca.tests.base import certs, override_tmpcadir, timestamps +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS +from django_ca.tests.base.utils import override_tmpcadir -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeNewOrderViewTestCase(AcmeWithAccountViewTestCaseMixin[NewOrder], TestCase): """Test creating a new order.""" - url = reverse_lazy("django_ca:acme-new-order", kwargs={"serial": certs["root"]["serial"]}) + url = reverse_lazy("django_ca:acme-new-order", kwargs={"serial": CERT_DATA["root"]["serial"]}) message_cls = NewOrder def get_message(self, **kwargs: Any) -> NewOrder: diff --git a/ca/django_ca/tests/acme/views/test_order.py b/ca/django_ca/tests/acme/views/test_order.py index cb31542c1..38352dbea 100644 --- a/ca/django_ca/tests/acme/views/test_order.py +++ b/ca/django_ca/tests/acme/views/test_order.py @@ -27,10 +27,11 @@ from django_ca.acme.errors import AcmeUnauthorized from django_ca.models import AcmeAccount, AcmeAuthorization, AcmeCertificate, AcmeOrder from django_ca.tests.acme.views.base import AcmeWithAccountViewTestCaseMixin -from django_ca.tests.base import override_tmpcadir, timestamps +from django_ca.tests.base.constants import TIMESTAMPS +from django_ca.tests.base.utils import override_tmpcadir -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeOrderViewTestCase(AcmeWithAccountViewTestCaseMixin[jose.json_util.JSONObjectWithFields], TestCase): """Test retrieving an order.""" diff --git a/ca/django_ca/tests/acme/views/test_order_finalize.py b/ca/django_ca/tests/acme/views/test_order_finalize.py index 73019823e..524c8b2dd 100644 --- a/ca/django_ca/tests/acme/views/test_order_finalize.py +++ b/ca/django_ca/tests/acme/views/test_order_finalize.py @@ -34,12 +34,12 @@ from django_ca.models import AcmeAccount, AcmeAuthorization, AcmeOrder from django_ca.tasks import acme_issue_certificate from django_ca.tests.acme.views.base import AcmeWithAccountViewTestCaseMixin -from django_ca.tests.base import certs, dns, override_tmpcadir, timestamps -from django_ca.tests.base.constants import FIXTURES_DIR +from django_ca.tests.base.constants import CERT_DATA, FIXTURES_DIR, TIMESTAMPS from django_ca.tests.base.typehints import HttpResponse +from django_ca.tests.base.utils import dns, override_tmpcadir -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeOrderFinalizeViewTestCase( AcmeWithAccountViewTestCaseMixin[CertificateRequest], TransactionTestCase ): @@ -47,7 +47,7 @@ class AcmeOrderFinalizeViewTestCase( slug = "92MPyl7jm0zw" url = reverse_lazy( - "django_ca:acme-order-finalize", kwargs={"serial": certs["root"]["serial"], "slug": slug} + "django_ca:acme-order-finalize", kwargs={"serial": CERT_DATA["root"]["serial"], "slug": slug} ) def setUp(self) -> None: @@ -59,7 +59,7 @@ def setUp(self) -> None: x509.CertificateSigningRequestBuilder() .subject_name(x509.Name([])) .add_extension(x509.SubjectAlternativeName([dns(self.hostname)]), critical=False) - .sign(certs["root-cert"]["key"]["parsed"], hashes.SHA256()) + .sign(CERT_DATA["root-cert"]["key"]["parsed"], hashes.SHA256()) ) self.order = AcmeOrder.objects.create( @@ -213,7 +213,7 @@ def test_csr_valid_subject(self) -> None: ) ) .add_extension(x509.SubjectAlternativeName([dns(self.hostname)]), critical=False) - .sign(certs["root-cert"]["key"]["parsed"], hashes.SHA256()) + .sign(CERT_DATA["root-cert"]["key"]["parsed"], hashes.SHA256()) ) with self.patch("django_ca.acme.views.run_task") as mockcm: @@ -249,7 +249,7 @@ def test_csr_subject_no_cn(self) -> None: ) ) .add_extension(x509.SubjectAlternativeName([dns(self.hostname)]), critical=False) - .sign(certs["root-cert"]["key"]["parsed"], hashes.SHA256()) + .sign(CERT_DATA["root-cert"]["key"]["parsed"], hashes.SHA256()) ) with self.patch("django_ca.acme.views.run_task") as mockcm: @@ -285,7 +285,7 @@ def test_csr_subject_no_domain(self) -> None: ) ) .add_extension(x509.SubjectAlternativeName([dns(self.hostname)]), critical=False) - .sign(certs["root-cert"]["key"]["parsed"], hashes.SHA256()) + .sign(CERT_DATA["root-cert"]["key"]["parsed"], hashes.SHA256()) ) with self.patch("django_ca.acme.views.run_task") as mockcm: @@ -306,7 +306,7 @@ def test_csr_subject_not_in_order(self) -> None: ) ) .add_extension(x509.SubjectAlternativeName([dns(self.hostname)]), critical=False) - .sign(certs["root-cert"]["key"]["parsed"], hashes.SHA256()) + .sign(CERT_DATA["root-cert"]["key"]["parsed"], hashes.SHA256()) ) with self.patch("django_ca.acme.views.run_task") as mockcm: @@ -320,7 +320,7 @@ def test_csr_no_san(self) -> None: csr = ( x509.CertificateSigningRequestBuilder() .subject_name(x509.Name([])) - .sign(certs["root-cert"]["key"]["parsed"], hashes.SHA256()) + .sign(CERT_DATA["root-cert"]["key"]["parsed"], hashes.SHA256()) ) with self.patch("django_ca.acme.views.run_task") as mockcm: @@ -338,7 +338,7 @@ def test_csr_different_names(self) -> None: x509.SubjectAlternativeName([dns(self.hostname), dns("example.net")]), critical=False, ) - .sign(certs["root-cert"]["key"]["parsed"], hashes.SHA256()) + .sign(CERT_DATA["root-cert"]["key"]["parsed"], hashes.SHA256()) ) with self.patch("django_ca.acme.views.run_task") as mockcm: diff --git a/ca/django_ca/tests/acme/views/test_revocation.py b/ca/django_ca/tests/acme/views/test_revocation.py index cd0e93067..6a33f559a 100644 --- a/ca/django_ca/tests/acme/views/test_revocation.py +++ b/ca/django_ca/tests/acme/views/test_revocation.py @@ -30,12 +30,13 @@ from django_ca.constants import ReasonFlags from django_ca.models import AcmeAccount, AcmeAuthorization, AcmeCertificate, AcmeOrder, Certificate from django_ca.tests.acme.views.base import AcmeWithAccountViewTestCaseMixin -from django_ca.tests.base import certs, override_tmpcadir, timestamps +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.typehints import HttpResponse +from django_ca.tests.base.utils import override_tmpcadir from django_ca.utils import get_cert_builder -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeCertificateRevocationViewTestCase( AcmeWithAccountViewTestCaseMixin[acme.messages.Revocation], TestCase ): @@ -80,7 +81,7 @@ def test_basic(self) -> None: self.cert.refresh_from_db() self.assertTrue(self.cert.revoked) - self.assertEqual(self.cert.revoked_date, timestamps["everything_valid"]) + self.assertEqual(self.cert.revoked_date, TIMESTAMPS["everything_valid"]) self.assertEqual(self.cert.revoked_reason, ReasonFlags.unspecified.value) @override_settings(USE_TZ=False) @@ -91,7 +92,7 @@ def test_basic_with_use_tz_false(self) -> None: self.cert.refresh_from_db() self.assertTrue(self.cert.revoked) - self.assertEqual(self.cert.revoked_date, timestamps["everything_valid_naive"]) + self.assertEqual(self.cert.revoked_date, TIMESTAMPS["everything_valid_naive"]) self.assertEqual(self.cert.revoked_reason, ReasonFlags.unspecified.value) def test_reason_code(self) -> None: @@ -102,7 +103,7 @@ def test_reason_code(self) -> None: self.cert.refresh_from_db() self.assertTrue(self.cert.revoked) - self.assertEqual(self.cert.revoked_date, timestamps["everything_valid"]) + self.assertEqual(self.cert.revoked_date, TIMESTAMPS["everything_valid"]) self.assertEqual(self.cert.revoked_reason, ReasonFlags.affiliation_changed.name) def test_already_revoked(self) -> None: @@ -132,7 +133,7 @@ def test_unknown_certificate(self) -> None: def test_wrong_certificate(self) -> None: """Test sending a different certificate with the same serial.""" # Create a clone of the existing certificate with the same serial number - pkey = certs["root-cert"]["csr"]["parsed"].public_key() + pkey = CERT_DATA["root-cert"]["csr"]["parsed"].public_key() builder = get_cert_builder(self.cert.expires, serial=self.cert.pub.loaded.serial_number) builder = builder.public_key(pkey) builder = builder.issuer_name(self.ca.subject) @@ -147,13 +148,13 @@ def test_wrong_certificate(self) -> None: def test_pass_csr(self) -> None: """Send a CSR instead of a certificate.""" - req = X509Req.from_cryptography(certs["root-cert"]["csr"]["parsed"]) + req = X509Req.from_cryptography(CERT_DATA["root-cert"]["csr"]["parsed"]) message = self.csr_class(certificate=jose.util.ComparableX509(req)) resp = self.acme(self.url, message, kid=self.kid) self.assertMalformed(resp, "Could not decode 'certificate'", regex=True) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeCertificateRevocationWithAuthorizationsViewTestCase(AcmeCertificateRevocationViewTestCase): """Test certificate revocation by signing the request with the compromised certificate.""" @@ -166,7 +167,7 @@ def setUp(self) -> None: ) def acme(self, *args: Any, **kwargs: Any) -> "HttpResponse": - kwargs.setdefault("cert", certs["child-cert"]["key"]["parsed"]) + kwargs.setdefault("cert", CERT_DATA["child-cert"]["key"]["parsed"]) kwargs["kid"] = self.child_kid return super().acme(*args, **kwargs) @@ -210,20 +211,20 @@ def test_non_dns_sans(self) -> None: self.assertUnauthorized(resp, "Certificate contains non-DNS subjectAlternativeNames.") -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeCertificateRevocationWithJWKViewTestCase(AcmeCertificateRevocationViewTestCase): """Test certificate revocation by signing the request with the compromised certificate.""" requires_kid = False def acme(self, *args: Any, **kwargs: Any) -> "HttpResponse": - kwargs.setdefault("cert", certs[self.default_cert]["key"]["parsed"]) + kwargs.setdefault("cert", CERT_DATA[self.default_cert]["key"]["parsed"]) kwargs["kid"] = None return super().acme(*args, **kwargs) def test_wrong_signer(self) -> None: """Sign the request with the wrong certificate.""" - cert = certs["root-cert"]["key"]["parsed"] + cert = CERT_DATA["root-cert"]["key"]["parsed"] resp = self.acme(self.url, self.message, cert=cert) self.assertUnauthorized(resp, "Request signed by the wrong certificate.") diff --git a/ca/django_ca/tests/acme/views/test_update_account.py b/ca/django_ca/tests/acme/views/test_update_account.py index 671e62abd..3d245ecc7 100644 --- a/ca/django_ca/tests/acme/views/test_update_account.py +++ b/ca/django_ca/tests/acme/views/test_update_account.py @@ -25,10 +25,10 @@ from django_ca.models import AcmeAccount, AcmeAuthorization, AcmeOrder from django_ca.tests.acme.views.base import AcmeWithAccountViewTestCaseMixin -from django_ca.tests.base import timestamps +from django_ca.tests.base.constants import TIMESTAMPS -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeUpdateAccountViewTestCase(AcmeWithAccountViewTestCaseMixin[acme.messages.Registration], TestCase): """Test updating and ACME account.""" diff --git a/ca/django_ca/tests/acme/views/test_view_cert.py b/ca/django_ca/tests/acme/views/test_view_cert.py index eefb22a6d..cd955e175 100644 --- a/ca/django_ca/tests/acme/views/test_view_cert.py +++ b/ca/django_ca/tests/acme/views/test_view_cert.py @@ -23,10 +23,11 @@ from django_ca.models import AcmeAccount, AcmeCertificate, AcmeOrder from django_ca.tests.acme.views.base import AcmeWithAccountViewTestCaseMixin -from django_ca.tests.base import CERT_PEM_REGEX, override_tmpcadir, timestamps +from django_ca.tests.base.constants import CERT_PEM_REGEX, TIMESTAMPS +from django_ca.tests.base.utils import override_tmpcadir -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeCertificateViewTestCase( AcmeWithAccountViewTestCaseMixin[jose.json_util.JSONObjectWithFields], TestCase ): diff --git a/ca/django_ca/tests/admin/test_actions.py b/ca/django_ca/tests/admin/test_actions.py index 648547165..aa9134d5b 100644 --- a/ca/django_ca/tests/admin/test_actions.py +++ b/ca/django_ca/tests/admin/test_actions.py @@ -36,9 +36,10 @@ from django_ca.constants import ReasonFlags from django_ca.models import Certificate, X509CertMixin from django_ca.signals import post_issue_cert, post_revoke_cert, pre_revoke_cert, pre_sign_cert -from django_ca.tests.base import override_tmpcadir, timestamps +from django_ca.tests.base.constants import TIMESTAMPS from django_ca.tests.base.mixins import AdminTestCaseMixin from django_ca.tests.base.typehints import DjangoCAModelTypeVar +from django_ca.tests.base.utils import override_tmpcadir if typing.TYPE_CHECKING: from django.test.client import _MonkeyPatchedWSGIResponse as HttpResponse @@ -252,7 +253,7 @@ def test_unknown_object(self) -> None: self.assertRedirects(response, "/admin/") -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class RevokeActionTestCase(AdminActionTestCaseMixin[Certificate], TestCase): """Test the revoke action.""" @@ -275,7 +276,7 @@ def assertSuccessfulRequest(self, response: "HttpResponse", *objects: Certificat self.assertRevoked(obj) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class RevokeChangeActionTestCase(AdminChangeActionTestCaseMixin[Certificate], TestCase): """Test the revoke change action.""" @@ -379,7 +380,7 @@ def test_revoked(self) -> None: self.assertRevoked(self.cert) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class ResignChangeActionTestCase(AdminChangeActionTestCaseMixin[Certificate], WebTestMixin, TestCase): """Test the "resign" change action.""" diff --git a/ca/django_ca/tests/admin/test_add_cert.py b/ca/django_ca/tests/admin/test_add_cert.py index d19d1f775..e428e6bcb 100644 --- a/ca/django_ca/tests/admin/test_add_cert.py +++ b/ca/django_ca/tests/admin/test_add_cert.py @@ -43,8 +43,8 @@ from django_ca.models import Certificate, CertificateAuthority from django_ca.profiles import Profile, profiles from django_ca.tests.admin.base import AddCertificateSeleniumTestCase, CertificateModelAdminTestCaseMixin -from django_ca.tests.base import certs, dns, override_tmpcadir, timestamps, uri from django_ca.tests.base.assertions import assert_css +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.testcases import SeleniumTestCase from django_ca.tests.base.typehints import HttpResponse from django_ca.tests.base.utils import ( @@ -53,20 +53,23 @@ certificate_policies, crl_distribution_points, distribution_point, + dns, extended_key_usage, freshest_crl, issuer_alternative_name, key_usage, ocsp_no_check, + override_tmpcadir, subject_alternative_name, subject_key_identifier, tls_feature, + uri, ) from django_ca.typehints import SerializedExtension from django_ca.utils import ca_storage, x509_name -@freeze_time(timestamps["after_child"]) +@freeze_time(TIMESTAMPS["after_child"]) class AddCertificateTestCase(CertificateModelAdminTestCaseMixin, TestCase): """Tests for adding certificates.""" @@ -92,7 +95,7 @@ def setUp(self) -> None: def add_cert(self, cname: str, ca: CertificateAuthority, algorithm: str = "SHA-256") -> None: """Add certificate based on given name with given CA.""" - csr = certs["root-cert"]["csr"]["pem"] + csr = CERT_DATA["root-cert"]["csr"]["pem"] with self.assertCreateCertSignals() as (pre, post): response = self.client.post( @@ -215,7 +218,7 @@ def test_default_ca_key_does_not_exist(self) -> None: self.assertNotEqual(bound_field.initial, self.ca) self.assertIsInstance(bound_field.initial, CertificateAuthority) - @override_tmpcadir(CA_DEFAULT_CA=certs["child"]["serial"]) + @override_tmpcadir(CA_DEFAULT_CA=CERT_DATA["child"]["serial"]) def test_cas_expired(self) -> None: """Do a basic get request (to test CSS etc).""" self.ca.enabled = False @@ -263,7 +266,7 @@ def test_add(self) -> None: def test_empty_subject(self) -> None: """Test passing an empty subject with a subject alternative name.""" ca = self.cas["root"] - csr = certs["root-cert"]["csr"]["pem"] + csr = CERT_DATA["root-cert"]["csr"]["pem"] with self.assertCreateCertSignals() as (pre, post): response = self.client.post( @@ -305,7 +308,7 @@ def test_empty_subject(self) -> None: def test_subject_with_multiple_org_units(self) -> None: """Test creating a certificate with multiple Org Units (which is allowed).""" ca = self.cas["root"] - csr = certs["root-cert"]["csr"]["pem"] + csr = CERT_DATA["root-cert"]["csr"]["pem"] with self.assertCreateCertSignals() as (pre, post): response = self.client.post( @@ -357,7 +360,7 @@ def test_subject_with_multiple_org_units(self) -> None: def test_add_no_common_name_and_no_subject_alternative_name(self) -> None: """Test posting a subject with no common name and no subject alternative name.""" ca = self.cas["root"] - csr = certs["root-cert"]["csr"]["pem"] + csr = CERT_DATA["root-cert"]["csr"]["pem"] cert_count = Certificate.objects.all().count() with self.assertCreateCertSignals(False, False): @@ -401,7 +404,7 @@ def test_add_no_common_name_and_no_subject_alternative_name(self) -> None: def test_subject_with_multiple_country_codes(self) -> None: """Test creating a certificate with multiple country codes (which is not allowed).""" ca = self.cas["root"] - csr = certs["root-cert"]["csr"]["pem"] + csr = CERT_DATA["root-cert"]["csr"]["pem"] with self.assertCreateCertSignals(False, False): response = self.client.post( @@ -439,7 +442,7 @@ def test_subject_with_multiple_country_codes(self) -> None: def test_subject_with_invalid_country_code(self) -> None: """Test creating a certificate with an invalid country code.""" ca = self.cas["root"] - csr = certs["root-cert"]["csr"]["pem"] + csr = CERT_DATA["root-cert"]["csr"]["pem"] with self.assertCreateCertSignals(False, False): response = self.client.post( @@ -476,7 +479,7 @@ def test_subject_with_invalid_country_code(self) -> None: def test_add_no_key_usage(self) -> None: """Test adding a cert with no (extended) key usage.""" ca = self.cas["root"] - csr = certs["root-cert"]["csr"]["pem"] + csr = CERT_DATA["root-cert"]["csr"]["pem"] cname = "test-add2.example.com" san = "test-san.example.com" @@ -531,7 +534,7 @@ def test_add_no_key_usage(self) -> None: def test_add_with_password(self) -> None: """Test adding with a password.""" ca = self.cas["pwd"] - csr = certs["pwd-cert"]["csr"]["pem"] + csr = CERT_DATA["pwd-cert"]["csr"]["pem"] cname = "with-password.example.com" # first post without password @@ -640,7 +643,7 @@ def test_add_with_password(self) -> None: "authority_information_access_0": ca.issuer_url, "authority_information_access_1": ca.ocsp_url, "authority_information_access_2": False, - "password": certs["pwd"]["password"].decode("utf-8"), + "password": CERT_DATA["pwd"]["password"].decode("utf-8"), }, ) self.assertRedirects(response, self.changelist_url) @@ -773,7 +776,7 @@ def test_unparsable_csr(self) -> None: def test_expires_in_the_past(self) -> None: """Test creating a cert that expires in the past.""" ca = self.cas["root"] - csr = certs["pwd-cert"]["csr"]["pem"] + csr = CERT_DATA["pwd-cert"]["csr"]["pem"] cname = "test-expires-in-the-past.example.com" expires = datetime.now() - timedelta(days=3) @@ -819,7 +822,7 @@ def test_expires_in_the_past(self) -> None: def test_expires_too_late(self) -> None: """Test that creating a cert that expires after the CA expires throws an error.""" ca = self.cas["root"] - csr = certs["pwd-cert"]["csr"]["pem"] + csr = CERT_DATA["pwd-cert"]["csr"]["pem"] cname = "test-expires-too-late.example.com" expires = ca.expires + timedelta(days=3) correct_expires = ca.expires.strftime("%Y-%m-%d") @@ -870,7 +873,7 @@ def test_invalid_cn_in_san(self) -> None: cname = "Foo Bar" error = "The CommonName cannot be parsed as general name. Either change the CommonName or do not include it." # NOQA ca = self.cas["root"] - csr = certs["root-cert"]["csr"]["pem"] + csr = CERT_DATA["root-cert"]["csr"]["pem"] with self.assertCreateCertSignals(False, False): response = self.client.post( @@ -918,7 +921,7 @@ def test_invalid_signature_hash_algorithm(self) -> None: response = self.client.post( self.add_url, data={ - "csr": certs["ed448-cert"]["csr"]["pem"], + "csr": CERT_DATA["ed448-cert"]["csr"]["pem"], "ca": self.cas["ed448"].pk, "profile": "webserver", "subject_0": json.dumps( @@ -939,7 +942,7 @@ def test_invalid_signature_hash_algorithm(self) -> None: response = self.client.post( self.add_url, data={ - "csr": certs["ed25519-cert"]["csr"]["pem"], + "csr": CERT_DATA["ed25519-cert"]["csr"]["pem"], "ca": self.cas["ed25519"].pk, "profile": "webserver", "subject_0": json.dumps( @@ -960,7 +963,7 @@ def test_invalid_signature_hash_algorithm(self) -> None: response = self.client.post( self.add_url, data={ - "csr": certs["dsa-cert"]["csr"]["pem"], + "csr": CERT_DATA["dsa-cert"]["csr"]["pem"], "ca": self.cas["dsa"].pk, "profile": "webserver", "subject_0": json.dumps( @@ -981,7 +984,7 @@ def test_invalid_signature_hash_algorithm(self) -> None: response = self.client.post( self.add_url, data={ - "csr": certs["root-cert"]["csr"]["pem"], + "csr": CERT_DATA["root-cert"]["csr"]["pem"], "ca": self.cas["root"].pk, "profile": "webserver", "subject_0": json.dumps( @@ -1001,7 +1004,7 @@ def test_invalid_signature_hash_algorithm(self) -> None: def test_certificate_policies_with_invalid_oid(self) -> None: """Test posting a certificate policies extension with an invalid OID.""" ca = self.cas["root"] - csr = certs["root-cert"]["csr"]["pem"] + csr = CERT_DATA["root-cert"]["csr"]["pem"] cert_count = Certificate.objects.all().count() with self.assertCreateCertSignals(False, False): @@ -1034,7 +1037,7 @@ def test_certificate_policies_with_invalid_oid(self) -> None: def test_add_no_cas(self) -> None: """Test adding when all CAs are disabled.""" ca = self.cas["root"] - csr = certs["pwd-cert"]["csr"]["pem"] + csr = CERT_DATA["pwd-cert"]["csr"]["pem"] CertificateAuthority.objects.update(enabled=False) response = self.client.get(self.add_url) self.assertEqual(response.status_code, HTTPStatus.FORBIDDEN) @@ -1068,7 +1071,7 @@ def test_add_no_cas(self) -> None: def test_add_unusable_cas(self) -> None: """Try adding with an unusable CA.""" ca = self.cas["root"] - csr = certs["pwd-cert"]["csr"]["pem"] + csr = CERT_DATA["pwd-cert"]["csr"]["pem"] CertificateAuthority.objects.update(private_key_path="not/exist/add-unusable-cas") # check that we have some enabled CAs, just to make sure this test is really useful @@ -1369,7 +1372,7 @@ def test_csr_integration(self) -> None: self.assertIs(has_content.is_displayed(), False) self.assertIs(no_content.is_displayed(), False) - cert = certs["all-extensions"] + cert = CERT_DATA["all-extensions"] csr = self.find("textarea#id_csr") csr.send_keys(cert["csr"]["pem"]) @@ -1404,7 +1407,7 @@ def test_paste_csr_no_subject(self) -> None: self.initialize() # Create a CSR with no subject - key = certs["all-extensions"]["key"]["parsed"] + key = CERT_DATA["all-extensions"]["key"]["parsed"] csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([])).sign(key, hashes.SHA256()) csr_pem = csr.public_bytes(serialization.Encoding.PEM).decode() @@ -1440,7 +1443,7 @@ def test_paste_csr_missing_delimiters(self) -> None: """Test that pasting a CSR shows text next to subject input fields.""" self.initialize() - cert = certs["all-extensions"] + cert = CERT_DATA["all-extensions"] csr = self.find("textarea#id_csr") # Elements of the CSR chapter @@ -1581,7 +1584,7 @@ def test_profile_integration(self) -> None: self.assertEqual(self.displayed_value, []) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AddCertificateWebTestTestCase(CertificateModelAdminTestCaseMixin, WebTestMixin, TestCase): """Tests for adding certificates.""" @@ -1607,7 +1610,7 @@ def test_empty_form_and_empty_cert(self) -> None: # Fill in the bare minimum fields form = response.forms["certificate_form"] - form["csr"] = certs["child-cert"]["csr"]["pem"] + form["csr"] = CERT_DATA["child-cert"]["csr"]["pem"] form["subject_0"] = json.dumps( [{"key": NameOID.COMMON_NAME.dotted_string, "value": "test-empty-form.example.com"}] ) @@ -1640,7 +1643,7 @@ def test_none_extension_and_subject_alternative_name_extension(self) -> None: """Test how saving the model behaves when profile has None-extension or SubjectAlternativeName.""" response = self.app.get(self.add_url, user=self.user.username) form = response.forms["certificate_form"] - form["csr"] = certs["child-cert"]["csr"]["pem"] + form["csr"] = CERT_DATA["child-cert"]["csr"]["pem"] form["subject_0"] = json.dumps([{"key": NameOID.COMMON_NAME.dotted_string, "value": self.hostname}]) response = form.submit().follow() self.assertEqual(response.status_code, 200) @@ -1686,7 +1689,7 @@ def test_only_ca_prefill(self) -> None: response = self.app.get(self.add_url, user=self.user.username) form = response.forms["certificate_form"] - form["csr"] = certs["child-cert"]["csr"]["pem"] + form["csr"] = CERT_DATA["child-cert"]["csr"]["pem"] form["subject_0"] = json.dumps([{"key": NameOID.COMMON_NAME.dotted_string, "value": cn}]) response = form.submit().follow() self.assertEqual(response.status_code, 200) @@ -1788,7 +1791,7 @@ def test_full_profile_prefill(self) -> None: # default value for form field is on import time, so override settings does not change # profile field form["profile"] = "everything" - form["csr"] = certs["child-cert"]["csr"]["pem"] + form["csr"] = CERT_DATA["child-cert"]["csr"]["pem"] form["subject_0"] = json.dumps([{"key": NameOID.COMMON_NAME.dotted_string, "value": cn}]) response = form.submit().follow() self.assertEqual(response.status_code, 200) @@ -1901,7 +1904,7 @@ def test_multiple_distribution_points(self) -> None: # default value for form field is on import time, so override settings does not change # profile field form["profile"] = "everything" - form["csr"] = certs["child-cert"]["csr"]["pem"] + form["csr"] = CERT_DATA["child-cert"]["csr"]["pem"] form["subject_0"] = json.dumps([{"key": NameOID.COMMON_NAME.dotted_string, "value": cn}]) response = form.submit() response = response.follow() diff --git a/ca/django_ca/tests/admin/test_extra_views.py b/ca/django_ca/tests/admin/test_extra_views.py index cae1d8ad3..b65e50568 100644 --- a/ca/django_ca/tests/admin/test_extra_views.py +++ b/ca/django_ca/tests/admin/test_extra_views.py @@ -34,7 +34,8 @@ from django_ca import ca_settings, constants from django_ca.models import CertificateAuthority from django_ca.tests.admin.base import CertificateModelAdminTestCaseMixin -from django_ca.tests.base import certs, override_tmpcadir, timestamps +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS +from django_ca.tests.base.utils import override_tmpcadir from django_ca.utils import serialize_name, x509_name @@ -42,7 +43,7 @@ class CSRDetailTestCase(CertificateModelAdminTestCaseMixin, TestCase): """Test the CSR detail view.""" url = reverse("admin:django_ca_certificate_csr_details") - csr_pem = certs["root-cert"]["csr"]["pem"] + csr_pem = CERT_DATA["root-cert"]["csr"]["pem"] @classmethod def create_csr( @@ -60,7 +61,7 @@ def create_csr( def test_basic(self) -> None: """Test a basic CSR info retrieval.""" - for cert_data in [v for v in certs.values() if v["type"] == "cert" and v["cat"] == "generated"]: + for cert_data in [v for v in CERT_DATA.values() if v["type"] == "cert" and v["cat"] == "generated"]: response = self.client.post( self.url, data=json.dumps({"csr": cert_data["csr"]["pem"]}), content_type="application/json" ) @@ -163,7 +164,7 @@ class CADetailsViewTestCase(CertificateModelAdminTestCaseMixin, TestCase): url = reverse("admin:django_ca_certificate_ca_details") @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_basic(self) -> None: """Test fetching CA with all kinds of URLs.""" self.ca.issuer_url = "http://issuer.child.example.com" @@ -224,7 +225,7 @@ def test_basic(self) -> None: ) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_empty_ca(self) -> None: """Test fetching CA with no URLs.""" self.ca.issuer_url = "" diff --git a/ca/django_ca/tests/admin/test_views.py b/ca/django_ca/tests/admin/test_views.py index 8ef28e3ad..3f6aa2719 100644 --- a/ca/django_ca/tests/admin/test_views.py +++ b/ca/django_ca/tests/admin/test_views.py @@ -24,8 +24,8 @@ from django_ca.constants import ReasonFlags from django_ca.models import Certificate, Watcher -from django_ca.tests.base import timestamps from django_ca.tests.base.assertions import assert_change_response, assert_changelist_response +from django_ca.tests.base.constants import TIMESTAMPS from django_ca.tests.base.typehints import HttpResponse @@ -162,7 +162,7 @@ def test_change_view_with_no_subject_alternative_name( assertContains(response, text=html, html=True) # type: ignore[arg-type] -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_changelist_autogenerated_filter(admin_client: Client, root_cert: Certificate) -> None: """Test :py:class:`~django_ca.admin.AutoGeneratedFilter`.""" response = admin_client.get(Certificate.admin_changelist_url) @@ -191,7 +191,7 @@ def test_changelist_status_filter( child_cert.revoke(ReasonFlags.unspecified) child_cert.save() - with freeze_time(timestamps["everything_valid"]): + with freeze_time(TIMESTAMPS["everything_valid"]): response = admin_client.get(Certificate.admin_changelist_url) assert_changelist_response(response, root_cert) response = admin_client.get(Certificate.admin_changelist_url, {"status": "expired"}) @@ -201,7 +201,7 @@ def test_changelist_status_filter( response = admin_client.get(Certificate.admin_changelist_url, {"status": "all"}) assert_changelist_response(response, root_cert, child_cert) - with freeze_time(timestamps["everything_expired"]): + with freeze_time(TIMESTAMPS["everything_expired"]): admin_client.force_login(admin_user) response = admin_client.get(Certificate.admin_changelist_url) assert_changelist_response(response) diff --git a/ca/django_ca/tests/api/test_list_cas.py b/ca/django_ca/tests/api/test_list_cas.py index 5763626aa..ea81419a8 100644 --- a/ca/django_ca/tests/api/test_list_cas.py +++ b/ca/django_ca/tests/api/test_list_cas.py @@ -26,7 +26,7 @@ from django_ca.models import CertificateAuthority from django_ca.tests.api.conftest import APIPermissionTestBase, ListResponse -from django_ca.tests.base import timestamps +from django_ca.tests.base.constants import TIMESTAMPS from django_ca.tests.base.typehints import HttpResponse path = reverse_lazy("django_ca:api:list_certificate_authorities") @@ -57,7 +57,7 @@ def test_empty_list_view(api_client: Client) -> None: assert response.json() == [] -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_list_view(api_client: Client, expected_response: ListResponse) -> None: """Test an ordinary list view.""" response = request(api_client) @@ -65,7 +65,7 @@ def test_list_view(api_client: Client, expected_response: ListResponse) -> None: assert response.json() == expected_response, response.json() -@freeze_time(timestamps["everything_expired"]) +@freeze_time(TIMESTAMPS["everything_expired"]) def test_expired_certificate_authorities_are_excluded(api_client: Client) -> None: """Test that expired CAs are excluded by default.""" response = request(api_client) @@ -73,7 +73,7 @@ def test_expired_certificate_authorities_are_excluded(api_client: Client) -> Non assert response.json() == [], response.json() -@freeze_time(timestamps["everything_expired"]) +@freeze_time(TIMESTAMPS["everything_expired"]) def test_expired_filter(api_client: Client, expected_response: ListResponse) -> None: """Test that expired CAs are excluded by default.""" response = request(api_client, {"expired": "1"}) @@ -81,7 +81,7 @@ def test_expired_filter(api_client: Client, expected_response: ListResponse) -> assert response.json() == expected_response, response.json() -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_disabled_ca(api_client: Client, root: CertificateAuthority) -> None: """Test that a disabled CA is *not* included.""" root.enabled = False diff --git a/ca/django_ca/tests/api/test_list_certs.py b/ca/django_ca/tests/api/test_list_certs.py index de9cb8dfc..47a1795e5 100644 --- a/ca/django_ca/tests/api/test_list_certs.py +++ b/ca/django_ca/tests/api/test_list_certs.py @@ -27,11 +27,10 @@ from django_ca.models import Certificate from django_ca.tests.api.conftest import APIPermissionTestBase, ListResponse -from django_ca.tests.base import timestamps -from django_ca.tests.base.conftest_helpers import certs +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.utils import iso_format -path = reverse_lazy("django_ca:api:list_certificates", kwargs={"serial": certs["root"]["serial"]}) +path = reverse_lazy("django_ca:api:list_certificates", kwargs={"serial": CERT_DATA["root"]["serial"]}) @pytest.fixture(scope="module") @@ -55,7 +54,7 @@ def test_empty_list_view(api_client: Client) -> None: assert response.json() == [], response.json() -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_list_view(api_client: Client, expected_response: ListResponse) -> None: """Test an ordinary list view.""" response = api_client.get(path) @@ -64,7 +63,7 @@ def test_list_view(api_client: Client, expected_response: ListResponse) -> None: @pytest.mark.usefixtures("root_cert") -@freeze_time(timestamps["everything_expired"]) +@freeze_time(TIMESTAMPS["everything_expired"]) def test_expired_certificates_are_excluded(api_client: Client) -> None: """Test that expired certificates are excluded by default.""" response = api_client.get(path) @@ -72,7 +71,7 @@ def test_expired_certificates_are_excluded(api_client: Client) -> None: assert response.json() == [], response.json() -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_autogenerated_are_excluded(api_client: Client, root_cert: Certificate) -> None: """Test that auto-generated certificates are excluded by default.""" root_cert.autogenerated = True @@ -83,7 +82,7 @@ def test_autogenerated_are_excluded(api_client: Client, root_cert: Certificate) assert response.json() == [], response.json() -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_revoked_are_excluded(api_client: Client, root_cert: Certificate) -> None: """Test that revoked certificates are excluded by default.""" root_cert.revoke() @@ -93,7 +92,7 @@ def test_revoked_are_excluded(api_client: Client, root_cert: Certificate) -> Non assert response.json() == [], response.json() -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_autogenerated_filter( api_client: Client, root_cert: Certificate, expected_response: ListResponse ) -> None: @@ -108,7 +107,7 @@ def test_autogenerated_filter( assert response.json() == expected_response, response.json() -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_profile_filter(api_client: Client, root_cert: Certificate, expected_response: ListResponse) -> None: """Test the `profile` filter.""" root_cert.profile = "webserver" @@ -126,7 +125,7 @@ def test_profile_filter(api_client: Client, root_cert: Certificate, expected_res assert response.json() == [], response.json() -@freeze_time(timestamps["everything_expired"]) +@freeze_time(TIMESTAMPS["everything_expired"]) def test_expired_filter(api_client: Client, expected_response: ListResponse) -> None: """Test the `expired` filter.""" response = api_client.get(path, {"expired": "1"}) @@ -134,7 +133,7 @@ def test_expired_filter(api_client: Client, expected_response: ListResponse) -> assert response.json() == expected_response, response.json() -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_revoked_filter(api_client: Client, root_cert: Certificate, expected_response: ListResponse) -> None: """Test the `revoked` filter.""" root_cert.revoke() @@ -146,7 +145,7 @@ def test_revoked_filter(api_client: Client, root_cert: Certificate, expected_res assert response.json() == expected_response, response.json() -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_disabled_ca(api_client: Client, root_cert: Certificate) -> None: """Test that certificates for a disabled can *not* be viewed.""" root_cert.ca.enabled = False diff --git a/ca/django_ca/tests/api/test_revoke_cert.py b/ca/django_ca/tests/api/test_revoke_cert.py index 081ece0af..a90e19c7a 100644 --- a/ca/django_ca/tests/api/test_revoke_cert.py +++ b/ca/django_ca/tests/api/test_revoke_cert.py @@ -27,14 +27,13 @@ from django_ca.models import Certificate from django_ca.tests.api.conftest import APIPermissionTestBase, DetailResponse -from django_ca.tests.base import timestamps -from django_ca.tests.base.conftest_helpers import certs +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.typehints import HttpResponse from django_ca.tests.base.utils import iso_format path = reverse_lazy( "django_ca:api:revoke_certificate", - kwargs={"serial": certs["root"]["serial"], "certificate_serial": certs["root-cert"]["serial"]}, + kwargs={"serial": CERT_DATA["root"]["serial"], "certificate_serial": CERT_DATA["root-cert"]["serial"]}, ) @@ -51,7 +50,7 @@ def expected_response(root_cert_response: Dict[str, Any]) -> DetailResponse: return root_cert_response -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_revoke_view(root_cert: Certificate, api_client: Client, expected_response: DetailResponse) -> None: """Test an ordinary certificate revocation.""" response = api_client.post(path, {}, content_type="application/json") @@ -64,7 +63,7 @@ def test_revoke_view(root_cert: Certificate, api_client: Client, expected_respon assert root_cert.compromised is None -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_revoke_with_parameters( root_cert: Certificate, api_client: Client, expected_response: DetailResponse ) -> None: @@ -87,7 +86,7 @@ def test_revoke_with_parameters( assert root_cert.compromised == now -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_revoked_certificate_fails(root_cert: Certificate, api_client: Client) -> None: """Test that revoking a revoked certificate fails.""" root_cert.revoke() @@ -97,7 +96,7 @@ def test_revoked_certificate_fails(root_cert: Certificate, api_client: Client) - assert response.json() == {"detail": "The certificate is already revoked."}, response.json() -@freeze_time(timestamps["everything_expired"]) +@freeze_time(TIMESTAMPS["everything_expired"]) def test_cannot_revoke_expired_certificate(root_cert: Certificate, api_client: Client) -> None: """Test that we cannot revoke a certificate if it is expired.""" response = api_client.post(path, {}, content_type="application/json") @@ -108,7 +107,7 @@ def test_cannot_revoke_expired_certificate(root_cert: Certificate, api_client: C assert root_cert.revoked is False # cert is still not revoked (just expired) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) @pytest.mark.usefixtures("root") # CA should exist, but certificate does not def test_certificate_not_found(api_client: Client) -> None: """Test response when a certificate was not found.""" @@ -117,7 +116,7 @@ def test_certificate_not_found(api_client: Client) -> None: assert response.json() == {"detail": "Not Found"}, response.json() -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_disabled_ca(root_cert: Certificate, api_client: Client) -> None: """Test that certificates for a disabled can *not* be viewed.""" root_cert.ca.enabled = False diff --git a/ca/django_ca/tests/api/test_sign_cert.py b/ca/django_ca/tests/api/test_sign_cert.py index 568f8039e..57cf06496 100644 --- a/ca/django_ca/tests/api/test_sign_cert.py +++ b/ca/django_ca/tests/api/test_sign_cert.py @@ -35,24 +35,27 @@ from django_ca import ca_settings, constants from django_ca.models import Certificate, CertificateAuthority from django_ca.tests.api.conftest import APIPermissionTestBase -from django_ca.tests.base import dns, ip, rdn, timestamps, uri -from django_ca.tests.base.conftest_helpers import certs +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.typehints import HttpResponse from django_ca.tests.base.utils import ( authority_information_access, certificate_policies, crl_distribution_points, distribution_point, + dns, extended_key_usage, freshest_crl, + ip, iso_format, key_usage, ocsp_no_check, + rdn, subject_alternative_name, tls_feature, + uri, ) -path = reverse_lazy("django_ca:api:sign_certificate", kwargs={"serial": certs["root"]["serial"]}) +path = reverse_lazy("django_ca:api:sign_certificate", kwargs={"serial": CERT_DATA["root"]["serial"]}) default_subject = [{"oid": NameOID.COMMON_NAME.dotted_string, "value": "api.example.com"}] @@ -67,14 +70,14 @@ def expected_response(root: CertificateAuthority) -> Dict[str, Any]: """Fixture for the non-dynamic parts of the expected response.""" return { "autogenerated": False, - "created": iso_format(timestamps["everything_valid"]), + "created": iso_format(TIMESTAMPS["everything_valid"]), "issuer": [{"oid": attr.oid.dotted_string, "value": attr.value} for attr in root.issuer], - "not_after": iso_format(timestamps["everything_valid"] + ca_settings.CA_DEFAULT_EXPIRES), - "not_before": iso_format(timestamps["everything_valid"]), + "not_after": iso_format(TIMESTAMPS["everything_valid"] + ca_settings.CA_DEFAULT_EXPIRES), + "not_before": iso_format(TIMESTAMPS["everything_valid"]), "profile": ca_settings.CA_DEFAULT_PROFILE, "revoked": False, "subject": default_subject, - "updated": iso_format(timestamps["everything_valid"]), + "updated": iso_format(TIMESTAMPS["everything_valid"]), } @@ -83,12 +86,12 @@ def request(client: Client, data: Dict[str, Any]) -> HttpResponse: return client.post(path, data, content_type="application/json") -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_sign_ca_values( api_client: Client, usable_root: CertificateAuthority, expected_response: Dict[str, Any] ) -> None: """Test that CA extensions are added.""" - response = request(api_client, {"csr": certs["root-cert"]["csr"]["pem"], "subject": default_subject}) + response = request(api_client, {"csr": CERT_DATA["root-cert"]["csr"]["pem"], "subject": default_subject}) assert response.status_code == HTTPStatus.OK, response.content # Get certificate and validate some properties @@ -115,7 +118,7 @@ def test_sign_ca_values( assert response.json() == expected_response, response.json() -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_sign_certificate_with_parameters( api_client: Client, usable_root: CertificateAuthority, @@ -129,7 +132,7 @@ def test_sign_certificate_with_parameters( response = request( api_client, { - "csr": certs["root-cert"]["csr"]["pem"], + "csr": CERT_DATA["root-cert"]["csr"]["pem"], "subject": default_subject, "autogenerated": True, "profile": "server", @@ -152,7 +155,7 @@ def test_sign_certificate_with_parameters( assert response.json() == expected_response, response.json() -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_sign_certificate_with_extensions( api_client: Client, usable_root: CertificateAuthority, expected_response: Dict[str, Any] ) -> None: @@ -160,7 +163,7 @@ def test_sign_certificate_with_extensions( response = request( api_client, { - "csr": certs["root-cert"]["csr"]["pem"], + "csr": CERT_DATA["root-cert"]["csr"]["pem"], "subject": default_subject, "extensions": { "authority_information_access": { @@ -283,7 +286,7 @@ def test_sign_certificate_with_extensions( assert exts[ExtensionOID.TLS_FEATURE] == tls_feature(x509.TLSFeatureType.status_request) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_sign_certificate_with_subject_alternative_name( api_client: Client, usable_root: CertificateAuthority, expected_response: Dict[str, Any] ) -> None: @@ -291,7 +294,7 @@ def test_sign_certificate_with_subject_alternative_name( response = request( api_client, { - "csr": certs["root-cert"]["csr"]["pem"], + "csr": CERT_DATA["root-cert"]["csr"]["pem"], "subject": default_subject, "extensions": { "subject_alternative_name": { @@ -324,13 +327,13 @@ def test_sign_certificate_with_subject_alternative_name( @pytest.mark.usefixtures("tmpcadir") -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_crldp_with_full_name_and_relative_name(api_client: Client) -> None: """Test sending a CRL Distribution point with a full_name and a relative_name.""" response = request( api_client, { - "csr": certs["root-cert"]["csr"]["pem"], + "csr": CERT_DATA["root-cert"]["csr"]["pem"], "subject": default_subject, "extensions": { "crl_distribution_points": { @@ -365,13 +368,13 @@ def test_crldp_with_full_name_and_relative_name(api_client: Client) -> None: @pytest.mark.usefixtures("tmpcadir") -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_crldp_with_no_full_name_or_relative_name(api_client: Client) -> None: """Test sending a CRL Distribution point with neither a full name nor a relative name.""" response = request( api_client, { - "csr": certs["root-cert"]["csr"]["pem"], + "csr": CERT_DATA["root-cert"]["csr"]["pem"], "subject": default_subject, "extensions": { "crl_distribution_points": { @@ -403,13 +406,13 @@ def test_crldp_with_no_full_name_or_relative_name(api_client: Client) -> None: @pytest.mark.usefixtures("tmpcadir") -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_with_invalid_key_usage(api_client: Client) -> None: """Test sending an invalid key usage.""" response = request( api_client, { - "csr": certs["root-cert"]["csr"]["pem"], + "csr": CERT_DATA["root-cert"]["csr"]["pem"], "subject": default_subject, "extensions": {"key_usage": {"value": ["unknown"]}}, }, @@ -427,19 +430,19 @@ def test_with_invalid_key_usage(api_client: Client) -> None: @pytest.mark.usefixtures("tmpcadir", "usable_root") -@freeze_time(timestamps["everything_expired"]) +@freeze_time(TIMESTAMPS["everything_expired"]) def test_expired_ca(api_client: Client) -> None: """Test that you can *not* sign a certificate for an expired CA.""" - response = request(api_client, {"csr": certs["root-cert"]["csr"]["pem"], "subject": default_subject}) + response = request(api_client, {"csr": CERT_DATA["root-cert"]["csr"]["pem"], "subject": default_subject}) assert response.status_code == HTTPStatus.NOT_FOUND, response.content assert response.json() == {"detail": "Not Found"}, response.json() @pytest.mark.usefixtures("root") -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_private_key_unavailable(api_client: Client) -> None: """Test the error when no private key is available.""" - response = request(api_client, {"csr": certs["root-cert"]["csr"]["pem"], "subject": default_subject}) + response = request(api_client, {"csr": CERT_DATA["root-cert"]["csr"]["pem"], "subject": default_subject}) assert response.status_code == HTTPStatus.BAD_REQUEST, response.content assert response.json() == { "detail": "This certificate authority can not be used to sign certificates via the API." @@ -447,13 +450,13 @@ def test_private_key_unavailable(api_client: Client) -> None: @pytest.mark.usefixtures("tmpcadir") -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_disabled_ca(api_client: Client, root: CertificateAuthority) -> None: """Test that you cannot sign a certificate for a disabled CA.""" root.enabled = False root.save() - response = request(api_client, {"csr": certs["root-cert"]["csr"]["pem"], "subject": default_subject}) + response = request(api_client, {"csr": CERT_DATA["root-cert"]["csr"]["pem"], "subject": default_subject}) assert response.status_code == HTTPStatus.NOT_FOUND, response.content assert response.json() == {"detail": "Not Found"}, response.json() @@ -464,4 +467,4 @@ class TestPermissions(APIPermissionTestBase): path = path def request(self, client: Client) -> HttpResponse: - return request(client, {"csr": certs["root-cert"]["csr"]["pem"], "subject": default_subject}) + return request(client, {"csr": CERT_DATA["root-cert"]["csr"]["pem"], "subject": default_subject}) diff --git a/ca/django_ca/tests/api/test_update_ca.py b/ca/django_ca/tests/api/test_update_ca.py index 09cba0017..b2d6a0772 100644 --- a/ca/django_ca/tests/api/test_update_ca.py +++ b/ca/django_ca/tests/api/test_update_ca.py @@ -30,13 +30,14 @@ from django_ca import constants from django_ca.models import CertificateAuthority from django_ca.tests.api.conftest import APIPermissionTestBase -from django_ca.tests.base import timestamps -from django_ca.tests.base.conftest_helpers import certs +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.typehints import HttpResponse from django_ca.tests.base.utils import certificate_policies, iso_format from django_ca.typehints import JSON -path = reverse_lazy("django_ca:api:update_certificate_authority", kwargs={"serial": certs["root"]["serial"]}) +path = reverse_lazy( + "django_ca:api:update_certificate_authority", kwargs={"serial": CERT_DATA["root"]["serial"]} +) def request(client: Client, payload: Optional[Dict[str, JSON]]) -> "HttpResponse": @@ -83,20 +84,20 @@ def expected_response(root: CertificateAuthority, payload: Dict[str, Any]) -> Di "issuer": [{"oid": attr.oid.dotted_string, "value": attr.value} for attr in root.issuer], "not_after": iso_format(root.expires), "not_before": iso_format(root.valid_from), - "pem": certs["root"]["pub"]["pem"], + "pem": CERT_DATA["root"]["pub"]["pem"], "revoked": False, - "serial": certs["root"]["serial"], + "serial": CERT_DATA["root"]["serial"], "sign_certificate_policies": { "critical": constants.EXTENSION_DEFAULT_CRITICAL[ExtensionOID.CERTIFICATE_POLICIES], "value": [{"policy_identifier": "1.1.1", "policy_qualifiers": None}], }, "subject": [{"oid": attr.oid.dotted_string, "value": attr.value} for attr in root.subject], - "updated": iso_format(timestamps["everything_valid"]), + "updated": iso_format(TIMESTAMPS["everything_valid"]), }, ) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_update( root: CertificateAuthority, api_client: Client, payload: Dict[str, Any], expected_response: Dict[str, Any] ) -> None: @@ -126,7 +127,7 @@ def test_update( assert expected == actual -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_minimal_update( root: CertificateAuthority, api_client: Client, payload: Dict[str, Any], expected_response: Dict[str, Any] ) -> None: @@ -146,7 +147,7 @@ def test_minimal_update( assert root.ocsp_responder_key_validity == 10 -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_validation( root: CertificateAuthority, api_client: Client, payload: Dict[str, Any], expected_response: Dict[str, Any] ) -> None: @@ -166,18 +167,18 @@ def test_validation( assert root.ocsp_responder_key_validity == refetched_root.ocsp_responder_key_validity -@freeze_time(timestamps["everything_expired"]) +@freeze_time(TIMESTAMPS["everything_expired"]) def test_update_expired_ca( api_client: Client, payload: Dict[str, Any], expected_response: Dict[str, Any] ) -> None: """Test that we can update an expired CA.""" - expected_response["updated"] = iso_format(timestamps["everything_expired"]) + expected_response["updated"] = iso_format(TIMESTAMPS["everything_expired"]) response = request(api_client, payload) assert response.status_code == HTTPStatus.OK, response.content assert response.json() == expected_response, response.json() -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_disabled_ca(root: CertificateAuthority, api_client: Client, payload: Dict[str, Any]) -> None: """Test that a disabled CA is *not* updatable.""" root.enabled = False diff --git a/ca/django_ca/tests/api/test_view_ca.py b/ca/django_ca/tests/api/test_view_ca.py index 8ede20e74..aa26a2e3e 100644 --- a/ca/django_ca/tests/api/test_view_ca.py +++ b/ca/django_ca/tests/api/test_view_ca.py @@ -24,10 +24,11 @@ from django_ca.models import CertificateAuthority from django_ca.tests.api.conftest import APIPermissionTestBase -from django_ca.tests.base import timestamps -from django_ca.tests.base.conftest_helpers import certs +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS -path = reverse_lazy("django_ca:api:view_certificate_authority", kwargs={"serial": certs["root"]["serial"]}) +path = reverse_lazy( + "django_ca:api:view_certificate_authority", kwargs={"serial": CERT_DATA["root"]["serial"]} +) @pytest.fixture(scope="module") @@ -36,7 +37,7 @@ def api_permission() -> Tuple[Type[Model], str]: return CertificateAuthority, "view_certificateauthority" -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_view(api_client: Client, root_response: Dict[str, Any]) -> None: """Test an ordinary view.""" response = api_client.get(path) @@ -44,7 +45,7 @@ def test_view(api_client: Client, root_response: Dict[str, Any]) -> None: assert response.json() == root_response, response.json() -@freeze_time(timestamps["everything_expired"]) +@freeze_time(TIMESTAMPS["everything_expired"]) def test_view_expired_ca(api_client: Client, root_response: Dict[str, Any]) -> None: """Test that we can view an expired CA.""" response = api_client.get(path) @@ -52,7 +53,7 @@ def test_view_expired_ca(api_client: Client, root_response: Dict[str, Any]) -> N assert response.json() == root_response, response.json() -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_disabled_ca(root: CertificateAuthority, api_client: Client) -> None: """Test that a disabled CA is *not* viewable.""" root.enabled = False diff --git a/ca/django_ca/tests/api/test_view_cert.py b/ca/django_ca/tests/api/test_view_cert.py index 5856ccd75..b5a7aea8a 100644 --- a/ca/django_ca/tests/api/test_view_cert.py +++ b/ca/django_ca/tests/api/test_view_cert.py @@ -24,11 +24,11 @@ from django_ca.models import Certificate, CertificateAuthority from django_ca.tests.api.conftest import APIPermissionTestBase -from django_ca.tests.base import certs, timestamps +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS path = reverse_lazy( "django_ca:api:view_certificate", - kwargs={"serial": certs["root"]["serial"], "certificate_serial": certs["root-cert"]["serial"]}, + kwargs={"serial": CERT_DATA["root"]["serial"], "certificate_serial": CERT_DATA["root-cert"]["serial"]}, ) @@ -38,7 +38,7 @@ def api_permission() -> Tuple[Type[Model], str]: return Certificate, "view_certificate" -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_detail_view(api_client: Client, root_cert_response: Dict[str, Any]) -> None: """Test an ordinary detail view.""" response = api_client.get(path) @@ -46,7 +46,7 @@ def test_detail_view(api_client: Client, root_cert_response: Dict[str, Any]) -> assert response.json() == root_cert_response, response.json() -@freeze_time(timestamps["everything_expired"]) +@freeze_time(TIMESTAMPS["everything_expired"]) def test_expired_certificate(api_client: Client, root_cert_response: Dict[str, Any]) -> None: """Test that we can view the certificate even if it is expired.""" response = api_client.get(path) @@ -54,7 +54,7 @@ def test_expired_certificate(api_client: Client, root_cert_response: Dict[str, A assert response.json() == root_cert_response, response.json() -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) def test_disabled_ca(root: CertificateAuthority, api_client: Client) -> None: """Test that certificates for a disabled can *not* be viewed.""" root.enabled = False diff --git a/ca/django_ca/tests/base/__init__.py b/ca/django_ca/tests/base/__init__.py index 6600d3f93..08bd6d714 100644 --- a/ca/django_ca/tests/base/__init__.py +++ b/ca/django_ca/tests/base/__init__.py @@ -12,457 +12,3 @@ # . """TestCase base classes that preload some data and add common helper methods.""" - -import inspect -import ipaddress -import json -import os -import re -import shutil -import tempfile -import typing -from contextlib import contextmanager -from datetime import datetime, timedelta, timezone as tz -from typing import Any, Dict, Iterable, Iterator, Tuple, Union -from unittest.mock import patch - -import cryptography -from cryptography import x509 -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric.types import CertificateIssuerPrivateKeyTypes - -from django.conf import settings -from django.test.utils import override_settings -from django.utils import timezone - -from django_ca.constants import EXTENSION_KEY_OIDS -from django_ca.extensions import parse_extension -from django_ca.profiles import profiles -from django_ca.tests.base.constants import ( - CRYPTOGRAPHY_VERSION, - DOC_DIR, - FIXTURES_DIR, - GECKODRIVER_PATH, - RUN_SELENIUM_TESTS, -) -from django_ca.utils import add_colons, ca_storage, format_name - -FuncTypeVar = typing.TypeVar("FuncTypeVar", bound=typing.Callable[..., Any]) -KeyDict = typing.TypedDict("KeyDict", {"pem": str, "parsed": CertificateIssuerPrivateKeyTypes}) -CsrDict = typing.TypedDict("CsrDict", {"pem": str, "parsed": x509.CertificateSigningRequest}) -_PubDict = typing.TypedDict("_PubDict", {"pem": str, "parsed": x509.Certificate}) - - -# Regex used by certbot to split PEM-encoded certificate chains/bundles as of 2022-01-23. See also: -# https://github.com/certbot/certbot/blob/master/certbot/certbot/crypto_util.py -CERT_PEM_REGEX = re.compile( - b"""-----BEGIN CERTIFICATE-----\r? -.+?\r? ------END CERTIFICATE-----\r? -""", - re.DOTALL, # DOTALL (/s) because the base64text may include newlines -) - - -# pylint: disable-next=inherit-non-class; False positive -class PubDict(_PubDict, total=False): - """TypedDict for the pub key of certs.""" - - der: bytes - - -def _load_key(data: Dict[Any, Any]) -> KeyDict: - with open(os.path.join(FIXTURES_DIR, data["key_filename"]), "rb") as stream: - raw = stream.read() - - parsed = serialization.load_pem_private_key( - raw, password=data.get("password"), unsafe_skip_rsa_key_validation=True - ) - - return { - "pem": raw.decode("utf-8"), - "parsed": parsed, # type: ignore[typeddict-item] # we do not support all key types - } - - -def _load_csr(data: Dict[Any, Any]) -> CsrDict: - with open(os.path.join(FIXTURES_DIR, data["csr_filename"]), "rb") as stream: - raw = stream.read() - - parsed = x509.load_pem_x509_csr(raw) - return { - "pem": raw.decode("utf-8"), - "parsed": parsed, - } - - -def _load_pub(data: Dict[Any, Any]) -> PubDict: - # basedir is set for certificates in docs/source/_files - basedir = data.get("basedir", FIXTURES_DIR) - path = os.path.join(basedir, data["pub_filename"]) - - with open(path, "rb") as stream: - pem = stream.read() - - pub_data: PubDict = { - "pem": pem.decode("utf-8"), - "parsed": x509.load_pem_x509_certificate(pem), - } - - if der_filename := data.get("pub_der_filename"): - with open(os.path.join(basedir, der_filename), "rb") as stream: - der = stream.read() - pub_data["der"] = der - - return pub_data - - -cryptography_version = tuple(int(t) for t in cryptography.__version__.split(".")[:2]) - -with open(os.path.join(FIXTURES_DIR, "cert-data.json"), encoding="utf-8") as cert_data_stream: - _fixture_data = json.load(cert_data_stream) -certs = _fixture_data.get("certs") - -# Update some data from contrib (data is not in cert-data.json, since we don't generate them) -certs["multiple_ous"] = { - "name": "multiple_ous", - "subject": [ - ["C", "US"], - ["O", "VeriSign, Inc."], - ["OU", "Class 3 Public Primary Certification Authority - G2"], - ["OU", "(c) 1998 VeriSign, Inc. - For authorized use only"], - ["OU", "VeriSign Trust Network"], - ], - "subject_str": "/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority - G2/OU=(c) 1998 VeriSign, Inc. - For authorized use only/OU=VeriSign Trust Network", # noqa: E501 - "cn": "", - "key_filename": False, - "csr_filename": False, - "pub_filename": os.path.join("contrib", "multiple_ous_and_no_ext.pem"), - "key_type": "RSA", - "cat": "contrib", - "type": "cert", - "valid_from": "1998-05-18 00:00:00", - "valid_until": "2028-08-01 23:59:59", - "ca": "root", - "serial": "7DD9FE07CFA81EB7107967FBA78934C6", - "md5": "A2:33:9B:4C:74:78:73:D4:6C:E7:C1:F3:8D:CB:5C:E9", - "sha1": "85:37:1C:A6:E5:50:14:3D:CE:28:03:47:1B:DE:3A:09:E8:F8:77:0F", - "sha256": "83:CE:3C:12:29:68:8A:59:3D:48:5F:81:97:3C:0F:91:95:43:1E:DA:37:CC:5E:36:43:0E:79:C7:A8:88:63:8B", # noqa: E501 - "sha512": "86:20:07:9F:8B:06:80:43:44:98:F6:7A:A4:22:DE:7E:2B:33:10:9B:65:72:79:C4:EB:F3:F3:0F:66:C8:6E:89:1D:4C:6C:09:1C:83:45:D1:25:6C:F8:65:EB:9A:B9:50:8F:26:A8:85:AE:3A:E4:8A:58:60:48:65:BB:44:B6:CE", # NOQA -} -certs["cloudflare_1"] = { - "name": "cloudflare_1", - "subject": [ - ["OU", "Domain Control Validated"], - ["OU", "PositiveSSL Multi-Domain"], - ["CN", "sni24142.cloudflaressl.com"], - ], - "subject_str": "/OU=Domain Control Validated/OU=PositiveSSL Multi-Domain/CN=sni24142.cloudflaressl.com", - "cn": "sni24142.cloudflaressl.com", - "key_filename": False, - "csr_filename": False, - "pub_filename": os.path.join("contrib", "cloudflare_1.pem"), - "cat": "contrib", - "type": "cert", - "key_type": "EC", - "valid_from": "2018-07-18 00:00:00", - "valid_until": "2019-01-24 23:59:59", - "ca": "root", - "serial": "92529ABD85F0A6A4D6C53FD1C91011C1", - "md5": "D6:76:03:E9:4F:3B:B0:F1:F7:E3:A1:40:80:8E:F0:4A", - "sha1": "71:BD:B8:21:80:BD:86:E8:E5:F4:2B:6D:96:82:B2:EF:19:53:ED:D3", - "sha256": "1D:8E:D5:41:E5:FF:19:70:6F:65:86:A9:A3:6F:DF:DE:F8:A0:07:22:92:71:9E:F1:CD:F8:28:37:39:02:E0:A1", # NOQA - "sha512": "FF:03:1B:8F:11:E8:A7:FF:91:4F:B9:97:E9:97:BC:77:37:C1:A7:69:86:F3:7C:E3:BB:BB:DF:A6:4F:0E:3C:C0:7F:B5:BC:CC:BD:0A:D5:EF:5F:94:55:E9:FF:48:41:34:B8:11:54:57:DD:90:85:41:2E:71:70:5E:FA:BA:E6:EA", # NOQA - "authority_information_access": { - "critical": False, - "value": { - "issuers": ["URI:http://crt.comodoca4.com/COMODOECCDomainValidationSecureServerCA2.crt"], - "ocsp": ["URI:http://ocsp.comodoca4.com"], - }, - }, - "authority_key_identifier": { - "critical": False, - "value": "40:09:61:67:F0:BC:83:71:4F:DE:12:08:2C:6F:D4:D4:2B:76:3D:96", - }, - "basic_constraints": { - "critical": True, - "value": {"ca": False}, - }, - "crl_distribution_points": { - "value": [ - { - "full_name": [ - "URI:http://crl.comodoca4.com/COMODOECCDomainValidationSecureServerCA2.crl", - ], - } - ], - "critical": False, - }, - "extended_key_usage": { - "critical": False, - "value": ["serverAuth", "clientAuth"], - }, - "key_usage": { - "critical": True, - "value": ["digital_signature"], - }, - "precert_poison": {"critical": True}, - "subject_alternative_name": { - "value": [ - "DNS:sni24142.cloudflaressl.com", - "DNS:*.animereborn.com", - "DNS:*.beglideas.ga", - "DNS:*.chroma.ink", - "DNS:*.chuckscleanings.ga", - "DNS:*.clipvuigiaitris.ga", - "DNS:*.cmvsjns.ga", - "DNS:*.competegraphs.ga", - "DNS:*.consoleprints.ga", - "DNS:*.copybreezes.ga", - "DNS:*.corphreyeds.ga", - "DNS:*.cyanigees.ga", - "DNS:*.dadpbears.ga", - "DNS:*.dahuleworldwides.ga", - "DNS:*.dailyopeningss.ga", - "DNS:*.daleylexs.ga", - "DNS:*.danajweinkles.ga", - "DNS:*.dancewthyogas.ga", - "DNS:*.darkmoosevpss.ga", - "DNS:*.daurat.com.ar", - "DNS:*.deltaberg.com", - "DNS:*.drjahanobgyns.ga", - "DNS:*.drunkgirliess.ga", - "DNS:*.duhiepkys.ga", - "DNS:*.dujuanjsqs.ga", - "DNS:*.dumbiseasys.ga", - "DNS:*.dumpsoftdrinkss.ga", - "DNS:*.dunhavenwoodss.ga", - "DNS:*.durabiliteas.ga", - "DNS:*.duxmangroups.ga", - "DNS:*.dvpdrivewayss.ga", - "DNS:*.dwellwizes.ga", - "DNS:*.dwwkouis.ga", - "DNS:*.entertastic.com", - "DNS:*.estudiogolber.com.ar", - "DNS:*.letsretro.team", - "DNS:*.maccuish.org.uk", - "DNS:*.madamsquiggles.com", - "DNS:*.sftw.ninja", - "DNS:*.spangenberg.io", - "DNS:*.timmutton.com.au", - "DNS:*.wyomingsexbook.com", - "DNS:*.ych.bid", - "DNS:animereborn.com", - "DNS:beglideas.ga", - "DNS:chroma.ink", - "DNS:chuckscleanings.ga", - "DNS:clipvuigiaitris.ga", - "DNS:cmvsjns.ga", - "DNS:competegraphs.ga", - "DNS:consoleprints.ga", - "DNS:copybreezes.ga", - "DNS:corphreyeds.ga", - "DNS:cyanigees.ga", - "DNS:dadpbears.ga", - "DNS:dahuleworldwides.ga", - "DNS:dailyopeningss.ga", - "DNS:daleylexs.ga", - "DNS:danajweinkles.ga", - "DNS:dancewthyogas.ga", - "DNS:darkmoosevpss.ga", - "DNS:daurat.com.ar", - "DNS:deltaberg.com", - "DNS:drjahanobgyns.ga", - "DNS:drunkgirliess.ga", - "DNS:duhiepkys.ga", - "DNS:dujuanjsqs.ga", - "DNS:dumbiseasys.ga", - "DNS:dumpsoftdrinkss.ga", - "DNS:dunhavenwoodss.ga", - "DNS:durabiliteas.ga", - "DNS:duxmangroups.ga", - "DNS:dvpdrivewayss.ga", - "DNS:dwellwizes.ga", - "DNS:dwwkouis.ga", - "DNS:entertastic.com", - "DNS:estudiogolber.com.ar", - "DNS:letsretro.team", - "DNS:maccuish.org.uk", - "DNS:madamsquiggles.com", - "DNS:sftw.ninja", - "DNS:spangenberg.io", - "DNS:timmutton.com.au", - "DNS:wyomingsexbook.com", - "DNS:ych.bid", - ] - }, - "subject_key_identifier": { - "critical": False, - "value": "05:86:D8:B4:ED:A9:7E:23:EE:2E:E7:75:AA:3B:2C:06:08:2A:93:B2", - }, - "certificate_policies": { - "value": [ - { - "policy_identifier": "1.3.6.1.4.1.6449.1.2.2.7", - "policy_qualifiers": ["https://secure.comodo.com/CPS"], - }, - {"policy_identifier": "2.23.140.1.2.1"}, - ], - "critical": False, - }, -} - -SPHINX_FIXTURES_DIR = os.path.join(os.path.dirname(settings.BASE_DIR), "docs", "source", "_files") -for cert_name, cert_data in certs.items(): - cert_data["serial_colons"] = add_colons(cert_data["serial"]) - if cert_data.get("password"): - cert_data["password"] = cert_data["password"].encode("utf-8") - if cert_data["cat"] == "sphinx-contrib": - cert_data["basedir"] = os.path.join(SPHINX_FIXTURES_DIR, cert_data["type"]) - - if cert_data["type"] == "ca": - cert_data.setdefault("children", []) - cert_data["children"] = [(k, add_colons(v)) for k, v in cert_data["children"]] - - # Load data from files - if cert_data["key_filename"] is not False: - cert_data["key"] = _load_key(cert_data) - if cert_data["csr_filename"] is not False: - cert_data["csr"] = _load_csr(cert_data) - cert_data["pub"] = _load_pub(cert_data) - cert_data["issuer"] = cert_data["pub"]["parsed"].issuer - cert_data["issuer_str"] = format_name(cert_data["issuer"]) - - if "subject" not in cert_data: # pragma: no cover - raise ValueError(f"subject not in {cert_name}") - if "subject_str" not in cert_data: # pragma: no cover - raise ValueError(f"subject_str not in {cert_name}") - if not isinstance(cert_data["subject"], list): # pragma: no cover - raise ValueError(cert_data["subject"]) - if not isinstance(cert_data["subject_str"], str): # pragma: no cover - raise ValueError(cert_data["subject_str"]) - - # parse some data from the dict - cert_data["valid_from"] = datetime.strptime(cert_data["valid_from"], "%Y-%m-%d %H:%M:%S") - cert_data["valid_until"] = datetime.strptime(cert_data["valid_until"], "%Y-%m-%d %H:%M:%S") - cert_data["valid_from_str"] = cert_data["valid_from"].replace(tzinfo=tz.utc).isoformat(" ") - cert_data["valid_until_str"] = cert_data["valid_until"].replace(tzinfo=tz.utc).isoformat(" ") - - # parse extensions - for ext_key in EXTENSION_KEY_OIDS: - if cert_data.get(ext_key): - cert_data[f"{ext_key}_serialized"] = cert_data[ext_key] - - # extensions are not parsable, see also: https://github.com/pyca/cryptography/issues/7824 - if ext_key not in ( - "precertificate_signed_certificate_timestamps", - "signed_certificate_timestamps", - ): - cert_data[ext_key] = parse_extension(ext_key, cert_data[ext_key]) - -# Calculate some fixed timestamps that we reuse throughout the tests -timestamps = { - "base": datetime.fromisoformat(_fixture_data["timestamp"]), - "before_everything": datetime(1990, 1, 1, tzinfo=tz.utc), -} -timestamps["before_cas"] = timestamps["base"] - timedelta(days=1) -timestamps["before_child"] = timestamps["base"] + timedelta(days=1) -timestamps["after_child"] = timestamps["base"] + timedelta(days=4) -timestamps["ca_certs_valid"] = timestamps["base"] + timedelta(days=7) -timestamps["profile_certs_valid"] = timestamps["base"] + timedelta(days=12) - -# When creating fixtures, latest valid_from of any generated cert is 20 days, we need to be after that -timestamps["everything_valid"] = timestamps["base"] + timedelta(days=23) -timestamps["everything_valid_naive"] = timezone.make_naive(timestamps["everything_valid"]) -timestamps["cas_expired"] = timestamps["base"] + timedelta(days=731, seconds=3600) -timestamps["ca_certs_expiring"] = certs["root-cert"]["valid_until"] - timedelta(days=3) -timestamps["ca_certs_expired"] = certs["root-cert"]["valid_until"] + timedelta(seconds=3600) -timestamps["profile_certs_expired"] = certs["profile-server"]["valid_until"] + timedelta(seconds=3600) -timestamps["everything_expired"] = timestamps["base"] + timedelta(days=365 * 20) -ocsp_data = _fixture_data["ocsp"] - - -def dns(name: str) -> x509.DNSName: # just a shortcut - """Shortcut to get a :py:class:`cg:cryptography.x509.DNSName`.""" - return x509.DNSName(name) - - -def uri(url: str) -> x509.UniformResourceIdentifier: # just a shortcut - """Shortcut to get a :py:class:`cg:cryptography.x509.UniformResourceIdentifier`.""" - return x509.UniformResourceIdentifier(url) - - -def ip( # pylint: disable=invalid-name # just a shortcut - name: Union[ipaddress.IPv4Address, ipaddress.IPv6Address, ipaddress.IPv4Network, ipaddress.IPv6Network] -) -> x509.IPAddress: - """Shortcut to get a :py:class:`cg:cryptography.x509.IPAddress`.""" - return x509.IPAddress(name) - - -def rdn( - name: Iterable[Tuple[x509.ObjectIdentifier, str]] -) -> x509.RelativeDistinguishedName: # just a shortcut - """Shortcut to get a :py:class:`cg:cryptography.x509.RelativeDistinguishedName`.""" - return x509.RelativeDistinguishedName([x509.NameAttribute(*t) for t in name]) - - -@contextmanager -def mock_cadir(path: str) -> Iterator[None]: - """Contextmanager to set the CA_DIR to a given path without actually creating it.""" - with override_settings(CA_DIR=path), patch.object(ca_storage, "location", path), patch.object( - ca_storage, "_location", path - ): - yield - - -class override_tmpcadir(override_settings): # pylint: disable=invalid-name; in line with parent class - """Sets the CA_DIR directory to a temporary directory. - - .. NOTE: This also takes any additional settings. - """ - - def __call__(self, test_func: FuncTypeVar) -> FuncTypeVar: - if not inspect.isfunction(test_func): - raise ValueError("Only functions can use override_tmpcadir()") - return super().__call__(test_func) # type: ignore[return-value] # cannot figure out whats right here - - def enable(self) -> None: - self.options["CA_DIR"] = tempfile.mkdtemp() - - # copy CAs - for filename in [v["key_filename"] for v in certs.values() if v["key_filename"] is not False]: - shutil.copy(os.path.join(FIXTURES_DIR, filename), self.options["CA_DIR"]) - - # Copy OCSP public key (required for OCSP tests) - shutil.copy(os.path.join(FIXTURES_DIR, certs["profile-ocsp"]["pub_filename"]), self.options["CA_DIR"]) - - # pylint: disable=attribute-defined-outside-init - self.mock = patch.object(ca_storage, "location", self.options["CA_DIR"]) - self.mock_ = patch.object(ca_storage, "_location", self.options["CA_DIR"]) - # pylint: enable=attribute-defined-outside-init - - # Reset profiles, so that they are loaded again on first access - profiles._reset() # pylint: disable=protected-access - - self.mock.start() - self.mock_.start() - - super().enable() - - def disable(self) -> None: - super().disable() - self.mock.stop() - self.mock_.stop() - shutil.rmtree(self.options["CA_DIR"]) - - -__all__ = [ - "CRYPTOGRAPHY_VERSION", - "DOC_DIR", - "FIXTURES_DIR", - "GECKODRIVER_PATH", - "RUN_SELENIUM_TESTS", - "dns", - "rdn", - "uri", -] diff --git a/ca/django_ca/tests/base/conftest_helpers.py b/ca/django_ca/tests/base/conftest_helpers.py index 757c0e1d1..29c3eded8 100644 --- a/ca/django_ca/tests/base/conftest_helpers.py +++ b/ca/django_ca/tests/base/conftest_helpers.py @@ -12,7 +12,7 @@ # . """Helpers for pytest conftest.""" -import json + import os import shutil import sys @@ -24,7 +24,6 @@ import cryptography from cryptography import x509 -from cryptography.hazmat.primitives import serialization import django from django.conf import settings @@ -36,7 +35,7 @@ from pytest_django.fixtures import SettingsWrapper from django_ca.models import Certificate, CertificateAuthority -from django_ca.tests.base import FIXTURES_DIR, timestamps +from django_ca.tests.base.constants import CERT_DATA, FIXTURES_DIR, TIMESTAMPS from django_ca.utils import int_to_hex @@ -159,7 +158,7 @@ def fixture( request: "SubRequest", db: Any, # pylint: disable=unused-argument,invalid-name # usefixtures does not work for fixtures ) -> Iterator[CertificateAuthority]: - data = fixture_data["certs"][name] + data = CERT_DATA[name] pub = request.getfixturevalue(f"{name}_pub") # Load any parent @@ -167,7 +166,7 @@ def fixture( if parent_name := data.get("parent"): parent = request.getfixturevalue(parent_name) - with freeze_time(timestamps["everything_valid"]): + with freeze_time(TIMESTAMPS["everything_valid"]): ca = load_ca(name, pub, parent) yield ca # NOTE: Yield must be outside the freeze-time block, or durations are wrong @@ -183,7 +182,7 @@ def generate_usable_ca_fixture( @pytest.fixture() def fixture(request: "SubRequest", tmpcadir: SettingsWrapper) -> Iterator[CertificateAuthority]: ca = request.getfixturevalue(name) # load the CA into the database - data = fixture_data["certs"][name] + data = CERT_DATA[name] shutil.copy(os.path.join(FIXTURES_DIR, data["key_filename"]), tmpcadir.CA_DIR) yield ca @@ -197,12 +196,12 @@ def generate_cert_fixture(name: str) -> typing.Callable[["SubRequest"], Iterator @pytest.fixture() def fixture(request: "SubRequest") -> Iterator[Certificate]: sanitized_name = name.replace("-", "_") - data = fixture_data["certs"][name] + data = CERT_DATA[name] ca = request.getfixturevalue(data["ca"]) pub = request.getfixturevalue(f"{sanitized_name}_pub") - with freeze_time(timestamps["everything_valid"]): - cert = load_cert(ca, certs[name]["csr"]["pem"], pub, data.get("profile", "")) + with freeze_time(TIMESTAMPS["everything_valid"]): + cert = load_cert(ca, data["csr"]["pem"], pub, data.get("profile", "")) yield cert # NOTE: Yield must be outside the freeze-time block, or durations are wrong @@ -237,28 +236,6 @@ def load_ca( return ca -def _load_certificate_signing_requests() -> None: - for name in usable_cert_names: - with open(os.path.join(FIXTURES_DIR, f"{name}.csr"), "rb") as stream: - pem_bytes = stream.read() - certs[name]["csr"] = { - "der": x509.load_pem_x509_csr(pem_bytes), - "pem": pem_bytes.decode("utf-8"), - } - - -def _load_public_keys() -> None: - for name in usable_ca_names + usable_cert_names: - with open(os.path.join(FIXTURES_DIR, f"{name}.pub.der"), "rb") as stream: - der_bytes = stream.read() - certificate = x509.load_der_x509_certificate(der_bytes) - certs[name]["pub"] = { - "der": der_bytes, - "loaded": x509.load_der_x509_certificate(der_bytes), - "pem": certificate.public_bytes(serialization.Encoding.PEM).decode("utf-8"), - } - - def load_cert( ca: CertificateAuthority, csr: x509.CertificateSigningRequest, pub: x509.Certificate, profile: str = "" ) -> Certificate: @@ -269,34 +246,20 @@ def load_cert( return cert -with open(os.path.join(FIXTURES_DIR, "cert-data.json"), encoding="utf-8") as cert_data_stream: - fixture_data = json.load(cert_data_stream) -certs = fixture_data["certs"] - # Define various classes of certificates usable_ca_names = [ - name for name, conf in fixture_data["certs"].items() if conf["type"] == "ca" and conf.get("key_filename") + name for name, conf in CERT_DATA.items() if conf["type"] == "ca" and conf.get("key_filename") ] unusable_ca_names = [ - name - for name, conf in fixture_data["certs"].items() - if conf["type"] == "ca" and name not in usable_ca_names + name for name, conf in CERT_DATA.items() if conf["type"] == "ca" and name not in usable_ca_names ] all_ca_names = usable_ca_names + unusable_ca_names usable_cert_names = [ - name - for name, conf in fixture_data["certs"].items() - if conf["type"] == "cert" and conf["cat"] == "generated" + name for name, conf in CERT_DATA.items() if conf["type"] == "cert" and conf["cat"] == "generated" ] unusable_cert_names = [ - name - for name, conf in fixture_data["certs"].items() - if conf["type"] == "cert" and name not in usable_ca_names + name for name, conf in CERT_DATA.items() if conf["type"] == "cert" and name not in usable_ca_names ] interesting_certificate_names = ["child-cert", "all-extensions", "alt-extensions", "no-extensions"] all_cert_names = usable_cert_names + unusable_cert_names - -# Load CSRs/public keys into certs -_load_certificate_signing_requests() -_load_public_keys() diff --git a/ca/django_ca/tests/base/constants.py b/ca/django_ca/tests/base/constants.py index 6f47c470b..05bda13e3 100644 --- a/ca/django_ca/tests/base/constants.py +++ b/ca/django_ca/tests/base/constants.py @@ -15,22 +15,30 @@ import json import os +import re import sys +from datetime import datetime, timedelta, timezone as tz from importlib.metadata import version from pathlib import Path -from typing import List, Tuple +from typing import Any, Dict, List, Tuple import packaging.version import cryptography +from cryptography import x509 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives._serialization import Encoding import django -# PYLINT NOTE: lazy import so that just importing this module has no external dependencies +from django_ca.constants import EXTENSION_KEYS +from django_ca.extensions import serialize_extension +from django_ca.tests.base.typehints import CsrDict, KeyDict, PubDict +from django_ca.utils import add_colons, format_name + try: - import tomllib # pylint: disable=import-outside-toplevel + import tomllib except ImportError: # pragma: only py<3.11 - # pylint: disable-next=import-outside-toplevel import tomli as tomllib # type: ignore[no-redef] @@ -46,11 +54,12 @@ def _load_latest_version(versions: List[str]) -> Tuple[int, int]: BASE_DIR = TEST_DIR.parent.parent # ca/ ROOT_DIR = BASE_DIR.parent # git repository root -with open(ROOT_DIR / "pyproject.toml", "rb") as stream: - PROJECT_CONFIG = tomllib.load(stream) +with open(ROOT_DIR / "pyproject.toml", "rb") as pyproject_stream: + PROJECT_CONFIG = tomllib.load(pyproject_stream) # Paths derived from ROOT_DIR DOC_DIR = ROOT_DIR / "docs" / "source" +SPHINX_FIXTURES_DIR = DOC_DIR / "_files" GECKODRIVER_PATH = ROOT_DIR / "contrib" / "selenium" / "geckodriver" if TOX_ENV_DIR := os.environ.get("TOX_ENV_DIR"): # pragma: no cover @@ -85,14 +94,318 @@ def _load_latest_version(versions: List[str]) -> Tuple[int, int]: FIXTURES_DATA = json.load(cert_data_stream) CERT_DATA = FIXTURES_DATA["certs"] + +# Update some data from contrib (data is not in cert-data.json, since we don't generate them) +CERT_DATA["multiple_ous"] = { + "name": "multiple_ous", + "subject": [ + ["C", "US"], + ["O", "VeriSign, Inc."], + ["OU", "Class 3 Public Primary Certification Authority - G2"], + ["OU", "(c) 1998 VeriSign, Inc. - For authorized use only"], + ["OU", "VeriSign Trust Network"], + ], + "subject_str": "/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority - G2/OU=(c) 1998 VeriSign, Inc. - For authorized use only/OU=VeriSign Trust Network", # noqa: E501 + "cn": "", + "key_filename": False, + "csr_filename": False, + "pub_filename": os.path.join("contrib", "multiple_ous_and_no_ext.pem"), + "key_type": "RSA", + "cat": "contrib", + "type": "cert", + "valid_from": "1998-05-18 00:00:00", + "valid_until": "2028-08-01 23:59:59", + "ca": "root", + "serial": "7DD9FE07CFA81EB7107967FBA78934C6", + "md5": "A2:33:9B:4C:74:78:73:D4:6C:E7:C1:F3:8D:CB:5C:E9", + "sha1": "85:37:1C:A6:E5:50:14:3D:CE:28:03:47:1B:DE:3A:09:E8:F8:77:0F", + "sha256": "83:CE:3C:12:29:68:8A:59:3D:48:5F:81:97:3C:0F:91:95:43:1E:DA:37:CC:5E:36:43:0E:79:C7:A8:88:63:8B", # noqa: E501 + "sha512": "86:20:07:9F:8B:06:80:43:44:98:F6:7A:A4:22:DE:7E:2B:33:10:9B:65:72:79:C4:EB:F3:F3:0F:66:C8:6E:89:1D:4C:6C:09:1C:83:45:D1:25:6C:F8:65:EB:9A:B9:50:8F:26:A8:85:AE:3A:E4:8A:58:60:48:65:BB:44:B6:CE", # NOQA +} +CERT_DATA["cloudflare_1"] = { + "name": "cloudflare_1", + "subject": [ + ["OU", "Domain Control Validated"], + ["OU", "PositiveSSL Multi-Domain"], + ["CN", "sni24142.cloudflaressl.com"], + ], + "subject_str": "/OU=Domain Control Validated/OU=PositiveSSL Multi-Domain/CN=sni24142.cloudflaressl.com", + "cn": "sni24142.cloudflaressl.com", + "key_filename": False, + "csr_filename": False, + "pub_filename": os.path.join("contrib", "cloudflare_1.pem"), + "cat": "contrib", + "type": "cert", + "key_type": "EC", + "valid_from": "2018-07-18 00:00:00", + "valid_until": "2019-01-24 23:59:59", + "ca": "root", + "serial": "92529ABD85F0A6A4D6C53FD1C91011C1", + "md5": "D6:76:03:E9:4F:3B:B0:F1:F7:E3:A1:40:80:8E:F0:4A", + "sha1": "71:BD:B8:21:80:BD:86:E8:E5:F4:2B:6D:96:82:B2:EF:19:53:ED:D3", + "sha256": "1D:8E:D5:41:E5:FF:19:70:6F:65:86:A9:A3:6F:DF:DE:F8:A0:07:22:92:71:9E:F1:CD:F8:28:37:39:02:E0:A1", # NOQA + "sha512": "FF:03:1B:8F:11:E8:A7:FF:91:4F:B9:97:E9:97:BC:77:37:C1:A7:69:86:F3:7C:E3:BB:BB:DF:A6:4F:0E:3C:C0:7F:B5:BC:CC:BD:0A:D5:EF:5F:94:55:E9:FF:48:41:34:B8:11:54:57:DD:90:85:41:2E:71:70:5E:FA:BA:E6:EA", # NOQA + "authority_information_access": { + "critical": False, + "value": { + "issuers": ["URI:http://crt.comodoca4.com/COMODOECCDomainValidationSecureServerCA2.crt"], + "ocsp": ["URI:http://ocsp.comodoca4.com"], + }, + }, + "authority_key_identifier": { + "critical": False, + "value": "40:09:61:67:F0:BC:83:71:4F:DE:12:08:2C:6F:D4:D4:2B:76:3D:96", + }, + "basic_constraints": { + "critical": True, + "value": {"ca": False}, + }, + "crl_distribution_points": { + "value": [ + { + "full_name": [ + "URI:http://crl.comodoca4.com/COMODOECCDomainValidationSecureServerCA2.crl", + ], + } + ], + "critical": False, + }, + "extended_key_usage": { + "critical": False, + "value": ["serverAuth", "clientAuth"], + }, + "key_usage": { + "critical": True, + "value": ["digital_signature"], + }, + "precert_poison": {"critical": True}, + "subject_alternative_name": { + "value": [ + "DNS:sni24142.cloudflaressl.com", + "DNS:*.animereborn.com", + "DNS:*.beglideas.ga", + "DNS:*.chroma.ink", + "DNS:*.chuckscleanings.ga", + "DNS:*.clipvuigiaitris.ga", + "DNS:*.cmvsjns.ga", + "DNS:*.competegraphs.ga", + "DNS:*.consoleprints.ga", + "DNS:*.copybreezes.ga", + "DNS:*.corphreyeds.ga", + "DNS:*.cyanigees.ga", + "DNS:*.dadpbears.ga", + "DNS:*.dahuleworldwides.ga", + "DNS:*.dailyopeningss.ga", + "DNS:*.daleylexs.ga", + "DNS:*.danajweinkles.ga", + "DNS:*.dancewthyogas.ga", + "DNS:*.darkmoosevpss.ga", + "DNS:*.daurat.com.ar", + "DNS:*.deltaberg.com", + "DNS:*.drjahanobgyns.ga", + "DNS:*.drunkgirliess.ga", + "DNS:*.duhiepkys.ga", + "DNS:*.dujuanjsqs.ga", + "DNS:*.dumbiseasys.ga", + "DNS:*.dumpsoftdrinkss.ga", + "DNS:*.dunhavenwoodss.ga", + "DNS:*.durabiliteas.ga", + "DNS:*.duxmangroups.ga", + "DNS:*.dvpdrivewayss.ga", + "DNS:*.dwellwizes.ga", + "DNS:*.dwwkouis.ga", + "DNS:*.entertastic.com", + "DNS:*.estudiogolber.com.ar", + "DNS:*.letsretro.team", + "DNS:*.maccuish.org.uk", + "DNS:*.madamsquiggles.com", + "DNS:*.sftw.ninja", + "DNS:*.spangenberg.io", + "DNS:*.timmutton.com.au", + "DNS:*.wyomingsexbook.com", + "DNS:*.ych.bid", + "DNS:animereborn.com", + "DNS:beglideas.ga", + "DNS:chroma.ink", + "DNS:chuckscleanings.ga", + "DNS:clipvuigiaitris.ga", + "DNS:cmvsjns.ga", + "DNS:competegraphs.ga", + "DNS:consoleprints.ga", + "DNS:copybreezes.ga", + "DNS:corphreyeds.ga", + "DNS:cyanigees.ga", + "DNS:dadpbears.ga", + "DNS:dahuleworldwides.ga", + "DNS:dailyopeningss.ga", + "DNS:daleylexs.ga", + "DNS:danajweinkles.ga", + "DNS:dancewthyogas.ga", + "DNS:darkmoosevpss.ga", + "DNS:daurat.com.ar", + "DNS:deltaberg.com", + "DNS:drjahanobgyns.ga", + "DNS:drunkgirliess.ga", + "DNS:duhiepkys.ga", + "DNS:dujuanjsqs.ga", + "DNS:dumbiseasys.ga", + "DNS:dumpsoftdrinkss.ga", + "DNS:dunhavenwoodss.ga", + "DNS:durabiliteas.ga", + "DNS:duxmangroups.ga", + "DNS:dvpdrivewayss.ga", + "DNS:dwellwizes.ga", + "DNS:dwwkouis.ga", + "DNS:entertastic.com", + "DNS:estudiogolber.com.ar", + "DNS:letsretro.team", + "DNS:maccuish.org.uk", + "DNS:madamsquiggles.com", + "DNS:sftw.ninja", + "DNS:spangenberg.io", + "DNS:timmutton.com.au", + "DNS:wyomingsexbook.com", + "DNS:ych.bid", + ] + }, + "subject_key_identifier": { + "critical": False, + "value": "05:86:D8:B4:ED:A9:7E:23:EE:2E:E7:75:AA:3B:2C:06:08:2A:93:B2", + }, + "certificate_policies": { + "value": [ + { + "policy_identifier": "1.3.6.1.4.1.6449.1.2.2.7", + "policy_qualifiers": ["https://secure.comodo.com/CPS"], + }, + {"policy_identifier": "2.23.140.1.2.1"}, + ], + "critical": False, + }, +} + + +def _load_key(data: Dict[Any, Any]) -> KeyDict: + with open(data["key_der_path"], "rb") as stream: + raw = stream.read() + + parsed = serialization.load_der_private_key( + raw, password=data.get("password"), unsafe_skip_rsa_key_validation=True + ) + + return { + "der": raw, + "pem": parsed.private_bytes( + Encoding.PEM, + serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ).decode(), + "parsed": parsed, # type: ignore[typeddict-item] # we do not support all key types + } + + +def _load_csr(data: Dict[Any, Any]) -> CsrDict: + with open(FIXTURES_DIR / data["csr_filename"], "rb") as stream: + raw = stream.read() + + parsed = x509.load_pem_x509_csr(raw) + return { + "pem": raw.decode("utf-8"), + "parsed": parsed, + } + + +def _load_pub(data: Dict[str, Any]) -> PubDict: + if pub_der_path := data.get("pub_der_path"): + with open(pub_der_path, "rb") as stream: + der = stream.read() + parsed = x509.load_der_x509_certificate(der) + pem = parsed.public_bytes(Encoding.PEM).decode("utf-8") + else: + pub_path = data["pub_path"] + with open(pub_path, "rb") as stream: + pub_pem_bytes = stream.read() + parsed = x509.load_pem_x509_certificate(pub_pem_bytes) + pem = pub_pem_bytes.decode("utf-8") + der = parsed.public_bytes(Encoding.DER) + + return {"pem": pem, "parsed": parsed, "der": der} + + +# Augment data with various pre-computed paths for _name, _cert_data in CERT_DATA.items(): + if _cert_data["cat"] == "sphinx-contrib": + basedir = SPHINX_FIXTURES_DIR / _cert_data["type"] + else: + basedir = FIXTURES_DIR + if _key_filename := _cert_data.get("key_filename"): - CERT_DATA[_name]["key_path"] = FIXTURES_DIR / _cert_data["key_filename"] + _cert_data["key_path"] = basedir / _cert_data["key_filename"] if _key_der_filename := _cert_data.get("key_der_filename"): - CERT_DATA[_name]["key_der_path"] = FIXTURES_DIR / _cert_data["key_der_filename"] + _cert_data["key_der_path"] = basedir / _cert_data["key_der_filename"] if _pub_der_filename := _cert_data.get("pub_der_filename"): - CERT_DATA[_name]["pub_der_path"] = FIXTURES_DIR / _cert_data["pub_der_filename"] + _cert_data["pub_der_path"] = basedir / _cert_data["pub_der_filename"] if _password := _cert_data.get("password"): - CERT_DATA[_name]["password"] = _cert_data["password"].encode("utf-8") - CERT_DATA[_name]["pub_path"] = FIXTURES_DIR / _cert_data["pub_filename"] - CERT_DATA[_name]["pub_path"] = FIXTURES_DIR / _cert_data["pub_filename"] + _cert_data["password"] = _cert_data["password"].encode("utf-8") + _cert_data["pub_path"] = basedir / _cert_data["pub_filename"] + + if _cert_data["type"] == "ca": + _cert_data.setdefault("children", []) + _cert_data["children"] = [(k, add_colons(v)) for k, v in _cert_data["children"]] + + # Load data from files + # if key_filename := _cert_data["key_filename"]: + # _cert_data["key"] = _load_key(key_filename, _cert_data) + if _cert_data.get("key_der_path"): + _cert_data["key"] = _load_key(_cert_data) + if _cert_data.get("csr_filename"): + _cert_data["csr"] = _load_csr(_cert_data) + _cert_data["pub"] = _load_pub(_cert_data) + _cert: x509.Certificate = _cert_data["pub"]["parsed"] + + # Data derived from public key + _cert_data["issuer"] = _cert.issuer + _cert_data["issuer_str"] = format_name(_cert_data["issuer"]) + _cert_data["serial_colons"] = add_colons(_cert_data["serial"]) + _cert_data["valid_from"] = _cert.not_valid_before # TODO: make tz-aware + _cert_data["valid_until"] = _cert.not_valid_after # TODO: make tz-aware + _cert_data["valid_from_str"] = _cert.not_valid_before.replace(tzinfo=tz.utc).isoformat(" ") + _cert_data["valid_until_str"] = _cert.not_valid_after.replace(tzinfo=tz.utc).isoformat(" ") + + for extension in _cert.extensions: + try: + key = EXTENSION_KEYS[extension.oid] + except KeyError: # unknown extensions from StartSSL CA + continue + _cert_data[key] = extension + _cert_data[f"{key}_serialized"] = serialize_extension(extension)["value"] + +# Calculate some fixed timestamps that we reuse throughout the tests +TIMESTAMPS = { + "base": datetime.fromisoformat(FIXTURES_DATA["timestamp"]), + "before_everything": datetime(1990, 1, 1, tzinfo=tz.utc), +} +TIMESTAMPS["before_cas"] = TIMESTAMPS["base"] - timedelta(days=1) +TIMESTAMPS["before_child"] = TIMESTAMPS["base"] + timedelta(days=1) +TIMESTAMPS["after_child"] = TIMESTAMPS["base"] + timedelta(days=4) +TIMESTAMPS["ca_certs_valid"] = TIMESTAMPS["base"] + timedelta(days=7) +TIMESTAMPS["profile_certs_valid"] = TIMESTAMPS["base"] + timedelta(days=12) + +# When creating fixtures, latest valid_from of any generated cert is 20 days, we need to be after that +TIMESTAMPS["everything_valid"] = TIMESTAMPS["base"] + timedelta(days=23) +TIMESTAMPS["everything_valid_naive"] = TIMESTAMPS["everything_valid"].astimezone(tz.utc).replace(tzinfo=None) +TIMESTAMPS["cas_expired"] = TIMESTAMPS["base"] + timedelta(days=731, seconds=3600) +TIMESTAMPS["ca_certs_expiring"] = CERT_DATA["root-cert"]["valid_until"] - timedelta(days=3) +TIMESTAMPS["ca_certs_expired"] = CERT_DATA["root-cert"]["valid_until"] + timedelta(seconds=3600) +TIMESTAMPS["profile_certs_expired"] = CERT_DATA["profile-server"]["valid_until"] + timedelta(seconds=3600) +TIMESTAMPS["everything_expired"] = TIMESTAMPS["base"] + timedelta(days=365 * 20) + +# Regex used by certbot to split PEM-encoded certificate chains/bundles as of 2022-01-23. See also: +# https://github.com/certbot/certbot/blob/master/certbot/certbot/crypto_util.py +CERT_PEM_REGEX = re.compile( + b"""-----BEGIN CERTIFICATE-----\r? +.+?\r? +-----END CERTIFICATE-----\r? +""", + re.DOTALL, # DOTALL (/s) because the base64text may include newlines +) diff --git a/ca/django_ca/tests/base/mixins.py b/ca/django_ca/tests/base/mixins.py index 91e6fea88..a2437af6e 100644 --- a/ca/django_ca/tests/base/mixins.py +++ b/ca/django_ca/tests/base/mixins.py @@ -60,11 +60,11 @@ pre_create_ca, pre_sign_cert, ) -from django_ca.tests.base import certs, timestamps, uri from django_ca.tests.base.assertions import assert_change_response, assert_changelist_response +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.typehints import DjangoCAModelTypeVar -from django_ca.tests.base.utils import basic_constraints, certificate_policies -from django_ca.utils import add_colons, ca_storage, parse_general_name +from django_ca.tests.base.utils import basic_constraints, certificate_policies, uri +from django_ca.utils import ca_storage, parse_general_name if typing.TYPE_CHECKING: # Use SimpleTestCase as base class when type checking. This way mypy will know about attributes/methods @@ -125,9 +125,9 @@ def setUp(self) -> None: def load_named_cas(self, cas: Union[str, Tuple[str, ...]]) -> Tuple[str, ...]: """Load CAs by the given name.""" if cas == "__all__": - cas = tuple(k for k, v in certs.items() if v.get("type") == "ca") + cas = tuple(k for k, v in CERT_DATA.items() if v.get("type") == "ca") elif cas == "__usable__": - cas = tuple(k for k, v in certs.items() if v.get("type") == "ca" and v["key_filename"]) + cas = tuple(k for k, v in CERT_DATA.items() if v.get("type") == "ca" and v["key_filename"]) elif isinstance(cas, str): # pragma: no cover self.fail(f"{cas}: Unknown alias for load_cas.") @@ -135,16 +135,18 @@ def load_named_cas(self, cas: Union[str, Tuple[str, ...]]) -> Tuple[str, ...]: cas = tuple(ca for ca in cas if ca not in self.cas) # Load all CAs (sort by len() of parent so that root CAs are loaded first) - for name in sorted(cas, key=lambda n: len(certs[n].get("parent", ""))): + for name in sorted(cas, key=lambda n: len(CERT_DATA[n].get("parent", ""))): self.cas[name] = self.load_ca(name) return cas def load_named_certs(self, names: Union[str, Tuple[str, ...]]) -> Tuple[str, ...]: """Load certs by the given name.""" if names == "__all__": - names = tuple(k for k, v in certs.items() if v.get("type") == "cert") + names = tuple(k for k, v in CERT_DATA.items() if v.get("type") == "cert") elif names == "__usable__": - names = tuple(k for k, v in certs.items() if v.get("type") == "cert" and v["cat"] == "generated") + names = tuple( + k for k, v in CERT_DATA.items() if v.get("type") == "cert" and v["cat"] == "generated" + ) elif isinstance(names, str): # pragma: no cover self.fail(f"{names}: Unknown alias for load_certs.") @@ -155,7 +157,7 @@ def load_named_certs(self, names: Union[str, Tuple[str, ...]]) -> Tuple[str, ... try: self.certs[name] = self.load_named_cert(name) except CertificateAuthority.DoesNotExist: # pragma: no cover - self.fail(f'{certs[name]["ca"]}: Could not load CertificateAuthority.') + self.fail(f'{CERT_DATA[name]["ca"]}: Could not load CertificateAuthority.') return names def absolute_uri(self, name: str, hostname: Optional[str] = None, **kwargs: Any) -> str: @@ -814,7 +816,7 @@ def crl_profiles(self) -> Dict[str, Dict[str, Any]]: for config in profiles.values(): config.setdefault("OVERRIDES", {}) - for data in [d for d in certs.values() if d.get("type") == "ca"]: + for data in [d for d in CERT_DATA.values() if d.get("type") == "ca"]: config["OVERRIDES"][data["serial"]] = {} if data.get("password"): config["OVERRIDES"][data["serial"]]["password"] = data["password"] @@ -898,11 +900,11 @@ def freeze_time( ) -> Iterator[Union[FrozenDateTimeFactory, StepTickTimeFactory]]: """Context manager to freeze time to a given timestamp. - If `timestamp` is a str that is in the `timestamps` dict (e.g. "everything-valid"), use that + If `timestamp` is a str that is in the `TIMESTAMPS` dict (e.g. "everything-valid"), use that timestamp. """ if isinstance(timestamp, str): # pragma: no branch - timestamp = timestamps[timestamp] + timestamp = TIMESTAMPS[timestamp] with freeze_time(timestamp) as frozen: yield frozen @@ -910,7 +912,8 @@ def freeze_time( def get_cert_context(self, name: str) -> Dict[str, Any]: """Get a dictionary suitable for testing output based on the dictionary in basic.certs.""" ctx: Dict[str, Any] = {} - for key, value in sorted(certs[name].items()): + + for key, value in sorted(CERT_DATA[name].items()): # Handle cryptography extensions if key == "precert_poison": ctx["precert_poison"] = "* Precert Poison (critical):\n Yes" @@ -921,27 +924,19 @@ def get_cert_context(self, name: str) -> Dict[str, Any]: ctx[f"{key}_critical"] = "" ctx[f"{key}_text"] = textwrap.indent(extension_as_text(value.value), " ") - elif key == "precertificate_signed_certificate_timestamps_serialized": - ctx["sct_critical"] = " (critical)" if value["critical"] else "" - ctx["sct_values"] = [] - for val in value["value"]: - ctx["sct_values"].append(val) - elif key == "precertificate_signed_certificate_timestamps": - continue # special extension b/c it cannot be created elif key == "path_length": ctx[key] = value ctx[f"{key}_text"] = "unlimited" if value is None else value else: ctx[key] = value - if certs[name].get("parent"): - parent = certs[certs[name]["parent"]] - ctx["parent_name"] = parent["name"] - ctx["parent_serial"] = parent["serial"] - ctx["parent_serial_colons"] = add_colons(parent["serial"]) + if parent := CERT_DATA[name].get("parent"): + ctx["parent_name"] = CERT_DATA[parent]["name"] + ctx["parent_serial"] = CERT_DATA[parent]["serial"] + ctx["parent_serial_colons"] = CERT_DATA[parent]["serial_colons"] - if certs[name]["key_filename"] is not False: - ctx["key_path"] = ca_storage.path(certs[name]["key_filename"]) + if CERT_DATA[name]["key_filename"] is not False: + ctx["key_path"] = ca_storage.path(CERT_DATA[name]["key_filename"]) return ctx @classmethod @@ -956,15 +951,15 @@ def load_ca( """Load a CA from one of the preloaded files.""" path = f"{name}.key" if parsed is None: - parsed = certs[name]["pub"]["parsed"] - if parent is None and certs[name].get("parent"): - parent = CertificateAuthority.objects.get(name=certs[name]["parent"]) + parsed = CERT_DATA[name]["pub"]["parsed"] + if parent is None and CERT_DATA[name].get("parent"): + parent = CertificateAuthority.objects.get(name=CERT_DATA[name]["parent"]) # set some default values - kwargs.setdefault("issuer_alt_name", certs[name].get("issuer_alternative_name", "")) - kwargs.setdefault("crl_url", certs[name].get("crl_url", "")) - kwargs.setdefault("ocsp_url", certs[name].get("ocsp_url", "")) - kwargs.setdefault("issuer_url", certs[name].get("issuer_url", "")) + kwargs.setdefault("issuer_alt_name", CERT_DATA[name].get("issuer_alternative_name", "")) + kwargs.setdefault("crl_url", CERT_DATA[name].get("crl_url", "")) + kwargs.setdefault("ocsp_url", CERT_DATA[name].get("ocsp_url", "")) + kwargs.setdefault("issuer_url", CERT_DATA[name].get("issuer_url", "")) ca = CertificateAuthority(name=name, private_key_path=path, enabled=enabled, parent=parent, **kwargs) ca.update_certificate(parsed) # calculates serial etc @@ -974,7 +969,7 @@ def load_ca( @classmethod def load_named_cert(cls, name: str) -> Certificate: """Load a certificate with the given mame.""" - data = certs[name] + data = CERT_DATA[name] ca = CertificateAuthority.objects.get(name=data["ca"]) csr = data.get("csr", {}).get("parsed", "") profile = data.get("profile", "") @@ -1062,14 +1057,14 @@ def reverse(self, name: str, *args: Any, **kwargs: Any) -> str: def usable_cas(self) -> Iterator[Tuple[str, CertificateAuthority]]: """Yield loaded generated certificates.""" for name, ca in self.cas.items(): - if certs[name]["key_filename"]: + if CERT_DATA[name]["key_filename"]: yield name, ca @property def usable_certs(self) -> Iterator[Tuple[str, Certificate]]: """Yield loaded generated certificates.""" for name, cert in self.certs.items(): - if certs[name]["cat"] == "generated": + if CERT_DATA[name]["cat"] == "generated": yield name, cert diff --git a/ca/django_ca/tests/base/typehints.py b/ca/django_ca/tests/base/typehints.py index 210455567..e7cf39b76 100644 --- a/ca/django_ca/tests/base/typehints.py +++ b/ca/django_ca/tests/base/typehints.py @@ -16,6 +16,9 @@ import typing from typing import Any, Dict +from cryptography import x509 +from cryptography.hazmat.primitives.asymmetric.types import CertificateIssuerPrivateKeyTypes + from django_ca.models import DjangoCAModel if typing.TYPE_CHECKING: @@ -57,4 +60,9 @@ class FixtureData(typing.TypedDict): certs: Dict[str, CertFixtureData] +KeyDict = typing.TypedDict("KeyDict", {"pem": str, "parsed": CertificateIssuerPrivateKeyTypes, "der": bytes}) +PubDict = typing.TypedDict("PubDict", {"pem": str, "parsed": x509.Certificate, "der": bytes}) +CsrDict = typing.TypedDict("CsrDict", {"pem": str, "parsed": x509.CertificateSigningRequest}) + + __all__ = ["HttpResponse", "User"] diff --git a/ca/django_ca/tests/base/utils.py b/ca/django_ca/tests/base/utils.py index 6f359d450..ad2701b22 100644 --- a/ca/django_ca/tests/base/utils.py +++ b/ca/django_ca/tests/base/utils.py @@ -12,14 +12,26 @@ # . """Utility functions used in testing.""" +import inspect +import ipaddress +import os +import shutil +import tempfile import typing +from contextlib import contextmanager from datetime import datetime -from typing import Iterable, Optional, Union +from typing import Any, Iterable, Iterator, Optional, Tuple, Union +from unittest.mock import patch from cryptography import x509 from cryptography.x509.oid import AuthorityInformationAccessOID, ExtensionOID +from django.test import override_settings + from django_ca.models import X509CertMixin +from django_ca.profiles import profiles +from django_ca.tests.base.constants import CERT_DATA, FIXTURES_DIR +from django_ca.utils import ca_storage def authority_information_access( @@ -186,3 +198,82 @@ def subject_key_identifier( def tls_feature(*features: x509.TLSFeatureType, critical: bool = False) -> x509.Extension[x509.TLSFeature]: """Shortcut for getting a TLSFeature extension.""" return x509.Extension(oid=ExtensionOID.TLS_FEATURE, critical=critical, value=x509.TLSFeature(features)) + + +FuncTypeVar = typing.TypeVar("FuncTypeVar", bound=typing.Callable[..., Any]) + + +def dns(name: str) -> x509.DNSName: # just a shortcut + """Shortcut to get a :py:class:`cg:cryptography.x509.DNSName`.""" + return x509.DNSName(name) + + +def uri(url: str) -> x509.UniformResourceIdentifier: # just a shortcut + """Shortcut to get a :py:class:`cg:cryptography.x509.UniformResourceIdentifier`.""" + return x509.UniformResourceIdentifier(url) + + +def ip( # pylint: disable=invalid-name # just a shortcut + name: Union[ipaddress.IPv4Address, ipaddress.IPv6Address, ipaddress.IPv4Network, ipaddress.IPv6Network] +) -> x509.IPAddress: + """Shortcut to get a :py:class:`cg:cryptography.x509.IPAddress`.""" + return x509.IPAddress(name) + + +def rdn( + name: Iterable[Tuple[x509.ObjectIdentifier, str]] +) -> x509.RelativeDistinguishedName: # just a shortcut + """Shortcut to get a :py:class:`cg:cryptography.x509.RelativeDistinguishedName`.""" + return x509.RelativeDistinguishedName([x509.NameAttribute(*t) for t in name]) + + +@contextmanager +def mock_cadir(path: str) -> Iterator[None]: + """Contextmanager to set the CA_DIR to a given path without actually creating it.""" + with override_settings(CA_DIR=path), patch.object(ca_storage, "location", path), patch.object( + ca_storage, "_location", path + ): + yield + + +class override_tmpcadir(override_settings): # pylint: disable=invalid-name; in line with parent class + """Sets the CA_DIR directory to a temporary directory. + + .. NOTE: This also takes any additional settings. + """ + + def __call__(self, test_func: FuncTypeVar) -> FuncTypeVar: + if not inspect.isfunction(test_func): + raise ValueError("Only functions can use override_tmpcadir()") + return super().__call__(test_func) # type: ignore[return-value] # cannot figure out what's here + + def enable(self) -> None: + self.options["CA_DIR"] = tempfile.mkdtemp() + + # copy CAs + for filename in [v["key_filename"] for v in CERT_DATA.values() if v["key_filename"] is not False]: + shutil.copy(os.path.join(FIXTURES_DIR, filename), self.options["CA_DIR"]) + + # Copy OCSP public key (required for OCSP tests) + shutil.copy( + os.path.join(FIXTURES_DIR, CERT_DATA["profile-ocsp"]["pub_filename"]), self.options["CA_DIR"] + ) + + # pylint: disable=attribute-defined-outside-init + self.mock = patch.object(ca_storage, "location", self.options["CA_DIR"]) + self.mock_ = patch.object(ca_storage, "_location", self.options["CA_DIR"]) + # pylint: enable=attribute-defined-outside-init + + # Reset profiles, so that they are loaded again on first access + profiles._reset() # pylint: disable=protected-access + + self.mock.start() + self.mock_.start() + + super().enable() + + def disable(self) -> None: + super().disable() + self.mock.stop() + self.mock_.stop() + shutil.rmtree(self.options["CA_DIR"]) diff --git a/ca/django_ca/tests/commands/test_cache_crls.py b/ca/django_ca/tests/commands/test_cache_crls.py index 7be753db1..9bba25c70 100644 --- a/ca/django_ca/tests/commands/test_cache_crls.py +++ b/ca/django_ca/tests/commands/test_cache_crls.py @@ -21,8 +21,9 @@ from freezegun import freeze_time -from django_ca.tests.base import override_tmpcadir, timestamps +from django_ca.tests.base.constants import TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin +from django_ca.tests.base.utils import override_tmpcadir from django_ca.utils import get_crl_cache_key @@ -32,7 +33,7 @@ class CacheCRLsTestCase(TestCaseMixin, TestCase): load_cas = "__usable__" @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_basic(self) -> None: """Test the basic command. diff --git a/ca/django_ca/tests/commands/test_convert_timestamps.py b/ca/django_ca/tests/commands/test_convert_timestamps.py index c7dedfc1c..3870a932f 100644 --- a/ca/django_ca/tests/commands/test_convert_timestamps.py +++ b/ca/django_ca/tests/commands/test_convert_timestamps.py @@ -19,14 +19,14 @@ from django_ca import ca_settings from django_ca.models import AcmeAccount, AcmeAuthorization, AcmeChallenge, AcmeOrder -from django_ca.tests.base import timestamps +from django_ca.tests.base.constants import TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin INPUT_PATH = "django_ca.management.commands.convert_timestamps.input" @override_settings(USE_TZ=False) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class ConvertTimestampsTestCase(TestCaseMixin, TestCase): """Test the convert_timestamps management command.""" @@ -40,11 +40,11 @@ def test_minimal_conversion(self) -> None: acme_auth = AcmeAuthorization.objects.create(order=acme_order) acme_challenge = AcmeChallenge.objects.create(auth=acme_auth) - self.assertEqual(self.ca.created, timestamps["everything_valid_naive"]) - self.assertEqual(self.cert.created, timestamps["everything_valid_naive"]) - self.assertEqual(acme_account.created, timestamps["everything_valid_naive"]) + self.assertEqual(self.ca.created, TIMESTAMPS["everything_valid_naive"]) + self.assertEqual(self.cert.created, TIMESTAMPS["everything_valid_naive"]) + self.assertEqual(acme_account.created, TIMESTAMPS["everything_valid_naive"]) self.assertEqual( - acme_order.expires, timestamps["everything_valid_naive"] + ca_settings.ACME_ORDER_VALIDITY + acme_order.expires, TIMESTAMPS["everything_valid_naive"] + ca_settings.ACME_ORDER_VALIDITY ) self.assertIsNone(acme_challenge.validated) @@ -57,11 +57,11 @@ def test_minimal_conversion(self) -> None: acme_order.refresh_from_db() acme_challenge.refresh_from_db() - self.assertEqual(self.ca.created, timestamps["everything_valid"]) - self.assertEqual(self.cert.created, timestamps["everything_valid"]) - self.assertEqual(acme_account.created, timestamps["everything_valid"]) + self.assertEqual(self.ca.created, TIMESTAMPS["everything_valid"]) + self.assertEqual(self.cert.created, TIMESTAMPS["everything_valid"]) + self.assertEqual(acme_account.created, TIMESTAMPS["everything_valid"]) self.assertEqual( - acme_order.expires, timestamps["everything_valid"] + ca_settings.ACME_ORDER_VALIDITY + acme_order.expires, TIMESTAMPS["everything_valid"] + ca_settings.ACME_ORDER_VALIDITY ) self.assertIsNone(self.ca.revoked_date) self.assertIsNone(self.ca.compromised) @@ -73,7 +73,7 @@ def test_minimal_conversion(self) -> None: def test_full_conversion(self) -> None: """Test conversion with all optional timestamps set.""" - now = timestamps["everything_valid_naive"] + now = TIMESTAMPS["everything_valid_naive"] self.ca.revoked_date = now self.ca.compromised = now self.ca.save() @@ -86,15 +86,15 @@ def test_full_conversion(self) -> None: acme_auth = AcmeAuthorization.objects.create(order=acme_order) acme_challenge = AcmeChallenge.objects.create(auth=acme_auth, validated=now) - self.assertEqual(self.ca.created, timestamps["everything_valid_naive"]) - self.assertEqual(self.ca.revoked_date, timestamps["everything_valid_naive"]) - self.assertEqual(self.ca.compromised, timestamps["everything_valid_naive"]) - self.assertEqual(self.cert.created, timestamps["everything_valid_naive"]) - self.assertEqual(self.cert.revoked_date, timestamps["everything_valid_naive"]) - self.assertEqual(self.cert.compromised, timestamps["everything_valid_naive"]) - self.assertEqual(acme_account.created, timestamps["everything_valid_naive"]) + self.assertEqual(self.ca.created, TIMESTAMPS["everything_valid_naive"]) + self.assertEqual(self.ca.revoked_date, TIMESTAMPS["everything_valid_naive"]) + self.assertEqual(self.ca.compromised, TIMESTAMPS["everything_valid_naive"]) + self.assertEqual(self.cert.created, TIMESTAMPS["everything_valid_naive"]) + self.assertEqual(self.cert.revoked_date, TIMESTAMPS["everything_valid_naive"]) + self.assertEqual(self.cert.compromised, TIMESTAMPS["everything_valid_naive"]) + self.assertEqual(acme_account.created, TIMESTAMPS["everything_valid_naive"]) self.assertEqual( - acme_order.expires, timestamps["everything_valid_naive"] + ca_settings.ACME_ORDER_VALIDITY + acme_order.expires, TIMESTAMPS["everything_valid_naive"] + ca_settings.ACME_ORDER_VALIDITY ) self.assertEqual(acme_order.not_before, now) self.assertEqual(acme_order.not_after, now) @@ -109,28 +109,28 @@ def test_full_conversion(self) -> None: acme_order.refresh_from_db() acme_challenge.refresh_from_db() - self.assertEqual(self.ca.created, timestamps["everything_valid"]) - self.assertEqual(self.ca.revoked_date, timestamps["everything_valid"]) - self.assertEqual(self.ca.compromised, timestamps["everything_valid"]) - self.assertEqual(self.cert.created, timestamps["everything_valid"]) - self.assertEqual(self.cert.revoked_date, timestamps["everything_valid"]) - self.assertEqual(self.cert.compromised, timestamps["everything_valid"]) - self.assertEqual(acme_account.created, timestamps["everything_valid"]) + self.assertEqual(self.ca.created, TIMESTAMPS["everything_valid"]) + self.assertEqual(self.ca.revoked_date, TIMESTAMPS["everything_valid"]) + self.assertEqual(self.ca.compromised, TIMESTAMPS["everything_valid"]) + self.assertEqual(self.cert.created, TIMESTAMPS["everything_valid"]) + self.assertEqual(self.cert.revoked_date, TIMESTAMPS["everything_valid"]) + self.assertEqual(self.cert.compromised, TIMESTAMPS["everything_valid"]) + self.assertEqual(acme_account.created, TIMESTAMPS["everything_valid"]) self.assertEqual( - acme_order.expires, timestamps["everything_valid"] + ca_settings.ACME_ORDER_VALIDITY + acme_order.expires, TIMESTAMPS["everything_valid"] + ca_settings.ACME_ORDER_VALIDITY ) - self.assertEqual(acme_order.not_before, timestamps["everything_valid"]) - self.assertEqual(acme_order.not_after, timestamps["everything_valid"]) - self.assertEqual(acme_challenge.validated, timestamps["everything_valid"]) + self.assertEqual(acme_order.not_before, TIMESTAMPS["everything_valid"]) + self.assertEqual(acme_order.not_after, TIMESTAMPS["everything_valid"]) + self.assertEqual(acme_challenge.validated, TIMESTAMPS["everything_valid"]) def test_no_confirmation(self) -> None: """Test that nothing happens if the user doesn't give confirmation.""" - self.assertEqual(self.ca.created, timestamps["everything_valid_naive"]) + self.assertEqual(self.ca.created, TIMESTAMPS["everything_valid_naive"]) with self.settings(USE_TZ=True), self.patch(INPUT_PATH, return_value="no"): out, err = self.cmd("convert_timestamps") self.assertIn("Aborting.", out) self.ca.refresh_from_db() - self.assertEqual(self.ca.created, timestamps["everything_valid_naive"]) + self.assertEqual(self.ca.created, TIMESTAMPS["everything_valid_naive"]) def test_use_tz_is_false(self) -> None: """Test error when USE_TZ=False.""" diff --git a/ca/django_ca/tests/commands/test_dump_ca.py b/ca/django_ca/tests/commands/test_dump_ca.py index 3684e8a18..76e19b5e8 100644 --- a/ca/django_ca/tests/commands/test_dump_ca.py +++ b/ca/django_ca/tests/commands/test_dump_ca.py @@ -22,8 +22,8 @@ from django.test import TestCase from django_ca import ca_settings -from django_ca.tests.base import override_tmpcadir from django_ca.tests.base.mixins import TestCaseMixin +from django_ca.tests.base.utils import override_tmpcadir class DumpCATestCase(TestCaseMixin, TestCase): diff --git a/ca/django_ca/tests/commands/test_dump_cert.py b/ca/django_ca/tests/commands/test_dump_cert.py index ce0c60c0d..b363393ac 100644 --- a/ca/django_ca/tests/commands/test_dump_cert.py +++ b/ca/django_ca/tests/commands/test_dump_cert.py @@ -22,8 +22,8 @@ from django.test import TestCase from django_ca import ca_settings -from django_ca.tests.base import override_tmpcadir from django_ca.tests.base.mixins import TestCaseMixin +from django_ca.tests.base.utils import override_tmpcadir class DumpCertTestCase(TestCaseMixin, TestCase): diff --git a/ca/django_ca/tests/commands/test_dump_crl.py b/ca/django_ca/tests/commands/test_dump_crl.py index c6352c8bf..cbb6b7243 100644 --- a/ca/django_ca/tests/commands/test_dump_crl.py +++ b/ca/django_ca/tests/commands/test_dump_crl.py @@ -30,11 +30,12 @@ from django_ca import ca_settings from django_ca.models import Certificate, CertificateAuthority -from django_ca.tests.base import certs, override_tmpcadir, timestamps +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin +from django_ca.tests.base.utils import override_tmpcadir -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class DumpCRLTestCase(TestCaseMixin, TestCase): """Test the dump_crl management command.""" @@ -157,7 +158,7 @@ def test_password(self) -> None: "dump_crl", ca=ca, scope="user", - password=certs["pwd"]["password"], + password=CERT_DATA["pwd"]["password"], stdout=BytesIO(), stderr=BytesIO(), ) diff --git a/ca/django_ca/tests/commands/test_edit_ca.py b/ca/django_ca/tests/commands/test_edit_ca.py index 0937d4cd7..c96e60bd1 100644 --- a/ca/django_ca/tests/commands/test_edit_ca.py +++ b/ca/django_ca/tests/commands/test_edit_ca.py @@ -18,8 +18,8 @@ from django_ca import ca_settings from django_ca.models import CertificateAuthority -from django_ca.tests.base import override_tmpcadir from django_ca.tests.base.mixins import TestCaseMixin +from django_ca.tests.base.utils import override_tmpcadir class EditCATestCase(TestCaseMixin, TestCase): diff --git a/ca/django_ca/tests/commands/test_import_ca.py b/ca/django_ca/tests/commands/test_import_ca.py index 59e29b556..f2476b603 100644 --- a/ca/django_ca/tests/commands/test_import_ca.py +++ b/ca/django_ca/tests/commands/test_import_ca.py @@ -28,9 +28,9 @@ from django_ca import ca_settings from django_ca.models import CertificateAuthority -from django_ca.tests.base import mock_cadir, override_tmpcadir, timestamps -from django_ca.tests.base.constants import CERT_DATA +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin +from django_ca.tests.base.utils import mock_cadir, override_tmpcadir class ImportCATest(TestCaseMixin, TestCase): @@ -48,7 +48,7 @@ def import_ca(self, *args: str) -> CertificateAuthority: return CertificateAuthority.objects.get(name=self.hostname) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_basic(self) -> None: """Test basic import command. @@ -103,7 +103,7 @@ def test_basic(self) -> None: self.assertIs(ca.acme_requires_contact, True) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_der(self) -> None: """Test importing a der key. @@ -155,7 +155,7 @@ def test_der(self) -> None: self.assertEqual(ca.serial, data["serial"]) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_password(self) -> None: """Test importing a CA with a password for the private key. diff --git a/ca/django_ca/tests/commands/test_import_cert.py b/ca/django_ca/tests/commands/test_import_cert.py index b1a9143fe..617a5640b 100644 --- a/ca/django_ca/tests/commands/test_import_cert.py +++ b/ca/django_ca/tests/commands/test_import_cert.py @@ -20,12 +20,12 @@ from freezegun import freeze_time from django_ca.models import Certificate -from django_ca.tests.base import certs, override_tmpcadir, timestamps -from django_ca.tests.base.constants import CERT_DATA +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin +from django_ca.tests.base.utils import override_tmpcadir -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class ImportCertTest(TestCaseMixin, TestCase): """Main test class for this command.""" @@ -39,7 +39,7 @@ def test_basic(self) -> None: self.assertEqual(out, "") self.assertEqual(err, "") - cert = Certificate.objects.get(serial=certs["root-cert"]["serial"]) + cert = Certificate.objects.get(serial=CERT_DATA["root-cert"]["serial"]) self.assertSignature([self.ca], cert) self.assertEqual(cert.ca, self.ca) cert.full_clean() # assert e.g. max_length in serials @@ -53,7 +53,7 @@ def test_der(self) -> None: self.assertEqual(out, "") self.assertEqual(err, "") - cert = Certificate.objects.get(serial=certs["root-cert"]["serial"]) + cert = Certificate.objects.get(serial=CERT_DATA["root-cert"]["serial"]) self.assertSignature([self.ca], cert) self.assertEqual(cert.ca, self.ca) cert.full_clean() # assert e.g. max_length in serials diff --git a/ca/django_ca/tests/commands/test_init_ca.py b/ca/django_ca/tests/commands/test_init_ca.py index 20854f6ee..9a34481d5 100644 --- a/ca/django_ca/tests/commands/test_init_ca.py +++ b/ca/django_ca/tests/commands/test_init_ca.py @@ -36,7 +36,7 @@ from django_ca import ca_settings from django_ca.constants import ExtendedKeyUsageOID from django_ca.models import Certificate, CertificateAuthority -from django_ca.tests.base import dns, override_tmpcadir, timestamps, uri +from django_ca.tests.base.constants import TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin from django_ca.tests.base.utils import ( authority_information_access, @@ -44,12 +44,15 @@ certificate_policies, crl_distribution_points, distribution_point, + dns, extended_key_usage, issuer_alternative_name, key_usage, name_constraints, ocsp_no_check, + override_tmpcadir, subject_alternative_name, + uri, ) from django_ca.utils import get_crl_cache_key, int_to_hex, x509_name @@ -95,7 +98,7 @@ def init_ca_e2e( return ca @override_tmpcadir(CA_MIN_KEY_SIZE=1024) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_basic(self) -> None: """Basic tests for the command.""" name = "test_basic" @@ -1040,7 +1043,7 @@ def test_password(self) -> None: self.assertEqual(key.key_size, 1024) @override_tmpcadir(CA_MIN_KEY_SIZE=1024) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_default_hostname(self) -> None: """Test manually passing a default hostname. diff --git a/ca/django_ca/tests/commands/test_list_cas.py b/ca/django_ca/tests/commands/test_list_cas.py index 34a83e1bd..8b2cfb9d8 100644 --- a/ca/django_ca/tests/commands/test_list_cas.py +++ b/ca/django_ca/tests/commands/test_list_cas.py @@ -22,7 +22,7 @@ from freezegun import freeze_time from django_ca.models import CertificateAuthority -from django_ca.tests.base import certs, timestamps +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin EXPECTED = """{dsa[serial_colons]} - {dsa[name]}{dsa_state} @@ -44,49 +44,49 @@ def assertOutput( # pylint: disable=invalid-name self, output: str, expected: str, **context: Any ) -> None: """Assert the output of this command.""" - context.update(certs) + context.update(CERT_DATA) for ca_name in self.cas: context.setdefault(f"{ca_name}_state", "") self.assertEqual(output, expected.format(**context)) def test_all_cas(self) -> None: """Test list with all CAs.""" - for name in [k for k, v in certs.items() if v.get("type") == "ca" and k not in self.cas]: + for name in [k for k, v in CERT_DATA.items() if v.get("type") == "ca" and k not in self.cas]: self.load_ca(name) stdout, stderr = self.cmd("list_cas") self.assertEqual( stdout, - f"""{certs['letsencrypt_x1']['serial_colons']} - {certs['letsencrypt_x1']['name']} -{certs['letsencrypt_x3']['serial_colons']} - {certs['letsencrypt_x3']['name']} -{certs['dst_root_x3']['serial_colons']} - {certs['dst_root_x3']['name']} -{certs['google_g3']['serial_colons']} - {certs['google_g3']['name']} -{certs['globalsign_r2_root']['serial_colons']} - {certs['globalsign_r2_root']['name']} -{certs['trustid_server_a52']['serial_colons']} - {certs['trustid_server_a52']['name']} -{certs['rapidssl_g3']['serial_colons']} - {certs['rapidssl_g3']['name']} -{certs['geotrust']['serial_colons']} - {certs['geotrust']['name']} -{certs['startssl_class2']['serial_colons']} - {certs['startssl_class2']['name']} -{certs['digicert_sha2']['serial_colons']} - {certs['digicert_sha2']['name']} -{certs['dsa']['serial_colons']} - {certs['dsa']['name']} -{certs['ec']['serial_colons']} - {certs['ec']['name']} -{certs['ed25519']['serial_colons']} - {certs['ed25519']['name']} -{certs['ed448']['serial_colons']} - {certs['ed448']['name']} -{certs['pwd']['serial_colons']} - {certs['pwd']['name']} -{certs['root']['serial_colons']} - {certs['root']['name']} -{certs['child']['serial_colons']} - {certs['child']['name']} -{certs['globalsign_dv']['serial_colons']} - {certs['globalsign_dv']['name']} -{certs['comodo_ev']['serial_colons']} - {certs['comodo_ev']['name']} -{certs['globalsign']['serial_colons']} - {certs['globalsign']['name']} -{certs['digicert_ha_intermediate']['serial_colons']} - {certs['digicert_ha_intermediate']['name']} -{certs['comodo_dv']['serial_colons']} - {certs['comodo_dv']['name']} -{certs['startssl_class3']['serial_colons']} - {certs['startssl_class3']['name']} -{certs['godaddy_g2_intermediate']['serial_colons']} - {certs['godaddy_g2_intermediate']['name']} -{certs['digicert_ev_root']['serial_colons']} - {certs['digicert_ev_root']['name']} -{certs['digicert_global_root']['serial_colons']} - {certs['digicert_global_root']['name']} -{certs['identrust_root_1']['serial_colons']} - {certs['identrust_root_1']['name']} -{certs['startssl_root']['serial_colons']} - {certs['startssl_root']['name']} -{certs['godaddy_g2_root']['serial_colons']} - {certs['godaddy_g2_root']['name']} -{certs['comodo']['serial_colons']} - {certs['comodo']['name']} + f"""{CERT_DATA['letsencrypt_x1']['serial_colons']} - {CERT_DATA['letsencrypt_x1']['name']} +{CERT_DATA['letsencrypt_x3']['serial_colons']} - {CERT_DATA['letsencrypt_x3']['name']} +{CERT_DATA['dst_root_x3']['serial_colons']} - {CERT_DATA['dst_root_x3']['name']} +{CERT_DATA['google_g3']['serial_colons']} - {CERT_DATA['google_g3']['name']} +{CERT_DATA['globalsign_r2_root']['serial_colons']} - {CERT_DATA['globalsign_r2_root']['name']} +{CERT_DATA['trustid_server_a52']['serial_colons']} - {CERT_DATA['trustid_server_a52']['name']} +{CERT_DATA['rapidssl_g3']['serial_colons']} - {CERT_DATA['rapidssl_g3']['name']} +{CERT_DATA['geotrust']['serial_colons']} - {CERT_DATA['geotrust']['name']} +{CERT_DATA['startssl_class2']['serial_colons']} - {CERT_DATA['startssl_class2']['name']} +{CERT_DATA['digicert_sha2']['serial_colons']} - {CERT_DATA['digicert_sha2']['name']} +{CERT_DATA['dsa']['serial_colons']} - {CERT_DATA['dsa']['name']} +{CERT_DATA['ec']['serial_colons']} - {CERT_DATA['ec']['name']} +{CERT_DATA['ed25519']['serial_colons']} - {CERT_DATA['ed25519']['name']} +{CERT_DATA['ed448']['serial_colons']} - {CERT_DATA['ed448']['name']} +{CERT_DATA['pwd']['serial_colons']} - {CERT_DATA['pwd']['name']} +{CERT_DATA['root']['serial_colons']} - {CERT_DATA['root']['name']} +{CERT_DATA['child']['serial_colons']} - {CERT_DATA['child']['name']} +{CERT_DATA['globalsign_dv']['serial_colons']} - {CERT_DATA['globalsign_dv']['name']} +{CERT_DATA['comodo_ev']['serial_colons']} - {CERT_DATA['comodo_ev']['name']} +{CERT_DATA['globalsign']['serial_colons']} - {CERT_DATA['globalsign']['name']} +{CERT_DATA['digicert_ha_intermediate']['serial_colons']} - {CERT_DATA['digicert_ha_intermediate']['name']} +{CERT_DATA['comodo_dv']['serial_colons']} - {CERT_DATA['comodo_dv']['name']} +{CERT_DATA['startssl_class3']['serial_colons']} - {CERT_DATA['startssl_class3']['name']} +{CERT_DATA['godaddy_g2_intermediate']['serial_colons']} - {CERT_DATA['godaddy_g2_intermediate']['name']} +{CERT_DATA['digicert_ev_root']['serial_colons']} - {CERT_DATA['digicert_ev_root']['name']} +{CERT_DATA['digicert_global_root']['serial_colons']} - {CERT_DATA['digicert_global_root']['name']} +{CERT_DATA['identrust_root_1']['serial_colons']} - {CERT_DATA['identrust_root_1']['name']} +{CERT_DATA['startssl_root']['serial_colons']} - {CERT_DATA['startssl_root']['name']} +{CERT_DATA['godaddy_g2_root']['serial_colons']} - {CERT_DATA['godaddy_g2_root']['name']} +{CERT_DATA['comodo']['serial_colons']} - {CERT_DATA['comodo']['name']} """, ) self.assertEqual(stderr, "") @@ -113,7 +113,7 @@ def test_disabled(self) -> None: self.assertOutput(stdout, EXPECTED, child_state=" (disabled)") self.assertEqual(stderr, "") - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_tree(self) -> None: """Test the tree output. @@ -122,13 +122,13 @@ def test_tree(self) -> None: stdout, stderr = self.cmd("list_cas", tree=True) self.assertEqual( stdout, - f"""{certs['dsa']['serial_colons']} - {certs['dsa']['name']} -{certs['ec']['serial_colons']} - {certs['ec']['name']} -{certs['ed25519']['serial_colons']} - {certs['ed25519']['name']} -{certs['ed448']['serial_colons']} - {certs['ed448']['name']} -{certs['pwd']['serial_colons']} - {certs['pwd']['name']} -{certs['root']['serial_colons']} - {certs['root']['name']} -└───{certs['child']['serial_colons']} - {certs['child']['name']} + f"""{CERT_DATA['dsa']['serial_colons']} - {CERT_DATA['dsa']['name']} +{CERT_DATA['ec']['serial_colons']} - {CERT_DATA['ec']['name']} +{CERT_DATA['ed25519']['serial_colons']} - {CERT_DATA['ed25519']['name']} +{CERT_DATA['ed448']['serial_colons']} - {CERT_DATA['ed448']['name']} +{CERT_DATA['pwd']['serial_colons']} - {CERT_DATA['pwd']['name']} +{CERT_DATA['root']['serial_colons']} - {CERT_DATA['root']['name']} +└───{CERT_DATA['child']['serial_colons']} - {CERT_DATA['child']['name']} """, ) self.assertEqual(stderr, "") @@ -137,7 +137,7 @@ def test_tree(self) -> None: expires = timezone.now() + timedelta(days=3) valid_from = timezone.now() - timedelta(days=3) root = self.cas["root"] - pub = certs["child-cert"]["pub"]["parsed"] + pub = CERT_DATA["child-cert"]["pub"]["parsed"] child3 = CertificateAuthority.objects.create( name="child3", serial="child3", parent=root, expires=expires, valid_from=valid_from, pub=pub ) @@ -151,15 +151,15 @@ def test_tree(self) -> None: stdout, stderr = self.cmd("list_cas", tree=True) self.assertEqual( stdout, - f"""{certs['dsa']['serial_colons']} - {certs['dsa']['name']} -{certs['ec']['serial_colons']} - {certs['ec']['name']} -{certs['ed25519']['serial_colons']} - {certs['ed25519']['name']} -{certs['ed448']['serial_colons']} - {certs['ed448']['name']} -{certs['pwd']['serial_colons']} - {certs['pwd']['name']} -{certs['root']['serial_colons']} - {certs['root']['name']} + f"""{CERT_DATA['dsa']['serial_colons']} - {CERT_DATA['dsa']['name']} +{CERT_DATA['ec']['serial_colons']} - {CERT_DATA['ec']['name']} +{CERT_DATA['ed25519']['serial_colons']} - {CERT_DATA['ed25519']['name']} +{CERT_DATA['ed448']['serial_colons']} - {CERT_DATA['ed448']['name']} +{CERT_DATA['pwd']['serial_colons']} - {CERT_DATA['pwd']['name']} +{CERT_DATA['root']['serial_colons']} - {CERT_DATA['root']['name']} │───ch:il:d3 - child3 │ └───ch:il:d3:.1 - child3.1 │───ch:il:d4 - child4 -└───{certs['child']['serial_colons']} - {certs['child']['name']} +└───{CERT_DATA['child']['serial_colons']} - {CERT_DATA['child']['name']} """, ) diff --git a/ca/django_ca/tests/commands/test_list_certs.py b/ca/django_ca/tests/commands/test_list_certs.py index 1a894a2f2..236c94787 100644 --- a/ca/django_ca/tests/commands/test_list_certs.py +++ b/ca/django_ca/tests/commands/test_list_certs.py @@ -21,7 +21,7 @@ from freezegun import freeze_time from django_ca.models import Certificate -from django_ca.tests.base import timestamps +from django_ca.tests.base.constants import TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin from django_ca.utils import add_colons @@ -51,18 +51,18 @@ def assertCerts(self, *certs: Certificate, **kwargs: Any) -> None: # pylint: di self.assertEqual(stdout, "".join([f"{self._line(c)}\n" for c in sorted_certs])) self.assertEqual(stderr, "") - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_basic(self) -> None: """Basic test.""" self.assertCerts(*self.certs.values()) - @freeze_time(timestamps["everything_expired"]) + @freeze_time(TIMESTAMPS["everything_expired"]) def test_expired(self) -> None: """Test listing of expired certs.""" self.assertCerts() self.assertCerts(*self.certs.values(), expired=True) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_revoked(self) -> None: """Test listing of revoked certs.""" cert = self.certs["root-cert"] @@ -71,7 +71,7 @@ def test_revoked(self) -> None: self.assertCerts(*[c for c in self.certs.values() if c != cert]) self.assertCerts(*self.certs.values(), revoked=True) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_autogenerated(self) -> None: """Test listing of autogenerated certs.""" cert = self.certs["root-cert"] @@ -81,7 +81,7 @@ def test_autogenerated(self) -> None: self.assertCerts(*[c for c in self.certs.values() if c != cert]) self.assertCerts(*self.certs.values(), autogenerated=True) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_ca(self) -> None: """Test listing for all CAs.""" for ca in self.cas.values(): diff --git a/ca/django_ca/tests/commands/test_notify.py b/ca/django_ca/tests/commands/test_notify.py index dbe277b8b..eb0573693 100644 --- a/ca/django_ca/tests/commands/test_notify.py +++ b/ca/django_ca/tests/commands/test_notify.py @@ -21,7 +21,7 @@ from freezegun import freeze_time from django_ca.models import Watcher -from django_ca.tests.base import timestamps +from django_ca.tests.base.constants import TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin @@ -32,7 +32,7 @@ class NotifyExpiringCertsTestCase(TestCaseMixin, TestCase): load_cas = "__usable__" load_certs = "__usable__" - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_no_certs(self) -> None: """Try notify command when all certs are still valid.""" stdout, stderr = self.cmd("notify_expiring_certs") @@ -40,7 +40,7 @@ def test_no_certs(self) -> None: self.assertEqual(stderr, "") self.assertEqual(len(mail.outbox), 0) - @freeze_time(timestamps["ca_certs_expiring"]) + @freeze_time(TIMESTAMPS["ca_certs_expiring"]) def test_no_watchers(self) -> None: """Try expiring certs, but with no watchers.""" # certs have no watchers by default, so we get no mails @@ -49,7 +49,7 @@ def test_no_watchers(self) -> None: self.assertEqual(stderr, "") self.assertEqual(len(mail.outbox), 0) - @freeze_time(timestamps["ca_certs_expiring"]) + @freeze_time(TIMESTAMPS["ca_certs_expiring"]) def test_one_watcher(self) -> None: """Test one expiring certificate.""" email = "user1@example.com" diff --git a/ca/django_ca/tests/commands/test_regenerate_ocsp_keys.py b/ca/django_ca/tests/commands/test_regenerate_ocsp_keys.py index a32c6dfda..1d257cc7e 100644 --- a/ca/django_ca/tests/commands/test_regenerate_ocsp_keys.py +++ b/ca/django_ca/tests/commands/test_regenerate_ocsp_keys.py @@ -25,9 +25,9 @@ from django.test import TestCase from django_ca.models import Certificate, CertificateAuthority -from django_ca.tests.base import certs, override_tmpcadir, uri +from django_ca.tests.base.constants import CERT_DATA from django_ca.tests.base.mixins import TestCaseMixin -from django_ca.tests.base.utils import authority_information_access +from django_ca.tests.base.utils import authority_information_access, override_tmpcadir, uri from django_ca.utils import add_colons, ca_storage @@ -100,7 +100,7 @@ def assertHasNoKey(self, serial: str) -> None: # pylint: disable=invalid-name def test_basic(self) -> None: """Basic test.""" with self.mute_celery(): - stdout, stderr = self.cmd("regenerate_ocsp_keys", certs["root"]["serial"]) + stdout, stderr = self.cmd("regenerate_ocsp_keys", CERT_DATA["root"]["serial"]) self.assertEqual(stdout, "") self.assertEqual(stderr, "") @@ -111,7 +111,7 @@ def test_rsa_with_key_size(self) -> None: """Test creating an RSA key with explicit key size.""" with self.mute_celery(): stdout, stderr = self.cmd( - "regenerate_ocsp_keys", certs["root"]["serial"], key_type="RSA", key_size=4096 + "regenerate_ocsp_keys", CERT_DATA["root"]["serial"], key_type="RSA", key_size=4096 ) self.assertEqual(stdout, "") @@ -123,7 +123,7 @@ def test_ec_with_curve(self) -> None: """Test creating an EC key with explicit elliptic curve.""" with self.mute_celery(): stdout, stderr = self.cmd( - "regenerate_ocsp_keys", certs["ec"]["serial"], elliptic_curve=ec.SECP384R1() + "regenerate_ocsp_keys", CERT_DATA["ec"]["serial"], elliptic_curve=ec.SECP384R1() ) self.assertEqual(stdout, "") @@ -135,7 +135,7 @@ def test_hash_algorithm(self) -> None: """Test the hash algorithm option.""" with self.mute_celery(): stdout, stderr = self.cmd( - "regenerate_ocsp_keys", certs["root"]["serial"], "--algorithm", "SHA-256" + "regenerate_ocsp_keys", CERT_DATA["root"]["serial"], "--algorithm", "SHA-256" ) self.assertEqual(stdout, "") @@ -148,7 +148,7 @@ def test_with_celery(self) -> None: with self.mute_celery( ( ( - (certs["root"]["serial"],), + (CERT_DATA["root"]["serial"],), { "profile": "ocsp", "expires": 172800.0, @@ -164,7 +164,7 @@ def test_with_celery(self) -> None: ), ): stdout, stderr = self.cmd_e2e( - ["regenerate_ocsp_keys", certs["root"]["serial"], "--algorithm", "SHA-256"] + ["regenerate_ocsp_keys", CERT_DATA["root"]["serial"], "--algorithm", "SHA-256"] ) self.assertEqual(stdout, "") self.assertEqual(stderr, "") @@ -173,7 +173,7 @@ def test_with_celery(self) -> None: def test_with_ed448_with_explicit_key_type(self) -> None: """Test creating an Ed448-based OCSP key for an RSA-based CA.""" stdout, stderr = self.cmd_e2e( - ["regenerate_ocsp_keys", certs["root"]["serial"], "--key-type", "Ed448"] + ["regenerate_ocsp_keys", CERT_DATA["root"]["serial"], "--key-type", "Ed448"] ) self.assertEqual(stdout, "") self.assertEqual(stderr, "") @@ -196,7 +196,7 @@ def test_all(self) -> None: @override_tmpcadir() def test_overwrite(self) -> None: """Test overwriting pre-generated OCSP keys.""" - stdout, stderr = self.cmd("regenerate_ocsp_keys", certs["root"]["serial"]) + stdout, stderr = self.cmd("regenerate_ocsp_keys", CERT_DATA["root"]["serial"]) self.assertEqual(stdout, "") self.assertEqual(stderr, "") priv, cert = self.assertKey(self.cas["root"]) @@ -205,7 +205,7 @@ def test_overwrite(self) -> None: excludes = list(Certificate.objects.all().values_list("pk", flat=True)) # write again - stdout, stderr = self.cmd("regenerate_ocsp_keys", certs["root"]["serial"], force=True) + stdout, stderr = self.cmd("regenerate_ocsp_keys", CERT_DATA["root"]["serial"], force=True) self.assertEqual(stdout, "") self.assertEqual(stderr, "") new_priv, new_cert = self.assertKey(self.cas["root"], excludes=excludes) @@ -227,8 +227,8 @@ def test_wrong_serial(self) -> None: def test_no_ocsp_profile(self) -> None: """Try when there is no OCSP profile.""" with self.assertCommandError(r"^ocsp: Undefined profile\.$"): - self.cmd("regenerate_ocsp_keys", certs["root"]["serial"]) - self.assertHasNoKey(certs["root"]["serial"]) + self.cmd("regenerate_ocsp_keys", CERT_DATA["root"]["serial"]) + self.assertHasNoKey(CERT_DATA["root"]["serial"]) @override_tmpcadir() def test_no_private_key(self) -> None: diff --git a/ca/django_ca/tests/commands/test_resign_cert.py b/ca/django_ca/tests/commands/test_resign_cert.py index 5b06ef100..1fdfd5625 100644 --- a/ca/django_ca/tests/commands/test_resign_cert.py +++ b/ca/django_ca/tests/commands/test_resign_cert.py @@ -29,23 +29,26 @@ from django_ca import ca_settings from django_ca.models import Certificate, CertificateAuthority, Watcher -from django_ca.tests.base import dns, override_tmpcadir, timestamps, uri +from django_ca.tests.base.constants import TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin from django_ca.tests.base.utils import ( basic_constraints, certificate_policies, crl_distribution_points, distribution_point, + dns, extended_key_usage, issuer_alternative_name, key_usage, ocsp_no_check, + override_tmpcadir, subject_alternative_name, tls_feature, + uri, ) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class ResignCertTestCase(TestCaseMixin, TestCase): """Main test class for this command.""" diff --git a/ca/django_ca/tests/commands/test_sign_cert.py b/ca/django_ca/tests/commands/test_sign_cert.py index 636df5863..23b6900d7 100644 --- a/ca/django_ca/tests/commands/test_sign_cert.py +++ b/ca/django_ca/tests/commands/test_sign_cert.py @@ -34,25 +34,28 @@ from django_ca import ca_settings from django_ca.models import Certificate, CertificateAuthority -from django_ca.tests.base import certs, dns, override_tmpcadir, timestamps, uri +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin from django_ca.tests.base.utils import ( authority_information_access, certificate_policies, crl_distribution_points, distribution_point, + dns, extended_key_usage, issuer_alternative_name, key_usage, ocsp_no_check, + override_tmpcadir, subject_alternative_name, tls_feature, + uri, ) from django_ca.utils import ca_storage @override_settings(CA_MIN_KEY_SIZE=1024, CA_PROFILES={}, CA_DEFAULT_SUBJECT=tuple()) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class SignCertTestCase(TestCaseMixin, TestCase): # pylint: disable=too-many-public-methods """Main test class for this command.""" @@ -61,7 +64,7 @@ class SignCertTestCase(TestCaseMixin, TestCase): # pylint: disable=too-many-pub def setUp(self) -> None: super().setUp() - self.csr_pem = certs["root-cert"]["csr"]["pem"] + self.csr_pem = CERT_DATA["root-cert"]["csr"]["pem"] @override_tmpcadir() def test_from_stdin(self) -> None: @@ -104,9 +107,9 @@ def test_with_bundle(self) -> None: def test_usable_cas(self) -> None: """Test signing with all usable CAs.""" for name, ca in self.cas.items(): - stdin = certs[f"{name}-cert"]["csr"]["pem"].encode() + stdin = CERT_DATA[f"{name}-cert"]["csr"]["pem"].encode() - password = certs[name].get("password") + password = CERT_DATA[name].get("password") with self.assertCreateCertSignals() as (pre, post): stdout, stderr = self.cmd( @@ -707,7 +710,7 @@ def test_der_csr(self) -> None: """Test using a DER CSR.""" csr_path = os.path.join(ca_settings.CA_DIR, "test.csr") with open(csr_path, "wb") as csr_stream: - csr_stream.write(certs["child-cert"]["csr"]["parsed"].public_bytes(Encoding.DER)) + csr_stream.write(CERT_DATA["child-cert"]["csr"]["parsed"].public_bytes(Encoding.DER)) with self.assertCreateCertSignals() as (pre, post): stdout, stderr = self.cmd("sign_cert", ca=self.ca, subject=self.subject, csr=csr_path) @@ -781,7 +784,7 @@ def test_unusable_ca(self) -> None: self.cmd("sign_cert", ca=self.ca, subject=self.subject, stdin=stdin) @override_tmpcadir() - @freeze_time(timestamps["everything_expired"]) + @freeze_time(TIMESTAMPS["everything_expired"]) def test_expired_ca(self) -> None: """Test signing with an expired CA.""" stdin = io.StringIO(self.csr_pem) diff --git a/ca/django_ca/tests/commands/test_view_ca.py b/ca/django_ca/tests/commands/test_view_ca.py index 5a42e09a0..89c01c832 100644 --- a/ca/django_ca/tests/commands/test_view_ca.py +++ b/ca/django_ca/tests/commands/test_view_ca.py @@ -22,8 +22,8 @@ from django.conf import settings from django.test import TestCase -from django_ca.tests.base import override_tmpcadir from django_ca.tests.base.mixins import TestCaseMixin +from django_ca.tests.base.utils import override_tmpcadir expected = { "ec": """* Name: {name} diff --git a/ca/django_ca/tests/commands/test_view_cert.py b/ca/django_ca/tests/commands/test_view_cert.py index c94821446..e42a3575a 100644 --- a/ca/django_ca/tests/commands/test_view_cert.py +++ b/ca/django_ca/tests/commands/test_view_cert.py @@ -24,8 +24,9 @@ from freezegun import freeze_time from django_ca.models import Watcher -from django_ca.tests.base import certs, override_tmpcadir, timestamps +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin +from django_ca.tests.base.utils import override_tmpcadir expected = { "root-cert": """* Subject: {subject_str} @@ -583,17 +584,17 @@ def assertBasicOutput(self, status: str) -> None: # pylint: disable=invalid-nam ) self.assertEqual(stderr, "") - @freeze_time(timestamps["before_everything"]) + @freeze_time(TIMESTAMPS["before_everything"]) def test_basic_not_yet_valid(self) -> None: """Basic tests when all certs are not yet valid.""" self.assertBasicOutput(status="Not yet valid") - @freeze_time(timestamps["everything_expired"]) + @freeze_time(TIMESTAMPS["everything_expired"]) def test_basic_expired(self) -> None: """Basic tests when all certs are expired.""" self.assertBasicOutput(status="Expired") - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_certs(self) -> None: """Test main certs.""" for name, cert in self.usable_certs: @@ -623,12 +624,12 @@ def test_revoked(self) -> None: SHA-256: {sha256} SHA-512: {sha512} """.format( - **certs["child-cert"] + **CERT_DATA["child-cert"] ), ) self.assertEqual(stderr, "") - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) @override_tmpcadir() def test_no_san_with_watchers(self) -> None: """Test a cert with no subjectAltNames but with watchers.""" @@ -733,13 +734,13 @@ def test_contrib_letsencrypt_jabber_at(self) -> None: context[ "id2" ] = "29:3C:51:96:54:C8:39:65:BA:AA:50:FC:58:07:D4:B7:6F:BF:58:7A:29:72:DC:A4:C3:0C:F4:E5:45:47:F4:78" # NOQA: E501 - sct = """* Precertificate Signed Certificate Timestamps{sct_critical}: - * Precertificate ({sct_values[0][version]}): - Timestamp: {sct_values[0][timestamp]} + sct = """* Precertificate Signed Certificate Timestamps{precertificate_signed_certificate_timestamps_critical}: + * Precertificate ({precertificate_signed_certificate_timestamps_serialized[0][version]}): + Timestamp: {precertificate_signed_certificate_timestamps_serialized[0][timestamp]} Log ID: {id1} - * Precertificate ({sct_values[1][version]}): - Timestamp: {sct_values[1][timestamp]} - Log ID: {id2}""".format( + * Precertificate ({precertificate_signed_certificate_timestamps_serialized[1][version]}): + Timestamp: {precertificate_signed_certificate_timestamps_serialized[1][timestamp]} + Log ID: {id2}""".format( # NOQA: E501 **context ) diff --git a/ca/django_ca/tests/conftest.py b/ca/django_ca/tests/conftest.py index 1fdde9d0d..61d1fc5ab 100644 --- a/ca/django_ca/tests/conftest.py +++ b/ca/django_ca/tests/conftest.py @@ -34,7 +34,6 @@ from django_ca.models import Certificate from django_ca.profiles import profiles -from django_ca.tests.base import GECKODRIVER_PATH, RUN_SELENIUM_TESTS from django_ca.tests.base.conftest_helpers import ( generate_ca_fixture, generate_cert_fixture, @@ -45,6 +44,7 @@ usable_ca_names, usable_cert_names, ) +from django_ca.tests.base.constants import GECKODRIVER_PATH, RUN_SELENIUM_TESTS from django_ca.tests.base.typehints import User from django_ca.utils import ca_storage diff --git a/ca/django_ca/tests/extensions/test_admin_html.py b/ca/django_ca/tests/extensions/test_admin_html.py index f81ed504d..d2faaf295 100644 --- a/ca/django_ca/tests/extensions/test_admin_html.py +++ b/ca/django_ca/tests/extensions/test_admin_html.py @@ -24,7 +24,7 @@ from django_ca.constants import KEY_USAGE_NAMES from django_ca.extensions.utils import extension_as_admin_html from django_ca.models import X509CertMixin -from django_ca.tests.base import certs +from django_ca.tests.base.constants import CERT_DATA from django_ca.tests.base.mixins import TestCaseMixin @@ -36,23 +36,23 @@ class CertificateExtensionTestCase(TestCaseMixin, TestCase): admin_html: Dict[str, Dict[x509.ObjectIdentifier, str]] = { "root": {}, "child": { - ExtensionOID.BASIC_CONSTRAINTS: f"CA: True, path length: {certs['child']['path_length']}", + ExtensionOID.BASIC_CONSTRAINTS: f"CA: True, path length: {CERT_DATA['child']['path_length']}", }, "ec": { - ExtensionOID.BASIC_CONSTRAINTS: f"CA: True, path length: {certs['ec']['path_length']}", + ExtensionOID.BASIC_CONSTRAINTS: f"CA: True, path length: {CERT_DATA['ec']['path_length']}", }, "dsa": { - ExtensionOID.BASIC_CONSTRAINTS: f"CA: True, path length: {certs['dsa']['path_length']}", - ExtensionOID.SUBJECT_KEY_IDENTIFIER: certs["dsa"]["subject_key_identifier_serialized"]["value"], + ExtensionOID.BASIC_CONSTRAINTS: f"CA: True, path length: {CERT_DATA['dsa']['path_length']}", + ExtensionOID.SUBJECT_KEY_IDENTIFIER: CERT_DATA["dsa"]["subject_key_identifier_serialized"], }, "pwd": { - ExtensionOID.BASIC_CONSTRAINTS: f"CA: True, path length: {certs['pwd']['path_length']}", + ExtensionOID.BASIC_CONSTRAINTS: f"CA: True, path length: {CERT_DATA['pwd']['path_length']}", }, "ed25519": { - ExtensionOID.BASIC_CONSTRAINTS: f"CA: True, path length: {certs['ed25519']['path_length']}", + ExtensionOID.BASIC_CONSTRAINTS: f"CA: True, path length: {CERT_DATA['ed25519']['path_length']}", }, "ed448": { - ExtensionOID.BASIC_CONSTRAINTS: f"CA: True, path length: {certs['ed448']['path_length']}", + ExtensionOID.BASIC_CONSTRAINTS: f"CA: True, path length: {CERT_DATA['ed448']['path_length']}", }, "trustid_server_a52": { ExtensionOID.CERTIFICATE_POLICIES: """
    @@ -228,7 +228,7 @@ class CertificateExtensionTestCase(TestCaseMixin, TestCase): ########################## "all-extensions": { ExtensionOID.FRESHEST_CRL: f"""DistributionPoint:
      -
    • Full Name: {certs['all-extensions']['freshest_crl_serialized']['value'][0]['full_name'][0]}
    • +
    • Full Name: {CERT_DATA['all-extensions']['freshest_crl_serialized'][0]['full_name'][0]}
    """, # NOQA: E501 ExtensionOID.INHIBIT_ANY_POLICY: "skip certs: 1", ExtensionOID.NAME_CONSTRAINTS: """Permitted:
    • DNS:.org
    @@ -606,32 +606,30 @@ def setUpCert(self, name: str) -> None: # pylint: disable=invalid-name """Set up default values for certificates.""" self.admin_html.setdefault(name, {}) - config = certs[name] + config = CERT_DATA[name] if config.get("subject_alternative_name_serialized"): - sans = [f"
  • {san}
  • " for san in config["subject_alternative_name_serialized"]["value"]] + sans = [f"
  • {san}
  • " for san in config["subject_alternative_name_serialized"]] self.admin_html[name].setdefault( ExtensionOID.SUBJECT_ALTERNATIVE_NAME, f"
      {''.join(sans)}
    " ) if config.get("issuer_alternative_name_serialized"): - sans = [f"
  • {san}
  • " for san in config["issuer_alternative_name_serialized"]["value"]] + sans = [f"
  • {san}
  • " for san in config["issuer_alternative_name_serialized"]] self.admin_html[name].setdefault( ExtensionOID.ISSUER_ALTERNATIVE_NAME, f"
      {''.join(sans)}
    " ) if config.get("key_usage_serialized"): - kus = sorted( - [f"
  • {KEY_USAGE_NAMES[ku]}
  • " for ku in config["key_usage_serialized"]["value"]] - ) + kus = sorted([f"
  • {KEY_USAGE_NAMES[ku]}
  • " for ku in config["key_usage_serialized"]]) self.admin_html[name].setdefault(ExtensionOID.KEY_USAGE, f"
      {''.join(kus)}
    ") # NOTE: Custom extension class sorts values, but we render them in order as they appear in the # certificate, so we still have to override this in some places. if config.get("extended_key_usage_serialized"): - ekus = [f"
  • {eku}
  • " for eku in config["extended_key_usage_serialized"]["value"]] + ekus = [f"
  • {eku}
  • " for eku in config["extended_key_usage_serialized"]] self.admin_html[name].setdefault(ExtensionOID.EXTENDED_KEY_USAGE, f"
      {''.join(ekus)}
    ") if config.get("crl_distribution_points_serialized"): - ext_config = config["crl_distribution_points_serialized"]["value"] + ext_config = config["crl_distribution_points_serialized"] full_names = [] for dpoint in ext_config: @@ -649,24 +647,23 @@ def setUpCert(self, name: str) -> None: # pylint: disable=invalid-name ), ) - if certs[name].get("subject_key_identifier_serialized"): + if CERT_DATA[name].get("subject_key_identifier_serialized"): self.admin_html[name].setdefault( ExtensionOID.SUBJECT_KEY_IDENTIFIER, - certs[name]["subject_key_identifier_serialized"]["value"], + CERT_DATA[name]["subject_key_identifier_serialized"], ) - if certs[name].get("ocsp_no_check_serialized"): + if CERT_DATA[name].get("ocsp_no_check"): self.admin_html[name].setdefault(ExtensionOID.OCSP_NO_CHECK, "Yes") - aki = certs[name].get("authority_key_identifier_serialized", {}).get("value", {}) + aki = CERT_DATA[name].get("authority_key_identifier_serialized", {}) if isinstance(aki, dict) and aki.get("key_identifier"): self.admin_html[name].setdefault( ExtensionOID.AUTHORITY_KEY_IDENTIFIER, f"
    • Key ID: {aki['key_identifier']}
    ", ) - aia = certs[name].get("authority_information_access_serialized", {}).get("value", {}) - if aia: + if aia := CERT_DATA[name].get("authority_information_access_serialized", {}): lines = [] if "issuers" in aia: issuers = [f"
  • {fn}
  • " for fn in aia["issuers"]] @@ -690,11 +687,10 @@ def setUp(self) -> None: def assertAdminHTML(self, name: str, cert: X509CertMixin) -> None: # pylint: disable=invalid-name """Assert that the actual extension HTML is equivalent to the expected HTML.""" for oid, ext in cert.x509_extensions.items(): - self.assertIn(oid, self.admin_html[name], name) + self.assertIn(oid, self.admin_html[name], (name, oid)) admin_html = self.admin_html[name][oid] admin_html = f'\n
    {admin_html}
    ' actual = extension_as_admin_html(ext) - msg_prefix = f"{name}, {oid}: actual:\n{actual}\n" self.assertInHTML(admin_html, mark_safe(actual), msg_prefix=msg_prefix) diff --git a/ca/django_ca/tests/extensions/test_extension_values.py b/ca/django_ca/tests/extensions/test_extension_values.py index 0ebdcf5e9..6d5b5e7dc 100644 --- a/ca/django_ca/tests/extensions/test_extension_values.py +++ b/ca/django_ca/tests/extensions/test_extension_values.py @@ -26,8 +26,9 @@ from django_ca.constants import EXTENSION_DEFAULT_CRITICAL, ExtendedKeyUsageOID from django_ca.extensions import extension_as_text, parse_extension, serialize_extension from django_ca.extensions.utils import extension_as_admin_html -from django_ca.tests.base import certs, dns, rdn, uri +from django_ca.tests.base.constants import CERT_DATA from django_ca.tests.base.mixins import TestCaseMixin, TestCaseProtocol +from django_ca.tests.base.utils import dns, rdn, uri from django_ca.typehints import CRLExtensionType, ParsableDistributionPoint, ParsablePolicyInformation _ExtensionExampleDict = typing.TypedDict( @@ -1132,8 +1133,8 @@ def test_serialize(self) -> None: """Test serialization.""" for key in self.load_certs: self.assertEqual( - serialize_extension(self.certs[key].x509_extensions[self.oid]), - certs[key]["precertificate_signed_certificate_timestamps"], + serialize_extension(self.certs[key].x509_extensions[self.oid])["value"], + CERT_DATA[key]["precertificate_signed_certificate_timestamps_serialized"], ) def test_parse(self) -> None: diff --git a/ca/django_ca/tests/test_admin_acme.py b/ca/django_ca/tests/test_admin_acme.py index 02e0ebb38..1a50b8ad6 100644 --- a/ca/django_ca/tests/test_admin_acme.py +++ b/ca/django_ca/tests/test_admin_acme.py @@ -27,10 +27,10 @@ AcmeOrder, CertificateAuthority, ) -from django_ca.tests.base import override_tmpcadir from django_ca.tests.base.assertions import assert_changelist_response from django_ca.tests.base.mixins import StandardAdminViewTestCaseMixin from django_ca.tests.base.typehints import DjangoCAModelTypeVar +from django_ca.tests.base.utils import override_tmpcadir PEM1 = """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvP5N/1KjBQniyyukn30E diff --git a/ca/django_ca/tests/test_base.py b/ca/django_ca/tests/test_base.py index 432e4f187..2839cb996 100644 --- a/ca/django_ca/tests/test_base.py +++ b/ca/django_ca/tests/test_base.py @@ -23,8 +23,8 @@ from django.test import TestCase, override_settings from django_ca import ca_settings -from django_ca.tests.base import override_tmpcadir from django_ca.tests.base.mixins import TestCaseMixin +from django_ca.tests.base.utils import override_tmpcadir from django_ca.utils import add_colons diff --git a/ca/django_ca/tests/test_docs.py b/ca/django_ca/tests/test_docs.py index 1703c209a..21357f970 100644 --- a/ca/django_ca/tests/test_docs.py +++ b/ca/django_ca/tests/test_docs.py @@ -24,7 +24,8 @@ import pytest from django_ca.models import Certificate, CertificateAuthority -from django_ca.tests.base import DOC_DIR, certs, override_tmpcadir +from django_ca.tests.base.constants import CERT_DATA, DOC_DIR +from django_ca.tests.base.utils import override_tmpcadir BASE = os.path.relpath(DOC_DIR, os.path.dirname(__file__)) @@ -37,7 +38,7 @@ def globs(usable_root: CertificateAuthority, root_cert: Certificate) -> Dict[str "ca_serial": usable_root.serial, "cert": root_cert, "cert_serial": root_cert.serial, - "csr": certs["root-cert"]["csr"]["parsed"], + "csr": CERT_DATA["root-cert"]["csr"]["parsed"], "x509": x509, } diff --git a/ca/django_ca/tests/test_fields.py b/ca/django_ca/tests/test_fields.py index 5b213cf36..bc220493e 100644 --- a/ca/django_ca/tests/test_fields.py +++ b/ca/django_ca/tests/test_fields.py @@ -27,7 +27,6 @@ from django_ca import ca_settings, fields from django_ca.constants import KEY_USAGE_NAMES, REVOCATION_REASONS -from django_ca.tests.base import rdn from django_ca.tests.base.mixins import TestCaseMixin from django_ca.tests.base.utils import ( authority_information_access, @@ -35,6 +34,7 @@ issuer_alternative_name, key_usage, ocsp_no_check, + rdn, tls_feature, ) diff --git a/ca/django_ca/tests/test_management_actions.py b/ca/django_ca/tests/test_management_actions.py index 63007ab05..9399d2a4c 100644 --- a/ca/django_ca/tests/test_management_actions.py +++ b/ca/django_ca/tests/test_management_actions.py @@ -32,9 +32,9 @@ from django_ca.constants import ReasonFlags from django_ca.management import actions from django_ca.models import Certificate, CertificateAuthority -from django_ca.tests.base import certs, dns, override_tmpcadir, uri +from django_ca.tests.base.constants import CERT_DATA from django_ca.tests.base.mixins import TestCaseMixin -from django_ca.tests.base.utils import key_usage +from django_ca.tests.base.utils import dns, key_usage, override_tmpcadir, uri class ParserTestCaseMixin(TestCaseMixin): @@ -694,12 +694,12 @@ def setUp(self) -> None: def test_basic(self) -> None: """Test basic functionality of action.""" for name, cert in self.certs.items(): - args = self.parser.parse_args([certs[name]["serial"]]) + args = self.parser.parse_args([CERT_DATA[name]["serial"]]) self.assertEqual(args.cert, cert) def test_abbreviation(self) -> None: """Test using an abbreviation.""" - args = self.parser.parse_args([certs["root-cert"]["serial"][:6]]) + args = self.parser.parse_args([CERT_DATA["root-cert"]["serial"][:6]]) self.assertEqual(args.cert, self.certs["root-cert"]) def test_missing(self) -> None: @@ -716,7 +716,7 @@ def test_multiple(self) -> None: """Test matching multiple certs with abbreviation.""" # Manually set almost the same serial on second cert cert = Certificate(ca=self.cas["root"]) - cert.update_certificate(certs["root-cert"]["pub"]["parsed"]) + cert.update_certificate(CERT_DATA["root-cert"]["pub"]["parsed"]) cert.serial = cert.serial[:-1] + "X" cert.save() @@ -743,13 +743,13 @@ def setUp(self) -> None: def test_basic(self) -> None: """Test basic functionality of action.""" for name, ca in self.usable_cas: - args = self.parser.parse_args([certs[name]["serial"]]) + args = self.parser.parse_args([CERT_DATA[name]["serial"]]) self.assertEqual(args.ca, ca) @override_tmpcadir() def test_abbreviation(self) -> None: """Test using an abbreviation.""" - args = self.parser.parse_args([certs["ec"]["serial"][:6]]) + args = self.parser.parse_args([CERT_DATA["ec"]["serial"][:6]]) self.assertEqual(args.ca, self.cas["ec"]) def test_missing(self) -> None: @@ -763,7 +763,7 @@ def test_missing(self) -> None: def test_multiple(self) -> None: """Test an abbreviation matching multiple CAs.""" ca2 = CertificateAuthority(name="child-duplicate") - ca2.update_certificate(certs["child"]["pub"]["parsed"]) + ca2.update_certificate(CERT_DATA["child"]["pub"]["parsed"]) ca2.serial = ca2.serial[:-1] + "X" ca2.save() @@ -809,7 +809,7 @@ def test_private_key_does_not_exists(self) -> None: @override_tmpcadir() def test_password(self) -> None: """Test that the action works with a password-encrypted CA.""" - args = self.parser.parse_args([certs["pwd"]["serial"]]) + args = self.parser.parse_args([CERT_DATA["pwd"]["serial"]]) self.assertEqual(args.ca, self.cas["pwd"]) diff --git a/ca/django_ca/tests/test_managers.py b/ca/django_ca/tests/test_managers.py index 3d01f8d67..17d685362 100644 --- a/ca/django_ca/tests/test_managers.py +++ b/ca/django_ca/tests/test_managers.py @@ -30,20 +30,23 @@ from django_ca.models import Certificate, CertificateAuthority from django_ca.profiles import profiles from django_ca.querysets import CertificateAuthorityQuerySet, CertificateQuerySet -from django_ca.tests.base import certs, dns, override_tmpcadir, timestamps, uri +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin from django_ca.tests.base.utils import ( authority_information_access, basic_constraints, crl_distribution_points, distribution_point, + dns, extended_key_usage, key_usage, name_constraints, ocsp_no_check, + override_tmpcadir, precert_poison, subject_alternative_name, tls_feature, + uri, ) @@ -371,8 +374,8 @@ def test_unknown_extension_type(self) -> None: self.assertEqual(CertificateAuthority.objects.filter(name=name).count(), 0) -@override_settings(CA_PROFILES={}, CA_DEFAULT_SUBJECT=tuple(), CA_DEFAULT_CA=certs["child"]["serial"]) -@freeze_time(timestamps["everything_valid"]) +@override_settings(CA_PROFILES={}, CA_DEFAULT_SUBJECT=tuple(), CA_DEFAULT_CA=CERT_DATA["child"]["serial"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class CertificateAuthorityManagerDefaultTestCase(TestCaseMixin, TestCase): """Tests for :py:func:`django_ca.managers.CertificateAuthorityManager.default`.""" @@ -393,13 +396,13 @@ def test_disabled(self) -> None: with self.assertImproperlyConfigured(rf"^CA_DEFAULT_CA: {self.ca.serial} is disabled\.$"): CertificateAuthority.objects.default() - @freeze_time(timestamps["everything_expired"]) + @freeze_time(TIMESTAMPS["everything_expired"]) def test_expired(self) -> None: """Test that an exception is raised if CA is expired.""" with self.assertImproperlyConfigured(rf"^CA_DEFAULT_CA: {self.ca.serial} is expired\.$"): CertificateAuthority.objects.default() - @freeze_time(timestamps["before_everything"]) + @freeze_time(TIMESTAMPS["before_everything"]) def test_not_yet_valid(self) -> None: """Test that an exception is raised if CA is not yet valid.""" with self.assertImproperlyConfigured(rf"^CA_DEFAULT_CA: {self.ca.serial} is not yet valid\.$"): @@ -413,7 +416,7 @@ def test_default_ca(self) -> None: self.assertEqual(CertificateAuthority.objects.default(), ca) @override_settings(CA_DEFAULT_CA="") - @freeze_time(timestamps["everything_expired"]) + @freeze_time(TIMESTAMPS["everything_expired"]) def test_default_ca_expired(self) -> None: """Test that exception is raised if no CA is currently valid.""" with self.assertImproperlyConfigured(r"^No CA is currently usable\.$"): @@ -430,7 +433,7 @@ def test_unknown_ca_configured(self) -> None: class CreateCertTestCase(TestCaseMixin, TestCase): """Test :py:class:`django_ca.managers.CertificateManager.create_cert` (create a new cert).""" - csr = certs["root-cert"]["csr"]["parsed"] + csr = CERT_DATA["root-cert"]["csr"]["parsed"] load_cas = ("root",) @override_tmpcadir(CA_PROFILES={ca_settings.CA_DEFAULT_PROFILE: {"extensions": {}}}) @@ -486,7 +489,7 @@ def test_profile_unsupported_type(self) -> None: with self.assertCreateCertSignals(False, False), self.assertRaisesRegex(TypeError, msg): Certificate.objects.create_cert( self.ca, - csr=certs["root-cert"]["csr"]["parsed"], + csr=CERT_DATA["root-cert"]["csr"]["parsed"], profile=False, # type: ignore[arg-type] # what we're testing subject=self.subject, add_crl_url=False, diff --git a/ca/django_ca/tests/test_models.py b/ca/django_ca/tests/test_models.py index ef0eaf8bf..d2df3b9fe 100644 --- a/ca/django_ca/tests/test_models.py +++ b/ca/django_ca/tests/test_models.py @@ -58,16 +58,19 @@ Watcher, X509CertMixin, ) -from django_ca.tests.base import CERT_PEM_REGEX, certs, dns, override_tmpcadir, timestamps, uri +from django_ca.tests.base.constants import CERT_DATA, CERT_PEM_REGEX, TIMESTAMPS from django_ca.tests.base.mixins import AcmeValuesMixin, TestCaseMixin, TestCaseProtocol from django_ca.tests.base.utils import ( authority_information_access, basic_constraints, crl_distribution_points, distribution_point, + dns, issuer_alternative_name, + override_tmpcadir, subject_alternative_name, subject_key_identifier, + uri, ) from django_ca.utils import ca_storage, get_crl_cache_key, x509_name @@ -179,7 +182,7 @@ def test_key(self) -> None: """Test access to the private key.""" for name, ca in self.usable_cas: self.assertTrue(ca.key_exists) - self.assertIsNotNone(ca.key(certs[name].get("password"))) + self.assertIsNotNone(ca.key(CERT_DATA[name].get("password"))) # test a second tome to make sure we reload the key with mock.patch("django_ca.utils.read_file") as patched: @@ -190,7 +193,7 @@ def test_key(self) -> None: ca.private_key_path = os.path.join(ca_settings.CA_DIR, ca.private_key_path) self.assertTrue(ca.key_exists) - self.assertIsNotNone(ca.key(certs[name].get("password"))) + self.assertIsNotNone(ca.key(CERT_DATA[name].get("password"))) # Check again - here we have an already loaded key (also: no logging here anymore) # NOTE: assertLogs() fails if there are *no* log messages, so we cannot test that @@ -217,21 +220,21 @@ def test_bundle_as_pem(self) -> None: def test_key_str_password(self) -> None: """Test accessing the private key with a string password.""" ca = self.cas["pwd"] - pwd = certs["pwd"]["password"].decode("utf-8") + pwd = CERT_DATA["pwd"]["password"].decode("utf-8") self.assertIsNotNone(ca.key(pwd)) def test_path_length(self) -> None: """Test the path_length attribute.""" for name, ca in self.cas.items(): - self.assertEqual(ca.path_length, certs[name].get("path_length")) + self.assertEqual(ca.path_length, CERT_DATA[name].get("path_length")) def test_root(self) -> None: """Test the root attribute.""" self.assertEqual(self.cas["root"].root, self.cas["root"]) self.assertEqual(self.cas["child"].root, self.cas["root"]) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) @override_tmpcadir() def test_full_crl(self) -> None: """Test getting the CRL for a CertificateAuthority.""" @@ -268,7 +271,7 @@ def test_full_crl(self) -> None: crl = ca.get_crl().public_bytes(Encoding.PEM) self.assertCRL(crl, expected=[child], crl_number=4, signer=ca, algorithm=ca.algorithm) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) @override_tmpcadir() def test_intermediate_crl(self) -> None: """Test getting the CRL of an intermediate CA.""" @@ -300,7 +303,7 @@ def test_full_crl_without_timezone_support(self) -> None: self.test_full_crl() @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_ca_crl(self) -> None: """Test getting a CA CRL.""" ca = self.cas["root"] @@ -319,7 +322,7 @@ def test_ca_crl(self) -> None: self.assertCRL(crl, expected=[child_ca], idp=idp, crl_number=1, signer=ca, algorithm=ca.algorithm) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_intermediate_ca_crl(self) -> None: """Test getting the CRL for an intermediate CA.""" # Intermediate CAs have a DP in the CRL that has the CA url @@ -329,7 +332,7 @@ def test_intermediate_ca_crl(self) -> None: crl = self.ca.get_crl(scope="ca").public_bytes(Encoding.PEM) self.assertCRL(crl, idp=idp, signer=self.ca, algorithm=self.ca.algorithm) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) @override_tmpcadir() def test_user_crl(self) -> None: """Test getting a user CRL.""" @@ -347,7 +350,7 @@ def test_user_crl(self) -> None: crl = ca.get_crl(scope="user").public_bytes(Encoding.PEM) self.assertCRL(crl, expected=[cert], idp=idp, crl_number=1, signer=ca, algorithm=ca.algorithm) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) @override_tmpcadir() def test_attr_crl(self) -> None: """Test getting an Attribute CRL (always an empty list).""" @@ -365,7 +368,7 @@ def test_attr_crl(self) -> None: self.assertCRL(crl, idp=idp, crl_number=1, signer=ca, algorithm=ca.algorithm) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_no_idp(self) -> None: """Test a CRL with no IDP.""" # CRLs require a full name (or only_some_reasons) if it's a full CRL @@ -375,7 +378,7 @@ def test_no_idp(self) -> None: self.assertCRL(crl, idp=None, algorithm=self.ca.algorithm) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_counter(self) -> None: """Test the counter for CRLs.""" idp = self.get_idp(full_name=self.get_idp_full_name(self.ca)) @@ -388,7 +391,7 @@ def test_counter(self) -> None: self.assertCRL(crl, idp=idp, crl_number=0, algorithm=self.ca.algorithm) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_no_auth_key_identifier(self) -> None: """Test getting the CRL from a CA with no AuthorityKeyIdentifier.""" @@ -406,7 +409,7 @@ def side_effect(cls: Any) -> typing.NoReturn: # Note that we still get an AKI because the value comes from the public key in this case self.assertCRL(crl, idp=idp, signer=self.ca, algorithm=self.ca.algorithm) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_get_crl_with_wrong_algorithm(self) -> None: """Test that we validate the algorithm if passed by the user.""" # DSA/RSA/EC keys cannot trigger this condition, as the algorithm would default to the one used by @@ -434,7 +437,7 @@ def test_crl_invalid_scope(self) -> None: self.ca.get_crl(scope="foobar").public_bytes(Encoding.PEM) # type: ignore[arg-type] @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_cache_crls(self) -> None: """Test caching of CRLs.""" crl_profiles = self.crl_profiles @@ -590,7 +593,7 @@ def test_cache_crls_algorithm(self) -> None: def test_max_path_length(self) -> None: """Test getting the maximum path_length.""" for name, ca in self.usable_cas: - self.assertEqual(ca.max_path_length, certs[name].get("max_path_length"), name) + self.assertEqual(ca.max_path_length, CERT_DATA[name].get("max_path_length"), name) def test_allows_intermediate(self) -> None: """Test checking if this CA allows intermediate CAs.""" @@ -638,7 +641,7 @@ def test_generate_ocsp_responder_certificate_for_rsa_ca_with_custom_curve(self) @override_tmpcadir() def test_regenerate_ocsp_responder_certificate(self) -> None: """Test regenerating an OCSP responder certificate that is due to expire soon.""" - with freeze_time(timestamps["everything_valid"]) as frozen_time: + with freeze_time(TIMESTAMPS["everything_valid"]) as frozen_time: # TYPEHINT NOTE: We know that the certificate was not yet generated here _, _, ocsp_responder_certificate = self.ca.generate_ocsp_key() # type: ignore[misc] @@ -763,7 +766,7 @@ class CertificateAuthoritySignTests(TestCaseMixin, X509CertMixinTestCaseMixin, T """Test signing a certificiate.""" load_cas = ("root", "child") - csr = certs["child-cert"]["csr"]["parsed"] + csr = CERT_DATA["child-cert"]["csr"]["parsed"] def assertBasicCert(self, cert: x509.Certificate) -> None: # pylint: disable=invalid-name """Basic assertions about the certificate.""" @@ -782,12 +785,12 @@ def assertExtensionDict( # pylint: disable=invalid-name self.assertEqual(actual, expected_dict) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_simple(self) -> None: """Test the simplest invocation of the function.""" now = datetime.now(tz=tz.utc).replace(tzinfo=None) cn = "example.com" - csr = certs["child-cert"]["csr"]["parsed"] + csr = CERT_DATA["child-cert"]["csr"]["parsed"] subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)]) with self.assertSignCertSignals(): cert = self.ca.sign(csr, subject=subject) @@ -807,11 +810,11 @@ def test_simple(self) -> None: ) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_non_default_values(self) -> None: """Pass non-default parameters.""" cn = "example.com" - csr = certs["child-cert"]["csr"]["parsed"] + csr = CERT_DATA["child-cert"]["csr"]["parsed"] algorithm = hashes.SHA256() expires = datetime.now(tz=tz.utc) + ca_settings.CA_DEFAULT_EXPIRES + timedelta(days=3) subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)]) @@ -823,11 +826,11 @@ def test_non_default_values(self) -> None: self.assertIsInstance(cert.signature_hash_algorithm, hashes.SHA256) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_non_default_extensions(self) -> None: """Pass non-default extensions.""" cn = "example.com" - csr = certs["child-cert"]["csr"]["parsed"] + csr = CERT_DATA["child-cert"]["csr"]["parsed"] subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)]) aki = x509.Extension( oid=ExtensionOID.AUTHORITY_KEY_IDENTIFIER, @@ -854,11 +857,11 @@ def test_non_default_extensions(self) -> None: self.assertExtensionDict(cert, [ski, basic_constraints(critical=False), aki]) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_cn_not_in_san(self) -> None: """Test the cn_in_san option.""" cn = "example.com" - csr = certs["child-cert"]["csr"]["parsed"] + csr = CERT_DATA["child-cert"]["csr"]["parsed"] subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)]) san = subject_alternative_name(dns("example.net")) with self.assertSignCertSignals(): @@ -876,11 +879,11 @@ def test_cn_not_in_san(self) -> None: ) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_append_cn_to_san(self) -> None: """Test appending a CommonName to SubjectAlternativeName.""" cn = "example.com" - csr = certs["child-cert"]["csr"]["parsed"] + csr = CERT_DATA["child-cert"]["csr"]["parsed"] subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)]) san = subject_alternative_name(dns("example.net")) with self.assertSignCertSignals(): @@ -898,11 +901,11 @@ def test_append_cn_to_san(self) -> None: ) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_cn_already_in_san(self) -> None: """Test using a CommonName that is already in SubjectAlternativeName.""" cn = "example.com" - csr = certs["child-cert"]["csr"]["parsed"] + csr = CERT_DATA["child-cert"]["csr"]["parsed"] subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)]) san = subject_alternative_name(dns(cn)) with self.assertSignCertSignals(): @@ -920,11 +923,11 @@ def test_cn_already_in_san(self) -> None: ) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_unparsable_cn(self) -> None: """Test using a CommonName that cannot be used as a SubjectAlternativeName.""" cn = "foo..bar*" - csr = certs["child-cert"]["csr"]["parsed"] + csr = CERT_DATA["child-cert"]["csr"]["parsed"] subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)]) san = subject_alternative_name(dns("example.net")) with self.assertSignCertSignals(): @@ -943,7 +946,7 @@ def test_unparsable_cn(self) -> None: def test_create_ca(self) -> None: """Try passing a BasicConstraints extension that allows creating a CA.""" - csr = certs["child-cert"]["csr"]["parsed"] + csr = CERT_DATA["child-cert"]["csr"]["parsed"] subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "example.com")]) msg = r"^This function cannot be used to create a Certificate Authority\.$" with self.assertSignCertSignals(pre=False, post=False), self.assertRaisesRegex(ValueError, msg): @@ -970,25 +973,25 @@ def test_bundle_as_pem(self) -> None: def test_dates(self) -> None: """Test valid_from/valid_until dates.""" for name, ca in self.cas.items(): - self.assertEqual(ca.valid_from, timezone.make_aware(certs[name]["valid_from"], tz.utc)) - self.assertEqual(ca.expires, timezone.make_aware(certs[name]["valid_until"], tz.utc)) + self.assertEqual(ca.valid_from, timezone.make_aware(CERT_DATA[name]["valid_from"], tz.utc)) + self.assertEqual(ca.expires, timezone.make_aware(CERT_DATA[name]["valid_until"], tz.utc)) for name, cert in self.certs.items(): - self.assertEqual(cert.valid_from, timezone.make_aware(certs[name]["valid_from"], tz.utc)) - self.assertEqual(cert.expires, timezone.make_aware(certs[name]["valid_until"], tz.utc)) + self.assertEqual(cert.valid_from, timezone.make_aware(CERT_DATA[name]["valid_from"], tz.utc)) + self.assertEqual(cert.expires, timezone.make_aware(CERT_DATA[name]["valid_until"], tz.utc)) @override_settings(USE_TZ=False) def test_dates_without_timezone_support(self) -> None: """Test valid_from/valid_until dates without timezone support.""" for name, ca in self.cas.items(): ca.refresh_from_db() # obj is loaded in setUp(), before decorator is active - self.assertEqual(ca.valid_from, certs[name]["valid_from"]) - self.assertEqual(ca.expires, certs[name]["valid_until"]) + self.assertEqual(ca.valid_from, CERT_DATA[name]["valid_from"]) + self.assertEqual(ca.expires, CERT_DATA[name]["valid_until"]) for name, cert in self.certs.items(): cert.refresh_from_db() # obj is loaded in setUp(), before decorator is active - self.assertEqual(cert.valid_from, certs[name]["valid_from"]) - self.assertEqual(cert.expires, certs[name]["valid_until"]) + self.assertEqual(cert.valid_from, CERT_DATA[name]["valid_from"]) + self.assertEqual(cert.expires, CERT_DATA[name]["valid_until"]) def test_revocation(self) -> None: """Test getting a revociation for a non-revoked certificate.""" @@ -1007,10 +1010,10 @@ def test_root(self) -> None: def test_serial(self) -> None: """Test getting the serial.""" for name, ca in self.cas.items(): - self.assertEqual(ca.serial, certs[ca.name].get("serial")) + self.assertEqual(ca.serial, CERT_DATA[ca.name].get("serial")) for name, cert in self.certs.items(): - self.assertEqual(cert.serial, certs[name].get("serial")) + self.assertEqual(cert.serial, CERT_DATA[name].get("serial")) @override_tmpcadir() def test_subject_alternative_name(self) -> None: @@ -1018,13 +1021,13 @@ def test_subject_alternative_name(self) -> None: for name, ca in self.cas.items(): self.assertEqual( ca.x509_extensions.get(ExtensionOID.SUBJECT_ALTERNATIVE_NAME), - certs[ca.name].get("subject_alternative_name"), + CERT_DATA[ca.name].get("subject_alternative_name"), ) for name, cert in self.certs.items(): self.assertEqual( cert.x509_extensions.get(ExtensionOID.SUBJECT_ALTERNATIVE_NAME), - certs[name].get("subject_alternative_name"), + CERT_DATA[name].get("subject_alternative_name"), ) # Create a cert with some weirder SANs to test that too @@ -1035,7 +1038,7 @@ def test_subject_alternative_name(self) -> None: ) weird_cert = self.create_cert( self.cas["child"], - certs["child-cert"]["csr"]["parsed"], + CERT_DATA["child-cert"]["csr"]["parsed"], subject=self.subject, extensions=[san], ) @@ -1121,21 +1124,21 @@ def test_get_fingerprint(self) -> None: } for name, ca in self.cas.items(): for algo_name, algorithm in algorithms.items(): - self.assertEqual(ca.get_fingerprint(algorithm), certs[name][algo_name]) + self.assertEqual(ca.get_fingerprint(algorithm), CERT_DATA[name][algo_name]) for name, cert in self.certs.items(): for algo_name, algorithm in algorithms.items(): - self.assertEqual(cert.get_fingerprint(algorithm), certs[name][algo_name]) + self.assertEqual(cert.get_fingerprint(algorithm), CERT_DATA[name][algo_name]) def test_jwk(self) -> None: """Test JWK property.""" for name, ca in self.cas.items(): # josepy does not support loading DSA/Ed448/Ed25519 keys: # https://github.com/certbot/josepy/pull/98 - if certs[name]["key_type"] in ("DSA", "Ed448", "Ed25519"): + if CERT_DATA[name]["key_type"] in ("DSA", "Ed448", "Ed25519"): continue - if certs[name]["key_type"] == "EC": + if CERT_DATA[name]["key_type"] == "EC": self.assertIsInstance(ca.jwk, jose.jwk.JWKEC, name) else: self.assertIsInstance(ca.jwk, jose.jwk.JWKRSA, name) @@ -1143,10 +1146,10 @@ def test_jwk(self) -> None: for name, cert in self.certs.items(): # josepy does not support loading DSA/Ed448/Ed25519 keys: # https://github.com/certbot/josepy/pull/98 - if certs[name]["key_type"] in ("DSA", "Ed448", "Ed25519"): + if CERT_DATA[name]["key_type"] in ("DSA", "Ed448", "Ed25519"): continue - if certs[name]["key_type"] == "EC": + if CERT_DATA[name]["key_type"] == "EC": self.assertIsInstance(cert.jwk, jose.jwk.JWKEC, name) else: self.assertIsInstance(cert.jwk, jose.jwk.JWKRSA, name) @@ -1201,7 +1204,7 @@ def test_get_authority_key_identifier(self) -> None: for name, ca in self.cas.items(): self.assertEqual( ca.get_authority_key_identifier().key_identifier, - certs[name]["subject_key_identifier"].value.key_identifier, + CERT_DATA[name]["subject_key_identifier"].value.key_identifier, ) # All CAs have a subject key identifier, so we mock that this exception is not present @@ -1214,7 +1217,7 @@ def side_effect(cls: Any) -> typing.NoReturn: ): self.assertEqual( ca.get_authority_key_identifier().key_identifier, - certs["child"]["subject_key_identifier"].value.key_identifier, + CERT_DATA["child"]["subject_key_identifier"].value.key_identifier, ) def test_get_authority_key_identifier_extension(self) -> None: @@ -1222,7 +1225,7 @@ def test_get_authority_key_identifier_extension(self) -> None: for name, ca in self.cas.items(): ext = ca.get_authority_key_identifier_extension() self.assertEqual( - ext.value.key_identifier, certs[name]["subject_key_identifier"].value.key_identifier + ext.value.key_identifier, CERT_DATA[name]["subject_key_identifier"].value.key_identifier ) def test_inconsistent_model_states(self) -> None: @@ -1244,8 +1247,8 @@ def test_inconsistent_model_states(self) -> None: class ModelfieldsTests(TestCaseMixin, TestCase): """Specialized tests for model fields.""" - csr = certs["root-cert"]["csr"] - pub = certs["root-cert"]["pub"] + csr = CERT_DATA["root-cert"]["csr"] + pub = CERT_DATA["root-cert"]["pub"] load_cas = ("root",) def test_create_pem_bytes(self) -> None: @@ -1417,7 +1420,7 @@ def test_invalid_value(self) -> None: """Test passing invalid values.""" with self.assertRaisesRegex(ValueError, r"^True: Could not parse CertificateSigningRequest$"): Certificate.objects.create( - pub=certs["child-cert"]["pub"]["parsed"], + pub=CERT_DATA["child-cert"]["pub"]["parsed"], csr=True, ca=self.ca, expires=timezone.now(), @@ -1426,7 +1429,7 @@ def test_invalid_value(self) -> None: with self.assertRaisesRegex(ValueError, r"^True: Could not parse Certificate$"): Certificate.objects.create( - csr=certs["child-cert"]["csr"]["parsed"], + csr=CERT_DATA["child-cert"]["csr"]["parsed"], pub=True, ca=self.ca, expires=timezone.now(), @@ -1480,7 +1483,7 @@ def test_serial(self) -> None: with self.assertRaisesRegex(AcmeAccount.ca.RelatedObjectDoesNotExist, r"^AcmeAccount has no ca\.$"): AcmeAccount().serial # pylint: disable=expression-not-assigned - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_usable(self) -> None: """Test the ``usable`` property.""" self.assertTrue(self.account1.usable) @@ -1731,7 +1734,7 @@ def test_acme_challenge(self) -> None: with self.assertRaisesRegex(ValueError, r"^foo: Unsupported challenge type\.$"): self.chall.acme_challenge # pylint: disable=pointless-statement - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_acme_validated(self) -> None: """Test acme_validated property.""" # preconditions for checks (might change them in setUp without realising it might affect this test) @@ -1744,12 +1747,12 @@ def test_acme_validated(self) -> None: self.assertIsNone(self.chall.acme_validated) # still None (no validated timestamp) self.chall.validated = timezone.now() - self.assertEqual(self.chall.acme_validated, timestamps["everything_valid"]) + self.assertEqual(self.chall.acme_validated, TIMESTAMPS["everything_valid"]) # We return a UTC timestamp, even if timezone support is disabled. with self.settings(USE_TZ=False): self.chall.validated = timezone.now() - self.assertEqual(self.chall.acme_validated, timestamps["everything_valid"]) + self.assertEqual(self.chall.acme_validated, TIMESTAMPS["everything_valid"]) def test_encoded(self) -> None: """Test the encoded property.""" @@ -1817,5 +1820,5 @@ def test_acme_url(self) -> None: def test_parse_csr(self) -> None: """Test the parse_csr property.""" - self.acme_cert.csr = certs["root-cert"]["csr"]["pem"] + self.acme_cert.csr = CERT_DATA["root-cert"]["csr"]["pem"] self.assertIsInstance(self.acme_cert.parse_csr(), x509.CertificateSigningRequest) diff --git a/ca/django_ca/tests/test_profiles.py b/ca/django_ca/tests/test_profiles.py index fe771e944..7740b7463 100644 --- a/ca/django_ca/tests/test_profiles.py +++ b/ca/django_ca/tests/test_profiles.py @@ -28,15 +28,18 @@ from django_ca.models import Certificate, CertificateAuthority from django_ca.profiles import Profile, get_profile, profile, profiles from django_ca.signals import pre_sign_cert -from django_ca.tests.base import certs, dns, override_tmpcadir, uri +from django_ca.tests.base.constants import CERT_DATA from django_ca.tests.base.mixins import TestCaseMixin from django_ca.tests.base.utils import ( authority_information_access, basic_constraints, + dns, issuer_alternative_name, ocsp_no_check, + override_tmpcadir, subject_alternative_name, subject_key_identifier, + uri, ) @@ -45,7 +48,7 @@ class DocumentationTestCase(TestCaseMixin, TestCase): def setUp(self) -> None: super().setUp() - self.ca = self.load_ca(name=certs["root"]["name"], parsed=certs["root"]["pub"]["parsed"]) + self.ca = self.load_ca(name=CERT_DATA["root"]["name"], parsed=CERT_DATA["root"]["pub"]["parsed"]) def get_globs(self) -> Dict[str, Any]: """Get globals for doctests.""" @@ -54,7 +57,7 @@ def get_globs(self) -> Dict[str, Any]: "get_profile": get_profile, "ca": self.ca, "ca_serial": self.ca.serial, - "csr": certs["root-cert"]["csr"]["parsed"], + "csr": CERT_DATA["root-cert"]["csr"]["parsed"], } @override_tmpcadir() @@ -185,8 +188,8 @@ def test_serialize(self) -> None: @override_tmpcadir() def test_create_cert_minimal(self) -> None: """Create a certificate with minimal parameters.""" - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) - csr = certs["child-cert"]["csr"]["parsed"] + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) + csr = CERT_DATA["child-cert"]["csr"]["parsed"] prof = Profile("example", subject=[]) with self.mockSignal(pre_sign_cert) as pre: @@ -207,10 +210,10 @@ def test_create_cert_minimal(self) -> None: @override_tmpcadir() def test_alternative_values(self) -> None: """Test overriding most values.""" - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) ca.issuer_alt_name = "https://example.com" ca.save() - csr = certs["child-cert"]["csr"]["parsed"] + csr = CERT_DATA["child-cert"]["csr"]["parsed"] country_name = x509.NameAttribute(NameOID.COUNTRY_NAME, "AT") subject = x509.Name([country_name, x509.NameAttribute(NameOID.COMMON_NAME, self.hostname)]) @@ -241,8 +244,8 @@ def test_alternative_values(self) -> None: @override_tmpcadir() def test_overrides(self) -> None: """Test other overrides.""" - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) - csr = certs["child-cert"]["csr"]["parsed"] + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) + csr = CERT_DATA["child-cert"]["csr"]["parsed"] country_name = x509.NameAttribute(NameOID.COUNTRY_NAME, "AT") expected_subject = x509.Name([country_name, x509.NameAttribute(NameOID.COMMON_NAME, self.hostname)]) @@ -297,8 +300,8 @@ def test_overrides(self) -> None: @override_tmpcadir() def test_none_extension(self) -> None: """Test passing an extension that is removed by the profile.""" - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) - csr = certs["child-cert"]["csr"]["parsed"] + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) + csr = CERT_DATA["child-cert"]["csr"]["parsed"] prof = Profile("example", subject=[("C", "AT")], extensions={"ocsp_no_check": None}) with self.mockSignal(pre_sign_cert) as pre: @@ -309,8 +312,8 @@ def test_none_extension(self) -> None: @override_tmpcadir() def test_cn_in_san(self) -> None: """Test writing the common name into the SAN.""" - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) - csr = certs["child-cert"]["csr"]["parsed"] + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) + csr = CERT_DATA["child-cert"]["csr"]["parsed"] subject = x509.Name( [ x509.NameAttribute(NameOID.COUNTRY_NAME, "AT"), @@ -387,8 +390,8 @@ def test_cn_in_san(self) -> None: @override_tmpcadir() def test_override_ski(self) -> None: """Test overriding the subject key identifier.""" - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) - csr = certs["child-cert"]["csr"]["parsed"] + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) + csr = CERT_DATA["child-cert"]["csr"]["parsed"] ski = x509.Extension( oid=ExtensionOID.SUBJECT_KEY_IDENTIFIER, critical=False, @@ -425,8 +428,8 @@ def test_override_ski(self) -> None: def test_add_distribution_point_with_ca_crldp(self) -> None: """Pass a custom distribution point when creating the cert, which matches ca.crl_url.""" prof = Profile("example", subject=[]) - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) - csr = certs["child-cert"]["csr"]["parsed"] + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) + csr = CERT_DATA["child-cert"]["csr"]["parsed"] # Add CRL url to CA ca.crl_url = "https://crl.ca.example.com" @@ -465,8 +468,8 @@ def test_add_distribution_point_with_ca_crldp(self) -> None: @override_tmpcadir() def test_with_algorithm(self) -> None: """Test a profile that manually overrides the algorithm.""" - root = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) - csr = certs["child-cert"]["csr"]["parsed"] + root = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) + csr = CERT_DATA["child-cert"]["csr"]["parsed"] prof = Profile("example", subject=[], algorithm="SHA-512") @@ -491,8 +494,8 @@ def test_with_algorithm(self) -> None: def test_issuer_alternative_name_override(self) -> None: """Pass a custom Issuer Alternative Name which overwrites the CA value.""" prof = Profile("example", subject=[]) - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) - csr = certs["child-cert"]["csr"]["parsed"] + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) + csr = CERT_DATA["child-cert"]["csr"]["parsed"] # Add CRL url to CA ca.issuer_alt_name = "https://ian.ca.example.com" @@ -532,8 +535,8 @@ def test_issuer_alternative_name_override(self) -> None: def test_merge_authority_information_access_existing_values(self) -> None: """Pass a custom distribution point when creating the cert, which matches ca.crl_url.""" prof = Profile("example", subject=[]) - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) - csr = certs["child-cert"]["csr"]["parsed"] + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) + csr = CERT_DATA["child-cert"]["csr"]["parsed"] # Add CRL url to CA ca.ocsp_url = "https://ocsp.ca.example.com" @@ -581,8 +584,8 @@ def test_merge_authority_information_access_existing_values(self) -> None: @override_tmpcadir() def test_extension_as_cryptography(self) -> None: """Test with a profile that has cryptography extensions.""" - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) - csr = certs["child-cert"]["csr"]["parsed"] + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) + csr = CERT_DATA["child-cert"]["csr"]["parsed"] prof = Profile("example", subject=[], extensions={EXTENSION_KEYS[ExtensionOID.OCSP_NO_CHECK]: {}}) with self.mockSignal(pre_sign_cert) as pre: @@ -620,13 +623,13 @@ def test_extension_overrides(self) -> None: ) }, ) - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) ca.ocsp_url = "http://ocsp.example.com/ca" ca.issuer_url = "http://issuer.example.com/issuer" ca.save() - csr = certs["child-cert"]["csr"]["parsed"] + csr = CERT_DATA["child-cert"]["csr"]["parsed"] expected_authority_information_access = authority_information_access( ocsp=[uri("http://ocsp.example.com/expected")], @@ -665,13 +668,13 @@ def test_partial_authority_information_access_override(self) -> None: ) }, ) - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) ca.ocsp_url = "http://ocsp.example.com/ca" ca.issuer_url = "http://issuer.example.com/ca" ca.save() - csr = certs["child-cert"]["csr"]["parsed"] + csr = CERT_DATA["child-cert"]["csr"]["parsed"] # Only pass an OCSP responder with self.mockSignal(pre_sign_cert) as pre: @@ -732,8 +735,8 @@ def test_partial_authority_information_access_override(self) -> None: @override_tmpcadir() def test_no_cn_no_san(self) -> None: """Test creating a cert with no cn in san.""" - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) - csr = certs["child-cert"]["csr"]["parsed"] + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) + csr = CERT_DATA["child-cert"]["csr"]["parsed"] prof = Profile("example", subject=[("C", "AT")]) msg = r"^Must name at least a CN or a subjectAlternativeName\.$" @@ -744,8 +747,8 @@ def test_no_cn_no_san(self) -> None: @override_tmpcadir() def test_no_valid_cn_in_san(self) -> None: """Test what happens when the SAN has nothing usable as CN.""" - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) - csr = certs["child-cert"]["csr"]["parsed"] + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) + csr = CERT_DATA["child-cert"]["csr"]["parsed"] prof = Profile("example", subject=[], extensions={EXTENSION_KEYS[ExtensionOID.OCSP_NO_CHECK]: {}}) san = subject_alternative_name(x509.RegisteredID(ExtensionOID.OCSP_NO_CHECK)) @@ -756,8 +759,8 @@ def test_no_valid_cn_in_san(self) -> None: @override_tmpcadir() def test_unparsable_cn(self) -> None: """Try creating a profile with an unparsable Common Name.""" - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) - csr = certs["child-cert"]["csr"]["parsed"] + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) + csr = CERT_DATA["child-cert"]["csr"]["parsed"] cname = "foo bar" prof = Profile("example", subject=[("C", "AT"), ("CN", cname)]) @@ -774,8 +777,8 @@ def test_unknown_signature_hash_algorithm(self) -> None: @override_tmpcadir(CA_DEFAULT_SUBJECT=None) def test_no_valid_subject(self) -> None: """Test case where no subject at all could be determined.""" - ca = self.load_ca(name="root", parsed=certs["root"]["pub"]["parsed"]) - csr = certs["child-cert"]["csr"]["parsed"] + ca = self.load_ca(name="root", parsed=CERT_DATA["root"]["pub"]["parsed"]) + csr = CERT_DATA["child-cert"]["csr"]["parsed"] prof = Profile("test") with self.assertRaisesRegex(ValueError, r"^Cannot determine subject for certificate\.$"): self.create_cert(prof, ca, csr) diff --git a/ca/django_ca/tests/test_querysets.py b/ca/django_ca/tests/test_querysets.py index af292d9b9..99955e870 100644 --- a/ca/django_ca/tests/test_querysets.py +++ b/ca/django_ca/tests/test_querysets.py @@ -38,9 +38,9 @@ Certificate, CertificateAuthority, ) -from django_ca.tests.base import override_tmpcadir, timestamps +from django_ca.tests.base.constants import TIMESTAMPS from django_ca.tests.base.mixins import AcmeValuesMixin, TestCaseMixin -from django_ca.tests.base.utils import basic_constraints, key_usage +from django_ca.tests.base.utils import basic_constraints, key_usage, override_tmpcadir from django_ca.utils import x509_name @@ -258,23 +258,23 @@ def test_valid(self) -> None: """Test valid/usable/invalid filters.""" self.load_named_cas("__usable__") - with freeze_time(timestamps["before_cas"]): + with freeze_time(TIMESTAMPS["before_cas"]): self.assertCountEqual(CertificateAuthority.objects.valid(), []) self.assertCountEqual(CertificateAuthority.objects.usable(), []) self.assertCountEqual(CertificateAuthority.objects.invalid(), self.cas.values()) - with freeze_time(timestamps["before_child"]): + with freeze_time(TIMESTAMPS["before_child"]): valid = [c for c in self.cas.values() if c.name != "child"] self.assertCountEqual(CertificateAuthority.objects.valid(), valid) self.assertCountEqual(CertificateAuthority.objects.usable(), valid) self.assertCountEqual(CertificateAuthority.objects.invalid(), [self.cas["child"]]) - with freeze_time(timestamps["after_child"]): + with freeze_time(TIMESTAMPS["after_child"]): self.assertCountEqual(CertificateAuthority.objects.valid(), self.cas.values()) self.assertCountEqual(CertificateAuthority.objects.usable(), self.cas.values()) self.assertCountEqual(CertificateAuthority.objects.invalid(), []) - with freeze_time(timestamps["cas_expired"]): + with freeze_time(TIMESTAMPS["cas_expired"]): self.assertCountEqual(CertificateAuthority.objects.valid(), []) self.assertCountEqual(CertificateAuthority.objects.usable(), []) self.assertCountEqual(CertificateAuthority.objects.invalid(), self.cas.values()) @@ -288,17 +288,17 @@ class CertificateQuerysetTestCase(QuerySetTestCaseMixin, TestCase): def test_validity(self) -> None: """Test validity filter.""" - with freeze_time(timestamps["everything_valid"]): + with freeze_time(TIMESTAMPS["everything_valid"]): self.assertQuerySet(Certificate.objects.expired()) self.assertQuerySet(Certificate.objects.not_yet_valid()) self.assertQuerySet(Certificate.objects.valid(), *self.certs.values()) - with freeze_time(timestamps["everything_expired"]): + with freeze_time(TIMESTAMPS["everything_expired"]): self.assertQuerySet(Certificate.objects.expired(), *self.certs.values()) self.assertQuerySet(Certificate.objects.not_yet_valid()) self.assertQuerySet(Certificate.objects.valid()) - with freeze_time(timestamps["before_everything"]): + with freeze_time(TIMESTAMPS["before_everything"]): self.assertQuerySet(Certificate.objects.expired()) self.assertQuerySet(Certificate.objects.not_yet_valid(), *self.certs.values()) self.assertQuerySet(Certificate.objects.valid()) @@ -313,7 +313,7 @@ def test_validity(self) -> None: self.certs["ed25519-cert"], ] valid = [c for c in self.certs.values() if c not in expired] - with freeze_time(timestamps["ca_certs_expired"]): + with freeze_time(TIMESTAMPS["ca_certs_expired"]): self.assertQuerySet(Certificate.objects.expired(), *expired) self.assertQuerySet(Certificate.objects.not_yet_valid()) self.assertQuerySet(Certificate.objects.valid(), *valid) @@ -362,7 +362,7 @@ def setUp(self) -> None: class AcmeAccountQuerySetTestCase(AcmeQuerySetTestCase): """Test cases for :py:class:`~django_ca.querysets.AcmeAccountQuerySet`.""" - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_viewable(self) -> None: """Test the viewable() method.""" self.assertQuerySet(AcmeAccount.objects.viewable(), self.account, self.account2) @@ -379,7 +379,7 @@ def test_viewable(self) -> None: # Test that we're back to the original state self.assertQuerySet(AcmeAccount.objects.viewable(), self.account, self.account2) - with freeze_time(timestamps["everything_expired"]): + with freeze_time(TIMESTAMPS["everything_expired"]): self.assertQuerySet(AcmeAccount.objects.viewable()) @@ -391,7 +391,7 @@ def test_account(self) -> None: self.assertQuerySet(AcmeOrder.objects.account(self.account), self.order) self.assertQuerySet(AcmeOrder.objects.account(self.account2)) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_viewable(self) -> None: """Test the viewable() method.""" self.assertQuerySet(AcmeOrder.objects.viewable(), self.order) @@ -399,7 +399,7 @@ def test_viewable(self) -> None: with self.attr(self.order.account, "status", AcmeAccount.STATUS_REVOKED): self.assertQuerySet(AcmeOrder.objects.viewable()) - with freeze_time(timestamps["everything_expired"]): + with freeze_time(TIMESTAMPS["everything_expired"]): self.assertQuerySet(AcmeOrder.objects.viewable()) @@ -411,7 +411,7 @@ def test_account(self) -> None: self.assertQuerySet(AcmeAuthorization.objects.account(self.account), self.auth) self.assertQuerySet(AcmeAuthorization.objects.account(self.account2)) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_url(self) -> None: """Test the url filter.""" # pylint: disable=expression-not-assigned @@ -419,7 +419,7 @@ def test_url(self) -> None: with self.assertNumQueries(1): AcmeAuthorization.objects.url().get(pk=self.auth.pk).acme_url - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_viewable(self) -> None: """Test the viewable() method.""" self.assertQuerySet(AcmeAuthorization.objects.viewable(), self.auth) @@ -436,7 +436,7 @@ def test_account(self) -> None: self.assertQuerySet(AcmeChallenge.objects.account(self.account), self.chall) self.assertQuerySet(AcmeChallenge.objects.account(self.account2)) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_url(self) -> None: """Test the url filter.""" # pylint: disable=expression-not-assigned @@ -444,7 +444,7 @@ def test_url(self) -> None: with self.assertNumQueries(1): AcmeChallenge.objects.url().get(pk=self.chall.pk).acme_url - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_viewable(self) -> None: """Test the viewable() method.""" self.assertQuerySet(AcmeChallenge.objects.viewable(), self.chall) @@ -461,7 +461,7 @@ def test_account(self) -> None: self.assertQuerySet(AcmeCertificate.objects.account(self.account), self.acme_cert) self.assertQuerySet(AcmeCertificate.objects.account(self.account2)) - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_url(self) -> None: """Test the url filter.""" # pylint: disable=expression-not-assigned @@ -469,7 +469,7 @@ def test_url(self) -> None: with self.assertNumQueries(1): AcmeCertificate.objects.url().get(pk=self.acme_cert.pk).acme_url - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_viewable(self) -> None: """Test the viewable() method.""" # none by default because we need a valid order and cert diff --git a/ca/django_ca/tests/test_settings.py b/ca/django_ca/tests/test_settings.py index 80bcf5d8c..eed935e3b 100644 --- a/ca/django_ca/tests/test_settings.py +++ b/ca/django_ca/tests/test_settings.py @@ -35,7 +35,7 @@ update_database_setting_from_environment, ) from django_ca import ca_settings -from django_ca.tests.base import FIXTURES_DIR +from django_ca.tests.base.constants import FIXTURES_DIR from django_ca.tests.base.mixins import TestCaseMixin @@ -522,7 +522,7 @@ def test_invalid_key(self) -> None: with self.settings(CA_DEFAULT_SUBJECT=[("invalid", "wrong")]): pass - def test_invalid_ocsp_repsonder_certificate_renewal(self) -> None: + def test_invalid_ocsp_responder_certificate_renewal(self) -> None: """Test the CA_OCSP_RESPONDER_CERTIFICATE_RENEWAL setting.""" with self.assertImproperlyConfigured( r"^CA_OCSP_RESPONDER_CERTIFICATE_RENEWAL must be a timedelta or int\.$" diff --git a/ca/django_ca/tests/test_tasks.py b/ca/django_ca/tests/test_tasks.py index 14ff916f6..bdbe8df04 100644 --- a/ca/django_ca/tests/test_tasks.py +++ b/ca/django_ca/tests/test_tasks.py @@ -49,9 +49,9 @@ AcmeOrder, Certificate, ) -from django_ca.tests.base import certs, override_tmpcadir, timestamps +from django_ca.tests.base.constants import CERT_DATA, TIMESTAMPS from django_ca.tests.base.mixins import AcmeValuesMixin, TestCaseMixin -from django_ca.tests.base.utils import subject_alternative_name +from django_ca.tests.base.utils import override_tmpcadir, subject_alternative_name from django_ca.utils import ca_storage, get_crl_cache_key @@ -112,7 +112,7 @@ def test_basic(self) -> None: self.assertIsInstance(crl.signature_hash_algorithm, type(ca.algorithm)) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_cache_all_crls(self) -> None: """Test caching when all CAs are valid.""" enc_cls = Encoding.DER @@ -131,7 +131,7 @@ def test_cache_all_crls(self) -> None: crl = x509.load_der_x509_crl(cache.get(key)) @override_tmpcadir() - @freeze_time(timestamps["everything_expired"]) + @freeze_time(TIMESTAMPS["everything_expired"]) def test_cache_all_crls_expired(self) -> None: """Test that nothing is cashed if all CAs are expired.""" tasks.cache_crls() @@ -153,7 +153,7 @@ def test_no_private_key(self) -> None: tasks.cache_crl(self.cas["pwd"].serial) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class GenerateOCSPKeysTestCase(TestCaseMixin, TestCase): """Test the generate_ocsp_key task.""" @@ -177,7 +177,7 @@ def test_all(self) -> None: self.assertTrue(ca_storage.exists(f"ocsp/{ca.serial}.pem")) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_repsonder_key_validity(self) -> None: """Test that the ocsp_responder_key_validity field works.""" ca = self.cas["root"] @@ -188,10 +188,10 @@ def test_repsonder_key_validity(self) -> None: tasks.generate_ocsp_key(ca.serial) cert = qs.get() - self.assertEqual(cert.expires, timestamps["everything_valid"] + timedelta(days=10)) + self.assertEqual(cert.expires, TIMESTAMPS["everything_valid"] + timedelta(days=10)) @override_tmpcadir() - @freeze_time(timestamps["everything_valid"]) + @freeze_time(TIMESTAMPS["everything_valid"]) def test_no_renewal_required(self) -> None: """Test that keys are not renewed and None is returned in this case.""" self.assertIsNotNone(tasks.generate_ocsp_key(self.ca.serial)) @@ -350,7 +350,7 @@ def test_multiple_auths(self) -> None: self.assertValid(AcmeOrder.STATUS_PENDING) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeValidateHttp01ChallengeTestCase(AcmeValidateChallengeTestCaseMixin, TestCase): """Test :py:func:`~django_ca.tasks.acme_validate_challenge`.""" @@ -408,7 +408,7 @@ def test_request_exception(self) -> None: self.assertEqual(logcm.output[1], f"INFO:django_ca.tasks:{str(self.chall)} is invalid") -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeValidateDns01ChallengeTestCase(AcmeValidateChallengeTestCaseMixin, TestCase): """Test :py:func:`~django_ca.tasks.acme_validate_challenge`.""" @@ -475,7 +475,7 @@ def test_nxdomain(self) -> None: ) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeIssueCertificateTestCase(TestCaseMixin, AcmeValuesMixin, TestCase): """Test :py:func:`~django_ca.tasks.acme_issue_certificate`.""" @@ -496,7 +496,7 @@ def setUp(self) -> None: # NOTE: This is of course not the right CSR for the order. It would be validated on submission, and # all data from the CSR is discarded anyway. self.acme_cert = AcmeCertificate.objects.create( - order=self.order, csr=certs["root-cert"]["csr"]["pem"] + order=self.order, csr=CERT_DATA["root-cert"]["csr"]["pem"] ) def test_acme_disabled(self) -> None: @@ -628,7 +628,7 @@ def test_profile(self) -> None: self.assertEqual(self.acme_cert.cert.profile, "client") -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class AcmeCleanupTestCase(TestCaseMixin, AcmeValuesMixin, TestCase): """Test :py:func:`~django_ca.tasks.acme_cleanup`.""" @@ -652,7 +652,7 @@ def setUp(self) -> None: # NOTE: This is of course not the right CSR for the order. It would be validated on submission, and # all data from the CSR is discarded anyway. self.acme_cert = AcmeCertificate.objects.create( - order=self.order, csr=certs["root-cert"]["csr"]["pem"] + order=self.order, csr=CERT_DATA["root-cert"]["csr"]["pem"] ) def test_basic(self) -> None: diff --git a/ca/django_ca/tests/test_utils.py b/ca/django_ca/tests/test_utils.py index d3c6a36d9..26d5e522f 100644 --- a/ca/django_ca/tests/test_utils.py +++ b/ca/django_ca/tests/test_utils.py @@ -38,7 +38,8 @@ from freezegun import freeze_time from django_ca import ca_settings, constants, utils -from django_ca.tests.base import CRYPTOGRAPHY_VERSION, dns, rdn, uri +from django_ca.tests.base.constants import CRYPTOGRAPHY_VERSION +from django_ca.tests.base.utils import dns, rdn, uri from django_ca.utils import ( bytes_to_hex, format_general_name, diff --git a/ca/django_ca/tests/test_verification.py b/ca/django_ca/tests/test_verification.py index a2c41e967..389b49116 100644 --- a/ca/django_ca/tests/test_verification.py +++ b/ca/django_ca/tests/test_verification.py @@ -27,8 +27,9 @@ from django.urls import reverse from django_ca.models import CertificateAuthority, X509CertMixin -from django_ca.tests.base import certs, override_tmpcadir, uri +from django_ca.tests.base.constants import CERT_DATA from django_ca.tests.base.mixins import TestCaseMixin +from django_ca.tests.base.utils import override_tmpcadir, uri class CRLValidationTestCase(TestCaseMixin, TestCase): @@ -36,7 +37,7 @@ class CRLValidationTestCase(TestCaseMixin, TestCase): def setUp(self) -> None: super().setUp() - self.csr_pem = certs["root-cert"]["csr"]["pem"] # just some CSR + self.csr_pem = CERT_DATA["root-cert"]["csr"]["pem"] # just some CSR def assertFullName( # pylint: disable=invalid-name self, diff --git a/ca/django_ca/tests/test_views.py b/ca/django_ca/tests/test_views.py index 54c29b4bb..602e6f5e3 100644 --- a/ca/django_ca/tests/test_views.py +++ b/ca/django_ca/tests/test_views.py @@ -26,8 +26,9 @@ from freezegun import freeze_time from django_ca import ca_settings -from django_ca.tests.base import certs, override_tmpcadir, uri +from django_ca.tests.base.constants import CERT_DATA from django_ca.tests.base.mixins import TestCaseMixin +from django_ca.tests.base.utils import override_tmpcadir, uri from django_ca.views import CertificateRevocationListView app_name = "django_ca" @@ -219,7 +220,7 @@ def test_password(self) -> None: for config in profiles.values(): config.setdefault("OVERRIDES", {}) config["OVERRIDES"].setdefault(ca.serial, {}) - config["OVERRIDES"][ca.serial]["password"] = certs["pwd"]["password"] + config["OVERRIDES"][ca.serial]["password"] = CERT_DATA["pwd"]["password"] with override_settings(CA_CRL_PROFILES=profiles): ca.cache_crls() # cache CRLs for this CA diff --git a/ca/django_ca/tests/test_views_ocsp.py b/ca/django_ca/tests/test_views_ocsp.py index 114b6ef4a..72a8318b2 100644 --- a/ca/django_ca/tests/test_views_ocsp.py +++ b/ca/django_ca/tests/test_views_ocsp.py @@ -14,7 +14,6 @@ """Test OCSP related views.""" import base64 -import os import typing from datetime import datetime, timedelta from http import HTTPStatus @@ -41,9 +40,10 @@ from django_ca.constants import ReasonFlags from django_ca.modelfields import LazyCertificate from django_ca.models import Certificate, CertificateAuthority -from django_ca.tests.base import FIXTURES_DIR, certs, ocsp_data, override_tmpcadir, timestamps +from django_ca.tests.base.constants import CERT_DATA, FIXTURES_DATA, FIXTURES_DIR, TIMESTAMPS from django_ca.tests.base.mixins import TestCaseMixin from django_ca.tests.base.typehints import HttpResponse +from django_ca.tests.base.utils import override_tmpcadir from django_ca.utils import ca_storage, hex_to_bytes from django_ca.views import OCSPView @@ -53,18 +53,17 @@ # # WHERE serial is an int: (int('0x'.replace(':', '').lower(), 0) def _load_req(req: str) -> bytes: - cert_path = os.path.join(FIXTURES_DIR, "ocsp", req) - with open(cert_path, "rb") as stream: + with open(FIXTURES_DIR / "ocsp" / req, "rb") as stream: return stream.read() -ocsp_profile = certs["profile-ocsp"] -ocsp_key_path = os.path.join(FIXTURES_DIR, ocsp_profile["key_filename"]) -ocsp_pem_path = os.path.join(FIXTURES_DIR, ocsp_profile["pub_filename"]) +ocsp_profile = CERT_DATA["profile-ocsp"] +ocsp_key_path = ocsp_profile["key_path"] +ocsp_pem_path = ocsp_profile["pub_path"] ocsp_pem = ocsp_profile["pub"]["pem"] -req1 = _load_req(ocsp_data["nonce"]["filename"]) -req1_nonce = hex_to_bytes(ocsp_data["nonce"]["nonce"]) -req_no_nonce = _load_req(ocsp_data["no-nonce"]["filename"]) +req1 = _load_req(FIXTURES_DATA["ocsp"]["nonce"]["filename"]) +req1_nonce = hex_to_bytes(FIXTURES_DATA["ocsp"]["nonce"]["nonce"]) +req_no_nonce = _load_req(FIXTURES_DATA["ocsp"]["no-nonce"]["filename"]) unknown_req = _load_req("unknown-serial") multiple_req = _load_req("multiple-serial") @@ -72,7 +71,7 @@ def _load_req(req: str) -> bytes: path( "ocsp/", OCSPView.as_view( - ca=certs["child"]["serial"], + ca=CERT_DATA["child"]["serial"], responder_key=ocsp_profile["key_filename"], responder_cert=ocsp_profile["pub_filename"], expires=1200, @@ -82,9 +81,9 @@ def _load_req(req: str) -> bytes: path( "ocsp/serial/", OCSPView.as_view( - ca=certs["child"]["serial"], + ca=CERT_DATA["child"]["serial"], responder_key=ocsp_profile["key_filename"], - responder_cert=certs["profile-ocsp"]["serial"], + responder_cert=CERT_DATA["profile-ocsp"]["serial"], expires=1300, ), name="post-serial", @@ -92,7 +91,7 @@ def _load_req(req: str) -> bytes: path( "ocsp/full-pem/", OCSPView.as_view( - ca=certs["child"]["serial"], + ca=CERT_DATA["child"]["serial"], responder_key=ocsp_profile["key_filename"], responder_cert=ocsp_pem, expires=1400, @@ -102,9 +101,9 @@ def _load_req(req: str) -> bytes: path( "ocsp/loaded-cryptography/", OCSPView.as_view( - ca=certs["child"]["serial"], + ca=CERT_DATA["child"]["serial"], responder_key=ocsp_profile["key_filename"], - responder_cert=certs["profile-ocsp"]["pub"]["parsed"], + responder_cert=CERT_DATA["profile-ocsp"]["pub"]["parsed"], expires=1500, ), name="post-loaded-cryptography", @@ -112,7 +111,7 @@ def _load_req(req: str) -> bytes: re_path( r"^ocsp/cert/(?P[a-zA-Z0-9=+/]+)$", OCSPView.as_view( - ca=certs["child"]["serial"], + ca=CERT_DATA["child"]["serial"], responder_key=ocsp_profile["key_filename"], responder_cert=ocsp_profile["pub_filename"], ), @@ -121,7 +120,7 @@ def _load_req(req: str) -> bytes: re_path( r"^ocsp/ca/(?P[a-zA-Z0-9=+/]+)$", OCSPView.as_view( - ca=certs["root"]["serial"], + ca=CERT_DATA["root"]["serial"], responder_key=ocsp_profile["key_filename"], responder_cert=ocsp_profile["pub_filename"], ca_ocsp=True, @@ -140,7 +139,7 @@ def _load_req(req: str) -> bytes: re_path( r"^ocsp/false-key/(?P[a-zA-Z0-9=+/]+)$", OCSPView.as_view( - ca=certs["child"]["serial"], + ca=CERT_DATA["child"]["serial"], responder_key="foobar", responder_cert=ocsp_profile["pub_filename"], expires=1200, @@ -151,7 +150,7 @@ def _load_req(req: str) -> bytes: re_path( r"^ocsp/false-pem/(?P[a-zA-Z0-9=+/]+)$", OCSPView.as_view( - ca=certs["child"]["serial"], + ca=CERT_DATA["child"]["serial"], responder_key=ocsp_profile["key_filename"], responder_cert="/false/foobar/", ), @@ -160,7 +159,7 @@ def _load_req(req: str) -> bytes: re_path( r"^ocsp/false-pem-serial/(?P[a-zA-Z0-9=+/]+)$", OCSPView.as_view( - ca=certs["child"]["serial"], + ca=CERT_DATA["child"]["serial"], responder_key=ocsp_profile["key_filename"], responder_cert="AA:BB:CC", ), @@ -169,7 +168,7 @@ def _load_req(req: str) -> bytes: re_path( r"^ocsp/false-pem-full/(?P[a-zA-Z0-9=+/]+)$", OCSPView.as_view( - ca=certs["child"]["serial"], + ca=CERT_DATA["child"]["serial"], responder_key=ocsp_profile["key_filename"], responder_cert="-----BEGIN CERTIFICATE-----\nvery-mean!", ), @@ -268,7 +267,7 @@ def assertOCSPResponse( # pylint: disable=invalid-name self.assertIsInstance(response.responder_key_hash, bytes) # TODO: Validate responder id # TODO: validate issuer_key_hash, issuer_name_hash - # Check timestamps + # Check TIMESTAMPS self.assertEqual(response.produced_at, datetime.now()) self.assertEqual(response.this_update, datetime.now()) self.assertEqual(response.next_update, datetime.now() + timedelta(seconds=expires)) @@ -653,18 +652,18 @@ def test_bad_responder_pem(self) -> None: @override_settings(ROOT_URLCONF=__name__) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class OCSPTestView(OCSPManualViewTestCaseMixin, TestCase): """Test manually configured OCSPView.""" @override_settings(ROOT_URLCONF=__name__, USE_TZ=False) -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class OCSPWithoutTimezoneSupportTestView(OCSPManualViewTestCaseMixin, TestCase): """Test manually configured OCSPView but with timezone support.""" -@freeze_time(timestamps["everything_valid"]) +@freeze_time(TIMESTAMPS["everything_valid"]) class GenericOCSPViewTestCase(OCSPViewTestMixin, TestCase): """Test generic OCSP view."""