mirror of
https://github.com/certbot/certbot.git
synced 2026-08-05 04:41:51 +02:00
Merge branch 'master' into storage_paranoia
This commit is contained in:
@@ -56,7 +56,7 @@ class Account(object): # pylint: disable=too-few-public-methods
|
||||
|
||||
self.id = hashlib.md5(
|
||||
self.key.key.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.DER,
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo)
|
||||
).hexdigest()
|
||||
# Implementation note: Email? Multiple accounts can have the
|
||||
|
||||
@@ -153,10 +153,8 @@ class AuthHandler(object):
|
||||
"""
|
||||
active_achalls = []
|
||||
for achall, resp in itertools.izip(achalls, resps):
|
||||
# XXX: make sure that all achalls, including those
|
||||
# corresponding to None or False returned from
|
||||
# Authenticator are removed from the queue and thus avoid
|
||||
# infinite loop
|
||||
# This line needs to be outside of the if block below to
|
||||
# ensure failed challenges are cleaned up correctly
|
||||
active_achalls.append(achall)
|
||||
|
||||
# Don't send challenges for None and False authenticator responses
|
||||
|
||||
+172
-69
@@ -28,7 +28,6 @@ from letsencrypt import configuration
|
||||
from letsencrypt import constants
|
||||
from letsencrypt import client
|
||||
from letsencrypt import crypto_util
|
||||
from letsencrypt import errors
|
||||
from letsencrypt import interfaces
|
||||
from letsencrypt import le_util
|
||||
from letsencrypt import log
|
||||
@@ -37,6 +36,7 @@ from letsencrypt import storage
|
||||
|
||||
from letsencrypt.display import util as display_util
|
||||
from letsencrypt.display import ops as display_ops
|
||||
from letsencrypt.errors import Error, PluginSelectionError, CertStorageError
|
||||
from letsencrypt.plugins import disco as plugins_disco
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ logger = logging.getLogger(__name__)
|
||||
# This is the stub to include in help generated by argparse
|
||||
|
||||
SHORT_USAGE = """
|
||||
letsencrypt [SUBCOMMAND] [options] [domains]
|
||||
letsencrypt [SUBCOMMAND] [options] [-d domain] [-d domain] ...
|
||||
|
||||
The Let's Encrypt agent can obtain and install HTTPS/TLS/SSL certificates. By
|
||||
default, it will attempt to use a webserver both for obtaining and installing
|
||||
@@ -59,7 +59,7 @@ the cert. """
|
||||
# altogether
|
||||
USAGE = SHORT_USAGE + """Major SUBCOMMANDS are:
|
||||
|
||||
(default) everything Obtain & install a cert in your current webserver
|
||||
(default) run Obtain & install a cert in your current webserver
|
||||
auth Authenticate & obtain cert, but do not install it
|
||||
install Install a previously obtained cert in a server
|
||||
revoke Revoke a previously obtained certificate
|
||||
@@ -70,7 +70,7 @@ Choice of server for authentication/installation:
|
||||
|
||||
--apache Use the Apache plugin for authentication & installation
|
||||
--nginx Use the Nginx plugin for authentication & installation
|
||||
--standalone Run a standalone HTTPS server (for authentication only)
|
||||
--standalone Run a standalone webserver for authentication
|
||||
OR:
|
||||
--authenticator standalone --installer nginx
|
||||
|
||||
@@ -91,8 +91,8 @@ def _find_domains(args, installer):
|
||||
domains = args.domains
|
||||
|
||||
if not domains:
|
||||
raise errors.Error("Please specify --domains, or --installer that "
|
||||
"will help in domain names autodiscovery")
|
||||
raise Error("Please specify --domains, or --installer that "
|
||||
"will help in domain names autodiscovery")
|
||||
|
||||
return domains
|
||||
|
||||
@@ -144,9 +144,9 @@ def _determine_account(args, config):
|
||||
try:
|
||||
acc, acme = client.register(
|
||||
config, account_storage, tos_cb=_tos_cb)
|
||||
except errors.Error as error:
|
||||
except Error as error:
|
||||
logger.debug(error, exc_info=True)
|
||||
raise errors.Error(
|
||||
raise Error(
|
||||
"Unable to register an account with ACME server")
|
||||
|
||||
args.account = acc.id
|
||||
@@ -180,7 +180,7 @@ def _find_duplicative_certs(config, domains):
|
||||
try:
|
||||
full_path = os.path.join(configs_dir, renewal_file)
|
||||
candidate_lineage = storage.RenewableCert(full_path, cli_config)
|
||||
except (errors.CertStorageError, IOError):
|
||||
except (CertStorageError, IOError):
|
||||
logger.warning("Renewal configuration file %s is broken. "
|
||||
"Skipping.", full_path)
|
||||
continue
|
||||
@@ -252,7 +252,7 @@ def _treat_as_renewal(config, domains):
|
||||
br=os.linesep
|
||||
),
|
||||
reporter_util.HIGH_PRIORITY)
|
||||
raise errors.Error(
|
||||
raise Error(
|
||||
"User did not use proper CLI and would like "
|
||||
"to reinvoke the client.")
|
||||
|
||||
@@ -262,12 +262,31 @@ def _treat_as_renewal(config, domains):
|
||||
return None
|
||||
|
||||
|
||||
def _report_new_cert(cert_path):
|
||||
"""Reports the creation of a new certificate to the user."""
|
||||
def _report_new_cert(cert_path, fullchain_path):
|
||||
"""Reports the creation of a new certificate to the user.
|
||||
|
||||
:param str cert_path: path to cert
|
||||
:param str fullchain_path: path to full chain
|
||||
|
||||
"""
|
||||
expiry = crypto_util.notAfter(cert_path).date()
|
||||
reporter_util = zope.component.getUtility(interfaces.IReporter)
|
||||
reporter_util.add_message("Congratulations! Your certificate has been "
|
||||
"saved at {0}.".format(cert_path),
|
||||
reporter_util.MEDIUM_PRIORITY)
|
||||
if fullchain_path:
|
||||
# Print the path to fullchain.pem because that's what modern webservers
|
||||
# (Nginx and Apache2.4) will want.
|
||||
and_chain = "and chain have"
|
||||
path = fullchain_path
|
||||
else:
|
||||
# Unless we're in .csr mode and there really isn't one
|
||||
and_chain = "has "
|
||||
path = cert_path
|
||||
# XXX Perhaps one day we could detect the presence of known old webservers
|
||||
# and say something more informative here.
|
||||
msg = ("Congratulations! Your certificate {0} been saved at {1}."
|
||||
" Your cert will expire on {2}. To obtain a new version of the "
|
||||
"certificate in the future, simply run Let's Encrypt again."
|
||||
.format(and_chain, path, expiry))
|
||||
reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY)
|
||||
|
||||
|
||||
def _auth_from_domains(le_client, config, domains, plugins):
|
||||
@@ -293,43 +312,119 @@ def _auth_from_domains(le_client, config, domains, plugins):
|
||||
# TREAT AS NEW REQUEST
|
||||
lineage = le_client.obtain_and_enroll_certificate(domains, plugins)
|
||||
if not lineage:
|
||||
raise errors.Error("Certificate could not be obtained")
|
||||
raise Error("Certificate could not be obtained")
|
||||
|
||||
_report_new_cert(lineage.cert)
|
||||
reporter_util = zope.component.getUtility(interfaces.IReporter)
|
||||
reporter_util.add_message(
|
||||
"Your certificate will expire on {0}. To obtain a new version of the "
|
||||
"certificate in the future, simply run this client again.".format(
|
||||
lineage.notafter().date()),
|
||||
reporter_util.MEDIUM_PRIORITY)
|
||||
_report_new_cert(lineage.cert, lineage.fullchain)
|
||||
|
||||
return lineage
|
||||
|
||||
|
||||
def set_configurator(previously, now):
|
||||
"""
|
||||
Setting configurators multiple ways is okay, as long as they all agree
|
||||
:param str previously: previously identified request for the installer/authenticator
|
||||
:param str requested: the request currently being processed
|
||||
"""
|
||||
if now is None:
|
||||
# we're not actually setting anything
|
||||
return previously
|
||||
if previously:
|
||||
if previously != now:
|
||||
msg = "Too many flags setting configurators/installers/authenticators {0} -> {1}"
|
||||
raise PluginSelectionError(msg.format(repr(previously), repr(now)))
|
||||
return now
|
||||
|
||||
|
||||
def diagnose_configurator_problem(cfg_type, requested, plugins):
|
||||
"""
|
||||
Raise the most helpful error message about a plugin being unavailable
|
||||
|
||||
:param str cfg_type: either "installer" or "authenticator"
|
||||
:param str requested: the plugin that was requested
|
||||
:param PluginRegistry plugins: available plugins
|
||||
|
||||
:raises error.PluginSelectionError: if there was a problem
|
||||
"""
|
||||
|
||||
if requested:
|
||||
if requested not in plugins:
|
||||
msg = "The requested {0} plugin does not appear to be installed".format(requested)
|
||||
else:
|
||||
msg = ("The {0} plugin is not working; there may be problems with "
|
||||
"your existing configuration").format(requested)
|
||||
elif cfg_type == "installer":
|
||||
if os.path.exists("/etc/debian_version"):
|
||||
# Debian... installers are at least possible
|
||||
msg = ('No installers seem to be present and working on your system; '
|
||||
'fix that or try running letsencrypt with the "auth" command')
|
||||
else:
|
||||
# XXX update this logic as we make progress on #788 and nginx support
|
||||
msg = ('No installers are available on your OS yet; try running '
|
||||
'"letsencrypt-auto auth" to get a cert you can install manually')
|
||||
else:
|
||||
msg = "{0} could not be determined or is not installed".format(cfg_type)
|
||||
raise PluginSelectionError(msg)
|
||||
|
||||
|
||||
def choose_configurator_plugins(args, config, plugins, verb):
|
||||
"""
|
||||
Figure out which configurator we're going to use
|
||||
|
||||
:raises error.PluginSelectionError if there was a problem
|
||||
"""
|
||||
|
||||
# Which plugins do we need?
|
||||
need_inst = need_auth = (verb == "run")
|
||||
if verb == "auth":
|
||||
need_auth = True
|
||||
if verb == "install":
|
||||
need_inst = True
|
||||
if args.authenticator:
|
||||
logger.warn("Specifying an authenticator doesn't make sense in install mode")
|
||||
|
||||
# Which plugins did the user request?
|
||||
req_inst = req_auth = args.configurator
|
||||
req_inst = set_configurator(req_inst, args.installer)
|
||||
req_auth = set_configurator(req_auth, args.authenticator)
|
||||
if args.nginx:
|
||||
req_inst = set_configurator(req_inst, "nginx")
|
||||
req_auth = set_configurator(req_auth, "nginx")
|
||||
if args.apache:
|
||||
req_inst = set_configurator(req_inst, "apache")
|
||||
req_auth = set_configurator(req_auth, "apache")
|
||||
if args.standalone:
|
||||
req_auth = set_configurator(req_auth, "standalone")
|
||||
logger.debug("Requested authenticator %s and installer %s", req_auth, req_inst)
|
||||
|
||||
# Try to meet the user's request and/or ask them to pick plugins
|
||||
authenticator = installer = None
|
||||
if verb == "run" and req_auth == req_inst:
|
||||
# Unless the user has explicitly asked for different auth/install,
|
||||
# only consider offering a single choice
|
||||
authenticator = installer = display_ops.pick_configurator(config, req_inst, plugins)
|
||||
else:
|
||||
if need_inst or req_inst:
|
||||
installer = display_ops.pick_installer(config, req_inst, plugins)
|
||||
if need_auth:
|
||||
authenticator = display_ops.pick_authenticator(config, req_auth, plugins)
|
||||
logger.debug("Selected authenticator %s and installer %s", authenticator, installer)
|
||||
|
||||
if need_inst and not installer:
|
||||
diagnose_configurator_problem("installer", req_inst, plugins)
|
||||
if need_auth and not authenticator:
|
||||
diagnose_configurator_problem("authenticator", req_auth, plugins)
|
||||
|
||||
return installer, authenticator
|
||||
|
||||
|
||||
# TODO: Make run as close to auth + install as possible
|
||||
# Possible difficulties: args.csr was hacked into auth
|
||||
def run(args, config, plugins): # pylint: disable=too-many-branches,too-many-locals
|
||||
"""Obtain a certificate and install."""
|
||||
# Begin authenticator and installer setup
|
||||
if args.configurator is not None and (args.installer is not None or
|
||||
args.authenticator is not None):
|
||||
return ("Either --configurator or --authenticator/--installer"
|
||||
"pair, but not both, is allowed")
|
||||
|
||||
if args.authenticator is not None or args.installer is not None:
|
||||
installer = display_ops.pick_installer(
|
||||
config, args.installer, plugins)
|
||||
authenticator = display_ops.pick_authenticator(
|
||||
config, args.authenticator, plugins)
|
||||
else:
|
||||
# TODO: this assumes that user doesn't want to pick authenticator
|
||||
# and installer separately...
|
||||
authenticator = installer = display_ops.pick_configurator(
|
||||
config, args.configurator, plugins)
|
||||
|
||||
if installer is None or authenticator is None:
|
||||
return "Configurator could not be determined"
|
||||
# End authenticator and installer setup
|
||||
try:
|
||||
installer, authenticator = choose_configurator_plugins(args, config, plugins, "run")
|
||||
except PluginSelectionError, e:
|
||||
return e.message
|
||||
|
||||
domains = _find_domains(args, installer)
|
||||
|
||||
@@ -357,15 +452,11 @@ def auth(args, config, plugins):
|
||||
# supplied, check if CSR matches given domains?
|
||||
return "--domains and --csr are mutually exclusive"
|
||||
|
||||
authenticator = display_ops.pick_authenticator(
|
||||
config, args.authenticator, plugins)
|
||||
if authenticator is None:
|
||||
return "Authenticator could not be determined"
|
||||
|
||||
if args.installer is not None:
|
||||
installer = display_ops.pick_installer(config, args.installer, plugins)
|
||||
else:
|
||||
installer = None
|
||||
try:
|
||||
# installers are used in auth mode to determine domain names
|
||||
installer, authenticator = choose_configurator_plugins(args, config, plugins, "auth")
|
||||
except PluginSelectionError, e:
|
||||
return e.message
|
||||
|
||||
# TODO: Handle errors from _init_le_client?
|
||||
le_client = _init_le_client(args, config, authenticator, installer)
|
||||
@@ -374,9 +465,9 @@ def auth(args, config, plugins):
|
||||
if args.csr is not None:
|
||||
certr, chain = le_client.obtain_certificate_from_csr(le_util.CSR(
|
||||
file=args.csr[0], data=args.csr[1], form="der"))
|
||||
le_client.save_certificate(
|
||||
certr, chain, args.cert_path, args.chain_path)
|
||||
_report_new_cert(args.cert_path)
|
||||
cert_path, _, cert_fullchain = le_client.save_certificate(
|
||||
certr, chain, args.cert_path, args.chain_path, args.fullchain_path)
|
||||
_report_new_cert(cert_path, cert_fullchain)
|
||||
else:
|
||||
domains = _find_domains(args, installer)
|
||||
_auth_from_domains(le_client, config, domains, plugins)
|
||||
@@ -385,9 +476,12 @@ def auth(args, config, plugins):
|
||||
def install(args, config, plugins):
|
||||
"""Install a previously obtained cert in a server."""
|
||||
# XXX: Update for renewer/RenewableCert
|
||||
installer = display_ops.pick_installer(config, args.installer, plugins)
|
||||
if installer is None:
|
||||
return "Installer could not be determined"
|
||||
|
||||
try:
|
||||
installer, _ = choose_configurator_plugins(args, config, plugins, "auth")
|
||||
except PluginSelectionError, e:
|
||||
return e.message
|
||||
|
||||
domains = _find_domains(args, installer)
|
||||
le_client = _init_le_client(
|
||||
args, config, authenticator=None, installer=installer)
|
||||
@@ -679,8 +773,8 @@ def create_parser(plugins, args):
|
||||
help="Select renewal by default when domains are a superset of a "
|
||||
"a previously attained cert")
|
||||
helpful.add(
|
||||
"automation", "--agree-eula", dest="eula", action="store_true",
|
||||
help="Agree to the Let's Encrypt Developer Preview EULA")
|
||||
"automation", "--agree-dev-preview", action="store_true",
|
||||
help="Agree to the Let's Encrypt Developer Preview Disclaimer")
|
||||
helpful.add(
|
||||
"automation", "--agree-tos", dest="tos", action="store_true",
|
||||
help="Agree to the Let's Encrypt Subscriber Agreement")
|
||||
@@ -770,7 +864,6 @@ def _create_subparsers(helpful):
|
||||
"--checkpoints", type=int, metavar="N",
|
||||
default=flag_default("rollback_checkpoints"),
|
||||
help="Revert configuration N number of checkpoints.")
|
||||
|
||||
helpful.add("plugins",
|
||||
"--init", action="store_true", help="Initialize plugins.")
|
||||
helpful.add("plugins",
|
||||
@@ -830,11 +923,17 @@ def _plugins_parsing(helpful, plugins):
|
||||
helpful.add(
|
||||
"plugins", "-a", "--authenticator", help="Authenticator plugin name.")
|
||||
helpful.add(
|
||||
"plugins", "-i", "--installer", help="Installer plugin name.")
|
||||
"plugins", "-i", "--installer", help="Installer plugin name (also used to find domains).")
|
||||
helpful.add(
|
||||
"plugins", "--configurator", help="Name of the plugin that is "
|
||||
"both an authenticator and an installer. Should not be used "
|
||||
"together with --authenticator or --installer.")
|
||||
helpful.add("plugins", "--apache", action="store_true",
|
||||
help="Obtain and install certs using Apache")
|
||||
helpful.add("plugins", "--nginx", action="store_true",
|
||||
help="Obtain and install certs using Nginx")
|
||||
helpful.add("plugins", "--standalone", action="store_true",
|
||||
help='Obtain certs using a "standalone" webserver.')
|
||||
|
||||
# things should not be reorder past/pre this comment:
|
||||
# plugins_group should be displayed in --help before plugin
|
||||
@@ -917,7 +1016,7 @@ def _handle_exception(exc_type, exc_value, trace, args):
|
||||
sys.exit("".join(
|
||||
traceback.format_exception(exc_type, exc_value, trace)))
|
||||
|
||||
if issubclass(exc_type, errors.Error):
|
||||
if issubclass(exc_type, Error):
|
||||
sys.exit(exc_value)
|
||||
else:
|
||||
# Tell the user a bit about what happened, without overwhelming
|
||||
@@ -958,6 +1057,7 @@ def main(cli_args=sys.argv[1:]):
|
||||
args.logs_dir, 0o700, os.geteuid(), "--strict-permissions" in cli_args)
|
||||
setup_logging(args, _cli_log_handler, logfile='letsencrypt.log')
|
||||
|
||||
logger.debug("letsencrypt version: %s", letsencrypt.__version__)
|
||||
# do not log `args`, as it contains sensitive data (e.g. revoke --key)!
|
||||
logger.debug("Arguments: %r", cli_args)
|
||||
logger.debug("Discovered plugins: %r", plugins)
|
||||
@@ -976,12 +1076,12 @@ def main(cli_args=sys.argv[1:]):
|
||||
zope.component.provideUtility(report)
|
||||
atexit.register(report.atexit_print_messages)
|
||||
|
||||
# TODO: remove developer EULA prompt for the launch
|
||||
if not config.eula:
|
||||
eula = pkg_resources.resource_string("letsencrypt", "EULA")
|
||||
# TODO: remove developer preview prompt for the launch
|
||||
if not config.agree_dev_preview:
|
||||
disclaimer = pkg_resources.resource_string("letsencrypt", "DISCLAIMER")
|
||||
if not zope.component.getUtility(interfaces.IDisplay).yesno(
|
||||
eula, "Agree", "Cancel"):
|
||||
raise errors.Error("Must agree to TOS")
|
||||
disclaimer, "Agree", "Cancel"):
|
||||
raise Error("Must agree to TOS")
|
||||
|
||||
if not os.geteuid() == 0:
|
||||
logger.warning(
|
||||
@@ -998,4 +1098,7 @@ def main(cli_args=sys.argv[1:]):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main()) # pragma: no cover
|
||||
err_string = main()
|
||||
if err_string:
|
||||
logger.warn("Exiting with message %s", err_string)
|
||||
sys.exit(err_string) # pragma: no cover
|
||||
|
||||
+35
-23
@@ -258,8 +258,8 @@ class Client(object):
|
||||
params, config, cli_config)
|
||||
return lineage
|
||||
|
||||
def save_certificate(self, certr, chain_cert, cert_path, chain_path):
|
||||
# pylint: disable=no-self-use
|
||||
def save_certificate(self, certr, chain_cert,
|
||||
cert_path, chain_path, fullchain_path):
|
||||
"""Saves the certificate received from the ACME server.
|
||||
|
||||
:param certr: ACME "certificate" resource.
|
||||
@@ -268,24 +268,23 @@ class Client(object):
|
||||
:param list chain_cert:
|
||||
:param str cert_path: Candidate path to a certificate.
|
||||
:param str chain_path: Candidate path to a certificate chain.
|
||||
:param str fullchain_path: Candidate path to a full cert chain.
|
||||
|
||||
:returns: cert_path, chain_path (absolute paths to the actual files)
|
||||
:returns: cert_path, chain_path, and fullchain_path as absolute
|
||||
paths to the actual files
|
||||
:rtype: `tuple` of `str`
|
||||
|
||||
:raises IOError: If unable to find room to write the cert files
|
||||
|
||||
"""
|
||||
for path in cert_path, chain_path:
|
||||
for path in cert_path, chain_path, fullchain_path:
|
||||
le_util.make_or_verify_dir(
|
||||
os.path.dirname(path), 0o755, os.geteuid(),
|
||||
self.config.strict_permissions)
|
||||
|
||||
# try finally close
|
||||
cert_chain_abspath = None
|
||||
cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644)
|
||||
# TODO: Except
|
||||
cert_pem = OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, certr.body)
|
||||
cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644)
|
||||
try:
|
||||
cert_file.write(cert_pem)
|
||||
finally:
|
||||
@@ -293,22 +292,15 @@ class Client(object):
|
||||
logger.info("Server issued certificate; certificate written to %s",
|
||||
act_cert_path)
|
||||
|
||||
cert_chain_abspath = None
|
||||
fullchain_abspath = None
|
||||
if chain_cert:
|
||||
chain_file, act_chain_path = le_util.unique_file(
|
||||
chain_path, 0o644)
|
||||
# TODO: Except
|
||||
chain_pem = crypto_util.dump_pyopenssl_chain(chain_cert)
|
||||
try:
|
||||
chain_file.write(chain_pem)
|
||||
finally:
|
||||
chain_file.close()
|
||||
cert_chain_abspath = _save_chain(chain_pem, chain_path)
|
||||
fullchain_abspath = _save_chain(cert_pem + chain_pem,
|
||||
fullchain_path)
|
||||
|
||||
logger.info("Cert chain written to %s", act_chain_path)
|
||||
|
||||
# This expects a valid chain file
|
||||
cert_chain_abspath = os.path.abspath(act_chain_path)
|
||||
|
||||
return os.path.abspath(act_cert_path), cert_chain_abspath
|
||||
return os.path.abspath(act_cert_path), cert_chain_abspath, fullchain_abspath
|
||||
|
||||
def deploy_certificate(self, domains, privkey_path,
|
||||
cert_path, chain_path, fullchain_path):
|
||||
@@ -329,8 +321,6 @@ class Client(object):
|
||||
|
||||
with error_handler.ErrorHandler(self.installer.recovery_routine):
|
||||
for dom in domains:
|
||||
# TODO: Provide a fullchain reference for installers like
|
||||
# nginx that want it
|
||||
self.installer.deploy_cert(
|
||||
domain=dom, cert_path=os.path.abspath(cert_path),
|
||||
key_path=os.path.abspath(privkey_path),
|
||||
@@ -467,3 +457,25 @@ def view_config_changes(config):
|
||||
rev = reverter.Reverter(config)
|
||||
rev.recovery_routine()
|
||||
rev.view_config_changes()
|
||||
|
||||
|
||||
def _save_chain(chain_pem, chain_path):
|
||||
"""Saves chain_pem at a unique path based on chain_path.
|
||||
|
||||
:param str chain_pem: certificate chain in PEM format
|
||||
:param str chain_path: candidate path for the cert chain
|
||||
|
||||
:returns: absolute path to saved cert chain
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
chain_file, act_chain_path = le_util.unique_file(chain_path, 0o644)
|
||||
try:
|
||||
chain_file.write(chain_pem)
|
||||
finally:
|
||||
chain_file.close()
|
||||
|
||||
logger.info("Cert chain written to %s", act_chain_path)
|
||||
|
||||
# This expects a valid chain file
|
||||
return os.path.abspath(act_chain_path)
|
||||
|
||||
@@ -8,6 +8,7 @@ import logging
|
||||
import os
|
||||
|
||||
import OpenSSL
|
||||
import pyrfc3339
|
||||
import zope.component
|
||||
|
||||
from acme import crypto_util as acme_crypto_util
|
||||
@@ -276,3 +277,48 @@ def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):
|
||||
# assumes that OpenSSL.crypto.dump_certificate includes ending
|
||||
# newline character
|
||||
return "".join(_dump_cert(cert) for cert in chain)
|
||||
|
||||
|
||||
def notBefore(cert_path):
|
||||
"""When does the cert at cert_path start being valid?
|
||||
|
||||
:param str cert_path: path to a cert in PEM format
|
||||
|
||||
:returns: the notBefore value from the cert at cert_path
|
||||
:rtype: :class:`datetime.datetime`
|
||||
|
||||
"""
|
||||
return _notAfterBefore(cert_path, OpenSSL.crypto.X509.get_notBefore)
|
||||
|
||||
|
||||
def notAfter(cert_path):
|
||||
"""When does the cert at cert_path stop being valid?
|
||||
|
||||
:param str cert_path: path to a cert in PEM format
|
||||
|
||||
:returns: the notAfter value from the cert at cert_path
|
||||
:rtype: :class:`datetime.datetime`
|
||||
|
||||
"""
|
||||
return _notAfterBefore(cert_path, OpenSSL.crypto.X509.get_notAfter)
|
||||
|
||||
|
||||
def _notAfterBefore(cert_path, method):
|
||||
"""Internal helper function for finding notbefore/notafter.
|
||||
|
||||
:param str cert_path: path to a cert in PEM format
|
||||
:param function method: one of ``OpenSSL.crypto.X509.get_notBefore``
|
||||
or ``OpenSSL.crypto.X509.get_notAfter``
|
||||
|
||||
:returns: the notBefore or notAfter value from the cert at cert_path
|
||||
:rtype: :class:`datetime.datetime`
|
||||
|
||||
"""
|
||||
with open(cert_path) as f:
|
||||
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
|
||||
f.read())
|
||||
timestamp = method(x509)
|
||||
reformatted_timestamp = [timestamp[0:4], "-", timestamp[4:6], "-",
|
||||
timestamp[6:8], "T", timestamp[8:10], ":",
|
||||
timestamp[10:12], ":", timestamp[12:]]
|
||||
return pyrfc3339.parse("".join(reformatted_timestamp))
|
||||
|
||||
@@ -66,6 +66,10 @@ class PluginError(Error):
|
||||
"""Let's Encrypt Plugin error."""
|
||||
|
||||
|
||||
class PluginSelectionError(Error):
|
||||
"""A problem with plugin/configurator selection or setup"""
|
||||
|
||||
|
||||
class NoInstallationError(PluginError):
|
||||
"""Let's Encrypt No Installation error."""
|
||||
|
||||
|
||||
@@ -130,10 +130,7 @@ binary for temporary key/certificate generation.""".replace("\n", "")
|
||||
if self.conf("test-mode"):
|
||||
logger.debug("Test mode. Executing the manual command: %s", command)
|
||||
# sh shipped with OS X does't support echo -n
|
||||
if sys.platform == "darwin":
|
||||
executable = "/bin/bash"
|
||||
else:
|
||||
executable = None
|
||||
executable = "/bin/bash" if sys.platform == "darwin" else None
|
||||
try:
|
||||
self._httpd = subprocess.Popen(
|
||||
command,
|
||||
|
||||
@@ -72,13 +72,12 @@ class ServerManager(object):
|
||||
except socket.error as error:
|
||||
raise errors.StandaloneBindError(error, port)
|
||||
|
||||
# if port == 0, then random free port on OS is taken
|
||||
# pylint: disable=no-member
|
||||
host, real_port = server.socket.getsockname()
|
||||
thread = threading.Thread(target=server.serve_forever2)
|
||||
logger.debug("Starting server at %s:%d", host, real_port)
|
||||
thread.start()
|
||||
|
||||
# if port == 0, then random free port on OS is taken
|
||||
# pylint: disable=no-member
|
||||
real_port = server.socket.getsockname()[1]
|
||||
self._instances[real_port] = self._Instance(server, thread)
|
||||
return server
|
||||
|
||||
@@ -89,6 +88,8 @@ class ServerManager(object):
|
||||
|
||||
"""
|
||||
instance = self._instances[port]
|
||||
logger.debug("Stopping server at %s:%d...",
|
||||
*instance.server.socket.getsockname()[:2])
|
||||
instance.server.shutdown2()
|
||||
instance.thread.join()
|
||||
del self._instances[port]
|
||||
|
||||
@@ -98,6 +98,10 @@ class AlreadyListeningTest(unittest.TestCase):
|
||||
self.assertEqual(mock_get_utility.call_count, 1)
|
||||
mock_process.assert_called_once_with(4420)
|
||||
|
||||
@mock.patch("letsencrypt.plugins.util.psutil.net_connections")
|
||||
def test_access_denied_exception(self, mock_net):
|
||||
mock_net.side_effect = psutil.AccessDenied("")
|
||||
self.assertFalse(self._call(12345))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -72,8 +72,7 @@ to serve all files under specified web root ({0})."""
|
||||
return os.path.join(self.full_root, achall.chall.encode("token"))
|
||||
|
||||
def _perform_single(self, achall):
|
||||
response, validation = achall.gen_response_and_validation(
|
||||
tls=(not self.config.no_simple_http_tls))
|
||||
response, validation = achall.gen_response_and_validation(tls=False)
|
||||
path = self._path_for_achall(achall)
|
||||
logger.debug("Attempting to save validation to %s", path)
|
||||
with open(path, "w") as validation_file:
|
||||
|
||||
@@ -94,7 +94,7 @@ def renew(cert, old_version):
|
||||
sans = crypto_util.get_sans_from_cert(f.read())
|
||||
new_certr, new_chain, new_key, _ = le_client.obtain_certificate(sans)
|
||||
if new_chain:
|
||||
# XXX: Assumes that there was no key change. We need logic
|
||||
# XXX: Assumes that there was a key change. We need logic
|
||||
# for figuring out whether there was or not. Probably
|
||||
# best is to have obtain_certificate return None for
|
||||
# new_key if the old key is to be used (since save_successor
|
||||
|
||||
+3
-45
@@ -5,10 +5,8 @@ import re
|
||||
import time
|
||||
|
||||
import configobj
|
||||
import OpenSSL
|
||||
import parsedatetime
|
||||
import pytz
|
||||
import pyrfc3339
|
||||
|
||||
from letsencrypt import constants
|
||||
from letsencrypt import crypto_util
|
||||
@@ -420,47 +418,6 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
|
||||
for _, link in previous_links:
|
||||
os.unlink(link)
|
||||
|
||||
def _notafterbefore(self, method, version):
|
||||
"""Internal helper function for finding notbefore/notafter."""
|
||||
if version is None:
|
||||
target = self.current_target("cert")
|
||||
else:
|
||||
target = self.version("cert", version)
|
||||
pem = open(target).read()
|
||||
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
|
||||
pem)
|
||||
i = method(x509)
|
||||
return pyrfc3339.parse(i[0:4] + "-" + i[4:6] + "-" + i[6:8] + "T" +
|
||||
i[8:10] + ":" + i[10:12] + ":" + i[12:])
|
||||
|
||||
def notbefore(self, version=None):
|
||||
"""When does the specified cert version start being valid?
|
||||
|
||||
(If no version is specified, use the current version.)
|
||||
|
||||
:param int version: the desired version number
|
||||
|
||||
:returns: the notBefore value from the specified cert version in
|
||||
this lineage
|
||||
:rtype: :class:`datetime.datetime`
|
||||
|
||||
"""
|
||||
return self._notafterbefore(lambda x509: x509.get_notBefore(), version)
|
||||
|
||||
def notafter(self, version=None):
|
||||
"""When does the specified cert version stop being valid?
|
||||
|
||||
(If no version is specified, use the current version.)
|
||||
|
||||
:param int version: the desired version number
|
||||
|
||||
:returns: the notAfter value from the specified cert version in
|
||||
this lineage
|
||||
:rtype: :class:`datetime.datetime`
|
||||
|
||||
"""
|
||||
return self._notafterbefore(lambda x509: x509.get_notAfter(), version)
|
||||
|
||||
def names(self, version=None):
|
||||
"""What are the subject names of this certificate?
|
||||
|
||||
@@ -509,7 +466,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
|
||||
interval = self.configuration.get("deploy_before_expiry",
|
||||
"5 days")
|
||||
autodeploy_interval = parse_time_interval(interval)
|
||||
expiry = self.notafter()
|
||||
expiry = crypto_util.notAfter(self.current_target("cert"))
|
||||
now = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC)
|
||||
remaining = expiry - now
|
||||
if remaining < autodeploy_interval:
|
||||
@@ -576,7 +533,8 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
|
||||
# Renewals on the basis of expiry time
|
||||
interval = self.configuration.get("renew_before_expiry", "10 days")
|
||||
autorenew_interval = parse_time_interval(interval)
|
||||
expiry = self.notafter(self.latest_common_version())
|
||||
expiry = crypto_util.notAfter(self.version(
|
||||
"cert", self.latest_common_version()))
|
||||
now = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC)
|
||||
remaining = expiry - now
|
||||
if remaining < autorenew_interval:
|
||||
|
||||
@@ -45,16 +45,16 @@ class AccountTest(unittest.TestCase):
|
||||
|
||||
def test_id(self):
|
||||
self.assertEqual(
|
||||
self.acc.id, "2ba35a3bdf380ed76a5ac9e740568395")
|
||||
self.acc.id, "bca5889f66457d5b62fbba7b25f9ab6f")
|
||||
|
||||
def test_slug(self):
|
||||
self.assertEqual(
|
||||
self.acc.slug, "test.letsencrypt.org@2015-07-04T14:04:10Z (2ba3)")
|
||||
self.acc.slug, "test.letsencrypt.org@2015-07-04T14:04:10Z (bca5)")
|
||||
|
||||
def test_repr(self):
|
||||
self.assertEqual(
|
||||
repr(self.acc),
|
||||
"<Account(2ba35a3bdf380ed76a5ac9e740568395)>")
|
||||
"<Account(bca5889f66457d5b62fbba7b25f9ab6f)>")
|
||||
|
||||
|
||||
class ReportNewAccountTest(unittest.TestCase):
|
||||
|
||||
@@ -13,6 +13,8 @@ from letsencrypt import account
|
||||
from letsencrypt import configuration
|
||||
from letsencrypt import errors
|
||||
|
||||
from letsencrypt.plugins import disco
|
||||
|
||||
from letsencrypt.tests import renewer_test
|
||||
from letsencrypt.tests import test_util
|
||||
|
||||
@@ -36,7 +38,7 @@ class CLITest(unittest.TestCase):
|
||||
from letsencrypt import cli
|
||||
args = ['--text', '--config-dir', self.config_dir,
|
||||
'--work-dir', self.work_dir, '--logs-dir', self.logs_dir,
|
||||
'--agree-eula'] + args
|
||||
'--agree-dev-preview'] + args
|
||||
with mock.patch('letsencrypt.cli.sys.stdout') as stdout:
|
||||
with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
|
||||
with mock.patch('letsencrypt.cli.client') as client:
|
||||
@@ -51,7 +53,7 @@ class CLITest(unittest.TestCase):
|
||||
from letsencrypt import cli
|
||||
args = ['--text', '--config-dir', self.config_dir,
|
||||
'--work-dir', self.work_dir, '--logs-dir', self.logs_dir,
|
||||
'--agree-eula'] + args
|
||||
'--agree-dev-preview'] + args
|
||||
with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
|
||||
with mock.patch('letsencrypt.cli.client') as client:
|
||||
ret = cli.main(args)
|
||||
@@ -75,7 +77,6 @@ class CLITest(unittest.TestCase):
|
||||
output.truncate(0)
|
||||
self.assertRaises(SystemExit, self._call_stdout, ['-h', 'nginx'])
|
||||
out = output.getvalue()
|
||||
from letsencrypt.plugins import disco
|
||||
if "nginx" in disco.PluginsRegistry.find_all():
|
||||
# may be false while building distributions without plugins
|
||||
self.assertTrue("--nginx-ctl" in out)
|
||||
@@ -93,6 +94,28 @@ class CLITest(unittest.TestCase):
|
||||
from letsencrypt import cli
|
||||
self.assertTrue(cli.USAGE in out)
|
||||
|
||||
def test_configurator_selection(self):
|
||||
real_plugins = disco.PluginsRegistry.find_all()
|
||||
args = ['--agree-dev-preview', '--apache',
|
||||
'--authenticator', 'standalone']
|
||||
|
||||
# This needed two calls to find_all(), which we're avoiding for now
|
||||
# because of possible side effects:
|
||||
# https://github.com/letsencrypt/letsencrypt/commit/51ed2b681f87b1eb29088dd48718a54f401e4855
|
||||
#with mock.patch('letsencrypt.cli.plugins_testable') as plugins:
|
||||
# plugins.return_value = {"apache": True, "nginx": True}
|
||||
# ret, _, _, _ = self._call(args)
|
||||
# self.assertTrue("Too many flags setting" in ret)
|
||||
|
||||
args = ["install", "--nginx", "--cert-path", "/tmp/blah", "--key-path", "/tmp/blah",
|
||||
"--nginx-server-root", "/nonexistent/thing", "-d",
|
||||
"example.com", "--debug"]
|
||||
if "nginx" in real_plugins:
|
||||
# Sending nginx a non-existent conf dir will simulate misconfiguration
|
||||
# (we can only do that if letsencrypt-nginx is actually present)
|
||||
ret, _, _, _ = self._call(args)
|
||||
self.assertTrue("The nginx plugin is not working" in ret)
|
||||
|
||||
def test_rollback(self):
|
||||
_, _, _, client = self._call(['rollback'])
|
||||
self.assertEqual(1, client.rollback.call_count)
|
||||
@@ -117,20 +140,25 @@ class CLITest(unittest.TestCase):
|
||||
self.assertEqual(ret, '--domains and --csr are mutually exclusive')
|
||||
|
||||
ret, _, _, _ = self._call(['-a', 'bad_auth', 'auth'])
|
||||
self.assertEqual(ret, 'Authenticator could not be determined')
|
||||
self.assertEqual(ret, 'The requested bad_auth plugin does not appear to be installed')
|
||||
|
||||
@mock.patch('letsencrypt.crypto_util.notAfter')
|
||||
@mock.patch('letsencrypt.cli.zope.component.getUtility')
|
||||
def test_auth_new_request_success(self, mock_get_utility):
|
||||
def test_auth_new_request_success(self, mock_get_utility, mock_notAfter):
|
||||
cert_path = '/etc/letsencrypt/live/foo.bar'
|
||||
mock_lineage = mock.MagicMock(cert=cert_path)
|
||||
date = '1970-01-01'
|
||||
mock_notAfter().date.return_value = date
|
||||
|
||||
mock_lineage = mock.MagicMock(cert=cert_path, fullchain=cert_path)
|
||||
mock_client = mock.MagicMock()
|
||||
mock_client.obtain_and_enroll_certificate.return_value = mock_lineage
|
||||
self._auth_new_request_common(mock_client)
|
||||
self.assertEqual(
|
||||
mock_client.obtain_and_enroll_certificate.call_count, 1)
|
||||
msg = mock_get_utility().add_message.call_args_list[0][0][0]
|
||||
self.assertTrue(cert_path in msg)
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 2)
|
||||
self.assertTrue(
|
||||
cert_path in mock_get_utility().add_message.call_args[0][0])
|
||||
self.assertTrue(
|
||||
date in mock_get_utility().add_message.call_args[0][0])
|
||||
|
||||
def test_auth_new_request_failure(self):
|
||||
mock_client = mock.MagicMock()
|
||||
@@ -149,8 +177,10 @@ class CLITest(unittest.TestCase):
|
||||
@mock.patch('letsencrypt.cli._treat_as_renewal')
|
||||
@mock.patch('letsencrypt.cli._init_le_client')
|
||||
def test_auth_renewal(self, mock_init, mock_renewal, mock_get_utility):
|
||||
cert_path = '/etc/letsencrypt/live/foo.bar'
|
||||
mock_lineage = mock.MagicMock(cert=cert_path)
|
||||
cert_path = '/etc/letsencrypt/live/foo.bar/cert.pem'
|
||||
chain_path = '/etc/letsencrypt/live/foo.bar/fullchain.pem'
|
||||
|
||||
mock_lineage = mock.MagicMock(cert=cert_path, fullchain=chain_path)
|
||||
mock_cert = mock.MagicMock(body='body')
|
||||
mock_key = mock.MagicMock(pem='pem_key')
|
||||
mock_renewal.return_value = mock_lineage
|
||||
@@ -165,28 +195,37 @@ class CLITest(unittest.TestCase):
|
||||
self.assertEqual(mock_lineage.save_successor.call_count, 1)
|
||||
mock_lineage.update_all_links_to.assert_called_once_with(
|
||||
mock_lineage.latest_common_version())
|
||||
msg = mock_get_utility().add_message.call_args_list[0][0][0]
|
||||
self.assertTrue(cert_path in msg)
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 2)
|
||||
self.assertTrue(
|
||||
chain_path in mock_get_utility().add_message.call_args[0][0])
|
||||
|
||||
@mock.patch('letsencrypt.crypto_util.notAfter')
|
||||
@mock.patch('letsencrypt.cli.display_ops.pick_installer')
|
||||
@mock.patch('letsencrypt.cli.zope.component.getUtility')
|
||||
@mock.patch('letsencrypt.cli._init_le_client')
|
||||
def test_auth_csr(self, mock_init, mock_get_utility, mock_pick_installer):
|
||||
cert_path = '/etc/letsencrypt/live/foo.bar'
|
||||
def test_auth_csr(self, mock_init, mock_get_utility,
|
||||
mock_pick_installer, mock_notAfter):
|
||||
cert_path = '/etc/letsencrypt/live/blahcert.pem'
|
||||
date = '1970-01-01'
|
||||
mock_notAfter().date.return_value = date
|
||||
|
||||
mock_client = mock.MagicMock()
|
||||
mock_client.obtain_certificate_from_csr.return_value = ('certr',
|
||||
'chain')
|
||||
mock_client.save_certificate.return_value = cert_path, None, None
|
||||
mock_init.return_value = mock_client
|
||||
|
||||
installer = 'installer'
|
||||
self._call(
|
||||
['-a', 'standalone', '-i', installer, 'auth', '--csr', CSR,
|
||||
'--cert-path', cert_path, '--chain-path', '/'])
|
||||
'--cert-path', cert_path, '--fullchain-path', '/',
|
||||
'--chain-path', '/'])
|
||||
self.assertEqual(mock_pick_installer.call_args[0][1], installer)
|
||||
mock_client.save_certificate.assert_called_once_with(
|
||||
'certr', 'chain', cert_path, '/')
|
||||
'certr', 'chain', cert_path, '/', '/')
|
||||
self.assertTrue(
|
||||
cert_path in mock_get_utility().add_message.call_args[0][0])
|
||||
self.assertTrue(
|
||||
date in mock_get_utility().add_message.call_args[0][0])
|
||||
|
||||
@mock.patch('letsencrypt.cli.sys')
|
||||
def test_handle_exception(self, mock_sys):
|
||||
|
||||
@@ -120,18 +120,22 @@ class ClientTest(unittest.TestCase):
|
||||
os.chmod(tmp_path, 0o755) # TODO: really??
|
||||
|
||||
certr = mock.MagicMock(body=test_util.load_cert(certs[0]))
|
||||
cert1 = test_util.load_cert(certs[1])
|
||||
cert2 = test_util.load_cert(certs[2])
|
||||
chain_cert = [test_util.load_cert(certs[1]),
|
||||
test_util.load_cert(certs[2])]
|
||||
candidate_cert_path = os.path.join(tmp_path, "certs", "cert.pem")
|
||||
candidate_chain_path = os.path.join(tmp_path, "chains", "chain.pem")
|
||||
candidate_fullchain_path = os.path.join(tmp_path, "chains", "fullchain.pem")
|
||||
|
||||
cert_path, chain_path = self.client.save_certificate(
|
||||
certr, [cert1, cert2], candidate_cert_path, candidate_chain_path)
|
||||
cert_path, chain_path, fullchain_path = self.client.save_certificate(
|
||||
certr, chain_cert, candidate_cert_path, candidate_chain_path,
|
||||
candidate_fullchain_path)
|
||||
|
||||
self.assertEqual(os.path.dirname(cert_path),
|
||||
os.path.dirname(candidate_cert_path))
|
||||
self.assertEqual(os.path.dirname(chain_path),
|
||||
os.path.dirname(candidate_chain_path))
|
||||
self.assertEqual(os.path.dirname(fullchain_path),
|
||||
os.path.dirname(candidate_fullchain_path))
|
||||
|
||||
with open(cert_path, "r") as cert_file:
|
||||
cert_contents = cert_file.read()
|
||||
|
||||
@@ -15,6 +15,7 @@ from letsencrypt.tests import test_util
|
||||
|
||||
RSA256_KEY = test_util.load_vector('rsa256_key.pem')
|
||||
RSA512_KEY = test_util.load_vector('rsa512_key.pem')
|
||||
CERT_PATH = test_util.vector_path('cert.pem')
|
||||
CERT = test_util.load_vector('cert.pem')
|
||||
SAN_CERT = test_util.load_vector('cert-san.pem')
|
||||
|
||||
@@ -232,5 +233,23 @@ class CertLoaderTest(unittest.TestCase):
|
||||
pyopenssl_load_certificate(bad_cert_data)
|
||||
|
||||
|
||||
class NotBeforeTest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.crypto_util.notBefore"""
|
||||
|
||||
def test_notBefore(self):
|
||||
from letsencrypt.crypto_util import notBefore
|
||||
self.assertEqual(notBefore(CERT_PATH).isoformat(),
|
||||
'2014-12-11T22:34:45+00:00')
|
||||
|
||||
|
||||
class NotAfterTest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.crypto_util.notAfter"""
|
||||
|
||||
def test_notAfter(self):
|
||||
from letsencrypt.crypto_util import notAfter
|
||||
self.assertEqual(notAfter(CERT_PATH).isoformat(),
|
||||
'2014-12-18T22:34:45+00:00')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -7,7 +7,6 @@ import unittest
|
||||
|
||||
import configobj
|
||||
import mock
|
||||
import pytz
|
||||
|
||||
from letsencrypt import configuration
|
||||
from letsencrypt import errors
|
||||
@@ -48,7 +47,6 @@ class BaseRenewableCertTest(unittest.TestCase):
|
||||
config_dir=self.tempdir,
|
||||
work_dir=self.tempdir,
|
||||
logs_dir=self.tempdir,
|
||||
no_simple_http_tls=False,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -379,26 +377,6 @@ class RenewableCertTests(BaseRenewableCertTest):
|
||||
self.assertEqual(self.test_rc.names(12),
|
||||
["example.com", "www.example.com"])
|
||||
|
||||
def _test_notafterbefore(self, function, timestamp):
|
||||
test_cert = test_util.load_vector("cert.pem")
|
||||
os.symlink(os.path.join("..", "..", "archive", "example.org",
|
||||
"cert12.pem"), self.test_rc.cert)
|
||||
with open(self.test_rc.cert, "w") as f:
|
||||
f.write(test_cert)
|
||||
desired_time = datetime.datetime.utcfromtimestamp(timestamp)
|
||||
desired_time = desired_time.replace(tzinfo=pytz.UTC)
|
||||
for result in (function(), function(12)):
|
||||
self.assertEqual(result, desired_time)
|
||||
self.assertEqual(result.utcoffset(), datetime.timedelta(0))
|
||||
|
||||
def test_notbefore(self):
|
||||
self._test_notafterbefore(self.test_rc.notbefore, 1418337285)
|
||||
# 2014-12-11 22:34:45+00:00 = Unix time 1418337285
|
||||
|
||||
def test_notafter(self):
|
||||
self._test_notafterbefore(self.test_rc.notafter, 1418942085)
|
||||
# 2014-12-18 22:34:45+00:00 = Unix time 1418942085
|
||||
|
||||
@mock.patch("letsencrypt.storage.datetime")
|
||||
def test_time_interval_judgments(self, mock_datetime):
|
||||
"""Test should_autodeploy() and should_autorenew() on the basis
|
||||
|
||||
Reference in New Issue
Block a user