From 50421e99beefd319cc864fa90cd7ba13e9f41786 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 18 May 2016 09:56:34 -0700 Subject: [PATCH 1/4] Factor loading cert/req into its own function --- certbot/crypto_util.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/certbot/crypto_util.py b/certbot/crypto_util.py index 3f2267af2..41e675471 100644 --- a/certbot/crypto_util.py +++ b/certbot/crypto_util.py @@ -228,15 +228,20 @@ def pyopenssl_load_certificate(data): str(error) for error in openssl_errors))) -def _get_sans_from_cert_or_req(cert_or_req_str, load_func, - typ=OpenSSL.crypto.FILETYPE_PEM): +def _load_cert_or_req(cert_or_req_str, load_func, + typ=OpenSSL.crypto.FILETYPE_PEM): try: - cert_or_req = load_func(typ, cert_or_req_str) + return load_func(typ, cert_or_req_str) except OpenSSL.crypto.Error as error: logger.exception(error) raise + + +def _get_sans_from_cert_or_req(cert_or_req_str, load_func, + typ=OpenSSL.crypto.FILETYPE_PEM): # pylint: disable=protected-access - return acme_crypto_util._pyopenssl_cert_or_req_san(cert_or_req) + return acme_crypto_util._pyopenssl_cert_or_req_san(_load_cert_or_req( + cert_or_req_str, load_func, typ)) def get_sans_from_cert(cert, typ=OpenSSL.crypto.FILETYPE_PEM): From 8e17d7498de484857daead51b56f50b186341233 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 18 May 2016 10:14:15 -0700 Subject: [PATCH 2/4] Add get_names_from_csr --- certbot/crypto_util.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/certbot/crypto_util.py b/certbot/crypto_util.py index 41e675471..b273cf59f 100644 --- a/certbot/crypto_util.py +++ b/certbot/crypto_util.py @@ -272,6 +272,27 @@ def get_sans_from_csr(csr, typ=OpenSSL.crypto.FILETYPE_PEM): csr, OpenSSL.crypto.load_certificate_request, typ) +def get_names_from_csr(csr, typ=OpenSSL.crypto.FILETYPE_PEM): + """Get a list of domains from a CSR, including the CN if it is set. + + :param str csr: CSR (encoded). + :param typ: `OpenSSL.crypto.FILETYPE_PEM` or `OpenSSL.crypto.FILETYPE_ASN1` + + :returns: A list of domain names. + :rtype: list + + """ + loaded_csr = _load_cert_or_req( + csr, OpenSSL.crypto.load_certificate_request, typ) + common_name = loaded_csr.get_subject().CN + + # Use a set to avoid duplication with CN and Subject Alt Names + domains = set() if common_name is None else set((common_name,)) + # pylint: disable=protected-access + domains.update(acme_crypto_util._pyopenssl_cert_or_req_san(loaded_csr)) + return list(domains) + + def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM): """Dump certificate chain into a bundle. From 77e4be933cbfffaadda97d45e2dfb17672de56b5 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 18 May 2016 13:59:17 -0700 Subject: [PATCH 3/4] Simplify get_names_from_csr --- certbot/crypto_util.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/certbot/crypto_util.py b/certbot/crypto_util.py index b273cf59f..1f87dc816 100644 --- a/certbot/crypto_util.py +++ b/certbot/crypto_util.py @@ -284,10 +284,8 @@ def get_names_from_csr(csr, typ=OpenSSL.crypto.FILETYPE_PEM): """ loaded_csr = _load_cert_or_req( csr, OpenSSL.crypto.load_certificate_request, typ) - common_name = loaded_csr.get_subject().CN - # Use a set to avoid duplication with CN and Subject Alt Names - domains = set() if common_name is None else set((common_name,)) + domains = set(d for d in (loaded_csr.get_subject().CN,) if d is not None) # pylint: disable=protected-access domains.update(acme_crypto_util._pyopenssl_cert_or_req_san(loaded_csr)) return list(domains) From 94549219c593dfc687b01f65ee89dc2287dae06e Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 18 May 2016 14:06:32 -0700 Subject: [PATCH 4/4] Add get_names_from_csr tests --- certbot/tests/crypto_util_test.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/certbot/tests/crypto_util_test.py b/certbot/tests/crypto_util_test.py index ff8d8142e..eade4861f 100644 --- a/certbot/tests/crypto_util_test.py +++ b/certbot/tests/crypto_util_test.py @@ -234,6 +234,36 @@ class GetSANsFromCSRTest(unittest.TestCase): [], self._call(test_util.load_vector('csr-nosans.pem'))) +class GetNamesFromCSRTest(unittest.TestCase): + """Tests for certbot.crypto_util.get_names_from_csr.""" + @classmethod + def _call(cls, *args, **kwargs): + from certbot.crypto_util import get_names_from_csr + return get_names_from_csr(*args, **kwargs) + + def test_extract_one_san(self): + self.assertEqual(['example.com'], self._call( + test_util.load_vector('csr.pem'))) + + def test_extract_two_sans(self): + self.assertEqual(set(('example.com', 'www.example.com',)), set( + self._call(test_util.load_vector('csr-san.pem')))) + + def test_extract_six_sans(self): + self.assertEqual( + set(self._call(test_util.load_vector('csr-6sans.pem'))), + set(("example.com", "example.org", "example.net", + "example.info", "subdomain.example.com", + "other.subdomain.example.com",))) + + def test_parse_non_csr(self): + self.assertRaises(OpenSSL.crypto.Error, self._call, "hello there") + + def test_parse_no_sans(self): + self.assertEqual(["example.org"], + self._call(test_util.load_vector('csr-nosans.pem'))) + + class CertLoaderTest(unittest.TestCase): """Tests for certbot.crypto_util.pyopenssl_load_certificate"""