From d102c8be12d1a529939383869a78a64ca4f6e6d0 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 9 Dec 2014 23:04:09 +0100 Subject: [PATCH 1/9] Add tests for create_sig --- letsencrypt/client/tests/crypto_util_test.py | 50 ++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 letsencrypt/client/tests/crypto_util_test.py diff --git a/letsencrypt/client/tests/crypto_util_test.py b/letsencrypt/client/tests/crypto_util_test.py new file mode 100644 index 000000000..88fea4c11 --- /dev/null +++ b/letsencrypt/client/tests/crypto_util_test.py @@ -0,0 +1,50 @@ +"""Tests for letsencrypt.client.crypto_util.""" +import unittest + + +class CreateSigTest(unittest.TestCase): + """Tests for letsencrypt.client.crypto_util.create_sig.""" + + def setUp(self): + self.privkey = """-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAKx1c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79 +vukFhN9HFoHZiUvOjm0c+pVE6K+EdE/twuUCAwEAAQJAMbrEnJCrQe8YqAbw1/Bn +elAzIamndfE3U8bTavf9sgFpS4HL83rhd6PDbvx81ucaJAT/5x048fM/nFl4fzAc +mQIhAOF/a9o3EIsDKEmUl+Z1OaOiUxDF3kqWSmALEsmvDhwXAiEAw8ljV5RO/rUp +Zu2YMDFq3MKpyyMgBIJ8CxmGRc6gCmMCIGRQzkcmhfqBrhOFwkmozrqIBRIKJIjj +8TRm2LXWZZ2DAiAqVO7PztdNpynugUy4jtbGKKjBrTSNBRGA7OHlUgm0dQIhALQq +6oGU29Vxlvt3k0vmiRKU4AVfLyNXIGtcWcNG46h/ +-----END RSA PRIVATE KEY-----""" + self.nonce = '\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9' + self.b64nonce = '7Nbyb1lI6xPVI3Hg3aKSqQ' + self.signature = { + 'nonce': self.b64nonce, + 'alg': 'RS256', + 'jwk': { + 'kty': 'RSA', + 'e': 'AQAB', + 'n': 'rHVztFHtH92ucFJD_N_HW9AsdRsUuHUBBBDlHwNlRd3fp5' + '80rv2-6QWE30cWgdmJS86ObRz6lUTor4R0T-3C5Q', + }, + 'sig': 'SUPYKucUnhlTt8_sMxLiigOYdf_wlOLXPI-o7aRLTsOquVjDd6r' + 'AX9AFJHk-bCMQPJbSzXKjG6H1IWbvxjS2Ew', + } + + def _call(self, *args, **kwargs): + from letsencrypt.client.crypto_util import create_sig + return create_sig(*args, **kwargs) + + def test_it(self): + self.assertEqual( + self._call('message', self.privkey, self.nonce), self.signature) + + def test_random_nonce(self): + signature = self._call('message', self.privkey) + sig = signature.pop('sig') + nonce = signature.pop('nonce') + del self.signature['sig'] + del self.signature['nonce'] + self.assertEqual(signature, self.signature) + +if __name__ == '__main__': + unittest.main() From 2322266b98b683de84c45b15595ae35cb267b50e Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 10 Dec 2014 15:46:07 +0100 Subject: [PATCH 2/9] Export RSA key to file --- letsencrypt/client/tests/acme_test.py | 12 +++--------- letsencrypt/client/tests/crypto_util_test.py | 12 +++--------- letsencrypt/client/tests/testdata/rsa256_key.pem | 9 +++++++++ 3 files changed, 15 insertions(+), 18 deletions(-) create mode 100644 letsencrypt/client/tests/testdata/rsa256_key.pem diff --git a/letsencrypt/client/tests/acme_test.py b/letsencrypt/client/tests/acme_test.py index df232c75a..808eefc1b 100644 --- a/letsencrypt/client/tests/acme_test.py +++ b/letsencrypt/client/tests/acme_test.py @@ -1,4 +1,5 @@ """Tests for letsencrypt.client.acme.""" +import pkg_resources import unittest import jsonschema @@ -58,15 +59,8 @@ class MessageFactoriesTest(unittest.TestCase): """Tests for ACME message factories from letsencrypt.client.acme.""" def setUp(self): - self.privkey = """-----BEGIN RSA PRIVATE KEY----- -MIIBOgIBAAJBAKx1c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79 -vukFhN9HFoHZiUvOjm0c+pVE6K+EdE/twuUCAwEAAQJAMbrEnJCrQe8YqAbw1/Bn -elAzIamndfE3U8bTavf9sgFpS4HL83rhd6PDbvx81ucaJAT/5x048fM/nFl4fzAc -mQIhAOF/a9o3EIsDKEmUl+Z1OaOiUxDF3kqWSmALEsmvDhwXAiEAw8ljV5RO/rUp -Zu2YMDFq3MKpyyMgBIJ8CxmGRc6gCmMCIGRQzkcmhfqBrhOFwkmozrqIBRIKJIjj -8TRm2LXWZZ2DAiAqVO7PztdNpynugUy4jtbGKKjBrTSNBRGA7OHlUgm0dQIhALQq -6oGU29Vxlvt3k0vmiRKU4AVfLyNXIGtcWcNG46h/ ------END RSA PRIVATE KEY-----""" + self.privkey = pkg_resources.resource_string( + __name__, 'testdata/rsa256_key.pem') self.nonce = '\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9' self.b64nonce = '7Nbyb1lI6xPVI3Hg3aKSqQ' diff --git a/letsencrypt/client/tests/crypto_util_test.py b/letsencrypt/client/tests/crypto_util_test.py index 88fea4c11..65b730df0 100644 --- a/letsencrypt/client/tests/crypto_util_test.py +++ b/letsencrypt/client/tests/crypto_util_test.py @@ -1,4 +1,5 @@ """Tests for letsencrypt.client.crypto_util.""" +import pkg_resources import unittest @@ -6,15 +7,8 @@ class CreateSigTest(unittest.TestCase): """Tests for letsencrypt.client.crypto_util.create_sig.""" def setUp(self): - self.privkey = """-----BEGIN RSA PRIVATE KEY----- -MIIBOgIBAAJBAKx1c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79 -vukFhN9HFoHZiUvOjm0c+pVE6K+EdE/twuUCAwEAAQJAMbrEnJCrQe8YqAbw1/Bn -elAzIamndfE3U8bTavf9sgFpS4HL83rhd6PDbvx81ucaJAT/5x048fM/nFl4fzAc -mQIhAOF/a9o3EIsDKEmUl+Z1OaOiUxDF3kqWSmALEsmvDhwXAiEAw8ljV5RO/rUp -Zu2YMDFq3MKpyyMgBIJ8CxmGRc6gCmMCIGRQzkcmhfqBrhOFwkmozrqIBRIKJIjj -8TRm2LXWZZ2DAiAqVO7PztdNpynugUy4jtbGKKjBrTSNBRGA7OHlUgm0dQIhALQq -6oGU29Vxlvt3k0vmiRKU4AVfLyNXIGtcWcNG46h/ ------END RSA PRIVATE KEY-----""" + self.privkey = pkg_resources.resource_string( + __name__, 'testdata/rsa256_key.pem') self.nonce = '\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9' self.b64nonce = '7Nbyb1lI6xPVI3Hg3aKSqQ' self.signature = { diff --git a/letsencrypt/client/tests/testdata/rsa256_key.pem b/letsencrypt/client/tests/testdata/rsa256_key.pem new file mode 100644 index 000000000..610c8d315 --- /dev/null +++ b/letsencrypt/client/tests/testdata/rsa256_key.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAKx1c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79 +vukFhN9HFoHZiUvOjm0c+pVE6K+EdE/twuUCAwEAAQJAMbrEnJCrQe8YqAbw1/Bn +elAzIamndfE3U8bTavf9sgFpS4HL83rhd6PDbvx81ucaJAT/5x048fM/nFl4fzAc +mQIhAOF/a9o3EIsDKEmUl+Z1OaOiUxDF3kqWSmALEsmvDhwXAiEAw8ljV5RO/rUp +Zu2YMDFq3MKpyyMgBIJ8CxmGRc6gCmMCIGRQzkcmhfqBrhOFwkmozrqIBRIKJIjj +8TRm2LXWZZ2DAiAqVO7PztdNpynugUy4jtbGKKjBrTSNBRGA7OHlUgm0dQIhALQq +6oGU29Vxlvt3k0vmiRKU4AVfLyNXIGtcWcNG46h/ +-----END RSA PRIVATE KEY----- From a0a81bf53398a93ecdf112d09766e18d5b00d490 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 10 Dec 2014 17:07:12 +0100 Subject: [PATCH 3/9] More coverage for crypto_util --- letsencrypt/client/crypto_util.py | 68 ++++++----- letsencrypt/client/tests/crypto_util_test.py | 114 ++++++++++++++++++ letsencrypt/client/tests/testdata/csr-san.der | Bin 0 -> 370 bytes letsencrypt/client/tests/testdata/csr-san.pem | 10 ++ letsencrypt/client/tests/testdata/csr.der | Bin 0 -> 353 bytes letsencrypt/client/tests/testdata/csr.pem | 10 ++ .../client/tests/testdata/rsa512_key.pem | 9 ++ 7 files changed, 178 insertions(+), 33 deletions(-) create mode 100644 letsencrypt/client/tests/testdata/csr-san.der create mode 100644 letsencrypt/client/tests/testdata/csr-san.pem create mode 100644 letsencrypt/client/tests/testdata/csr.der create mode 100644 letsencrypt/client/tests/testdata/csr.pem create mode 100644 letsencrypt/client/tests/testdata/rsa512_key.pem diff --git a/letsencrypt/client/crypto_util.py b/letsencrypt/client/crypto_util.py index d19cbc0da..754557326 100644 --- a/letsencrypt/client/crypto_util.py +++ b/letsencrypt/client/crypto_util.py @@ -1,6 +1,5 @@ """Let's Encrypt client crypto utility functions""" import binascii -import hashlib import logging import time @@ -15,8 +14,6 @@ from letsencrypt.client import CONFIG from letsencrypt.client import le_util -# TODO: All of these functions need unit tests - def b64_cert_to_pem(b64_der_cert): return M2Crypto.X509.load_cert_der_string( le_util.jose_b64decode(b64_der_cert)).as_pem() @@ -76,27 +73,32 @@ def leading_zeros(arg): return arg -def sha256(arg): - return hashlib.sha256(arg).hexdigest() - - # based on M2Crypto unit test written by Toby Allsopp def make_key(bits=CONFIG.RSA_KEY_SIZE): + """Generate PEM encoded RSA key. + + :param int bits: Number of bits. + + :returns: new RSA key in PEM form with specified number of bits + :rtype: str + """ - Returns new RSA key in PEM form with specified bits - """ - # Python Crypto module doesn't produce any stdout - key = Crypto.PublicKey.RSA.generate(bits) # rsa = M2Crypto.RSA.gen_key(bits, 65537) # key_pem = rsa.as_pem(cipher=None) # rsa = None # should not be freed here - - return key.exportKey(format='PEM') + # Python Crypto module doesn't produce any stdout + return Crypto.PublicKey.RSA.generate(bits).exportKey(format='PEM') def make_csr(key_str, domains): - """ - Returns new CSR in PEM and DER form using key_file containing all domains + """Generate a CSR. + + :param str key_str: RSA key. + :param list domains: Domains included in the certificate. + + :returns: new CSR in PEM and DER form containing all domains + :rtype: tuple + """ assert domains, "Must provide one or more hostnames for the CSR." rsa_key = M2Crypto.RSA.load_key_string(key_str) @@ -115,7 +117,7 @@ def make_csr(key_str, domains): extstack = M2Crypto.X509.X509_Extension_Stack() ext = M2Crypto.X509.new_extension( - 'subjectAltName', ", ".join(["DNS:%s" % d for d in domains])) + 'subjectAltName', ", ".join("DNS:%s" % d for d in domains)) extstack.push(ext) csr.add_extensions(extstack) @@ -210,7 +212,7 @@ def valid_csr(csr): Check if `csr` is a valid CSR for the given domains. - :param str csr: CSR file contents + :param str csr: CSR in PEM. :returns: Validity of CSR. :rtype: bool @@ -229,7 +231,7 @@ def csr_matches_names(csr, domains): M2Crypto currently does not expose the OpenSSL interface to also check the SAN extension. This is insufficient for full testing - :param str csr: CSR file contents + :param str csr: CSR in DER. :param list domains: Domains the CSR should contain. @@ -244,6 +246,21 @@ def csr_matches_names(csr, domains): return False +def csr_matches_pubkey(csr, privkey): + """Does private key correspond to the subject public key in the CSR? + + :param str csr: CSR in PEM. + :param str privkey: Private key file contents + + :returns: Correspondence of private key to CSR subject public key. + :rtype: bool + + """ + csr_obj = M2Crypto.X509.load_request_string(csr) + privkey_obj = M2Crypto.RSA.load_key_string(privkey) + return csr_obj.get_pubkey().get_rsa().pub() == privkey_obj.pub() + + def valid_privkey(privkey): """Is valid RSA private key? @@ -257,18 +274,3 @@ def valid_privkey(privkey): return bool(M2Crypto.RSA.load_key_string(privkey).check_key()) except M2Crypto.RSA.RSAError: return False - - -def csr_matches_pubkey(csr, privkey): - """Does private key correspond to the subject public key in the CSR? - - :param str csr: CSR file contents - :param str privkey: Private key file contents - - :returns: Correspondence of private key to CSR subject public key. - :rtype: bool - - """ - csr_obj = M2Crypto.X509.load_request_string(csr) - privkey_obj = M2Crypto.RSA.load_key_string(privkey) - return csr_obj.get_pubkey().get_rsa().pub() == privkey_obj.pub() diff --git a/letsencrypt/client/tests/crypto_util_test.py b/letsencrypt/client/tests/crypto_util_test.py index 65b730df0..aad8ba1cf 100644 --- a/letsencrypt/client/tests/crypto_util_test.py +++ b/letsencrypt/client/tests/crypto_util_test.py @@ -1,5 +1,7 @@ """Tests for letsencrypt.client.crypto_util.""" +import os import pkg_resources +import tempfile import unittest @@ -40,5 +42,117 @@ class CreateSigTest(unittest.TestCase): del self.signature['nonce'] self.assertEqual(signature, self.signature) + +class MakeCSRTest(unittest.TestCase): + """Tests for letsencrypt.client.crypto_util.make_csr.""" + + def setUp(self): + self.key = pkg_resources.resource_string( + __name__, 'testdata/rsa256_key.pem') + + def test_single_domain(self): + from letsencrypt.client.crypto_util import make_csr + pem, der = make_csr(self.key, ['example.com']) + self.assertEqual(pem, pkg_resources.resource_string( + __name__, 'testdata/csr.pem')) + self.assertEqual(der, pkg_resources.resource_string( + __name__, 'testdata/csr.der')) + + def test_san(self): + from letsencrypt.client.crypto_util import make_csr + pem, der = make_csr(self.key, ['example.com', 'www.example.com']) + self.assertEqual(pem, pkg_resources.resource_string( + __name__, 'testdata/csr-san.pem')) + self.assertEqual(der, pkg_resources.resource_string( + __name__, 'testdata/csr-san.der')) + + +class ValidCSRTest(unittest.TestCase): + """Tests for letsencrypt.client.crypto_util.valid_csr.""" + + def _call(self, csr): + from letsencrypt.client.crypto_util import valid_csr + return valid_csr(csr) + + def _call_testdata(self, name): + return self._call(pkg_resources.resource_string( + __name__, os.path.join('testdata', name))) + + def test_valid_pem_true(self): + self.assertTrue(self._call_testdata('csr.pem')) + + def test_valid_pem_san_true(self): + self.assertTrue(self._call_testdata('csr-san.pem')) + + def test_valid_der_false(self): + self.assertFalse(self._call_testdata('csr.der')) + + def test_valid_der_san_false(self): + self.assertFalse(self._call_testdata('csr-san.der')) + + def test_empty_false(self): + self.assertFalse(self._call('')) + + def test_rubbis_false(self): + self.assertFalse(self._call('foo bar')) + + +class CSRMatchesNamesTest(unittest.TestCase): + """Tests for letsencrypt.client.crypto_util.csr_matches_names.""" + + def _call(self, csr, domains): + from letsencrypt.client.crypto_util import csr_matches_names + return csr_matches_names(csr, domains) + + def _call_testdata(self, name, domains): + return self._call(pkg_resources.resource_string( + __name__, os.path.join('testdata', name)), domains) + + def test_it(self): + self.assertTrue(self._call_testdata('csr.der', ['example.com'])) + self.assertFalse(self._call_testdata('csr.der', ['www.example.com'])) + self.assertFalse(self._call_testdata('csr.der', ['example'])) + + def test_san(self): + self.assertTrue(self._call_testdata('csr-san.der', ['example.com'])) + self.assertTrue(self._call_testdata('csr-san.der', ['www.example.com'])) + self.assertFalse(self._call_testdata('csr-san.der', ['example'])) + + +class CSRMatchesPubkeyTest(unittest.TestCase): + """Tests for letsencrypt.client.crypto_util.csr_matches_pubkey.""" + + def _call_testdata(self, name, privkey): + from letsencrypt.client.crypto_util import csr_matches_pubkey + return csr_matches_pubkey(pkg_resources.resource_string( + __name__, os.path.join('testdata', name)), privkey) + + def test_valid_true(self): + key = pkg_resources.resource_string(__name__, 'testdata/rsa256_key.pem') + self.assertTrue(self._call_testdata('csr.pem', key)) + + def test_invalid_false(self): + key = pkg_resources.resource_string(__name__, 'testdata/rsa512_key.pem') + self.assertFalse(self._call_testdata('csr.pem', key)) + + +class ValidPrivkeyTest(unittest.TestCase): + """Tests fro letsencrypt.client.crypto_util.valid_privkey.""" + + def _call(self, privkey): + from letsencrypt.client.crypto_util import valid_privkey + return valid_privkey(privkey) + + def test_valid_true(self): + self.assertTrue(self._call(pkg_resources.resource_string( + __name__, 'testdata/rsa256_key.pem'))) + + def test_empty_false(self): + self.assertFalse(self._call('')) + + def test_rubbish_false(self): + self.assertFalse(self._call('foo bar')) + + if __name__ == '__main__': unittest.main() diff --git a/letsencrypt/client/tests/testdata/csr-san.der b/letsencrypt/client/tests/testdata/csr-san.der new file mode 100644 index 0000000000000000000000000000000000000000..68fd38723ddd62a2d60fd6c27e67925d273d51f8 GIT binary patch literal 370 zcmXqLV$3sWVw7NFWH6{S`GxkDEsS*Ho7m>sw0v z-yV9(#LURRxWLN50&Y4dpP{yarhz)p5we0T3I=jb$PQ`ZFE20GLv|UGySRbwa%4zJ z@cA$|+wI(oIT5`GxkDEsS*Ho7m>sw0v z-yV9(#LURRxIoiD9d0@&pP`h2gn<~)5we0T{06*DC=Nk#1~ Date: Wed, 10 Dec 2014 18:50:54 +0100 Subject: [PATCH 4/9] wording --- letsencrypt/client/tests/crypto_util_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/letsencrypt/client/tests/crypto_util_test.py b/letsencrypt/client/tests/crypto_util_test.py index aad8ba1cf..affccfed7 100644 --- a/letsencrypt/client/tests/crypto_util_test.py +++ b/letsencrypt/client/tests/crypto_util_test.py @@ -93,7 +93,7 @@ class ValidCSRTest(unittest.TestCase): def test_empty_false(self): self.assertFalse(self._call('')) - def test_rubbis_false(self): + def test_random_false(self): self.assertFalse(self._call('foo bar')) @@ -150,7 +150,7 @@ class ValidPrivkeyTest(unittest.TestCase): def test_empty_false(self): self.assertFalse(self._call('')) - def test_rubbish_false(self): + def test_random_false(self): self.assertFalse(self._call('foo bar')) From 40837e9d56ffee85e6894a10b5632a8b93adb983 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Thu, 11 Dec 2014 23:53:24 +0100 Subject: [PATCH 5/9] More tests for crypto_util --- letsencrypt/client/crypto_util.py | 196 +++++++++--------- letsencrypt/client/tests/crypto_util_test.py | 85 ++++++-- .../client/tests/testdata/cert-san.pem | 14 ++ letsencrypt/client/tests/testdata/cert.pem | 13 ++ 4 files changed, 191 insertions(+), 117 deletions(-) create mode 100644 letsencrypt/client/tests/testdata/cert-san.pem create mode 100644 letsencrypt/client/tests/testdata/cert.pem diff --git a/letsencrypt/client/crypto_util.py b/letsencrypt/client/crypto_util.py index 754557326..1d323cec2 100644 --- a/letsencrypt/client/crypto_util.py +++ b/letsencrypt/client/crypto_util.py @@ -14,11 +14,6 @@ from letsencrypt.client import CONFIG from letsencrypt.client import le_util -def b64_cert_to_pem(b64_der_cert): - return M2Crypto.X509.load_cert_der_string( - le_util.jose_b64decode(b64_der_cert)).as_pem() - - def create_sig(msg, key_str, nonce=None, nonce_len=CONFIG.NONCE_SIZE): """Create signature with nonce prepended to the message. @@ -73,23 +68,6 @@ def leading_zeros(arg): return arg -# based on M2Crypto unit test written by Toby Allsopp -def make_key(bits=CONFIG.RSA_KEY_SIZE): - """Generate PEM encoded RSA key. - - :param int bits: Number of bits. - - :returns: new RSA key in PEM form with specified number of bits - :rtype: str - - """ - # rsa = M2Crypto.RSA.gen_key(bits, 65537) - # key_pem = rsa.as_pem(cipher=None) - # rsa = None # should not be freed here - # Python Crypto module doesn't produce any stdout - return Crypto.PublicKey.RSA.generate(bits).exportKey(format='PEM') - - def make_csr(key_str, domains): """Generate a CSR. @@ -128,80 +106,6 @@ def make_csr(key_str, domains): return csr.as_pem(), csr.as_der() -def make_ss_cert(key_str, domains): - """Returns new self-signed cert in PEM form. - - Uses key_str and contains all domains. - """ - assert domains, "Must provide one or more hostnames for the CSR." - - rsa_key = M2Crypto.RSA.load_key_string(key_str) - pubkey = M2Crypto.EVP.PKey() - pubkey.assign_rsa(rsa_key) - - cert = M2Crypto.X509.X509() - cert.set_pubkey(pubkey) - cert.set_serial_number(1337) - cert.set_version(2) - - current_ts = long(time.time()) - current = M2Crypto.ASN1.ASN1_UTCTIME() - current.set_time(current_ts) - expire = M2Crypto.ASN1.ASN1_UTCTIME() - expire.set_time((7 * 24 * 60 * 60) + current_ts) - cert.set_not_before(current) - cert.set_not_after(expire) - - subject = cert.get_subject() - subject.C = "US" - subject.ST = "Michigan" - subject.L = "Ann Arbor" - subject.O = "University of Michigan and the EFF" - subject.CN = domains[0] - cert.set_issuer(cert.get_subject()) - - cert.add_ext(M2Crypto.X509.new_extension('basicConstraints', 'CA:FALSE')) - # cert.add_ext(M2Crypto.X509.new_extension( - # 'extendedKeyUsage', 'TLS Web Server Authentication')) - cert.add_ext(M2Crypto.X509.new_extension( - 'subjectAltName', ", ".join(["DNS:%s" % d for d in domains]))) - - cert.sign(pubkey, 'sha256') - assert cert.verify(pubkey) - assert cert.verify() - # print check_purpose(,0 - return cert.as_pem() - - -def get_cert_info(filename): - """Get certificate info. - - :param str filename: Name of file containing certificate in PEM format. - - :rtype: dict - - """ - # M2Crypto Library only supports RSA right now - cert = M2Crypto.X509.load_cert(filename) - - try: - san = cert.get_ext("subjectAltName").get_value() - except: - san = "" - - return { - "not_before": cert.get_not_before().get_datetime(), - "not_after": cert.get_not_after().get_datetime(), - "subject": cert.get_subject().as_text(), - "cn": cert.get_subject().CN, - "issuer": cert.get_issuer().as_text(), - "fingerprint": cert.get_fingerprint(md='sha1'), - "san": san, - "serial": cert.get_serial_number(), - "pub_key": "RSA " + str(cert.get_pubkey().size() * 8), - } - - # WARNING: the csr and private key file are possible attack vectors for TOCTOU # We should either... # A. Do more checks to verify that the CSR is trusted/valid @@ -261,6 +165,23 @@ def csr_matches_pubkey(csr, privkey): return csr_obj.get_pubkey().get_rsa().pub() == privkey_obj.pub() +# based on M2Crypto unit test written by Toby Allsopp +def make_key(bits=CONFIG.RSA_KEY_SIZE): + """Generate PEM encoded RSA key. + + :param int bits: Number of bits. + + :returns: new RSA key in PEM form with specified number of bits + :rtype: str + + """ + # rsa = M2Crypto.RSA.gen_key(bits, 65537) + # key_pem = rsa.as_pem(cipher=None) + # rsa = None # should not be freed here + # Python Crypto module doesn't produce any stdout + return Crypto.PublicKey.RSA.generate(bits).exportKey(format='PEM') + + def valid_privkey(privkey): """Is valid RSA private key? @@ -274,3 +195,86 @@ def valid_privkey(privkey): return bool(M2Crypto.RSA.load_key_string(privkey).check_key()) except M2Crypto.RSA.RSAError: return False + + +def make_ss_cert(key_str, domains, not_before=None, + validity=(7 * 24 * 60 * 60)): + """Returns new self-signed cert in PEM form. + + Uses key_str and contains all domains. + + """ + assert domains, "Must provide one or more hostnames for the CSR." + + rsa_key = M2Crypto.RSA.load_key_string(key_str) + pubkey = M2Crypto.EVP.PKey() + pubkey.assign_rsa(rsa_key) + + cert = M2Crypto.X509.X509() + cert.set_pubkey(pubkey) + cert.set_serial_number(1337) + cert.set_version(2) + + current_ts = long(time.time() if not_before is None else not_before) + current = M2Crypto.ASN1.ASN1_UTCTIME() + current.set_time(current_ts) + expire = M2Crypto.ASN1.ASN1_UTCTIME() + expire.set_time(current_ts + validity) + cert.set_not_before(current) + cert.set_not_after(expire) + + subject = cert.get_subject() + subject.C = "US" + subject.ST = "Michigan" + subject.L = "Ann Arbor" + subject.O = "University of Michigan and the EFF" + subject.CN = domains[0] + cert.set_issuer(cert.get_subject()) + + if len(domains) > 1: + cert.add_ext(M2Crypto.X509.new_extension( + 'basicConstraints', 'CA:FALSE')) + # cert.add_ext(M2Crypto.X509.new_extension( + # 'extendedKeyUsage', 'TLS Web Server Authentication')) + cert.add_ext(M2Crypto.X509.new_extension( + 'subjectAltName', ", ".join(["DNS:%s" % d for d in domains]))) + + cert.sign(pubkey, 'sha256') + assert cert.verify(pubkey) + assert cert.verify() + # print check_purpose(,0 + return cert.as_pem() + + +def get_cert_info(filename): + """Get certificate info. + + :param str filename: Name of file containing certificate in PEM format. + + :rtype: dict + + """ + # M2Crypto Library only supports RSA right now + cert = M2Crypto.X509.load_cert(filename) + + try: + san = cert.get_ext("subjectAltName").get_value() + except: + san = "" + + return { + "not_before": cert.get_not_before().get_datetime(), + "not_after": cert.get_not_after().get_datetime(), + "subject": cert.get_subject().as_text(), + "cn": cert.get_subject().CN, + "issuer": cert.get_issuer().as_text(), + "fingerprint": cert.get_fingerprint(md='sha1'), + "san": san, + "serial": cert.get_serial_number(), + "pub_key": "RSA " + str(cert.get_pubkey().size() * 8), + } + + +def b64_cert_to_pem(b64_der_cert): + return M2Crypto.X509.load_cert_der_string( + le_util.jose_b64decode(b64_der_cert)).as_pem() diff --git a/letsencrypt/client/tests/crypto_util_test.py b/letsencrypt/client/tests/crypto_util_test.py index affccfed7..d1f40f360 100644 --- a/letsencrypt/client/tests/crypto_util_test.py +++ b/letsencrypt/client/tests/crypto_util_test.py @@ -1,16 +1,20 @@ """Tests for letsencrypt.client.crypto_util.""" +import datetime import os import pkg_resources -import tempfile import unittest +import M2Crypto + + +RSA256_KEY = pkg_resources.resource_string(__name__, 'testdata/rsa256_key.pem') +RSA512_KEY = pkg_resources.resource_string(__name__, 'testdata/rsa512_key.pem') + class CreateSigTest(unittest.TestCase): """Tests for letsencrypt.client.crypto_util.create_sig.""" def setUp(self): - self.privkey = pkg_resources.resource_string( - __name__, 'testdata/rsa256_key.pem') self.nonce = '\xec\xd6\xf2oYH\xeb\x13\xd5#q\xe0\xdd\xa2\x92\xa9' self.b64nonce = '7Nbyb1lI6xPVI3Hg3aKSqQ' self.signature = { @@ -32,12 +36,12 @@ class CreateSigTest(unittest.TestCase): def test_it(self): self.assertEqual( - self._call('message', self.privkey, self.nonce), self.signature) + self._call('message', RSA256_KEY, self.nonce), self.signature) def test_random_nonce(self): - signature = self._call('message', self.privkey) - sig = signature.pop('sig') - nonce = signature.pop('nonce') + signature = self._call('message', RSA256_KEY) + signature.pop('sig') + signature.pop('nonce') del self.signature['sig'] del self.signature['nonce'] self.assertEqual(signature, self.signature) @@ -46,13 +50,9 @@ class CreateSigTest(unittest.TestCase): class MakeCSRTest(unittest.TestCase): """Tests for letsencrypt.client.crypto_util.make_csr.""" - def setUp(self): - self.key = pkg_resources.resource_string( - __name__, 'testdata/rsa256_key.pem') - def test_single_domain(self): from letsencrypt.client.crypto_util import make_csr - pem, der = make_csr(self.key, ['example.com']) + pem, der = make_csr(RSA256_KEY, ['example.com']) self.assertEqual(pem, pkg_resources.resource_string( __name__, 'testdata/csr.pem')) self.assertEqual(der, pkg_resources.resource_string( @@ -60,7 +60,7 @@ class MakeCSRTest(unittest.TestCase): def test_san(self): from letsencrypt.client.crypto_util import make_csr - pem, der = make_csr(self.key, ['example.com', 'www.example.com']) + pem, der = make_csr(RSA256_KEY, ['example.com', 'www.example.com']) self.assertEqual(pem, pkg_resources.resource_string( __name__, 'testdata/csr-san.pem')) self.assertEqual(der, pkg_resources.resource_string( @@ -108,7 +108,7 @@ class CSRMatchesNamesTest(unittest.TestCase): return self._call(pkg_resources.resource_string( __name__, os.path.join('testdata', name)), domains) - def test_it(self): + def test_single_domain(self): self.assertTrue(self._call_testdata('csr.der', ['example.com'])) self.assertFalse(self._call_testdata('csr.der', ['www.example.com'])) self.assertFalse(self._call_testdata('csr.der', ['example'])) @@ -128,24 +128,21 @@ class CSRMatchesPubkeyTest(unittest.TestCase): __name__, os.path.join('testdata', name)), privkey) def test_valid_true(self): - key = pkg_resources.resource_string(__name__, 'testdata/rsa256_key.pem') - self.assertTrue(self._call_testdata('csr.pem', key)) + self.assertTrue(self._call_testdata('csr.pem', RSA256_KEY)) def test_invalid_false(self): - key = pkg_resources.resource_string(__name__, 'testdata/rsa512_key.pem') - self.assertFalse(self._call_testdata('csr.pem', key)) + self.assertFalse(self._call_testdata('csr.pem', RSA512_KEY)) class ValidPrivkeyTest(unittest.TestCase): - """Tests fro letsencrypt.client.crypto_util.valid_privkey.""" + """Tests for letsencrypt.client.crypto_util.valid_privkey.""" def _call(self, privkey): from letsencrypt.client.crypto_util import valid_privkey return valid_privkey(privkey) def test_valid_true(self): - self.assertTrue(self._call(pkg_resources.resource_string( - __name__, 'testdata/rsa256_key.pem'))) + self.assertTrue(self._call(RSA256_KEY)) def test_empty_false(self): self.assertFalse(self._call('')) @@ -154,5 +151,51 @@ class ValidPrivkeyTest(unittest.TestCase): self.assertFalse(self._call('foo bar')) +class MakeSSCertTest(unittest.TestCase): + """Tests for letsencrypt.client.crypto_util.make_ss_cert.""" + + def test_it(self): + from letsencrypt.client.crypto_util import make_ss_cert + make_ss_cert(RSA256_KEY, ['example.com', 'www.example.com']) + + +class GetCertInfoTest(unittest.TestCase): + """Tests for letsencrypt.client.crypto_util.get_cert_info.""" + + def setUp(self): + self.cert_info = { + 'not_before': datetime.datetime( + 2014, 12, 11, 22, 34, 45, tzinfo=M2Crypto.ASN1.UTC), + 'not_after': datetime.datetime( + 2014, 12, 18, 22, 34, 45, tzinfo=M2Crypto.ASN1.UTC), + 'subject': 'C=US, ST=Michigan, L=Ann Arbor, O=University ' + 'of Michigan and the EFF, CN=example.com', + 'cn': 'example.com', + 'issuer': 'C=US, ST=Michigan, L=Ann Arbor, O=University ' + 'of Michigan and the EFF, CN=example.com', + 'serial': 1337L, + 'pub_key': 'RSA 512', + } + + def _call(self, name): + from letsencrypt.client.crypto_util import get_cert_info + self.assertEqual(get_cert_info(pkg_resources.resource_filename( + __name__, os.path.join('testdata', name))), self.cert_info) + + def test_single_domain(self): + self.cert_info.update({ + 'san': '', + 'fingerprint': '9F8CE01450D288467C3326AC0457E351939C72E', + }) + self._call('cert.pem') + + def test_san(self): + self.cert_info.update({ + 'san': 'DNS:example.com, DNS:www.example.com', + 'fingerprint': '62F7110431B8E8F55905DBE5592518F9634AC50A', + }) + self._call('cert-san.pem') + + if __name__ == '__main__': unittest.main() diff --git a/letsencrypt/client/tests/testdata/cert-san.pem b/letsencrypt/client/tests/testdata/cert-san.pem new file mode 100644 index 000000000..dcb835994 --- /dev/null +++ b/letsencrypt/client/tests/testdata/cert-san.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICFjCCAcCgAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM +IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 +YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG +A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix +KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS +BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR +7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c ++pVE6K+EdE/twuUCAwEAAaM2MDQwCQYDVR0TBAIwADAnBgNVHREEIDAeggtleGFt +cGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA0EASuvNKFTF +nTJsvnSXn52f4BMZJJ2id/kW7+r+FJRm+L20gKQ1aqq8d3e/lzRUrv5SMf1TAOe7 +RDjyGMKy5ZgM2w== +-----END CERTIFICATE----- diff --git a/letsencrypt/client/tests/testdata/cert.pem b/letsencrypt/client/tests/testdata/cert.pem new file mode 100644 index 000000000..96c55cbf4 --- /dev/null +++ b/letsencrypt/client/tests/testdata/cert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB3jCCAYigAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx +ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM +IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 +YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG +A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix +KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS +BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR +7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c ++pVE6K+EdE/twuUCAwEAATANBgkqhkiG9w0BAQsFAANBAC24z0IdwIVKSlntksll +vr6zJepBH5fMndfk3XJp10jT6VE+14KNtjh02a56GoraAvJAT5/H67E8GvJ/ocNn +B/o= +-----END CERTIFICATE----- From 0b7121341f0c3124ac1061075ec3f8e2de4eb6ba Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Fri, 12 Dec 2014 10:37:53 +0100 Subject: [PATCH 6/9] Remove csr_matches_names. c.f. #127 and https://github.com/letsencrypt/lets-encrypt-preview/pull/127#discussion-diff-21613376 --- letsencrypt/client/client.py | 5 ----- letsencrypt/client/crypto_util.py | 21 ------------------- letsencrypt/client/tests/crypto_util_test.py | 22 -------------------- 3 files changed, 48 deletions(-) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index b6f507688..4765014ec 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -118,11 +118,6 @@ class Client(object): # Make sure we have key and csr to perform challenges self.init_key_csr() - # TODO: Handle this exception/problem - if not crypto_util.csr_matches_names(self.csr.data, self.names): - raise errors.LetsEncryptClientError( - "CSR subject does not contain one of the specified names") - # Perform Challenges responses, challenge_objs = self.verify_identity(challenge_msg) # Get Authorization diff --git a/letsencrypt/client/crypto_util.py b/letsencrypt/client/crypto_util.py index 1d323cec2..bf9989495 100644 --- a/letsencrypt/client/crypto_util.py +++ b/letsencrypt/client/crypto_util.py @@ -129,27 +129,6 @@ def valid_csr(csr): return False -def csr_matches_names(csr, domains): - """Check if CSR contains the subject of one of the domains. - - M2Crypto currently does not expose the OpenSSL interface to - also check the SAN extension. This is insufficient for full testing - - :param str csr: CSR in DER. - - :param list domains: Domains the CSR should contain. - - :returns: If the CSR subject contains one of the domains - :rtype: bool - - """ - try: - csr_obj = M2Crypto.X509.load_request_der_string(csr) - return csr_obj.get_subject().CN in domains - except M2Crypto.X509.X509Error: - return False - - def csr_matches_pubkey(csr, privkey): """Does private key correspond to the subject public key in the CSR? diff --git a/letsencrypt/client/tests/crypto_util_test.py b/letsencrypt/client/tests/crypto_util_test.py index d1f40f360..76cbc8310 100644 --- a/letsencrypt/client/tests/crypto_util_test.py +++ b/letsencrypt/client/tests/crypto_util_test.py @@ -97,28 +97,6 @@ class ValidCSRTest(unittest.TestCase): self.assertFalse(self._call('foo bar')) -class CSRMatchesNamesTest(unittest.TestCase): - """Tests for letsencrypt.client.crypto_util.csr_matches_names.""" - - def _call(self, csr, domains): - from letsencrypt.client.crypto_util import csr_matches_names - return csr_matches_names(csr, domains) - - def _call_testdata(self, name, domains): - return self._call(pkg_resources.resource_string( - __name__, os.path.join('testdata', name)), domains) - - def test_single_domain(self): - self.assertTrue(self._call_testdata('csr.der', ['example.com'])) - self.assertFalse(self._call_testdata('csr.der', ['www.example.com'])) - self.assertFalse(self._call_testdata('csr.der', ['example'])) - - def test_san(self): - self.assertTrue(self._call_testdata('csr-san.der', ['example.com'])) - self.assertTrue(self._call_testdata('csr-san.der', ['www.example.com'])) - self.assertFalse(self._call_testdata('csr-san.der', ['example'])) - - class CSRMatchesPubkeyTest(unittest.TestCase): """Tests for letsencrypt.client.crypto_util.csr_matches_pubkey.""" From ccfeef3e8e5cf68985f7154b6fedd93c552a4b5b Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Fri, 12 Dec 2014 10:54:17 +0100 Subject: [PATCH 7/9] Add tests for b64_cert_to_pem --- letsencrypt/client/tests/crypto_util_test.py | 11 +++++++++++ letsencrypt/client/tests/testdata/cert.b64jose | 1 + 2 files changed, 12 insertions(+) create mode 100644 letsencrypt/client/tests/testdata/cert.b64jose diff --git a/letsencrypt/client/tests/crypto_util_test.py b/letsencrypt/client/tests/crypto_util_test.py index 76cbc8310..272555d80 100644 --- a/letsencrypt/client/tests/crypto_util_test.py +++ b/letsencrypt/client/tests/crypto_util_test.py @@ -175,5 +175,16 @@ class GetCertInfoTest(unittest.TestCase): self._call('cert-san.pem') +class B64CertToPEMTest(unittest.TestCase): + """Tests for letsencrypt.client.crypto_util.b64_cert_to_pem.""" + + def test_it(self): + from letsencrypt.client.crypto_util import b64_cert_to_pem + self.assertEqual( + b64_cert_to_pem(pkg_resources.resource_string( + __name__, 'testdata/cert.b64jose')), + pkg_resources.resource_string(__name__, 'testdata/cert.pem')) + + if __name__ == '__main__': unittest.main() diff --git a/letsencrypt/client/tests/testdata/cert.b64jose b/letsencrypt/client/tests/testdata/cert.b64jose new file mode 100644 index 000000000..fa1abdb9f --- /dev/null +++ b/letsencrypt/client/tests/testdata/cert.b64jose @@ -0,0 +1 @@ +MIIB3jCCAYigAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR7R_drnBSQ_zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c-pVE6K-EdE_twuUCAwEAATANBgkqhkiG9w0BAQsFAANBAC24z0IdwIVKSlntksllvr6zJepBH5fMndfk3XJp10jT6VE-14KNtjh02a56GoraAvJAT5_H67E8GvJ_ocNnB_o \ No newline at end of file From 44a050a0610c0ad2745b64129b9c690f6bcb86ec Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Fri, 12 Dec 2014 11:16:17 +0100 Subject: [PATCH 8/9] Add tests for make_key --- letsencrypt/client/crypto_util.py | 2 +- letsencrypt/client/tests/crypto_util_test.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/letsencrypt/client/crypto_util.py b/letsencrypt/client/crypto_util.py index bf9989495..554ccf684 100644 --- a/letsencrypt/client/crypto_util.py +++ b/letsencrypt/client/crypto_util.py @@ -148,7 +148,7 @@ def csr_matches_pubkey(csr, privkey): def make_key(bits=CONFIG.RSA_KEY_SIZE): """Generate PEM encoded RSA key. - :param int bits: Number of bits. + :param int bits: Number of bits, at least 1024. :returns: new RSA key in PEM form with specified number of bits :rtype: str diff --git a/letsencrypt/client/tests/crypto_util_test.py b/letsencrypt/client/tests/crypto_util_test.py index 272555d80..ac4b59d37 100644 --- a/letsencrypt/client/tests/crypto_util_test.py +++ b/letsencrypt/client/tests/crypto_util_test.py @@ -112,6 +112,14 @@ class CSRMatchesPubkeyTest(unittest.TestCase): self.assertFalse(self._call_testdata('csr.pem', RSA512_KEY)) +class MakeKeyTest(unittest.TestCase): + """Tests for letsencrypt.client.crypto_util.make_key.""" + + def test_it(self): + from letsencrypt.client.crypto_util import make_key + M2Crypto.RSA.load_key_string(make_key(1024)) + + class ValidPrivkeyTest(unittest.TestCase): """Tests for letsencrypt.client.crypto_util.valid_privkey.""" From b8902c272d12b8c315bc505607314a9ef7a14301 Mon Sep 17 00:00:00 2001 From: James Kasten Date: Sun, 14 Dec 2014 04:30:12 -0800 Subject: [PATCH 9/9] Remove MakeCSRTest --- letsencrypt/client/tests/crypto_util_test.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/letsencrypt/client/tests/crypto_util_test.py b/letsencrypt/client/tests/crypto_util_test.py index ac4b59d37..e80988d83 100644 --- a/letsencrypt/client/tests/crypto_util_test.py +++ b/letsencrypt/client/tests/crypto_util_test.py @@ -47,26 +47,6 @@ class CreateSigTest(unittest.TestCase): self.assertEqual(signature, self.signature) -class MakeCSRTest(unittest.TestCase): - """Tests for letsencrypt.client.crypto_util.make_csr.""" - - def test_single_domain(self): - from letsencrypt.client.crypto_util import make_csr - pem, der = make_csr(RSA256_KEY, ['example.com']) - self.assertEqual(pem, pkg_resources.resource_string( - __name__, 'testdata/csr.pem')) - self.assertEqual(der, pkg_resources.resource_string( - __name__, 'testdata/csr.der')) - - def test_san(self): - from letsencrypt.client.crypto_util import make_csr - pem, der = make_csr(RSA256_KEY, ['example.com', 'www.example.com']) - self.assertEqual(pem, pkg_resources.resource_string( - __name__, 'testdata/csr-san.pem')) - self.assertEqual(der, pkg_resources.resource_string( - __name__, 'testdata/csr-san.der')) - - class ValidCSRTest(unittest.TestCase): """Tests for letsencrypt.client.crypto_util.valid_csr."""