diff --git a/certbot/cert_manager.py b/certbot/cert_manager.py index 35d539a16..6519e7068 100644 --- a/certbot/cert_manager.py +++ b/certbot/cert_manager.py @@ -6,6 +6,7 @@ import pytz import traceback import zope.component +from certbot import crypto_util from certbot import errors from certbot import interfaces from certbot import ocsp @@ -73,6 +74,7 @@ def certificates(config): for renewal_file in storage.renewal_conf_files(config): try: renewal_candidate = storage.RenewableCert(renewal_file, config) + crypto_util.verify_renewable_cert(renewal_candidate) parsed_certs.append(renewal_candidate) except Exception as e: # pylint: disable=broad-except logger.warning("Renewal configuration file %s produced an " diff --git a/certbot/crypto_util.py b/certbot/crypto_util.py index 2b2e7d0d8..0ac0eee40 100644 --- a/certbot/crypto_util.py +++ b/certbot/crypto_util.py @@ -12,6 +12,8 @@ import OpenSSL import pyrfc3339 import six import zope.component +from cryptography.hazmat.backends import default_backend +from cryptography import x509 from acme import crypto_util as acme_crypto_util from acme import jose @@ -206,6 +208,95 @@ def valid_privkey(privkey): return False +def verify_renewable_cert(renewable_cert): + """For checking that your certs were not corrupted on disk. + + Several things are checked: + 1. Signature verification for the cert. + 2. That fullchain matches cert and chain when concatenated. + 3. Check that the private key matches the certificate. + + :param `.storage.RenewableCert` renewable_cert: cert to verify + + :raises errors.Error: If verification fails. + """ + verify_renewable_cert_sig(renewable_cert) + verify_fullchain(renewable_cert) + verify_cert_matches_priv_key(renewable_cert) + + +def verify_renewable_cert_sig(renewable_cert): + """ Verifies the signature of a `.storage.RenewableCert` object. + + :param `.storage.RenewableCert` renewable_cert: cert to verify + + :raises errors.Error: If signature verification fails. + """ + try: + with open(renewable_cert.chain, 'rb') as chain: + chain, _ = pyopenssl_load_certificate(chain.read()) + with open(renewable_cert.cert, 'rb') as cert: + cert = x509.load_pem_x509_certificate(cert.read(), default_backend()) + hash_name = cert.signature_hash_algorithm.name + OpenSSL.crypto.verify(chain, cert.signature, cert.tbs_certificate_bytes, hash_name) + except (IOError, ValueError, OpenSSL.crypto.Error) as e: + error_str = "verifying the signature of the cert located at {0} has failed. \ + Details: {1}".format(renewable_cert.cert, e) + logger.exception(error_str) + raise errors.Error(error_str) + + +def verify_cert_matches_priv_key(renewable_cert): + """ Verifies that the private key and cert match. + + :param `.storage.RenewableCert` renewable_cert: cert to verify + + :raises errors.Error: If they don't match. + """ + try: + with open(renewable_cert.cert) as cert: + cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert.read()) + with open(renewable_cert.privkey) as privkey: + privkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey.read()) + context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) + context.use_privatekey(privkey) + context.use_certificate(cert) + context.check_privatekey() + except (IOError, OpenSSL.SSL.Error) as e: + error_str = "verifying the cert located at {0} matches the \ + private key located at {1} has failed. \ + Details: {2}".format(renewable_cert.cert, + renewable_cert.privkey, e) + logger.exception(error_str) + raise errors.Error(error_str) + + +def verify_fullchain(renewable_cert): + """ Verifies that fullchain is indeed cert concatenated with chain. + + :param `.storage.RenewableCert` renewable_cert: cert to verify + + :raises errors.Error: If cert and chain do not combine to fullchain. + """ + try: + with open(renewable_cert.chain) as chain: + chain = chain.read() + with open(renewable_cert.cert) as cert: + cert = cert.read() + with open(renewable_cert.fullchain) as fullchain: + fullchain = fullchain.read() + if (cert + chain) != fullchain: + error_str = "fullchain does not match cert + chain for {0}!" + error_str = error_str.format(renewable_cert.lineagename) + raise errors.Error(error_str) + except IOError as e: + error_str = "reading one of cert, chain, or fullchain has failed: {0}".format(e) + logger.exception(error_str) + raise errors.Error(error_str) + except errors.Error as e: + raise e + + def pyopenssl_load_certificate(data): """Load PEM/DER certificate. @@ -340,6 +431,7 @@ def _notAfterBefore(cert_path, method): :rtype: :class:`datetime.datetime` """ + # pylint: disable=redefined-outer-name with open(cert_path) as f: x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, f.read()) diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index b5731fbd6..f4a108019 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -149,12 +149,14 @@ class CertificatesTest(BaseCertManagerTest): self.assertFalse(mock_utility.notification.called) self.assertTrue(mock_logger.warning.called) #pylint: disable=no-member + @mock.patch('certbot.crypto_util.verify_renewable_cert') @mock.patch('certbot.cert_manager.logger') @test_util.patch_get_utility() @mock.patch("certbot.storage.RenewableCert") @mock.patch('certbot.cert_manager._report_human_readable') def test_certificates_parse_success(self, mock_report, mock_renewable_cert, - mock_utility, mock_logger): + mock_utility, mock_logger, mock_verifier): + mock_verifier.return_value = None mock_report.return_value = "" self._certificates(self.cli_config) self.assertFalse(mock_logger.warning.called) #pylint: disable=no-member diff --git a/certbot/tests/crypto_util_test.py b/certbot/tests/crypto_util_test.py index c678dc501..ea5a81062 100644 --- a/certbot/tests/crypto_util_test.py +++ b/certbot/tests/crypto_util_test.py @@ -14,11 +14,14 @@ import certbot.tests.util as test_util RSA256_KEY = test_util.load_vector('rsa256_key.pem') +RSA256_KEY_PATH = test_util.vector_path('rsa256_key.pem') RSA512_KEY = test_util.load_vector('rsa512_key.pem') +RSA2048_KEY_PATH = test_util.vector_path('rsa2048_key.pem') CERT_PATH = test_util.vector_path('cert.pem') CERT = test_util.load_vector('cert.pem') SAN_CERT = test_util.load_vector('cert-san.pem') - +SS_CERT_PATH = test_util.vector_path('self_signed_cert.pem') +SS_CERT = test_util.load_vector('self_signed_cert.pem') class InitSaveKeyTest(test_util.TempDirTestCase): """Tests for certbot.crypto_util.init_save_key.""" @@ -194,6 +197,101 @@ class MakeKeyTest(unittest.TestCase): # pylint: disable=too-few-public-methods OpenSSL.crypto.FILETYPE_PEM, make_key(1024)) +class VerifyCertSetup(unittest.TestCase): + """Refactoring for verification tests.""" + + def setUp(self): + super(VerifyCertSetup, self).setUp() + + self.renewable_cert = mock.MagicMock() + self.renewable_cert.cert = SS_CERT_PATH + self.renewable_cert.chain = SS_CERT_PATH + self.renewable_cert.privkey = RSA2048_KEY_PATH + self.renewable_cert.fullchain = test_util.vector_path('self_signed_fullchain.pem') + + self.bad_renewable_cert = mock.MagicMock() + self.bad_renewable_cert.chain = SS_CERT_PATH + self.bad_renewable_cert.cert = SS_CERT_PATH + self.bad_renewable_cert.fullchain = SS_CERT_PATH + + +class VerifyRenewableCertTest(VerifyCertSetup): + """Tests for certbot.crypto_util.verify_renewable_cert.""" + + def setUp(self): + super(VerifyRenewableCertTest, self).setUp() + + def _call(self, renewable_cert): + from certbot.crypto_util import verify_renewable_cert + return verify_renewable_cert(renewable_cert) + + def test_verify_renewable_cert(self): + self.assertEqual(None, self._call(self.renewable_cert)) + + @mock.patch('certbot.crypto_util.verify_renewable_cert_sig', side_effect=errors.Error("")) + def test_verify_renewable_cert_failure(self, unused_verify_renewable_cert_sign): + self.assertRaises(errors.Error, self._call, self.bad_renewable_cert) + + +class VerifyRenewableCertSigTest(VerifyCertSetup): + """Tests for certbot.crypto_util.verify_renewable_cert.""" + + def setUp(self): + super(VerifyRenewableCertSigTest, self).setUp() + + def _call(self, renewable_cert): + from certbot.crypto_util import verify_renewable_cert_sig + return verify_renewable_cert_sig(renewable_cert) + + def test_cert_sig_match(self): + self.assertEqual(None, self._call(self.renewable_cert)) + + def test_cert_sig_mismatch(self): + self.bad_renewable_cert.cert = test_util.vector_path('self_signed_cert_bad.pem') + self.assertRaises(errors.Error, self._call, self.bad_renewable_cert) + + +class VerifyFullchainTest(VerifyCertSetup): + """Tests for certbot.crypto_util.verify_fullchain.""" + + def setUp(self): + super(VerifyFullchainTest, self).setUp() + + def _call(self, renewable_cert): + from certbot.crypto_util import verify_fullchain + return verify_fullchain(renewable_cert) + + def test_fullchain_matches(self): + self.assertEqual(None, self._call(self.renewable_cert)) + + def test_fullchain_mismatch(self): + self.assertRaises(errors.Error, self._call, self.bad_renewable_cert) + + def test_fullchain_ioerror(self): + self.bad_renewable_cert.chain = "dog" + self.assertRaises(errors.Error, self._call, self.bad_renewable_cert) + + +class VerifyCertMatchesPrivKeyTest(VerifyCertSetup): + """Tests for certbot.crypto_util.verify_cert_matches_priv_key.""" + + def setUp(self): + super(VerifyCertMatchesPrivKeyTest, self).setUp() + + def _call(self, renewable_cert): + from certbot.crypto_util import verify_cert_matches_priv_key + return verify_cert_matches_priv_key(renewable_cert) + + def test_cert_priv_key_match(self): + self.assertEqual(None, self._call(self.renewable_cert)) + + def test_cert_priv_key_mismatch(self): + self.bad_renewable_cert.privkey = RSA256_KEY_PATH + self.bad_renewable_cert.cert = SS_CERT_PATH + + self.assertRaises(errors.Error, self._call, self.bad_renewable_cert) + + class ValidPrivkeyTest(unittest.TestCase): """Tests for certbot.crypto_util.valid_privkey.""" diff --git a/certbot/tests/testdata/rsa2048_key.pem b/certbot/tests/testdata/rsa2048_key.pem new file mode 100644 index 000000000..33a68c74d --- /dev/null +++ b/certbot/tests/testdata/rsa2048_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDm1WIecnHjL4Fs +JvxDP27GyeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop ++D7s+oh0apV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3 +DaNokGn7r3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf +5QU5pFx60a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDV +MJ3mIB8FOW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf +3nCJFk1fAgMBAAECggEAJkhbVntagfgd+cbZbXm2sIdKQGlwXk92/Zxd3tZMcuNY +rU+/C2bJ5uTEm+0R/V9f3FXlsCagGde2t7ExFnJScSRAGCuFRxudMMI/wNvUvnpR +O9vN3HxrRo2rZqBkqHIZCR0d2Bxs/0cvGqTLZgsVWKV4xM07TThcE7DtvsNGegRn +WFxfsRcRypkIvZoba1HagvCituRBEa07R7mQp8kRhP9ZeRq3bZws9qBmqzj1cylG +q8QA4Foq7sK8P78bpIhrcOFBDAr+Vr1ZGY6u01J0w13MUtl6iIx4VCjQKt4NkzsK +dj2q+GAMwhReR2ZS42o8LiyGpwusj+dKIFfFekgK2QKBgQD4wwmRDgvt85brQTNF +Tkhui0eToz5oXt8mVDb58nwkpojFQOv87ZyNsEqm7S0t/3RtEViVio2aymTMsrz4 +21vRq46dvhINQ3DoMok6xIchEOEgMeonOilkURWtrMjD/Kn297Asv7zOqI5BCNiP +3FFcRqf+CaqbhnOgMkcI5z6b7QKBgQDtjM1otFFHyS7ctyLRuMeFyxWUSbWHvi8U +xjUW256c6wpQ2DBLSVB61VQjfrSjkZ5DJVFGnbw42HxSDafL11mzTbY1vDbgtgLK +YiuVHG7OYZJTLaZoM68BseX4xHN8FztnvvP1ttuk5oFb+vD8q6ODZSEawRd3PvtX +D7RtNouc+wKBgQDiwBWGTUF+gt18T5BGilbnvLlf0Btg06mgrH74UpnqZoqhEs6J +XKWpWZqSkfruxL4BdSBEH2l4QSiklgA+7uTBOBnlm42k3WaboQUJtn5eG5651AXV +/+Qe9vJFvwu56iObZKcIAzY9QdN5YHDWoULgU99pZrJG1cWrrmilqvOc+QKBgQCB +iOslslY0N+926eJxzDn4qkJtJzh2+e1AfcjLWx0F4mEwroK/Ow5IvPVxmZE1NJ3B +baMBR9gwg1RfhhS+4gKG9NRsPuMJ7BZfd+LeH7AImEorU1RPtAc1fGW0HqP+wchi +DU2I6pqhNBTMLG2myo2Sg93mce6y1sRFuEmh2EGPawKBgQC3uUEdjQekXaxXfYHi +1Dk3Ht1a9t8XxwoCVRqicE7lqlwDtS2y9lHAeUP7JNy8ZGNjx8srRZpkYVMztugo +Ecw26UA7FbNqJP5OPkGjfiFqtOq70h9vlfLdiAPmoqyOx//RkgiNXt9m5xcDzzdB +7EtBK59KSiQkB8fHtooy7Ipiiw== +-----END PRIVATE KEY----- diff --git a/certbot/tests/testdata/self_signed_cert.pem b/certbot/tests/testdata/self_signed_cert.pem new file mode 100644 index 000000000..e02f18e91 --- /dev/null +++ b/certbot/tests/testdata/self_signed_cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIJAIYLtIQHBBG0MA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV +BAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwHVG9yb250bzEMMAoGA1UECgwD +RUZGMB4XDTE3MDUyOTA3NDIwMVoXDTQ4MDMzMDA3NDIwMVowOjELMAkGA1UEBhMC +Q0ExCzAJBgNVBAgMAk9OMRAwDgYDVQQHDAdUb3JvbnRvMQwwCgYDVQQKDANFRkYw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm1WIecnHjL4FsJvxDP27G +yeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop+D7s+oh0 +apV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3DaNokGn7 +r3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf5QU5pFx6 +0a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDVMJ3mIB8F +OW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf3nCJFk1f +AgMBAAGjUzBRMB0GA1UdDgQWBBSdJ++M23AW3LkFD7LKhsH7gL6/2jAfBgNVHSME +GDAWgBSdJ++M23AW3LkFD7LKhsH7gL6/2jAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBCwUAA4IBAQCV5kSt1HTFzUPdBvxT455YrLd3jIsRt1pRNuGjVaUYIRxh +vds8NN1Z8h/8Cdzz8NVkIdCuYb2lFaDjs3zNVUQxCyVcH7xVyPwFI85NR27+HPRv +xzz2rwzST+NKYst6ZBg086BKjqFtxs16lpU/TD6tOJqg86TBbfP6gib/ocGeER2D +HEEik69FjmUCziT6uXyYW5y1PxD15UWO3RWoTpao0vGtTPceTeeuO05PVeCUlx8X +YXg9zoVWBba0GF+qQJ67zT5nvfc2KJcgnWRIRr/90YXzBf+FdFVuC4xFHINBI1OJ +5XBLJOv61Zu+Du/nmlBVcb8KL/Vd2oZyfoH+0oCN +-----END CERTIFICATE----- diff --git a/certbot/tests/testdata/self_signed_cert_bad.pem b/certbot/tests/testdata/self_signed_cert_bad.pem new file mode 100644 index 000000000..d868dc445 --- /dev/null +++ b/certbot/tests/testdata/self_signed_cert_bad.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICYzCCAg2gAwIBAgIJAPvqv4TcAtuFMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD +VQQGEwJDQTEQMA4GA1UECAwHT250YXJpbzEQMA4GA1UEBwwHVG9yb250bzEMMAoG +A1UECgwDRUZGMRYwFAYDVQQLDA1UZWNoIFByb2plY3RzMQ4wDAYDVQQDDAVZb21u +YTEjMCEGCSqGSIb3DQEJARYUeW9tbmEubmFzc2VyQGVmZi5vcmcwHhcNMTcwMzI0 +MjIzMjUxWhcNNDgwMTI0MjIzMjUxWjCBjDELMAkGA1UEBhMCQ0ExEDAOBgNVBAgM +B09udGFyaW8xEDAOBgNVBAcMB1Rvcm9udG8xDDAKBgNVBAoMA0VGRjEWMBQGA1UE +CwwNVGVjaCBQcm9qZWN0czEOMAwGA1UEAwwFWW9tbmExIzAhBgkqhkiG9w0BCQEW +FHlvbW5hLm5hc3NlckBlZmYub3JnMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1 +c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvO +jm0c+pVE6K+EdE/twuUCAwEAAaNQME4wHQYDVR0OBBYEFCbPyO1L15Sy5CUDWCSP +BMB01ZeKMB8GA1UdIwQYMBaAFCbPyO1L15Sy5CUDWCSPBMB01ZeKMAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQELBQADQQAeWDdcrJOolFHr3m8TrlDJ/Ca4SfJya2jb +K1wahbX83sC42834HbDOQASGBhoLYDhC1cMPbKDDjMbR9rjYuf7T +-----END CERTIFICATE----- diff --git a/certbot/tests/testdata/self_signed_fullchain.pem b/certbot/tests/testdata/self_signed_fullchain.pem new file mode 100644 index 000000000..422d221fb --- /dev/null +++ b/certbot/tests/testdata/self_signed_fullchain.pem @@ -0,0 +1,40 @@ +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIJAIYLtIQHBBG0MA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV +BAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwHVG9yb250bzEMMAoGA1UECgwD +RUZGMB4XDTE3MDUyOTA3NDIwMVoXDTQ4MDMzMDA3NDIwMVowOjELMAkGA1UEBhMC +Q0ExCzAJBgNVBAgMAk9OMRAwDgYDVQQHDAdUb3JvbnRvMQwwCgYDVQQKDANFRkYw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm1WIecnHjL4FsJvxDP27G +yeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop+D7s+oh0 +apV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3DaNokGn7 +r3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf5QU5pFx6 +0a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDVMJ3mIB8F +OW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf3nCJFk1f +AgMBAAGjUzBRMB0GA1UdDgQWBBSdJ++M23AW3LkFD7LKhsH7gL6/2jAfBgNVHSME +GDAWgBSdJ++M23AW3LkFD7LKhsH7gL6/2jAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBCwUAA4IBAQCV5kSt1HTFzUPdBvxT455YrLd3jIsRt1pRNuGjVaUYIRxh +vds8NN1Z8h/8Cdzz8NVkIdCuYb2lFaDjs3zNVUQxCyVcH7xVyPwFI85NR27+HPRv +xzz2rwzST+NKYst6ZBg086BKjqFtxs16lpU/TD6tOJqg86TBbfP6gib/ocGeER2D +HEEik69FjmUCziT6uXyYW5y1PxD15UWO3RWoTpao0vGtTPceTeeuO05PVeCUlx8X +YXg9zoVWBba0GF+qQJ67zT5nvfc2KJcgnWRIRr/90YXzBf+FdFVuC4xFHINBI1OJ +5XBLJOv61Zu+Du/nmlBVcb8KL/Vd2oZyfoH+0oCN +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIJAIYLtIQHBBG0MA0GCSqGSIb3DQEBCwUAMDoxCzAJBgNV +BAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwHVG9yb250bzEMMAoGA1UECgwD +RUZGMB4XDTE3MDUyOTA3NDIwMVoXDTQ4MDMzMDA3NDIwMVowOjELMAkGA1UEBhMC +Q0ExCzAJBgNVBAgMAk9OMRAwDgYDVQQHDAdUb3JvbnRvMQwwCgYDVQQKDANFRkYw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm1WIecnHjL4FsJvxDP27G +yeqnXKc41HsRP9cv4z+NDjE94mDgva5ndieiA9xZ0Sh7LXtZcGDcpGop+D7s+oh0 +apV6idIJ9eEPUegYlGxOFJQnZ8re6hD7MaAlNZEVhZrwJvrGy6rTFpi3DaNokGn7 +r3s2nrQ9aziljkWRp1PnTBnRNgOdi3c1IB2f4+2PdykjihxlnYUuI4Wf5QU5pFx6 +0a2mdTVDC+bKAP22IvuQnnkHgJYYS/oMxFCT9QR4xQRPOx7U2RWVrFDVMJ3mIB8F +OW6JXfQSmaZZr46xclbEIr4QQ6RcPWvcJ1cCV1idFjEmufi52sV7r1Bf3nCJFk1f +AgMBAAGjUzBRMB0GA1UdDgQWBBSdJ++M23AW3LkFD7LKhsH7gL6/2jAfBgNVHSME +GDAWgBSdJ++M23AW3LkFD7LKhsH7gL6/2jAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBCwUAA4IBAQCV5kSt1HTFzUPdBvxT455YrLd3jIsRt1pRNuGjVaUYIRxh +vds8NN1Z8h/8Cdzz8NVkIdCuYb2lFaDjs3zNVUQxCyVcH7xVyPwFI85NR27+HPRv +xzz2rwzST+NKYst6ZBg086BKjqFtxs16lpU/TD6tOJqg86TBbfP6gib/ocGeER2D +HEEik69FjmUCziT6uXyYW5y1PxD15UWO3RWoTpao0vGtTPceTeeuO05PVeCUlx8X +YXg9zoVWBba0GF+qQJ67zT5nvfc2KJcgnWRIRr/90YXzBf+FdFVuC4xFHINBI1OJ +5XBLJOv61Zu+Du/nmlBVcb8KL/Vd2oZyfoH+0oCN +-----END CERTIFICATE----- diff --git a/setup.py b/setup.py index 80050a2c9..6ffc9a134 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ install_requires = [ # in which we added 2.6 support (see #2243), so we relax the requirement. 'ConfigArgParse>=0.9.3', 'configobj', - 'cryptography>=0.7', # load_pem_x509_certificate + 'cryptography>=1.2', # load_pem_x509_certificate 'mock', 'parsedatetime>=1.3', # Calendar.parseDT 'PyOpenSSL', diff --git a/tox.ini b/tox.ini index 75b6ce2f2..6040dd818 100644 --- a/tox.ini +++ b/tox.ini @@ -79,7 +79,7 @@ setenv = # https://github.com/shazow/urllib3/pull/930 deps = py{26,27}-oldest: cffi<=1.7 - py{26,27}-oldest: cryptography==0.8 + py{26,27}-oldest: cryptography==1.2 py{26,27}-oldest: configargparse==0.10.0 py{26,27}-oldest: PyOpenSSL==0.13 py{26,27}-oldest: requests<=2.11.1