acme: M2Crypto -> pyOpenSSL

This commit is contained in:
Jakub Warmuz
2015-07-06 12:18:17 +00:00
parent e0293d81f3
commit 9197fa6b5c
17 changed files with 151 additions and 80 deletions
+5 -4
View File
@@ -5,8 +5,8 @@ import unittest
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import M2Crypto
import mock
import OpenSSL
import requests
import urlparse
@@ -14,8 +14,8 @@ from acme import jose
from acme import other
CERT = jose.ComparableX509(M2Crypto.X509.load_cert(
pkg_resources.resource_filename(
CERT = jose.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, pkg_resources.resource_string(
'letsencrypt.tests', os.path.join('testdata', 'cert.pem'))))
KEY = jose.ComparableRSAKey(serialization.load_pem_private_key(
pkg_resources.resource_string(
@@ -370,7 +370,8 @@ class ProofOfPossessionHintsTest(unittest.TestCase):
self.jmsg_to = {
'jwk': jwk,
'certFingerprints': cert_fingerprints,
'certs': (jose.b64encode(CERT.as_der()),),
'certs': (jose.b64encode(OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, CERT)),),
'subjectKeyIdentifiers': subject_key_identifiers,
'serialNumbers': serial_numbers,
'issuers': issuers,
+9 -9
View File
@@ -5,7 +5,7 @@ import httplib
import logging
import time
import M2Crypto
import OpenSSL
import requests
import werkzeug
@@ -256,7 +256,7 @@ class Client(object): # pylint: disable=too-many-instance-attributes
"""Request issuance.
:param csr: CSR
:type csr: `M2Crypto.X509.Request` wrapped in `.ComparableX509`
:type csr: `OpenSSL.crypto.X509Req` wrapped in `.ComparableX509`
:param authzrs: `list` of `.AuthorizationResource`
@@ -287,8 +287,8 @@ class Client(object): # pylint: disable=too-many-instance-attributes
return messages.CertificateResource(
uri=uri, authzrs=authzrs, cert_chain_uri=cert_chain_uri,
body=jose.ComparableX509(
M2Crypto.X509.load_cert_der_string(response.content)))
body=jose.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_ASN1, response.content)))
def poll_and_request_issuance(self, csr, authzrs, mintime=5):
"""Poll and request issuance.
@@ -300,7 +300,7 @@ class Client(object): # pylint: disable=too-many-instance-attributes
.. todo:: add `max_attempts` or `timeout`
:param csr: CSR.
:type csr: `M2Crypto.X509.Request` wrapped in `.ComparableX509`
:type csr: `OpenSSL.crypto.X509Req` wrapped in `.ComparableX509`
:param authzrs: `list` of `.AuthorizationResource`
@@ -359,8 +359,8 @@ class Client(object): # pylint: disable=too-many-instance-attributes
content_type = self.DER_CONTENT_TYPE # TODO: make it a param
response = self.net.get(uri, headers={'Accept': content_type},
content_type=content_type)
return response, jose.ComparableX509(
M2Crypto.X509.load_cert_der_string(response.content))
return response, jose.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_ASN1, response.content))
def check_cert(self, certr):
"""Check for new cert.
@@ -403,7 +403,7 @@ class Client(object): # pylint: disable=too-many-instance-attributes
:type certr: `.CertificateResource`
:returns: Certificate chain, or `None` if no "up" Link was provided.
:rtype: `M2Crypto.X509.X509` wrapped in `.ComparableX509`
:rtype: `OpenSSL.crypto.X509` wrapped in `.ComparableX509`
"""
if certr.cert_chain_uri is not None:
@@ -414,7 +414,7 @@ class Client(object): # pylint: disable=too-many-instance-attributes
def revoke(self, cert):
"""Revoke certificate.
:param .ComparableX509 cert: `M2Crypto.X509.X509` wrapped in
:param .ComparableX509 cert: `OpenSSL.crypto.X509` wrapped in
`.ComparableX509`
:raises .ClientError: If revocation is unsuccessful.
+6 -4
View File
@@ -16,6 +16,8 @@ from acme import messages
from acme import messages_test
CERT_DER = pkg_resources.resource_string(
'acme.jose', os.path.join('testdata', 'cert.der'))
KEY = jose.JWKRSA.load(pkg_resources.resource_string(
'acme.jose', os.path.join('testdata', 'rsa512_key.pem')))
KEY2 = jose.JWKRSA.load(pkg_resources.resource_string(
@@ -204,7 +206,7 @@ class ClientTest(unittest.TestCase):
errors.UnexpectedUpdate, self.client.poll, self.authzr)
def test_request_issuance(self):
self.response.content = messages_test.CERT.as_der()
self.response.content = CERT_DER
self.response.headers['Location'] = self.certr.uri
self.response.links['up'] = {'url': self.certr.cert_chain_uri}
self.assertEqual(self.certr, self.client.request_issuance(
@@ -212,7 +214,7 @@ class ClientTest(unittest.TestCase):
# TODO: check POST args
def test_request_issuance_missing_up(self):
self.response.content = messages_test.CERT.as_der()
self.response.content = CERT_DER
self.response.headers['Location'] = self.certr.uri
self.assertEqual(
self.certr.update(cert_chain_uri=None),
@@ -306,7 +308,7 @@ class ClientTest(unittest.TestCase):
def test_check_cert(self):
self.response.headers['Location'] = self.certr.uri
self.response.content = messages_test.CERT.as_der()
self.response.content = CERT_DER
self.assertEqual(self.certr.update(body=messages_test.CERT),
self.client.check_cert(self.certr))
@@ -316,7 +318,7 @@ class ClientTest(unittest.TestCase):
errors.UnexpectedUpdate, self.client.check_cert, self.certr)
def test_check_cert_missing_location(self):
self.response.content = messages_test.CERT.as_der()
self.response.content = CERT_DER
self.assertRaises(
errors.ClientError, self.client.check_cert, self.certr)
+11 -9
View File
@@ -10,7 +10,7 @@ import abc
import binascii
import logging
import M2Crypto
import OpenSSL
from acme.jose import b64
from acme.jose import errors
@@ -321,26 +321,28 @@ def encode_cert(cert):
:type cert: :class:`acme.jose.util.ComparableX509`
"""
return b64.b64encode(cert.as_der())
return b64.b64encode(OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, cert))
def decode_cert(b64der):
"""Decode JOSE Base-64 DER-encoded certificate."""
try:
return util.ComparableX509(M2Crypto.X509.load_cert_der_string(
decode_b64jose(b64der)))
except M2Crypto.X509.X509Error as error:
return util.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_ASN1, decode_b64jose(b64der)))
except OpenSSL.crypto.Error as error:
raise errors.DeserializationError(error)
def encode_csr(csr):
"""Encode CSR as JOSE Base-64 DER."""
return encode_cert(csr)
return b64.b64encode(OpenSSL.crypto.dump_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, csr))
def decode_csr(b64der):
"""Decode JOSE Base-64 DER-encoded CSR."""
try:
return util.ComparableX509(M2Crypto.X509.load_request_der_string(
decode_b64jose(b64der)))
except M2Crypto.X509.X509Error as error:
return util.ComparableX509(OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, decode_b64jose(b64der)))
except OpenSSL.crypto.Error as error:
raise errors.DeserializationError(error)
+8 -6
View File
@@ -4,18 +4,20 @@ import os
import pkg_resources
import unittest
import M2Crypto
import mock
import OpenSSL
from acme.jose import errors
from acme.jose import interfaces
from acme.jose import util
CERT = M2Crypto.X509.load_cert(pkg_resources.resource_filename(
'letsencrypt.tests', os.path.join('testdata', 'cert.pem')))
CSR = M2Crypto.X509.load_request(pkg_resources.resource_filename(
'letsencrypt.tests', os.path.join('testdata', 'csr.pem')))
CERT = OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, pkg_resources.resource_string(
'letsencrypt.tests', os.path.join('testdata', 'cert.pem')))
CSR = OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, pkg_resources.resource_string(
'letsencrypt.tests', os.path.join('testdata', 'csr.pem')))
class FieldTest(unittest.TestCase):
@@ -280,7 +282,7 @@ class DeEncodersTest(unittest.TestCase):
def test_encode_csr(self):
from acme.jose.json_util import encode_csr
self.assertEqual(self.b64_cert, encode_csr(CERT))
self.assertEqual(self.b64_csr, encode_csr(CSR))
def test_decode_csr(self):
from acme.jose.json_util import decode_csr
+6 -4
View File
@@ -3,7 +3,7 @@ import argparse
import base64
import sys
import M2Crypto
import OpenSSL
from acme.jose import b64
from acme.jose import errors
@@ -122,14 +122,16 @@ class Header(json_util.JSONObjectWithFields):
@x5c.encoder
def x5c(value): # pylint: disable=missing-docstring,no-self-argument
return [base64.b64encode(cert.as_der()) for cert in value]
return [base64.b64encode(OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, cert)) for cert in value]
@x5c.decoder
def x5c(value): # pylint: disable=missing-docstring,no-self-argument
try:
return tuple(util.ComparableX509(M2Crypto.X509.load_cert_der_string(
return tuple(util.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_ASN1,
base64.b64decode(cert))) for cert in value)
except M2Crypto.X509.X509Error as error:
except OpenSSL.crypto.Error as error:
raise errors.DeserializationError(error)
+8 -5
View File
@@ -6,8 +6,8 @@ import unittest
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import M2Crypto
import mock
import OpenSSL
from acme.jose import b64
from acme.jose import errors
@@ -16,8 +16,8 @@ from acme.jose import jwk
from acme.jose import util
CERT = util.ComparableX509(M2Crypto.X509.load_cert(
pkg_resources.resource_filename(
CERT = util.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, pkg_resources.resource_string(
'letsencrypt.tests', 'testdata/cert.pem')))
RSA512_KEY = util.ComparableRSAKey(serialization.load_pem_private_key(
pkg_resources.resource_string(
@@ -76,10 +76,13 @@ class HeaderTest(unittest.TestCase):
from acme.jose.jws import Header
header = Header(x5c=(CERT, CERT))
jobj = header.to_partial_json()
cert_b64 = base64.b64encode(CERT.as_der())
cert_b64 = base64.b64encode(OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, CERT))
self.assertEqual(jobj, {'x5c': [cert_b64, cert_b64]})
self.assertEqual(header, Header.from_json(jobj))
jobj['x5c'][0] = base64.b64encode('xxx' + CERT.as_der())
jobj['x5c'][0] = base64.b64encode(
'xxx' + OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_ASN1, CERT))
self.assertRaises(errors.DeserializationError, Header.from_json, jobj)
def test_find_key(self):
+21 -4
View File
@@ -2,6 +2,7 @@
import collections
from cryptography.hazmat.primitives.asymmetric import rsa
import OpenSSL
class abstractclassmethod(classmethod):
@@ -25,12 +26,12 @@ class abstractclassmethod(classmethod):
class ComparableX509(object): # pylint: disable=too-few-public-methods
"""Wrapper for M2Crypto.X509.* objects that supports __eq__.
"""Wrapper for OpenSSL.crypto.X509** objects that supports __eq__.
Wraps around:
- :class:`M2Crypto.X509.X509`
- :class:`M2Crypto.X509.Request`
- :class:`OpenSSL.crypto.X509`
- :class:`OpenSSL.crypto.X509Req`
"""
def __init__(self, wrapped):
@@ -40,7 +41,23 @@ class ComparableX509(object): # pylint: disable=too-few-public-methods
return getattr(self._wrapped, name)
def __eq__(self, other):
return self.as_der() == other.as_der()
filetype = OpenSSL.crypto.FILETYPE_ASN1
def as_der(obj):
# pylint: disable=missing-docstring,protected-access
if isinstance(obj, type(self)):
obj = obj._wrapped
if isinstance(obj, OpenSSL.crypto.X509):
func = OpenSSL.crypto.dump_certificate
elif isinstance(obj, OpenSSL.crypto.X509Req):
func = OpenSSL.crypto.dump_certificate_request
else:
raise TypeError(
"Equality for {0} not provided".format(obj.__class__))
return func(filetype, obj)
return as_der(self) == as_der(other)
def __repr__(self):
return '<{0}({1!r})>'.format(self.__class__.__name__, self._wrapped)
class ComparableRSAKey(object): # pylint: disable=too-few-public-methods
+32
View File
@@ -6,6 +6,38 @@ import unittest
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import OpenSSL
class ComparableX509Test(unittest.TestCase):
"""Tests for acme.jose.util.ComparableX509."""
def setUp(self):
from acme.jose.util import ComparableX509
def load_cert(): # pylint: disable=missing-docstring
return ComparableX509(OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, pkg_resources.resource_string(
__name__, os.path.join('testdata', 'csr.der'))))
self.cert = load_cert()
self.cert_same = load_cert()
self.cert2 = ComparableX509(OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, pkg_resources.resource_string(
'letsencrypt.tests', os.path.join('testdata', 'csr-san.pem'))))
def test_eq(self):
self.assertEqual(self.cert, self.cert_same)
def test_not_eq(self):
self.assertNotEqual(self.cert, self.cert2)
def test_eq_wrong_types(self):
from acme.jose.util import ComparableX509
self.assertRaises(
TypeError, ComparableX509(5).__eq__, ComparableX509(5))
def test_repr(self):
self.assertTrue(repr(self.cert).startswith(
'<ComparableX509(<OpenSSL.crypto.X509'))
class ComparableRSAKeyTest(unittest.TestCase):
+3 -3
View File
@@ -312,7 +312,7 @@ class CertificateRequest(jose.JSONObjectWithFields):
"""ACME new-cert request.
:ivar acme.jose.util.ComparableX509 csr:
`M2Crypto.X509.Request` wrapped in `.ComparableX509`
`OpenSSL.crypto.X509Req` wrapped in `.ComparableX509`
:ivar tuple authorizations: `tuple` of URIs (`str`)
"""
@@ -324,7 +324,7 @@ class CertificateResource(ResourceWithURI):
"""Certificate Resource.
:ivar acme.jose.util.ComparableX509 body:
`M2Crypto.X509.X509` wrapped in `.ComparableX509`
`OpenSSL.crypto.X509` wrapped in `.ComparableX509`
:ivar str cert_chain_uri: URI found in the 'up' ``Link`` header
:ivar tuple authzrs: `tuple` of `AuthorizationResource`.
@@ -336,7 +336,7 @@ class CertificateResource(ResourceWithURI):
class Revocation(jose.JSONObjectWithFields):
"""Revocation message.
:ivar .ComparableX509 certificate: `M2Crypto.X509.X509` wrapped in
:ivar .ComparableX509 certificate: `OpenSSL.crypto.X509` wrapped in
`.ComparableX509`
"""
+9 -11
View File
@@ -5,27 +5,25 @@ import unittest
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import M2Crypto
import mock
import OpenSSL
from acme import challenges
from acme import jose
CERT = jose.ComparableX509(M2Crypto.X509.load_cert_string(
pkg_resources.resource_string(
'acme.jose', os.path.join('testdata', 'cert.der')),
M2Crypto.X509.FORMAT_DER))
CSR = jose.ComparableX509(M2Crypto.X509.load_request_string(
pkg_resources.resource_string(
'acme.jose', os.path.join('testdata', 'csr.der')),
M2Crypto.X509.FORMAT_DER))
CERT = jose.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_ASN1, pkg_resources.resource_string(
'acme.jose', os.path.join('testdata', 'cert.der'))))
CSR = jose.ComparableX509(OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, pkg_resources.resource_string(
'acme.jose', os.path.join('testdata', 'csr.der'))))
KEY = jose.util.ComparableRSAKey(serialization.load_pem_private_key(
pkg_resources.resource_string(
'acme.jose', os.path.join('testdata', 'rsa512_key.pem')),
password=None, backend=default_backend()))
CERT = jose.ComparableX509(M2Crypto.X509.load_cert(
format=M2Crypto.X509.FORMAT_DER, file=pkg_resources.resource_filename(
CERT = jose.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_ASN1, pkg_resources.resource_string(
'acme.jose', os.path.join('testdata', 'cert.der'))))
+4 -4
View File
@@ -5,7 +5,7 @@ import pkg_resources
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
import M2Crypto
import OpenSSL
from acme import client
from acme import messages
@@ -39,9 +39,9 @@ logging.debug(authzr)
authzr, authzr_response = acme.poll(authzr)
csr = M2Crypto.X509.load_request_string(pkg_resources.resource_string(
'acme.jose', os.path.join('testdata', 'csr.der')),
M2Crypto.X509.FORMAT_DER)
csr = OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, pkg_resources.resource_string(
'acme.jose', os.path.join('testdata', 'csr.der')))
try:
acme.request_issuance(csr, (authzr,))
except messages.Error as error:
+11 -7
View File
@@ -157,8 +157,8 @@ class Client(object):
authzr = self.auth_handler.get_authorizations(domains)
certr = self.network.request_issuance(
jose.ComparableX509(
M2Crypto.X509.load_request_der_string(csr.data)),
jose.ComparableX509(OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, csr.data)),
authzr)
return certr, self.network.fetch_chain(certr)
@@ -247,10 +247,12 @@ class Client(object):
# XXX: just to stop RenewableCert from complaining; this is
# probably not a good solution
chain_pem = "" if chain is None else chain.as_pem()
chain_pem = "" if chain is None else OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, chain)
lineage = storage.RenewableCert.new_lineage(
domains[0], certr.body.as_pem(), key.pem, chain_pem, params,
config, cli_config)
domains[0], OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, certr.body),
key.pem, chain_pem, params, config, cli_config)
self._report_renewal_status(lineage)
return lineage
@@ -306,7 +308,8 @@ class Client(object):
cert_chain_abspath = None
cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644)
# TODO: Except
cert_pem = certr.body.as_pem()
cert_pem = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, certr.body)
try:
cert_file.write(cert_pem)
finally:
@@ -318,7 +321,8 @@ class Client(object):
chain_file, act_chain_path = le_util.unique_file(
chain_path, 0o644)
# TODO: Except
chain_pem = chain_cert.as_pem()
chain_pem = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, chain_cert)
try:
chain_file.write(chain_pem)
finally:
+6 -2
View File
@@ -12,6 +12,7 @@ import os
import sys
import configobj
import OpenSSL
import zope.component
from letsencrypt import configuration
@@ -90,8 +91,11 @@ def renew(cert, old_version):
# best is to have obtain_certificate return None for
# 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.as_pem(),
new_key.pem, new_chain.as_pem())
return cert.save_successor(
old_version, OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, new_certr.body),
new_key.pem, OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, new_chain))
# TODO: Notify results
else:
# TODO: Notify negative results
+3 -3
View File
@@ -6,7 +6,7 @@ import shutil
import tempfile
import configobj
import M2Crypto.X509
import OpenSSL
import mock
from acme import jose
@@ -51,8 +51,8 @@ class ClientTest(unittest.TestCase):
self.client.auth_handler.get_authorizations.assert_called_once_with(
["example.com", "www.example.com"])
self.network.request_issuance.assert_callend_once_with(
jose.ComparableX509(
M2Crypto.X509.load_request_der_string(CSR_SAN)),
jose.ComparableX509(OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, CSR_SAN)),
self.client.auth_handler.get_authorizations())
self.network().fetch_chain.assert_called_once_with(mock.sentinel.certr)
+8 -4
View File
@@ -8,12 +8,18 @@ import unittest
import configobj
import mock
import OpenSSL
import pytz
from letsencrypt import configuration
from letsencrypt.storage import ALL_FOUR
CERT = OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, pkg_resources.resource_string(
'letsencrypt.tests', os.path.join('testdata', 'cert.pem')))
def unlink_all(rc_object):
"""Unlink all four items associated with this RenewableCert."""
for kind in ALL_FOUR:
@@ -553,7 +559,6 @@ class RenewableCertTests(unittest.TestCase):
@mock.patch("letsencrypt.client.determine_account")
@mock.patch("letsencrypt.client.Client")
def test_renew(self, mock_c, mock_da, mock_pd):
"""Tests for renew()."""
from letsencrypt import renewer
test_cert = pkg_resources.resource_string(
@@ -583,9 +588,8 @@ class RenewableCertTests(unittest.TestCase):
mock_client = mock.MagicMock()
# pylint: disable=star-args
mock_client.obtain_certificate.return_value = (
mock.Mock(**{'body.as_pem.return_value': 'cert'}),
mock.Mock(**{'as_pem.return_value': 'chain'}),
mock.Mock(pem="key"), mock.sentinel.csr)
mock.MagicMock(body=CERT), 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
+1 -1
View File
@@ -43,10 +43,10 @@ acme_install_requires = [
'pyrfc3339',
'ndg-httpsclient', # urllib3 InsecurePlatformWarning (#304)
'pyasn1', # urllib3 InsecurePlatformWarning (#304)
#'PyOpenSSL', # version pin would cause mismatch
'pytz',
'requests',
'werkzeug',
'M2Crypto',
]
letsencrypt_install_requires = [
#'acme',