mirror of
https://github.com/certbot/certbot.git
synced 2026-01-23 07:20:55 +03:00
Merge remote-tracking branch 'github/letsencrypt/master' into renewer
Conflicts: tests/boulder-integration.sh
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -15,4 +15,7 @@ dist/
|
||||
# editor temporary files
|
||||
*~
|
||||
*.swp
|
||||
\#*#
|
||||
\#*#
|
||||
|
||||
# auth --cert-path --chain-path
|
||||
/*.pem
|
||||
3
examples/.gitignore
vendored
Normal file
3
examples/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# generate-csr.sh:
|
||||
/key.pem
|
||||
/csr.der
|
||||
28
examples/generate-csr.sh
Executable file
28
examples/generate-csr.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# This script generates a simple SAN CSR to be used with Let's Encrypt
|
||||
# CA. Mostly intedened for "auth --csr" testing, but, since its easily
|
||||
# auditable, feel free to adjust it and use on you production web
|
||||
# server.
|
||||
|
||||
if [ "$#" -lt 1 ]
|
||||
then
|
||||
echo "Usage: $0 domain [domain...]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
domains="DNS:$1"
|
||||
shift
|
||||
for x in "$@"
|
||||
do
|
||||
domains="$domains,DNS:$x"
|
||||
done
|
||||
|
||||
SAN="$domains" openssl req -config "${OPENSSL_CNF:-openssl.cnf}" \
|
||||
-new -nodes -subj '/' -reqexts san \
|
||||
-out "${CSR_PATH:-csr.der}" \
|
||||
-keyout key.pem \
|
||||
-newkey rsa:2048 \
|
||||
-outform DER
|
||||
# 512 or 1024 too low for Boulder, 2048 is smallest for tests
|
||||
|
||||
echo "You can now run: letsencrypt auth --csr ${CSR_PATH:-csr.der}"
|
||||
5
examples/openssl.cnf
Normal file
5
examples/openssl.cnf
Normal file
@@ -0,0 +1,5 @@
|
||||
[ req ]
|
||||
distinguished_name = req_distinguished_name
|
||||
[ req_distinguished_name ]
|
||||
[ san ]
|
||||
subjectAltName=${ENV::SAN}
|
||||
@@ -89,16 +89,20 @@ def _account_init(args, config):
|
||||
return None
|
||||
|
||||
|
||||
def _common_run(args, config, acc, authenticator, installer):
|
||||
def _find_domains(args, installer):
|
||||
if args.domains is None:
|
||||
doms = display_ops.choose_names(installer)
|
||||
domains = display_ops.choose_names(installer)
|
||||
else:
|
||||
doms = args.domains
|
||||
domains = args.domains
|
||||
|
||||
if not doms:
|
||||
if not domains:
|
||||
sys.exit("Please specify --domains, or --installer that will "
|
||||
"help in domain names autodiscovery")
|
||||
|
||||
return domains
|
||||
|
||||
|
||||
def _init_acme(config, acc, authenticator, installer):
|
||||
acme = client.Client(config, acc, authenticator, installer)
|
||||
|
||||
# Validate the key and csr
|
||||
@@ -111,7 +115,7 @@ def _common_run(args, config, acc, authenticator, installer):
|
||||
except errors.LetsEncryptClientError:
|
||||
sys.exit("Unable to register an account with ACME server")
|
||||
|
||||
return acme, doms
|
||||
return acme
|
||||
|
||||
|
||||
def run(args, config, plugins):
|
||||
@@ -139,19 +143,26 @@ def run(args, config, plugins):
|
||||
if installer is None or authenticator is None:
|
||||
return "Configurator could not be determined"
|
||||
|
||||
acme, doms = _common_run(args, config, acc, authenticator, installer)
|
||||
# TODO: Handle errors from _common_run?
|
||||
lineage = acme.obtain_and_enroll_certificate(doms, authenticator,
|
||||
installer, plugins)
|
||||
domains = _find_domains(args, installer)
|
||||
# TODO: Handle errors from _init_acme?
|
||||
acme = _init_acme(config, acc, authenticator, installer)
|
||||
lineage = acme.obtain_and_enroll_certificate(
|
||||
domains, authenticator, installer, plugins)
|
||||
if not lineage:
|
||||
return "Certificate could not be obtained"
|
||||
acme.deploy_certificate(doms, lineage.privkey, lineage.cert, lineage.chain)
|
||||
acme.enhance_config(doms, args.redirect)
|
||||
acme.deploy_certificate(domains, lineage.privkey, lineage.cert, lineage.chain)
|
||||
acme.enhance_config(domains, args.redirect)
|
||||
|
||||
|
||||
def auth(args, config, plugins):
|
||||
"""Authenticate & obtain cert, but do not install it."""
|
||||
# XXX: Update for renewer / RenewableCert
|
||||
|
||||
if args.domains is not None and args.csr is not None:
|
||||
# TODO: --csr could have a priority, when --domains is
|
||||
# supplied, check if CSR matches given domains?
|
||||
return "--domains and --csr are mutually exclusive"
|
||||
|
||||
acc = _account_init(args, config)
|
||||
if acc is None:
|
||||
return None
|
||||
@@ -166,13 +177,18 @@ def auth(args, config, plugins):
|
||||
else:
|
||||
installer = None
|
||||
|
||||
# TODO: Handle errors from _common_run?
|
||||
acme, doms = _common_run(
|
||||
args, config, acc, authenticator=authenticator, installer=installer)
|
||||
if not acme.obtain_and_enroll_certificate(
|
||||
doms, authenticator, installer, plugins):
|
||||
return "Certificate could not be obtained"
|
||||
# TODO: Handle errors from _init_acme?
|
||||
acme = _init_acme(config, acc, authenticator, installer)
|
||||
|
||||
if args.csr is not None:
|
||||
certr, chain = acme.obtain_certificate_from_csr(le_util.CSR(
|
||||
file=args.csr[0], data=args.csr[1], form="der"))
|
||||
acme.save_certificate(certr, chain, args.cert_path, args.chain_path)
|
||||
else:
|
||||
domains = _find_domains(args, installer)
|
||||
if not acme.obtain_and_enroll_certificate(
|
||||
domains, authenticator, installer, plugins):
|
||||
return "Certificate could not be obtained"
|
||||
|
||||
def install(args, config, plugins):
|
||||
"""Install a previously obtained cert in a server."""
|
||||
@@ -184,11 +200,11 @@ def install(args, config, plugins):
|
||||
installer = display_ops.pick_installer(config, args.installer, plugins)
|
||||
if installer is None:
|
||||
return "Installer could not be determined"
|
||||
acme, doms = _common_run(
|
||||
args, config, acc, authenticator=None, installer=installer)
|
||||
domains = _find_domains(args, installer)
|
||||
acme = _init_acme(config, acc, authenticator=None, installer=installer)
|
||||
assert args.cert_path is not None
|
||||
acme.deploy_certificate(doms, acc.key.file, args.cert_path, args.chain_path)
|
||||
acme.enhance_config(doms, args.redirect)
|
||||
acme.deploy_certificate(domains, acc.key.file, args.cert_path, args.chain_path)
|
||||
acme.enhance_config(domains, args.redirect)
|
||||
|
||||
|
||||
def revoke(args, unused_config, unused_plugins):
|
||||
@@ -243,10 +259,11 @@ def plugins_cmd(args, config, plugins): # TODO: Use IDiplay rathern than print
|
||||
print str(available)
|
||||
|
||||
|
||||
def read_file(filename):
|
||||
"""Returns the given file's contents with universal new line support.
|
||||
def read_file(filename, mode="rb"):
|
||||
"""Returns the given file's contents.
|
||||
|
||||
:param str filename: Filename
|
||||
:param str mode: open mode (see `open`)
|
||||
|
||||
:returns: A tuple of filename and its contents
|
||||
:rtype: tuple
|
||||
@@ -255,7 +272,7 @@ def read_file(filename):
|
||||
|
||||
"""
|
||||
try:
|
||||
return filename, open(filename, "rU").read()
|
||||
return filename, open(filename, mode).read()
|
||||
except IOError as exc:
|
||||
raise argparse.ArgumentTypeError(exc.strerror)
|
||||
|
||||
@@ -466,12 +483,22 @@ def create_parser(plugins, args):
|
||||
return subparser
|
||||
|
||||
add_subparser("run", run)
|
||||
add_subparser("auth", auth)
|
||||
parser_auth = add_subparser("auth", auth)
|
||||
add_subparser("install", install)
|
||||
parser_revoke = add_subparser("revoke", revoke)
|
||||
parser_rollback = add_subparser("rollback", rollback)
|
||||
add_subparser("config_changes", config_changes)
|
||||
|
||||
parser_auth.add_argument(
|
||||
"--csr", type=read_file, help="Path to a Certificate Signing "
|
||||
"Request (CSR) in DER format.")
|
||||
parser_auth.add_argument(
|
||||
"--cert-path", default=flag_default("cert_path"),
|
||||
help="When using --csr this is where certificate is saved.")
|
||||
parser_auth.add_argument(
|
||||
"--chain-path", default=flag_default("chain_path"),
|
||||
help="When using --csr this is where certificate chain is saved.")
|
||||
|
||||
parser_plugins = add_subparser("plugins", plugins_cmd)
|
||||
parser_plugins.add_argument("--init", action="store_true")
|
||||
parser_plugins.add_argument("--prepare", action="store_true")
|
||||
@@ -539,10 +566,6 @@ def _paths_parser(helpful):
|
||||
help=config_help("cert_dir"))
|
||||
add("paths", "--le-vhost-ext", default="-le-ssl.conf",
|
||||
help=config_help("le_vhost_ext"))
|
||||
add("paths", "--cert-path", default=flag_default("cert_path"),
|
||||
help=config_help("cert_path"))
|
||||
add("paths", "--chain-path", default=flag_default("chain_path"),
|
||||
help=config_help("chain_path"))
|
||||
add("paths", "--renewer-config-file", default=flag_default("renewer_config_file"),
|
||||
help=config_help("renewer_config_file"))
|
||||
add("paths", "-s", "--server", default=flag_default("server"),
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
import pkg_resources
|
||||
|
||||
import M2Crypto
|
||||
import OpenSSL.crypto
|
||||
import zope.component
|
||||
|
||||
from acme import jose
|
||||
@@ -125,22 +126,20 @@ class Client(object):
|
||||
"{0}.".format(self.account.email))
|
||||
reporter.add_message(recovery_msg, reporter.HIGH_PRIORITY, True)
|
||||
|
||||
def obtain_certificate(self, domains, csr=None):
|
||||
"""Obtains a certificate from the ACME server.
|
||||
def _obtain_certificate(self, domains, csr):
|
||||
"""Obtain certificate.
|
||||
|
||||
:meth:`.register` must be called before :meth:`.obtain_certificate`
|
||||
Internal function with precondition that `domains` are
|
||||
consistent with identifiers present in the `csr`.
|
||||
|
||||
.. todo:: This function does not currently handle CSR correctly.
|
||||
:param list domains: Domain names.
|
||||
:param .le_util.CSR csr: DER-encoded Certificate Signing
|
||||
Request. The key used to generate this CSR can be different
|
||||
than `authkey`.
|
||||
|
||||
:param set domains: domains to get a certificate
|
||||
|
||||
:param csr: CSR must contain requested domains, the key used to generate
|
||||
this CSR can be different than self.authkey
|
||||
:type csr: :class:`CSR`
|
||||
|
||||
:returns: Certificate, private key, and certificate chain (all
|
||||
PEM-encoded).
|
||||
:rtype: `tuple` of `str`
|
||||
:returns: `.CertificateResource` and certificate chain (as
|
||||
returned by `.fetch_chain`).
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
if self.auth_handler is None:
|
||||
@@ -151,37 +150,54 @@ class Client(object):
|
||||
if self.account.regr is None:
|
||||
raise errors.Error("Please register with the ACME server first.")
|
||||
|
||||
# Perform Challenges/Get Authorizations
|
||||
logging.debug("CSR: %s, domains: %s", csr, domains)
|
||||
|
||||
authzr = self.auth_handler.get_authorizations(domains)
|
||||
|
||||
# Create CSR from names
|
||||
cert_key = crypto_util.init_save_key(
|
||||
self.config.rsa_key_size, self.config.key_dir)
|
||||
csr = crypto_util.init_save_csr(
|
||||
cert_key, domains, self.config.cert_dir)
|
||||
|
||||
# Retrieve certificate
|
||||
certr = self.network.request_issuance(
|
||||
jose.ComparableX509(
|
||||
M2Crypto.X509.load_request_der_string(csr.data)),
|
||||
authzr)
|
||||
return certr, self.network.fetch_chain(certr)
|
||||
|
||||
cert_pem = certr.body.as_pem()
|
||||
chain_pem = None
|
||||
if certr.cert_chain_uri is not None:
|
||||
chain_pem = self.network.fetch_chain(certr)
|
||||
def obtain_certificate_from_csr(self, csr):
|
||||
"""Obtain certficiate from CSR.
|
||||
|
||||
if chain_pem is None:
|
||||
# XXX: just to stop RenewableCert from complaining; this is
|
||||
# probably not a good solution
|
||||
chain_pem = ""
|
||||
else:
|
||||
chain_pem = chain_pem.as_pem()
|
||||
:param .le_util.CSR csr: DER-encoded Certificate Signing
|
||||
Request.
|
||||
|
||||
return cert_pem, cert_key.pem, chain_pem
|
||||
:returns: `.CertificateResource` and certificate chain (as
|
||||
returned by `.fetch_chain`).
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
return self._obtain_certificate(
|
||||
# TODO: add CN to domains?
|
||||
crypto_util.get_sans_from_csr(
|
||||
csr.data, OpenSSL.crypto.FILETYPE_ASN1), csr)
|
||||
|
||||
def obtain_certificate(self, domains):
|
||||
"""Obtains a certificate from the ACME server.
|
||||
|
||||
`.register` must be called before `.obtain_certificate`
|
||||
|
||||
:param set domains: domains to get a certificate
|
||||
|
||||
:returns: `.CertificateResource`, certificate chain (as
|
||||
returned by `.fetch_chain`), and newly generated private key
|
||||
(`.le_util.Key`) and DER-encoded Certificate Signing Request
|
||||
(`.le_util.CSR`).
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
# Create CSR from names
|
||||
key = crypto_util.init_save_key(
|
||||
self.config.rsa_key_size, self.config.key_dir)
|
||||
csr = crypto_util.init_save_csr(key, domains, self.config.cert_dir)
|
||||
|
||||
return self._obtain_certificate(domains, csr) + (key, csr)
|
||||
|
||||
def obtain_and_enroll_certificate(
|
||||
self, domains, authenticator, installer, plugins, csr=None):
|
||||
self, domains, authenticator, installer, plugins):
|
||||
"""Obtain and enroll certificate.
|
||||
|
||||
Get a new certificate for the specified domains using the specified
|
||||
@@ -197,14 +213,14 @@ class Client(object):
|
||||
|
||||
:param plugins: A PluginsFactory object.
|
||||
|
||||
:param str csr: A preexisting CSR to use with this request.
|
||||
|
||||
:returns: A new :class:`letsencrypt.storage.RenewableCert` instance
|
||||
referred to the enrolled cert lineage, or False if the cert could
|
||||
not be obtained.
|
||||
|
||||
"""
|
||||
cert, privkey, chain = self.obtain_certificate(domains, csr)
|
||||
certr, chain, key, _ = self.obtain_certificate(domains)
|
||||
|
||||
# TODO: remove this dirty hack
|
||||
self.config.namespace.authenticator = plugins.find_init(
|
||||
authenticator).name
|
||||
if installer is not None:
|
||||
@@ -226,8 +242,12 @@ class Client(object):
|
||||
"Non-standard path(s), might not work with crontab installed "
|
||||
"by your operating system package manager")
|
||||
|
||||
# XXX: just to stop RenewableCert from complaining; this is
|
||||
# probably not a good solution
|
||||
chain_pem = "" if chain is None else chain.as_pem()
|
||||
lineage = storage.RenewableCert.new_lineage(
|
||||
domains[0], cert, privkey, chain, params, config, cli_config)
|
||||
domains[0], certr.body.as_pem(), key.pem, chain_pem, params,
|
||||
config, cli_config)
|
||||
self._report_renewal_status(lineage)
|
||||
return lineage
|
||||
|
||||
@@ -258,13 +278,14 @@ class Client(object):
|
||||
reporter = zope.component.getUtility(interfaces.IReporter)
|
||||
reporter.add_message(msg, reporter.LOW_PRIORITY, True)
|
||||
|
||||
def save_certificate(self, certr, cert_path, chain_path):
|
||||
def save_certificate(self, certr, chain_cert, cert_path, chain_path):
|
||||
# pylint: disable=no-self-use
|
||||
"""Saves the certificate received from the ACME server.
|
||||
|
||||
:param certr: ACME "certificate" resource.
|
||||
:type certr: :class:`acme.messages.Certificate`
|
||||
|
||||
:param chain_cert:
|
||||
:param str cert_path: Candidate path to a certificate.
|
||||
:param str chain_path: Candidate path to a certificate chain.
|
||||
|
||||
@@ -274,6 +295,10 @@ class Client(object):
|
||||
:raises IOError: If unable to find room to write the cert files
|
||||
|
||||
"""
|
||||
for path in cert_path, chain_path:
|
||||
le_util.make_or_verify_dir(
|
||||
os.path.dirname(path), 0o755, os.geteuid())
|
||||
|
||||
# try finally close
|
||||
cert_chain_abspath = None
|
||||
cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644)
|
||||
@@ -286,22 +311,20 @@ class Client(object):
|
||||
logging.info("Server issued certificate; certificate written to %s",
|
||||
act_cert_path)
|
||||
|
||||
if certr.cert_chain_uri is not None:
|
||||
if chain_cert is not None:
|
||||
chain_file, act_chain_path = le_util.unique_file(
|
||||
chain_path, 0o644)
|
||||
# TODO: Except
|
||||
chain_cert = self.network.fetch_chain(certr)
|
||||
if chain_cert is not None:
|
||||
chain_file, act_chain_path = le_util.unique_file(
|
||||
chain_path, 0o644)
|
||||
chain_pem = chain_cert.as_pem()
|
||||
try:
|
||||
chain_file.write(chain_pem)
|
||||
finally:
|
||||
chain_file.close()
|
||||
chain_pem = chain_cert.as_pem()
|
||||
try:
|
||||
chain_file.write(chain_pem)
|
||||
finally:
|
||||
chain_file.close()
|
||||
|
||||
logging.info("Cert chain written to %s", act_chain_path)
|
||||
logging.info("Cert chain written to %s", act_chain_path)
|
||||
|
||||
# This expects a valid chain file
|
||||
cert_chain_abspath = os.path.abspath(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
|
||||
|
||||
@@ -390,8 +413,7 @@ def validate_key_csr(privkey, csr=None):
|
||||
:param privkey: Key associated with CSR
|
||||
:type privkey: :class:`letsencrypt.le_util.Key`
|
||||
|
||||
:param csr: CSR
|
||||
:type csr: :class:`letsencrypt.le_util.CSR`
|
||||
:param .le_util.CSR csr: CSR
|
||||
|
||||
:raises .errors.Error: when validation fails
|
||||
|
||||
|
||||
@@ -59,15 +59,15 @@ class NamespaceConfig(object):
|
||||
def backup_dir(self): # pylint: disable=missing-docstring
|
||||
return os.path.join(self.namespace.work_dir, constants.BACKUP_DIR)
|
||||
|
||||
@property
|
||||
def cert_dir(self): # pylint: disable=missing-docstring
|
||||
return os.path.join(self.namespace.config_dir, constants.CERT_DIR)
|
||||
|
||||
@property
|
||||
def cert_key_backup(self): # pylint: disable=missing-docstring
|
||||
return os.path.join(self.namespace.work_dir,
|
||||
constants.CERT_KEY_BACKUP_DIR, self.server_path)
|
||||
|
||||
@property
|
||||
def cert_dir(self): # pylint: disable=missing-docstring
|
||||
return os.path.join(self.namespace.config_dir, constants.CERT_DIR)
|
||||
|
||||
@property
|
||||
def in_progress_dir(self): # pylint: disable=missing-docstring
|
||||
return os.path.join(self.namespace.work_dir, constants.IN_PROGRESS_DIR)
|
||||
|
||||
@@ -17,13 +17,13 @@ CLI_DEFAULTS = dict(
|
||||
work_dir="/var/lib/letsencrypt",
|
||||
no_verify_ssl=False,
|
||||
dvsni_port=challenges.DVSNI.PORT,
|
||||
cert_path="./cert.pem",
|
||||
chain_path="./chain.pem",
|
||||
|
||||
# TODO: blocked by #485, values ignored
|
||||
backup_dir="not used",
|
||||
key_dir="not used",
|
||||
certs_dir="not used",
|
||||
cert_path="not used",
|
||||
chain_path="not used",
|
||||
renewer_config_file="not used",
|
||||
)
|
||||
"""Defaults for CLI flags and `.IConfig` attributes."""
|
||||
@@ -69,13 +69,13 @@ ACCOUNT_KEYS_DIR = "keys"
|
||||
BACKUP_DIR = "backups"
|
||||
"""Directory (relative to `IConfig.work_dir`) where backups are kept."""
|
||||
|
||||
CERT_DIR = "certs"
|
||||
"""See `.IConfig.cert_dir`."""
|
||||
|
||||
CERT_KEY_BACKUP_DIR = "keys-certs"
|
||||
"""Directory where all certificates and keys are stored (relative to
|
||||
`IConfig.work_dir`). Used for easy revocation."""
|
||||
|
||||
CERT_DIR = "certs"
|
||||
"""See `.IConfig.cert_dir`."""
|
||||
|
||||
IN_PROGRESS_DIR = "IN_PROGRESS"
|
||||
"""Directory used before a permanent checkpoint is finalized (relative to
|
||||
`IConfig.work_dir`)."""
|
||||
|
||||
@@ -90,6 +90,9 @@ def make_csr(key_str, domains):
|
||||
:param str key_str: RSA key.
|
||||
:param list domains: Domains included in the certificate.
|
||||
|
||||
.. todo:: Detect duplicates in `domains`? Using a set doesn't
|
||||
preserve order...
|
||||
|
||||
:returns: new CSR in PEM and DER form containing all domains
|
||||
:rtype: tuple
|
||||
|
||||
@@ -101,13 +104,7 @@ def make_csr(key_str, domains):
|
||||
|
||||
csr = M2Crypto.X509.Request()
|
||||
csr.set_pubkey(pubkey)
|
||||
name = csr.get_subject()
|
||||
name.C = "US"
|
||||
name.ST = "Michigan"
|
||||
name.L = "Ann Arbor"
|
||||
name.O = "EFF"
|
||||
name.OU = "University of Michigan"
|
||||
name.CN = domains[0]
|
||||
# TODO: what to put into csr.get_subject()?
|
||||
|
||||
extstack = M2Crypto.X509.X509_Extension_Stack()
|
||||
ext = M2Crypto.X509.new_extension(
|
||||
|
||||
@@ -205,8 +205,22 @@ def success_installation(domains):
|
||||
|
||||
"""
|
||||
util(interfaces.IDisplay).notification(
|
||||
"Congratulations! You have successfully enabled "
|
||||
"%s!" % _gen_https_names(domains), pause=False)
|
||||
"Congratulations! You have successfully enabled {0}!{1}{1}"
|
||||
"You should test your configuration at:{1}{2}".format(
|
||||
_gen_https_names(domains),
|
||||
os.linesep,
|
||||
os.linesep.join(_gen_ssl_lab_urls(domains))),
|
||||
height=(10 + len(domains)),
|
||||
pause=False)
|
||||
|
||||
|
||||
def _gen_ssl_lab_urls(domains):
|
||||
"""Returns a list of urls.
|
||||
|
||||
:param list domains: Each domain is a 'str'
|
||||
|
||||
"""
|
||||
return ["https://www.ssllabs.com/ssltest/analyze.html?d=%s" % dom for dom in domains]
|
||||
|
||||
|
||||
def _gen_https_names(domains):
|
||||
|
||||
@@ -190,8 +190,6 @@ class IConfig(zope.interface.Interface):
|
||||
|
||||
# TODO: the following are not used, but blocked by #485
|
||||
le_vhost_ext = zope.interface.Attribute("not used")
|
||||
cert_path = zope.interface.Attribute("not used")
|
||||
chain_path = zope.interface.Attribute("not used")
|
||||
|
||||
|
||||
class IInstaller(IPlugin):
|
||||
|
||||
@@ -83,14 +83,15 @@ def renew(cert, old_version):
|
||||
our_client = client.Client(config, account, authenticator, None)
|
||||
with open(cert.version("cert", old_version)) as f:
|
||||
sans = crypto_util.get_sans_from_cert(f.read())
|
||||
new_cert, new_key, new_chain = our_client.obtain_certificate(sans)
|
||||
if new_cert and new_key and new_chain:
|
||||
new_certr, new_chain, new_key, _ = our_client.obtain_certificate(sans)
|
||||
if new_chain is not None:
|
||||
# XXX: Assumes that there was no 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
|
||||
# already understands this distinction!)
|
||||
return cert.save_successor(old_version, new_cert, new_key, new_chain)
|
||||
return cert.save_successor(old_version, new_certr.body.as_pem(),
|
||||
new_key.pem, new_chain.as_pem())
|
||||
# TODO: Notify results
|
||||
else:
|
||||
# TODO: Notify negative results
|
||||
|
||||
@@ -6,8 +6,11 @@ import shutil
|
||||
import tempfile
|
||||
|
||||
import configobj
|
||||
import M2Crypto.X509
|
||||
import mock
|
||||
|
||||
from acme import jose
|
||||
|
||||
from letsencrypt import account
|
||||
from letsencrypt import configuration
|
||||
from letsencrypt import le_util
|
||||
@@ -15,6 +18,8 @@ from letsencrypt import le_util
|
||||
|
||||
KEY = pkg_resources.resource_string(
|
||||
__name__, os.path.join("testdata", "rsa512_key.pem"))
|
||||
CSR_SAN = pkg_resources.resource_string(
|
||||
__name__, os.path.join("testdata", "csr-san.der"))
|
||||
|
||||
|
||||
class ClientTest(unittest.TestCase):
|
||||
@@ -27,16 +32,57 @@ class ClientTest(unittest.TestCase):
|
||||
self.account = mock.MagicMock(**{"key.pem": KEY})
|
||||
|
||||
from letsencrypt.client import Client
|
||||
with mock.patch("letsencrypt.client.network") as network:
|
||||
with mock.patch("letsencrypt.client.network.Network") as network:
|
||||
self.client = Client(
|
||||
config=self.config, account_=self.account, dv_auth=None,
|
||||
installer=None)
|
||||
config=self.config, account_=self.account,
|
||||
dv_auth=None, installer=None)
|
||||
self.network = network
|
||||
|
||||
def test_init_network_verify_ssl(self):
|
||||
self.network.Network.assert_called_once_with(
|
||||
self.network.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, verify_ssl=True)
|
||||
|
||||
def _mock_obtain_certificate(self):
|
||||
self.client.auth_handler = mock.MagicMock()
|
||||
self.network().request_issuance.return_value = mock.sentinel.certr
|
||||
self.network().fetch_chain.return_value = mock.sentinel.chain
|
||||
|
||||
def _check_obtain_certificate(self):
|
||||
self.client.auth_handler.get_authorizations.assert_called_once_with(
|
||||
["example.com", "www.example.com"])
|
||||
self.network.request_issuance.assert_callend_once_with(
|
||||
jose.ComparableX509(
|
||||
M2Crypto.X509.load_request_der_string(CSR_SAN)),
|
||||
self.client.auth_handler.get_authorizations())
|
||||
self.network().fetch_chain.assert_called_once_with(mock.sentinel.certr)
|
||||
|
||||
def test_obtain_certificate_from_csr(self):
|
||||
self._mock_obtain_certificate()
|
||||
self.assertEqual(
|
||||
(mock.sentinel.certr, mock.sentinel.chain),
|
||||
self.client.obtain_certificate_from_csr(le_util.CSR(
|
||||
form="der", file=None, data=CSR_SAN)))
|
||||
self._check_obtain_certificate()
|
||||
|
||||
@mock.patch("letsencrypt.client.crypto_util")
|
||||
def test_obtain_certificate(self, mock_crypto_util):
|
||||
self._mock_obtain_certificate()
|
||||
|
||||
csr = le_util.CSR(form="der", file=None, data=CSR_SAN)
|
||||
mock_crypto_util.init_save_csr.return_value = csr
|
||||
mock_crypto_util.init_save_key.return_value = mock.sentinel.key
|
||||
domains = ["example.com", "www.example.com"]
|
||||
|
||||
self.assertEqual(
|
||||
self.client.obtain_certificate(domains),
|
||||
(mock.sentinel.certr, mock.sentinel.chain, mock.sentinel.key, csr))
|
||||
|
||||
mock_crypto_util.init_save_key.assert_called_once_with(
|
||||
self.config.rsa_key_size, self.config.key_dir)
|
||||
mock_crypto_util.init_save_csr.assert_called_once_with(
|
||||
mock.sentinel.key, domains, self.config.cert_dir)
|
||||
self._check_obtain_certificate()
|
||||
|
||||
@mock.patch("letsencrypt.client.zope.component.getUtility")
|
||||
def test_report_new_account(self, mock_zope):
|
||||
# pylint: disable=protected-access
|
||||
|
||||
@@ -182,6 +182,24 @@ class ChooseAccountTest(unittest.TestCase):
|
||||
self.assertTrue(self._call([self.acc1, self.acc2]) is None)
|
||||
|
||||
|
||||
class GenSSLLabURLs(unittest.TestCase):
|
||||
"""Loose test of _gen_ssl_lab_urls. URL can change easily in the future."""
|
||||
def setUp(self):
|
||||
zope.component.provideUtility(display_util.FileDisplay(sys.stdout))
|
||||
|
||||
@classmethod
|
||||
def _call(cls, domains):
|
||||
from letsencrypt.display.ops import _gen_ssl_lab_urls
|
||||
return _gen_ssl_lab_urls(domains)
|
||||
|
||||
def test_zero(self):
|
||||
self.assertEqual(self._call([]), [])
|
||||
|
||||
def test_two(self):
|
||||
urls = self._call(["eff.org", "umich.edu"])
|
||||
self.assertTrue("eff.org" in urls[0])
|
||||
self.assertTrue("umich.edu" in urls[1])
|
||||
|
||||
class GenHttpsNamesTest(unittest.TestCase):
|
||||
"""Test _gen_https_names."""
|
||||
def setUp(self):
|
||||
|
||||
@@ -581,13 +581,18 @@ class RenewableCertTests(unittest.TestCase):
|
||||
self.assertFalse(renewer.renew(self.test_rc, 1))
|
||||
self.test_rc.configfile["renewalparams"]["authenticator"] = "apache"
|
||||
mock_client = mock.MagicMock()
|
||||
mock_client.obtain_certificate.return_value = ("cert", "key", "chain")
|
||||
# pylint: disable=star-args
|
||||
mock_client.obtain_certificate.return_value = (
|
||||
mock.Mock(**{'body.as_pem.return_value': 'cert'}),
|
||||
mock.Mock(**{'as_pem.return_value': 'chain'}),
|
||||
mock.Mock(pem="key"), mock.sentinel.csr)
|
||||
mock_c.return_value = mock_client
|
||||
self.assertEqual(2, renewer.renew(self.test_rc, 1))
|
||||
# TODO: We could also make several assertions about calls that should
|
||||
# have been made to the mock functions here.
|
||||
self.assertEqual(mock_da.call_count, 1)
|
||||
mock_client.obtain_certificate.return_value = (None, None, None)
|
||||
mock_client.obtain_certificate.return_value = (
|
||||
mock.sentinel.certr, None, mock.sentinel.key, mock.sentinel.csr)
|
||||
# This should fail because the renewal itself appears to fail
|
||||
self.assertFalse(renewer.renew(self.test_rc, 1))
|
||||
|
||||
|
||||
@@ -7,19 +7,29 @@ root="$(mktemp -d)"
|
||||
echo "\nRoot integration tests directory: $root"
|
||||
store_flags="--config-dir $root/conf --work-dir $root/work"
|
||||
|
||||
# first three flags required, rest is handy defaults
|
||||
letsencrypt \
|
||||
--server http://localhost:4000/acme/new-reg \
|
||||
--no-verify-ssl \
|
||||
--dvsni-port 5001 \
|
||||
$store_flags \
|
||||
--text \
|
||||
--agree-eula \
|
||||
--email "" \
|
||||
--domains le.wtf \
|
||||
--authenticator standalone \
|
||||
-vvvvvvv \
|
||||
auth
|
||||
common() {
|
||||
# first three flags required, rest is handy defaults
|
||||
letsencrypt \
|
||||
--server http://localhost:4000/acme/new-reg \
|
||||
--no-verify-ssl \
|
||||
--dvsni-port 5001 \
|
||||
$store_flags \
|
||||
--text \
|
||||
--agree-eula \
|
||||
--email "" \
|
||||
--authenticator standalone \
|
||||
-vvvvvvv "$@"
|
||||
}
|
||||
|
||||
common --domains le.wtf auth
|
||||
|
||||
export CSR_PATH="${root}/csr.der" OPENSSL_CNF=examples/openssl.cnf
|
||||
./examples/generate-csr.sh le.wtf
|
||||
common auth --csr "$CSR_PATH" \
|
||||
--cert-path "${root}/csr/cert.pem" \
|
||||
--chain-path "${root}/csr/chain.pem"
|
||||
openssl x509 -in "${root}/csr/0000_cert.pem" -text
|
||||
openssl x509 -in "${root}/csr/0000_chain.pem" -text
|
||||
|
||||
# the following assumes that Boulder issues certificates for less than
|
||||
# 10 years, otherwise renewal will not take place
|
||||
|
||||
Reference in New Issue
Block a user