From 8906f7a8c11cb893d85c72509cbedf032cbde335 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Fri, 11 May 2018 10:48:59 +0200 Subject: [PATCH] Add support for generating certs with multiple SANs --- .../utilitites/test_zaza_utilitites_cert.py | 49 +++++++++++++++++++ zaza/utilities/cert.py | 17 ++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/unit_tests/utilitites/test_zaza_utilitites_cert.py b/unit_tests/utilitites/test_zaza_utilitites_cert.py index 0b9be6f..53116b5 100644 --- a/unit_tests/utilitites/test_zaza_utilitites_cert.py +++ b/unit_tests/utilitites/test_zaza_utilitites_cert.py @@ -16,6 +16,35 @@ class TestUtilitiesCert(ut_utils.BaseTestCase): self.cryptography.x509.oid.NameOID.COMMON_NAME, 'unit_test.ci.local', ) + self.cryptography.x509.SubjectAlternativeName.assert_called_with( + [ + self.cryptography.x509.DNSName('unit_test.ci.local'), + ] + ) + self.cryptography.x509.BasicConstraints.assert_called_with( + ca=False, path_length=None + ) + + def test_generate_cert_san(self): + self.patch_object(cert, 'serialization') + self.patch_object(cert, 'rsa') + self.patch_object(cert, 'cryptography') + cert.generate_cert( + 'unit_test.ci.local', + alternative_names=['unit_test_second.ci.local', '172.16.42.1'] + ) + self.assertTrue(self.serialization.NoEncryption.called) + self.cryptography.x509.NameAttribute.assert_called_with( + self.cryptography.x509.oid.NameOID.COMMON_NAME, + 'unit_test.ci.local', + ) + self.cryptography.x509.SubjectAlternativeName.assert_called_with( + [ + self.cryptography.x509.DNSName('unit_test.ci.local'), + self.cryptography.x509.DNSName('unit_test_second.ci.local'), + self.cryptography.x509.IPAddress('172.16.42.1'), + ] + ) self.cryptography.x509.BasicConstraints.assert_called_with( ca=False, path_length=None ) @@ -30,6 +59,11 @@ class TestUtilitiesCert(ut_utils.BaseTestCase): self.cryptography.x509.oid.NameOID.COMMON_NAME, 'unit_test.ci.local', ) + self.cryptography.x509.SubjectAlternativeName.assert_called_with( + [ + self.cryptography.x509.DNSName('unit_test.ci.local'), + ] + ) self.cryptography.x509.BasicConstraints.assert_called_with( ca=False, path_length=None ) @@ -62,6 +96,11 @@ class TestUtilitiesCert(ut_utils.BaseTestCase): self.cryptography.x509.oid.NameOID.COMMON_NAME, 'unit_test.ci.local', ) + self.cryptography.x509.SubjectAlternativeName.assert_called_with( + [ + self.cryptography.x509.DNSName('unit_test.ci.local'), + ] + ) self.cryptography.x509.BasicConstraints.assert_called_with( ca=False, path_length=None ) @@ -85,6 +124,11 @@ class TestUtilitiesCert(ut_utils.BaseTestCase): self.cryptography.x509.oid.NameOID.COMMON_NAME, 'unit_test.ci.local', ) + self.cryptography.x509.SubjectAlternativeName.assert_called_with( + [ + self.cryptography.x509.DNSName('unit_test.ci.local'), + ] + ) self.cryptography.x509.BasicConstraints.assert_called_with( ca=False, path_length=None ) @@ -99,6 +143,11 @@ class TestUtilitiesCert(ut_utils.BaseTestCase): self.cryptography.x509.oid.NameOID.COMMON_NAME, 'unit_test.ci.local', ) + self.cryptography.x509.SubjectAlternativeName.assert_called_with( + [ + self.cryptography.x509.DNSName('unit_test.ci.local'), + ] + ) self.cryptography.x509.BasicConstraints.assert_called_with( ca=True, path_length=None ) diff --git a/zaza/utilities/cert.py b/zaza/utilities/cert.py index bb317e0..e04542f 100644 --- a/zaza/utilities/cert.py +++ b/zaza/utilities/cert.py @@ -19,9 +19,11 @@ from cryptography.hazmat.primitives.asymmetric import rsa import cryptography.hazmat.primitives.hashes as hashes import cryptography.hazmat.primitives.serialization as serialization import datetime +import ipaddress def generate_cert(common_name, + alternative_names=None, password=None, issuer_name=None, signing_key=None, @@ -38,6 +40,8 @@ def generate_cert(common_name, :param common_name: Common Name to use in generated certificate :type common_name: str + :param alternative_names: List of names to add as SubjectAlternativeName + :type alternative_names: Optional[list(str)] :param password: Password to protect encrypted private key with :type password: Optional[str] :param issuer_name: Issuer name, must match provided_private_key issuer @@ -92,9 +96,20 @@ def generate_cert(common_name, ) builder = builder.serial_number(cryptography.x509.random_serial_number()) builder = builder.public_key(public_key) + + san_list = [cryptography.x509.DNSName(common_name)] + if alternative_names is not None: + for name in alternative_names: + try: + addr = ipaddress.ip_address(name) + except ValueError: + san_list.append(cryptography.x509.DNSName(name)) + else: + san_list.append(cryptography.x509.IPAddress(addr)) + builder = builder.add_extension( cryptography.x509.SubjectAlternativeName( - [cryptography.x509.DNSName(common_name)], + san_list, ), critical=False, )