From db712534e545fa05211568b738474b74b0c92217 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 16 Dec 2015 16:53:12 -0800 Subject: [PATCH 01/60] Make dump() public --- acme/acme/jose/util.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/acme/acme/jose/util.py b/acme/acme/jose/util.py index ab3606efc8a..1d98aad4e34 100644 --- a/acme/acme/jose/util.py +++ b/acme/acme/jose/util.py @@ -43,8 +43,17 @@ def __init__(self, wrapped): def __getattr__(self, name): return getattr(self._wrapped, name) - def _dump(self, filetype=OpenSSL.crypto.FILETYPE_ASN1): - # pylint: disable=missing-docstring,protected-access + def dump(self, filetype=OpenSSL.crypto.FILETYPE_ASN1): + """Dumps the object into a buffer with the specified encoding. + + :param int filetype: The desired encoding. Should be one of + OpenSSL.crypto.FILETYPE_ASN1, OpenSSL.crypto.FILETYPE_PEM, + or OpenSSL.crypto.FILETYPE_TEXT. + + :returns: Encoded X509 object. + :rtype: str + + """ if isinstance(self._wrapped, OpenSSL.crypto.X509): func = OpenSSL.crypto.dump_certificate else: # assert in __init__ makes sure this is X509Req @@ -54,10 +63,10 @@ def _dump(self, filetype=OpenSSL.crypto.FILETYPE_ASN1): def __eq__(self, other): if not isinstance(other, self.__class__): return NotImplemented - return self._dump() == other._dump() # pylint: disable=protected-access + return self.dump() == other.dump() def __hash__(self): - return hash((self.__class__, self._dump())) + return hash((self.__class__, self.dump())) def __ne__(self, other): return not self == other From d21ca90560ef590526971eb3c753eb96a34cc041 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 16 Dec 2015 17:33:08 -0800 Subject: [PATCH 02/60] Use dump on ComparableX509 --- acme/acme/challenges_test.py | 3 +-- acme/acme/jose/json_util.py | 6 ++---- acme/acme/jose/jws.py | 3 +-- acme/acme/jose/jws_test.py | 8 ++------ letsencrypt/cli.py | 6 +++--- letsencrypt/client.py | 6 ++---- letsencrypt/renewer.py | 3 +-- letsencrypt/tests/cli_test.py | 4 ++-- 8 files changed, 14 insertions(+), 25 deletions(-) diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index a4e78ebe9e6..c0151117115 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -420,8 +420,7 @@ def setUp(self): self.jmsg_to = { 'jwk': jwk, 'certFingerprints': cert_fingerprints, - 'certs': (jose.encode_b64jose(OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_ASN1, CERT)),), + 'certs': (jose.encode_b64jose(CERT.dump()),), 'subjectKeyIdentifiers': subject_key_identifiers, 'serialNumbers': serial_numbers, 'issuers': issuers, diff --git a/acme/acme/jose/json_util.py b/acme/acme/jose/json_util.py index 7b95e3fce4d..66776172b91 100644 --- a/acme/acme/jose/json_util.py +++ b/acme/acme/jose/json_util.py @@ -372,8 +372,7 @@ def encode_cert(cert): :rtype: unicode """ - return encode_b64jose(OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_ASN1, cert)) + return encode_b64jose(cert.dump()) def decode_cert(b64der): @@ -397,8 +396,7 @@ def encode_csr(csr): :rtype: unicode """ - return encode_b64jose(OpenSSL.crypto.dump_certificate_request( - OpenSSL.crypto.FILETYPE_ASN1, csr)) + return encode_b64jose(csr.dump()) def decode_csr(b64der): diff --git a/acme/acme/jose/jws.py b/acme/acme/jose/jws.py index 1a073e17dd3..939932d369a 100644 --- a/acme/acme/jose/jws.py +++ b/acme/acme/jose/jws.py @@ -123,8 +123,7 @@ def crit(unused_value): @x5c.encoder def x5c(value): # pylint: disable=missing-docstring,no-self-argument - return [base64.b64encode(OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_ASN1, cert)) for cert in value] + return [base64.b64encode(cert.dump()) for cert in value] @x5c.decoder def x5c(value): # pylint: disable=missing-docstring,no-self-argument diff --git a/acme/acme/jose/jws_test.py b/acme/acme/jose/jws_test.py index 69341f2287e..06524377466 100644 --- a/acme/acme/jose/jws_test.py +++ b/acme/acme/jose/jws_test.py @@ -3,7 +3,6 @@ import unittest import mock -import OpenSSL from acme import test_util @@ -68,13 +67,10 @@ def test_x5c_decoding(self): from acme.jose.jws import Header header = Header(x5c=(CERT, CERT)) jobj = header.to_partial_json() - cert_b64 = base64.b64encode(OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_ASN1, CERT)) + cert_b64 = base64.b64encode(CERT.dump()) self.assertEqual(jobj, {'x5c': [cert_b64, cert_b64]}) self.assertEqual(header, Header.from_json(jobj)) - jobj['x5c'][0] = base64.b64encode( - b'xxx' + OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_ASN1, CERT)) + jobj['x5c'][0] = base64.b64encode(b'xxx' + CERT.dump()) self.assertRaises(errors.DeserializationError, Header.from_json, jobj) def test_find_key(self): diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 29519d43061..1a141f55647 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -391,9 +391,9 @@ def _auth_from_domains(le_client, config, domains): new_certr, new_chain, new_key, _ = le_client.obtain_certificate(domains) # TODO: Check whether it worked! <- or make sure errors are thrown (jdk) lineage.save_successor( - lineage.latest_common_version(), OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, new_certr.body), - new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain)) + lineage.latest_common_version(), + new_certr.body.dump(OpenSSL.crypto.FILETYPE_PEM), new_key.pem, + crypto_util.dump_pyopenssl_chain(new_chain)) lineage.update_all_links_to(lineage.latest_common_version()) # TODO: Check return value of save_successor diff --git a/letsencrypt/client.py b/letsencrypt/client.py index 080ee7991eb..59ac11a724a 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -299,8 +299,7 @@ def obtain_and_enroll_certificate(self, domains): "by your operating system package manager") lineage = storage.RenewableCert.new_lineage( - domains[0], OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, certr.body), + domains[0], certr.body.dump(OpenSSL.crypto.FILETYPE_PEM), key.pem, crypto_util.dump_pyopenssl_chain(chain), params, config, cli_config) return lineage @@ -329,8 +328,7 @@ def save_certificate(self, certr, chain_cert, os.path.dirname(path), 0o755, os.geteuid(), self.config.strict_permissions) - cert_pem = OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, certr.body) + cert_pem = certr.body.dump(OpenSSL.crypto.FILETYPE_PEM) cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644) try: cert_file.write(cert_pem) diff --git a/letsencrypt/renewer.py b/letsencrypt/renewer.py index 3996cfe6794..6e2366d825d 100644 --- a/letsencrypt/renewer.py +++ b/letsencrypt/renewer.py @@ -102,8 +102,7 @@ def renew(cert, old_version): # new_key if the old key is to be used (since save_successor # already understands this distinction!) return cert.save_successor( - old_version, OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, new_certr.body), + old_version, new_certr.body.dump(OpenSSL.crypto.FILETYPE_PEM), new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain)) # TODO: Notify results else: diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py index ccf16f5b559..39c09deded7 100644 --- a/letsencrypt/tests/cli_test.py +++ b/letsencrypt/tests/cli_test.py @@ -417,11 +417,11 @@ def test_certonly_renewal(self, mock_init, mock_renewal, mock_get_utility, _sugg chain_path = '/etc/letsencrypt/live/foo.bar/fullchain.pem' mock_lineage = mock.MagicMock(cert=cert_path, fullchain=chain_path) - mock_cert = mock.MagicMock(body='body') + mock_certr = mock.MagicMock() mock_key = mock.MagicMock(pem='pem_key') mock_renewal.return_value = ("renew", mock_lineage) mock_client = mock.MagicMock() - mock_client.obtain_certificate.return_value = (mock_cert, 'chain', + mock_client.obtain_certificate.return_value = (mock_certr, 'chain', mock_key, 'csr') mock_init.return_value = mock_client with mock.patch('letsencrypt.cli.OpenSSL'): From 20b3188c6527588f1f6ab552104bf3af39978e2d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 16 Dec 2015 19:46:56 -0800 Subject: [PATCH 03/60] No kwargs plz --- letsencrypt/plugins/standalone.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/plugins/standalone.py b/letsencrypt/plugins/standalone.py index 4319e51f985..cde7041d867 100644 --- a/letsencrypt/plugins/standalone.py +++ b/letsencrypt/plugins/standalone.py @@ -150,7 +150,7 @@ def __init__(self, *args, **kwargs): # one self-signed key for all tls-sni-01 certificates self.key = OpenSSL.crypto.PKey() - self.key.generate_key(OpenSSL.crypto.TYPE_RSA, bits=2048) + self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) self.served = collections.defaultdict(set) From 7efdac6c66720715e6aeb595aa5d6149967c89f2 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 17 Dec 2015 17:28:36 -0800 Subject: [PATCH 04/60] Fixed SANs problem --- acme/acme/challenges_test.py | 4 +++- acme/acme/crypto_util.py | 30 ++++++++++-------------------- acme/acme/crypto_util_test.py | 16 ++++++++++------ acme/acme/jose/util_test.py | 3 +++ 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index c0151117115..5c2b842ec42 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -264,7 +264,9 @@ def test_gen_verify_cert_gen_key(self): def test_verify_bad_cert(self): self.assertFalse(self.response.verify_cert( - test_util.load_cert('cert.pem'))) + OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, + test_util.load_vector('cert.pem')))) def test_simple_verify_bad_key_authorization(self): key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index 72a93141ac4..cde8b2a9af8 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -4,11 +4,10 @@ import socket import sys -from six.moves import range # pylint: disable=import-error,redefined-builtin - import OpenSSL from acme import errors +from acme import jose logger = logging.getLogger(__name__) @@ -161,31 +160,22 @@ def _pyopenssl_cert_or_req_san(cert_or_req): :rtype: `list` of `unicode` """ - # constants based on implementation of - # OpenSSL.crypto.X509Error._subjectAltNameString + # constants based on PyOpenSSL certificate/CSR text dump + label = "DNS" parts_separator = ", " part_separator = ":" - extension_short_name = b"subjectAltName" - - if hasattr(cert_or_req, 'get_extensions'): # X509Req - extensions = cert_or_req.get_extensions() - else: # X509 - extensions = [cert_or_req.get_extension(i) - for i in range(cert_or_req.get_extension_count())] - - # pylint: disable=protected-access,no-member - label = OpenSSL.crypto.X509Extension._prefixes[OpenSSL.crypto._lib.GEN_DNS] - assert parts_separator not in label prefix = label + part_separator + title = "X509v3 Subject Alternative Name:" - san_extensions = [ - ext._subjectAltNameString().split(parts_separator) - for ext in extensions if ext.get_short_name() == extension_short_name] + text = jose.ComparableX509(cert_or_req).dump(OpenSSL.crypto.FILETYPE_TEXT) + lines = iter(text.decode("utf-8").splitlines()) + sans = [next(lines).split(parts_separator) + for line in lines if title in line] # WARNING: this function assumes that no SAN can include # parts_separator, hence the split! - return [part.split(part_separator)[1] for parts in san_extensions - for part in parts if part.startswith(prefix)] + return [part.split(part_separator)[1] for parts in sans + for part in parts if part.lstrip().startswith(prefix)] def gen_ss_cert(key, domains, not_before=None, diff --git a/acme/acme/crypto_util_test.py b/acme/acme/crypto_util_test.py index bfd16388ce3..9e306277458 100644 --- a/acme/acme/crypto_util_test.py +++ b/acme/acme/crypto_util_test.py @@ -6,6 +6,8 @@ from six.moves import socketserver # pylint: disable=import-error +import OpenSSL + from acme import errors from acme import jose from acme import test_util @@ -64,16 +66,18 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase): """Test for acme.crypto_util._pyopenssl_cert_or_req_san.""" @classmethod - def _call(cls, loader, name): + def _call(cls, cert_or_req): # pylint: disable=protected-access from acme.crypto_util import _pyopenssl_cert_or_req_san - return _pyopenssl_cert_or_req_san(loader(name)) + return _pyopenssl_cert_or_req_san(cert_or_req) - def _call_cert(self, name): - return self._call(test_util.load_cert, name) + def _call_cert(self, name, filetype=OpenSSL.crypto.FILETYPE_PEM): + return self._call(OpenSSL.crypto.load_certificate( + filetype, test_util.load_vector(name))) - def _call_csr(self, name): - return self._call(test_util.load_csr, name) + def _call_csr(self, name, filetype=OpenSSL.crypto.FILETYPE_PEM): + return self._call(OpenSSL.crypto.load_certificate_request( + filetype, test_util.load_vector(name))) def test_cert_no_sans(self): self.assertEqual(self._call_cert('cert.pem'), []) diff --git a/acme/acme/jose/util_test.py b/acme/acme/jose/util_test.py index 4cdd9127fec..5920ce11fb9 100644 --- a/acme/acme/jose/util_test.py +++ b/acme/acme/jose/util_test.py @@ -20,6 +20,9 @@ def setUp(self): self.cert2 = test_util.load_cert('cert.pem') self.cert_other = test_util.load_cert('cert-san.pem') + def test_getattr_proxy(self): + self.assertTrue(self.cert1.has_expired()) + def test_eq(self): self.assertEqual(self.req1, self.req2) self.assertEqual(self.cert1, self.cert2) From a28f8fe4427c12c2523b16903325d0362b53123e Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 17 Dec 2015 17:47:15 -0800 Subject: [PATCH 05/60] Drop version dependency --- acme/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index e35b40d6e2f..5fc1bd8d057 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -12,8 +12,8 @@ 'cryptography>=0.8', 'ndg-httpsclient', # urllib3 InsecurePlatformWarning (#304) 'pyasn1', # urllib3 InsecurePlatformWarning (#304) - # Connection.set_tlsext_host_name (>=0.13), X509Req.get_extensions (>=0.15) - 'PyOpenSSL>=0.15', + # Connection.set_tlsext_host_name (>=0.13) + 'PyOpenSSL>=0.13', 'pyrfc3339', 'pytz', 'requests', From 66a861ead1cc9188bc93133558567eaf41aa72b9 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 23 Dec 2015 13:48:52 -0500 Subject: [PATCH 06/60] Add test_comparable_{cert,csr} --- acme/acme/challenges_test.py | 2 +- acme/acme/crypto_util_test.py | 2 +- acme/acme/jose/json_util_test.py | 4 ++-- acme/acme/jose/jws_test.py | 2 +- acme/acme/jose/util_test.py | 16 ++++++++-------- acme/acme/messages_test.py | 4 ++-- acme/acme/standalone_test.py | 4 ++-- acme/acme/test_util.py | 16 ++++++++++++---- letsencrypt/tests/client_test.py | 2 +- letsencrypt/tests/renewer_test.py | 7 +++++-- letsencrypt/tests/test_util.py | 16 ++++++++++++---- 11 files changed, 47 insertions(+), 28 deletions(-) diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index 5c2b842ec42..0b02102b29b 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -13,7 +13,7 @@ from acme import test_util -CERT = test_util.load_cert('cert.pem') +CERT = test_util.load_comparable_cert('cert.pem') KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem')) diff --git a/acme/acme/crypto_util_test.py b/acme/acme/crypto_util_test.py index 9e306277458..72530ec9de9 100644 --- a/acme/acme/crypto_util_test.py +++ b/acme/acme/crypto_util_test.py @@ -17,7 +17,7 @@ class SSLSocketAndProbeSNITest(unittest.TestCase): """Tests for acme.crypto_util.SSLSocket/probe_sni.""" def setUp(self): - self.cert = test_util.load_cert('cert.pem') + self.cert = test_util.load_comparable_cert('cert.pem') key = test_util.load_pyopenssl_private_key('rsa512_key.pem') # pylint: disable=protected-access certs = {b'foo': (key, self.cert._wrapped)} diff --git a/acme/acme/jose/json_util_test.py b/acme/acme/jose/json_util_test.py index a055f3bf774..25e36211eec 100644 --- a/acme/acme/jose/json_util_test.py +++ b/acme/acme/jose/json_util_test.py @@ -12,8 +12,8 @@ from acme.jose import util -CERT = test_util.load_cert('cert.pem') -CSR = test_util.load_csr('csr.pem') +CERT = test_util.load_comparable_cert('cert.pem') +CSR = test_util.load_comparable_csr('csr.pem') class FieldTest(unittest.TestCase): diff --git a/acme/acme/jose/jws_test.py b/acme/acme/jose/jws_test.py index 06524377466..3fd0fbb89bc 100644 --- a/acme/acme/jose/jws_test.py +++ b/acme/acme/jose/jws_test.py @@ -12,7 +12,7 @@ from acme.jose import jwk -CERT = test_util.load_cert('cert.pem') +CERT = test_util.load_comparable_cert('cert.pem') KEY = jwk.JWKRSA.load(test_util.load_vector('rsa512_key.pem')) diff --git a/acme/acme/jose/util_test.py b/acme/acme/jose/util_test.py index 5920ce11fb9..392f817772b 100644 --- a/acme/acme/jose/util_test.py +++ b/acme/acme/jose/util_test.py @@ -11,14 +11,14 @@ class ComparableX509Test(unittest.TestCase): """Tests for acme.jose.util.ComparableX509.""" def setUp(self): - # test_util.load_{csr,cert} return ComparableX509 - self.req1 = test_util.load_csr('csr.pem') - self.req2 = test_util.load_csr('csr.pem') - self.req_other = test_util.load_csr('csr-san.pem') - - self.cert1 = test_util.load_cert('cert.pem') - self.cert2 = test_util.load_cert('cert.pem') - self.cert_other = test_util.load_cert('cert-san.pem') + # test_util.load_comparable_{csr,cert} return ComparableX509 + self.req1 = test_util.load_comparable_csr('csr.pem') + self.req2 = test_util.load_comparable_csr('csr.pem') + self.req_other = test_util.load_comparable_csr('csr-san.pem') + + self.cert1 = test_util.load_comparable_cert('cert.pem') + self.cert2 = test_util.load_comparable_cert('cert.pem') + self.cert_other = test_util.load_comparable_cert('cert-san.pem') def test_getattr_proxy(self): self.assertTrue(self.cert1.has_expired()) diff --git a/acme/acme/messages_test.py b/acme/acme/messages_test.py index 5a7a71299d8..8e74826bff4 100644 --- a/acme/acme/messages_test.py +++ b/acme/acme/messages_test.py @@ -8,8 +8,8 @@ from acme import test_util -CERT = test_util.load_cert('cert.der') -CSR = test_util.load_csr('csr.der') +CERT = test_util.load_comparable_cert('cert.der') +CSR = test_util.load_comparable_csr('csr.der') KEY = test_util.load_rsa_private_key('rsa512_key.pem') diff --git a/acme/acme/standalone_test.py b/acme/acme/standalone_test.py index 02b1f69d358..2778635f5e4 100644 --- a/acme/acme/standalone_test.py +++ b/acme/acme/standalone_test.py @@ -35,7 +35,7 @@ def setUp(self): self.certs = { b'localhost': (test_util.load_pyopenssl_private_key('rsa512_key.pem'), # pylint: disable=protected-access - test_util.load_cert('cert.pem')._wrapped), + test_util.load_cert('cert.pem')), } from acme.standalone import TLSSNI01Server self.server = TLSSNI01Server(("", 0), certs=self.certs) @@ -146,7 +146,7 @@ def test_it(self): time.sleep(1) # wait until thread starts else: self.assertEqual(jose.ComparableX509(cert), - test_util.load_cert('cert.pem')) + test_util.load_comparable_cert('cert.pem')) break diff --git a/acme/acme/test_util.py b/acme/acme/test_util.py index 2b4c6e00cc7..24eceff5ae5 100644 --- a/acme/acme/test_util.py +++ b/acme/acme/test_util.py @@ -40,16 +40,24 @@ def load_cert(*names): """Load certificate.""" loader = _guess_loader( names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1) - return jose.ComparableX509(OpenSSL.crypto.load_certificate( - loader, load_vector(*names))) + return OpenSSL.crypto.load_certificate(loader, load_vector(*names)) + + +def load_comparable_cert(*names): + """Load ComparableX509 cert.""" + return jose.ComparableX509(load_cert(*names)) def load_csr(*names): """Load certificate request.""" loader = _guess_loader( names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1) - return jose.ComparableX509(OpenSSL.crypto.load_certificate_request( - loader, load_vector(*names))) + return OpenSSL.crypto.load_certificate_request(loader, load_vector(*names)) + + +def load_comparable_csr(*names): + """Load ComparableX509 certificate request.""" + return jose.ComparableX509(load_csr(*names)) def load_rsa_private_key(*names): diff --git a/letsencrypt/tests/client_test.py b/letsencrypt/tests/client_test.py index 6b76f70c956..cf1b3b39f7f 100644 --- a/letsencrypt/tests/client_test.py +++ b/letsencrypt/tests/client_test.py @@ -141,7 +141,7 @@ def test_save_certificate(self): tmp_path = tempfile.mkdtemp() os.chmod(tmp_path, 0o755) # TODO: really?? - certr = mock.MagicMock(body=test_util.load_cert(certs[0])) + certr = mock.MagicMock(body=test_util.load_comparable_cert(certs[0])) chain_cert = [test_util.load_cert(certs[1]), test_util.load_cert(certs[2])] candidate_cert_path = os.path.join(tmp_path, "certs", "cert.pem") diff --git a/letsencrypt/tests/renewer_test.py b/letsencrypt/tests/renewer_test.py index daec9678f68..269a9193d2c 100644 --- a/letsencrypt/tests/renewer_test.py +++ b/letsencrypt/tests/renewer_test.py @@ -9,6 +9,8 @@ import configobj import mock +from acme import jose + from letsencrypt import configuration from letsencrypt import errors from letsencrypt.storage import ALL_FOUR @@ -702,9 +704,10 @@ def test_renew(self, mock_c, mock_acc_storage, mock_pd): self.test_rc.configfile["renewalparams"]["authenticator"] = "apache" mock_client = mock.MagicMock() # pylint: disable=star-args + comparable_cert = jose.ComparableX509(CERT) mock_client.obtain_certificate.return_value = ( - mock.MagicMock(body=CERT), [CERT], mock.Mock(pem="key"), - mock.sentinel.csr) + mock.MagicMock(body=comparable_cert), [comparable_cert], + mock.Mock(pem="key"), mock.sentinel.csr) mock_c.return_value = mock_client self.assertEqual(2, renewer.renew(self.test_rc, 1)) # TODO: We could also make several assertions about calls that should diff --git a/letsencrypt/tests/test_util.py b/letsencrypt/tests/test_util.py index 2b4c6e00cc7..24eceff5ae5 100644 --- a/letsencrypt/tests/test_util.py +++ b/letsencrypt/tests/test_util.py @@ -40,16 +40,24 @@ def load_cert(*names): """Load certificate.""" loader = _guess_loader( names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1) - return jose.ComparableX509(OpenSSL.crypto.load_certificate( - loader, load_vector(*names))) + return OpenSSL.crypto.load_certificate(loader, load_vector(*names)) + + +def load_comparable_cert(*names): + """Load ComparableX509 cert.""" + return jose.ComparableX509(load_cert(*names)) def load_csr(*names): """Load certificate request.""" loader = _guess_loader( names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1) - return jose.ComparableX509(OpenSSL.crypto.load_certificate_request( - loader, load_vector(*names))) + return OpenSSL.crypto.load_certificate_request(loader, load_vector(*names)) + + +def load_comparable_csr(*names): + """Load ComparableX509 certificate request.""" + return jose.ComparableX509(load_csr(*names)) def load_rsa_private_key(*names): From 980637a936d24199061f4e4b1182971812062ddf Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 23 Dec 2015 17:12:33 -0500 Subject: [PATCH 07/60] Audit calls to test_util.load_cert --- acme/acme/challenges.py | 9 ++++++++- acme/acme/crypto_util_test.py | 16 ++++++---------- letsencrypt/tests/client_test.py | 4 ++-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 1e456d32579..a121b4639cd 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -391,7 +391,14 @@ def probe_cert(self, domain, **kwargs): return crypto_util.probe_sni(**kwargs) def verify_cert(self, cert): - """Verify tls-sni-01 challenge certificate.""" + """Verify tls-sni-01 challenge certificate. + + :param OpensSSL.crypto.X509 cert: Challenge certificate. + + :returns: Whether the certificate was successfully verified. + :rtype: bool + + """ # pylint: disable=protected-access sans = crypto_util._pyopenssl_cert_or_req_san(cert) logging.debug('Certificate %s. SANs: %s', cert.digest('sha1'), sans) diff --git a/acme/acme/crypto_util_test.py b/acme/acme/crypto_util_test.py index 72530ec9de9..25b0d2f67c8 100644 --- a/acme/acme/crypto_util_test.py +++ b/acme/acme/crypto_util_test.py @@ -6,8 +6,6 @@ from six.moves import socketserver # pylint: disable=import-error -import OpenSSL - from acme import errors from acme import jose from acme import test_util @@ -66,18 +64,16 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase): """Test for acme.crypto_util._pyopenssl_cert_or_req_san.""" @classmethod - def _call(cls, cert_or_req): + def _call(cls, loader, name): # pylint: disable=protected-access from acme.crypto_util import _pyopenssl_cert_or_req_san - return _pyopenssl_cert_or_req_san(cert_or_req) + return _pyopenssl_cert_or_req_san(loader(name)) - def _call_cert(self, name, filetype=OpenSSL.crypto.FILETYPE_PEM): - return self._call(OpenSSL.crypto.load_certificate( - filetype, test_util.load_vector(name))) + def _call_cert(self, name): + return self._call(test_util.load_cert, name) - def _call_csr(self, name, filetype=OpenSSL.crypto.FILETYPE_PEM): - return self._call(OpenSSL.crypto.load_certificate_request( - filetype, test_util.load_vector(name))) + def _call_csr(self, name): + return self._call(test_util.load_csr, name) def test_cert_no_sans(self): self.assertEqual(self._call_cert('cert.pem'), []) diff --git a/letsencrypt/tests/client_test.py b/letsencrypt/tests/client_test.py index cf1b3b39f7f..2f117f80c42 100644 --- a/letsencrypt/tests/client_test.py +++ b/letsencrypt/tests/client_test.py @@ -142,8 +142,8 @@ def test_save_certificate(self): os.chmod(tmp_path, 0o755) # TODO: really?? certr = mock.MagicMock(body=test_util.load_comparable_cert(certs[0])) - chain_cert = [test_util.load_cert(certs[1]), - test_util.load_cert(certs[2])] + chain_cert = [test_util.load_comparable_cert(certs[1]), + test_util.load_comparable_cert(certs[2])] candidate_cert_path = os.path.join(tmp_path, "certs", "cert.pem") candidate_chain_path = os.path.join(tmp_path, "chains", "chain.pem") candidate_fullchain_path = os.path.join(tmp_path, "chains", "fullchain.pem") From 75b551762b461de933d83de231735380751c5044 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 23 Dec 2015 19:09:05 -0500 Subject: [PATCH 08/60] Expose wrapped, not dump --- acme/acme/challenges_test.py | 3 ++- acme/acme/crypto_util.py | 8 ++++++-- acme/acme/crypto_util_test.py | 2 +- acme/acme/jose/json_util.py | 6 ++++-- acme/acme/jose/jws.py | 3 ++- acme/acme/jose/jws_test.py | 7 +++++-- acme/acme/jose/util.py | 28 ++++++++++++++-------------- letsencrypt/cli.py | 6 +++--- letsencrypt/client.py | 6 ++++-- letsencrypt/crypto_util.py | 2 +- letsencrypt/renewer.py | 3 ++- 11 files changed, 44 insertions(+), 30 deletions(-) diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index 0b02102b29b..6b277ac274f 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -422,7 +422,8 @@ def setUp(self): self.jmsg_to = { 'jwk': jwk, 'certFingerprints': cert_fingerprints, - 'certs': (jose.encode_b64jose(CERT.dump()),), + 'certs': (jose.encode_b64jose(OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_ASN1, CERT.wrapped)),), 'subjectKeyIdentifiers': subject_key_identifiers, 'serialNumbers': serial_numbers, 'issuers': issuers, diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index cde8b2a9af8..15890175fb4 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -7,7 +7,6 @@ import OpenSSL from acme import errors -from acme import jose logger = logging.getLogger(__name__) @@ -167,7 +166,12 @@ def _pyopenssl_cert_or_req_san(cert_or_req): prefix = label + part_separator title = "X509v3 Subject Alternative Name:" - text = jose.ComparableX509(cert_or_req).dump(OpenSSL.crypto.FILETYPE_TEXT) + if isinstance(cert_or_req, OpenSSL.crypto.X509): + func = OpenSSL.crypto.dump_certificate + else: + func = OpenSSL.crypto.dump_certificate_request + text = func(OpenSSL.crypto.FILETYPE_TEXT, cert_or_req) + lines = iter(text.decode("utf-8").splitlines()) sans = [next(lines).split(parts_separator) for line in lines if title in line] diff --git a/acme/acme/crypto_util_test.py b/acme/acme/crypto_util_test.py index 25b0d2f67c8..b3c39c38809 100644 --- a/acme/acme/crypto_util_test.py +++ b/acme/acme/crypto_util_test.py @@ -18,7 +18,7 @@ def setUp(self): self.cert = test_util.load_comparable_cert('cert.pem') key = test_util.load_pyopenssl_private_key('rsa512_key.pem') # pylint: disable=protected-access - certs = {b'foo': (key, self.cert._wrapped)} + certs = {b'foo': (key, self.cert.wrapped)} from acme.crypto_util import SSLSocket diff --git a/acme/acme/jose/json_util.py b/acme/acme/jose/json_util.py index 66776172b91..42fb389e6a5 100644 --- a/acme/acme/jose/json_util.py +++ b/acme/acme/jose/json_util.py @@ -372,7 +372,8 @@ def encode_cert(cert): :rtype: unicode """ - return encode_b64jose(cert.dump()) + return encode_b64jose(OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_ASN1, cert.wrapped)) def decode_cert(b64der): @@ -396,7 +397,8 @@ def encode_csr(csr): :rtype: unicode """ - return encode_b64jose(csr.dump()) + return encode_b64jose(OpenSSL.crypto.dump_certificate_request( + OpenSSL.crypto.FILETYPE_ASN1, csr.wrapped)) def decode_csr(b64der): diff --git a/acme/acme/jose/jws.py b/acme/acme/jose/jws.py index 939932d369a..9c14cf72992 100644 --- a/acme/acme/jose/jws.py +++ b/acme/acme/jose/jws.py @@ -123,7 +123,8 @@ def crit(unused_value): @x5c.encoder def x5c(value): # pylint: disable=missing-docstring,no-self-argument - return [base64.b64encode(cert.dump()) for cert in value] + return [base64.b64encode(OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_ASN1, cert.wrapped)) for cert in value] @x5c.decoder def x5c(value): # pylint: disable=missing-docstring,no-self-argument diff --git a/acme/acme/jose/jws_test.py b/acme/acme/jose/jws_test.py index 3fd0fbb89bc..ec91f6a1bf3 100644 --- a/acme/acme/jose/jws_test.py +++ b/acme/acme/jose/jws_test.py @@ -3,6 +3,7 @@ import unittest import mock +import OpenSSL from acme import test_util @@ -67,10 +68,12 @@ def test_x5c_decoding(self): from acme.jose.jws import Header header = Header(x5c=(CERT, CERT)) jobj = header.to_partial_json() - cert_b64 = base64.b64encode(CERT.dump()) + cert_asn1 = OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_ASN1, CERT.wrapped) + cert_b64 = base64.b64encode(cert_asn1) self.assertEqual(jobj, {'x5c': [cert_b64, cert_b64]}) self.assertEqual(header, Header.from_json(jobj)) - jobj['x5c'][0] = base64.b64encode(b'xxx' + CERT.dump()) + jobj['x5c'][0] = base64.b64encode(b'xxx' + cert_asn1) self.assertRaises(errors.DeserializationError, Header.from_json, jobj) def test_find_key(self): diff --git a/acme/acme/jose/util.py b/acme/acme/jose/util.py index 1d98aad4e34..a7a129800fb 100644 --- a/acme/acme/jose/util.py +++ b/acme/acme/jose/util.py @@ -29,50 +29,50 @@ def __init__(self, target): class ComparableX509(object): # pylint: disable=too-few-public-methods """Wrapper for OpenSSL.crypto.X509** objects that supports __eq__. - Wraps around: - - - :class:`OpenSSL.crypto.X509` - - :class:`OpenSSL.crypto.X509Req` + :ivar wrapped: Wrapped certificate or certificate request. + :type wrapped: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`. """ def __init__(self, wrapped): assert isinstance(wrapped, OpenSSL.crypto.X509) or isinstance( wrapped, OpenSSL.crypto.X509Req) - self._wrapped = wrapped + self.wrapped = wrapped def __getattr__(self, name): - return getattr(self._wrapped, name) + return getattr(self.wrapped, name) - def dump(self, filetype=OpenSSL.crypto.FILETYPE_ASN1): + def _dump(self, filetype=OpenSSL.crypto.FILETYPE_ASN1): """Dumps the object into a buffer with the specified encoding. :param int filetype: The desired encoding. Should be one of - OpenSSL.crypto.FILETYPE_ASN1, OpenSSL.crypto.FILETYPE_PEM, - or OpenSSL.crypto.FILETYPE_TEXT. + `OpenSSL.crypto.FILETYPE_ASN1`, + `OpenSSL.crypto.FILETYPE_PEM`, or + `OpenSSL.crypto.FILETYPE_TEXT`. :returns: Encoded X509 object. :rtype: str """ - if isinstance(self._wrapped, OpenSSL.crypto.X509): + if isinstance(self.wrapped, OpenSSL.crypto.X509): func = OpenSSL.crypto.dump_certificate else: # assert in __init__ makes sure this is X509Req func = OpenSSL.crypto.dump_certificate_request - return func(filetype, self._wrapped) + return func(filetype, self.wrapped) def __eq__(self, other): if not isinstance(other, self.__class__): return NotImplemented - return self.dump() == other.dump() + # pylint: disable=protected-access + return self._dump() == other._dump() def __hash__(self): - return hash((self.__class__, self.dump())) + return hash((self.__class__, self._dump())) def __ne__(self, other): return not self == other def __repr__(self): - return '<{0}({1!r})>'.format(self.__class__.__name__, self._wrapped) + return '<{0}({1!r})>'.format(self.__class__.__name__, self.wrapped) class ComparableKey(object): # pylint: disable=too-few-public-methods diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 1a141f55647..dfebde13cc8 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -391,9 +391,9 @@ def _auth_from_domains(le_client, config, domains): new_certr, new_chain, new_key, _ = le_client.obtain_certificate(domains) # TODO: Check whether it worked! <- or make sure errors are thrown (jdk) lineage.save_successor( - lineage.latest_common_version(), - new_certr.body.dump(OpenSSL.crypto.FILETYPE_PEM), new_key.pem, - crypto_util.dump_pyopenssl_chain(new_chain)) + lineage.latest_common_version(), OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_PEM, new_certr.body.wrapped), + new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain)) lineage.update_all_links_to(lineage.latest_common_version()) # TODO: Check return value of save_successor diff --git a/letsencrypt/client.py b/letsencrypt/client.py index 59ac11a724a..c2dfca1bf37 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -299,7 +299,8 @@ def obtain_and_enroll_certificate(self, domains): "by your operating system package manager") lineage = storage.RenewableCert.new_lineage( - domains[0], certr.body.dump(OpenSSL.crypto.FILETYPE_PEM), + domains[0], OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped), key.pem, crypto_util.dump_pyopenssl_chain(chain), params, config, cli_config) return lineage @@ -328,7 +329,8 @@ def save_certificate(self, certr, chain_cert, os.path.dirname(path), 0o755, os.geteuid(), self.config.strict_permissions) - cert_pem = certr.body.dump(OpenSSL.crypto.FILETYPE_PEM) + cert_pem = OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped) cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644) try: cert_file.write(cert_pem) diff --git a/letsencrypt/crypto_util.py b/letsencrypt/crypto_util.py index f897ec85233..730c32398de 100644 --- a/letsencrypt/crypto_util.py +++ b/letsencrypt/crypto_util.py @@ -271,7 +271,7 @@ def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM): def _dump_cert(cert): if isinstance(cert, jose.ComparableX509): # pylint: disable=protected-access - cert = cert._wrapped + cert = cert.wrapped return OpenSSL.crypto.dump_certificate(filetype, cert) # assumes that OpenSSL.crypto.dump_certificate includes ending diff --git a/letsencrypt/renewer.py b/letsencrypt/renewer.py index 6e2366d825d..ed4910ef9d5 100644 --- a/letsencrypt/renewer.py +++ b/letsencrypt/renewer.py @@ -102,7 +102,8 @@ def renew(cert, old_version): # new_key if the old key is to be used (since save_successor # already understands this distinction!) return cert.save_successor( - old_version, new_certr.body.dump(OpenSSL.crypto.FILETYPE_PEM), + old_version, OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_PEM, new_certr.body.wrapped), new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain)) # TODO: Notify results else: From 10a49532ae459ee1b1cc45b3b72072d8bdd37310 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Tue, 5 Jan 2016 11:25:21 +0200 Subject: [PATCH 09/60] Add / replace functionality to augeas transform paths to overwrite old narrower scope if needed --- .../letsencrypt_apache/parser.py | 64 ++++++++++++++++--- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/parser.py b/letsencrypt-apache/letsencrypt_apache/parser.py index 593c807cccd..04b61b2d452 100644 --- a/letsencrypt-apache/letsencrypt_apache/parser.py +++ b/letsencrypt-apache/letsencrypt_apache/parser.py @@ -35,6 +35,7 @@ def __init__(self, aug, root, vhostroot, version=(2, 4)): # https://httpd.apache.org/docs/2.4/mod/core.html#define # https://httpd.apache.org/docs/2.4/mod/core.html#ifdefine # This only handles invocation parameters and Define directives! + self.parser_paths = {} self.variables = {} if version >= (2, 4): self.update_runtime_variables() @@ -471,16 +472,61 @@ def _parse_file(self, filepath): :param str filepath: Apache config file path """ + use_new, remove_old = self._check_path_actions(filepath) # Test if augeas included file for Httpd.lens # Note: This works for augeas globs, ie. *.conf - inc_test = self.aug.match( - "/augeas/load/Httpd/incl [. ='%s']" % filepath) - if not inc_test: - # Load up files - # This doesn't seem to work on TravisCI - # self.aug.add_transform("Httpd.lns", [filepath]) - self._add_httpd_transform(filepath) - self.aug.load() + if use_new: + inc_test = self.aug.match( + "/augeas/load/Httpd/incl [. ='%s']" % filepath) + if not inc_test: + # Load up files + # This doesn't seem to work on TravisCI + # self.aug.add_transform("Httpd.lns", [filepath]) + if remove_old: + self._remove_httpd_transform(filepath) + self._add_httpd_transform(filepath) + self.aug.load() + + def _check_path_actions(self, filepath): + """Determine actions to take with a new augeas path + + This helper function will return a tuple that defines + if we should try to append the new filepath to augeas + parser paths, and / or remove the old one with more + narrow matching. + + :param str filepath: filepath to check the actions for + + """ + + try: + use_new = False + remove_old = False + new_file_match = os.path.basename(filepath) + existing_match = self.parser_paths[os.path.dirname(filepath)] + if existing_match == new_file_match: + # True here to let augeas verify that the path is parsed + use_new = True + elif new_file_match == "*": + use_new = True + remove_old = True + except KeyError: + use_new = True + return use_new, remove_old + + def _remove_httpd_transform(self, filepath): + """Remove path from Augeas transform + + :param str filepath: filepath to remove + """ + + remove_basename = self.parser_paths[os.path.dirname(filepath)] + remove_dirname = os.path.dirname(filepath) + remove_path = remove_dirname + "/" + remove_basename + remove_inc = self.aug.match( + "/augeas/load/Httpd/incl [. ='%s']" % remove_path) + self.aug.remove(remove_inc[0]) + self.parser_paths.pop(remove_dirname) def _add_httpd_transform(self, incl): """Add a transform to Augeas. @@ -502,6 +548,8 @@ def _add_httpd_transform(self, incl): # Augeas uses base 1 indexing... insert at beginning... self.aug.set("/augeas/load/Httpd/lens", "Httpd.lns") self.aug.set("/augeas/load/Httpd/incl", incl) + # Add included path to paths dictionary + self.parser_paths[os.path.dirname(incl)] = os.path.basename(incl) def standardize_excl(self): """Standardize the excl arguments for the Httpd lens in Augeas. From 63f311eea492433b5a12ac685907b5d49105c44f Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Tue, 5 Jan 2016 13:16:49 +0200 Subject: [PATCH 10/60] Refactored the checking method to be easier to read --- letsencrypt-apache/letsencrypt_apache/parser.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/parser.py b/letsencrypt-apache/letsencrypt_apache/parser.py index 04b61b2d452..23ca05ae066 100644 --- a/letsencrypt-apache/letsencrypt_apache/parser.py +++ b/letsencrypt-apache/letsencrypt_apache/parser.py @@ -500,18 +500,19 @@ def _check_path_actions(self, filepath): """ try: - use_new = False - remove_old = False new_file_match = os.path.basename(filepath) existing_match = self.parser_paths[os.path.dirname(filepath)] - if existing_match == new_file_match: - # True here to let augeas verify that the path is parsed - use_new = True - elif new_file_match == "*": + if existing_match == "*": + use_new = False + else: use_new = True + if new_file_match == "*": remove_old = True + else: + remove_old = False except KeyError: use_new = True + remove_old = False return use_new, remove_old def _remove_httpd_transform(self, filepath): From 32d5375b9bb101f7e9239627c2ccb17a728046da Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Tue, 5 Jan 2016 13:21:18 +0200 Subject: [PATCH 11/60] Change test to check out adding a file not already in the augeas paths --- letsencrypt-apache/letsencrypt_apache/tests/parser_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt-apache/letsencrypt_apache/tests/parser_test.py b/letsencrypt-apache/letsencrypt_apache/tests/parser_test.py index b871f89b7a7..9b78bf6d6ca 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/parser_test.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/parser_test.py @@ -36,7 +36,7 @@ def test_parse_file(self): """ file_path = os.path.join( - self.config_path, "sites-available", "letsencrypt.conf") + self.config_path, "not-parsed-by-default", "letsencrypt.conf") self.parser._parse_file(file_path) # pylint: disable=protected-access From ffeef67e542dfb170d051d60b3e519fa689bbe03 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Tue, 5 Jan 2016 14:36:42 +0200 Subject: [PATCH 12/60] Use lists to handle multiple different matching wildcards in same directory --- .../letsencrypt_apache/parser.py | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/parser.py b/letsencrypt-apache/letsencrypt_apache/parser.py index 23ca05ae066..82effad2bba 100644 --- a/letsencrypt-apache/letsencrypt_apache/parser.py +++ b/letsencrypt-apache/letsencrypt_apache/parser.py @@ -501,8 +501,8 @@ def _check_path_actions(self, filepath): try: new_file_match = os.path.basename(filepath) - existing_match = self.parser_paths[os.path.dirname(filepath)] - if existing_match == "*": + existing_matches = self.parser_paths[os.path.dirname(filepath)] + if "*" in existing_matches: use_new = False else: use_new = True @@ -521,12 +521,13 @@ def _remove_httpd_transform(self, filepath): :param str filepath: filepath to remove """ - remove_basename = self.parser_paths[os.path.dirname(filepath)] + remove_basenames = self.parser_paths[os.path.dirname(filepath)] remove_dirname = os.path.dirname(filepath) - remove_path = remove_dirname + "/" + remove_basename - remove_inc = self.aug.match( - "/augeas/load/Httpd/incl [. ='%s']" % remove_path) - self.aug.remove(remove_inc[0]) + for name in remove_basenames: + remove_path = remove_dirname + "/" + name + remove_inc = self.aug.match( + "/augeas/load/Httpd/incl [. ='%s']" % remove_path) + self.aug.remove(remove_inc[0]) self.parser_paths.pop(remove_dirname) def _add_httpd_transform(self, incl): @@ -550,7 +551,12 @@ def _add_httpd_transform(self, incl): self.aug.set("/augeas/load/Httpd/lens", "Httpd.lns") self.aug.set("/augeas/load/Httpd/incl", incl) # Add included path to paths dictionary - self.parser_paths[os.path.dirname(incl)] = os.path.basename(incl) + try: + self.parser_paths[os.path.dirname(incl)].append( + os.path.basename(incl)) + except KeyError: + self.parser_paths[os.path.dirname(incl)] = [ + os.path.basename(incl)] def standardize_excl(self): """Standardize the excl arguments for the Httpd lens in Augeas. From 74237d101041897231881a5ba55142e7fb255a4d Mon Sep 17 00:00:00 2001 From: Reinaldo de Souza Jr Date: Mon, 4 Jan 2016 12:00:20 -0500 Subject: [PATCH 13/60] Requires chain_path for nginx versions supporting OCSP stapling --chain-path config is not mandatory, so we require this property if nginx supports OCSP stapling. Alternatively, we could disable OCSP stapling on supported nginx versions if --chain-path is missing. --- letsencrypt-nginx/letsencrypt_nginx/configurator.py | 11 ++++++++++- .../letsencrypt_nginx/tests/configurator_test.py | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/letsencrypt-nginx/letsencrypt_nginx/configurator.py b/letsencrypt-nginx/letsencrypt_nginx/configurator.py index aaaf43c5f0b..99a864141a6 100644 --- a/letsencrypt-nginx/letsencrypt_nginx/configurator.py +++ b/letsencrypt-nginx/letsencrypt_nginx/configurator.py @@ -122,7 +122,7 @@ def prepare(self): # Entry point in main.py for installing cert def deploy_cert(self, domain, cert_path, key_path, - chain_path, fullchain_path): + chain_path=None, fullchain_path=None): # pylint: disable=unused-argument """Deploys certificate to specified virtual host. @@ -136,6 +136,9 @@ def deploy_cert(self, domain, cert_path, key_path, .. note:: This doesn't save the config files! + :raises errors.PluginError: When unable to deploy certificate due to + a lack of directives or configuration + """ vhost = self.choose_vhost(domain) cert_directives = [['ssl_certificate', fullchain_path], @@ -150,6 +153,12 @@ def deploy_cert(self, domain, cert_path, key_path, ['ssl_stapling', 'on'], ['ssl_stapling_verify', 'on']] + if len(stapling_directives) != 0 and not chain_path: + raise errors.PluginError( + "--chain-path is required to enable " + "Online Certificate Status Protocol (OCSP) stapling " + "on nginx >= 1.3.7.") + try: self.parser.add_server_directives(vhost.filep, vhost.names, cert_directives, replace=True) diff --git a/letsencrypt-nginx/letsencrypt_nginx/tests/configurator_test.py b/letsencrypt-nginx/letsencrypt_nginx/tests/configurator_test.py index 56ad5110c22..35a55befd4d 100644 --- a/letsencrypt-nginx/letsencrypt_nginx/tests/configurator_test.py +++ b/letsencrypt-nginx/letsencrypt_nginx/tests/configurator_test.py @@ -125,6 +125,15 @@ def test_deploy_cert_stapling(self): self.assertTrue(util.contains_at_depth(generated_conf, ['ssl_trusted_certificate', 'example/chain.pem'], 2)) + def test_deploy_cert_stapling_requires_chain_path(self): + self.config.version = (1, 3, 7) + self.assertRaises(errors.PluginError, self.config.deploy_cert, + "www.example.com", + "example/cert.pem", + "example/key.pem", + None, + "example/fullchain.pem") + def test_deploy_cert(self): server_conf = self.config.parser.abs_path('server.conf') nginx_conf = self.config.parser.abs_path('nginx.conf') From e8fc2eca0139df1d8cde1e23c12eaca184371100 Mon Sep 17 00:00:00 2001 From: Reinaldo de Souza Jr Date: Mon, 4 Jan 2016 15:02:15 -0500 Subject: [PATCH 14/60] nginx plugin requires fullchain_path --- letsencrypt-nginx/letsencrypt_nginx/configurator.py | 4 ++++ .../letsencrypt_nginx/tests/configurator_test.py | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/letsencrypt-nginx/letsencrypt_nginx/configurator.py b/letsencrypt-nginx/letsencrypt_nginx/configurator.py index 99a864141a6..59a977f09d6 100644 --- a/letsencrypt-nginx/letsencrypt_nginx/configurator.py +++ b/letsencrypt-nginx/letsencrypt_nginx/configurator.py @@ -140,6 +140,10 @@ def deploy_cert(self, domain, cert_path, key_path, a lack of directives or configuration """ + if not fullchain_path: + raise errors.PluginError( + "--fullchain-path is required for nginx plugin.") + vhost = self.choose_vhost(domain) cert_directives = [['ssl_certificate', fullchain_path], ['ssl_certificate_key', key_path]] diff --git a/letsencrypt-nginx/letsencrypt_nginx/tests/configurator_test.py b/letsencrypt-nginx/letsencrypt_nginx/tests/configurator_test.py index 35a55befd4d..bab107582bc 100644 --- a/letsencrypt-nginx/letsencrypt_nginx/tests/configurator_test.py +++ b/letsencrypt-nginx/letsencrypt_nginx/tests/configurator_test.py @@ -134,6 +134,15 @@ def test_deploy_cert_stapling_requires_chain_path(self): None, "example/fullchain.pem") + def test_deploy_cert_requires_fullchain_path(self): + self.config.version = (1, 3, 1) + self.assertRaises(errors.PluginError, self.config.deploy_cert, + "www.example.com", + "example/cert.pem", + "example/key.pem", + "example/chain.pem", + None) + def test_deploy_cert(self): server_conf = self.config.parser.abs_path('server.conf') nginx_conf = self.config.parser.abs_path('nginx.conf') From 5b5051b6ced180c10311cea89b37900d147929c1 Mon Sep 17 00:00:00 2001 From: Reinaldo de Souza Jr Date: Mon, 4 Jan 2016 15:16:30 -0500 Subject: [PATCH 15/60] The notes should display the fullchain_path See d01b17f1 and dd8c6d65 --- letsencrypt-nginx/letsencrypt_nginx/configurator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt-nginx/letsencrypt_nginx/configurator.py b/letsencrypt-nginx/letsencrypt_nginx/configurator.py index 59a977f09d6..963ae8a42c1 100644 --- a/letsencrypt-nginx/letsencrypt_nginx/configurator.py +++ b/letsencrypt-nginx/letsencrypt_nginx/configurator.py @@ -181,7 +181,7 @@ def deploy_cert(self, domain, cert_path, key_path, self.save_notes += ("Changed vhost at %s with addresses of %s\n" % (vhost.filep, ", ".join(str(addr) for addr in vhost.addrs))) - self.save_notes += "\tssl_certificate %s\n" % cert_path + self.save_notes += "\tssl_certificate %s\n" % fullchain_path self.save_notes += "\tssl_certificate_key %s\n" % key_path ####################### From 0b9f505ed5beb7a861c9cb027b5e4349a67eb60d Mon Sep 17 00:00:00 2001 From: Fan Jiang Date: Mon, 4 Jan 2016 15:29:26 -0500 Subject: [PATCH 16/60] update document for --chain-path when required by Nginx >= 1.3.7 --- docs/using.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/using.rst b/docs/using.rst index 5da13f02cb1..bd40dec02c9 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -237,7 +237,9 @@ The following files are available: server certificate, i.e. root and intermediate certificates only. This is what Apache < 2.4.8 needs for `SSLCertificateChainFile - `_. + `_, + and what nginx >= 1.3.7 needs for `ssl_trusted_certificate + `_. ``fullchain.pem`` All certificates, **including** server certificate. This is From 6719d0d3804987914e6b53b6227d3ec0c7d4a010 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Jan 2016 12:40:44 -0500 Subject: [PATCH 17/60] Rewrote _pyopenssl_cert_or_req_san --- acme/acme/crypto_util.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index 15890175fb4..ecec351c222 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -1,6 +1,7 @@ """Crypto utilities.""" import contextlib import logging +import re import socket import sys @@ -160,26 +161,22 @@ def _pyopenssl_cert_or_req_san(cert_or_req): """ # constants based on PyOpenSSL certificate/CSR text dump - label = "DNS" - parts_separator = ", " part_separator = ":" - prefix = label + part_separator - title = "X509v3 Subject Alternative Name:" + parts_separator = ", " + prefix = "DNS" + part_separator if isinstance(cert_or_req, OpenSSL.crypto.X509): func = OpenSSL.crypto.dump_certificate else: func = OpenSSL.crypto.dump_certificate_request - text = func(OpenSSL.crypto.FILETYPE_TEXT, cert_or_req) - - lines = iter(text.decode("utf-8").splitlines()) - sans = [next(lines).split(parts_separator) - for line in lines if title in line] + text = func(OpenSSL.crypto.FILETYPE_TEXT, cert_or_req).decode("utf-8") + match = re.search(r"X509v3 Subject Alternative Name:\s*(.*)", text) + sans_parts = [] if match is None else match.group(1).split(parts_separator) # WARNING: this function assumes that no SAN can include # parts_separator, hence the split! - return [part.split(part_separator)[1] for parts in sans - for part in parts if part.lstrip().startswith(prefix)] + return [part.split(part_separator)[1] + for part in sans_parts if part.startswith(prefix)] def gen_ss_cert(key, domains, not_before=None, From 858dadd85b2b387f8e5c0485e370efa57bb7fe18 Mon Sep 17 00:00:00 2001 From: Reinaldo de Souza Jr Date: Wed, 6 Jan 2016 13:36:52 -0500 Subject: [PATCH 18/60] Update error message This is supposed to not happen once #1391 is fixed. --- letsencrypt-nginx/letsencrypt_nginx/configurator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/letsencrypt-nginx/letsencrypt_nginx/configurator.py b/letsencrypt-nginx/letsencrypt_nginx/configurator.py index 963ae8a42c1..43ad36c7feb 100644 --- a/letsencrypt-nginx/letsencrypt_nginx/configurator.py +++ b/letsencrypt-nginx/letsencrypt_nginx/configurator.py @@ -142,7 +142,8 @@ def deploy_cert(self, domain, cert_path, key_path, """ if not fullchain_path: raise errors.PluginError( - "--fullchain-path is required for nginx plugin.") + "The nginx plugin currently requires --fullchain-path to " + "install a cert.") vhost = self.choose_vhost(domain) cert_directives = [['ssl_certificate', fullchain_path], From 1af997158dbc6a3f2233e3c0a6ea7e7b9bc3fc7a Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Jan 2016 13:39:14 -0500 Subject: [PATCH 19/60] Fix repr differences between PyOpenSSL versions --- acme/acme/jose/util_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acme/acme/jose/util_test.py b/acme/acme/jose/util_test.py index 392f817772b..0038a6cc14e 100644 --- a/acme/acme/jose/util_test.py +++ b/acme/acme/jose/util_test.py @@ -44,8 +44,8 @@ def test_hash(self): def test_repr(self): for x509 in self.req1, self.cert1: - self.assertTrue(repr(x509).startswith( - ''.format(x509.wrapped)) class ComparableRSAKeyTest(unittest.TestCase): From 32650a6d08803d6f07b4880a7a037e25bba1de96 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Jan 2016 14:10:57 -0500 Subject: [PATCH 20/60] Added 100 SANs cert and csr --- acme/acme/testdata/cert-100sans.pem | 44 +++++++++++++++++++++++++++++ acme/acme/testdata/csr-100sans.pem | 41 +++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 acme/acme/testdata/cert-100sans.pem create mode 100644 acme/acme/testdata/csr-100sans.pem diff --git a/acme/acme/testdata/cert-100sans.pem b/acme/acme/testdata/cert-100sans.pem new file mode 100644 index 00000000000..3fdc9404f4d --- /dev/null +++ b/acme/acme/testdata/cert-100sans.pem @@ -0,0 +1,44 @@ +-----BEGIN CERTIFICATE----- +MIIHxDCCB26gAwIBAgIJAOGrG1Un9lHiMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV +BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv +bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X +DTE2MDEwNjE5MDkzN1oXDTE2MDEwNzE5MDkzN1owZDELMAkGA1UECAwCQ0ExFjAU +BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp +ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B +AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580 +rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IGATCCBf0wCQYDVR0T +BAIwADALBgNVHQ8EBAMCBeAwggXhBgNVHREEggXYMIIF1IIMZXhhbXBsZTEuY29t +ggxleGFtcGxlMi5jb22CDGV4YW1wbGUzLmNvbYIMZXhhbXBsZTQuY29tggxleGFt +cGxlNS5jb22CDGV4YW1wbGU2LmNvbYIMZXhhbXBsZTcuY29tggxleGFtcGxlOC5j +b22CDGV4YW1wbGU5LmNvbYINZXhhbXBsZTEwLmNvbYINZXhhbXBsZTExLmNvbYIN +ZXhhbXBsZTEyLmNvbYINZXhhbXBsZTEzLmNvbYINZXhhbXBsZTE0LmNvbYINZXhh +bXBsZTE1LmNvbYINZXhhbXBsZTE2LmNvbYINZXhhbXBsZTE3LmNvbYINZXhhbXBs +ZTE4LmNvbYINZXhhbXBsZTE5LmNvbYINZXhhbXBsZTIwLmNvbYINZXhhbXBsZTIx +LmNvbYINZXhhbXBsZTIyLmNvbYINZXhhbXBsZTIzLmNvbYINZXhhbXBsZTI0LmNv +bYINZXhhbXBsZTI1LmNvbYINZXhhbXBsZTI2LmNvbYINZXhhbXBsZTI3LmNvbYIN +ZXhhbXBsZTI4LmNvbYINZXhhbXBsZTI5LmNvbYINZXhhbXBsZTMwLmNvbYINZXhh +bXBsZTMxLmNvbYINZXhhbXBsZTMyLmNvbYINZXhhbXBsZTMzLmNvbYINZXhhbXBs +ZTM0LmNvbYINZXhhbXBsZTM1LmNvbYINZXhhbXBsZTM2LmNvbYINZXhhbXBsZTM3 +LmNvbYINZXhhbXBsZTM4LmNvbYINZXhhbXBsZTM5LmNvbYINZXhhbXBsZTQwLmNv +bYINZXhhbXBsZTQxLmNvbYINZXhhbXBsZTQyLmNvbYINZXhhbXBsZTQzLmNvbYIN +ZXhhbXBsZTQ0LmNvbYINZXhhbXBsZTQ1LmNvbYINZXhhbXBsZTQ2LmNvbYINZXhh +bXBsZTQ3LmNvbYINZXhhbXBsZTQ4LmNvbYINZXhhbXBsZTQ5LmNvbYINZXhhbXBs +ZTUwLmNvbYINZXhhbXBsZTUxLmNvbYINZXhhbXBsZTUyLmNvbYINZXhhbXBsZTUz +LmNvbYINZXhhbXBsZTU0LmNvbYINZXhhbXBsZTU1LmNvbYINZXhhbXBsZTU2LmNv +bYINZXhhbXBsZTU3LmNvbYINZXhhbXBsZTU4LmNvbYINZXhhbXBsZTU5LmNvbYIN +ZXhhbXBsZTYwLmNvbYINZXhhbXBsZTYxLmNvbYINZXhhbXBsZTYyLmNvbYINZXhh +bXBsZTYzLmNvbYINZXhhbXBsZTY0LmNvbYINZXhhbXBsZTY1LmNvbYINZXhhbXBs +ZTY2LmNvbYINZXhhbXBsZTY3LmNvbYINZXhhbXBsZTY4LmNvbYINZXhhbXBsZTY5 +LmNvbYINZXhhbXBsZTcwLmNvbYINZXhhbXBsZTcxLmNvbYINZXhhbXBsZTcyLmNv +bYINZXhhbXBsZTczLmNvbYINZXhhbXBsZTc0LmNvbYINZXhhbXBsZTc1LmNvbYIN +ZXhhbXBsZTc2LmNvbYINZXhhbXBsZTc3LmNvbYINZXhhbXBsZTc4LmNvbYINZXhh +bXBsZTc5LmNvbYINZXhhbXBsZTgwLmNvbYINZXhhbXBsZTgxLmNvbYINZXhhbXBs +ZTgyLmNvbYINZXhhbXBsZTgzLmNvbYINZXhhbXBsZTg0LmNvbYINZXhhbXBsZTg1 +LmNvbYINZXhhbXBsZTg2LmNvbYINZXhhbXBsZTg3LmNvbYINZXhhbXBsZTg4LmNv +bYINZXhhbXBsZTg5LmNvbYINZXhhbXBsZTkwLmNvbYINZXhhbXBsZTkxLmNvbYIN +ZXhhbXBsZTkyLmNvbYINZXhhbXBsZTkzLmNvbYINZXhhbXBsZTk0LmNvbYINZXhh +bXBsZTk1LmNvbYINZXhhbXBsZTk2LmNvbYINZXhhbXBsZTk3LmNvbYINZXhhbXBs +ZTk4LmNvbYINZXhhbXBsZTk5LmNvbYIOZXhhbXBsZTEwMC5jb20wDQYJKoZIhvcN +AQELBQADQQBEunJbKUXcyNKTSfA0pKRyWNiKmkoBqYgfZS6eHNrNH/hjFzHtzyDQ +XYHHK6kgEWBvHfRXGmqhFvht+b1tQKkG +-----END CERTIFICATE----- diff --git a/acme/acme/testdata/csr-100sans.pem b/acme/acme/testdata/csr-100sans.pem new file mode 100644 index 00000000000..199814126f8 --- /dev/null +++ b/acme/acme/testdata/csr-100sans.pem @@ -0,0 +1,41 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIHNTCCBt8CAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz +Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG +A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt +H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6 +lUTor4R0T+3C5QIDAQABoIIGFDCCBhAGCSqGSIb3DQEJDjGCBgEwggX9MAkGA1Ud +EwQCMAAwCwYDVR0PBAQDAgXgMIIF4QYDVR0RBIIF2DCCBdSCDGV4YW1wbGUxLmNv +bYIMZXhhbXBsZTIuY29tggxleGFtcGxlMy5jb22CDGV4YW1wbGU0LmNvbYIMZXhh +bXBsZTUuY29tggxleGFtcGxlNi5jb22CDGV4YW1wbGU3LmNvbYIMZXhhbXBsZTgu +Y29tggxleGFtcGxlOS5jb22CDWV4YW1wbGUxMC5jb22CDWV4YW1wbGUxMS5jb22C +DWV4YW1wbGUxMi5jb22CDWV4YW1wbGUxMy5jb22CDWV4YW1wbGUxNC5jb22CDWV4 +YW1wbGUxNS5jb22CDWV4YW1wbGUxNi5jb22CDWV4YW1wbGUxNy5jb22CDWV4YW1w +bGUxOC5jb22CDWV4YW1wbGUxOS5jb22CDWV4YW1wbGUyMC5jb22CDWV4YW1wbGUy +MS5jb22CDWV4YW1wbGUyMi5jb22CDWV4YW1wbGUyMy5jb22CDWV4YW1wbGUyNC5j +b22CDWV4YW1wbGUyNS5jb22CDWV4YW1wbGUyNi5jb22CDWV4YW1wbGUyNy5jb22C +DWV4YW1wbGUyOC5jb22CDWV4YW1wbGUyOS5jb22CDWV4YW1wbGUzMC5jb22CDWV4 +YW1wbGUzMS5jb22CDWV4YW1wbGUzMi5jb22CDWV4YW1wbGUzMy5jb22CDWV4YW1w +bGUzNC5jb22CDWV4YW1wbGUzNS5jb22CDWV4YW1wbGUzNi5jb22CDWV4YW1wbGUz +Ny5jb22CDWV4YW1wbGUzOC5jb22CDWV4YW1wbGUzOS5jb22CDWV4YW1wbGU0MC5j +b22CDWV4YW1wbGU0MS5jb22CDWV4YW1wbGU0Mi5jb22CDWV4YW1wbGU0My5jb22C +DWV4YW1wbGU0NC5jb22CDWV4YW1wbGU0NS5jb22CDWV4YW1wbGU0Ni5jb22CDWV4 +YW1wbGU0Ny5jb22CDWV4YW1wbGU0OC5jb22CDWV4YW1wbGU0OS5jb22CDWV4YW1w +bGU1MC5jb22CDWV4YW1wbGU1MS5jb22CDWV4YW1wbGU1Mi5jb22CDWV4YW1wbGU1 +My5jb22CDWV4YW1wbGU1NC5jb22CDWV4YW1wbGU1NS5jb22CDWV4YW1wbGU1Ni5j +b22CDWV4YW1wbGU1Ny5jb22CDWV4YW1wbGU1OC5jb22CDWV4YW1wbGU1OS5jb22C +DWV4YW1wbGU2MC5jb22CDWV4YW1wbGU2MS5jb22CDWV4YW1wbGU2Mi5jb22CDWV4 +YW1wbGU2My5jb22CDWV4YW1wbGU2NC5jb22CDWV4YW1wbGU2NS5jb22CDWV4YW1w +bGU2Ni5jb22CDWV4YW1wbGU2Ny5jb22CDWV4YW1wbGU2OC5jb22CDWV4YW1wbGU2 +OS5jb22CDWV4YW1wbGU3MC5jb22CDWV4YW1wbGU3MS5jb22CDWV4YW1wbGU3Mi5j +b22CDWV4YW1wbGU3My5jb22CDWV4YW1wbGU3NC5jb22CDWV4YW1wbGU3NS5jb22C +DWV4YW1wbGU3Ni5jb22CDWV4YW1wbGU3Ny5jb22CDWV4YW1wbGU3OC5jb22CDWV4 +YW1wbGU3OS5jb22CDWV4YW1wbGU4MC5jb22CDWV4YW1wbGU4MS5jb22CDWV4YW1w +bGU4Mi5jb22CDWV4YW1wbGU4My5jb22CDWV4YW1wbGU4NC5jb22CDWV4YW1wbGU4 +NS5jb22CDWV4YW1wbGU4Ni5jb22CDWV4YW1wbGU4Ny5jb22CDWV4YW1wbGU4OC5j +b22CDWV4YW1wbGU4OS5jb22CDWV4YW1wbGU5MC5jb22CDWV4YW1wbGU5MS5jb22C +DWV4YW1wbGU5Mi5jb22CDWV4YW1wbGU5My5jb22CDWV4YW1wbGU5NC5jb22CDWV4 +YW1wbGU5NS5jb22CDWV4YW1wbGU5Ni5jb22CDWV4YW1wbGU5Ny5jb22CDWV4YW1w +bGU5OC5jb22CDWV4YW1wbGU5OS5jb22CDmV4YW1wbGUxMDAuY29tMA0GCSqGSIb3 +DQEBCwUAA0EAW05UMFavHn2rkzMyUfzsOvWzVNlm43eO2yHu5h5TzDb23gkDnNEo +duUAbQ+CLJHYd+MvRCmPQ+3ZnaPy7l/0Hg== +-----END CERTIFICATE REQUEST----- From ba93c576977eafa754c41bb7c4410cc876143b4a Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Jan 2016 14:22:13 -0500 Subject: [PATCH 21/60] Added large sans cert and csr test --- acme/acme/crypto_util_test.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/acme/acme/crypto_util_test.py b/acme/acme/crypto_util_test.py index b3c39c38809..e926fc31797 100644 --- a/acme/acme/crypto_util_test.py +++ b/acme/acme/crypto_util_test.py @@ -82,6 +82,10 @@ def test_cert_two_sans(self): self.assertEqual(self._call_cert('cert-san.pem'), ['example.com', 'www.example.com']) + def test_cert_hundred_sans(self): + self.assertEqual(self._call_cert('cert-100sans.pem'), + ['example{0}.com'.format(i) for i in range(1, 101)]) + def test_csr_no_sans(self): self.assertEqual(self._call_csr('csr-nosans.pem'), []) @@ -98,6 +102,10 @@ def test_csr_six_sans(self): "example.info", "subdomain.example.com", "other.subdomain.example.com"]) + def test_csr_hundred_sans(self): + self.assertEqual(self._call_csr('csr-100sans.pem'), + ['example{0}.com'.format(i) for i in range(1, 101)]) + if __name__ == "__main__": unittest.main() # pragma: no cover From 96114ba84e2a6177cca9533e5d5fb9fafa7fa4b1 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Jan 2016 15:10:08 -0500 Subject: [PATCH 22/60] Add IDN SANs CSR and cert --- acme/acme/testdata/cert-idnsans.pem | 30 +++++++++++++++++++++++++++++ acme/acme/testdata/csr-idnsans.pem | 27 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 acme/acme/testdata/cert-idnsans.pem create mode 100644 acme/acme/testdata/csr-idnsans.pem diff --git a/acme/acme/testdata/cert-idnsans.pem b/acme/acme/testdata/cert-idnsans.pem new file mode 100644 index 00000000000..932649692ba --- /dev/null +++ b/acme/acme/testdata/cert-idnsans.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFNjCCBOCgAwIBAgIJAP4rNqqOKifCMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV +BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv +bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X +DTE2MDEwNjIwMDg1OFoXDTE2MDEwNzIwMDg1OFowZDELMAkGA1UECAwCQ0ExFjAU +BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp +ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B +AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580 +rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IDczCCA28wCQYDVR0T +BAIwADALBgNVHQ8EBAMCBeAwggNTBgNVHREEggNKMIIDRoJiz4PPhM+Fz4bPh8+I +z4nPis+Lz4zPjc+Oz4/PkM+Rz5LPk8+Uz5XPls+Xz5jPmc+az5vPnM+dz57Pn8+g +z6HPos+jz6TPpc+mz6fPqM+pz6rPq8+sz63Prs+vLmludmFsaWSCYs+wz7HPss+z +z7TPtc+2z7fPuM+5z7rPu8+8z73Pvs+/2YHZgtmD2YTZhdmG2YfZiNmJ2YrZi9mM +2Y3ZjtmP2ZDZkdmS2ZPZlNmV2ZbZl9mY2ZnZmtmb2ZzZnS5pbnZhbGlkgmLZntmf +2aDZodmi2aPZpNml2abZp9mo2anZqtmr2azZrdmu2a/ZsNmx2bLZs9m02bXZttm3 +2bjZudm62bvZvNm92b7Zv9qA2oHagtqD2oTahdqG2ofaiNqJ2oouaW52YWxpZIJi +2ovajNqN2o7aj9qQ2pHaktqT2pTaldqW2pfamNqZ2pram9qc2p3antqf2qDaodqi +2qPapNql2qbap9qo2qnaqtqr2qzardqu2q/asNqx2rLas9q02rXattq3LmludmFs +aWSCYtq42rnautq72rzavdq+2r/bgNuB24Lbg9uE24XbhtuH24jbiduK24vbjNuN +247bj9uQ25HbktuT25TblduW25fbmNuZ25rbm9uc253bntuf26Dbodui26PbpC5p +bnZhbGlkgnjbpdum26fbqNup26rbq9us263brtuv27Dbsduy27PbtNu127bbt9u4 +27nbutu74aCg4aCh4aCi4aCj4aCk4aCl4aCm4aCn4aCo4aCp4aCq4aCr4aCs4aCt +4aCu4aCv4aCw4aCx4aCy4aCz4aC04aC1LmludmFsaWSCgY/hoLbhoLfhoLjhoLnh +oLrhoLvhoLzhoL3hoL7hoL/hoYDhoYHhoYLhoYPhoYThoYXhoYbhoYfhoYjhoYnh +oYrhoYvhoYzhoY3hoY7hoY/hoZDhoZHhoZLhoZPhoZThoZXhoZbhoZfhoZjhoZnh +oZrhoZvhoZzhoZ3hoZ7hoZ/hoaDhoaHhoaIuaW52YWxpZIJE4aGj4aGk4aGl4aGm +4aGn4aGo4aGp4aGq4aGr4aGs4aGt4aGu4aGv4aGw4aGx4aGy4aGz4aG04aG14aG2 +LmludmFsaWQwDQYJKoZIhvcNAQELBQADQQAzOQL/54yXxln87/YvEQbBm9ik9zoT +TxEkvnZ4kmTRhDsUPtRjMXhY2FH7LOtXKnJQ7POUB7AsJ2Z6uq2w623G +-----END CERTIFICATE----- diff --git a/acme/acme/testdata/csr-idnsans.pem b/acme/acme/testdata/csr-idnsans.pem new file mode 100644 index 00000000000..d6e91a420e0 --- /dev/null +++ b/acme/acme/testdata/csr-idnsans.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEpzCCBFECAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz +Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG +A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt +H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6 +lUTor4R0T+3C5QIDAQABoIIDhjCCA4IGCSqGSIb3DQEJDjGCA3MwggNvMAkGA1Ud +EwQCMAAwCwYDVR0PBAQDAgXgMIIDUwYDVR0RBIIDSjCCA0aCYs+Dz4TPhc+Gz4fP +iM+Jz4rPi8+Mz43Pjs+Pz5DPkc+Sz5PPlM+Vz5bPl8+Yz5nPms+bz5zPnc+ez5/P +oM+hz6LPo8+kz6XPps+nz6jPqc+qz6vPrM+tz67Pry5pbnZhbGlkgmLPsM+xz7LP +s8+0z7XPts+3z7jPuc+6z7vPvM+9z77Pv9mB2YLZg9mE2YXZhtmH2YjZidmK2YvZ +jNmN2Y7Zj9mQ2ZHZktmT2ZTZldmW2ZfZmNmZ2ZrZm9mc2Z0uaW52YWxpZIJi2Z7Z +n9mg2aHZotmj2aTZpdmm2afZqNmp2arZq9ms2a3Zrtmv2bDZsdmy2bPZtNm12bbZ +t9m42bnZutm72bzZvdm+2b/agNqB2oLag9qE2oXahtqH2ojaidqKLmludmFsaWSC +YtqL2ozajdqO2o/akNqR2pLak9qU2pXaltqX2pjamdqa2pvanNqd2p7an9qg2qHa +otqj2qTapdqm2qfaqNqp2qraq9qs2q3artqv2rDasdqy2rPatNq12rbaty5pbnZh +bGlkgmLauNq52rrau9q82r3avtq/24DbgduC24PbhNuF24bbh9uI24nbituL24zb +jduO24/bkNuR25Lbk9uU25XbltuX25jbmdua25vbnNud257bn9ug26Hbotuj26Qu +aW52YWxpZIJ426Xbptun26jbqduq26vbrNut267br9uw27Hbstuz27Tbtdu227fb +uNu527rbu+GgoOGgoeGgouGgo+GgpOGgpeGgpuGgp+GgqOGgqeGgquGgq+GgrOGg +reGgruGgr+GgsOGgseGgsuGgs+GgtOGgtS5pbnZhbGlkgoGP4aC24aC34aC44aC5 +4aC64aC74aC84aC94aC+4aC/4aGA4aGB4aGC4aGD4aGE4aGF4aGG4aGH4aGI4aGJ +4aGK4aGL4aGM4aGN4aGO4aGP4aGQ4aGR4aGS4aGT4aGU4aGV4aGW4aGX4aGY4aGZ +4aGa4aGb4aGc4aGd4aGe4aGf4aGg4aGh4aGiLmludmFsaWSCROGho+GhpOGhpeGh +puGhp+GhqOGhqeGhquGhq+GhrOGhreGhruGhr+GhsOGhseGhsuGhs+GhtOGhteGh +ti5pbnZhbGlkMA0GCSqGSIb3DQEBCwUAA0EAeNkY0M0+kMnjRo6dEUoGE4dX9fEr +dfGrpPUBcwG0P5QBdZJWvZxTfRl14yuPYHbGHULXeGqRdkU6HK5pOlzpng== +-----END CERTIFICATE REQUEST----- From 9dc4af5cee5445e1a8bdfab97d56ecbceea6d659 Mon Sep 17 00:00:00 2001 From: Ben Ubois Date: Wed, 6 Jan 2016 12:10:21 -0800 Subject: [PATCH 23/60] Document webroot request path. It's handy to know the implementation details of the webroot plugin so that a server can be configured to properly the ACME challenge files. --- docs/using.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/using.rst b/docs/using.rst index 5da13f02cb1..f7a59cf7c47 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -139,9 +139,20 @@ Would obtain a single certificate for all of those names, using the ``/var/www/example`` webroot directory for the first two, and ``/var/www/eg`` for the second two. +The webroot plugin works by creating a temporary file for each of your requested +domains in ``${webroot-path}/.well-known/acme-challenge``. Then the Let's +Encrypt validation server makes HTTP requests to validate that the DNS for each +requested domain resolves to the server running letsencrypt. An example request +made to your web server would look like: + +:: + + 66.133.109.36 - - [05/Jan/2016:20:11:24 -0500] "GET /.well-known/acme-challenge/HGr8U1IeTW4kY_Z6UIyaakzOkyQgPr_7ArlLgtZE8SX HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)" + Note that to use the webroot plugin, your server must be configured to serve files from hidden directories. + Manual ------ From 1cdff156c90e075c888829f928fab644d46b98f9 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Jan 2016 15:33:36 -0500 Subject: [PATCH 24/60] Add IDN test --- acme/acme/crypto_util_test.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/acme/acme/crypto_util_test.py b/acme/acme/crypto_util_test.py index e926fc31797..0f3c9225beb 100644 --- a/acme/acme/crypto_util_test.py +++ b/acme/acme/crypto_util_test.py @@ -1,9 +1,11 @@ """Tests for acme.crypto_util.""" +import itertools import socket import threading import time import unittest +import six from six.moves import socketserver # pylint: disable=import-error from acme import errors @@ -69,6 +71,14 @@ def _call(cls, loader, name): from acme.crypto_util import _pyopenssl_cert_or_req_san return _pyopenssl_cert_or_req_san(loader(name)) + @classmethod + def _get_idn_names(cls): + chars = [six.unichr(i) for i in itertools.chain(range(0x3c3, 0x400), + range(0x641, 0x6fc), + range(0x1820, 0x1877))] + return [''.join(chars[i: i + 45]) + '.invalid' + for i in range(0, len(chars), 45)] + def _call_cert(self, name): return self._call(test_util.load_cert, name) @@ -86,6 +96,10 @@ def test_cert_hundred_sans(self): self.assertEqual(self._call_cert('cert-100sans.pem'), ['example{0}.com'.format(i) for i in range(1, 101)]) + def test_cert_idn_sans(self): + self.assertEqual(self._call_cert('cert-idnsans.pem'), + self._get_idn_names()) + def test_csr_no_sans(self): self.assertEqual(self._call_csr('csr-nosans.pem'), []) @@ -106,6 +120,10 @@ def test_csr_hundred_sans(self): self.assertEqual(self._call_csr('csr-100sans.pem'), ['example{0}.com'.format(i) for i in range(1, 101)]) + def test_csr(self): + self.assertEqual(self._call_csr('csr-idnsans.pem'), + self._get_idn_names()) + if __name__ == "__main__": unittest.main() # pragma: no cover From 51bc1311a219b81feba302dc8a7e1a9756201bf8 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Jan 2016 15:34:42 -0500 Subject: [PATCH 25/60] Fixed rogue quotes --- acme/acme/crypto_util_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/acme/acme/crypto_util_test.py b/acme/acme/crypto_util_test.py index 0f3c9225beb..446c19eecc5 100644 --- a/acme/acme/crypto_util_test.py +++ b/acme/acme/crypto_util_test.py @@ -112,9 +112,9 @@ def test_csr_two_sans(self): def test_csr_six_sans(self): self.assertEqual(self._call_csr('csr-6sans.pem'), - ["example.com", "example.org", "example.net", - "example.info", "subdomain.example.com", - "other.subdomain.example.com"]) + ['example.com', 'example.org', 'example.net', + 'example.info', 'subdomain.example.com', + 'other.subdomain.example.com']) def test_csr_hundred_sans(self): self.assertEqual(self._call_csr('csr-100sans.pem'), @@ -125,5 +125,5 @@ def test_csr(self): self._get_idn_names()) -if __name__ == "__main__": +if __name__ == '__main__': unittest.main() # pragma: no cover From 2f569f77836d1628a1cf04afe41fc688c0aae516 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Jan 2016 21:40:26 -0500 Subject: [PATCH 26/60] Tox fanciness --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index dbd6d51fab4..c1d23b69f53 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ # acme and letsencrypt are not yet on pypi, so when Tox invokes # "install *.zip", it will not find deps skipsdist = true -envlist = py26,py27,py33,py34,py35,cover,lint +envlist = py{26,27,33,34,35},cover,lint # nosetest -v => more verbose output, allows to detect busy waiting # loops, especially on Travis From 90f0b15c9d8f4b34d6fcbaa599446fffd0f53dba Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Jan 2016 21:51:42 -0500 Subject: [PATCH 27/60] Add old dependency test --- tox.ini | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c1d23b69f53..e2d7900e000 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ # acme and letsencrypt are not yet on pypi, so when Tox invokes # "install *.zip", it will not find deps skipsdist = true -envlist = py{26,27,33,34,35},cover,lint +envlist = py{26,27,33,34,35},py{26,27}-oldest,cover,lint # nosetest -v => more verbose output, allows to detect busy waiting # loops, especially on Travis @@ -31,6 +31,15 @@ setenv = PYTHONHASHSEED = 0 # https://testrun.org/tox/latest/example/basic.html#special-handling-of-pythonhas + +deps = + py{26,27}-oldest: cryptography==0.8 + py{26,27}-oldest: configargparse==0.10.0 + py{26,27}-oldest: psutil==2.1.0 + py{26,27}-oldest: PyOpenSSL==0.13 + py{26,27}-oldest: pyparsing==1.5.5 + py{26,27}-oldest: python2-pythondialog==3.2.2rc1 + [testenv:py33] commands = pip install -e acme[testing] From 07b3a9fd955121d1ea976bab03d2d6fd7f7ad42d Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Jan 2016 21:52:14 -0500 Subject: [PATCH 28/60] Add old dependency tests to Travis --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index a5d6d8a8588..2532216f164 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,8 @@ env: matrix: - TOXENV=py26 BOULDER_INTEGRATION=1 - TOXENV=py27 BOULDER_INTEGRATION=1 + - TOXENV=py26-oldest + - TOXENV=py27-oldest - TOXENV=lint - TOXENV=cover # Disabled for now due to requiring sudo -> causing more boulder integration From 8a1931e23c015f40cd21c5b003ed56d5c1550bec Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Jan 2016 21:56:52 -0500 Subject: [PATCH 29/60] Run integration tests with old deps --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2532216f164..c3e2e92aad4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,8 +22,8 @@ env: matrix: - TOXENV=py26 BOULDER_INTEGRATION=1 - TOXENV=py27 BOULDER_INTEGRATION=1 - - TOXENV=py26-oldest - - TOXENV=py27-oldest + - TOXENV=py26-oldest BOULDER_INTEGRATION=1 + - TOXENV=py27-oldest BOULDER_INTEGRATION=1 - TOXENV=lint - TOXENV=cover # Disabled for now due to requiring sudo -> causing more boulder integration From 94508b00dfbcd1265967ee23c13f0dda944b9d30 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Jan 2016 21:57:15 -0500 Subject: [PATCH 30/60] Don't pin pyparsing version --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index e2d7900e000..f91285bec64 100644 --- a/tox.ini +++ b/tox.ini @@ -37,7 +37,6 @@ deps = py{26,27}-oldest: configargparse==0.10.0 py{26,27}-oldest: psutil==2.1.0 py{26,27}-oldest: PyOpenSSL==0.13 - py{26,27}-oldest: pyparsing==1.5.5 py{26,27}-oldest: python2-pythondialog==3.2.2rc1 [testenv:py33] From 0b1e1d0937da0087f252807e72fba27848ed1cda Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 6 Jan 2016 22:07:45 -0500 Subject: [PATCH 31/60] Use test_util.load_cert --- acme/acme/challenges_test.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/acme/acme/challenges_test.py b/acme/acme/challenges_test.py index 6b277ac274f..4f2d0616712 100644 --- a/acme/acme/challenges_test.py +++ b/acme/acme/challenges_test.py @@ -264,9 +264,7 @@ def test_gen_verify_cert_gen_key(self): def test_verify_bad_cert(self): self.assertFalse(self.response.verify_cert( - OpenSSL.crypto.load_certificate( - OpenSSL.crypto.FILETYPE_PEM, - test_util.load_vector('cert.pem')))) + test_util.load_cert('cert.pem'))) def test_simple_verify_bad_key_authorization(self): key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) From a815ddbafd2856700b9f30ea71b594ef8961da9e Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 7 Jan 2016 10:05:33 -0500 Subject: [PATCH 32/60] Remove excessive newline --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index f91285bec64..45c7c43d28f 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,6 @@ setenv = PYTHONHASHSEED = 0 # https://testrun.org/tox/latest/example/basic.html#special-handling-of-pythonhas - deps = py{26,27}-oldest: cryptography==0.8 py{26,27}-oldest: configargparse==0.10.0 From e7da21eec0cadfe80f8836d44f1aa64cd89130de Mon Sep 17 00:00:00 2001 From: Reinaldo de Souza Jr Date: Thu, 7 Jan 2016 11:27:40 -0500 Subject: [PATCH 33/60] Makes NginxParser aware of directive Fixes #2059 --- letsencrypt-nginx/letsencrypt_nginx/parser.py | 6 ++++-- .../letsencrypt_nginx/tests/parser_test.py | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/letsencrypt-nginx/letsencrypt_nginx/parser.py b/letsencrypt-nginx/letsencrypt_nginx/parser.py index c60d0102a30..3b1dd049e72 100644 --- a/letsencrypt-nginx/letsencrypt_nginx/parser.py +++ b/letsencrypt-nginx/letsencrypt_nginx/parser.py @@ -113,7 +113,7 @@ def get_vhosts(self): for filename in servers: for server in servers[filename]: # Parse the server block into a VirtualHost object - parsed_server = _parse_server(server) + parsed_server = parse_server(server) vhost = obj.VirtualHost(filename, parsed_server['addrs'], parsed_server['ssl'], @@ -451,7 +451,7 @@ def _get_servernames(names): return names.split(' ') -def _parse_server(server): +def parse_server(server): """Parses a list of server directives. :param list server: list of directives in a server block @@ -471,6 +471,8 @@ def _parse_server(server): elif directive[0] == 'server_name': parsed_server['names'].update( _get_servernames(directive[1])) + elif directive[0] == 'ssl' and directive[1] == 'on': + parsed_server['ssl'] = True return parsed_server diff --git a/letsencrypt-nginx/letsencrypt_nginx/tests/parser_test.py b/letsencrypt-nginx/letsencrypt_nginx/tests/parser_test.py index 6559a5df687..b64f1dee383 100644 --- a/letsencrypt-nginx/letsencrypt_nginx/tests/parser_test.py +++ b/letsencrypt-nginx/letsencrypt_nginx/tests/parser_test.py @@ -228,6 +228,26 @@ def test_get_all_certs_keys(self): c_k = nparser.get_all_certs_keys() self.assertEqual(set([('foo.pem', 'bar.key', filep)]), c_k) + def test_parse_server_ssl(self): + server = parser.parse_server([ + ['listen', '443'] + ]) + self.assertFalse(server['ssl']) + + server = parser.parse_server([ + ['listen', '443 ssl'] + ]) + self.assertTrue(server['ssl']) + + server = parser.parse_server([ + ['listen', '443'], ['ssl', 'off'] + ]) + self.assertFalse(server['ssl']) + + server = parser.parse_server([ + ['listen', '443'], ['ssl', 'on'] + ]) + self.assertTrue(server['ssl']) if __name__ == "__main__": unittest.main() # pragma: no cover From bbf25a2c5edf66acc8df06708cab3181a7928b4f Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 7 Jan 2016 08:57:13 -0800 Subject: [PATCH 34/60] Setenvif may be required for conf tests on ubuntu 12.04? --- .../letsencrypt_apache/tests/apache-conf-files/apache-conf-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/apache-conf-test b/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/apache-conf-test index 4e0443bb77a..0afddbb337b 100755 --- a/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/apache-conf-test +++ b/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/apache-conf-test @@ -49,7 +49,7 @@ if [ "$1" = --debian-modules ] ; then sudo apt-get install -y libapache2-mod-wsgi sudo apt-get install -y libapache2-mod-macro - for mod in ssl rewrite macro wsgi deflate userdir version mime ; do + for mod in ssl rewrite macro wsgi deflate userdir version mime setenvif ; do sudo a2enmod $mod done fi From 4bdd96a29e98cd0aa5b29c1565c986d7b1a8f8e3 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 7 Jan 2016 08:59:53 -0800 Subject: [PATCH 35/60] Verbosity --- .../letsencrypt_apache/tests/apache-conf-files/apache-conf-test | 1 + 1 file changed, 1 insertion(+) diff --git a/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/apache-conf-test b/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/apache-conf-test index 0afddbb337b..7b3f83d13a7 100755 --- a/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/apache-conf-test +++ b/letsencrypt-apache/letsencrypt_apache/tests/apache-conf-files/apache-conf-test @@ -50,6 +50,7 @@ if [ "$1" = --debian-modules ] ; then sudo apt-get install -y libapache2-mod-macro for mod in ssl rewrite macro wsgi deflate userdir version mime setenvif ; do + echo -n enabling $mod sudo a2enmod $mod done fi From dc7f479fe3ac34388da208f97abfff4af98eaef0 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 7 Jan 2016 09:44:13 -0800 Subject: [PATCH 36/60] [deb bootstrap] Add precise-backports for libaugeas0 --- bootstrap/_deb_common.sh | 42 ++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/bootstrap/_deb_common.sh b/bootstrap/_deb_common.sh index 8b96fe6f16a..180caf1a1ba 100755 --- a/bootstrap/_deb_common.sh +++ b/bootstrap/_deb_common.sh @@ -35,25 +35,39 @@ fi augeas_pkg=libaugeas0 AUGVERSION=`apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2` -if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then - if lsb_release -a | grep -q wheezy ; then - if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q wheezy-backports ; then - # This can theoretically error if sources.list.d is empty, but in that case we don't care. - if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q wheezy-backports ; then - /bin/echo -n "Installing augeas from wheezy-backports in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rInstalling augeas from wheezy-backports in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rInstalling augeas from wheezy-backports in 1 second ..." - sleep 1s +AddBackportRepo() { + # ARGS: + BACKPORT_NAME="$0" + BACKPORT_SOURCELINE="$1" + if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then + # This can theoretically error if sources.list.d is empty, but in that case we don't care. + if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then + /bin/echo -n "Installing augeas from wheezy-backports in 3 seconds..." + sleep 1s + /bin/echo -ne "\e[0K\rInstalling augeas from wheezy-backports in 2 seconds..." + sleep 1s + /bin/echo -e "\e[0K\rInstalling augeas from wheezy-backports in 1 second ..." + sleep 1s + if echo $BACKPORT_NAME | grep -q wheezy ; then /bin/echo '(Backports are only installed if explicitly requested via "apt-get install -t wheezy-backports")' - - echo deb http://http.debian.net/debian wheezy-backports main >> /etc/apt/sources.list.d/wheezy-backports.list - apt-get update fi + + echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/"$BACKPORT_NAME".list + apt-get update fi + fi + +} + + +if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then + if lsb_release -a | grep -q wheezy ; then + AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main" apt-get install -y --no-install-recommends -t wheezy-backports libaugeas0 augeas_pkg= + elif lsb_release -a | grep -q precise ; then + # XXX add ARM case + AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse" else echo "No libaugeas0 version is available that's new enough to run the" echo "Let's Encrypt apache plugin..." From 491a08e4d47201b8b30891aa9da636c403664776 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 7 Jan 2016 09:46:45 -0800 Subject: [PATCH 37/60] fixen --- bootstrap/_deb_common.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bootstrap/_deb_common.sh b/bootstrap/_deb_common.sh index 180caf1a1ba..410f4e10eaf 100755 --- a/bootstrap/_deb_common.sh +++ b/bootstrap/_deb_common.sh @@ -37,16 +37,16 @@ AUGVERSION=`apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut - AddBackportRepo() { # ARGS: - BACKPORT_NAME="$0" - BACKPORT_SOURCELINE="$1" + BACKPORT_NAME="$1" + BACKPORT_SOURCELINE="$2" if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then # This can theoretically error if sources.list.d is empty, but in that case we don't care. if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then - /bin/echo -n "Installing augeas from wheezy-backports in 3 seconds..." + /bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..." sleep 1s - /bin/echo -ne "\e[0K\rInstalling augeas from wheezy-backports in 2 seconds..." + /bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..." sleep 1s - /bin/echo -e "\e[0K\rInstalling augeas from wheezy-backports in 1 second ..." + /bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..." sleep 1s if echo $BACKPORT_NAME | grep -q wheezy ; then /bin/echo '(Backports are only installed if explicitly requested via "apt-get install -t wheezy-backports")' From 154cd47c83cf5753354563ceb0c92febb2c868ce Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 7 Jan 2016 09:52:10 -0800 Subject: [PATCH 38/60] precision --- bootstrap/_deb_common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/_deb_common.sh b/bootstrap/_deb_common.sh index 410f4e10eaf..72f8e85c832 100755 --- a/bootstrap/_deb_common.sh +++ b/bootstrap/_deb_common.sh @@ -67,7 +67,7 @@ if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then augeas_pkg= elif lsb_release -a | grep -q precise ; then # XXX add ARM case - AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse" + AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse" else echo "No libaugeas0 version is available that's new enough to run the" echo "Let's Encrypt apache plugin..." From caf9b1f26111d0a51212aa3b5b70d13d7347b547 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 7 Jan 2016 13:00:29 -0500 Subject: [PATCH 39/60] Clarify _get_idn_sans method --- acme/acme/crypto_util_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/acme/acme/crypto_util_test.py b/acme/acme/crypto_util_test.py index 446c19eecc5..8b510e43e9e 100644 --- a/acme/acme/crypto_util_test.py +++ b/acme/acme/crypto_util_test.py @@ -73,6 +73,7 @@ def _call(cls, loader, name): @classmethod def _get_idn_names(cls): + """Returns expected names from '{cert,csr}-idnsans.pem'.""" chars = [six.unichr(i) for i in itertools.chain(range(0x3c3, 0x400), range(0x641, 0x6fc), range(0x1820, 0x1877))] From 0f239e0029e77f302401c3c6089e67f45c19d51c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 7 Jan 2016 13:04:29 -0500 Subject: [PATCH 40/60] Add comment about dependency version --- acme/setup.py | 1 + letsencrypt-apache/setup.py | 1 + letsencrypt-nginx/setup.py | 1 + setup.py | 1 + 4 files changed, 4 insertions(+) diff --git a/acme/setup.py b/acme/setup.py index 3b1a423273d..4993d758485 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -6,6 +6,7 @@ version = '0.2.0.dev0' +# Please update tox.ini when modifying dependency version requirements install_requires = [ # load_pem_private/public_key (>=0.6) # rsa_recover_prime_factors (>=0.8) diff --git a/letsencrypt-apache/setup.py b/letsencrypt-apache/setup.py index 58008e1e412..a5c5e8a7af0 100644 --- a/letsencrypt-apache/setup.py +++ b/letsencrypt-apache/setup.py @@ -6,6 +6,7 @@ version = '0.2.0.dev0' +# Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme=={0}'.format(version), 'letsencrypt=={0}'.format(version), diff --git a/letsencrypt-nginx/setup.py b/letsencrypt-nginx/setup.py index 1d42fe48897..bfb3c3758b7 100644 --- a/letsencrypt-nginx/setup.py +++ b/letsencrypt-nginx/setup.py @@ -6,6 +6,7 @@ version = '0.2.0.dev0' +# Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme=={0}'.format(version), 'letsencrypt=={0}'.format(version), diff --git a/setup.py b/setup.py index f95f672ff2f..ad7fb6909e2 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ def read_file(filename, encoding='utf8'): changes = read_file(os.path.join(here, 'CHANGES.rst')) version = meta['version'] +# Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme=={0}'.format(version), 'configobj', From 99616864766e7cf8acbf8cda7625153159c56738 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 7 Jan 2016 10:09:20 -0800 Subject: [PATCH 41/60] We might need -t afterall --- bootstrap/_deb_common.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/_deb_common.sh b/bootstrap/_deb_common.sh index 72f8e85c832..2b6ee11be01 100755 --- a/bootstrap/_deb_common.sh +++ b/bootstrap/_deb_common.sh @@ -54,6 +54,8 @@ AddBackportRepo() { echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/"$BACKPORT_NAME".list apt-get update + apt-get install -y --no-install-recommends -t "$BACKPORT_NAME" libaugeas0 + augeas_pkg= fi fi @@ -63,8 +65,6 @@ AddBackportRepo() { if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then if lsb_release -a | grep -q wheezy ; then AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main" - apt-get install -y --no-install-recommends -t wheezy-backports libaugeas0 - augeas_pkg= elif lsb_release -a | grep -q precise ; then # XXX add ARM case AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse" From 710eb59f41e9ba0f258bc9b6d74761a6158513ac Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 7 Jan 2016 16:19:21 -0500 Subject: [PATCH 42/60] Fix IDN CSR test name --- acme/acme/crypto_util_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme/acme/crypto_util_test.py b/acme/acme/crypto_util_test.py index 8b510e43e9e..147cd5a2a87 100644 --- a/acme/acme/crypto_util_test.py +++ b/acme/acme/crypto_util_test.py @@ -121,7 +121,7 @@ def test_csr_hundred_sans(self): self.assertEqual(self._call_csr('csr-100sans.pem'), ['example{0}.com'.format(i) for i in range(1, 101)]) - def test_csr(self): + def test_csr_idn_sans(self): self.assertEqual(self._call_csr('csr-idnsans.pem'), self._get_idn_names()) From 32957cc5eccc9dc6d61f3c02327edabc860f5d31 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 7 Jan 2016 16:25:23 -0500 Subject: [PATCH 43/60] Comment _pyopenssl_cert_or_req_san method --- acme/acme/crypto_util.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index ecec351c222..0d0e78df6ff 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -169,11 +169,14 @@ def _pyopenssl_cert_or_req_san(cert_or_req): func = OpenSSL.crypto.dump_certificate else: func = OpenSSL.crypto.dump_certificate_request + + # This method of finding SANs is used to support PyOpenSSL version 0.13. text = func(OpenSSL.crypto.FILETYPE_TEXT, cert_or_req).decode("utf-8") match = re.search(r"X509v3 Subject Alternative Name:\s*(.*)", text) - sans_parts = [] if match is None else match.group(1).split(parts_separator) + # WARNING: this function assumes that no SAN can include # parts_separator, hence the split! + sans_parts = [] if match is None else match.group(1).split(parts_separator) return [part.split(part_separator)[1] for part in sans_parts if part.startswith(prefix)] From 946f4474da7be9c784b732224f395ab2c22ed621 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 7 Jan 2016 16:45:46 -0500 Subject: [PATCH 44/60] Add warning about multiple SANs extensions --- acme/acme/crypto_util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index 0d0e78df6ff..a25819cf508 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -169,11 +169,11 @@ def _pyopenssl_cert_or_req_san(cert_or_req): func = OpenSSL.crypto.dump_certificate else: func = OpenSSL.crypto.dump_certificate_request - # This method of finding SANs is used to support PyOpenSSL version 0.13. text = func(OpenSSL.crypto.FILETYPE_TEXT, cert_or_req).decode("utf-8") + # WARNING: this function does not support multiple SANs extensions. + # Multiple X509v3 extensions of the same type is disallowed by RFC 5280. match = re.search(r"X509v3 Subject Alternative Name:\s*(.*)", text) - # WARNING: this function assumes that no SAN can include # parts_separator, hence the split! sans_parts = [] if match is None else match.group(1).split(parts_separator) From cc168c8ef13bb2d7a53c3ce488806632d4f2ca31 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Thu, 7 Jan 2016 22:17:40 +0000 Subject: [PATCH 45/60] Generate fresh pylintrc pylint 1.4.2, -generate-rcfile --- acme/.pylintrc | 380 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 acme/.pylintrc diff --git a/acme/.pylintrc b/acme/.pylintrc new file mode 100644 index 00000000000..a31ace48df8 --- /dev/null +++ b/acme/.pylintrc @@ -0,0 +1,380 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# DEPRECATED +include-ids=no + +# DEPRECATED +symbols=no + +# Use multiple processes to speed up Pylint. +jobs=1 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=E1608,W1627,E1601,E1603,E1602,E1605,E1604,E1607,E1606,W1621,W1620,W1623,W1622,W1625,W1624,W1609,W1608,W1607,W1606,W1605,W1604,W1603,W1602,W1601,W1639,W1640,I0021,W1638,I0020,W1618,W1619,W1630,W1626,W1637,W1634,W1635,W1610,W1611,W1612,W1613,W1614,W1615,W1616,W1617,W1632,W1633,W0704,W1628,W1629,W1636 + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=100 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis +ignored-modules= + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,input + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception From dba69d079f734f13437f1845f0008d1f1e81f6fa Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Thu, 7 Jan 2016 22:18:21 +0000 Subject: [PATCH 46/60] Separate pylintrc for acme --- acme/.pylintrc | 23 +++++++++++++---------- acme/acme/challenges.py | 2 +- acme/acme/crypto_util.py | 2 +- acme/acme/messages.py | 4 ++-- tox.ini | 2 +- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/acme/.pylintrc b/acme/.pylintrc index a31ace48df8..33650310d67 100644 --- a/acme/.pylintrc +++ b/acme/.pylintrc @@ -19,7 +19,7 @@ persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. -load-plugins= +load-plugins=linter_plugin # DEPRECATED include-ids=no @@ -60,7 +60,10 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=E1608,W1627,E1601,E1603,E1602,E1605,E1604,E1607,E1606,W1621,W1620,W1623,W1622,W1625,W1624,W1609,W1608,W1607,W1606,W1605,W1604,W1603,W1602,W1601,W1639,W1640,I0021,W1638,I0020,W1618,W1619,W1630,W1626,W1637,W1634,W1635,W1610,W1611,W1612,W1613,W1614,W1615,W1616,W1617,W1632,W1633,W0704,W1628,W1629,W1636 +disable=fixme,locally-disabled,abstract-class-not-used +# bstract-class-not-used cannot be disabled locally (at least in +# pylint 1.4.1/2) + [REPORTS] @@ -133,7 +136,7 @@ notes=FIXME,XXX,TODO # Logging modules to check that the string format arguments are in logging # function parameter format -logging-modules=logging +logging-modules=logging,logger [SPELLING] @@ -200,7 +203,7 @@ init-import=no # A regular expression matching the name of dummy variables (i.e. expectedly # not used). -dummy-variables-rgx=_$|dummy +dummy-variables-rgx=_$|dummy|unused # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. @@ -220,7 +223,7 @@ required-attributes= bad-functions=map,filter,input # Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ +good-names=i,j,k,ex,Run,_,logger # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata @@ -233,7 +236,7 @@ name-group= include-naming-hint=no # Regular expression matching correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ +function-rgx=[a-z_][a-z0-9_]{2,40}$ # Naming hint for function names function-name-hint=[a-z_][a-z0-9_]{2,30}$ @@ -287,14 +290,14 @@ module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression matching correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ +method-rgx=[a-z_][a-z0-9_]{2,49}$ # Naming hint for method names method-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match function or class names that do # not require a docstring. -no-docstring-rgx=__.*__ +no-docstring-rgx=__.*__|test_[A-Za-z0-9_]*|_.*|.*Test # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. @@ -324,7 +327,7 @@ exclude-protected=_asdict,_fields,_replace,_source,_make [DESIGN] # Maximum number of arguments for function / method -max-args=5 +max-args=6 # Argument names that match this expression will be ignored. Default to name # with leading underscore @@ -343,7 +346,7 @@ max-branches=12 max-statements=50 # Maximum number of parents for a class (see R0901). -max-parents=7 +max-parents=12 # Maximum number of attributes for a class (see R0902). max-attributes=7 diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 1e456d32579..fc414d93b6a 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -336,7 +336,7 @@ class TLSSNI01Response(KeyAuthorizationChallengeResponse): """ @property - def z(self): + def z(self): # pylint: disable=invalid-name """``z`` value used for verification. :rtype bytes: diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index 72a93141ac4..757f7b5a1d0 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -70,7 +70,7 @@ def _pick_certificate_cb(self, connection): class FakeConnection(object): """Fake OpenSSL.SSL.Connection.""" - # pylint: disable=missing-docstring + # pylint: disable=too-few-public-methods,missing-docstring def __init__(self, connection): self._wrapped = connection diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 0b73864ec5f..6ef00399d0a 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -23,13 +23,13 @@ class Error(jose.JSONObjectWithFields, errors.Error): ('badCSR', 'The CSR is unacceptable (e.g., due to a short key)'), ('badNonce', 'The client sent an unacceptable anti-replay nonce'), ('connection', 'The server could not connect to the client to ' - 'verify the domain'), + 'verify the domain'), ('dnssec', 'The server could not validate a DNSSEC signed domain'), ('malformed', 'The request message was malformed'), ('rateLimited', 'There were too many requests of a given type'), ('serverInternal', 'The server experienced an internal error'), ('tls', 'The server experienced a TLS error during domain ' - 'verification'), + 'verification'), ('unauthorized', 'The client lacks sufficient authorization'), ('unknownHost', 'The server could not resolve a domain name'), ) diff --git a/tox.ini b/tox.ini index dbd6d51fab4..36e3632c0e3 100644 --- a/tox.ini +++ b/tox.ini @@ -62,7 +62,7 @@ commands = pip install -e acme -e .[dev] -e letsencrypt-apache -e letsencrypt-nginx -e letsencrypt-compatibility-test -e letshelp-letsencrypt ./pep8.travis.sh pylint --rcfile=.pylintrc letsencrypt - pylint --rcfile=.pylintrc acme/acme + pylint --rcfile=acme/.pylintrc acme/acme pylint --rcfile=.pylintrc letsencrypt-apache/letsencrypt_apache pylint --rcfile=.pylintrc letsencrypt-nginx/letsencrypt_nginx pylint --rcfile=.pylintrc letsencrypt-compatibility-test/letsencrypt_compatibility_test From 639cbeb7d02801b79429f17f9cd55dad09e962f6 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Thu, 7 Jan 2016 21:11:09 -0500 Subject: [PATCH 47/60] sans_text_dump_comment += 1 --- acme/acme/crypto_util.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index a25819cf508..a1a1be73bf6 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -160,6 +160,12 @@ def _pyopenssl_cert_or_req_san(cert_or_req): :rtype: `list` of `unicode` """ + # This function finds SANs by dumping the certificate/CSR to text and + # searching for "X509v3 Subject Alternative Name" in the text. This method + # is used to support PyOpenSSL version 0.13 where the + # `_subjectAltNameString` and `get_extensions` methods are not available + # for CSRs. + # constants based on PyOpenSSL certificate/CSR text dump part_separator = ":" parts_separator = ", " @@ -169,7 +175,6 @@ def _pyopenssl_cert_or_req_san(cert_or_req): func = OpenSSL.crypto.dump_certificate else: func = OpenSSL.crypto.dump_certificate_request - # This method of finding SANs is used to support PyOpenSSL version 0.13. text = func(OpenSSL.crypto.FILETYPE_TEXT, cert_or_req).decode("utf-8") # WARNING: this function does not support multiple SANs extensions. # Multiple X509v3 extensions of the same type is disallowed by RFC 5280. From fc3acc69b69da9875c054c2e1ac602462a2ab533 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 7 Jan 2016 19:12:19 -0800 Subject: [PATCH 48/60] Try updating augeas-lenses as well --- bootstrap/_deb_common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/_deb_common.sh b/bootstrap/_deb_common.sh index 2b6ee11be01..84ab9e35a1a 100755 --- a/bootstrap/_deb_common.sh +++ b/bootstrap/_deb_common.sh @@ -54,7 +54,7 @@ AddBackportRepo() { echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/"$BACKPORT_NAME".list apt-get update - apt-get install -y --no-install-recommends -t "$BACKPORT_NAME" libaugeas0 + apt-get install -y --no-install-recommends -t "$BACKPORT_NAME" libaugeas0 augeas-lenses augeas_pkg= fi fi From b1e67f241e64137077215f4f432995ae10439006 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 8 Jan 2016 14:31:30 -0500 Subject: [PATCH 49/60] Fix merge conflicts properly --- acme/setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/acme/setup.py b/acme/setup.py index 82a08b6ef09..c10c955464a 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -10,7 +10,6 @@ install_requires = [ # load_pem_private/public_key (>=0.6) # rsa_recover_prime_factors (>=0.8) -<<<<<<< HEAD 'cryptography>=0.8,<1.2', # Connection.set_tlsext_host_name (>=0.13) 'PyOpenSSL>=0.13', From 96b55c8f349ac29e314e5468c406d0e07f4b4917 Mon Sep 17 00:00:00 2001 From: bmw Date: Fri, 8 Jan 2016 17:02:35 -0500 Subject: [PATCH 50/60] Revert "Don't use cryptography version 1.2" --- acme/setup.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 54c4d82d9bb..7314152cde7 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -9,7 +9,7 @@ install_requires = [ # load_pem_private/public_key (>=0.6) # rsa_recover_prime_factors (>=0.8) - 'cryptography>=0.8,<1.2', + 'cryptography>=0.8', # Connection.set_tlsext_host_name (>=0.13), X509Req.get_extensions (>=0.15) 'PyOpenSSL>=0.15', 'pyrfc3339', diff --git a/setup.py b/setup.py index 4005b09735c..f95f672ff2f 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ def read_file(filename, encoding='utf8'): install_requires = [ 'acme=={0}'.format(version), 'configobj', - 'cryptography>=0.7,<1.2', # load_pem_x509_certificate + 'cryptography>=0.7', # load_pem_x509_certificate 'parsedatetime', 'psutil>=2.1.0', # net_connections introduced in 2.1.0 'PyOpenSSL', From 705032bc67afe3705af7916d767487f4eeb38bc1 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Fri, 8 Jan 2016 19:12:30 -0800 Subject: [PATCH 51/60] [Always] Install augeas-lenses - But do we need the augeas-lenses package? - Install augeas from backports even if the backports were already available (this is the third time fixing that bug!) --- bootstrap/_deb_common.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/_deb_common.sh b/bootstrap/_deb_common.sh index 84ab9e35a1a..58ea67a45da 100755 --- a/bootstrap/_deb_common.sh +++ b/bootstrap/_deb_common.sh @@ -32,7 +32,7 @@ if apt-cache show python-virtualenv > /dev/null 2>&1; then virtualenv="$virtualenv python-virtualenv" fi -augeas_pkg=libaugeas0 +augeas_pkg="libaugeas0 augeas-lenses" AUGVERSION=`apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2` AddBackportRepo() { @@ -54,7 +54,7 @@ AddBackportRepo() { echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/"$BACKPORT_NAME".list apt-get update - apt-get install -y --no-install-recommends -t "$BACKPORT_NAME" libaugeas0 augeas-lenses + apt-get install -y --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg augeas_pkg= fi fi From 7728f4d28a14724783bd8ee42d54ab047d645500 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 9 Jan 2016 13:24:39 +0000 Subject: [PATCH 52/60] Python 3 Travis testing for acme. Despite its description, https://github.com/letsencrypt/letsencrypt/pull/630, removed not only Python 2.6 support, but also Travis tests against Python 3. ACME library supports Python 3 and Travis should tests it. This must be merged before any pending PRs agains acme library. --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index a5d6d8a8588..6cadbd36ee0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,9 @@ env: matrix: - TOXENV=py26 BOULDER_INTEGRATION=1 - TOXENV=py27 BOULDER_INTEGRATION=1 + - TOXENV=py33 + - TOXENV=py34 + - TOXENV=py35 - TOXENV=lint - TOXENV=cover # Disabled for now due to requiring sudo -> causing more boulder integration From 34010a0168d0df97a08bdd5b985e7694a37123f2 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 9 Jan 2016 13:31:50 +0000 Subject: [PATCH 53/60] Python 3.5 needs explicit Travis setting --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6cadbd36ee0..680dfeb8a63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,12 +24,15 @@ env: - TOXENV=py27 BOULDER_INTEGRATION=1 - TOXENV=py33 - TOXENV=py34 - - TOXENV=py35 - TOXENV=lint - TOXENV=cover # Disabled for now due to requiring sudo -> causing more boulder integration # DNS timeouts :( # - TOXENV=apacheconftest +matrix: + include: + - env: TOXENV=py35 + python: 3.5 # Only build pushes to the master branch, PRs, and branches beginning with From b26dda3afe859ca6586d6d89d5b500ee416d2841 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sat, 9 Jan 2016 13:38:12 +0000 Subject: [PATCH 54/60] Add Python 3.5 trove classifier to acme --- acme/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/acme/setup.py b/acme/setup.py index 7314152cde7..f585b3cdd0e 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -66,6 +66,7 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', ], From 5b3bd890b70f831c492c904d05e26fec9c5b9201 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Sat, 9 Jan 2016 15:15:05 -0800 Subject: [PATCH 55/60] Default: renew 30 days before expiry, rather than 10 - gives more time for various fallback strategies if renewal doesn't work the first time --- letsencrypt/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/storage.py b/letsencrypt/storage.py index ac71bd9fe53..fc58ffb8d37 100644 --- a/letsencrypt/storage.py +++ b/letsencrypt/storage.py @@ -565,7 +565,7 @@ def should_autorenew(self, interactive=False): return True # Renewals on the basis of expiry time - interval = self.configuration.get("renew_before_expiry", "10 days") + interval = self.configuration.get("renew_before_expiry", "30 days") expiry = crypto_util.notAfter(self.version( "cert", self.latest_common_version())) now = pytz.UTC.fromutc(datetime.datetime.utcnow()) From 3a7565afe5633abb5329b06c954c72f225e462c0 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Sun, 10 Jan 2016 01:03:50 -0800 Subject: [PATCH 56/60] trigger travis rerun --- letsencrypt/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/storage.py b/letsencrypt/storage.py index fc58ffb8d37..f7e5c3ad3ee 100644 --- a/letsencrypt/storage.py +++ b/letsencrypt/storage.py @@ -564,7 +564,7 @@ def should_autorenew(self, interactive=False): logger.debug("Should renew, certificate is revoked.") return True - # Renewals on the basis of expiry time + # Renews some period before expiry time interval = self.configuration.get("renew_before_expiry", "30 days") expiry = crypto_util.notAfter(self.version( "cert", self.latest_common_version())) From f5862a7a4f1313c9b1bf63dc8acedb2b07dac62f Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Sun, 10 Jan 2016 18:38:53 +0200 Subject: [PATCH 57/60] Parse all included paths in apache root configuration --- .../letsencrypt_apache/configurator.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index 2a9fb025043..1d46188259f 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -488,15 +488,27 @@ def get_virtual_hosts(self): :rtype: list """ - # Search vhost-root, httpd.conf for possible virtual hosts - paths = self.aug.match( - ("/files%s//*[label()=~regexp('%s')]" % - (self.conf("vhost-root"), parser.case_i("VirtualHost")))) - + # Search base config, and all included paths for VirtualHosts vhs = [] - - for path in paths: - vhs.append(self._create_vhost(path)) + vhost_paths = {} + for vhost_path in self.parser.parser_paths.keys(): + paths = self.aug.match( + ("/files%s//*[label()=~regexp('%s')]" % + (vhost_path, parser.case_i("VirtualHost")))) + for path in paths: + new_vhost = self._create_vhost(path) + realpath = os.path.realpath(new_vhost.filep) + if realpath not in vhost_paths.keys(): + vhs.append(new_vhost) + vhost_paths[realpath] = new_vhost.filep + elif realpath == new_vhost.filep: + # Prefer "real" vhost paths instead of symlinked ones + # ex: sites-enabled/vh.conf -> sites-available/vh.conf + + # remove old (most likely) symlinked one + vhs = [v for v in vhs if v.filep != vhost_paths[realpath]] + vhs.append(new_vhost) + vhost_paths[realpath] = realpath return vhs From 4c02902762dc09b2c2ece0f23ba5da60102f8dad Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Sun, 10 Jan 2016 09:01:34 -0800 Subject: [PATCH 58/60] [bootstrap/_deb_common] Re-fix the always-install-backports * This bug seems to come back every time it's fixed :( --- bootstrap/_deb_common.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/_deb_common.sh b/bootstrap/_deb_common.sh index 58ea67a45da..c2f58db757c 100755 --- a/bootstrap/_deb_common.sh +++ b/bootstrap/_deb_common.sh @@ -54,10 +54,10 @@ AddBackportRepo() { echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/"$BACKPORT_NAME".list apt-get update - apt-get install -y --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg - augeas_pkg= fi fi + apt-get install -y --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg + augeas_pkg= } From 39e4053b82b4649b6413606c4896c4b2c5cc987a Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Sun, 10 Jan 2016 19:15:09 +0200 Subject: [PATCH 59/60] Removed some now obsolete mock code from tests --- .../letsencrypt_apache/tests/configurator_test.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py index 9838b4f5287..212f128f227 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py @@ -128,20 +128,10 @@ def test_get_virtual_hosts(self): self.assertEqual(found, 6) # Handle case of non-debian layout get_virtual_hosts - orig_conf = self.config.conf with mock.patch( "letsencrypt_apache.configurator.ApacheConfigurator.conf" - ) as mock_conf: - def conf_sideeffect(key): - """Handle calls to configurator.conf() - :param key: configuration key - :return: configuration value - """ - if key == "handle-sites": - return False - else: - return orig_conf(key) - mock_conf.side_effect = conf_sideeffect + ) as mock_conf: + mock_conf.return_value = False vhs = self.config.get_virtual_hosts() self.assertEqual(len(vhs), 6) From c89dcad313e933630e1b8e7b81b4878ebc4c5534 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Mon, 11 Jan 2016 12:22:22 -0800 Subject: [PATCH 60/60] This default shouldn't be a magic string --- letsencrypt/storage.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/letsencrypt/storage.py b/letsencrypt/storage.py index f7e5c3ad3ee..08b48ff5e71 100644 --- a/letsencrypt/storage.py +++ b/letsencrypt/storage.py @@ -565,7 +565,8 @@ def should_autorenew(self, interactive=False): return True # Renews some period before expiry time - interval = self.configuration.get("renew_before_expiry", "30 days") + default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"] + interval = self.configuration.get("renew_before_expiry", default_interval) expiry = crypto_util.notAfter(self.version( "cert", self.latest_common_version())) now = pytz.UTC.fromutc(datetime.datetime.utcnow())