Drop M2Crypto

This commit is contained in:
Jakub Warmuz
2015-07-06 12:18:27 +00:00
parent 61aa29d28c
commit 02e7154c0d
17 changed files with 204 additions and 217 deletions
-6
View File
@@ -44,20 +44,14 @@ else
fi 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 \ apt-get install -y --no-install-recommends \
git-core \ git-core \
python \ python \
python-dev \ python-dev \
"$virtualenv" \ "$virtualenv" \
gcc \ gcc \
swig \
dialog \ dialog \
libaugeas0 \ libaugeas0 \
libssl-dev \ libssl-dev \
libffi-dev \ libffi-dev \
ca-certificates \ ca-certificates \
dpkg-dev \
-1
View File
@@ -12,7 +12,6 @@ yum install -y \
python-virtualenv \ python-virtualenv \
python-devel \ python-devel \
gcc \ gcc \
swig \
dialog \ dialog \
augeas-libs \ augeas-libs \
openssl-devel \ openssl-devel \
+1 -1
View File
@@ -1,2 +1,2 @@
#!/bin/sh #!/bin/sh
brew install augeas swig brew install augeas
+1 -1
View File
@@ -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 # http://docs.readthedocs.org/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules
# c.f. #262 # c.f. #262
sys.modules.update( 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__)) here = os.path.abspath(os.path.dirname(__file__))
-18
View File
@@ -52,7 +52,6 @@ are provided mainly for the :ref:`developers <hacking>` reference.
In general: In general:
* ``sudo`` is required as a suggested way of running privileged process * ``sudo`` is required as a suggested way of running privileged process
* `SWIG`_ is required for compiling `M2Crypto`_
* `Augeas`_ is required for the Python bindings * `Augeas`_ is required for the Python bindings
@@ -102,14 +101,6 @@ Centos 7
sudo ./bootstrap/centos.sh 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 Installation
============ ============
@@ -127,13 +118,6 @@ Installation
your operating system and are **not supported** by the your operating system and are **not supported** by the
Let's Encrypt team! 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 Usage
===== =====
@@ -152,6 +136,4 @@ The ``letsencrypt`` commandline tool has a builtin help:
.. _Augeas: http://augeas.net/ .. _Augeas: http://augeas.net/
.. _M2Crypto: https://github.com/M2Crypto/M2Crypto
.. _SWIG: http://www.swig.org/
.. _Virtualenv: https://virtualenv.pypa.io .. _Virtualenv: https://virtualenv.pypa.io
+4 -1
View File
@@ -58,7 +58,10 @@ class DVSNI(AnnotatedChallenge):
""" """
response = challenges.DVSNIResponse(s=s) response = challenges.DVSNIResponse(s=s)
cert_pem = crypto_util.make_ss_cert(self.key.pem, [ 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 return cert_pem, response
+4 -3
View File
@@ -3,7 +3,6 @@ import logging
import os import os
import pkg_resources import pkg_resources
import M2Crypto
import OpenSSL.crypto import OpenSSL.crypto
import zope.component import zope.component
@@ -435,8 +434,10 @@ def validate_key_csr(privkey, csr=None):
if csr: if csr:
if csr.form == "der": if csr.form == "der":
csr_obj = M2Crypto.X509.load_request_der_string(csr.data) csr_obj = OpenSSL.crypto.load_certificate_request(
csr = le_util.CSR(csr.file, csr_obj.as_pem(), "der") 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 is provided, it must be readable and valid.
if csr.data and not crypto_util.valid_csr(csr.data): if csr.data and not crypto_util.valid_csr(csr.data):
+96 -62
View File
@@ -4,13 +4,13 @@
is capable of handling the signatures. is capable of handling the signatures.
""" """
import datetime
import logging import logging
import os import os
import time
import M2Crypto
import OpenSSL import OpenSSL
from letsencrypt import errors
from letsencrypt import le_util 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): def make_csr(key_str, domains):
"""Generate a CSR. """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. :param list domains: Domains included in the certificate.
.. todo:: Detect duplicates in `domains`? Using a set doesn't .. 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." assert domains, "Must provide one or more hostnames for the CSR."
rsa_key = M2Crypto.RSA.load_key_string(key_str) pkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, key_str)
pubkey = M2Crypto.EVP.PKey() req = OpenSSL.crypto.X509Req()
pubkey.assign_rsa(rsa_key) req.get_subject().CN = domains[0]
# TODO: what to put into req.get_subject()?
csr = M2Crypto.X509.Request() # TODO: put SAN if len(domains) > 1
csr.set_pubkey(pubkey) req.add_extensions([
# TODO: what to put into csr.get_subject()? OpenSSL.crypto.X509Extension(
"subjectAltName",
extstack = M2Crypto.X509.X509_Extension_Stack() critical=False,
ext = M2Crypto.X509.new_extension( value=", ".join("DNS:%s" % d for d in domains)
"subjectAltName", ", ".join("DNS:%s" % d for d in domains)) ),
])
extstack.push(ext) req.set_pubkey(pkey)
csr.add_extensions(extstack) req.sign(pkey, "sha256")
csr.sign(pubkey, "sha256") return tuple(OpenSSL.crypto.dump_certificate_request(method, req)
assert csr.verify(pubkey) for method in (OpenSSL.crypto.FILETYPE_PEM,
pubkey2 = csr.get_pubkey() OpenSSL.crypto.FILETYPE_ASN1))
assert csr.verify(pubkey2)
return csr.as_pem(), csr.as_der()
# WARNING: the csr and private key file are possible attack vectors for TOCTOU # WARNING: the csr and private key file are possible attack vectors for TOCTOU
@@ -135,9 +133,11 @@ def valid_csr(csr):
""" """
try: try:
csr_obj = M2Crypto.X509.load_request_string(csr) req = OpenSSL.crypto.load_certificate_request(
return bool(csr_obj.verify(csr_obj.get_pubkey())) OpenSSL.crypto.FILETYPE_PEM, csr)
except M2Crypto.X509.X509Error: return req.verify(req.get_pubkey())
except OpenSSL.crypto.Error as error:
logger.debug(error, exc_info=True)
return False return False
@@ -145,15 +145,20 @@ def csr_matches_pubkey(csr, privkey):
"""Does private key correspond to the subject public key in the CSR? """Does private key correspond to the subject public key in the CSR?
:param str csr: CSR in PEM. :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. :returns: Correspondence of private key to CSR subject public key.
:rtype: bool :rtype: bool
""" """
csr_obj = M2Crypto.X509.load_request_string(csr) req = OpenSSL.crypto.load_certificate_request(
privkey_obj = M2Crypto.RSA.load_key_string(privkey) OpenSSL.crypto.FILETYPE_PEM, csr)
return csr_obj.get_pubkey().get_rsa().pub() == privkey_obj.pub() 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): def make_key(bits):
@@ -174,18 +179,39 @@ def make_key(bits):
def valid_privkey(privkey): def valid_privkey(privkey):
"""Is valid RSA private key? """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. :returns: Validity of private key.
:rtype: bool :rtype: bool
""" """
try: try:
return bool(M2Crypto.RSA.load_key_string(privkey).check_key()) return OpenSSL.crypto.load_privatekey(
except M2Crypto.RSA.RSAError: OpenSSL.crypto.FILETYPE_PEM, privkey).check()
except (TypeError, OpenSSL.crypto.Error):
return False 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, def make_ss_cert(key_str, domains, not_before=None,
validity=(7 * 24 * 60 * 60)): validity=(7 * 24 * 60 * 60)):
"""Returns new self-signed cert in PEM form. """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. Uses key_str and contains all domains.
""" """
assert domains, "Must provide one or more hostnames for the CSR." assert domains, "Must provide one or more hostnames for the cert."
pkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, key_str)
rsa_key = M2Crypto.RSA.load_key_string(key_str) cert = OpenSSL.crypto.X509()
pubkey = M2Crypto.EVP.PKey()
pubkey.assign_rsa(rsa_key)
cert = M2Crypto.X509.X509()
cert.set_pubkey(pubkey)
cert.set_serial_number(1337) cert.set_serial_number(1337)
cert.set_version(2) cert.set_version(2)
current_ts = long(time.time() if not_before is None else not_before) extensions = [
current = M2Crypto.ASN1.ASN1_UTCTIME() OpenSSL.crypto.X509Extension(
current.set_time(current_ts) "basicConstraints", True, 'CA:TRUE, pathlen:0'),
expire = M2Crypto.ASN1.ASN1_UTCTIME() ]
expire.set_time(current_ts + validity)
cert.set_not_before(current)
cert.set_not_after(expire)
subject = cert.get_subject() cert.get_subject().CN = domains[0]
subject.C = "US" # TODO: what to put into cert.get_subject()?
subject.ST = "Michigan"
subject.L = "Ann Arbor"
subject.O = "University of Michigan and the EFF"
subject.CN = domains[0]
cert.set_issuer(cert.get_subject()) cert.set_issuer(cert.get_subject())
if len(domains) > 1: if len(domains) > 1:
cert.add_ext(M2Crypto.X509.new_extension( extensions.append(OpenSSL.crypto.X509Extension(
"basicConstraints", "CA:FALSE")) "subjectAltName",
cert.add_ext(M2Crypto.X509.new_extension( critical=False,
"subjectAltName", ", ".join(["DNS:%s" % d for d in domains]))) value=", ".join("DNS:%s" % d for d in domains)
))
cert.sign(pubkey, "sha256") cert.add_extensions(extensions)
assert cert.verify(pubkey)
assert cert.verify() cert.gmtime_adj_notBefore(0 if not_before is None else not_before)
# print check_purpose(,0 cert.gmtime_adj_notAfter(validity)
return cert.as_pem()
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): 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( return _get_sans_from_cert_or_req(
csr, OpenSSL.crypto.load_certificate_request, typ) 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())
+1
View File
@@ -409,6 +409,7 @@ def separate_list_input(input_):
""" """
no_commas = input_.replace(",", " ") no_commas = input_.replace(",", " ")
# Each string is naturally unicode, this causes problems with M2Crypto SANs # 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()] return [str(string) for string in no_commas.split()]
+16 -8
View File
@@ -1,7 +1,9 @@
"""Proof of Possession Identifier Validation Challenge.""" """Proof of Possession Identifier Validation Challenge."""
import M2Crypto
import logging import logging
import os import os
from cryptography import x509
from cryptography.hazmat.backends import default_backend
import zope.component import zope.component
from acme import challenges from acme import challenges
@@ -43,15 +45,21 @@ class ProofOfPossession(object): # pylint: disable=too-few-public-methods
return None return None
for cert, key, _ in self.installer.get_all_certs_keys(): 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: try:
rsa_pkey = pkey.get_rsa() cert_obj = x509.load_pem_x509_certificate(
cert_data, default_backend())
except ValueError: except ValueError:
logger.warn("Only RSA supported at this time") try:
continue cert_obj = x509.load_der_x509_certificate(
pem_cert_key = rsa_pkey.as_pem() cert_data, default_backend())
cert_key = achall.alg.kty.load(pem_cert_key) except ValueError:
# TODO: If JWKES.load raises other exceptions, they should be caught here 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: if cert_key == achall.hints.jwk:
return self._gen_response(achall, key) return self._gen_response(achall, key)
+41 -24
View File
@@ -13,11 +13,11 @@ import os
import shutil import shutil
import tempfile import tempfile
import M2Crypto
import OpenSSL import OpenSSL
from acme.jose import util as jose_util from acme.jose import util as jose_util
from letsencrypt import crypto_util
from letsencrypt import errors from letsencrypt import errors
from letsencrypt import le_util from letsencrypt import le_util
from letsencrypt import network from letsencrypt import network
@@ -196,10 +196,15 @@ class Revoker(object):
for (cert_path, _, path) in self.installer.get_all_certs_keys(): for (cert_path, _, path) in self.installer.get_all_certs_keys():
try: try:
cert_sha1 = M2Crypto.X509.load_cert( with open(cert_path) as cert_file:
cert_path).get_fingerprint(md="sha1") cert_data = cert_file.read()
except (IOError, M2Crypto.X509.X509Error): except IOError:
continue 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: if cert_sha1 in csha1_vhlist:
csha1_vhlist[cert_sha1].append(path) csha1_vhlist[cert_sha1].append(path)
else: else:
@@ -246,7 +251,6 @@ class Revoker(object):
""" """
# XXX | pylint: disable=unused-variable # XXX | pylint: disable=unused-variable
# These will both have to change in the future away from M2Crypto
# pylint: disable=protected-access # pylint: disable=protected-access
certificate = jose_util.ComparableX509(cert._cert) certificate = jose_util.ComparableX509(cert._cert)
try: try:
@@ -373,8 +377,8 @@ class Revoker(object):
class Cert(object): class Cert(object):
"""Cert object used for Revocation convenience. """Cert object used for Revocation convenience.
:ivar _cert: M2Crypto X509 cert :ivar _cert: Certificate
:type _cert: :class:`M2Crypto.X509` :type _cert: :class:`OpenSSL.crypto.X509`
:ivar int idx: convenience index used for listing :ivar int idx: convenience index used for listing
:ivar orig: (`str` path - original certificate, `str` status) :ivar orig: (`str` path - original certificate, `str` status)
@@ -402,8 +406,16 @@ class Cert(object):
""" """
try: try:
self._cert = M2Crypto.X509.load_cert(cert_path) with open(cert_path) as cert_file:
except (IOError, M2Crypto.X509.X509Error): 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( raise errors.RevokerError(
"Error loading certificate: %s" % cert_path) "Error loading certificate: %s" % cert_path)
@@ -451,8 +463,11 @@ class Cert(object):
if not os.path.isfile(orig): if not os.path.isfile(orig):
status = Cert.DELETED_MSG status = Cert.DELETED_MSG
else: else:
o_cert = M2Crypto.X509.load_cert(orig) with open(orig) as orig_file:
if self.get_fingerprint() != o_cert.get_fingerprint(md="sha1"): 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 status = Cert.CHANGED_MSG
# Verify original key path # Verify original key path
@@ -472,47 +487,49 @@ class Cert(object):
self.backup_path = backup self.backup_path = backup
self.backup_key_path = backup_key self.backup_key_path = backup_key
# M2Crypto is eventually going to be replaced, hence the reason for _cert
def get_cn(self): def get_cn(self):
"""Get common name.""" """Get common name."""
return self._cert.get_subject().CN return self._cert.get_subject().CN
def get_fingerprint(self): def get_fingerprint(self):
"""Get SHA1 fingerprint.""" """Get SHA1 fingerprint."""
return self._cert.get_fingerprint(md="sha1") return self._cert.digest("sha1")
def get_not_before(self): def get_not_before(self):
"""Get not_valid_before field.""" """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): def get_not_after(self):
"""Get not_valid_after field.""" """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): def get_der(self):
"""Get certificate in der format.""" """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): def get_pub_key(self):
"""Get public key size. """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): def get_san(self):
"""Get subject alternative name if available.""" """Get subject alternative name if available."""
try: # pylint: disable=protected-access
return self._cert.get_ext("subjectAltName").get_value() return ", ".join(crypto_util._pyopenssl_cert_or_req_san(self._cert))
except LookupError:
return ""
def __str__(self): def __str__(self):
text = [ 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(), "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(), "Public Key: %s" % self.get_pub_key(),
"Not Before: %s" % str(self.get_not_before()), "Not Before: %s" % str(self.get_not_before()),
"Not After: %s" % str(self.get_not_after()), "Not After: %s" % str(self.get_not_after()),
+10 -11
View File
@@ -1,12 +1,13 @@
"""Tests for letsencrypt.achallenges.""" """Tests for letsencrypt.achallenges."""
import os import os
import pkg_resources import pkg_resources
import re
import unittest import unittest
import M2Crypto import OpenSSL
from acme import challenges from acme import challenges
from letsencrypt import crypto_util
from letsencrypt import le_util from letsencrypt import le_util
from letsencrypt.tests import acme_util from letsencrypt.tests import acme_util
@@ -31,15 +32,13 @@ class DVSNITest(unittest.TestCase):
def test_gen_cert_and_response(self): def test_gen_cert_and_response(self):
cert_pem, _ = self.achall.gen_cert_and_response(s=self.response.s) cert_pem, _ = self.achall.gen_cert_and_response(s=self.response.s)
cert = M2Crypto.X509.load_cert_string(cert_pem) cert = OpenSSL.crypto.load_certificate(
self.assertEqual(cert.get_subject().CN, self.chall.nonce_domain) OpenSSL.crypto.FILETYPE_PEM, cert_pem)
self.assertEqual(cert.get_subject().CN, "example.com")
sans = cert.get_ext("subjectAltName").get_value() # pylint: disable=protected-access
self.assertEqual( self.assertEqual(crypto_util._pyopenssl_cert_or_req_san(cert), [
set([self.chall.nonce_domain, "example.com", "example.com", self.chall.nonce_domain,
self.response.z_domain(self.chall)]), self.response.z_domain(self.chall)])
set(re.findall(r"DNS:([^, $]*)", sans)),
)
if __name__ == "__main__": if __name__ == "__main__":
+2 -2
View File
@@ -6,7 +6,6 @@ import shutil
import tempfile import tempfile
import unittest import unittest
import M2Crypto
import OpenSSL import OpenSSL
import mock 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 def test_it(self): # pylint: disable=no-self-use
from letsencrypt.crypto_util import make_key from letsencrypt.crypto_util import make_key
# Do not test larger keys as it takes too long. # 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): class ValidPrivkeyTest(unittest.TestCase):
@@ -1,6 +1,7 @@
"""Tests for letsencrypt.proof_of_possession.""" """Tests for letsencrypt.proof_of_possession."""
import os import os
import pkg_resources import pkg_resources
import tempfile
import unittest import unittest
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
@@ -18,9 +19,7 @@ from letsencrypt.display import util as display_util
BASE_PACKAGE = "letsencrypt.tests" BASE_PACKAGE = "letsencrypt.tests"
CERT0_PATH = pkg_resources.resource_filename( CERT0_PATH = pkg_resources.resource_filename(
BASE_PACKAGE, os.path.join("testdata", "cert.pem")) "acme.jose", os.path.join("testdata", "cert.der"))
CERT1_PATH = pkg_resources.resource_filename(
BASE_PACKAGE, os.path.join("testdata", "cert-san.pem"))
CERT2_PATH = pkg_resources.resource_filename( CERT2_PATH = pkg_resources.resource_filename(
BASE_PACKAGE, os.path.join("testdata", "dsa_cert.pem")) BASE_PACKAGE, os.path.join("testdata", "dsa_cert.pem"))
CERT2_KEY_PATH = pkg_resources.resource_filename( CERT2_KEY_PATH = pkg_resources.resource_filename(
@@ -38,7 +37,8 @@ with open(CERT3_KEY_PATH) as cert3_file:
class ProofOfPossessionTest(unittest.TestCase): class ProofOfPossessionTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.installer = mock.MagicMock() 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] keys = [None, None, CERT2_KEY_PATH, CERT3_KEY_PATH]
self.installer.get_all_certs_keys.return_value = zip( self.installer.get_all_certs_keys.return_value = zip(
certs, keys, 4 * [None]) certs, keys, 4 * [None])
@@ -56,6 +56,9 @@ class ProofOfPossessionTest(unittest.TestCase):
self.achall = achallenges.ProofOfPossession( self.achall = achallenges.ProofOfPossession(
challb=challb, domain="example.com") challb=challb, domain="example.com")
def tearDown(self):
os.remove(self.cert1_path)
def test_perform_bad_challenge(self): def test_perform_bad_challenge(self):
hints = challenges.ProofOfPossession.Hints( hints = challenges.ProofOfPossession.Hints(
jwk=jose.jwk.JWKOct(key="foo"), cert_fingerprints=(), jwk=jose.jwk.JWKOct(key="foo"), cert_fingerprints=(),
+20 -3
View File
@@ -267,18 +267,28 @@ class RevokerInstallerTest(RevokerBase):
self.assertEqual( self.assertEqual(
sha_vh[cert.get_fingerprint()], self.installs[i]) sha_vh[cert.get_fingerprint()], self.installs[i])
@mock.patch("letsencrypt.revoker.M2Crypto.X509.load_cert") @mock.patch("letsencrypt.revoker.OpenSSL.crypto.load_certificate")
def test_get_installed_load_failure(self, mock_m2): def test_get_installed_load_failure(self, mock_load_certificate):
mock_installer = mock.MagicMock() mock_installer = mock.MagicMock()
mock_installer.get_all_certs_keys.return_value = self.certs_keys 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) revoker = self._get_revoker(mock_installer)
# pylint: disable=protected-access # pylint: disable=protected-access
self.assertEqual(revoker._get_installed_locations(), {}) 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): class RevokerClassMethodsTest(RevokerBase):
def setUp(self): def setUp(self):
@@ -334,6 +344,13 @@ class CertTest(unittest.TestCase):
from letsencrypt.revoker import Cert from letsencrypt.revoker import Cert
self.assertRaises(errors.RevokerError, Cert, self.key_path) 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): def test_no_row(self):
self.assertEqual(self.certs[0].get_row(), None) self.assertEqual(self.certs[0].get_row(), None)
-67
View File
@@ -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 <module>
# 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 "<stdin>", line 1, in <module>
# 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 <module>
# 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 <module>
# _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.
+1 -5
View File
@@ -53,7 +53,7 @@ letsencrypt_install_requires = [
'argparse', 'argparse',
'ConfigArgParse', 'ConfigArgParse',
'configobj', 'configobj',
'M2Crypto', #'cryptography>=0.7', # load_pem_x509_certificate, version pin mismatch
'mock', 'mock',
'parsedatetime', 'parsedatetime',
'psutil>=2.1.0', # net_connections introduced in 2.1.0 'psutil>=2.1.0', # net_connections introduced in 2.1.0
@@ -64,7 +64,6 @@ letsencrypt_install_requires = [
'pytz', 'pytz',
'zope.component', 'zope.component',
'zope.interface', 'zope.interface',
'M2Crypto',
] ]
letsencrypt_apache_install_requires = [ letsencrypt_apache_install_requires = [
#'acme', #'acme',
@@ -103,9 +102,6 @@ install_requires = [
'werkzeug', 'werkzeug',
'zope.component', 'zope.component',
'zope.interface', '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 ( assert set(install_requires) == set.union(*(set(ireq) for ireq in (