Compare commits
19 Commits
test-upgra
...
test-crypt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73ab9a3972 | ||
|
|
f05b187568 | ||
|
|
f53f982a22 | ||
|
|
b2ff0669e0 | ||
|
|
49d636d6f8 | ||
|
|
b3cd72c54a | ||
|
|
180dcd0b9c | ||
|
|
1f0891f7f4 | ||
|
|
4b5c4638b9 | ||
|
|
6852850bb8 | ||
|
|
e20328db60 | ||
|
|
ee98077c87 | ||
|
|
2b854d0137 | ||
|
|
5761c123e5 | ||
|
|
ffa3b1b52e | ||
|
|
391d2f8f14 | ||
|
|
057859f9de | ||
|
|
5e4eaf2841 | ||
|
|
ac2d2f01f2 |
161
.travis.yml
161
.travis.yml
@@ -25,160 +25,6 @@ matrix:
|
||||
env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=all TOXENV=py27_install
|
||||
sudo: required
|
||||
services: docker
|
||||
- python: "2.7"
|
||||
env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=all TOXENV=py27_install
|
||||
sudo: required
|
||||
services: docker
|
||||
- python: "2.7"
|
||||
env: TOXENV=py27-cover FYI="py27 tests + code coverage"
|
||||
- sudo: required
|
||||
env: TOXENV=nginx_compat
|
||||
services: docker
|
||||
before_install:
|
||||
addons:
|
||||
- python: "2.7"
|
||||
env: TOXENV=lint
|
||||
- python: "3.4"
|
||||
env: TOXENV=mypy
|
||||
- python: "3.5"
|
||||
env: TOXENV=mypy
|
||||
- python: "2.7"
|
||||
env: TOXENV='py27-{acme,apache,certbot,dns,nginx,postfix}-oldest'
|
||||
sudo: required
|
||||
services: docker
|
||||
- python: "3.4"
|
||||
env: TOXENV=py34
|
||||
sudo: required
|
||||
services: docker
|
||||
- python: "3.7"
|
||||
dist: xenial
|
||||
env: TOXENV=py37
|
||||
sudo: required
|
||||
services: docker
|
||||
- sudo: required
|
||||
env: TOXENV=apache_compat
|
||||
services: docker
|
||||
before_install:
|
||||
addons:
|
||||
- sudo: required
|
||||
env: TOXENV=le_auto_trusty
|
||||
services: docker
|
||||
before_install:
|
||||
addons:
|
||||
- python: "2.7"
|
||||
env: TOXENV=apacheconftest-with-pebble
|
||||
sudo: required
|
||||
services: docker
|
||||
- python: "2.7"
|
||||
env: TOXENV=nginxroundtrip
|
||||
|
||||
# These environments are executed on cron events and commits to tested
|
||||
# branches other than master. Which branches are tested is controlled by
|
||||
# the "branches" section earlier in this file.
|
||||
- python: "3.7"
|
||||
dist: xenial
|
||||
env: TOXENV=py37 CERTBOT_NO_PIN=1
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- python: "2.7"
|
||||
env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=certbot TOXENV=py27-certbot-oldest
|
||||
sudo: required
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- python: "2.7"
|
||||
env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=certbot TOXENV=py27-certbot-oldest
|
||||
sudo: required
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- python: "2.7"
|
||||
env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=nginx TOXENV=py27-nginx-oldest
|
||||
sudo: required
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- python: "2.7"
|
||||
env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=nginx TOXENV=py27-nginx-oldest
|
||||
sudo: required
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- python: "3.4"
|
||||
env: TOXENV=py34 BOULDER_INTEGRATION=v1
|
||||
sudo: required
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- python: "3.4"
|
||||
env: TOXENV=py34 BOULDER_INTEGRATION=v2
|
||||
sudo: required
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- python: "3.5"
|
||||
env: TOXENV=py35 BOULDER_INTEGRATION=v1
|
||||
sudo: required
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- python: "3.5"
|
||||
env: TOXENV=py35 BOULDER_INTEGRATION=v2
|
||||
sudo: required
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- python: "3.6"
|
||||
env: TOXENV=py36 BOULDER_INTEGRATION=v1
|
||||
sudo: required
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- python: "3.6"
|
||||
env: TOXENV=py36 BOULDER_INTEGRATION=v2
|
||||
sudo: required
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- python: "3.7"
|
||||
dist: xenial
|
||||
env: TOXENV=py37 BOULDER_INTEGRATION=v1
|
||||
sudo: required
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- python: "3.7"
|
||||
dist: xenial
|
||||
env: TOXENV=py37 BOULDER_INTEGRATION=v2
|
||||
sudo: required
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- sudo: required
|
||||
env: TOXENV=le_auto_xenial
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- sudo: required
|
||||
env: TOXENV=le_auto_jessie
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- sudo: required
|
||||
env: TOXENV=le_auto_centos6
|
||||
services: docker
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- sudo: required
|
||||
env: TOXENV=docker_dev
|
||||
services: docker
|
||||
addons:
|
||||
apt:
|
||||
packages: # don't install nginx and apache
|
||||
- libaugeas0
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- language: generic
|
||||
env: TOXENV=py27
|
||||
os: osx
|
||||
addons:
|
||||
homebrew:
|
||||
packages:
|
||||
- augeas
|
||||
- python2
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
- language: generic
|
||||
env: TOXENV=py3
|
||||
os: osx
|
||||
addons:
|
||||
homebrew:
|
||||
packages:
|
||||
- augeas
|
||||
- python3
|
||||
if: type = cron OR (type = push AND branch != master)
|
||||
|
||||
# container-based infrastructure
|
||||
sudo: false
|
||||
@@ -206,10 +52,3 @@ after_success: '[ "$TOXENV" == "py27-cover" ] && codecov'
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
channels:
|
||||
- secure: "SGWZl3ownKx9xKVV2VnGt7DqkTmutJ89oJV9tjKhSs84kLijU6EYdPnllqISpfHMTxXflNZuxtGo0wTDYHXBuZL47w1O32W6nzuXdra5zC+i4sYQwYULUsyfOv9gJX8zWAULiK0Z3r0oho45U+FR5ZN6TPCidi8/eGU+EEPwaAw="
|
||||
on_cancel: never
|
||||
on_success: never
|
||||
on_failure: always
|
||||
use_notice: true
|
||||
|
||||
@@ -39,6 +39,9 @@ More details about these changes can be found on our GitHub repo.
|
||||
|
||||
* Avoid reprocessing challenges that are already validated
|
||||
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
|
||||
with the `acme` module.
|
||||
|
||||
|
||||
@@ -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.rsa import RSAPublicKey
|
||||
# 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 SSL # type: ignore
|
||||
|
||||
@@ -226,7 +226,7 @@ def verify_renewable_cert(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
|
||||
|
||||
@@ -239,22 +239,8 @@ def verify_renewable_cert_sig(renewable_cert):
|
||||
cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend())
|
||||
pk = chain.public_key()
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
if isinstance(pk, RSAPublicKey):
|
||||
# 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")
|
||||
verify_signed_payload(pk, cert.signature, cert.tbs_certificate_bytes,
|
||||
cert.signature_hash_algorithm)
|
||||
except (IOError, ValueError, InvalidSignature) as e:
|
||||
error_str = "verifying the signature of the cert located at {0} has failed. \
|
||||
Details: {1}".format(renewable_cert.cert, e)
|
||||
@@ -262,6 +248,37 @@ def verify_renewable_cert_sig(renewable_cert):
|
||||
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):
|
||||
""" Verifies that the private key and cert match.
|
||||
|
||||
|
||||
211
certbot/ocsp.py
211
certbot/ocsp.py
@@ -1,53 +1,79 @@
|
||||
"""Tools for checking certificate revocation."""
|
||||
import logging
|
||||
import re
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
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 util
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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.use_openssl_binary = enforce_openssl_binary_usage or not ocsp
|
||||
|
||||
if not util.exe_exists("openssl"):
|
||||
logger.info("openssl not installed, can't check revocation")
|
||||
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]
|
||||
if self.use_openssl_binary:
|
||||
if not util.exe_exists("openssl"):
|
||||
logger.info("openssl not installed, can't check revocation")
|
||||
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]
|
||||
|
||||
def ocsp_revoked(self, cert_path, chain_path):
|
||||
# type: (str, str) -> bool
|
||||
"""Get revoked status for a particular cert version.
|
||||
|
||||
.. todo:: Make this a non-blocking call
|
||||
|
||||
:param str cert_path: Path to certificate
|
||||
:param str chain_path: Path to intermediate cert
|
||||
:rtype bool or None:
|
||||
:returns: True if revoked; False if valid or the check failed
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
if self.broken:
|
||||
return False
|
||||
|
||||
|
||||
url, host = self.determine_ocsp_server(cert_path)
|
||||
if not host:
|
||||
url, host = _determine_ocsp_server(cert_path)
|
||||
if not host or not url:
|
||||
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!
|
||||
cmd = ["openssl", "ocsp",
|
||||
"-no_nonce",
|
||||
@@ -65,33 +91,131 @@ class RevocationChecker(object):
|
||||
except errors.SubprocessError:
|
||||
logger.info("OCSP check failed for %s (are we offline?)", cert_path)
|
||||
return False
|
||||
|
||||
return _translate_ocsp_query(cert_path, output, err)
|
||||
|
||||
|
||||
def determine_ocsp_server(self, cert_path):
|
||||
"""Extract the OCSP server host from a certificate.
|
||||
def _determine_ocsp_server(cert_path):
|
||||
# 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
|
||||
:rtype tuple:
|
||||
:returns: (OCSP server URL or None, OCSP server host or None)
|
||||
:param str cert_path: Path to the cert we're checking OCSP for
|
||||
:rtype tuple:
|
||||
:returns: (OCSP server URL or None, OCSP server host or None)
|
||||
|
||||
"""
|
||||
try:
|
||||
url, _err = util.run_script(
|
||||
["openssl", "x509", "-in", cert_path, "-noout", "-ocsp_uri"],
|
||||
log=logger.debug)
|
||||
except errors.SubprocessError:
|
||||
logger.info("Cannot extract OCSP URI from %s", cert_path)
|
||||
return None, None
|
||||
"""
|
||||
with open(cert_path, 'rb') as file_handler:
|
||||
cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend())
|
||||
try:
|
||||
extension = cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess)
|
||||
ocsp_oid = x509.AuthorityInformationAccessOID.OCSP
|
||||
descriptions = [description for description in extension.value
|
||||
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)
|
||||
try:
|
||||
response = requests.post(url, data=request_binary,
|
||||
headers={'Content-Type': 'application/ocsp-request'})
|
||||
except requests.exceptions.RequestException:
|
||||
logger.info("OCSP check failed for %s (are we offline?)", cert_path, exc_info=True)
|
||||
return False
|
||||
if response.status_code != 200:
|
||||
logger.info("OCSP check failed for %s (HTTP status: %d)", cert_path, response.status_code)
|
||||
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.')
|
||||
|
||||
# In following checks, two situations can occur:
|
||||
# * nextUpdate is set, and requirement is thisUpdate < now < nextUpdate
|
||||
# * nextUpdate is not set, and requirement is thisUpdate < now
|
||||
# NB1: We add a validity period tolerance to handle clock time inconsistencies,
|
||||
# value is 5 min like for OpenSSL.
|
||||
# NB2: Another check is to verify that thisUpdate is not too old, it is optional
|
||||
# for OpenSSL, so we do not do it here.
|
||||
# See OpenSSL implementation as a reference:
|
||||
# https://github.com/openssl/openssl/blob/ef45aa14c5af024fcb8bef1c9007f3d1c115bd85/crypto/ocsp/ocsp_cl.c#L338-L391
|
||||
now = datetime.now()
|
||||
if not response_ocsp.this_update:
|
||||
raise AssertionError('param thisUpdate is not set.')
|
||||
if response_ocsp.this_update > now + timedelta(minutes=5):
|
||||
raise AssertionError('param thisUpdate is in the future.')
|
||||
if response_ocsp.next_update and response_ocsp.next_update < now - timedelta(minutes=5):
|
||||
raise AssertionError('param nextUpdate is in the past.')
|
||||
|
||||
|
||||
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):
|
||||
"""Parse openssl's weird output to work out what it means."""
|
||||
@@ -102,7 +226,7 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors):
|
||||
|
||||
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.debug("Uncertain output:\n%s\nstderr:\n%s", ocsp_output, ocsp_errors)
|
||||
return False
|
||||
@@ -115,6 +239,5 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors):
|
||||
return True
|
||||
else:
|
||||
logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s",
|
||||
ocsp_output, ocsp_errors)
|
||||
ocsp_output, ocsp_errors)
|
||||
return False
|
||||
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
"""Tests for ocsp.py"""
|
||||
# pylint: disable=protected-access
|
||||
|
||||
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
|
||||
|
||||
from certbot import errors
|
||||
from certbot.tests import util as test_util
|
||||
|
||||
out = """Missing = in header key=value
|
||||
ocsp: Use -help for summary.
|
||||
"""
|
||||
|
||||
class OCSPTest(unittest.TestCase):
|
||||
|
||||
class OCSPTestOpenSSL(unittest.TestCase):
|
||||
"""
|
||||
OCSP revokation tests using OpenSSL binary.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
from certbot import ocsp
|
||||
@@ -22,7 +37,7 @@ class OCSPTest(unittest.TestCase):
|
||||
mock_communicate.communicate.return_value = (None, out)
|
||||
mock_popen.return_value = mock_communicate
|
||||
mock_exists.return_value = True
|
||||
self.checker = ocsp.RevocationChecker()
|
||||
self.checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True)
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
@@ -37,23 +52,23 @@ class OCSPTest(unittest.TestCase):
|
||||
mock_exists.return_value = True
|
||||
|
||||
from certbot import ocsp
|
||||
checker = ocsp.RevocationChecker()
|
||||
checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True)
|
||||
self.assertEqual(mock_popen.call_count, 1)
|
||||
self.assertEqual(checker.host_args("x"), ["Host=x"])
|
||||
|
||||
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.broken, False)
|
||||
|
||||
mock_exists.return_value = False
|
||||
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_log.call_count, 1)
|
||||
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')
|
||||
def test_ocsp_revoked(self, mock_run, mock_determine):
|
||||
self.checker.broken = True
|
||||
@@ -71,21 +86,12 @@ class OCSPTest(unittest.TestCase):
|
||||
self.assertEqual(self.checker.ocsp_revoked("x", "y"), False)
|
||||
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')
|
||||
@mock.patch('certbot.util.run_script')
|
||||
def test_determine_ocsp_server(self, mock_run, mock_info):
|
||||
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))
|
||||
from certbot import ocsp
|
||||
result = ocsp._determine_ocsp_server(cert_path)
|
||||
self.assertEqual(('http://ocsp.digicert.com', 'ocsp.digicert.com'), result)
|
||||
|
||||
@mock.patch('certbot.ocsp.logger')
|
||||
@mock.patch('certbot.util.run_script')
|
||||
@@ -112,6 +118,129 @@ class OCSPTest(unittest.TestCase):
|
||||
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
|
||||
openssl_confused = ("", """
|
||||
/etc/letsencrypt/live/example.org/cert.pem: good
|
||||
@@ -165,5 +294,6 @@ revoked
|
||||
""",
|
||||
"""Response verify OK""")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
41
certbot/tests/testdata/google_certificate.pem
vendored
Normal file
41
certbot/tests/testdata/google_certificate.pem
vendored
Normal file
@@ -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-----
|
||||
26
certbot/tests/testdata/google_issuer_certificate.pem
vendored
Normal file
26
certbot/tests/testdata/google_issuer_certificate.pem
vendored
Normal file
@@ -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-----
|
||||
@@ -62,7 +62,7 @@ def run_script(params, log=logger.error):
|
||||
"""Run the script with the given params.
|
||||
|
||||
: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:
|
||||
|
||||
@@ -1034,26 +1034,26 @@ ConfigArgParse==0.12.0 \
|
||||
configobj==5.0.6 \
|
||||
--hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \
|
||||
--no-binary configobj
|
||||
cryptography==2.2.2 \
|
||||
--hash=sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd \
|
||||
--hash=sha256:5251e7de0de66810833606439ca65c9b9e45da62196b0c88bfadf27740aac09f \
|
||||
--hash=sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04 \
|
||||
--hash=sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f \
|
||||
--hash=sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd \
|
||||
--hash=sha256:64b5c67acc9a7c83fbb4b69166f3105a0ab722d27934fac2cb26456718eec2ba \
|
||||
--hash=sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb \
|
||||
--hash=sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2 \
|
||||
--hash=sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037 \
|
||||
--hash=sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd \
|
||||
--hash=sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531 \
|
||||
--hash=sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63 \
|
||||
--hash=sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e \
|
||||
--hash=sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351 \
|
||||
--hash=sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a \
|
||||
--hash=sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563 \
|
||||
--hash=sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab \
|
||||
--hash=sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471 \
|
||||
--hash=sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887
|
||||
cryptography==2.5 \
|
||||
--hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \
|
||||
--hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \
|
||||
--hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \
|
||||
--hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \
|
||||
--hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \
|
||||
--hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \
|
||||
--hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \
|
||||
--hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \
|
||||
--hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \
|
||||
--hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \
|
||||
--hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \
|
||||
--hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \
|
||||
--hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \
|
||||
--hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \
|
||||
--hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \
|
||||
--hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \
|
||||
--hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \
|
||||
--hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \
|
||||
--hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401
|
||||
enum34==1.1.2 ; python_version < '3.4' \
|
||||
--hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \
|
||||
--hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501
|
||||
|
||||
@@ -60,26 +60,26 @@ ConfigArgParse==0.12.0 \
|
||||
configobj==5.0.6 \
|
||||
--hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \
|
||||
--no-binary configobj
|
||||
cryptography==2.2.2 \
|
||||
--hash=sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd \
|
||||
--hash=sha256:5251e7de0de66810833606439ca65c9b9e45da62196b0c88bfadf27740aac09f \
|
||||
--hash=sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04 \
|
||||
--hash=sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f \
|
||||
--hash=sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd \
|
||||
--hash=sha256:64b5c67acc9a7c83fbb4b69166f3105a0ab722d27934fac2cb26456718eec2ba \
|
||||
--hash=sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb \
|
||||
--hash=sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2 \
|
||||
--hash=sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037 \
|
||||
--hash=sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd \
|
||||
--hash=sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531 \
|
||||
--hash=sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63 \
|
||||
--hash=sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e \
|
||||
--hash=sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351 \
|
||||
--hash=sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a \
|
||||
--hash=sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563 \
|
||||
--hash=sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab \
|
||||
--hash=sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471 \
|
||||
--hash=sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887
|
||||
cryptography==2.5 \
|
||||
--hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \
|
||||
--hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \
|
||||
--hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \
|
||||
--hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \
|
||||
--hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \
|
||||
--hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \
|
||||
--hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \
|
||||
--hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \
|
||||
--hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \
|
||||
--hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \
|
||||
--hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \
|
||||
--hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \
|
||||
--hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \
|
||||
--hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \
|
||||
--hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \
|
||||
--hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \
|
||||
--hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \
|
||||
--hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \
|
||||
--hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401
|
||||
enum34==1.1.2 ; python_version < '3.4' \
|
||||
--hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \
|
||||
--hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501
|
||||
|
||||
@@ -15,9 +15,11 @@ command -v python > /dev/null || (echo "Error, python executable is not in the P
|
||||
|
||||
. ./tests/integration/_common.sh
|
||||
export PATH="$PATH:/usr/sbin" # /usr/sbin/nginx
|
||||
CURRENT_DIR="$(pwd)"
|
||||
|
||||
cleanup_and_exit() {
|
||||
EXIT_STATUS=$?
|
||||
cd $CURRENT_DIR
|
||||
if SERVER_STILL_RUNNING=`ps -p $python_server_pid -o pid=`
|
||||
then
|
||||
echo Kill server subprocess, left running by abnormal exit
|
||||
@@ -527,3 +529,56 @@ if [ "${BOULDER_INTEGRATION:-v1}" = "v2" ]; then
|
||||
fi
|
||||
|
||||
coverage report --fail-under 64 --include 'certbot/*' --show-missing
|
||||
|
||||
# Test OCSP status
|
||||
|
||||
## OCSP 1: Check stale OCSP status
|
||||
pushd ./tests/integration
|
||||
|
||||
OUT=`common certificates --config-dir sample-config`
|
||||
TEST_CERTS=`echo "$OUT" | grep TEST_CERT | wc -l`
|
||||
EXPIRED=`echo "$OUT" | grep EXPIRED | wc -l`
|
||||
|
||||
if [ "$TEST_CERTS" != 2 ] ; then
|
||||
echo "Did not find two test certs as expected ($TEST_CERTS)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$EXPIRED" != 2 ] ; then
|
||||
echo "Did not find two test certs as expected ($EXPIRED)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
popd
|
||||
|
||||
## OSCP 2: Check live certificate OCSP status (VALID)
|
||||
common --domains le-ocsp-check.wtf
|
||||
OUT=`common certificates`
|
||||
VALID=`echo $OUT | grep 'Domains: le-ocsp-check.wtf' -A 1 | grep VALID | wc -l`
|
||||
EXPIRED=`echo $OUT | grep 'Domains: le-ocsp-check.wtf' -A 1 | grep EXPIRED | wc -l`
|
||||
|
||||
if [ "$VALID" != 1 ] ; then
|
||||
echo "Expected le-ocsp-check.wtf to be VALID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$EXPIRED" != 0 ] ; then
|
||||
echo "Did not expect le-ocsp-check.wtf to be EXPIRED"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## OSCP 3: Check live certificate OCSP status (REVOKED)
|
||||
common revoke --cert-name le-ocsp-check.wtf --no-delete-after-revoke
|
||||
OUT=`common certificates`
|
||||
INVALID=`echo $OUT | grep 'Domains: le-ocsp-check.wtf' -A 1 | grep INVALID | wc -l`
|
||||
REVOKED=`echo $OUT | grep 'Domains: le-ocsp-check.wtf' -A 1 | grep REVOKED | wc -l`
|
||||
|
||||
if [ "$INVALID" != 1 ] ; then
|
||||
echo "Expected le-ocsp-check.wtf to be INVALID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$REVOKED" != 1 ] ; then
|
||||
echo "Expected le-ocsp-check.wtf to be REVOKED"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
../../archive/a.encryption-example.com/cert1.pem
|
||||
@@ -0,0 +1 @@
|
||||
../../archive/a.encryption-example.com/chain1.pem
|
||||
@@ -0,0 +1 @@
|
||||
../../archive/a.encryption-example.com/fullchain1.pem
|
||||
@@ -0,0 +1 @@
|
||||
../../archive/a.encryption-example.com/privkey1.pem
|
||||
@@ -0,0 +1 @@
|
||||
../../archive/b.encryption-example.com/cert1.pem
|
||||
@@ -0,0 +1 @@
|
||||
../../archive/b.encryption-example.com/chain1.pem
|
||||
@@ -0,0 +1 @@
|
||||
../../archive/b.encryption-example.com/fullchain1.pem
|
||||
@@ -0,0 +1 @@
|
||||
../../archive/b.encryption-example.com/privkey1.pem
|
||||
@@ -17,27 +17,6 @@ letsencrypt-auto certonly --no-self-upgrade -v --standalone --debug \
|
||||
--register-unsafely-without-email \
|
||||
--domain $PUBLIC_HOSTNAME --server $BOULDER_URL
|
||||
|
||||
# we have to jump through some hoops to cope with relative paths in renewal
|
||||
# conf files ...
|
||||
# 1. be in the right directory
|
||||
cd tests/letstest/testdata/
|
||||
|
||||
# 2. refer to the config with the same level of relativity that it itself
|
||||
# contains :/
|
||||
OUT=`letsencrypt-auto certificates --config-dir sample-config -v --no-self-upgrade`
|
||||
TEST_CERTS=`echo "$OUT" | grep TEST_CERT | wc -l`
|
||||
REVOKED=`echo "$OUT" | grep REVOKED | wc -l`
|
||||
|
||||
if [ "$TEST_CERTS" != 2 ] ; then
|
||||
echo "Did not find two test certs as expected ($TEST_CERTS)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$REVOKED" != 1 ] ; then
|
||||
echo "Did not find one revoked cert as expected ($REVOKED)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! letsencrypt-auto --help --no-self-upgrade | grep -F "letsencrypt-auto [SUBCOMMAND]"; then
|
||||
echo "letsencrypt-auto not included in help output!"
|
||||
exit 1
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../archive/a.encryption-example.com/cert1.pem
|
||||
@@ -1 +0,0 @@
|
||||
../../archive/a.encryption-example.com/chain1.pem
|
||||
@@ -1 +0,0 @@
|
||||
../../archive/a.encryption-example.com/fullchain1.pem
|
||||
@@ -1 +0,0 @@
|
||||
../../archive/a.encryption-example.com/privkey1.pem
|
||||
@@ -1 +0,0 @@
|
||||
../../archive/b.encryption-example.com/cert1.pem
|
||||
@@ -1 +0,0 @@
|
||||
../../archive/b.encryption-example.com/chain1.pem
|
||||
@@ -1 +0,0 @@
|
||||
../../archive/b.encryption-example.com/fullchain1.pem
|
||||
@@ -1 +0,0 @@
|
||||
../../archive/b.encryption-example.com/privkey1.pem
|
||||
Reference in New Issue
Block a user