mirror of
https://github.com/certbot/certbot.git
synced 2026-08-01 02:54:34 +02:00
Use built-in support for OCSP in cryptography >= 2.5 (#6603)
In response to #6594. [Fixes #6594.] To execute OCSP requests, certbot relies currently on a openssl binary execution. If openssl is not present in the PATH, the OCSP check will be silently ignored. Since version 2.4, cryptography has support for OCSP requests, without the need to have openssl binary available locally. This PR takes advantage of it, and will use the built-in support of OCSP in cryptography for versions >= 2.4. Otherwise, fallback is done do a direct call to openssl binary, allowing oldest requirements to still work with legacy cryptography versions. Update: requirement is now cryptography >= 2.5, to avoid to rely on a private method from cryptography. * Implement logic using cryptography * Working OSCP using pure cryptography * Fix openssl usage in unit tests * Reduce verbosity * Add tests * Improve naive skipIf * Test resiliency * Update ocsp.py * Validate OCSP response. Unify OCSP URL get * Improve resiliency checks, correct lint/mypy * Improve hash selection * Fix warnings when calling openssl bin * Load OCSP tests assets as vectors. * Update ocsp.py * Protect against invalid ocsp response. * Add checks to OCSP response * Add more control on ocsp response * Be lenient about assertion that next_update must be in the future, similarly to openssl. * Construct a more advanced OCSP response mock to trigger more logic in ocsp module. * Add test * Refactor signature process to use crypto_util * Fallback for cryptography 2.4 * Avoid a collision with a meteor. * Correct method signature documentation * Relax OCSP update interval * Trigger built-in ocsp logic from cryptography with 2.5+ * Update pinned version of cryptography * Update certbot/ocsp.py Co-Authored-By: adferrand <adferrand@users.noreply.github.com> * Update ocsp.py * Update ocsp_test.py * Update CHANGELOG.md * Update CHANGELOG.md
This commit is contained in:
committed by
Brad Warren
parent
9671985885
commit
2ddaf3db04
+4
-1
@@ -6,8 +6,11 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Avoid to process again challenges that are already validated
|
* Avoid reprocessing challenges that are already validated
|
||||||
when a certificate is issued.
|
when a certificate is issued.
|
||||||
|
* If possible, Certbot uses built-in support for OCSP from recent cryptography
|
||||||
|
versions instead of the OpenSSL binary: as a consequence Certbot does not need
|
||||||
|
the OpenSSL binary to be installed anymore if cryptography>=2.5 is installed.
|
||||||
* Support for initiating (but not solving end-to-end) TLS-ALPN-01 challenges
|
* Support for initiating (but not solving end-to-end) TLS-ALPN-01 challenges
|
||||||
with the `acme` module.
|
with the `acme` module.
|
||||||
|
|
||||||
|
|||||||
+35
-18
@@ -19,7 +19,7 @@ from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
|
|||||||
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
||||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
||||||
# https://github.com/python/typeshed/tree/master/third_party/2/cryptography
|
# https://github.com/python/typeshed/tree/master/third_party/2/cryptography
|
||||||
from cryptography import x509 # type: ignore
|
from cryptography import x509 # type: ignore
|
||||||
from OpenSSL import crypto
|
from OpenSSL import crypto
|
||||||
from OpenSSL import SSL # type: ignore
|
from OpenSSL import SSL # type: ignore
|
||||||
|
|
||||||
@@ -226,7 +226,7 @@ def verify_renewable_cert(renewable_cert):
|
|||||||
|
|
||||||
|
|
||||||
def verify_renewable_cert_sig(renewable_cert):
|
def verify_renewable_cert_sig(renewable_cert):
|
||||||
""" Verifies the signature of a `.storage.RenewableCert` object.
|
"""Verifies the signature of a `.storage.RenewableCert` object.
|
||||||
|
|
||||||
:param `.storage.RenewableCert` renewable_cert: cert to verify
|
:param `.storage.RenewableCert` renewable_cert: cert to verify
|
||||||
|
|
||||||
@@ -239,22 +239,8 @@ def verify_renewable_cert_sig(renewable_cert):
|
|||||||
cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend())
|
cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend())
|
||||||
pk = chain.public_key()
|
pk = chain.public_key()
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
warnings.simplefilter("ignore")
|
verify_signed_payload(pk, cert.signature, cert.tbs_certificate_bytes,
|
||||||
if isinstance(pk, RSAPublicKey):
|
cert.signature_hash_algorithm)
|
||||||
# https://github.com/python/typeshed/blob/master/third_party/2/cryptography/hazmat/primitives/asymmetric/rsa.pyi
|
|
||||||
verifier = pk.verifier( # type: ignore
|
|
||||||
cert.signature, PKCS1v15(), cert.signature_hash_algorithm
|
|
||||||
)
|
|
||||||
verifier.update(cert.tbs_certificate_bytes)
|
|
||||||
verifier.verify()
|
|
||||||
elif isinstance(pk, EllipticCurvePublicKey):
|
|
||||||
verifier = pk.verifier(
|
|
||||||
cert.signature, ECDSA(cert.signature_hash_algorithm)
|
|
||||||
)
|
|
||||||
verifier.update(cert.tbs_certificate_bytes)
|
|
||||||
verifier.verify()
|
|
||||||
else:
|
|
||||||
raise errors.Error("Unsupported public key type")
|
|
||||||
except (IOError, ValueError, InvalidSignature) as e:
|
except (IOError, ValueError, InvalidSignature) as e:
|
||||||
error_str = "verifying the signature of the cert located at {0} has failed. \
|
error_str = "verifying the signature of the cert located at {0} has failed. \
|
||||||
Details: {1}".format(renewable_cert.cert, e)
|
Details: {1}".format(renewable_cert.cert, e)
|
||||||
@@ -262,6 +248,37 @@ def verify_renewable_cert_sig(renewable_cert):
|
|||||||
raise errors.Error(error_str)
|
raise errors.Error(error_str)
|
||||||
|
|
||||||
|
|
||||||
|
def verify_signed_payload(public_key, signature, payload, signature_hash_algorithm):
|
||||||
|
"""Check the signature of a payload.
|
||||||
|
|
||||||
|
:param RSAPublicKey/EllipticCurvePublicKey public_key: the public_key to check signature
|
||||||
|
:param bytes signature: the signature bytes
|
||||||
|
:param bytes payload: the payload bytes
|
||||||
|
:param cryptography.hazmat.primitives.hashes.HashAlgorithm
|
||||||
|
signature_hash_algorithm: algorithm used to hash the payload
|
||||||
|
|
||||||
|
:raises InvalidSignature: If signature verification fails.
|
||||||
|
:raises errors.Error: If public key type is not supported
|
||||||
|
"""
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
if isinstance(public_key, RSAPublicKey):
|
||||||
|
# https://github.com/python/typeshed/blob/master/third_party/2/cryptography/hazmat/primitives/asymmetric/rsa.pyi
|
||||||
|
verifier = public_key.verifier( # type: ignore
|
||||||
|
signature, PKCS1v15(), signature_hash_algorithm
|
||||||
|
)
|
||||||
|
verifier.update(payload)
|
||||||
|
verifier.verify()
|
||||||
|
elif isinstance(public_key, EllipticCurvePublicKey):
|
||||||
|
verifier = public_key.verifier(
|
||||||
|
signature, ECDSA(signature_hash_algorithm)
|
||||||
|
)
|
||||||
|
verifier.update(payload)
|
||||||
|
verifier.verify()
|
||||||
|
else:
|
||||||
|
raise errors.Error("Unsupported public key type")
|
||||||
|
|
||||||
|
|
||||||
def verify_cert_matches_priv_key(cert_path, key_path):
|
def verify_cert_matches_priv_key(cert_path, key_path):
|
||||||
""" Verifies that the private key and cert match.
|
""" Verifies that the private key and cert match.
|
||||||
|
|
||||||
|
|||||||
+153
-44
@@ -1,53 +1,79 @@
|
|||||||
"""Tools for checking certificate revocation."""
|
"""Tools for checking certificate revocation."""
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Only cryptography>=2.5 has ocsp module
|
||||||
|
# and signature_hash_algorithm attribute in OCSPResponse class
|
||||||
|
from cryptography.x509 import ocsp # pylint: disable=import-error
|
||||||
|
getattr(ocsp.OCSPResponse, 'signature_hash_algorithm')
|
||||||
|
except (ImportError, AttributeError): # pragma: no cover
|
||||||
|
ocsp = None # type: ignore
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
from cryptography.hazmat.primitives import hashes # type: ignore
|
||||||
|
from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module
|
||||||
|
from certbot import crypto_util
|
||||||
from certbot import errors
|
from certbot import errors
|
||||||
from certbot import util
|
from certbot import util
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RevocationChecker(object):
|
class RevocationChecker(object):
|
||||||
"This class figures out OCSP checking on this system, and performs it."
|
"""This class figures out OCSP checking on this system, and performs it."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, enforce_openssl_binary_usage=False):
|
||||||
self.broken = False
|
self.broken = False
|
||||||
|
self.use_openssl_binary = enforce_openssl_binary_usage or not ocsp
|
||||||
|
|
||||||
if not util.exe_exists("openssl"):
|
if self.use_openssl_binary:
|
||||||
logger.info("openssl not installed, can't check revocation")
|
if not util.exe_exists("openssl"):
|
||||||
self.broken = True
|
logger.info("openssl not installed, can't check revocation")
|
||||||
return
|
self.broken = True
|
||||||
|
return
|
||||||
# New versions of openssl want -header var=val, old ones want -header var val
|
|
||||||
test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"],
|
|
||||||
stdout=PIPE, stderr=PIPE, universal_newlines=True)
|
|
||||||
_out, err = test_host_format.communicate()
|
|
||||||
if "Missing =" in err:
|
|
||||||
self.host_args = lambda host: ["Host=" + host]
|
|
||||||
else:
|
|
||||||
self.host_args = lambda host: ["Host", host]
|
|
||||||
|
|
||||||
|
# New versions of openssl want -header var=val, old ones want -header var val
|
||||||
|
test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"],
|
||||||
|
stdout=PIPE, stderr=PIPE, universal_newlines=True)
|
||||||
|
_out, err = test_host_format.communicate()
|
||||||
|
if "Missing =" in err:
|
||||||
|
self.host_args = lambda host: ["Host=" + host]
|
||||||
|
else:
|
||||||
|
self.host_args = lambda host: ["Host", host]
|
||||||
|
|
||||||
def ocsp_revoked(self, cert_path, chain_path):
|
def ocsp_revoked(self, cert_path, chain_path):
|
||||||
|
# type: (str, str) -> bool
|
||||||
"""Get revoked status for a particular cert version.
|
"""Get revoked status for a particular cert version.
|
||||||
|
|
||||||
.. todo:: Make this a non-blocking call
|
.. todo:: Make this a non-blocking call
|
||||||
|
|
||||||
:param str cert_path: Path to certificate
|
:param str cert_path: Path to certificate
|
||||||
:param str chain_path: Path to intermediate cert
|
:param str chain_path: Path to intermediate cert
|
||||||
:rtype bool or None:
|
|
||||||
:returns: True if revoked; False if valid or the check failed
|
:returns: True if revoked; False if valid or the check failed
|
||||||
|
:rtype: bool
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.broken:
|
if self.broken:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
url, host = _determine_ocsp_server(cert_path)
|
||||||
url, host = self.determine_ocsp_server(cert_path)
|
if not host or not url:
|
||||||
if not host:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if self.use_openssl_binary:
|
||||||
|
return self._check_ocsp_openssl_bin(cert_path, chain_path, host, url)
|
||||||
|
else:
|
||||||
|
return _check_ocsp_cryptography(cert_path, chain_path, url)
|
||||||
|
|
||||||
|
def _check_ocsp_openssl_bin(self, cert_path, chain_path, host, url):
|
||||||
|
# type: (str, str, str, str) -> bool
|
||||||
# jdkasten thanks "Bulletproof SSL and TLS - Ivan Ristic" for documenting this!
|
# jdkasten thanks "Bulletproof SSL and TLS - Ivan Ristic" for documenting this!
|
||||||
cmd = ["openssl", "ocsp",
|
cmd = ["openssl", "ocsp",
|
||||||
"-no_nonce",
|
"-no_nonce",
|
||||||
@@ -65,33 +91,117 @@ class RevocationChecker(object):
|
|||||||
except errors.SubprocessError:
|
except errors.SubprocessError:
|
||||||
logger.info("OCSP check failed for %s (are we offline?)", cert_path)
|
logger.info("OCSP check failed for %s (are we offline?)", cert_path)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return _translate_ocsp_query(cert_path, output, err)
|
return _translate_ocsp_query(cert_path, output, err)
|
||||||
|
|
||||||
|
|
||||||
def determine_ocsp_server(self, cert_path):
|
def _determine_ocsp_server(cert_path):
|
||||||
"""Extract the OCSP server host from a certificate.
|
# type: (str) -> Tuple[Optional[str], Optional[str]]
|
||||||
|
"""Extract the OCSP server host from a certificate.
|
||||||
|
|
||||||
:param str cert_path: Path to the cert we're checking OCSP for
|
:param str cert_path: Path to the cert we're checking OCSP for
|
||||||
:rtype tuple:
|
:rtype tuple:
|
||||||
:returns: (OCSP server URL or None, OCSP server host or None)
|
:returns: (OCSP server URL or None, OCSP server host or None)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
with open(cert_path, 'rb') as file_handler:
|
||||||
url, _err = util.run_script(
|
cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend())
|
||||||
["openssl", "x509", "-in", cert_path, "-noout", "-ocsp_uri"],
|
try:
|
||||||
log=logger.debug)
|
extension = cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess)
|
||||||
except errors.SubprocessError:
|
ocsp_oid = x509.AuthorityInformationAccessOID.OCSP
|
||||||
logger.info("Cannot extract OCSP URI from %s", cert_path)
|
descriptions = [description for description in extension.value
|
||||||
return None, None
|
if description.access_method == ocsp_oid]
|
||||||
|
|
||||||
|
url = descriptions[0].access_location.value
|
||||||
|
except (x509.ExtensionNotFound, IndexError):
|
||||||
|
logger.info("Cannot extract OCSP URI from %s", cert_path)
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
url = url.rstrip()
|
||||||
|
host = url.partition("://")[2].rstrip("/")
|
||||||
|
|
||||||
|
if host:
|
||||||
|
return url, host
|
||||||
|
else:
|
||||||
|
logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path)
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def _check_ocsp_cryptography(cert_path, chain_path, url):
|
||||||
|
# type: (str, str, str) -> bool
|
||||||
|
# Retrieve OCSP response
|
||||||
|
with open(chain_path, 'rb') as file_handler:
|
||||||
|
issuer = x509.load_pem_x509_certificate(file_handler.read(), default_backend())
|
||||||
|
with open(cert_path, 'rb') as file_handler:
|
||||||
|
cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend())
|
||||||
|
builder = ocsp.OCSPRequestBuilder()
|
||||||
|
builder = builder.add_certificate(cert, issuer, hashes.SHA1())
|
||||||
|
request = builder.build()
|
||||||
|
request_binary = request.public_bytes(serialization.Encoding.DER)
|
||||||
|
response = requests.post(url, data=request_binary,
|
||||||
|
headers={'Content-Type': 'application/ocsp-request'})
|
||||||
|
if response.status_code != 200:
|
||||||
|
logger.info("OCSP check failed for %s (are we offline?)", cert_path)
|
||||||
|
return False
|
||||||
|
response_ocsp = ocsp.load_der_ocsp_response(response.content)
|
||||||
|
|
||||||
|
# Check OCSP response validity
|
||||||
|
if response_ocsp.response_status != ocsp.OCSPResponseStatus.SUCCESSFUL:
|
||||||
|
logger.error("Invalid OCSP response status for %s: %s",
|
||||||
|
cert_path, response_ocsp.response_status)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check OCSP signature
|
||||||
|
try:
|
||||||
|
_check_ocsp_response(response_ocsp, request, issuer)
|
||||||
|
except UnsupportedAlgorithm as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
except errors.Error as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
except InvalidSignature:
|
||||||
|
logger.error('Invalid signature on OCSP response for %s', cert_path)
|
||||||
|
except AssertionError as error:
|
||||||
|
logger.error('Invalid OCSP response for %s: %s.', cert_path, str(error))
|
||||||
|
else:
|
||||||
|
# Check OCSP certificate status
|
||||||
|
logger.debug("OCSP certificate status for %s is: %s",
|
||||||
|
cert_path, response_ocsp.certificate_status)
|
||||||
|
return response_ocsp.certificate_status == ocsp.OCSPCertStatus.REVOKED
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert):
|
||||||
|
"""Verify that the OCSP is valid for serveral criterias"""
|
||||||
|
# Assert OCSP response corresponds to the certificate we are talking about
|
||||||
|
if response_ocsp.serial_number != request_ocsp.serial_number:
|
||||||
|
raise AssertionError('the certificate in response does not correspond '
|
||||||
|
'to the certificate in request')
|
||||||
|
|
||||||
|
# Assert signature is valid
|
||||||
|
_check_ocsp_response_signature(response_ocsp, issuer_cert)
|
||||||
|
|
||||||
|
# Assert issuer in response is the expected one
|
||||||
|
if (not isinstance(response_ocsp.hash_algorithm, type(request_ocsp.hash_algorithm))
|
||||||
|
or response_ocsp.issuer_key_hash != request_ocsp.issuer_key_hash
|
||||||
|
or response_ocsp.issuer_name_hash != request_ocsp.issuer_name_hash):
|
||||||
|
raise AssertionError('the issuer does not correspond to issuer of the certificate.')
|
||||||
|
|
||||||
|
# Assert nextUpdate is in the future, and that thisUpdate is not too old
|
||||||
|
if response_ocsp.next_update:
|
||||||
|
if response_ocsp.next_update < datetime.now() - timedelta(minutes=5):
|
||||||
|
raise AssertionError('next update is in the past.')
|
||||||
|
interval = response_ocsp.next_update - response_ocsp.this_update
|
||||||
|
if datetime.now() - response_ocsp.this_update > interval + timedelta(minutes=5):
|
||||||
|
raise AssertionError('this update is too old.')
|
||||||
|
|
||||||
|
|
||||||
|
def _check_ocsp_response_signature(response_ocsp, issuer_cert):
|
||||||
|
"""Verify an OCSP response signature against certificate issuer"""
|
||||||
|
# Following line may raise UnsupportedAlgorithm
|
||||||
|
chosen_hash = response_ocsp.signature_hash_algorithm
|
||||||
|
crypto_util.verify_signed_payload(issuer_cert.public_key(), response_ocsp.signature,
|
||||||
|
response_ocsp.tbs_response_bytes, chosen_hash)
|
||||||
|
|
||||||
url = url.rstrip()
|
|
||||||
host = url.partition("://")[2].rstrip("/")
|
|
||||||
if host:
|
|
||||||
return url, host
|
|
||||||
else:
|
|
||||||
logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path)
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors):
|
def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors):
|
||||||
"""Parse openssl's weird output to work out what it means."""
|
"""Parse openssl's weird output to work out what it means."""
|
||||||
@@ -102,7 +212,7 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors):
|
|||||||
|
|
||||||
warning = good.group(1) if good else None
|
warning = good.group(1) if good else None
|
||||||
|
|
||||||
if (not "Response verify OK" in ocsp_errors) or (good and warning) or unknown:
|
if ("Response verify OK" not in ocsp_errors) or (good and warning) or unknown:
|
||||||
logger.info("Revocation status for %s is unknown", cert_path)
|
logger.info("Revocation status for %s is unknown", cert_path)
|
||||||
logger.debug("Uncertain output:\n%s\nstderr:\n%s", ocsp_output, ocsp_errors)
|
logger.debug("Uncertain output:\n%s\nstderr:\n%s", ocsp_output, ocsp_errors)
|
||||||
return False
|
return False
|
||||||
@@ -115,6 +225,5 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s",
|
logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s",
|
||||||
ocsp_output, ocsp_errors)
|
ocsp_output, ocsp_errors)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
+151
-21
@@ -1,18 +1,33 @@
|
|||||||
"""Tests for ocsp.py"""
|
"""Tests for ocsp.py"""
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import hashes # type: ignore
|
||||||
|
from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature
|
||||||
|
from cryptography import x509
|
||||||
|
try:
|
||||||
|
# Only cryptography>=2.5 has ocsp module
|
||||||
|
# and signature_hash_algorithm attribute in OCSPResponse class
|
||||||
|
from cryptography.x509 import ocsp as ocsp_lib # pylint: disable=import-error
|
||||||
|
getattr(ocsp_lib.OCSPResponse, 'signature_hash_algorithm')
|
||||||
|
except (ImportError, AttributeError): # pragma: no cover
|
||||||
|
ocsp_lib = None # type: ignore
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from certbot import errors
|
from certbot import errors
|
||||||
|
from certbot.tests import util as test_util
|
||||||
|
|
||||||
out = """Missing = in header key=value
|
out = """Missing = in header key=value
|
||||||
ocsp: Use -help for summary.
|
ocsp: Use -help for summary.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class OCSPTest(unittest.TestCase):
|
|
||||||
|
|
||||||
|
class OCSPTestOpenSSL(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
OCSP revokation tests using OpenSSL binary.
|
||||||
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
from certbot import ocsp
|
from certbot import ocsp
|
||||||
@@ -22,7 +37,7 @@ class OCSPTest(unittest.TestCase):
|
|||||||
mock_communicate.communicate.return_value = (None, out)
|
mock_communicate.communicate.return_value = (None, out)
|
||||||
mock_popen.return_value = mock_communicate
|
mock_popen.return_value = mock_communicate
|
||||||
mock_exists.return_value = True
|
mock_exists.return_value = True
|
||||||
self.checker = ocsp.RevocationChecker()
|
self.checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
@@ -37,23 +52,23 @@ class OCSPTest(unittest.TestCase):
|
|||||||
mock_exists.return_value = True
|
mock_exists.return_value = True
|
||||||
|
|
||||||
from certbot import ocsp
|
from certbot import ocsp
|
||||||
checker = ocsp.RevocationChecker()
|
checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True)
|
||||||
self.assertEqual(mock_popen.call_count, 1)
|
self.assertEqual(mock_popen.call_count, 1)
|
||||||
self.assertEqual(checker.host_args("x"), ["Host=x"])
|
self.assertEqual(checker.host_args("x"), ["Host=x"])
|
||||||
|
|
||||||
mock_communicate.communicate.return_value = (None, out.partition("\n")[2])
|
mock_communicate.communicate.return_value = (None, out.partition("\n")[2])
|
||||||
checker = ocsp.RevocationChecker()
|
checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True)
|
||||||
self.assertEqual(checker.host_args("x"), ["Host", "x"])
|
self.assertEqual(checker.host_args("x"), ["Host", "x"])
|
||||||
self.assertEqual(checker.broken, False)
|
self.assertEqual(checker.broken, False)
|
||||||
|
|
||||||
mock_exists.return_value = False
|
mock_exists.return_value = False
|
||||||
mock_popen.call_count = 0
|
mock_popen.call_count = 0
|
||||||
checker = ocsp.RevocationChecker()
|
checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True)
|
||||||
self.assertEqual(mock_popen.call_count, 0)
|
self.assertEqual(mock_popen.call_count, 0)
|
||||||
self.assertEqual(mock_log.call_count, 1)
|
self.assertEqual(mock_log.call_count, 1)
|
||||||
self.assertEqual(checker.broken, True)
|
self.assertEqual(checker.broken, True)
|
||||||
|
|
||||||
@mock.patch('certbot.ocsp.RevocationChecker.determine_ocsp_server')
|
@mock.patch('certbot.ocsp._determine_ocsp_server')
|
||||||
@mock.patch('certbot.util.run_script')
|
@mock.patch('certbot.util.run_script')
|
||||||
def test_ocsp_revoked(self, mock_run, mock_determine):
|
def test_ocsp_revoked(self, mock_run, mock_determine):
|
||||||
self.checker.broken = True
|
self.checker.broken = True
|
||||||
@@ -71,21 +86,12 @@ class OCSPTest(unittest.TestCase):
|
|||||||
self.assertEqual(self.checker.ocsp_revoked("x", "y"), False)
|
self.assertEqual(self.checker.ocsp_revoked("x", "y"), False)
|
||||||
self.assertEqual(mock_run.call_count, 2)
|
self.assertEqual(mock_run.call_count, 2)
|
||||||
|
|
||||||
|
def test_determine_ocsp_server(self):
|
||||||
|
cert_path = test_util.vector_path('google_certificate.pem')
|
||||||
|
|
||||||
@mock.patch('certbot.ocsp.logger.info')
|
from certbot import ocsp
|
||||||
@mock.patch('certbot.util.run_script')
|
result = ocsp._determine_ocsp_server(cert_path)
|
||||||
def test_determine_ocsp_server(self, mock_run, mock_info):
|
self.assertEqual(('http://ocsp.digicert.com', 'ocsp.digicert.com'), result)
|
||||||
uri = "http://ocsp.stg-int-x1.letsencrypt.org/"
|
|
||||||
host = "ocsp.stg-int-x1.letsencrypt.org"
|
|
||||||
mock_run.return_value = uri, ""
|
|
||||||
self.assertEqual(self.checker.determine_ocsp_server("beep"), (uri, host))
|
|
||||||
mock_run.return_value = "ftp:/" + host + "/", ""
|
|
||||||
self.assertEqual(self.checker.determine_ocsp_server("beep"), (None, None))
|
|
||||||
self.assertEqual(mock_info.call_count, 1)
|
|
||||||
|
|
||||||
c = "confusion"
|
|
||||||
mock_run.side_effect = errors.SubprocessError(c)
|
|
||||||
self.assertEqual(self.checker.determine_ocsp_server("beep"), (None, None))
|
|
||||||
|
|
||||||
@mock.patch('certbot.ocsp.logger')
|
@mock.patch('certbot.ocsp.logger')
|
||||||
@mock.patch('certbot.util.run_script')
|
@mock.patch('certbot.util.run_script')
|
||||||
@@ -112,6 +118,129 @@ class OCSPTest(unittest.TestCase):
|
|||||||
self.assertEqual(mock_log.info.call_count, 1)
|
self.assertEqual(mock_log.info.call_count, 1)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(not ocsp_lib,
|
||||||
|
reason='This class tests functionalities available only on cryptography>=2.5.0')
|
||||||
|
class OSCPTestCryptography(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
OCSP revokation tests using Cryptography >= 2.4.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
from certbot import ocsp
|
||||||
|
self.checker = ocsp.RevocationChecker()
|
||||||
|
self.cert_path = test_util.vector_path('google_certificate.pem')
|
||||||
|
self.chain_path = test_util.vector_path('google_issuer_certificate.pem')
|
||||||
|
|
||||||
|
@mock.patch('certbot.ocsp._determine_ocsp_server')
|
||||||
|
@mock.patch('certbot.ocsp._check_ocsp_cryptography')
|
||||||
|
def test_ensure_cryptography_toggled(self, mock_revoke, mock_determine):
|
||||||
|
mock_determine.return_value = ('http://example.com', 'example.com')
|
||||||
|
self.checker.ocsp_revoked(self.cert_path, self.chain_path)
|
||||||
|
|
||||||
|
mock_revoke.assert_called_once_with(self.cert_path, self.chain_path, 'http://example.com')
|
||||||
|
|
||||||
|
@mock.patch('certbot.ocsp.requests.post')
|
||||||
|
@mock.patch('certbot.ocsp.ocsp.load_der_ocsp_response')
|
||||||
|
def test_revoke(self, mock_ocsp_response, mock_post):
|
||||||
|
with mock.patch('certbot.ocsp.crypto_util.verify_signed_payload'):
|
||||||
|
mock_ocsp_response.return_value = _construct_mock_ocsp_response(
|
||||||
|
ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL)
|
||||||
|
mock_post.return_value = mock.Mock(status_code=200)
|
||||||
|
revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
|
||||||
|
|
||||||
|
self.assertTrue(revoked)
|
||||||
|
|
||||||
|
@mock.patch('certbot.ocsp.crypto_util.verify_signed_payload')
|
||||||
|
@mock.patch('certbot.ocsp.requests.post')
|
||||||
|
@mock.patch('certbot.ocsp.ocsp.load_der_ocsp_response')
|
||||||
|
def test_revoke_resiliency(self, mock_ocsp_response, mock_post, mock_check):
|
||||||
|
# Server return an invalid HTTP response
|
||||||
|
mock_ocsp_response.return_value = _construct_mock_ocsp_response(
|
||||||
|
ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL)
|
||||||
|
mock_post.return_value = mock.Mock(status_code=400)
|
||||||
|
revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
|
||||||
|
|
||||||
|
self.assertFalse(revoked)
|
||||||
|
|
||||||
|
# OCSP response in invalid
|
||||||
|
mock_ocsp_response.return_value = _construct_mock_ocsp_response(
|
||||||
|
ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.UNAUTHORIZED)
|
||||||
|
mock_post.return_value = mock.Mock(status_code=200)
|
||||||
|
revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
|
||||||
|
|
||||||
|
self.assertFalse(revoked)
|
||||||
|
|
||||||
|
# OCSP response is valid, but certificate status is unknown
|
||||||
|
mock_ocsp_response.return_value = _construct_mock_ocsp_response(
|
||||||
|
ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL)
|
||||||
|
mock_post.return_value = mock.Mock(status_code=200)
|
||||||
|
revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
|
||||||
|
|
||||||
|
self.assertFalse(revoked)
|
||||||
|
|
||||||
|
# The OCSP response says that the certificate is revoked, but certificate
|
||||||
|
# does not contain the OCSP extension.
|
||||||
|
mock_ocsp_response.return_value = _construct_mock_ocsp_response(
|
||||||
|
ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL)
|
||||||
|
mock_post.return_value = mock.Mock(status_code=200)
|
||||||
|
with mock.patch('cryptography.x509.Extensions.get_extension_for_class',
|
||||||
|
side_effect=x509.ExtensionNotFound(
|
||||||
|
'Not found', x509.AuthorityInformationAccessOID.OCSP)):
|
||||||
|
revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
|
||||||
|
|
||||||
|
self.assertFalse(revoked)
|
||||||
|
|
||||||
|
# Valid response, OCSP extension is present,
|
||||||
|
# but OCSP response uses an unsupported signature.
|
||||||
|
mock_ocsp_response.return_value = _construct_mock_ocsp_response(
|
||||||
|
ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL)
|
||||||
|
mock_post.return_value = mock.Mock(status_code=200)
|
||||||
|
mock_check.side_effect = UnsupportedAlgorithm('foo')
|
||||||
|
revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
|
||||||
|
|
||||||
|
self.assertFalse(revoked)
|
||||||
|
|
||||||
|
# And now, the signature itself is invalid.
|
||||||
|
mock_ocsp_response.return_value = _construct_mock_ocsp_response(
|
||||||
|
ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL)
|
||||||
|
mock_post.return_value = mock.Mock(status_code=200)
|
||||||
|
mock_check.side_effect = InvalidSignature('foo')
|
||||||
|
revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
|
||||||
|
|
||||||
|
self.assertFalse(revoked)
|
||||||
|
|
||||||
|
# Finally, assertion error on OCSP response validity
|
||||||
|
mock_ocsp_response.return_value = _construct_mock_ocsp_response(
|
||||||
|
ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL)
|
||||||
|
mock_post.return_value = mock.Mock(status_code=200)
|
||||||
|
mock_check.side_effect = AssertionError('foo')
|
||||||
|
revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path)
|
||||||
|
|
||||||
|
self.assertFalse(revoked)
|
||||||
|
|
||||||
|
|
||||||
|
def _construct_mock_ocsp_response(certificate_status, response_status):
|
||||||
|
cert = x509.load_pem_x509_certificate(
|
||||||
|
test_util.load_vector('google_certificate.pem'), default_backend())
|
||||||
|
issuer = x509.load_pem_x509_certificate(
|
||||||
|
test_util.load_vector('google_issuer_certificate.pem'), default_backend())
|
||||||
|
builder = ocsp_lib.OCSPRequestBuilder()
|
||||||
|
builder = builder.add_certificate(cert, issuer, hashes.SHA1())
|
||||||
|
request = builder.build()
|
||||||
|
|
||||||
|
return mock.Mock(
|
||||||
|
response_status=response_status,
|
||||||
|
certificate_status=certificate_status,
|
||||||
|
serial_number=request.serial_number,
|
||||||
|
issuer_key_hash=request.issuer_key_hash,
|
||||||
|
issuer_name_hash=request.issuer_name_hash,
|
||||||
|
hash_algorithm=hashes.SHA1(),
|
||||||
|
next_update=datetime.now() + timedelta(days=1),
|
||||||
|
this_update=datetime.now() - timedelta(days=1),
|
||||||
|
signature_algorithm_oid=x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=line-too-long
|
# pylint: disable=line-too-long
|
||||||
openssl_confused = ("", """
|
openssl_confused = ("", """
|
||||||
/etc/letsencrypt/live/example.org/cert.pem: good
|
/etc/letsencrypt/live/example.org/cert.pem: good
|
||||||
@@ -165,5 +294,6 @@ revoked
|
|||||||
""",
|
""",
|
||||||
"""Response verify OK""")
|
"""Response verify OK""")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main() # pragma: no cover
|
unittest.main() # pragma: no cover
|
||||||
|
|||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIHQjCCBiqgAwIBAgIQCgYwQn9bvO1pVzllk7ZFHzANBgkqhkiG9w0BAQsFADB1
|
||||||
|
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||||
|
d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk
|
||||||
|
IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE4MDUwODAwMDAwMFoXDTIwMDYwMzEy
|
||||||
|
MDAwMFowgccxHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB
|
||||||
|
BAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQF
|
||||||
|
Ewc1MTU3NTUwMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG
|
||||||
|
A1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRMwEQYD
|
||||||
|
VQQDEwpnaXRodWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
|
||||||
|
xjyq8jyXDDrBTyitcnB90865tWBzpHSbindG/XqYQkzFMBlXmqkzC+FdTRBYyneZ
|
||||||
|
w5Pz+XWQvL+74JW6LsWNc2EF0xCEqLOJuC9zjPAqbr7uroNLghGxYf13YdqbG5oj
|
||||||
|
/4x+ogEG3dF/U5YIwVr658DKyESMV6eoYV9mDVfTuJastkqcwero+5ZAKfYVMLUE
|
||||||
|
sMwFtoTDJFmVf6JlkOWwsxp1WcQ/MRQK1cyqOoUFUgYylgdh3yeCDPeF22Ax8AlQ
|
||||||
|
xbcaI+GwfQL1FB7Jy+h+KjME9lE/UpgV6Qt2R1xNSmvFCBWu+NFX6epwFP/JRbkM
|
||||||
|
fLz0beYFUvmMgLtwVpEPSwIDAQABo4IDeTCCA3UwHwYDVR0jBBgwFoAUPdNQpdag
|
||||||
|
re7zSmAKZdMh1Pj41g8wHQYDVR0OBBYEFMnCU2FmnV+rJfQmzQ84mqhJ6kipMCUG
|
||||||
|
A1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMA4GA1UdDwEB/wQE
|
||||||
|
AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0fBG4wbDA0
|
||||||
|
oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItZXYtc2VydmVyLWcy
|
||||||
|
LmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItZXYtc2Vy
|
||||||
|
dmVyLWcyLmNybDBLBgNVHSAERDBCMDcGCWCGSAGG/WwCATAqMCgGCCsGAQUFBwIB
|
||||||
|
FhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAcGBWeBDAEBMIGIBggrBgEF
|
||||||
|
BQcBAQR8MHowJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBS
|
||||||
|
BggrBgEFBQcwAoZGaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0
|
||||||
|
U0hBMkV4dGVuZGVkVmFsaWRhdGlvblNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAA
|
||||||
|
MIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdgCkuQmQtBhYFIe7E6LMZ3AKPDWY
|
||||||
|
BPkb37jjd80OyA3cEAAAAWNBYm0KAAAEAwBHMEUCIQDRZp38cTWsWH2GdBpe/uPT
|
||||||
|
Wnsu/m4BEC2+dIcvSykZYgIgCP5gGv6yzaazxBK2NwGdmmyuEFNSg2pARbMJlUFg
|
||||||
|
U5UAdgBWFAaaL9fC7NP14b1Esj7HRna5vJkRXMDvlJhV1onQ3QAAAWNBYm0tAAAE
|
||||||
|
AwBHMEUCIQCi7omUvYLm0b2LobtEeRAYnlIo7n6JxbYdrtYdmPUWJQIgVgw1AZ51
|
||||||
|
vK9ENinBg22FPxb82TvNDO05T17hxXRC2IYAdgC72d+8H4pxtZOUI5eqkntHOFeV
|
||||||
|
CqtS6BqQlmQ2jh7RhQAAAWNBYm3fAAAEAwBHMEUCIQChzdTKUU2N+XcqcK0OJYrN
|
||||||
|
8EYynloVxho4yPk6Dq3EPgIgdNH5u8rC3UcslQV4B9o0a0w204omDREGKTVuEpxG
|
||||||
|
eOQwDQYJKoZIhvcNAQELBQADggEBAHAPWpanWOW/ip2oJ5grAH8mqQfaunuCVE+v
|
||||||
|
ac+88lkDK/LVdFgl2B6kIHZiYClzKtfczG93hWvKbST4NRNHP9LiaQqdNC17e5vN
|
||||||
|
HnXVUGw+yxyjMLGqkgepOnZ2Rb14kcTOGp4i5AuJuuaMwXmCo7jUwPwfLe1NUlVB
|
||||||
|
Kqg6LK0Hcq4K0sZnxE8HFxiZ92WpV2AVWjRMEc/2z2shNoDvxvFUYyY1Oe67xINk
|
||||||
|
myQKc+ygSBZzyLnXSFVWmHr3u5dcaaQGGAR42v6Ydr4iL38Hd4dOiBma+FXsXBIq
|
||||||
|
WUjbST4VXmdaol7uzFMojA4zkxQDZAvF5XgJlAFadfySna/teik=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEXDCCA0SgAwIBAgINAeOpMBz8cgY4P5pTHTANBgkqhkiG9w0BAQsFADBMMSAw
|
||||||
|
HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs
|
||||||
|
U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy
|
||||||
|
MTUwMDAwNDJaMFQxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3Qg
|
||||||
|
U2VydmljZXMxJTAjBgNVBAMTHEdvb2dsZSBJbnRlcm5ldCBBdXRob3JpdHkgRzMw
|
||||||
|
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKUkvqHv/OJGuo2nIYaNVW
|
||||||
|
XQ5IWi01CXZaz6TIHLGp/lOJ+600/4hbn7vn6AAB3DVzdQOts7G5pH0rJnnOFUAK
|
||||||
|
71G4nzKMfHCGUksW/mona+Y2emJQ2N+aicwJKetPKRSIgAuPOB6Aahh8Hb2XO3h9
|
||||||
|
RUk2T0HNouB2VzxoMXlkyW7XUR5mw6JkLHnA52XDVoRTWkNty5oCINLvGmnRsJ1z
|
||||||
|
ouAqYGVQMc/7sy+/EYhALrVJEA8KbtyX+r8snwU5C1hUrwaW6MWOARa8qBpNQcWT
|
||||||
|
kaIeoYvy/sGIJEmjR0vFEwHdp1cSaWIr6/4g72n7OqXwfinu7ZYW97EfoOSQJeAz
|
||||||
|
AgMBAAGjggEzMIIBLzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUH
|
||||||
|
AwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHfCuFCa
|
||||||
|
Z3Z2sS3ChtCDoH6mfrpLMB8GA1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYu
|
||||||
|
MDUGCCsGAQUFBwEBBCkwJzAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdv
|
||||||
|
b2cvZ3NyMjAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dz
|
||||||
|
cjIvZ3NyMi5jcmwwPwYDVR0gBDgwNjA0BgZngQwBAgIwKjAoBggrBgEFBQcCARYc
|
||||||
|
aHR0cHM6Ly9wa2kuZ29vZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEA
|
||||||
|
HLeJluRT7bvs26gyAZ8so81trUISd7O45skDUmAge1cnxhG1P2cNmSxbWsoiCt2e
|
||||||
|
ux9LSD+PAj2LIYRFHW31/6xoic1k4tbWXkDCjir37xTTNqRAMPUyFRWSdvt+nlPq
|
||||||
|
wnb8Oa2I/maSJukcxDjNSfpDh/Bd1lZNgdd/8cLdsE3+wypufJ9uXO1iQpnh9zbu
|
||||||
|
FIwsIONGl1p3A8CgxkqI/UAih3JaGOqcpcdaCIzkBaR9uYQ1X4k2Vg5APRLouzVy
|
||||||
|
7a8IVk6wuy6pm+T7HT4LY8ibS5FEZlfAFLSW8NwsVz9SBK2Vqn1N0PIMn5xA6NZV
|
||||||
|
c7o835DLAFshEWfC7TIe3g==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
+1
-1
@@ -62,7 +62,7 @@ def run_script(params, log=logger.error):
|
|||||||
"""Run the script with the given params.
|
"""Run the script with the given params.
|
||||||
|
|
||||||
:param list params: List of parameters to pass to Popen
|
:param list params: List of parameters to pass to Popen
|
||||||
:param logging.Logger log: Logger to use for errors
|
:param callable log: Logger method to use for errors
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1034,26 +1034,26 @@ ConfigArgParse==0.12.0 \
|
|||||||
configobj==5.0.6 \
|
configobj==5.0.6 \
|
||||||
--hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \
|
--hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \
|
||||||
--no-binary configobj
|
--no-binary configobj
|
||||||
cryptography==2.2.2 \
|
cryptography==2.5 \
|
||||||
--hash=sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd \
|
--hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \
|
||||||
--hash=sha256:5251e7de0de66810833606439ca65c9b9e45da62196b0c88bfadf27740aac09f \
|
--hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \
|
||||||
--hash=sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04 \
|
--hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \
|
||||||
--hash=sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f \
|
--hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \
|
||||||
--hash=sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd \
|
--hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \
|
||||||
--hash=sha256:64b5c67acc9a7c83fbb4b69166f3105a0ab722d27934fac2cb26456718eec2ba \
|
--hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \
|
||||||
--hash=sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb \
|
--hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \
|
||||||
--hash=sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2 \
|
--hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \
|
||||||
--hash=sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037 \
|
--hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \
|
||||||
--hash=sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd \
|
--hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \
|
||||||
--hash=sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531 \
|
--hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \
|
||||||
--hash=sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63 \
|
--hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \
|
||||||
--hash=sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e \
|
--hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \
|
||||||
--hash=sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351 \
|
--hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \
|
||||||
--hash=sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a \
|
--hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \
|
||||||
--hash=sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563 \
|
--hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \
|
||||||
--hash=sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab \
|
--hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \
|
||||||
--hash=sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471 \
|
--hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \
|
||||||
--hash=sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887
|
--hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401
|
||||||
enum34==1.1.2 ; python_version < '3.4' \
|
enum34==1.1.2 ; python_version < '3.4' \
|
||||||
--hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \
|
--hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \
|
||||||
--hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501
|
--hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501
|
||||||
|
|||||||
@@ -60,26 +60,26 @@ ConfigArgParse==0.12.0 \
|
|||||||
configobj==5.0.6 \
|
configobj==5.0.6 \
|
||||||
--hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \
|
--hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \
|
||||||
--no-binary configobj
|
--no-binary configobj
|
||||||
cryptography==2.2.2 \
|
cryptography==2.5 \
|
||||||
--hash=sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd \
|
--hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \
|
||||||
--hash=sha256:5251e7de0de66810833606439ca65c9b9e45da62196b0c88bfadf27740aac09f \
|
--hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \
|
||||||
--hash=sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04 \
|
--hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \
|
||||||
--hash=sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f \
|
--hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \
|
||||||
--hash=sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd \
|
--hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \
|
||||||
--hash=sha256:64b5c67acc9a7c83fbb4b69166f3105a0ab722d27934fac2cb26456718eec2ba \
|
--hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \
|
||||||
--hash=sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb \
|
--hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \
|
||||||
--hash=sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2 \
|
--hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \
|
||||||
--hash=sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037 \
|
--hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \
|
||||||
--hash=sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd \
|
--hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \
|
||||||
--hash=sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531 \
|
--hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \
|
||||||
--hash=sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63 \
|
--hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \
|
||||||
--hash=sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e \
|
--hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \
|
||||||
--hash=sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351 \
|
--hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \
|
||||||
--hash=sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a \
|
--hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \
|
||||||
--hash=sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563 \
|
--hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \
|
||||||
--hash=sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab \
|
--hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \
|
||||||
--hash=sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471 \
|
--hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \
|
||||||
--hash=sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887
|
--hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401
|
||||||
enum34==1.1.2 ; python_version < '3.4' \
|
enum34==1.1.2 ; python_version < '3.4' \
|
||||||
--hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \
|
--hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \
|
||||||
--hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501
|
--hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501
|
||||||
|
|||||||
Reference in New Issue
Block a user