diff --git a/pyproject.toml b/pyproject.toml index 96095c2b..05a2528c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ pytest-html = "*" dynaconf = "*" python-keycloak = ">=2.13" python-jose = "*" +cryptography = "*" backoff = "*" httpx = { version = "*", extras = ["http2"] } openshift-client = ">=1.0.14" diff --git a/testsuite/certificates/__init__.py b/testsuite/certificates/__init__.py index bcdbcaf1..7e39c990 100644 --- a/testsuite/certificates/__init__.py +++ b/testsuite/certificates/__init__.py @@ -1,4 +1,5 @@ """Module containing classes for working with TLS certificates""" +import datetime import dataclasses import json import shutil @@ -7,6 +8,8 @@ from importlib import resources from typing import Optional, List, Dict, Any, Tuple, Collection, Union +from cryptography import x509 + class CFSSLException(Exception): """Common exception for CFSSL errors""" @@ -22,7 +25,7 @@ class CertInfo: names: Optional[List[Dict[str, str]]] = None -@dataclasses.dataclass +@dataclasses.dataclass(frozen=True) class Certificate: """Object representing Signed certificate""" @@ -30,6 +33,31 @@ class Certificate: certificate: str chain: str + @cached_property + def decoded(self) -> x509.Certificate: + """Returns decoded certificate""" + return x509.load_pem_x509_certificate(self.certificate.encode("utf-8")) + + @cached_property + def common_names(self) -> list[x509.NameAttribute]: + """Returns Common Names of the certificate""" + return self.decoded.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME) + + @cached_property + def duration(self) -> datetime.timedelta: + """Returns duration of the certificate""" + return self.decoded.not_valid_after - self.decoded.not_valid_before + + @cached_property + def usages(self) -> x509.KeyUsage: + """Returns certificate usages""" + return self.decoded.extensions.get_extension_for_class(x509.KeyUsage).value + + @cached_property + def algorithm(self) -> x509.ObjectIdentifier: + """Returns certificate algorithm""" + return self.decoded.signature_algorithm_oid + @dataclasses.dataclass class UnsignedKey: diff --git a/testsuite/openshift/objects/tlspolicy.py b/testsuite/openshift/objects/tlspolicy.py index a419590f..33728270 100644 --- a/testsuite/openshift/objects/tlspolicy.py +++ b/testsuite/openshift/objects/tlspolicy.py @@ -15,7 +15,12 @@ def create_instance( parent: Referencable, issuer: Referencable, labels: dict[str, str] = None, - ): + commonName: str = None, + duration: str = None, + usages: list[str] = None, + algorithm: str = None, + key_size: int = None, + ): # pylint: disable=invalid-name """Creates new instance of TLSPolicy""" model = { @@ -25,7 +30,20 @@ def create_instance( "spec": { "targetRef": parent.reference, "issuerRef": issuer.reference, + "commonName": commonName, + "duration": duration, + "usages": usages, + "privateKey": { + "algorithm": algorithm, + "size": key_size, + }, }, } return cls(model, context=openshift.context) + + def __setitem__(self, key, value): + self.model.spec[key] = value + + def __getitem__(self, key): + return self.model.spec[key] diff --git a/testsuite/tests/mgc/tlspolicy/__init__.py b/testsuite/tests/mgc/tlspolicy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testsuite/tests/mgc/tlspolicy/test_cert_parameters.py b/testsuite/tests/mgc/tlspolicy/test_cert_parameters.py new file mode 100644 index 00000000..b060792d --- /dev/null +++ b/testsuite/tests/mgc/tlspolicy/test_cert_parameters.py @@ -0,0 +1,58 @@ +"""Test parameters of TLS certificate generated by the TLSPolicy""" +from datetime import timedelta + +import pytest +from cryptography import x509 + +pytestmark = [pytest.mark.mgc] + + +@pytest.fixture(scope="module") +def dns_policy(): + """Don't need DNSPolicy because only testing certificate generated by TLSPolicy""" + return None + + +@pytest.fixture(scope="module") +def tls_policy(tls_policy): + """Update TLSPolicy with custom certificate parameters""" + tls_policy["commonName"] = "testCommonName" + tls_policy["duration"] = "240h" + tls_policy["usages"] = ["digital signature", "cert sign", "crl sign"] + tls_policy["privateKey"] = { + "algorithm": "ECDSA", + "size": 384, + } + + return tls_policy + + +@pytest.fixture(scope="module") +def tls_cert(upstream_gateway, gateway): # pylint: disable=unused-argument + """Return certificate generated by TLSPolicy""" + return upstream_gateway.get_tls_cert() + + +def test_tls_cert_common_name(tls_cert): + """Test certificate Common Name""" + assert tls_cert.common_names[0].value == "testCommonName" + + +def test_tls_cert_duration(tls_cert): + """Test certificate duration""" + assert tls_cert.duration == timedelta(hours=240) + + +def test_tls_cert_usages(tls_cert): + """Test certificate usages""" + assert tls_cert.usages.digital_signature + assert tls_cert.usages.key_cert_sign + assert tls_cert.usages.crl_sign + + assert not tls_cert.usages.key_encipherment + assert not tls_cert.usages.key_agreement + + +def test_tls_cert_algorithm(tls_cert): + """Test certificate algorithm""" + assert tls_cert.algorithm == x509.SignatureAlgorithmOID.ECDSA_WITH_SHA384