Merge branch 'master' into nginx-utf8

This commit is contained in:
schoen
2020-03-13 16:28:24 -07:00
committed by GitHub
78 changed files with 924 additions and 415 deletions
+33 -4
View File
@@ -2,7 +2,32 @@
Certbot adheres to [Semantic Versioning](https://semver.org/).
## 1.3.0 - master
## 1.4.0 - master
### Added
* Added serial number of certificate to the output of `certbot certificates`
* Expose two new environment variables in the authenticator and cleanup scripts used by
the `manual` plugin: `CERTBOT_REMAINING_CHALLENGES` is equal to the number of challenges
remaining after the current challenge, `CERTBOT_ALL_DOMAINS` is a comma-separated list
of all domains challenged for the current certificate.
* Added TLS-ALPN-01 challenge support in the `acme` library. Support of this
challenge in the Certbot client is planned to be added in a future release.
### Changed
*
### Fixed
* When using an RFC 8555 compliant endpoint, the `acme` library no longer sends the
`resource` field in any requests or the `type` field when responding to challenges.
* Fix nginx plugin crash when non-ASCII configuration file is being read (instead,
the user will be warned that UTF-8 must be used).
More details about these changes can be found on our GitHub repo.
## 1.3.0 - 2020-03-03
### Added
@@ -10,14 +35,19 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
determine the OCSP status of certificates.
* Don't verify the existing certificate in HTTP01Response.simple_verify, for
compatibility with the real-world ACME challenge checks.
* Added support for `$hostname` in nginx `server_name` directive
### Changed
* certbot._internal.cli is now a package split in submodules instead of a whole module.
* Certbot will now renew certificates early if they have been revoked according
to OCSP.
* Fix acme module warnings when response Content-Type includes params (e.g. charset).
* Fixed issue where webroot plugin would incorrectly raise `Read-only file system`
error when creating challenge directories (issue #7165).
### Fixed
* Fix nginx plugin crash when non-ASCII configuration file is being read
* Fix Apache plugin to use less restrictive umask for making the challenge directory when a restrictive umask was set when certbot was started.
More details about these changes can be found on our GitHub repo.
@@ -26,7 +56,6 @@ More details about these changes can be found on our GitHub repo.
### Added
* Added support for Cloudflare's limited-scope API Tokens
* Added support for `$hostname` in nginx `server_name` directive
### Changed
+1 -5
View File
@@ -71,16 +71,12 @@ ACME spec: http://ietf-wg-acme.github.io/acme/
ACME working area in github: https://github.com/ietf-wg-acme/acme
|build-status| |coverage| |container|
|build-status| |container|
.. |build-status| image:: https://travis-ci.com/certbot/certbot.svg?branch=master
:target: https://travis-ci.com/certbot/certbot
:alt: Travis CI status
.. |coverage| image:: https://codecov.io/gh/certbot/certbot/branch/master/graph/badge.svg
:target: https://codecov.io/gh/certbot/certbot
:alt: Coverage status
.. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status
:target: https://quay.io/repository/letsencrypt/letsencrypt
:alt: Docker Repository on Quay.io
+1 -1
View File
@@ -1,4 +1,4 @@
"""Certbot client."""
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
__version__ = '1.3.0.dev0'
__version__ = '1.4.0.dev0'
+7 -4
View File
@@ -276,12 +276,15 @@ def human_readable_cert_info(config, cert, skip_filter_checks=False):
status = "VALID: {0} days".format(diff.days)
valid_string = "{0} ({1})".format(cert.target_expiry, status)
serial = format(crypto_util.get_serial_from_cert(cert.cert_path), 'x')
certinfo.append(" Certificate Name: {0}\n"
" Domains: {1}\n"
" Expiry Date: {2}\n"
" Certificate Path: {3}\n"
" Private Key Path: {4}".format(
" Serial Number: {1}\n"
" Domains: {2}\n"
" Expiry Date: {3}\n"
" Certificate Path: {4}\n"
" Private Key Path: {5}".format(
cert.lineagename,
serial,
" ".join(cert.names()),
valid_string,
cert.fullchain,
+1 -1
View File
@@ -394,7 +394,7 @@ def _find_domains_or_certname(config, installer, question=None):
:param installer: Installer object
:type installer: interfaces.IInstaller
:param `str` question: Overriding dialog question to ask the user if asked
:param `str` question: Overriding default question to ask the user if asked
to choose from domain names.
:returns: Two-part tuple of domains and certname
-34
View File
@@ -1,34 +0,0 @@
"""Send e-mail notification to system administrators."""
import email
import smtplib
import socket
import subprocess
def notify(subject, whom, what):
"""Send email notification.
Try to notify the addressee (``whom``) by e-mail, with Subject:
defined by ``subject`` and message body by ``what``.
"""
msg = email.message_from_string(what)
msg.add_header("From", "Certbot renewal agent <root>")
msg.add_header("To", whom)
msg.add_header("Subject", subject)
msg = msg.as_string()
try:
lmtp = smtplib.LMTP()
lmtp.connect()
lmtp.sendmail("root", [whom], msg)
except (smtplib.SMTPHeloError, smtplib.SMTPRecipientsRefused,
smtplib.SMTPSenderRefused, smtplib.SMTPDataError, socket.error):
# We should try using /usr/sbin/sendmail in this case
try:
proc = subprocess.Popen(["/usr/sbin/sendmail", "-t"],
stdin=subprocess.PIPE)
proc.communicate(msg)
except OSError:
return False
return True
+13 -8
View File
@@ -35,7 +35,11 @@ class Authenticator(common.Plugin):
'is the validation string, and $CERTBOT_TOKEN is the filename of the '
'resource requested when performing an HTTP-01 challenge. An additional '
'cleanup script can also be provided and can use the additional variable '
'$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth script.')
'$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth script.'
'For both authenticator and cleanup script, on HTTP-01 and DNS-01 challenges,'
'$CERTBOT_REMAINING_CHALLENGES will be equal to the number of challenges that '
'remain after the current one, and $CERTBOT_ALL_DOMAINS contains a comma-separated '
'list of all domains that are challenged for the current certificate.')
_DNS_INSTRUCTIONS = """\
Please deploy a DNS TXT record under the name
{domain} with the following value:
@@ -109,14 +113,13 @@ permitted by DNS standards.)
def perform(self, achalls): # pylint: disable=missing-function-docstring
self._verify_ip_logging_ok()
if self.conf('auth-hook'):
perform_achall = self._perform_achall_with_script
else:
perform_achall = self._perform_achall_manually
responses = []
for achall in achalls:
perform_achall(achall)
if self.conf('auth-hook'):
self._perform_achall_with_script(achall, achalls)
else:
self._perform_achall_manually(achall)
responses.append(achall.response(achall.account_key))
return responses
@@ -134,9 +137,11 @@ permitted by DNS standards.)
else:
raise errors.PluginError('Must agree to IP logging to proceed')
def _perform_achall_with_script(self, achall):
def _perform_achall_with_script(self, achall, achalls):
env = dict(CERTBOT_DOMAIN=achall.domain,
CERTBOT_VALIDATION=achall.validation(achall.account_key))
CERTBOT_VALIDATION=achall.validation(achall.account_key),
CERTBOT_ALL_DOMAINS=','.join(one_achall.domain for one_achall in achalls),
CERTBOT_REMAINING_CHALLENGES=str(len(achalls) - achalls.index(achall) - 1))
if isinstance(achall.chall, challenges.HTTP01):
env['CERTBOT_TOKEN'] = achall.chall.encode('token')
else:
+10 -8
View File
@@ -1,7 +1,6 @@
"""Webroot plugin."""
import argparse
import collections
import errno
import json
import logging
@@ -71,7 +70,7 @@ to serve all files under specified web root ({0})."""
super(Authenticator, self).__init__(*args, **kwargs)
self.full_roots = {} # type: Dict[str, str]
self.performed = collections.defaultdict(set) \
# type: DefaultDict[str, Set[achallenges.KeyAuthorizationAnnotatedChallenge]]
# type: DefaultDict[str, Set[achallenges.KeyAuthorizationAnnotatedChallenge]]
# stack of dirs successfully created by this authenticator
self._created_dirs = [] # type: List[str]
@@ -137,7 +136,7 @@ to serve all files under specified web root ({0})."""
"webroot when using the webroot plugin.")
return None if index == 0 else known_webroots[index - 1] # code == display_util.OK
def _prompt_for_new_webroot(self, domain, allowraise=False):
def _prompt_for_new_webroot(self, domain, allowraise=False): # pylint: no-self-use
code, webroot = ops.validated_directory(
_validate_webroot,
"Input the webroot for {0}:".format(domain),
@@ -170,6 +169,10 @@ to serve all files under specified web root ({0})."""
# We ignore the last prefix in the next iteration,
# as it does not correspond to a folder path ('/' or 'C:')
for prefix in sorted(util.get_prefixes(self.full_roots[name])[:-1], key=len):
if os.path.isdir(prefix):
# Don't try to create directory if it already exists, as some filesystems
# won't reliably raise EEXIST or EISDIR if directory exists.
continue
try:
# Set owner as parent directory if possible, apply mode for Linux/Windows.
# For Linux, this is coupled with the "umask" call above because
@@ -184,14 +187,13 @@ to serve all files under specified web root ({0})."""
logger.info("Unable to change owner and uid of webroot directory")
logger.debug("Error was: %s", exception)
except OSError as exception:
if exception.errno not in (errno.EEXIST, errno.EISDIR):
raise errors.PluginError(
"Couldn't create root for {0} http-01 "
"challenge responses: {1}".format(name, exception))
raise errors.PluginError(
"Couldn't create root for {0} http-01 "
"challenge responses: {1}".format(name, exception))
finally:
os.umask(old_umask)
def _get_validation_path(self, root_path, achall):
def _get_validation_path(self, root_path, achall): # pylint: no-self-use
return os.path.join(root_path, achall.chall.encode("token"))
def _perform_single(self, achall):
+20 -13
View File
@@ -15,6 +15,7 @@ import certbot
from certbot import crypto_util
from certbot import errors
from certbot import interfaces
from certbot import ocsp
from certbot import util
from certbot._internal import cli
from certbot._internal import constants
@@ -882,27 +883,33 @@ class RenewableCert(interfaces.RenewableCert):
with open(target) as f:
return crypto_util.get_names_from_cert(f.read())
def ocsp_revoked(self, version=None):
# pylint: disable=unused-argument
def ocsp_revoked(self, version):
"""Is the specified cert version revoked according to OCSP?
Also returns True if the cert version is declared as intended
to be revoked according to Let's Encrypt OCSP extensions.
(If no version is specified, uses the current version.)
This method is not yet implemented and currently always returns
False.
Also returns True if the cert version is declared as revoked
according to OCSP. If OCSP status could not be determined, False
is returned.
:param int version: the desired version number
:returns: whether the certificate is or will be revoked
:returns: True if the certificate is revoked, otherwise, False
:rtype: bool
"""
# XXX: This query and its associated network service aren't
# implemented yet, so we currently return False (indicating that the
# certificate is not revoked).
return False
cert_path = self.version("cert", version)
chain_path = self.version("chain", version)
# While the RevocationChecker should return False if it failed to
# determine the OCSP status, let's ensure we don't crash Certbot by
# catching all exceptions here.
try:
return ocsp.RevocationChecker().ocsp_revoked_by_paths(cert_path,
chain_path)
except Exception as e: # pylint: disable=broad-except
logger.warning(
"An error occurred determining the OCSP status of %s.",
cert_path)
logger.debug(str(e))
return False
def autorenewal_is_enabled(self):
"""Is automatic renewal enabled for this cert?
+14
View File
@@ -491,3 +491,17 @@ def cert_and_chain_from_fullchain(fullchain_pem):
crypto.load_certificate(crypto.FILETYPE_PEM, fullchain_pem)).decode()
chain = fullchain_pem[len(cert):].lstrip()
return (cert, chain)
def get_serial_from_cert(cert_path):
"""Retrieve the serial number of a certificate from certificate path
:param str cert_path: path to a cert in PEM format
:returns: serial number of the certificate
:rtype: int
"""
# pylint: disable=redefined-outer-name
with open(cert_path) as f:
x509 = crypto.load_certificate(crypto.FILETYPE_PEM,
f.read())
return x509.get_serial_number()
+1 -1
View File
@@ -107,7 +107,7 @@ def choose_names(installer, question=None):
:param installer: An installer object
:type installer: :class:`certbot.interfaces.IInstaller`
:param `str` question: Overriding dialog question to ask the user if asked
:param `str` question: Overriding default question to ask the user if asked
to choose from domain names.
:returns: List of selected names
+12 -1
View File
@@ -68,8 +68,19 @@ class RevocationChecker(object):
:rtype: bool
"""
cert_path, chain_path = cert.cert_path, cert.chain_path
return self.ocsp_revoked_by_paths(cert.cert_path, cert.chain_path)
def ocsp_revoked_by_paths(self, cert_path, chain_path):
# type: (str, str) -> bool
"""Performs the OCSP revocation check
:param str cert_path: Certificate filepath
:param str chain_path: Certificate chain filepath
:returns: True if revoked; False if valid or the check failed or cert is expired.
:rtype: bool
"""
if self.broken:
return False
+2 -4
View File
@@ -25,11 +25,9 @@ from certbot._internal import lock
from certbot.compat import filesystem
from certbot.compat import os
if sys.platform.startswith('linux'):
_USE_DISTRO = sys.platform.startswith('linux')
if _USE_DISTRO:
import distro
_USE_DISTRO = True
else:
_USE_DISTRO = False
logger = logging.getLogger(__name__)
+1 -1
View File
@@ -113,7 +113,7 @@ optional arguments:
case, and to know when to deprecate support for past
Python versions and flags. If you wish to hide this
information from the Let's Encrypt server, set this to
"". (default: CertbotACMEClient/1.2.0 (certbot(-auto);
"". (default: CertbotACMEClient/1.3.0 (certbot(-auto);
OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY
(SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel).
The flags encoded in the user agent are: --duplicate,
+16 -38
View File
@@ -280,6 +280,7 @@ pritunl_ N Y Install certificates in pritunl distributed OpenVPN
proxmox_ N Y Install certificates in Proxmox Virtualization servers
dns-standalone_ Y N Obtain certificates via an integrated DNS server
dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server
dns-clouddns_ Y N DNS Authentication using CloudDNS API
================== ==== ==== ===============================================================
.. _haproxy: https://github.com/greenhost/certbot-haproxy
@@ -291,6 +292,7 @@ dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server
.. _external-auth: https://github.com/EnigmaBridge/certbot-external-auth
.. _dns-standalone: https://github.com/siilike/certbot-dns-standalone
.. _dns-ispconfig: https://github.com/m42e/certbot-dns-ispconfig
.. _dns-clouddns: https://github.com/vshosting/certbot-dns-clouddns
If you're interested, you can also :ref:`write your own plugin <dev-plugin>`.
@@ -485,43 +487,6 @@ If you want your hook to run only after a successful renewal, use
``certbot renew --deploy-hook /path/to/deploy-hook-script``
For example, if you have a daemon that does not read its certificates as the
root user, a deploy hook like this can copy them to the correct location and
apply appropriate file permissions.
/path/to/deploy-hook-script
.. code-block:: none
#!/bin/sh
set -e
for domain in $RENEWED_DOMAINS; do
case $domain in
example.com)
daemon_cert_root=/etc/some-daemon/certs
# Make sure the certificate and private key files are
# never world readable, even just for an instant while
# we're copying them into daemon_cert_root.
umask 077
cp "$RENEWED_LINEAGE/fullchain.pem" "$daemon_cert_root/$domain.cert"
cp "$RENEWED_LINEAGE/privkey.pem" "$daemon_cert_root/$domain.key"
# Apply the proper file ownership and permissions for
# the daemon to read its certificate and key.
chown some-daemon "$daemon_cert_root/$domain.cert" \
"$daemon_cert_root/$domain.key"
chmod 400 "$daemon_cert_root/$domain.cert" \
"$daemon_cert_root/$domain.key"
service some-daemon restart >/dev/null
;;
esac
done
You can also specify hooks by placing files in subdirectories of Certbot's
configuration directory. Assuming your configuration directory is
``/etc/letsencrypt``, any executable files found in
@@ -686,6 +651,17 @@ your (web) server configuration directly to those files (or create
symlinks). During the renewal_, ``/etc/letsencrypt/live`` is updated
with the latest necessary files.
For historical reasons, the containing directories are created with
permissions of ``0700`` meaning that certificates are accessible only
to servers that run as the root user. **If you will never downgrade
to an older version of Certbot**, then you can safely fix this using
``chmod 0755 /etc/letsencrypt/{live,archive}``.
For servers that drop root privileges before attempting to read the
private key file, you will also need to use ``chgrp`` and ``chmod
0640`` to allow the server to read
``/etc/letsencrypt/live/$domain/privkey.pem``.
.. note:: ``/etc/letsencrypt/archive`` and ``/etc/letsencrypt/keys``
contain all previous keys and certificates, while
``/etc/letsencrypt/live`` symlinks to the latest versions.
@@ -762,8 +738,10 @@ the ``cleanup.sh`` script. Additionally certbot will pass relevant environment
variables to these scripts:
- ``CERTBOT_DOMAIN``: The domain being authenticated
- ``CERTBOT_VALIDATION``: The validation string (HTTP-01 and DNS-01 only)
- ``CERTBOT_VALIDATION``: The validation string
- ``CERTBOT_TOKEN``: Resource name part of the HTTP-01 challenge (HTTP-01 only)
- ``CERTBOT_REMAINING_CHALLENGES``: Number of challenges remaining after the current challenge
- ``CERTBOT_ALL_DOMAINS``: A comma-separated list of all domains challenged for the current certificate
Additionally for cleanup:
+1 -1
View File
@@ -1,2 +1,2 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.40.0
-e acme[dev]
+1 -1
View File
@@ -36,7 +36,7 @@ version = meta['version']
# specified here to avoid masking the more specific request requirements in
# acme. See https://github.com/pypa/pip/issues/988 for more info.
install_requires = [
'acme>=0.40.0',
'acme>=1.4.0.dev0',
# We technically need ConfigArgParse 0.10.0 for Python 2.6 support, but
# saying so here causes a runtime error against our temporary fork of 0.9.3
# in which we added 2.6 support (see #2243), so we relax the requirement.
+3 -1
View File
@@ -200,9 +200,11 @@ class CertificatesTest(BaseCertManagerTest):
self.assertTrue(mock_utility.called)
shutil.rmtree(empty_tempdir)
@mock.patch('certbot.crypto_util.get_serial_from_cert')
@mock.patch('certbot._internal.cert_manager.ocsp.RevocationChecker.ocsp_revoked')
def test_report_human_readable(self, mock_revoked):
def test_report_human_readable(self, mock_revoked, mock_serial):
mock_revoked.return_value = None
mock_serial.return_value = 1234567890
from certbot._internal import cert_manager
import datetime
import pytz
+12 -6
View File
@@ -30,15 +30,23 @@ class TestReadFile(TempDirTestCase):
# However a relative path between two different drives is invalid. So we move to
# self.tempdir to ensure that we stay on the same drive.
os.chdir(self.tempdir)
rel_test_path = os.path.relpath(os.path.join(self.tempdir, 'foo'))
# The read-only filesystem introduced with macOS Catalina can break
# code using relative paths below. See
# https://bugs.python.org/issue38295 for another example of this.
# Eliminating any possible symlinks in self.tempdir before passing
# it to os.path.relpath solves the problem. This is done by calling
# filesystem.realpath which removes any symlinks in the path on
# POSIX systems.
real_path = filesystem.realpath(os.path.join(self.tempdir, 'foo'))
relative_path = os.path.relpath(real_path)
self.assertRaises(
argparse.ArgumentTypeError, cli.read_file, rel_test_path)
argparse.ArgumentTypeError, cli.read_file, relative_path)
test_contents = b'bar\n'
with open(rel_test_path, 'wb') as f:
with open(relative_path, 'wb') as f:
f.write(test_contents)
path, contents = cli.read_file(rel_test_path)
path, contents = cli.read_file(relative_path)
self.assertEqual(path, os.path.abspath(path))
self.assertEqual(contents, test_contents)
finally:
@@ -142,7 +150,6 @@ class ParseTest(unittest.TestCase):
self.assertTrue("how a certificate is deployed" in out)
self.assertTrue("--webroot-path" in out)
self.assertTrue("--text" not in out)
self.assertTrue("--dialog" not in out)
self.assertTrue("%s" not in out)
self.assertTrue("{0}" not in out)
self.assertTrue("--renew-hook" not in out)
@@ -203,7 +210,6 @@ class ParseTest(unittest.TestCase):
self.assertTrue("how a certificate is deployed" in out)
self.assertTrue("--webroot-path" in out)
self.assertTrue("--text" not in out)
self.assertTrue("--dialog" not in out)
self.assertTrue("%s" not in out)
self.assertTrue("{0}" not in out)
-52
View File
@@ -1,52 +0,0 @@
"""Tests for certbot._internal.notify."""
import socket
import unittest
import mock
class NotifyTests(unittest.TestCase):
"""Tests for the notifier."""
@mock.patch("certbot._internal.notify.smtplib.LMTP")
def test_smtp_success(self, mock_lmtp):
from certbot._internal.notify import notify
lmtp_obj = mock.MagicMock()
mock_lmtp.return_value = lmtp_obj
self.assertTrue(notify("Goose", "auntrhody@example.com",
"The old grey goose is dead."))
self.assertEqual(lmtp_obj.connect.call_count, 1)
self.assertEqual(lmtp_obj.sendmail.call_count, 1)
@mock.patch("certbot._internal.notify.smtplib.LMTP")
@mock.patch("certbot._internal.notify.subprocess.Popen")
def test_smtp_failure(self, mock_popen, mock_lmtp):
from certbot._internal.notify import notify
lmtp_obj = mock.MagicMock()
mock_lmtp.return_value = lmtp_obj
lmtp_obj.sendmail.side_effect = socket.error(17)
proc = mock.MagicMock()
mock_popen.return_value = proc
self.assertTrue(notify("Goose", "auntrhody@example.com",
"The old grey goose is dead."))
self.assertEqual(lmtp_obj.sendmail.call_count, 1)
self.assertEqual(proc.communicate.call_count, 1)
@mock.patch("certbot._internal.notify.smtplib.LMTP")
@mock.patch("certbot._internal.notify.subprocess.Popen")
def test_everything_fails(self, mock_popen, mock_lmtp):
from certbot._internal.notify import notify
lmtp_obj = mock.MagicMock()
mock_lmtp.return_value = lmtp_obj
lmtp_obj.sendmail.side_effect = socket.error(17)
proc = mock.MagicMock()
mock_popen.return_value = proc
proc.communicate.side_effect = OSError("What we have here is a "
"failure to communicate.")
self.assertFalse(notify("Goose", "auntrhody@example.com",
"The old grey goose is dead."))
self.assertEqual(lmtp_obj.sendmail.call_count, 1)
self.assertEqual(proc.communicate.call_count, 1)
if __name__ == "__main__":
unittest.main() # pragma: no cover
+1 -1
View File
@@ -177,7 +177,7 @@ class InstallerTest(test_util.ConfigTestCase):
class AddrTest(unittest.TestCase):
"""Tests for certbot._internal.client.plugins.common.Addr."""
"""Tests for certbot.plugins.common.Addr."""
def setUp(self):
from certbot.plugins.common import Addr
+13 -6
View File
@@ -72,16 +72,23 @@ class AuthenticatorTest(test_util.TempDirTestCase):
self.config.manual_public_ip_logging_ok = True
self.config.manual_auth_hook = (
'{0} -c "from __future__ import print_function;'
'from certbot.compat import os; print(os.environ.get(\'CERTBOT_DOMAIN\'));'
'from certbot.compat import os;'
'print(os.environ.get(\'CERTBOT_DOMAIN\'));'
'print(os.environ.get(\'CERTBOT_TOKEN\', \'notoken\'));'
'print(os.environ.get(\'CERTBOT_VALIDATION\', \'novalidation\'));"'
'print(os.environ.get(\'CERTBOT_VALIDATION\', \'novalidation\'));'
'print(os.environ.get(\'CERTBOT_ALL_DOMAINS\'));'
'print(os.environ.get(\'CERTBOT_REMAINING_CHALLENGES\'));"'
.format(sys.executable))
dns_expected = '{0}\n{1}\n{2}'.format(
dns_expected = '{0}\n{1}\n{2}\n{3}\n{4}'.format(
self.dns_achall.domain, 'notoken',
self.dns_achall.validation(self.dns_achall.account_key))
http_expected = '{0}\n{1}\n{2}'.format(
self.dns_achall.validation(self.dns_achall.account_key),
','.join(achall.domain for achall in self.achalls),
len(self.achalls) - self.achalls.index(self.dns_achall) - 1)
http_expected = '{0}\n{1}\n{2}\n{3}\n{4}'.format(
self.http_achall.domain, self.http_achall.chall.encode('token'),
self.http_achall.validation(self.http_achall.account_key))
self.http_achall.validation(self.http_achall.account_key),
','.join(achall.domain for achall in self.achalls),
len(self.achalls) - self.achalls.index(self.http_achall) - 1)
self.assertEqual(
self.auth.perform(self.achalls),
+29 -4
View File
@@ -672,10 +672,35 @@ class RenewableCertTests(BaseRenewableCertTest):
errors.CertStorageError,
self.test_rc._update_link_to, "elephant", 17)
def test_ocsp_revoked(self):
# XXX: This is currently hardcoded to False due to a lack of an
# OCSP server to test against.
self.assertFalse(self.test_rc.ocsp_revoked())
@mock.patch("certbot.ocsp.RevocationChecker.ocsp_revoked_by_paths")
def test_ocsp_revoked(self, mock_checker):
# Write out test files
for kind in ALL_FOUR:
self._write_out_kind(kind, 1)
version = self.test_rc.latest_common_version()
expected_cert_path = self.test_rc.version("cert", version)
expected_chain_path = self.test_rc.version("chain", version)
# Test with cert revoked
mock_checker.return_value = True
self.assertTrue(self.test_rc.ocsp_revoked(version))
self.assertEqual(mock_checker.call_args[0][0], expected_cert_path)
self.assertEqual(mock_checker.call_args[0][1], expected_chain_path)
# Test with cert not revoked
mock_checker.return_value = False
self.assertFalse(self.test_rc.ocsp_revoked(version))
self.assertEqual(mock_checker.call_args[0][0], expected_cert_path)
self.assertEqual(mock_checker.call_args[0][1], expected_chain_path)
# Test with error
mock_checker.side_effect = ValueError
with mock.patch("certbot._internal.storage.logger.warning") as logger:
self.assertFalse(self.test_rc.ocsp_revoked(version))
self.assertEqual(mock_checker.call_args[0][0], expected_cert_path)
self.assertEqual(mock_checker.call_args[0][1], expected_chain_path)
log_msg = logger.call_args[0][0]
self.assertIn("An error occurred determining the OCSP status", log_msg)
def test_add_time_interval(self):
from certbot._internal import storage