diff --git a/bootstrap/_deb_common.sh b/bootstrap/_deb_common.sh index a4dff6b1c..687f30d09 100755 --- a/bootstrap/_deb_common.sh +++ b/bootstrap/_deb_common.sh @@ -44,20 +44,14 @@ else fi -# dpkg-dev: dpkg-architecture binary necessary to compile M2Crypto, c.f. -# #276, https://github.com/martinpaljak/M2Crypto/issues/62, -# M2Crypto setup.py:add_multiarch_paths - apt-get install -y --no-install-recommends \ git-core \ python \ python-dev \ "$virtualenv" \ gcc \ - swig \ dialog \ libaugeas0 \ libssl-dev \ libffi-dev \ ca-certificates \ - dpkg-dev \ diff --git a/bootstrap/_rpm_common.sh b/bootstrap/_rpm_common.sh index 1209cd44a..398cfe315 100755 --- a/bootstrap/_rpm_common.sh +++ b/bootstrap/_rpm_common.sh @@ -12,7 +12,6 @@ yum install -y \ python-virtualenv \ python-devel \ gcc \ - swig \ dialog \ augeas-libs \ openssl-devel \ diff --git a/bootstrap/mac.sh b/bootstrap/mac.sh index 9f0f22a17..a48afe11e 100755 --- a/bootstrap/mac.sh +++ b/bootstrap/mac.sh @@ -1,2 +1,2 @@ #!/bin/sh -brew install augeas swig +brew install augeas diff --git a/docs/conf.py b/docs/conf.py index a6e5da4ff..c285c19e5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,7 +23,7 @@ import mock # http://docs.readthedocs.org/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules # c.f. #262 sys.modules.update( - (mod_name, mock.MagicMock()) for mod_name in ['augeas', 'M2Crypto']) + (mod_name, mock.MagicMock()) for mod_name in ['augeas']) here = os.path.abspath(os.path.dirname(__file__)) diff --git a/docs/using.rst b/docs/using.rst index 951c991d2..1fe3702f9 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -52,7 +52,6 @@ are provided mainly for the :ref:`developers ` reference. In general: * ``sudo`` is required as a suggested way of running privileged process -* `SWIG`_ is required for compiling `M2Crypto`_ * `Augeas`_ is required for the Python bindings @@ -102,14 +101,6 @@ Centos 7 sudo ./bootstrap/centos.sh -For installation run this modified command (note the trailing -backslash): - -.. code-block:: shell - - SWIG_FEATURES="-includeall -D__`uname -m`__-I/usr/include/openssl" \ - ./venv/bin/pip install -r requirements.txt . - Installation ============ @@ -127,13 +118,6 @@ Installation your operating system and are **not supported** by the Let's Encrypt team! -.. note:: If your operating system uses SWIG 3.0.5+, you will need to - run ``pip install -r requirements-swig-3.0.5.txt -r - requirements.txt .`` instead. Known affected systems: - - * Fedora 22 - * some versions of Mac OS X - Usage ===== @@ -152,6 +136,4 @@ The ``letsencrypt`` commandline tool has a builtin help: .. _Augeas: http://augeas.net/ -.. _M2Crypto: https://github.com/M2Crypto/M2Crypto -.. _SWIG: http://www.swig.org/ .. _Virtualenv: https://virtualenv.pypa.io diff --git a/letsencrypt/achallenges.py b/letsencrypt/achallenges.py index 88dcdbe11..80535026b 100644 --- a/letsencrypt/achallenges.py +++ b/letsencrypt/achallenges.py @@ -58,7 +58,10 @@ class DVSNI(AnnotatedChallenge): """ response = challenges.DVSNIResponse(s=s) cert_pem = crypto_util.make_ss_cert(self.key.pem, [ - self.nonce_domain, self.domain, response.z_domain(self.challb)]) + # TODO: setting nonce_domain first will cause "Error: + # [('asn1 encoding routines', 'ASN1_mbstring_ncopy', + # 'string too long')]" (get_subject().CN = domains[0]) + self.domain, self.nonce_domain, response.z_domain(self.challb)]) return cert_pem, response diff --git a/letsencrypt/client.py b/letsencrypt/client.py index 71fe05e29..2db07081c 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -3,7 +3,6 @@ import logging import os import pkg_resources -import M2Crypto import OpenSSL.crypto import zope.component @@ -435,8 +434,10 @@ def validate_key_csr(privkey, csr=None): if csr: if csr.form == "der": - csr_obj = M2Crypto.X509.load_request_der_string(csr.data) - csr = le_util.CSR(csr.file, csr_obj.as_pem(), "der") + csr_obj = OpenSSL.crypto.load_certificate_request( + OpenSSL.crypto.FILETYPE_ASN1, csr.data) + csr = le_util.CSR(csr.file, OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_PEM, csr_obj), "pem") # If CSR is provided, it must be readable and valid. if csr.data and not crypto_util.valid_csr(csr.data): diff --git a/letsencrypt/crypto_util.py b/letsencrypt/crypto_util.py index 3abe2d130..a57f0634d 100644 --- a/letsencrypt/crypto_util.py +++ b/letsencrypt/crypto_util.py @@ -4,13 +4,13 @@ is capable of handling the signatures. """ +import datetime import logging import os -import time -import M2Crypto import OpenSSL +from letsencrypt import errors from letsencrypt import le_util @@ -86,7 +86,7 @@ def init_save_csr(privkey, names, path, csrname="csr-letsencrypt.pem"): def make_csr(key_str, domains): """Generate a CSR. - :param str key_str: RSA key. + :param str key_str: PEM-encoded RSA key. :param list domains: Domains included in the certificate. .. todo:: Detect duplicates in `domains`? Using a set doesn't @@ -97,25 +97,23 @@ def make_csr(key_str, domains): """ assert domains, "Must provide one or more hostnames for the CSR." - rsa_key = M2Crypto.RSA.load_key_string(key_str) - pubkey = M2Crypto.EVP.PKey() - pubkey.assign_rsa(rsa_key) - - csr = M2Crypto.X509.Request() - csr.set_pubkey(pubkey) - # TODO: what to put into csr.get_subject()? - - extstack = M2Crypto.X509.X509_Extension_Stack() - ext = M2Crypto.X509.new_extension( - "subjectAltName", ", ".join("DNS:%s" % d for d in domains)) - - extstack.push(ext) - csr.add_extensions(extstack) - csr.sign(pubkey, "sha256") - assert csr.verify(pubkey) - pubkey2 = csr.get_pubkey() - assert csr.verify(pubkey2) - return csr.as_pem(), csr.as_der() + pkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, key_str) + req = OpenSSL.crypto.X509Req() + req.get_subject().CN = domains[0] + # TODO: what to put into req.get_subject()? + # TODO: put SAN if len(domains) > 1 + req.add_extensions([ + OpenSSL.crypto.X509Extension( + "subjectAltName", + critical=False, + value=", ".join("DNS:%s" % d for d in domains) + ), + ]) + req.set_pubkey(pkey) + req.sign(pkey, "sha256") + return tuple(OpenSSL.crypto.dump_certificate_request(method, req) + for method in (OpenSSL.crypto.FILETYPE_PEM, + OpenSSL.crypto.FILETYPE_ASN1)) # WARNING: the csr and private key file are possible attack vectors for TOCTOU @@ -135,9 +133,11 @@ def valid_csr(csr): """ try: - csr_obj = M2Crypto.X509.load_request_string(csr) - return bool(csr_obj.verify(csr_obj.get_pubkey())) - except M2Crypto.X509.X509Error: + req = OpenSSL.crypto.load_certificate_request( + OpenSSL.crypto.FILETYPE_PEM, csr) + return req.verify(req.get_pubkey()) + except OpenSSL.crypto.Error as error: + logger.debug(error, exc_info=True) return False @@ -145,15 +145,20 @@ def csr_matches_pubkey(csr, privkey): """Does private key correspond to the subject public key in the CSR? :param str csr: CSR in PEM. - :param str privkey: Private key file contents + :param str privkey: Private key file contents (PEM) :returns: Correspondence of private key to CSR subject public key. :rtype: bool """ - csr_obj = M2Crypto.X509.load_request_string(csr) - privkey_obj = M2Crypto.RSA.load_key_string(privkey) - return csr_obj.get_pubkey().get_rsa().pub() == privkey_obj.pub() + req = OpenSSL.crypto.load_certificate_request( + OpenSSL.crypto.FILETYPE_PEM, csr) + pkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey) + try: + return req.verify(pkey) + except OpenSSL.crypto.Error as error: + logger.debug(error, exc_info=True) + return False def make_key(bits): @@ -174,18 +179,39 @@ def make_key(bits): def valid_privkey(privkey): """Is valid RSA private key? - :param str privkey: Private key file contents + :param str privkey: Private key file contents in PEM :returns: Validity of private key. :rtype: bool """ try: - return bool(M2Crypto.RSA.load_key_string(privkey).check_key()) - except M2Crypto.RSA.RSAError: + return OpenSSL.crypto.load_privatekey( + OpenSSL.crypto.FILETYPE_PEM, privkey).check() + except (TypeError, OpenSSL.crypto.Error): return False +def _pyopenssl_load(data, method, types=( + OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)): + openssl_errors = [] + for filetype in types: + try: + return method(filetype, data), filetype + except OpenSSL.crypto.Error as error: # TODO: anything else? + openssl_errors.append(error) + raise errors.Error("Unable to load: {0}".format(",".join( + str(error) for error in openssl_errors))) + +def pyopenssl_load_certificate(data): + """Load PEM/DER certificate. + + :raises errors.Error: + + """ + return _pyopenssl_load(data, OpenSSL.crypto.load_certificate) + + def make_ss_cert(key_str, domains, not_before=None, validity=(7 * 24 * 60 * 60)): """Returns new self-signed cert in PEM form. @@ -193,44 +219,36 @@ def make_ss_cert(key_str, domains, not_before=None, Uses key_str and contains all domains. """ - assert domains, "Must provide one or more hostnames for the CSR." - - rsa_key = M2Crypto.RSA.load_key_string(key_str) - pubkey = M2Crypto.EVP.PKey() - pubkey.assign_rsa(rsa_key) - - cert = M2Crypto.X509.X509() - cert.set_pubkey(pubkey) + assert domains, "Must provide one or more hostnames for the cert." + pkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, key_str) + cert = OpenSSL.crypto.X509() cert.set_serial_number(1337) cert.set_version(2) - current_ts = long(time.time() if not_before is None else not_before) - current = M2Crypto.ASN1.ASN1_UTCTIME() - current.set_time(current_ts) - expire = M2Crypto.ASN1.ASN1_UTCTIME() - expire.set_time(current_ts + validity) - cert.set_not_before(current) - cert.set_not_after(expire) + extensions = [ + OpenSSL.crypto.X509Extension( + "basicConstraints", True, 'CA:TRUE, pathlen:0'), + ] - subject = cert.get_subject() - subject.C = "US" - subject.ST = "Michigan" - subject.L = "Ann Arbor" - subject.O = "University of Michigan and the EFF" - subject.CN = domains[0] + cert.get_subject().CN = domains[0] + # TODO: what to put into cert.get_subject()? cert.set_issuer(cert.get_subject()) if len(domains) > 1: - cert.add_ext(M2Crypto.X509.new_extension( - "basicConstraints", "CA:FALSE")) - cert.add_ext(M2Crypto.X509.new_extension( - "subjectAltName", ", ".join(["DNS:%s" % d for d in domains]))) + extensions.append(OpenSSL.crypto.X509Extension( + "subjectAltName", + critical=False, + value=", ".join("DNS:%s" % d for d in domains) + )) - cert.sign(pubkey, "sha256") - assert cert.verify(pubkey) - assert cert.verify() - # print check_purpose(,0 - return cert.as_pem() + cert.add_extensions(extensions) + + cert.gmtime_adj_notBefore(0 if not_before is None else not_before) + cert.gmtime_adj_notAfter(validity) + + cert.set_pubkey(pkey) + cert.sign(pkey, "sha256") + return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) def _pyopenssl_cert_or_req_san(cert_or_req): @@ -308,3 +326,19 @@ def get_sans_from_csr(csr, typ=OpenSSL.crypto.FILETYPE_PEM): """ return _get_sans_from_cert_or_req( csr, OpenSSL.crypto.load_certificate_request, typ) + + +def asn1_generalizedtime_to_dt(timestamp): + """Convert ASN.1 GENERALIZEDTIME to datetime. + + Useful for deserialization of `OpenSSL.crypto.X509.get_notAfter` and + `OpenSSL.crypto.X509.get_notAfter` outputs. + + """ + # TODO: there are two other ts formats + return datetime.datetime.strptime(timestamp, '%Y%m%d%H%M%SZ') + + +def pyopenssl_x509_name_as_text(x509name): + """Convert `OpenSSL.crypto.X509Name to text.""" + return "/".join("{0}={1}" for key, value in x509name.get_components()) diff --git a/letsencrypt/display/util.py b/letsencrypt/display/util.py index bd509dd28..de3e829fe 100644 --- a/letsencrypt/display/util.py +++ b/letsencrypt/display/util.py @@ -409,6 +409,7 @@ def separate_list_input(input_): """ no_commas = input_.replace(",", " ") # Each string is naturally unicode, this causes problems with M2Crypto SANs + # TODO: check if above is still true when M2Crypto is gone ^ return [str(string) for string in no_commas.split()] diff --git a/letsencrypt/proof_of_possession.py b/letsencrypt/proof_of_possession.py index a70fff697..3c625ba90 100644 --- a/letsencrypt/proof_of_possession.py +++ b/letsencrypt/proof_of_possession.py @@ -1,7 +1,9 @@ """Proof of Possession Identifier Validation Challenge.""" -import M2Crypto import logging import os + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend import zope.component from acme import challenges @@ -43,15 +45,21 @@ class ProofOfPossession(object): # pylint: disable=too-few-public-methods return None for cert, key, _ in self.installer.get_all_certs_keys(): - pkey = M2Crypto.X509.load_cert(cert).get_pubkey() + with open(cert) as cert_file: + cert_data = cert_file.read() try: - rsa_pkey = pkey.get_rsa() + cert_obj = x509.load_pem_x509_certificate( + cert_data, default_backend()) except ValueError: - logger.warn("Only RSA supported at this time") - continue - pem_cert_key = rsa_pkey.as_pem() - cert_key = achall.alg.kty.load(pem_cert_key) - # TODO: If JWKES.load raises other exceptions, they should be caught here + try: + cert_obj = x509.load_der_x509_certificate( + cert_data, default_backend()) + except ValueError: + logger.warn("Certificate is neither PER nor DER: %s", cert) + + # TODO: only RSA is supported + cert_key = achall.alg.kty(key=jose.ComparableRSAKey( + cert_obj.public_key())) if cert_key == achall.hints.jwk: return self._gen_response(achall, key) diff --git a/letsencrypt/revoker.py b/letsencrypt/revoker.py index d8bec5fff..7597ff2ce 100644 --- a/letsencrypt/revoker.py +++ b/letsencrypt/revoker.py @@ -13,11 +13,11 @@ import os import shutil import tempfile -import M2Crypto import OpenSSL from acme.jose import util as jose_util +from letsencrypt import crypto_util from letsencrypt import errors from letsencrypt import le_util from letsencrypt import network @@ -196,10 +196,15 @@ class Revoker(object): for (cert_path, _, path) in self.installer.get_all_certs_keys(): try: - cert_sha1 = M2Crypto.X509.load_cert( - cert_path).get_fingerprint(md="sha1") - except (IOError, M2Crypto.X509.X509Error): + with open(cert_path) as cert_file: + cert_data = cert_file.read() + except IOError: continue + try: + cert_obj, _ = crypto_util.pyopenssl_load_certificate(cert_data) + except errors.Error: + continue + cert_sha1 = cert_obj.digest("sha1") if cert_sha1 in csha1_vhlist: csha1_vhlist[cert_sha1].append(path) else: @@ -246,7 +251,6 @@ class Revoker(object): """ # XXX | pylint: disable=unused-variable - # These will both have to change in the future away from M2Crypto # pylint: disable=protected-access certificate = jose_util.ComparableX509(cert._cert) try: @@ -373,8 +377,8 @@ class Revoker(object): class Cert(object): """Cert object used for Revocation convenience. - :ivar _cert: M2Crypto X509 cert - :type _cert: :class:`M2Crypto.X509` + :ivar _cert: Certificate + :type _cert: :class:`OpenSSL.crypto.X509` :ivar int idx: convenience index used for listing :ivar orig: (`str` path - original certificate, `str` status) @@ -402,8 +406,16 @@ class Cert(object): """ try: - self._cert = M2Crypto.X509.load_cert(cert_path) - except (IOError, M2Crypto.X509.X509Error): + with open(cert_path) as cert_file: + cert_data = cert_file.read() + except IOError: + raise errors.RevokerError( + "Error loading certificate: %s" % cert_path) + + try: + self._cert = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, cert_data) + except OpenSSL.crypto.Error: raise errors.RevokerError( "Error loading certificate: %s" % cert_path) @@ -451,8 +463,11 @@ class Cert(object): if not os.path.isfile(orig): status = Cert.DELETED_MSG else: - o_cert = M2Crypto.X509.load_cert(orig) - if self.get_fingerprint() != o_cert.get_fingerprint(md="sha1"): + with open(orig) as orig_file: + orig_data = orig_file.read() + o_cert = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, orig_data) + if self.get_fingerprint() != o_cert.digest("sha1"): status = Cert.CHANGED_MSG # Verify original key path @@ -472,47 +487,49 @@ class Cert(object): self.backup_path = backup self.backup_key_path = backup_key - # M2Crypto is eventually going to be replaced, hence the reason for _cert def get_cn(self): """Get common name.""" return self._cert.get_subject().CN def get_fingerprint(self): """Get SHA1 fingerprint.""" - return self._cert.get_fingerprint(md="sha1") + return self._cert.digest("sha1") def get_not_before(self): """Get not_valid_before field.""" - return self._cert.get_not_before().get_datetime() + return crypto_util.asn1_generalizedtime_to_dt( + self._cert.get_notBefore()) def get_not_after(self): """Get not_valid_after field.""" - return self._cert.get_not_after().get_datetime() + return crypto_util.asn1_generalizedtime_to_dt( + self._cert.get_notAfter()) def get_der(self): """Get certificate in der format.""" - return self._cert.as_der() + return OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_ASN1, self._cert) def get_pub_key(self): """Get public key size. - .. todo:: M2Crypto doesn't support ECC, this will have to be updated + .. todo:: Support for ECC """ - return "RSA " + str(self._cert.get_pubkey().size() * 8) + return "RSA {0}".format(self._cert.get_pubkey().bits) def get_san(self): """Get subject alternative name if available.""" - try: - return self._cert.get_ext("subjectAltName").get_value() - except LookupError: - return "" + # pylint: disable=protected-access + return ", ".join(crypto_util._pyopenssl_cert_or_req_san(self._cert)) def __str__(self): text = [ - "Subject: %s" % self._cert.get_subject().as_text(), + "Subject: %s" % crypto_util.pyopenssl_x509_name_as_text( + self._cert.get_subject()), "SAN: %s" % self.get_san(), - "Issuer: %s" % self._cert.get_issuer().as_text(), + "Issuer: %s" % crypto_util.pyopenssl_x509_name_as_text( + self._cert.get_issuer()), "Public Key: %s" % self.get_pub_key(), "Not Before: %s" % str(self.get_not_before()), "Not After: %s" % str(self.get_not_after()), diff --git a/letsencrypt/tests/achallenges_test.py b/letsencrypt/tests/achallenges_test.py index b4ed6265b..b06398fe2 100644 --- a/letsencrypt/tests/achallenges_test.py +++ b/letsencrypt/tests/achallenges_test.py @@ -1,12 +1,13 @@ """Tests for letsencrypt.achallenges.""" import os import pkg_resources -import re import unittest -import M2Crypto +import OpenSSL from acme import challenges + +from letsencrypt import crypto_util from letsencrypt import le_util from letsencrypt.tests import acme_util @@ -31,15 +32,13 @@ class DVSNITest(unittest.TestCase): def test_gen_cert_and_response(self): cert_pem, _ = self.achall.gen_cert_and_response(s=self.response.s) - cert = M2Crypto.X509.load_cert_string(cert_pem) - self.assertEqual(cert.get_subject().CN, self.chall.nonce_domain) - - sans = cert.get_ext("subjectAltName").get_value() - self.assertEqual( - set([self.chall.nonce_domain, "example.com", - self.response.z_domain(self.chall)]), - set(re.findall(r"DNS:([^, $]*)", sans)), - ) + cert = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, cert_pem) + self.assertEqual(cert.get_subject().CN, "example.com") + # pylint: disable=protected-access + self.assertEqual(crypto_util._pyopenssl_cert_or_req_san(cert), [ + "example.com", self.chall.nonce_domain, + self.response.z_domain(self.chall)]) if __name__ == "__main__": diff --git a/letsencrypt/tests/crypto_util_test.py b/letsencrypt/tests/crypto_util_test.py index e2d996640..06bdc4cd8 100644 --- a/letsencrypt/tests/crypto_util_test.py +++ b/letsencrypt/tests/crypto_util_test.py @@ -6,7 +6,6 @@ import shutil import tempfile import unittest -import M2Crypto import OpenSSL import mock @@ -146,7 +145,8 @@ class MakeKeyTest(unittest.TestCase): # pylint: disable=too-few-public-methods def test_it(self): # pylint: disable=no-self-use from letsencrypt.crypto_util import make_key # Do not test larger keys as it takes too long. - M2Crypto.RSA.load_key_string(make_key(1024)) + OpenSSL.crypto.load_privatekey( + OpenSSL.crypto.FILETYPE_PEM, make_key(1024)) class ValidPrivkeyTest(unittest.TestCase): diff --git a/letsencrypt/tests/proof_of_possession_test.py b/letsencrypt/tests/proof_of_possession_test.py index 68f740ebd..3013e2e12 100644 --- a/letsencrypt/tests/proof_of_possession_test.py +++ b/letsencrypt/tests/proof_of_possession_test.py @@ -1,6 +1,7 @@ """Tests for letsencrypt.proof_of_possession.""" import os import pkg_resources +import tempfile import unittest from cryptography.hazmat.backends import default_backend @@ -18,9 +19,7 @@ from letsencrypt.display import util as display_util BASE_PACKAGE = "letsencrypt.tests" CERT0_PATH = pkg_resources.resource_filename( - BASE_PACKAGE, os.path.join("testdata", "cert.pem")) -CERT1_PATH = pkg_resources.resource_filename( - BASE_PACKAGE, os.path.join("testdata", "cert-san.pem")) + "acme.jose", os.path.join("testdata", "cert.der")) CERT2_PATH = pkg_resources.resource_filename( BASE_PACKAGE, os.path.join("testdata", "dsa_cert.pem")) CERT2_KEY_PATH = pkg_resources.resource_filename( @@ -38,7 +37,8 @@ with open(CERT3_KEY_PATH) as cert3_file: class ProofOfPossessionTest(unittest.TestCase): def setUp(self): self.installer = mock.MagicMock() - certs = [CERT0_PATH, CERT1_PATH, CERT2_PATH, CERT3_PATH] + self.cert1_path = tempfile.mkstemp()[1] + certs = [CERT0_PATH, self.cert1_path, CERT2_PATH, CERT3_PATH] keys = [None, None, CERT2_KEY_PATH, CERT3_KEY_PATH] self.installer.get_all_certs_keys.return_value = zip( certs, keys, 4 * [None]) @@ -56,6 +56,9 @@ class ProofOfPossessionTest(unittest.TestCase): self.achall = achallenges.ProofOfPossession( challb=challb, domain="example.com") + def tearDown(self): + os.remove(self.cert1_path) + def test_perform_bad_challenge(self): hints = challenges.ProofOfPossession.Hints( jwk=jose.jwk.JWKOct(key="foo"), cert_fingerprints=(), diff --git a/letsencrypt/tests/revoker_test.py b/letsencrypt/tests/revoker_test.py index ae4eaa900..24336d023 100644 --- a/letsencrypt/tests/revoker_test.py +++ b/letsencrypt/tests/revoker_test.py @@ -267,18 +267,28 @@ class RevokerInstallerTest(RevokerBase): self.assertEqual( sha_vh[cert.get_fingerprint()], self.installs[i]) - @mock.patch("letsencrypt.revoker.M2Crypto.X509.load_cert") - def test_get_installed_load_failure(self, mock_m2): + @mock.patch("letsencrypt.revoker.OpenSSL.crypto.load_certificate") + def test_get_installed_load_failure(self, mock_load_certificate): mock_installer = mock.MagicMock() mock_installer.get_all_certs_keys.return_value = self.certs_keys - mock_m2.side_effect = IOError + mock_load_certificate.side_effect = OpenSSL.crypto.Error revoker = self._get_revoker(mock_installer) # pylint: disable=protected-access self.assertEqual(revoker._get_installed_locations(), {}) + def test_get_installed_load_failure_open(self): + tmp = tempfile.mkdtemp() + mock_installer = mock.MagicMock() + mock_installer.get_all_certs_keys.return_value = [( + os.path.join(tmp, 'missing'), None, None)] + revoker = self._get_revoker(mock_installer) + # pylint: disable=protected-access + self.assertEqual(revoker._get_installed_locations(), {}) + os.rmdir(tmp) + class RevokerClassMethodsTest(RevokerBase): def setUp(self): @@ -334,6 +344,13 @@ class CertTest(unittest.TestCase): from letsencrypt.revoker import Cert self.assertRaises(errors.RevokerError, Cert, self.key_path) + def test_failed_load_open(self): + tmp = tempfile.mkdtemp() + from letsencrypt.revoker import Cert + self.assertRaises( + errors.RevokerError, Cert, os.path.join(tmp, 'missing')) + os.rmdir(tmp) + def test_no_row(self): self.assertEqual(self.certs[0].get_row(), None) diff --git a/requirements-swig-3.0.5.txt b/requirements-swig-3.0.5.txt deleted file mode 100644 index 9ef45d950..000000000 --- a/requirements-swig-3.0.5.txt +++ /dev/null @@ -1,67 +0,0 @@ -# Support swig 3.0.5+ -# https://github.com/M2Crypto/M2Crypto/issues/24 -# https://github.com/M2Crypto/M2Crypto/pull/30 -git+https://github.com/M2Crypto/M2Crypto.git@d13a3a46c8934c5f50b31d5f95b23e6e06f845c3#egg=M2Crypto - -# This requirements file will fail on Travis CI 12.04 LTS Ubuntu build -# machine under TOX_ENV=py26 with very confusing error (full tracback -# at https://api.travis-ci.org/jobs/66529698/log.txt?deansi=true): - -#Traceback (most recent call last): -# File "setup.py", line 133, in -# include_package_data=True, -# File "/opt/python/2.6.9/lib/python2.6/distutils/core.py", line 152, in setup -# dist.run_commands() -# File "/opt/python/2.6.9/lib/python2.6/distutils/dist.py", line 975, in run_commands -# self.run_command(cmd) -# File "/opt/python/2.6.9/lib/python2.6/distutils/dist.py", line 995, in run_command -# cmd_obj.run() -# File "/home/travis/build/letsencrypt/lets-encrypt-preview/.tox/py26/lib/python2.6/site-packages/setuptools/command/test.py", line 142, in run -# self.with_project_on_sys_path(self.run_tests) -# File "/home/travis/build/letsencrypt/lets-encrypt-preview/.tox/py26/lib/python2.6/site-packages/setuptools/command/test.py", line 122, in with_project_on_sys_path -# func() -# File "/home/travis/build/letsencrypt/lets-encrypt-preview/.tox/py26/lib/python2.6/site-packages/setuptools/command/test.py", line 163, in run_tests -# testRunner=self._resolve_as_ep(self.test_runner), -# File "/opt/python/2.6.9/lib/python2.6/unittest.py", line 816, in __init__ -# self.parseArgs(argv) -# File "/opt/python/2.6.9/lib/python2.6/unittest.py", line 843, in parseArgs -# self.createTests() -# File "/opt/python/2.6.9/lib/python2.6/unittest.py", line 849, in createTests -# self.module) -# File "/opt/python/2.6.9/lib/python2.6/unittest.py", line 613, in loadTestsFromNames -# suites = [self.loadTestsFromName(name, module) for name in names] -# File "/opt/python/2.6.9/lib/python2.6/unittest.py", line 587, in loadTestsFromName -# return self.loadTestsFromModule(obj) -# File "/home/travis/build/letsencrypt/lets-encrypt-preview/.tox/py26/lib/python2.6/site-packages/setuptools/command/test.py", line 37, in loadTestsFromModule -# tests.append(self.loadTestsFromName(submodule)) -# File "/opt/python/2.6.9/lib/python2.6/unittest.py", line 584, in loadTestsFromName -# parent, obj = obj, getattr(obj, part) -#AttributeError: 'module' object has no attribute 'continuity_auth' - -# the above error happens because letsencrypt.continuity_auth cannot import M2Crypto: - -#>>> import M2Crypto -#Traceback (most recent call last): -# File "", line 1, in -# File "/root/lets-encrypt-preview/venv/lib/python2.6/site-packages/M2Crypto-0.21.1-py2.6-linux-x86_64.egg/M2Crypto/__init__.py", line 22, in -# import m2crypto -# File "/root/lets-encrypt-preview/venv/lib/python2.6/site-packages/M2Crypto-0.21.1-py2.6-linux-x86_64.egg/M2Crypto/m2crypto.py", line 26, in -# _m2crypto = swig_import_helper() -# File "/root/lets-encrypt-preview/venv/lib/python2.6/site-packages/M2Crypto-0.21.1-py2.6-linux-x86_64.egg/M2Crypto/m2crypto.py", line 22, in swig_import_helper -# _mod = imp.load_module('_m2crypto', fp, pathname, description) -#ImportError: /root/lets-encrypt-preview/venv/lib/python2.6/site-packages/M2Crypto-0.21.1-py2.6-linux-x86_64.egg/M2Crypto/_m2crypto.so: undefined symbol: SSLv2_method - -# For more info see: - -# - https://github.com/martinpaljak/M2Crypto/commit/84977c532c2444c5487db57146d81bb68dd5431d -# - http://stackoverflow.com/questions/10547332/install-m2crypto-on-a-virtualenv-without-system-packages -# - http://stackoverflow.com/questions/8206546/undefined-symbol-sslv2-method - -# In short: Python has been built without SSLv2 support, and -# github.com/M2Crypto/M2Crypto version doesn't contain necessary -# patch, but it's the only one that has a patch for newer versions of -# swig... - -# Problem seems not exists on Python 2.7. It's unlikely that the -# target distribution has swig 3.0.5+ and doesn't have Python 2.7, so -# this file should only be used in conjuction with Python 2.6. diff --git a/setup.py b/setup.py index b80983f24..042c6e159 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ letsencrypt_install_requires = [ 'argparse', 'ConfigArgParse', 'configobj', - 'M2Crypto', + #'cryptography>=0.7', # load_pem_x509_certificate, version pin mismatch 'mock', 'parsedatetime', 'psutil>=2.1.0', # net_connections introduced in 2.1.0 @@ -64,7 +64,6 @@ letsencrypt_install_requires = [ 'pytz', 'zope.component', 'zope.interface', - 'M2Crypto', ] letsencrypt_apache_install_requires = [ #'acme', @@ -103,9 +102,6 @@ install_requires = [ 'werkzeug', 'zope.component', 'zope.interface', - # order of items in install_requires DOES matter and M2Crypto has - # to go last, see #152 - 'M2Crypto', ] assert set(install_requires) == set.union(*(set(ireq) for ireq in (