mirror of
https://github.com/certbot/certbot.git
synced 2026-07-28 08:45:19 +02:00
Drop M2Crypto
This commit is contained in:
@@ -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 \
|
||||
|
||||
@@ -12,7 +12,6 @@ yum install -y \
|
||||
python-virtualenv \
|
||||
python-devel \
|
||||
gcc \
|
||||
swig \
|
||||
dialog \
|
||||
augeas-libs \
|
||||
openssl-devel \
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
#!/bin/sh
|
||||
brew install augeas swig
|
||||
brew install augeas
|
||||
|
||||
+1
-1
@@ -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__))
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@ are provided mainly for the :ref:`developers <hacking>` 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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
+96
-62
@@ -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())
|
||||
|
||||
@@ -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()]
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
+41
-24
@@ -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()),
|
||||
|
||||
@@ -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__":
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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=(),
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user