Introduction

Certbot is part of EFF’s effort to encrypt the entire Internet. Secure communication over the Web relies on HTTPS, which requires the use of a digital certificate that lets browsers verify the identity of web servers (e.g., is that really google.com?). Web servers obtain their certificates from trusted third parties called certificate authorities (CAs). Certbot is an easy-to-use client that fetches a certificate from Let’s Encrypt—an open certificate authority launched by the EFF, Mozilla, and others—and deploys it to a web server.

Anyone who has gone through the trouble of setting up a secure website knows what a hassle getting and maintaining a certificate is. Certbot and Let’s Encrypt can automate away the pain and let you turn on and manage HTTPS with simple commands. Using Certbot and Let’s Encrypt is free, so there’s no need to arrange payment.

How you use Certbot depends on the configuration of your web server. The best way to get started is to use our interactive guide [https://certbot.eff.org]. It generates instructions based on your configuration settings. In most cases, you’ll need root or administrator access [https://certbot.eff.org/faq/#does-certbot-require-root-administrator-privileges] to your web server to run Certbot.

If you’re using a hosted service and don’t have direct access to your web server, you might not be able to use Certbot. Check with your hosting provider for documentation about uploading certificates or using certificates issued by Let’s Encrypt.

Certbot is a fully-featured, extensible client for the Let’s Encrypt CA (or any other CA that speaks the ACME [https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md] protocol) that can automate the tasks of obtaining certificates and configuring webservers to use them. This client runs on Unix-based operating systems.

Until May 2016, Certbot was named simply letsencrypt or letsencrypt-auto, depending on install method. Instructions on the Internet, and some pieces of the software, may still refer to this older name.

Contributing

If you’d like to contribute to this project please read Developer Guide [https://certbot.eff.org/docs/contributing.html].

Installation

The easiest way to install Certbot is by visiting certbot.eff.org [https://certbot.eff.org/], where you can find the correct installation instructions for many web server and OS combinations. For more information, see Get Certbot [https://certbot.eff.org/docs/install.html].

How to run the client

In many cases, you can just run certbot-auto or certbot, and the client will guide you through the process of obtaining and installing certs interactively.

For full command line help, you can type:

./certbot-auto --help all

You can also tell it exactly what you want it to do from the command line. For instance, if you want to obtain a cert for example.com, www.example.com, and other.example.net, using the Apache plugin to both obtain and install the certs, you could do this:

./certbot-auto --apache -d example.com -d www.example.com -d other.example.net

(The first time you run the command, it will make an account, and ask for an email and agreement to the Let’s Encrypt Subscriber Agreement; you can automate those with --email and --agree-tos)

If you want to use a webserver that doesn’t have full plugin support yet, you can still use “standalone” or “webroot” plugins to obtain a certificate:

./certbot-auto certonly --standalone --email admin@example.com -d example.com -d www.example.com -d other.example.net

Understanding the client in more depth

To understand what the client is doing in detail, it’s important to understand the way it uses plugins. Please see the explanation of plugins [https://certbot.eff.org/docs/using.html#plugins] in the User Guide.

Links

Documentation: https://certbot.eff.org/docs

Software project: https://github.com/certbot/certbot

Notes for developers: https://certbot.eff.org/docs/contributing.html

Main Website: https://certbot.eff.org

Let’s Encrypt Website: https://letsencrypt.org

IRC Channel: #letsencrypt on Freenode [https://webchat.freenode.net?channels=%23letsencrypt]

Community: https://community.letsencrypt.org

ACME spec: http://ietf-wg-acme.github.io/acme/

ACME working area in github: https://github.com/ietf-wg-acme/acme

Travis CI status [https://travis-ci.org/certbot/certbot] Coverage status [https://coveralls.io/r/certbot/certbot] Documentation status [https://readthedocs.org/projects/letsencrypt/] [https://quay.io/repository/letsencrypt/letsencrypt]

System Requirements

See https://certbot.eff.org/docs/install.html#system-requirements.

Get Certbot

Table of Contents

About Certbot

System Requirements

Alternate installation methods

Certbot-Auto

Problems with Python virtual environment

Running with Docker

Operating System Packages

Installing from source

About Certbot

Certbot is packaged for many common operating systems and web servers. Check whether certbot (or letsencrypt) is packaged for your web server’s OS by visiting certbot.eff.org [https://certbot.eff.org], where you will also find the correct installation instructions for your system.

Note

Unless you have very specific requirements, we kindly suggest that you use the Certbot packages provided by your package manager (see certbot.eff.org [https://certbot.eff.org]). If such packages are not available, we recommend using certbot-auto, which automates the process of installing Certbot on your system.

System Requirements

Certbot currently requires Python 2.6, 2.7, or 3.3+. By default, it requires root access in order to write to /etc/letsencrypt, /var/log/letsencrypt, /var/lib/letsencrypt; to bind to ports 80 and 443 (if you use the standalone plugin) and to read and modify webserver configurations (if you use the apache or nginx plugins). If none of these apply to you, it is theoretically possible to run without root privileges, but for most users who want to avoid running an ACME client as root, either letsencrypt-nosudo [https://github.com/diafygi/letsencrypt-nosudo] or simp_le [https://github.com/kuba/simp_le] are more appropriate choices.

The Apache plugin currently requires an OS with augeas version 1.0; currently it supports [https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/constants.py] modern OSes based on Debian, Fedora, SUSE, Gentoo and Darwin.

Installing with certbot-auto requires 512MB of RAM in order to build some of the dependencies. Installing from pre-built OS packages avoids this requirement. You can also temporarily set a swap file. See “Problems with Python virtual environment” below for details.

Alternate installation methods

If you are offline or your operating system doesn’t provide a package, you can use an alternate method for installing certbot.

Certbot-Auto

The certbot-auto wrapper script installs Certbot, obtaining some dependencies from your web server OS and putting others in a python virtual environment. You can download and run it as follows:

user@webserver:~$ wget https://dl.eff.org/certbot-auto
user@webserver:~$ chmod a+x ./certbot-auto
user@webserver:~$ ./certbot-auto --help

Hint

The certbot-auto download is protected by HTTPS, which is pretty good, but if you’d like to double check the integrity of the certbot-auto script, you can use these steps for verification before running it:

user@server:~$ wget -N https://dl.eff.org/certbot-auto.asc
user@server:~$ gpg2 --recv-key A2CFB51FA275A7286234E7B24D17C995CD9775F2
user@server:~$ gpg2 --trusted-key 4D17C995CD9775F2 --verify certbot-auto.asc certbot-auto

The certbot-auto command updates to the latest client release automatically. Since certbot-auto is a wrapper to certbot, it accepts exactly the same command line flags and arguments. For more information, see Certbot command-line options [https://certbot.eff.org/docs/using.html#command-line-options].

For full command line help, you can type:

./certbot-auto --help all

Problems with Python virtual environment

On a low memory system such as VPS with less than 512MB of RAM, the required dependencies of Certbot will failed to build. This can be identified if the pip outputs contains something like internal compiler error: Killed (program cc1). You can workaround this restriction by creating a temporary swapfile:

user@webserver:~$ sudo fallocate -l 1G /tmp/swapfile
user@webserver:~$ sudo chmod 600 /tmp/swapfile
user@webserver:~$ sudo mkswap /tmp/swapfile
user@webserver:~$ sudo swapon /tmp/swapfile

Disable and remove the swapfile once the virtual enviroment is constructed:

user@webserver:~$ sudo swapoff /tmp/swapfile
user@webserver:~$ sudo rm /tmp/swapfile

Running with Docker

Docker [https://docker.com] is an amazingly simple and quick way to obtain a certificate. However, this mode of operation is unable to install certificates or configure your webserver, because our installer plugins cannot reach your webserver from inside the Docker container.

Most users should use the operating system packages (see instructions at certbot.eff.org [https://certbot.eff.org]) or, as a fallback, certbot-auto. You should only use Docker if you are sure you know what you are doing and have a good reason to do so.

You should definitely read the Where are my certificates? section, in order to know how to manage the certs manually. Our ciphersuites page provides some information about recommended ciphersuites. If none of these make much sense to you, you should definitely use the certbot-auto method, which enables you to use installer plugins that cover both of those hard topics.

If you’re still not convinced and have decided to use this method, from the server that the domain you’re requesting a cert for resolves to, install Docker [https://docs.docker.com/engine/installation/], then issue the following command:

sudo docker run -it --rm -p 443:443 -p 80:80 --name certbot \
            -v "/etc/letsencrypt:/etc/letsencrypt" \
            -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
            certbot/certbot certonly

Running Certbot with the certonly command will obtain a certificate and place it in the directory /etc/letsencrypt/live on your system. Because Certonly cannot install the certificate from within Docker, you must install the certificate manually according to the procedure recommended by the provider of your webserver.

For more information about the layout of the /etc/letsencrypt directory, see Where are my certificates?.

Operating System Packages

Arch Linux

sudo pacman -S certbot

Debian

If you run Debian Stretch or Debian Sid, you can install certbot packages.

sudo apt-get update
sudo apt-get install certbot python-certbot-apache

If you don’t want to use the Apache plugin, you can omit the python-certbot-apache package.

Packages exist for Debian Jessie via backports. First you’ll have to follow the instructions at http://backports.debian.org/Instructions/ to enable the Jessie backports repo, if you have not already done so. Then run:

sudo apt-get install certbot python-certbot-apache -t jessie-backports

Fedora

sudo dnf install certbot python2-certbot-apache

FreeBSD

Port: cd /usr/ports/security/py-certbot && make install clean

Package: pkg install py27-certbot

Gentoo

The official Certbot client is available in Gentoo Portage. If you want to use the Apache plugin, it has to be installed separately:

emerge -av app-crypt/certbot
emerge -av app-crypt/certbot-apache

When using the Apache plugin, you will run into a “cannot find a cert or key directive” error if you’re sporting the default Gentoo httpd.conf. You can fix this by commenting out two lines in /etc/apache2/httpd.conf as follows:

Change

<IfDefine SSL>
LoadModule ssl_module modules/mod_ssl.so
</IfDefine>

to

#<IfDefine SSL>
LoadModule ssl_module modules/mod_ssl.so
#</IfDefine>

For the time being, this is the only way for the Apache plugin to recognise the appropriate directives when installing the certificate. Note: this change is not required for the other plugins.

NetBSD

Build from source: cd /usr/pkgsrc/security/py-certbot && make install clean

Install pre-compiled package: pkg_add py27-certbot

OpenBSD

Port: cd /usr/ports/security/letsencrypt/client && make install clean

Package: pkg_add letsencrypt

Other Operating Systems

OS packaging is an ongoing effort. If you’d like to package Certbot for your distribution of choice please have a look at the Packaging Guide.

Installing from source

Installation from source is only supported for developers and the whole process is described in the Developer Guide.

Warning

Please do not use python setup.py install or python pip install .. Please do not attempt the installation commands as superuser/root and/or without virtual environment, e.g. sudo python setup.py install, sudo pip install, sudo ./venv/bin/.... These modes of operation might corrupt your operating system and are not supported by the Certbot team!

User Guide

Table of Contents

Certbot Commands

Getting certificates (and choosing plugins)

Apache

Webroot

Nginx

Standalone

Manual

Third-party plugins

Managing certificates

Re-creating and Updating Existing Certificates

Changing a Certificate’s Domains

Revoking certificates

Renewing certificates

Modifying the Renewal Configuration File

Where are my certificates?

Pre and Post Validation Hooks

Configuration file

Certbot command-line options

Getting help

Certbot Commands

Certbot uses a number of different commands (also referred to as “subcommands”) to request specific actions such as obtaining, renewing, or revoking certificates. The most important and commonly-used commands will be discussed throughout this document; an exhaustive list also appears near the end of the document.

The certbot script on your web server might be named letsencrypt if your system uses an older package, or certbot-auto if you used an alternate installation method. Throughout the docs, whenever you see certbot, swap in the correct name as needed.

Getting certificates (and choosing plugins)

The Certbot client supports two types of plugins for obtaining and installing certificates: authenticators and installers.

Authenticators are plugins used with the certonly command to obtain a certificate. The authenticator validates that you control the domain(s) you are requesting a certificate for, obtains a certificate for the specified domain(s), and places the certificate in the /etc/letsencrypt directory on your machine. The authenticator does not install the certificate (it does not edit any of your server’s configuration files to serve the obtained certificate). If you specify multiple domains to authenticate, they will all be listed in a single certificate. To obtain multiple separate certificates you will need to run Certbot multiple times.

Installers are Plugins used with the install command to install a certificate. These plugins can modify your webserver’s configuration to serve your website over HTTPS using certificates obtained by certbot.

Plugins that do both can be used with the certbot run command, which is the default when no command is specified. The run subcommand can also be used to specify a combination of distinct authenticator and installer plugins.

Plugin
Auth
Inst
Notes
Challenge types (and port)

apache
Y
Y

Automates obtaining and installing a certificate with Apache

2.4 on Debian-based distributions with libaugeas0 1.0+.

tls-sni-01 [https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.3] (443)

webroot
Y
N

Obtains a certificate by writing to the webroot directory of

an already running webserver.

http-01 [https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.2] (80)

nginx
Y
Y

Automates obtaining and installing a certificate with Nginx.

Shipped with Certbot 0.9.0.

tls-sni-01 [https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.3] (443)

standalone
Y
N

Uses a “standalone” webserver to obtain a certificate.

Requires port 80 or 443 to be available. This is useful on

systems with no webserver, or when direct integration with

the local webserver is not supported or not desired.

http-01 [https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.2] (80) or tls-sni-01 [https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.3] (443)

manual
Y
N

Helps you obtain a certificate by giving you instructions to

perform domain validation yourself. Additionally allows you

to specify scripts to automate the validation task in a

customized way.

http-01 [https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.2] (80) or dns-01 [https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.4] (53)

Under the hood, plugins use one of several ACME protocol challenges [https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7] to prove you control a domain. The options are http-01 [https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.2] (which uses port 80), tls-sni-01 [https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.3] (port 443) and dns-01 [https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.4] (requiring configuration of a DNS server on port 53, though that’s often not the same machine as your webserver). A few plugins support more than one challenge type, in which case you can choose one with --preferred-challenges.

There are also many third-party-plugins available. Below we describe in more detail the circumstances in which each plugin can be used, and how to use it.

Apache

The Apache plugin currently requires an OS with augeas version 1.0; currently it supports [https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/constants.py] modern OSes based on Debian, Fedora, SUSE, Gentoo and Darwin. This automates both obtaining and installing certificates on an Apache webserver. To specify this plugin on the command line, simply include --apache.

Webroot

If you’re running a local webserver for which you have the ability to modify the content being served, and you’d prefer not to stop the webserver during the certificate issuance process, you can use the webroot plugin to obtain a certificate by including certonly and --webroot on the command line. In addition, you’ll need to specify --webroot-path or -w with the top-level directory (“web root”) containing the files served by your webserver. For example, --webroot-path /var/www/html or --webroot-path /usr/share/nginx/html are two common webroot paths.

If you’re getting a certificate for many domains at once, the plugin needs to know where each domain’s files are served from, which could potentially be a separate directory for each domain. When requesting a certificate for multiple domains, each domain will use the most recently specified --webroot-path. So, for instance,

certbot certonly --webroot -w /var/www/example/ -d www.example.com -d example.com -w /var/www/other -d other.example.net -d another.other.example.net

would obtain a single certificate for all of those names, using the /var/www/example webroot directory for the first two, and /var/www/other for the second two.

The webroot plugin works by creating a temporary file for each of your requested domains in ${webroot-path}/.well-known/acme-challenge. Then the Let’s Encrypt validation server makes HTTP requests to validate that the DNS for each requested domain resolves to the server running certbot. An example request made to your web server would look like:

66.133.109.36 - - [05/Jan/2016:20:11:24 -0500] "GET /.well-known/acme-challenge/HGr8U1IeTW4kY_Z6UIyaakzOkyQgPr_7ArlLgtZE8SX HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"

Note that to use the webroot plugin, your server must be configured to serve files from hidden directories. If /.well-known is treated specially by your webserver configuration, you might need to modify the configuration to ensure that files inside /.well-known/acme-challenge are served by the webserver.

Nginx

The Nginx plugin has been distributed with Certbot since version 0.9.0 and should work for most configurations. We recommend backing up Nginx configurations before using it (though you can also revert changes to configurations with certbot --nginx rollback). You can use it by providing the --nginx flag on the commandline.

certbot --nginx

Standalone

Use standalone mode to obtain a certificate if you don’t want to use (or don’t currently have) existing server software. The standalone plugin does not rely on any other server software running on the machine where you obtain the certificate.

To obtain a certificate using a “standalone” webserver, you can use the standalone plugin by including certonly and --standalone on the command line. This plugin needs to bind to port 80 or 443 in order to perform domain validation, so you may need to stop your existing webserver. To control which port the plugin uses, include one of the options shown below on the command line.

--preferred-challenges http to use port 80

--preferred-challenges tls-sni to use port 443

It must still be possible for your machine to accept inbound connections from the Internet on the specified port using each requested domain name.

Note

The --standalone-supported-challenges option has been deprecated since certbot version 0.9.0.

Manual

If you’d like to obtain a certificate running certbot on a machine other than your target webserver or perform the steps for domain validation yourself, you can use the manual plugin. While hidden from the UI, you can use the plugin to obtain a certificate by specifying certonly and --manual on the command line. This requires you to copy and paste commands into another terminal session, which may be on a different computer.

The manual plugin can use either the http or the dns challenge. You can use the --preferred-challenges option to choose the challenge of your preference. The http challenge will ask you to place a file with a specific name and specific content in the /.well-known/acme-challenge/ directory directly in the top-level directory (“web root”) containing the files served by your webserver. In essence it’s the same as the webroot plugin, but not automated. When using the dns challenge, certbot will ask you to place a TXT DNS record with specific contents under the domain name consisting of the hostname for which you want a certificate issued, prepended by _acme-challenge.

For example, for the domain example.com, a zone file entry would look like:

_acme-challenge.example.com. 300 IN TXT "gfj9Xq...Rg85nM"

Additionally you can specify scripts to prepare for validation and perform the authentication procedure and/or clean up after it by using the --manual-auth-hook and --manual-cleanup-hook flags. This is described in more depth in the hooks section.

Third-party plugins

There are also a number of third-party plugins for the client, provided by other developers. Many are beta/experimental, but some are already in widespread use:

Plugin | Auth | Inst | Notes
plesk [https://github.com/plesk/letsencrypt-plesk] | Y | Y | Integration with the Plesk web hosting tool
haproxy [https://github.com/greenhost/certbot-haproxy] | Y | Y | Integration with the HAProxy load balancer
s3front [https://github.com/dlapiduz/letsencrypt-s3front] | Y | Y | Integration with Amazon CloudFront distribution of S3 buckets
gandi [https://github.com/Gandi/letsencrypt-gandi] | Y | Y | Integration with Gandi’s hosting products and API
varnish [http://git.sesse.net/?p=letsencrypt-varnish-plugin] | Y | N | Obtain certificates via a Varnish server
external [https://github.com/marcan/letsencrypt-external] | Y | N | A plugin for convenient scripting (See also ticket 2782 [https://github.com/certbot/certbot/issues/2782])
icecast [https://github.com/e00E/lets-encrypt-icecast] | N | Y | Deploy certificates to Icecast 2 streaming media servers
pritunl [https://github.com/kharkevich/letsencrypt-pritunl] | N | Y | Install certificates in pritunl distributed OpenVPN servers
proxmox [https://github.com/kharkevich/letsencrypt-proxmox] | N | Y | Install certificates in Proxmox Virtualization servers
postfix [https://github.com/EFForg/starttls-everywhere] | N | Y | STARTTLS Everywhere is becoming a Certbot Postfix/Exim plugin
heroku [https://github.com/gboudreau/certbot-heroku] | Y | Y | Integration with Heroku SSL

If you’re interested, you can also write your own plugin.

Managing certificates

To view a list of the certificates Certbot knows about, run the certificates subcommand:

certbot certificates

This returns information in the following format:

Found the following certs:
  Certificate Name: example.com
    Domains: example.com, www.example.com
    Expiry Date: 2017-02-19 19:53:00+00:00 (VALID: 30 days)
    Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem

Certificate Name shows the name of the certificate. Pass this name using the --cert-name flag to specify a particular certificate for the run, certonly, certificates, renew, and delete commands. Example:

certbot certonly --cert-name example.com

Re-creating and Updating Existing Certificates

You can use certonly or run subcommands to request the creation of a single new certificate even if you already have an existing certificate with some of the same domain names.

If a certificate is requested with run or certonly specifying a certificate name that already exists, Certbot updates the existing certificate. Otherwise a new certificate is created and assigned the specified name.

The --force-renewal, --duplicate, and --expand options control Certbot’s behavior when re-creating a certificate with the same name as an existing certificate. If you don’t specify a requested behavior, Certbot may ask you what you intended.

--force-renewal tells Certbot to request a new certificate with the same domains as an existing certificate. Each domain must be explicitly specified via -d. If successful, this certificate is saved alongside the earlier one and symbolic links (the “live” reference) will be updated to point to the new certificate. This is a valid method of renewing a specific individual certificate.

--duplicate tells Certbot to create a separate, unrelated certificate with the same domains as an existing certificate. This certificate is saved completely separately from the prior one. Most users will not need to issue this command in normal circumstances.

--expand tells Certbot to update an existing certificate with a new certificate that contains all of the old domains and one or more additional new domains.

--allow-subset-of-names tells Certbot to continue with certificate generation if only some of the specified domain authorizations can be obtained. This may be useful if some domains specified in a certificate no longer point at this system.

Whenever you obtain a new certificate in any of these ways, the new certificate exists alongside any previously obtained certificates, whether or not the previous certificates have expired. The generation of a new certificate counts against several rate limits that are intended to prevent abuse of the ACME protocol, as described here [https://community.letsencrypt.org/t/rate-limits-for-lets-encrypt/6769].

Changing a Certificate’s Domains

The --cert-name flag can also be used to modify the domains a certificate contains, by specifying new domains using the -d or --domains flag. If certificate example.com previously contained example.com and www.example.com, it can be modified to only contain example.com by specifying only example.com with the -d or --domains flag. Example:

certbot certonly --cert-name example.com -d example.com

The same format can be used to expand the set of domains a certificate contains, or to replace that set entirely:

certbot certonly --cert-name example.com -d example.org,www.example.org

Revoking certificates

If your account key has been compromised or you otherwise need to revoke a certificate, use the revoke command to do so. Note that the revoke command takes the certificate path (ending in cert.pem), not a certificate name or domain. Example:

certbot revoke --cert-path /etc/letsencrypt/live/CERTNAME/cert.pem

Additionally, if a certificate is a test certificate obtained via the --staging or --test-cert flag, that flag must be passed to the revoke subcommand. Once a certificate is revoked (or for other certificate management tasks), all of a certificate’s relevant files can be removed from the system with the delete subcommand:

certbot delete --cert-name example.com

Note

If you don’t use delete to remove the certificate completely, it will be renewed automatically at the next renewal event.

Renewing certificates

Note

Let’s Encrypt CA issues short-lived certificates (90 days). Make sure you renew the certificates at least once in 3 months.

As of version 0.10.0, Certbot supports a renew action to check all installed certificates for impending expiry and attempt to renew them. The simplest form is simply

certbot renew

This command attempts to renew any previously-obtained certificates that expire in less than 30 days. The same plugin and options that were used at the time the certificate was originally issued will be used for the renewal attempt, unless you specify other plugins or options. Unlike certonly, renew acts on multiple certificates and always takes into account whether each one is near expiry. Because of this, renew is suitable (and designed) for automated use, to allow your system to automatically renew each certificate when appropriate. Since renew only renews certificates that are near expiry it can be run as frequently as you want - since it will usually take no action.

The renew command includes hooks for running commands or scripts before or after a certificate is renewed. For example, if you have a single certificate obtained using the standalone plugin, you might need to stop the webserver before renewing so standalone can bind to the necessary ports, and then restart it after the plugin is finished. Example:

certbot renew --pre-hook "service nginx stop" --post-hook "service nginx start"

If a hook exits with a non-zero exit code, the error will be printed to stderr but renewal will be attempted anyway. A failing hook doesn’t directly cause Certbot to exit with a non-zero exit code, but since Certbot exits with a non-zero exit code when renewals fail, a failed hook causing renewal failures will indirectly result in a non-zero exit code. Hooks will only be run if a certificate is due for renewal, so you can run the above command frequently without unnecessarily stopping your webserver.

--pre-hook and --post-hook hooks run before and after every renewal attempt. If you want your hook to run only after a successful renewal, use --renew-hook in a command like this.

certbot renew --renew-hook /path/to/renew-hook-script

For example, if you have a daemon that does not read its certificates as the root user, a renew hook like this can copy them to the correct location and apply appropriate file permissions.

/path/to/renew-hook-script

#!/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

More information about renewal hooks can be found by running certbot --help renew.

If you’re sure that this command executes successfully without human intervention, you can add the command to crontab (since certificates are only renewed when they’re determined to be near expiry, the command can run on a regular basis, like every week or every day). In that case, you are likely to want to use the -q or --quiet quiet flag to silence all output except errors.

If you are manually renewing all of your certificates, the --force-renewal flag may be helpful; it causes the expiration time of the certificate(s) to be ignored when considering renewal, and attempts to renew each and every installed certificate regardless of its age. (This form is not appropriate to run daily because each certificate will be renewed every day, which will quickly run into the certificate authority rate limit.)

Note that options provided to certbot renew will apply to every certificate for which renewal is attempted; for example, certbot renew --rsa-key-size 4096 would try to replace every near-expiry certificate with an equivalent certificate using a 4096-bit RSA public key. If a certificate is successfully renewed using specified options, those options will be saved and used for future renewals of that certificate.

An alternative form that provides for more fine-grained control over the renewal process (while renewing specified certificates one at a time), is certbot certonly with the complete set of subject domains of a specific certificate specified via -d flags. You may also want to include the -n or --noninteractive flag to prevent blocking on user input (which is useful when running the command from cron).

certbot certonly -n -d example.com -d www.example.com

All of the domains covered by the certificate must be specified in this case in order to renew and replace the old certificate rather than obtaining a new one; don’t forget any www. domains! Specifying a subset of the domains creates a new, separate certificate containing only those domains, rather than replacing the original certificate. When run with a set of domains corresponding to an existing certificate, the certonly command attempts to renew that specific certificate.

Please note that the CA will send notification emails to the address you provide if you do not renew certificates that are about to expire.

Certbot is working hard to improve the renewal process, and we apologize for any inconvenience you encounter in integrating these commands into your individual environment.

Note

certbot renew exit status will only be 1 if a renewal attempt failed. This means certbot renew exit status will be 0 if no cert needs to be updated. If you write a custom script and expect to run a command only after a cert was actually renewed you will need to use the --post-hook since the exit status will be 0 both on successful renewal and when renewal is not necessary.

Modifying the Renewal Configuration File

For advanced certificate management tasks, it is possible to manually modify the certificate’s renewal configuration file, located at /etc/letsencrypt/renewal/CERTNAME.

Warning

Modifying any files in /etc/letsencrypt can damage them so Certbot can no longer properly manage its certificates, and we do not recommend doing so.

For most tasks, it is safest to limit yourself to pointing symlinks at the files there, or using --renew-hook to copy / make new files based upon those files, if your operational situation requires it (for instance, combining certificates and keys in different way, or having copies of things with different specific permissions that are demanded by other programs).

If the contents of /etc/letsencrypt/archive/CERTNAME are moved to a new folder, first specify the new folder’s name in the renewal configuration file, then run certbot update_symlinks to point the symlinks in /etc/letsencrypt/live/CERTNAME to the new folder.

If you would like the live certificate files whose symlink location Certbot updates on each run to reside in a different location, first move them to that location, then specify the full path of each of the four files in the renewal configuration file. Since the symlinks are relative links, you must follow this with an invocation of certbot update_symlinks.

For example, say that a certificate’s renewal configuration file previously contained the following directives:

archive_dir = /etc/letsencrypt/archive/example.com
cert = /etc/letsencrypt/live/example.com/cert.pem
privkey = /etc/letsencrypt/live/example.com/privkey.pem
chain = /etc/letsencrypt/live/example.com/chain.pem
fullchain = /etc/letsencrypt/live/example.com/fullchain.pem

The following commands could be used to specify where these files are located:

mv /etc/letsencrypt/archive/example.com /home/user/me/certbot/example_archive
sed -i 's,/etc/letsencrypt/archive/example.com,/home/user/me/certbot/example_archive,' /etc/letsencrypt/renewal/example.com.conf
mv /etc/letsencrypt/live/example.com/*.pem /home/user/me/certbot/
sed -i 's,/etc/letsencrypt/live/example.com,/home/user/me/certbot,g' /etc/letsencrypt/renewal/example.com.conf
certbot update_symlinks

Where are my certificates?

All generated keys and issued certificates can be found in /etc/letsencrypt/live/$domain. Rather than copying, please point your (web) server configuration directly to those files (or create symlinks). During the renewal, /etc/letsencrypt/live is updated with the latest necessary files.

Note

/etc/letsencrypt/archive and /etc/letsencrypt/keys contain all previous keys and certificates, while /etc/letsencrypt/live symlinks to the latest versions.

The following files are available:

privkey.pem

Private key for the certificate.

Warning

This must be kept secret at all times! Never share it with anyone, including Certbot developers. You cannot put it into a safe, however - your server still needs to access this file in order for SSL/TLS to work.

This is what Apache needs for SSLCertificateKeyFile [https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatekeyfile], and Nginx for ssl_certificate_key [http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate_key].

fullchain.pem

All certificates, including server certificate (aka leaf certificate or end-entity certificate). The server certificate is the first one in this file, followed by any intermediates.

This is what Apache >= 2.4.8 needs for SSLCertificateFile [https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatefile], and what Nginx needs for ssl_certificate [http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate].

cert.pem and chain.pem (less common)

cert.pem contains the server certificate by itself, and chain.pem contains the additional intermediate certificate or certificates that web browsers will need in order to validate the server certificate. If you provide one of these files to your web server, you must provide both of them, or some browsers will show “This Connection is Untrusted” errors for your site, some of the time [https://whatsmychaincert.com/].

Apache < 2.4.8 needs these for SSLCertificateFile [https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatefile]. and SSLCertificateChainFile [https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatechainfile], respectively.

If you’re using OCSP stapling with Nginx >= 1.3.7, chain.pem should be provided as the ssl_trusted_certificate [http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_trusted_certificate] to validate OCSP responses.

Note

All files are PEM-encoded. If you need other format, such as DER or PFX, then you could convert using openssl. You can automate that with --renew-hook if you’re using automatic renewal.

Pre and Post Validation Hooks

Certbot allows for the specification of pre and post validation hooks when run in manual mode. The flags to specify these scripts are --manual-auth-hook and --manual-cleanup-hook respectively and can be used as follows:

certbot certonly --manual --manual-auth-hook /path/to/http/authenticator.sh --manual-cleanup-hook /path/to/http/cleanup.sh -d secure.example.com

This will run the authenticator.sh script, attempt the validation, and then run the cleanup.sh script. Additionally certbot will pass three environment variables to these scripts:

CERTBOT_DOMAIN: The domain being authenticated

CERTBOT_VALIDATION: The validation string

CERTBOT_TOKEN: Resource name part of the HTTP-01 challenge (HTTP-01 only)

Additionally for cleanup:

CERTBOT_AUTH_OUTPUT: Whatever the auth script wrote to stdout

Example usage for HTTP-01:

certbot certonly --manual --preferred-challenges=http --manual-auth-hook /path/to/http/authenticator.sh --manual-cleanup-hook /path/to/http/cleanup.sh -d secure.example.com

/path/to/http/authenticator.sh

#!/bin/bash
echo $CERTBOT_VALIDATION > /var/www/htdocs/.well-known/acme-challenge/$CERTBOT_TOKEN

/path/to/http/cleanup.sh

#!/bin/bash
rm -f /var/www/htdocs/.well-known/acme-challenge/$CERTBOT_TOKEN

Example usage for DNS-01 (Cloudflare API v4) (for example purposes only, do not use as-is)

certbot certonly --manual --preferred-challenges=dns --manual-auth-hook /path/to/dns/authenticator.sh --manual-cleanup-hook /path/to/dns/cleanup.sh -d secure.example.com

/path/to/dns/authenticator.sh

#!/bin/bash

# Get your API key from https://www.cloudflare.com/a/account/my-account
API_KEY="your-api-key"
EMAIL="your.email@example.com"

# Strip only the top domain to get the zone id
DOMAIN=$(expr match "$CERTBOT_DOMAIN" '.*\.\(.*\..*\)')

# Get the Cloudflare zone id
ZONE_EXTRA_PARAMS="status=active&page=1&per_page=20&order=status&direction=desc&match=all"
ZONE_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$DOMAIN&$ZONE_EXTRA_PARAMS" \
     -H     "X-Auth-Email: $EMAIL" \
     -H     "X-Auth-Key: $API_KEY" \
     -H     "Content-Type: application/json" | python -c "import sys,json;print(json.load(sys.stdin)['result'][0]['id'])")

# Create TXT record
CREATE_DOMAIN="_acme-challenge.$CERTBOT_DOMAIN"
RECORD_ID=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
     -H     "X-Auth-Email: $EMAIL" \
     -H     "X-Auth-Key: $API_KEY" \
     -H     "Content-Type: application/json" \
     --data '{"type":"TXT","name":"'"$CREATE_DOMAIN"'","content":"'"$CERTBOT_VALIDATION"'","ttl":120}' \
             | python -c "import sys,json;print(json.load(sys.stdin)['result']['id'])")
# Save info for cleanup
if [ ! -d /tmp/CERTBOT_$CERTBOT_DOMAIN ];then
        mkdir -m 0700 /tmp/CERTBOT_$CERTBOT_DOMAIN
fi
echo $ZONE_ID > /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID
echo $RECORD_ID > /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID

# Sleep to make sure the change has time to propagate over to DNS
sleep 25

/path/to/dns/cleanup.sh

#!/bin/bash

# Get your API key from https://www.cloudflare.com/a/account/my-account
API_KEY="your-api-key"
EMAIL="your.email@example.com"

if [ -f /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID ]; then
        ZONE_ID=$(cat /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID)
        rm -f /tmp/CERTBOT_$CERTBOT_DOMAIN/ZONE_ID
fi

if [ -f /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID ]; then
        RECORD_ID=$(cat /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID)
        rm -f /tmp/CERTBOT_$CERTBOT_DOMAIN/RECORD_ID
fi

# Remove the challenge TXT record from the zone
if [ -n "${ZONE_ID}" ]; then
    if [ -n "${RECORD_ID}" ]; then
        curl -s -X DELETE "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
                -H "X-Auth-Email: $EMAIL" \
                -H "X-Auth-Key: $API_KEY" \
                -H "Content-Type: application/json"
    fi
fi

Configuration file

It is possible to specify configuration file with certbot-auto --config cli.ini (or shorter -c cli.ini). An example configuration file is shown below:

# This is an example of the kind of things you can do in a configuration file.
# All flags used by the client can be configured here. Run Certbot with
# "--help" to learn more about the available options.
#
# Note that these options apply automatically to all use of Certbot for
# obtaining or renewing certificates, so options specific to a single
# certificate on a system with several certificates should not be placed
# here.

# Use a 4096 bit RSA key instead of 2048
rsa-key-size = 4096

# Uncomment and update to register with the specified e-mail address
# email = foo@example.com

# Uncomment to use the standalone authenticator on port 443
# authenticator = standalone
# standalone-supported-challenges = tls-sni-01

# Uncomment to use the webroot authenticator. Replace webroot-path with the
# path to the public_html / webroot folder being served by your web server.
# authenticator = webroot
# webroot-path = /usr/share/nginx/html

By default, the following locations are searched:

/etc/letsencrypt/cli.ini

$XDG_CONFIG_HOME/letsencrypt/cli.ini (or ~/.config/letsencrypt/cli.ini if $XDG_CONFIG_HOME is not set).

Certbot command-line options

Certbot supports a lot of command line options. Here’s the full list, from certbot --help all:

usage:
  certbot [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ...

Certbot can obtain and install HTTPS/TLS/SSL certificates.  By default,
it will attempt to use a webserver both for obtaining and installing the
certificate. The most common SUBCOMMANDS and flags are:

obtain, install, and renew certificates:
    (default) run   Obtain & install a certificate in your current webserver
    certonly        Obtain or renew a certificate, but do not install it
    renew           Renew all previously obtained certificates that are near expiry
   -d DOMAINS       Comma-separated list of domains to obtain a certificate for

  --apache          Use the Apache plugin for authentication & installation
  --standalone      Run a standalone webserver for authentication
  --nginx           Use the Nginx plugin for authentication & installation
  --webroot         Place files in a server's webroot folder for authentication
  --manual          Obtain certificates interactively, or using shell script hooks

   -n               Run non-interactively
  --test-cert       Obtain a test certificate from a staging server
  --dry-run         Test "renew" or "certonly" without saving any certificates to disk

manage certificates:
    certificates    Display information about certificates you have from Certbot
    revoke          Revoke a certificate (supply --cert-path)
    delete          Delete a certificate

manage your account with Let's Encrypt:
    register        Create a Let's Encrypt ACME account
  --agree-tos       Agree to the ACME server's Subscriber Agreement
   -m EMAIL         Email address for important account notifications

optional arguments:
  -h, --help            show this help message and exit
  -c CONFIG_FILE, --config CONFIG_FILE
                        path to config file (default: /etc/letsencrypt/cli.ini
                        and ~/.config/letsencrypt/cli.ini)
  -v, --verbose         This flag can be used multiple times to incrementally
                        increase the verbosity of output, e.g. -vvv. (default:
                        -2)
  -n, --non-interactive, --noninteractive
                        Run without ever asking for user input. This may
                        require additional command line flags; the client will
                        try to explain which ones are required if it finds one
                        missing (default: False)
  --force-interactive   Force Certbot to be interactive even if it detects
                        it's not being run in a terminal. This flag cannot be
                        used with the renew subcommand. (default: False)
  -d DOMAIN, --domains DOMAIN, --domain DOMAIN
                        Domain names to apply. For multiple domains you can
                        use multiple -d flags or enter a comma separated list
                        of domains as a parameter. (default: Ask)
  --cert-name CERTNAME  Certificate name to apply. Only one certificate name
                        can be used per Certbot run. To see certificate names,
                        run 'certbot certificates'. When creating a new
                        certificate, specifies the new certificate's name.
                        (default: None)
  --dry-run             Perform a test run of the client, obtaining test
                        (invalid) certificates but not saving them to disk.
                        This can currently only be used with the 'certonly'
                        and 'renew' subcommands. Note: Although --dry-run
                        tries to avoid making any persistent changes on a
                        system, it is not completely side-effect free: if used
                        with webserver authenticator plugins like apache and
                        nginx, it makes and then reverts temporary config
                        changes in order to obtain test certificates, and
                        reloads webservers to deploy and then roll back those
                        changes. It also calls --pre-hook and --post-hook
                        commands if they are defined because they may be
                        necessary to accurately simulate renewal. --renew-hook
                        commands are not called. (default: False)
  --debug-challenges    After setting up challenges, wait for user input
                        before submitting to CA (default: False)
  --preferred-challenges PREF_CHALLS
                        A sorted, comma delimited list of the preferred
                        challenge to use during authorization with the most
                        preferred challenge listed first (Eg, "dns" or "tls-
                        sni-01,http,dns"). Not all plugins support all
                        challenges. See
                        https://certbot.eff.org/docs/using.html#plugins for
                        details. ACME Challenges are versioned, but if you
                        pick "http" rather than "http-01", Certbot will select
                        the latest version automatically. (default: [])
  --user-agent USER_AGENT
                        Set a custom user agent string for the client. User
                        agent strings allow the CA to collect high level
                        statistics about success rates by OS, plugin and use
                        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/0.15.0 (certbot;
                        Ubuntu 16.04.2 LTS) Authenticator/XXX Installer/YYY
                        (SUBCOMMAND; flags: FLAGS) Py/2.7.12). The flags
                        encoded in the user agent are: --duplicate, --force-
                        renew, --allow-subset-of-names, -n, and whether any
                        hooks are set.

automation:
  Arguments for automating execution & other tweaks

  --keep-until-expiring, --keep, --reinstall
                        If the requested certificate matches an existing
                        certificate, always keep the existing one until it is
                        due for renewal (for the 'run' subcommand this means
                        reinstall the existing certificate). (default: Ask)
  --expand              If an existing certificate is a strict subset of the
                        requested names, always expand and replace it with the
                        additional names. (default: Ask)
  --version             show program's version number and exit
  --force-renewal, --renew-by-default
                        If a certificate already exists for the requested
                        domains, renew it now, regardless of whether it is
                        near expiry. (Often --keep-until-expiring is more
                        appropriate). Also implies --expand. (default: False)
  --renew-with-new-domains
                        If a certificate already exists for the requested
                        certificate name but does not match the requested
                        domains, renew it now, regardless of whether it is
                        near expiry. (default: False)
  --allow-subset-of-names
                        When performing domain validation, do not consider it
                        a failure if authorizations can not be obtained for a
                        strict subset of the requested domains. This may be
                        useful for allowing renewals for multiple domains to
                        succeed even if some domains no longer point at this
                        system. This option cannot be used with --csr.
                        (default: False)
  --agree-tos           Agree to the ACME Subscriber Agreement (default: Ask)
  --duplicate           Allow making a certificate lineage that duplicates an
                        existing one (both can be renewed in parallel)
                        (default: False)
  --os-packages-only    (certbot-auto only) install OS package dependencies
                        and then stop (default: False)
  --no-self-upgrade     (certbot-auto only) prevent the certbot-auto script
                        from upgrading itself to newer released versions
                        (default: Upgrade automatically)
  --no-bootstrap        (certbot-auto only) prevent the certbot-auto script
                        from installing OS-level dependencies (default: Prompt
                        to install OS-wide dependencies, but exit if the user
                        says 'No')
  -q, --quiet           Silence all output except errors. Useful for
                        automation via cron. Implies --non-interactive.
                        (default: False)

security:
  Security parameters & server settings

  --rsa-key-size N      Size of the RSA key. (default: 2048)
  --must-staple         Adds the OCSP Must Staple extension to the
                        certificate. Autoconfigures OCSP Stapling for
                        supported setups (Apache version >= 2.3.3 ). (default:
                        False)
  --redirect            Automatically redirect all HTTP traffic to HTTPS for
                        the newly authenticated vhost. (default: Ask)
  --no-redirect         Do not automatically redirect all HTTP traffic to
                        HTTPS for the newly authenticated vhost. (default:
                        Ask)
  --hsts                Add the Strict-Transport-Security header to every HTTP
                        response. Forcing browser to always use SSL for the
                        domain. Defends against SSL Stripping. (default:
                        False)
  --uir                 Add the "Content-Security-Policy: upgrade-insecure-
                        requests" header to every HTTP response. Forcing the
                        browser to use https:// for every http:// resource.
                        (default: None)
  --staple-ocsp         Enables OCSP Stapling. A valid OCSP response is
                        stapled to the certificate that the server offers
                        during TLS. (default: None)
  --strict-permissions  Require that all configuration files are owned by the
                        current user; only needed if your config is somewhere
                        unsafe like /tmp/ (default: False)

testing:
  The following flags are meant for testing and integration purposes only.

  --test-cert, --staging
                        Use the staging server to obtain or revoke test
                        (invalid) certificates; equivalent to --server https
                        ://acme-staging.api.letsencrypt.org/directory
                        (default: False)
  --debug               Show tracebacks in case of errors, and allow certbot-
                        auto execution on experimental platforms (default:
                        False)
  --no-verify-ssl       Disable verification of the ACME server's certificate.
                        (default: False)
  --tls-sni-01-port TLS_SNI_01_PORT
                        Port used during tls-sni-01 challenge. This only
                        affects the port Certbot listens on. A conforming ACME
                        server will still attempt to connect on port 443.
                        (default: 443)
  --tls-sni-01-address TLS_SNI_01_ADDRESS
                        The address the server listens to during tls-sni-01
                        challenge. (default: )
  --http-01-port HTTP01_PORT
                        Port used in the http-01 challenge. This only affects
                        the port Certbot listens on. A conforming ACME server
                        will still attempt to connect on port 80. (default:
                        80)
  --http-01-address HTTP01_ADDRESS
                        The address the server listens to during http-01
                        challenge. (default: )
  --break-my-certs      Be willing to replace or renew valid certificates with
                        invalid (testing/staging) certificates (default:
                        False)

paths:
  Arguments changing execution paths & servers

  --cert-path CERT_PATH
                        Path to where certificate is saved (with auth --csr),
                        installed from, or revoked. (default: None)
  --key-path KEY_PATH   Path to private key for certificate installation or
                        revocation (if account key is missing) (default: None)
  --fullchain-path FULLCHAIN_PATH
                        Accompanying path to a full certificate chain
                        (certificate plus chain). (default: None)
  --chain-path CHAIN_PATH
                        Accompanying path to a certificate chain. (default:
                        None)
  --config-dir CONFIG_DIR
                        Configuration directory. (default: /etc/letsencrypt)
  --work-dir WORK_DIR   Working directory. (default: /var/lib/letsencrypt)
  --logs-dir LOGS_DIR   Logs directory. (default: /var/log/letsencrypt)
  --server SERVER       ACME Directory Resource URI. (default:
                        https://acme-v01.api.letsencrypt.org/directory)

manage:
  Various subcommands and flags are available for managing your
  certificates:

  certificates          List certificates managed by Certbot
  delete                Clean up all files related to a certificate
  renew                 Renew all certificates (or one specified with --cert-
                        name)
  revoke                Revoke a certificate specified with --cert-path
  update_symlinks       Recreate symlinks in your /etc/letsencrypt/live/
                        directory

run:
  Options for obtaining & installing certificates

certonly:
  Options for modifying how a certificate is obtained

  --csr CSR             Path to a Certificate Signing Request (CSR) in DER or
                        PEM format. Currently --csr only works with the
                        'certonly' subcommand. (default: None)

renew:
  The 'renew' subcommand will attempt to renew all certificates (or more
  precisely, certificate lineages) you have previously obtained if they are
  close to expiry, and print a summary of the results. By default, 'renew'
  will reuse the options used to create obtain or most recently successfully
  renew each certificate lineage. You can try it with `--dry-run` first. For
  more fine-grained control, you can renew individual lineages with the
  `certonly` subcommand. Hooks are available to run commands before and
  after renewal; see https://certbot.eff.org/docs/using.html#renewal for
  more information on these.

  --pre-hook PRE_HOOK   Command to be run in a shell before obtaining any
                        certificates. Intended primarily for renewal, where it
                        can be used to temporarily shut down a webserver that
                        might conflict with the standalone plugin. This will
                        only be called if a certificate is actually to be
                        obtained/renewed. When renewing several certificates
                        that have identical pre-hooks, only the first will be
                        executed. (default: None)
  --post-hook POST_HOOK
                        Command to be run in a shell after attempting to
                        obtain/renew certificates. Can be used to deploy
                        renewed certificates, or to restart any servers that
                        were stopped by --pre-hook. This is only run if an
                        attempt was made to obtain/renew a certificate. If
                        multiple renewed certificates have identical post-
                        hooks, only one will be run. (default: None)
  --renew-hook RENEW_HOOK
                        Command to be run in a shell once for each
                        successfully renewed certificate. For this command,
                        the shell variable $RENEWED_LINEAGE will point to the
                        config live subdirectory (for example,
                        "/etc/letsencrypt/live/example.com") containing the
                        new certificates and keys; the shell variable
                        $RENEWED_DOMAINS will contain a space-delimited list
                        of renewed certificate domains (for example,
                        "example.com www.example.com" (default: None)
  --disable-hook-validation
                        Ordinarily the commands specified for --pre-hook
                        /--post-hook/--renew-hook will be checked for
                        validity, to see if the programs being run are in the
                        $PATH, so that mistakes can be caught early, even when
                        the hooks aren't being run just yet. The validation is
                        rather simplistic and fails if you use more advanced
                        shell constructs, so you can use this switch to
                        disable it. (default: False)

certificates:
  List certificates managed by Certbot

delete:
  Options for deleting a certificate

revoke:
  Options for revocation of certificates

  --reason {keycompromise,affiliationchanged,superseded,unspecified,cessationofoperation}
                        Specify reason for revoking certificate. (default: 0)

register:
  Options for account registration & modification

  --register-unsafely-without-email
                        Specifying this flag enables registering an account
                        with no email address. This is strongly discouraged,
                        because in the event of key loss or account compromise
                        you will irrevocably lose access to your account. You
                        will also be unable to receive notice about impending
                        expiration or revocation of your certificates. Updates
                        to the Subscriber Agreement will still affect you, and
                        will be effective 14 days after posting an update to
                        the web site. (default: False)
  --update-registration
                        With the register verb, indicates that details
                        associated with an existing registration, such as the
                        e-mail address, should be updated, rather than
                        registering a new account. (default: False)
  -m EMAIL, --email EMAIL
                        Email used for registration and recovery contact.
                        (default: Ask)
  --eff-email           Share your e-mail address with EFF (default: None)
  --no-eff-email        Don't share your e-mail address with EFF (default:
                        None)

unregister:
  Options for account deactivation.

  --account ACCOUNT_ID  Account ID to use (default: None)

install:
  Options for modifying how a certificate is deployed

config_changes:
  Options for controlling which changes are displayed

  --num NUM             How many past revisions you want to be displayed
                        (default: None)

rollback:
  Options for rolling back server configuration changes

  --checkpoints N       Revert configuration N number of checkpoints.
                        (default: 1)

plugins:
  Options for for the "plugins" subcommand

  --init                Initialize plugins. (default: False)
  --prepare             Initialize and prepare plugins. (default: False)
  --authenticators      Limit to authenticator plugins only. (default: None)
  --installers          Limit to installer plugins only. (default: None)

update_symlinks:
  Recreates certificate and key symlinks in /etc/letsencrypt/live, if you
  changed them by hand or edited a renewal configuration file

plugins:
  Plugin Selection: Certbot client supports an extensible plugins
  architecture. See 'certbot plugins' for a list of all installed plugins
  and their names. You can force a particular plugin by setting options
  provided below. Running --help <plugin_name> will list flags specific to
  that plugin.

  --configurator CONFIGURATOR
                        Name of the plugin that is both an authenticator and
                        an installer. Should not be used together with
                        --authenticator or --installer. (default: Ask)
  -a AUTHENTICATOR, --authenticator AUTHENTICATOR
                        Authenticator plugin name. (default: None)
  -i INSTALLER, --installer INSTALLER
                        Installer plugin name (also used to find domains).
                        (default: None)
  --apache              Obtain and install certificates using Apache (default:
                        False)
  --nginx               Obtain and install certificates using Nginx (default:
                        False)
  --standalone          Obtain certificates using a "standalone" webserver.
                        (default: False)
  --manual              Provide laborious manual instructions for obtaining a
                        certificate (default: False)
  --webroot             Obtain certificates by placing files in a webroot
                        directory. (default: False)
  --dns-cloudflare      Obtain certificates using a DNS TXT record (if you are
                        using Cloudflare for DNS). (default: False)
  --dns-cloudxns        Obtain certificates using a DNS TXT record (if you are
                        using CloudXNS for DNS). (default: False)
  --dns-digitalocean    Obtain certificates using a DNS TXT record (if you are
                        using DigitalOcean for DNS). (default: False)
  --dns-dnsimple        Obtain certificates using a DNS TXT record (if you are
                        using DNSimple for DNS). (default: False)
  --dns-google          Obtain certificates using a DNS TXT record (if you are
                        using Google Cloud DNS). (default: False)
  --dns-nsone           Obtain certificates using a DNS TXT record (if you are
                        using NS1 for DNS). (default: False)
  --dns-route53         Obtain certificates using a DNS TXT record (if you are
                        using Route53 for DNS). (default: False)

apache:
  Apache Web Server plugin

  --apache-enmod APACHE_ENMOD
                        Path to the Apache 'a2enmod' binary. (default:
                        a2enmod)
  --apache-dismod APACHE_DISMOD
                        Path to the Apache 'a2dismod' binary. (default:
                        a2dismod)
  --apache-le-vhost-ext APACHE_LE_VHOST_EXT
                        SSL vhost configuration extension. (default: -le-
                        ssl.conf)
  --apache-server-root APACHE_SERVER_ROOT
                        Apache server root directory. (default: /etc/apache2)
  --apache-vhost-root APACHE_VHOST_ROOT
                        Apache server VirtualHost configuration root (default:
                        /etc/apache2/sites-available)
  --apache-logs-root APACHE_LOGS_ROOT
                        Apache server logs directory (default:
                        /var/log/apache2)
  --apache-challenge-location APACHE_CHALLENGE_LOCATION
                        Directory path for challenge configuration. (default:
                        /etc/apache2)
  --apache-handle-modules APACHE_HANDLE_MODULES
                        Let installer handle enabling required modules for
                        you.(Only Ubuntu/Debian currently) (default: True)
  --apache-handle-sites APACHE_HANDLE_SITES
                        Let installer handle enabling sites for you.(Only
                        Ubuntu/Debian currently) (default: True)

certbot-route53:auth:
  Obtain certificates using a DNS TXT record (if you are using AWS Route53
  for DNS).

  --certbot-route53:auth-propagation-seconds CERTBOT_ROUTE53:AUTH_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 10)

dns-cloudflare:
  Obtain certificates using a DNS TXT record (if you are using Cloudflare
  for DNS).

  --dns-cloudflare-propagation-seconds DNS_CLOUDFLARE_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 10)
  --dns-cloudflare-credentials DNS_CLOUDFLARE_CREDENTIALS
                        Cloudflare credentials INI file. (default: None)

dns-cloudxns:
  Obtain certificates using a DNS TXT record (if you are using CloudXNS for
  DNS).

  --dns-cloudxns-propagation-seconds DNS_CLOUDXNS_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 30)
  --dns-cloudxns-credentials DNS_CLOUDXNS_CREDENTIALS
                        CloudXNS credentials INI file. (default: None)

dns-digitalocean:
  Obtain certs using a DNS TXT record (if you are using DigitalOcean for
  DNS).

  --dns-digitalocean-propagation-seconds DNS_DIGITALOCEAN_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 10)
  --dns-digitalocean-credentials DNS_DIGITALOCEAN_CREDENTIALS
                        DigitalOcean credentials INI file. (default: None)

dns-dnsimple:
  Obtain certificates using a DNS TXT record (if you are using DNSimple for
  DNS).

  --dns-dnsimple-propagation-seconds DNS_DNSIMPLE_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 30)
  --dns-dnsimple-credentials DNS_DNSIMPLE_CREDENTIALS
                        DNSimple credentials INI file. (default: None)

dns-google:
  Obtain certificates using a DNS TXT record (if you are using Google Cloud
  DNS for DNS).

  --dns-google-propagation-seconds DNS_GOOGLE_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 60)
  --dns-google-credentials DNS_GOOGLE_CREDENTIALS
                        Path to Google Cloud DNS service account JSON file.
                        (See https://developers.google.com/identity/protocols/
                        OAuth2ServiceAccount#creatinganaccount forinformation
                        about creating a service account and
                        https://cloud.google.com/dns/access-
                        control#permissions_and_roles for information about
                        therequired permissions.) (default: None)

dns-nsone:
  Obtain certificates using a DNS TXT record (if you are using NS1 for DNS).

  --dns-nsone-propagation-seconds DNS_NSONE_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 30)
  --dns-nsone-credentials DNS_NSONE_CREDENTIALS
                        NS1 credentials file. (default: None)

dns-route53:
  Obtain certificates using a DNS TXT record (if you are using AWS Route53
  for DNS).

  --dns-route53-propagation-seconds DNS_ROUTE53_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 10)

manual:
  Authenticate through manual configuration or custom shell scripts. When
  using shell scripts, an authenticator script must be provided. The
  environment variables available to this script are $CERTBOT_DOMAIN which
  contains the domain being authenticated, $CERTBOT_VALIDATION which is the
  validation string, and $CERTBOT_TOKEN which 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.

  --manual-auth-hook MANUAL_AUTH_HOOK
                        Path or command to execute for the authentication
                        script (default: None)
  --manual-cleanup-hook MANUAL_CLEANUP_HOOK
                        Path or command to execute for the cleanup script
                        (default: None)
  --manual-public-ip-logging-ok
                        Automatically allows public IP logging (default: Ask)

nginx:
  Nginx Web Server plugin

  --nginx-server-root NGINX_SERVER_ROOT
                        Nginx server root directory. (default: /etc/nginx)
  --nginx-ctl NGINX_CTL
                        Path to the 'nginx' binary, used for 'configtest' and
                        retrieving nginx version number. (default: nginx)

null:
  Null Installer

standalone:
  Spin up a temporary webserver

webroot:
  Place files in webroot directory

  --webroot-path WEBROOT_PATH, -w WEBROOT_PATH
                        public_html / webroot path. This can be specified
                        multiple times to handle different domains; each
                        domain will have the webroot path that preceded it.
                        For instance: `-w /var/www/example -d example.com -d
                        www.example.com -w /var/www/thing -d thing.net -d
                        m.thing.net` (default: Ask)
  --webroot-map WEBROOT_MAP
                        JSON dictionary mapping domains to webroot paths; this
                        implies -d for each entry. You may need to escape this
                        from your shell. E.g.: --webroot-map
                        '{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}'
                        This option is merged with, but takes precedence over,
                        -w / -d entries. At present, if you put webroot-map in
                        a config file, it needs to be on a single line, like:
                        webroot-map = {"example.com":"/var/www"}. (default:
                        {})

Getting help

If you’re having problems, we recommend posting on the Let’s Encrypt Community Forum [https://community.letsencrypt.org].

You can also chat with us on IRC: (#letsencrypt @ freenode) [https://webchat.freenode.net?channels=%23letsencrypt]

If you find a bug in the software, please do report it in our issue tracker [https://github.com/certbot/certbot/issues]. Remember to give us as much information as possible:

copy and paste exact command line used and the output (though mind that the latter might include some personally identifiable information, including your email and domains)

copy and paste logs from /var/log/letsencrypt (though mind they also might contain personally identifiable information)

copy and paste certbot --version output

your operating system, including specific version

specify which installation method you’ve chosen

Developer Guide

Table of Contents

Getting Started

Running a local copy of the client

Find issues to work on

Testing

Integration testing with the Boulder CA

Code components and layout

Plugin-architecture

Authenticators

Installer

Installer Development

Writing your own plugin

Coding style

Submitting a pull request

Updating certbot-auto and letsencrypt-auto

Updating the scripts

Building letsencrypt-auto-source/letsencrypt-auto

Opening a PR

Updating the documentation

Running the client with Docker

Notes on OS dependencies

Debian

FreeBSD

Getting Started

Running a local copy of the client

Running the client in developer mode from your local tree is a little different than running Certbot as a user. To get set up, clone our git repository by running:

git clone https://github.com/certbot/certbot

If you’re on macOS, we recommend you skip the rest of this section and instead run Certbot in Docker. You can find instructions for how to do this here. If you’re running on Linux, you can run the following commands to install dependencies and set up a virtual environment where you can run Certbot. You will need to repeat this when Certbot’s dependencies change or when a new plugin is introduced.

cd certbot
./certbot-auto --os-packages-only
./tools/venv.sh

Then in each shell where you’re working on the client, do:

source ./venv/bin/activate
export SERVER=https://acme-staging.api.letsencrypt.org/directory
source tests/integration/_common.sh

After that, your shell will be using the virtual environment, and you run the client by typing certbot or certbot_test. The latter is an alias that includes several flags useful for testing. For instance, it sets various output directories to point to /tmp/, and uses non-privileged ports for challenges, so root privileges are not required.

Activating a shell with venv/bin/activate sets environment variables so that Python pulls in the correct versions of various packages needed by Certbot. More information can be found in the virtualenv docs [https://virtualenv.pypa.io].

Find issues to work on

You can find the open issues in the github issue tracker [https://github.com/certbot/certbot/issues]. Comparatively easy ones are marked Good Volunteer Task [https://github.com/certbot/certbot/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+Volunteer+Task%22]. If you’re starting work on something, post a comment to let others know and seek feedback on your plan where appropriate.

Once you’ve got a working branch, you can open a pull request. All changes in your pull request must have thorough unit test coverage, pass our tests, and be compliant with the coding style.

Testing

When you are working in a file foo.py, there should also be a file foo_test.py either in the same directory as foo.py or in the tests subdirectory (if there isn’t, make one). While you are working on your code and tests, run python foo_test.py to run the relevant tests.

For debugging, we recommend putting import ipdb; ipdb.set_trace() statements inside the source code.

Once you are done with your code changes, and the tests in foo_test.py pass, run all of the unittests for Certbot with tox -e py27 (this uses Python 2.7).

Once all the unittests pass, check for sufficient test coverage using tox -e cover, and then check for code style with tox -e lint (all files) or pylint --rcfile=.pylintrc path/to/file.py (single file at a time).

Once all of the above is successful, you may run the full test suite, including integration tests, using tox. We recommend running the commands above first, because running all tests with tox is very slow, and the large amount of tox output can make it hard to find specific failures when they happen. Also note that the full test suite will attempt to modify your system’s Apache config if your user has sudo permissions, so it should not be run on a production Apache server.

If you have trouble getting the full tox suite to run locally, it is generally sufficient to open a pull request and let Github and Travis run integration tests for you.

Integration testing with the Boulder CA

To run integration tests locally, you need Docker and docker-compose installed and working. Fetch and start Boulder using:

./tests/boulder-fetch.sh

If you have problems with Docker, you may want to try removing all containers and volumes [https://www.digitalocean.com/community/tutorials/how-to-remove-docker-images-containers-and-volumes] and making sure you have at least 1GB of memory.

Set up a certbot_test alias that enables easily running against the local Boulder:

export SERVER=http://localhost:4000/directory
source tests/integration/_common.sh

Run the integration tests using:

./tests/boulder-integration.sh

Code components and layout

acme

contains all protocol specific code

certbot

main client code

certbot-apache and certbot-nginx

client code to configure specific web servers

certbot.egg-info

configuration for packaging Certbot

Plugin-architecture

Certbot has a plugin architecture to facilitate support for different webservers, other TLS servers, and operating systems. The interfaces available for plugins to implement are defined in interfaces.py [https://github.com/certbot/certbot/blob/master/certbot/interfaces.py] and plugins/common.py [https://github.com/certbot/certbot/blob/master/certbot/plugins/common.py#L34].

The main two plugin interfaces are IAuthenticator, which implements various ways of proving domain control to a certificate authority, and IInstaller, which configures a server to use a certificate once it is issued. Some plugins, like the built-in Apache and Nginx plugins, implement both interfaces and perform both tasks. Others, like the built-in Standalone authenticator, implement just one interface.

There are also IDisplay plugins, which can change how prompts are displayed to a user.

Authenticators

Authenticators are plugins that prove control of a domain name by solving a challenge provided by the ACME server. ACME currently defines three types of challenges: HTTP, TLS-SNI, and DNS, represented by classes in acme.challenges [https://acme-python.readthedocs.io/en/latest/api/challenges.html#module-acme.challenges]. An authenticator plugin should implement support for at least one challenge type.

An Authenticator indicates which challenges it supports by implementing get_chall_pref(domain) to return a sorted list of challenge types in preference order.

An Authenticator must also implement perform(achalls), which “performs” a list of challenges by, for instance, provisioning a file on an HTTP server, or setting a TXT record in DNS. Once all challenges have succeeded or failed, Certbot will call the plugin’s cleanup(achalls) method to remove any files or DNS records that were needed only during authentication.

Installer

Installers plugins exist to actually setup the certificate in a server, possibly tweak the security configuration to make it more correct and secure (Fix some mixed content problems, turn on HSTS, redirect to HTTPS, etc). Installer plugins tell the main client about their abilities to do the latter via the supported_enhancements() call. We currently have two Installers in the tree, the ApacheConfigurator. and the NginxConfigurator. External projects have made some progress toward support for IIS, Icecast and Plesk.

Installers and Authenticators will oftentimes be the same class/object (because for instance both tasks can be performed by a webserver like nginx) though this is not always the case (the standalone plugin is an authenticator that listens on port 443, but it cannot install certs; a postfix plugin would be an installer but not an authenticator).

Installers and Authenticators are kept separate because it should be possible to use the StandaloneAuthenticator (it sets up its own Python server to perform challenges) with a program that cannot solve challenges itself (Such as MTA installers).

Installer Development

There are a few existing classes that may be beneficial while developing a new IInstaller. Installers aimed to reconfigure UNIX servers may use Augeas for configuration parsing and can inherit from AugeasConfigurator class to handle much of the interface. Installers that are unable to use Augeas may still find the Reverter class helpful in handling configuration checkpoints and rollback.

Writing your own plugin

Certbot client supports dynamic discovery of plugins through the setuptools entry points [http://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points] using the certbot.plugins group. This way you can, for example, create a custom implementation of IAuthenticator or the IInstaller without having to merge it with the core upstream source code. An example is provided in examples/plugins/ directory.

While developing, you can install your plugin into a Certbot development virtualenv like this:

. venv/bin/activate
. tests/integration/_common.sh
pip install -e examples/plugins/
certbot_test plugins

Your plugin should show up in the output of the last command. If not, it was not installed properly.

Once you’ve finished your plugin and published it, you can have your users install it system-wide with pip install. Note that this will only work for users who have Certbot installed from OS packages or via pip. Users who run certbot-auto are currently unable to use third-party plugins. It’s technically possible to install third-party plugins into the virtualenv used by certbot-auto, but they will be wiped away when certbot-auto upgrades.

Warning

Please be aware though that as this client is still in a developer-preview stage, the API may undergo a few changes. If you believe the plugin will be beneficial to the community, please consider submitting a pull request to the repo and we will update it with any necessary API changes.

Coding style

Please:

Be consistent with the rest of the code.

Read PEP 8 - Style Guide for Python Code [https://www.python.org/dev/peps/pep-0008].

Follow the Google Python Style Guide [https://google.github.io/styleguide/pyguide.html], with the exception that we use Sphinx-style [http://sphinx-doc.org/] documentation:

def foo(arg):
    """Short description.

    :param int arg: Some number.

    :returns: Argument
    :rtype: int

    """
    return arg

Remember to use pylint.

Submitting a pull request

Steps:

Write your code!

Make sure your environment is set up properly and that you’re in your virtualenv. You can do this by running ./tools/venv.sh. (this is a very important step)

Run tox -e lint to check for pylint errors. Fix any errors.

Run tox --skip-missing-interpreters to run the entire test suite including coverage. The --skip-missing-interpreters argument ignores missing versions of Python needed for running the tests. Fix any errors.

If your code touches communication with an ACME server/Boulder, you should run the integration tests, see integration.

Submit the PR.

Did your tests pass on Travis? If they didn’t, fix any errors.

Updating certbot-auto and letsencrypt-auto

Updating the scripts

Developers should not modify the certbot-auto and letsencrypt-auto files in the root directory of the repository. Rather, modify the letsencrypt-auto.template and associated platform-specific shell scripts in the letsencrypt-auto-source and letsencrypt-auto-source/pieces/bootstrappers directory, respectively.

Building letsencrypt-auto-source/letsencrypt-auto

Once changes to any of the aforementioned files have been made, the letsencrypt-auto-source/letsencrypt-auto script should be updated. In lieu of manually updating this script, run the build script, which lives at letsencrypt-auto-source/build.py:

python letsencrypt-auto-source/build.py

Running build.py will update the letsencrypt-auto-source/letsencrypt-auto script. Note that the certbot-auto and letsencrypt-auto scripts in the root directory of the repository will remain unchanged after this script is run. Your changes will be propagated to these files during the next release of Certbot.

Opening a PR

When opening a PR, ensure that the following files are committed:

letsencrypt-auto-source/letsencrypt-auto.template and letsencrypt-auto-source/pieces/bootstrappers/*

letsencrypt-auto-source/letsencrypt-auto (generated by build.py)

It might also be a good idea to double check that no changes were inadvertently made to the certbot-auto or letsencrypt-auto scripts in the root of the repository. These scripts will be updated by the core developers during the next release.

Updating the documentation

In order to generate the Sphinx documentation, run the following commands:

make -C docs clean html man

This should generate documentation in the docs/_build/html directory.

Running the client with Docker

You can use Docker Compose to quickly set up an environment for running and testing Certbot. This is especially useful for macOS users. To install Docker Compose, follow the instructions at https://docs.docker.com/compose/install/.

Note

Linux users can simply run pip install docker-compose to get Docker Compose after installing Docker Engine and activating your shell as described in the Getting Started section.

Now you can develop on your host machine, but run Certbot and test your changes in Docker. When using docker-compose make sure you are inside your clone of the Certbot repository. As an example, you can run the following command to check for linting errors:

docker-compose run --rm --service-ports development bash -c 'tox -e lint'

You can also leave a terminal open running a shell in the Docker container and modify Certbot code in another window. The Certbot repo on your host machine is mounted inside of the container so any changes you make immediately take effect. To do this, run:

docker-compose run --rm --service-ports development bash

Now running the check for linting errors described above is as easy as:

tox -e lint

Notes on OS dependencies

OS-level dependencies can be installed like so:

letsencrypt-auto-source/letsencrypt-auto --os-packages-only

In general...

sudo is required as a suggested way of running privileged process

Python [https://wiki.python.org/moin/BeginnersGuide/Download] 2.6/2.7 is required

Augeas [http://augeas.net/] is required for the Python bindings

virtualenv and pip are used for managing other python library dependencies

Debian

For squeeze you will need to:

Use virtualenv --no-site-packages -p python instead of -p python2.

FreeBSD

Packages can be installed on FreeBSD using pkg, or any other port-management tool (portupgrade, portmanager, etc.) from the pre-built package or can be built and installed from ports. Either way will ensure proper installation of all the dependencies required for the package.

FreeBSD by default uses tcsh. In order to activate virtualenv (see above), you will need a compatible shell, e.g. pkg install bash && bash.

Packaging Guide

Releases

We release packages and upload them to PyPI (wheels and source tarballs).

https://pypi.python.org/pypi/acme

https://pypi.python.org/pypi/certbot

https://pypi.python.org/pypi/certbot-apache

https://pypi.python.org/pypi/certbot-nginx

https://pypi.python.org/pypi/certbot-dns-cloudflare

https://pypi.python.org/pypi/certbot-dns-cloudxns

https://pypi.python.org/pypi/certbot-dns-digitalocean

https://pypi.python.org/pypi/certbot-dns-dnsimple

https://pypi.python.org/pypi/certbot-dns-google

https://pypi.python.org/pypi/certbot-dns-nsone

https://pypi.python.org/pypi/certbot-dns-route53

The following scripts are used in the process:

https://github.com/letsencrypt/letsencrypt/blob/master/tools/release.sh

We use git tags to identify releases, using Semantic Versioning [http://semver.org/]. For example: v0.11.1.

Notes for package maintainers

Please use our tagged releases, not master!

Do not package certbot-compatibility-test or letshelp-certbot - it’s only used internally.

If you’d like to include automated renewal in your package certbot renew -q should be added to crontab or systemd timer. Additionally you should include a random per-machine time offset to avoid having a large number of your clients hit Let’s Encrypt’s servers simultaneously.

jws is an internal script for acme module and it doesn’t have to be packaged - it’s mostly for debugging: you can use it as echo foo | jws sign | jws verify.

Do get in touch with us. We are happy to make any changes that will make packaging easier. If you need to apply some patches don’t do it downstream - make a PR here.

Already ongoing efforts

Arch

From our official releases:

https://www.archlinux.org/packages/community/any/python-acme

https://www.archlinux.org/packages/community/any/certbot

https://www.archlinux.org/packages/community/any/certbot-apache

https://www.archlinux.org/packages/community/any/certbot-nginx

https://www.archlinux.org/packages/community/any/certbot-dns-cloudflare

https://www.archlinux.org/packages/community/any/certbot-dns-cloudxns

https://www.archlinux.org/packages/community/any/certbot-dns-digitalocean

https://www.archlinux.org/packages/community/any/certbot-dns-dnsimple

https://www.archlinux.org/packages/community/any/certbot-dns-google

https://www.archlinux.org/packages/community/any/certbot-dns-nsone

https://www.archlinux.org/packages/community/any/certbot-dns-route53

From master: https://aur.archlinux.org/packages/certbot-git

Debian (and its derivatives, including Ubuntu)

https://packages.debian.org/sid/certbot

https://packages.debian.org/sid/python-certbot

https://packages.debian.org/sid/python-certbot-apache

Fedora

In Fedora 23+.

https://admin.fedoraproject.org/pkgdb/package/certbot/

https://admin.fedoraproject.org/pkgdb/package/python-acme/

FreeBSD

https://www.freshports.org/security/py-acme/

https://www.freshports.org/security/py-certbot/

Gentoo

Currently, all certbot related packages are in the testing branch:

https://packages.gentoo.org/packages/app-crypt/certbot

https://packages.gentoo.org/packages/app-crypt/certbot-apache

https://packages.gentoo.org/packages/app-crypt/certbot-nginx

https://packages.gentoo.org/packages/app-crypt/acme

GNU Guix

https://www.gnu.org/software/guix/package-list.html#certbot

OpenBSD

http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/security/letsencrypt/client/

Resources

Documentation: https://certbot.eff.org/docs

Software project: https://github.com/certbot/certbot

Notes for developers: https://certbot.eff.org/docs/contributing.html

Main Website: https://certbot.eff.org

Let’s Encrypt Website: https://letsencrypt.org

IRC Channel: #letsencrypt on Freenode [https://webchat.freenode.net?channels=%23letsencrypt]

Community: https://community.letsencrypt.org

ACME spec: http://ietf-wg-acme.github.io/acme/

ACME working area in github: https://github.com/ietf-wg-acme/acme

Travis CI status [https://travis-ci.org/certbot/certbot] Coverage status [https://coveralls.io/r/certbot/certbot] Documentation status [https://readthedocs.org/projects/letsencrypt/] [https://quay.io/repository/letsencrypt/letsencrypt]

API Documentation

certbot.account

certbot.achallenges

certbot.auth_handler

certbot.client

certbot.configuration

certbot.constants

certbot.crypto_util

certbot.display

certbot.display.util

certbot.display.ops

certbot.display.enhancements

certbot.errors

certbot

certbot.interfaces

certbot.plugins.common

certbot.plugins.disco

certbot.plugins.dns_common

certbot.plugins.dns_common_lexicon

certbot.plugins.manual

certbot.plugins.standalone

certbot.plugins.util

certbot.plugins.webroot

certbot.reporter

certbot.reverter

certbot.storage

certbot.util

certbot.account

Creates ACME accounts for server.

class certbot.account.Account(regr, key, meta=None)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

ACME protocol registration.

Variables:

regr (RegistrationResource) – Registration Resource

key (JWK) – Authorized Account Key

Meta – Account metadata

id [https://docs.python.org/2/library/functions.html#id] (str [https://docs.python.org/2/library/functions.html#str]) – Globally unique account identifier.

class Meta(**kwargs)[source]

Bases: acme.jose.json_util.JSONObjectWithFields [https://acme-python.readthedocs.io/en/latest/api/jose/json_util.html#acme.jose.json_util.JSONObjectWithFields]

Account metadata

Variables:

creation_dt (datetime.datetime [https://docs.python.org/2/library/datetime.html#datetime.datetime]) – Creation date and time (UTC).

creation_host (str [https://docs.python.org/2/library/functions.html#str]) – FQDN of host, where account has been created.

Note

creation_dt and creation_host are useful in cross-machine migration scenarios.

slug

Short account identification string, useful for UI.

certbot.account.report_new_account(config)[source]

Informs the user about their new ACME account.

class certbot.account.AccountMemoryStorage(initial_accounts=None)[source]

Bases: certbot.interfaces.AccountStorage

In-memory account storage.

class certbot.account.RegistrationResourceWithNewAuthzrURI(**kwargs)[source]

Bases: acme.messages.RegistrationResource [https://acme-python.readthedocs.io/en/latest/api/messages.html#acme.messages.RegistrationResource]

A backwards-compatible RegistrationResource with a new-authz URI.

Hack: Certbot versions pre-0.11.1 expect to load new_authzr_uri as part of the account. Because people sometimes switch between old and new versions, we will continue to write out this field for some time so older clients don’t crash in that scenario.

class certbot.account.AccountFileStorage(config)[source]

Bases: certbot.interfaces.AccountStorage

Accounts file storage.

Variables:
config (IConfig) – Client configuration

save_regr(account, acme)[source]

Save the registration resource.

Parameters:
account (Account) – account whose regr should be saved

delete(account_id)[source]

Delete registration info from disk

Parameters:
account_id – id of account which should be deleted

certbot.achallenges

Client annotated ACME challenges.

Please use names such as achall to distinguish from variables “of type” acme.challenges.Challenge [https://acme-python.readthedocs.io/en/latest/api/challenges.html#acme.challenges.Challenge] (denoted by chall) and ChallengeBody (denoted by challb):

from acme import challenges
from acme import messages
from certbot import achallenges

chall = challenges.DNS(token='foo')
challb = messages.ChallengeBody(chall=chall)
achall = achallenges.DNS(chall=challb, domain='example.com')

Note, that all annotated challenges act as a proxy objects:

achall.token == challb.token

class certbot.achallenges.AnnotatedChallenge(**kwargs)[source]

Bases: acme.jose.util.ImmutableMap [https://acme-python.readthedocs.io/en/latest/api/jose/util.html#acme.jose.util.ImmutableMap]

Client annotated challenge.

Wraps around server provided challenge and annotates with data useful for the client.

Variables:
challb – Wrapped ChallengeBody.

class certbot.achallenges.KeyAuthorizationAnnotatedChallenge(**kwargs)[source]

Bases: certbot.achallenges.AnnotatedChallenge

Client annotated KeyAuthorizationChallenge challenge.

response_and_validation(*args, **kwargs)[source]

Generate response and validation.

class certbot.achallenges.DNS(**kwargs)[source]

Bases: certbot.achallenges.AnnotatedChallenge

Client annotated “dns” ACME challenge.

acme_type

alias of DNS

certbot.auth_handler

ACME AuthHandler.

class certbot.auth_handler.AuthHandler(auth, acme, account, pref_challs)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

ACME Authorization Handler for a client.

Variables:

auth – Authenticator capable of solving Challenge [https://acme-python.readthedocs.io/en/latest/api/challenges.html#acme.challenges.Challenge] types

acme [https://acme-python.readthedocs.io/en/latest/index.html#module-acme] (acme.client.Client [https://acme-python.readthedocs.io/en/latest/api/client.html#acme.client.Client]) – ACME client API.

account – Client’s Account

authzr (dict [https://docs.python.org/2/library/stdtypes.html#dict]) – ACME Authorization Resource dict where keys are domains and values are acme.messages.AuthorizationResource [https://acme-python.readthedocs.io/en/latest/api/messages.html#acme.messages.AuthorizationResource]

achalls (list) – DV challenges in the form of certbot.achallenges.AnnotatedChallenge

pref_challs (list) – sorted user specified preferred challenges type strings with the most preferred challenge listed first

get_authorizations(domains, best_effort=False)[source]

Retrieve all authorizations for challenges.

Parameters:

domains (list) – Domains for authorization

best_effort (bool [https://docs.python.org/2/library/functions.html#bool]) – Whether or not all authorizations are required (this is useful in renewal)

Returns:

List of authorization resources

Return type:

list

Raises:

AuthorizationError – If unable to retrieve all authorizations

_choose_challenges(domains)[source]

Retrieve necessary challenges to satisfy server.

_solve_challenges()[source]

Get Responses for challenges from authenticators.

_respond(resp, best_effort)[source]

Send/Receive confirmation of all challenges.

Note

This method also cleans up the auth_handler state.

_send_responses(achalls, resps, chall_update)[source]

Send responses and make sure errors are handled.

Parameters:
chall_update (dict [https://docs.python.org/2/library/stdtypes.html#dict]) – parameter that is updated to hold authzr -> list of outstanding solved annotated challenges

_poll_challenges(chall_update, best_effort, min_sleep=3, max_rounds=15)[source]

Wait for all challenge results to be determined.

_handle_check(domain, achalls)[source]

Returns tuple of (‘completed’, ‘failed’).

_find_updated_challb(authzr, achall)[source]

Find updated challenge body within Authorization Resource.

Warning

This assumes only one instance of type of challenge in each challenge resource.

Parameters:

authzr (AuthorizationResource) – Authorization Resource

achall (AnnotatedChallenge) – Annotated challenge for which to get status

_get_chall_pref(domain)[source]

Return list of challenge preferences.

Parameters:
domain (str [https://docs.python.org/2/library/functions.html#str]) – domain for which you are requesting preferences

_cleanup_challenges(achall_list=None)[source]

Cleanup challenges.

If achall_list is not provided, cleanup all achallenges.

verify_authzr_complete()[source]

Verifies that all authorizations have been decided.

Returns: | Whether all authzr are complete
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

_challenge_factory(domain, path)[source]

Construct Namedtuple Challenges

Parameters:

domain (str [https://docs.python.org/2/library/functions.html#str]) – domain of the enrollee

path (list) – List of indices from challenges.

Returns:

achalls, list of challenge type certbot.achallenges.Indexed

Return type:

list

Raises:

errors.Error – if challenge type is not recognized

certbot.auth_handler.challb_to_achall(challb, account_key, domain)[source]

Converts a ChallengeBody object to an AnnotatedChallenge.

Parameters:

challb (ChallengeBody) – ChallengeBody

account_key (JWK) – Authorized Account Key

domain (str [https://docs.python.org/2/library/functions.html#str]) – Domain of the challb

Returns:

Appropriate AnnotatedChallenge

Return type:

certbot.achallenges.AnnotatedChallenge

certbot.auth_handler.gen_challenge_path(challbs, preferences, combinations)[source]

Generate a plan to get authority over the identity.

Todo

This can be possibly be rewritten to use resolved_combinations.

Parameters:

challbs (tuple [https://docs.python.org/2/library/functions.html#tuple]) – A tuple of challenges (acme.messages.Challenge) from acme.messages.AuthorizationResource [https://acme-python.readthedocs.io/en/latest/api/messages.html#acme.messages.AuthorizationResource] to be fulfilled by the client in order to prove possession of the identifier.

preferences (list) – List of challenge preferences for domain (acme.challenges.Challenge [https://acme-python.readthedocs.io/en/latest/api/challenges.html#acme.challenges.Challenge] subclasses)

combinations (tuple [https://docs.python.org/2/library/functions.html#tuple]) – A collection of sets of challenges from acme.messages.Challenge, each of which would be sufficient to prove possession of the identifier.

Returns:

tuple of indices from challenges.

Return type:

tuple [https://docs.python.org/2/library/functions.html#tuple]

Raises:

certbot.errors.AuthorizationError – If a path cannot be created that satisfies the CA given the preferences and combinations.

certbot.auth_handler._find_smart_path(challbs, preferences, combinations)[source]

Find challenge path with server hints.

Can be called if combinations is included. Function uses a simple ranking system to choose the combo with the lowest cost.

certbot.auth_handler._find_dumb_path(challbs, preferences)[source]

Find challenge path without server hints.

Should be called if the combinations hint is not included by the server. This function either returns a path containing all challenges provided by the CA or raises an exception.

certbot.auth_handler._report_no_chall_path()[source]

Logs and raises an error that no satisfiable chall path exists.

certbot.auth_handler._report_failed_challs(failed_achalls)[source]

Notifies the user about failed challenges.

Parameters:
failed_achalls (set [https://docs.python.org/2/library/stdtypes.html#set]) – A set of failed certbot.achallenges.AnnotatedChallenge.

certbot.auth_handler._generate_failed_chall_msg(failed_achalls)[source]

Creates a user friendly error message about failed challenges.

Parameters: | failed_achalls (list) – A list of failed certbot.achallenges.AnnotatedChallenge with the same error type.
Returns: | A formatted error message for the client.
Return type: | str [https://docs.python.org/2/library/functions.html#str]

certbot.client

Certbot client API.

certbot.client.acme_from_config_key(config, key)[source]

Wrangle ACME client construction

certbot.client.determine_user_agent(config)[source]

Set a user_agent string in the config based on the choice of plugins. (this wasn’t knowable at construction time)

Returns: | the client’s User-Agent string
Return type: | str [https://docs.python.org/2/library/functions.html#str]

certbot.client.ua_flags(config)[source]

Turn some very important CLI flags into clues in the user agent.

class certbot.client.DummyConfig[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

Shim for computing a sample user agent.

certbot.client.sample_user_agent()[source]

Document what this Certbot’s user agent string will be like.

certbot.client.register(config, account_storage, tos_cb=None)[source]

Register new account with an ACME CA.

This function takes care of generating fresh private key, registering the account, optionally accepting CA Terms of Service and finally saving the account. It should be called prior to initialization of Client, unless account has already been created.

Parameters:

config (IConfig) – Client configuration.

account_storage (AccountStorage) – Account storage where newly registered account will be saved to. Save happens only after TOS acceptance step, so any account private keys or RegistrationResource will not be persisted if tos_cb returns False.

tos_cb – If ACME CA requires the user to accept a Terms of Service before registering account, client action is necessary. For example, a CLI tool would prompt the user acceptance. tos_cb must be a callable that should accept RegistrationResource and return a bool [https://docs.python.org/2/library/functions.html#bool]: True iff the Terms of Service present in the contained Registration.terms_of_service is accepted by the client, and False otherwise. tos_cb will be called only if the client action is necessary, i.e. when terms_of_service is not None. This argument is optional, if not supplied it will default to automatic acceptance!

Raises:

certbot.errors.Error – In case of any client problems, in particular registration failure, or unaccepted Terms of Service.

acme.errors.Error [https://acme-python.readthedocs.io/en/latest/api/errors.html#acme.errors.Error] – In case of any protocol problems.

Returns:

Newly registered and saved account, as well as protocol API handle (should be used in Client initialization).

Return type:

tuple [https://docs.python.org/2/library/functions.html#tuple] of Account and acme.client.Client [https://acme-python.readthedocs.io/en/latest/api/client.html#acme.client.Client]

certbot.client.perform_registration(acme, config)[source]

Actually register new account, trying repeatedly if there are email problems

Parameters:

config (IConfig) – Client configuration.

client (acme.client.Client [https://acme-python.readthedocs.io/en/latest/api/client.html#acme.client.Client]) – ACME client object.

Returns:

Registration Resource.

Return type:

acme.messages.RegistrationResource [https://acme-python.readthedocs.io/en/latest/api/messages.html#acme.messages.RegistrationResource]

class certbot.client.Client(config, account_, auth, installer, acme=None)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

Certbot’s client.

Variables:

config (IConfig) – Client configuration.

account (Account) – Account registered with register.

auth_handler (AuthHandler) – Authorizations handler that will dispatch DV challenges to appropriate authenticators (providing IAuthenticator interface).

auth (IAuthenticator) – Prepared (IAuthenticator.prepare) authenticator that can solve ACME challenges.

installer (IInstaller) – Installer.

acme [https://acme-python.readthedocs.io/en/latest/index.html#module-acme] (acme.client.Client [https://acme-python.readthedocs.io/en/latest/api/client.html#acme.client.Client]) – Optional ACME client API handle. You might already have one from register.

obtain_certificate_from_csr(domains, csr, authzr=None)[source]

Obtain certificate.

Internal function with precondition that domains are consistent with identifiers present in the csr.

Parameters:

domains (list) – Domain names.

csr (util.CSR) – PEM-encoded Certificate Signing Request. The key used to generate this CSR can be different than authkey.

authzr (list) – List of acme.messages.AuthorizationResource [https://acme-python.readthedocs.io/en/latest/api/messages.html#acme.messages.AuthorizationResource]

Returns:

CertificateResource and certificate chain (as returned by fetch_chain).

Return type:

tuple [https://docs.python.org/2/library/functions.html#tuple]

obtain_certificate(domains)[source]

Obtains a certificate from the ACME server.

register must be called before obtain_certificate

Parameters: | domains (list) – domains to get a certificate
Returns: | CertificateResource, certificate chain (as returned by fetch_chain), and newly generated private key (util.Key) and DER-encoded Certificate Signing Request (util.CSR).
Return type: | tuple [https://docs.python.org/2/library/functions.html#tuple]

obtain_and_enroll_certificate(domains, certname)[source]

Obtain and enroll certificate.

Get a new certificate for the specified domains using the specified authenticator and installer, and then create a new renewable lineage containing it.

Parameters:

domains (list) – Domains to request.

plugins – A PluginsFactory object.

certname (str [https://docs.python.org/2/library/functions.html#str]) – Name of new cert

Returns:

A new certbot.storage.RenewableCert instance referred to the enrolled cert lineage, False if the cert could not be obtained, or None if doing a successful dry run.

save_certificate(certr, chain_cert, cert_path, chain_path, fullchain_path)[source]

Saves the certificate received from the ACME server.

Parameters:

certr (acme.messages.Certificate) – ACME “certificate” resource.

chain_cert (list) –

cert_path (str [https://docs.python.org/2/library/functions.html#str]) – Candidate path to a certificate.

chain_path (str [https://docs.python.org/2/library/functions.html#str]) – Candidate path to a certificate chain.

fullchain_path (str [https://docs.python.org/2/library/functions.html#str]) – Candidate path to a full cert chain.

Returns:

cert_path, chain_path, and fullchain_path as absolute paths to the actual files

Return type:

tuple [https://docs.python.org/2/library/functions.html#tuple] of str [https://docs.python.org/2/library/functions.html#str]

Raises:

IOError – If unable to find room to write the cert files

deploy_certificate(domains, privkey_path, cert_path, chain_path, fullchain_path)[source]

Install certificate

Parameters:

domains (list) – list of domains to install the certificate

privkey_path (str [https://docs.python.org/2/library/functions.html#str]) – path to certificate private key

cert_path (str [https://docs.python.org/2/library/functions.html#str]) – certificate file path (optional)

chain_path (str [https://docs.python.org/2/library/functions.html#str]) – chain file path

enhance_config(domains, chain_path)[source]

Enhance the configuration.

Parameters:

domains (list) – list of domains to configure

chain_path (str [https://docs.python.org/2/library/functions.html#str] or None [https://docs.python.org/2/library/constants.html#None]) – chain file path

Raises:

errors.Error – if no installer is specified in the client.

apply_enhancement(domains, enhancement, options=None)[source]

Applies an enhancement on all domains.

Parameters:

domains (list) – list of ssl_vhosts (as strings)

enhancement (str [https://docs.python.org/2/library/functions.html#str]) – name of enhancement, e.g. ensure-http-header

options (str [https://docs.python.org/2/library/functions.html#str]) –

options to enhancement, e.g. Strict-Transport-Security

Note

When more options are needed, make options a list.

Raises:

errors.PluginError – If Enhancement is not supported, or if there is any other problem with the enhancement.

_recovery_routine_with_msg(success_msg)[source]

Calls the installer’s recovery routine and prints success_msg

Parameters:
success_msg (str [https://docs.python.org/2/library/functions.html#str]) – message to show on successful recovery

_rollback_and_restart(success_msg)[source]

Rollback the most recent checkpoint and restart the webserver

Parameters:
success_msg (str [https://docs.python.org/2/library/functions.html#str]) – message to show on successful rollback

certbot.client.validate_key_csr(privkey, csr=None)[source]

Validate Key and CSR files.

Verifies that the client key and csr arguments are valid and correspond to one another. This does not currently check the names in the CSR due to the inability to read SANs from CSRs in python crypto libraries.

If csr is left as None, only the key will be validated.

Parameters:

privkey (certbot.util.Key) – Key associated with CSR

csr (util.CSR) – CSR

Raises:

errors.Error – when validation fails

certbot.client.rollback(default_installer, checkpoints, config, plugins)[source]

Revert configuration the specified number of checkpoints.

Parameters:

checkpoints (int [https://docs.python.org/2/library/functions.html#int]) – Number of checkpoints to revert.

config (certbot.interfaces.IConfig) – Configuration.

certbot.client.view_config_changes(config, num=None)[source]

View checkpoints and associated configuration changes.

Note

This assumes that the installation is using a Reverter object.

Parameters:
config (certbot.interfaces.IConfig) – Configuration.

certbot.client._open_pem_file(cli_arg_path, pem_path)[source]

Open a pem file.

If cli_arg_path was set by the client, open that. Otherwise, uniquify the file path.

Parameters:

cli_arg_path (str [https://docs.python.org/2/library/functions.html#str]) – the cli arg name, e.g. cert_path

pem_path (str [https://docs.python.org/2/library/functions.html#str]) – the pem file path to open

Returns:

a tuple of file object and its absolute file path

certbot.client._save_chain(chain_pem, chain_file)[source]

Saves chain_pem at a unique path based on chain_path.

Parameters:

chain_pem (str [https://docs.python.org/2/library/functions.html#str]) – certificate chain in PEM format

chain_file (str [https://docs.python.org/2/library/functions.html#str]) – chain file object

certbot.configuration

Certbot user-supplied configuration.

class certbot.configuration.NamespaceConfig(namespace)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

Configuration wrapper around argparse.Namespace [https://docs.python.org/2/library/argparse.html#argparse.Namespace].

For more documentation, including available attributes, please see certbot.interfaces.IConfig. However, note that the following attributes are dynamically resolved using work_dir and relative paths defined in certbot.constants:

accounts_dir

csr_dir

in_progress_dir

key_dir

temp_checkpoint_dir

And the following paths are dynamically resolved using config_dir and relative paths defined in certbot.constants:

default_archive_dir

live_dir

renewal_configs_dir

Variables:
namespace – Namespace typically produced by argparse.ArgumentParser.parse_args() [https://docs.python.org/2/library/argparse.html#argparse.ArgumentParser.parse_args].

server_path

File path based on server.

certbot.configuration.check_config_sanity(config)[source]

Validate command line options and display error message if requirements are not met.

Parameters:
config – IConfig instance holding user configuration

certbot.constants

Certbot constants.

certbot.constants.SETUPTOOLS_PLUGINS_ENTRY_POINT = 'certbot.plugins'

Setuptools entry point group name for plugins.

certbot.constants.OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT = 'letsencrypt.plugins'

Plugins Setuptools entry point before rename.

certbot.constants.REVOCATION_REASONS = {'keycompromise': 1, 'affiliationchanged': 3, 'superseded': 4, 'unspecified': 0, 'cessationofoperation': 5}

Defaults for CLI flags and IConfig attributes.

certbot.constants.QUIET_LOGGING_LEVEL = 30

Logging level to use in quiet mode.

certbot.constants.RENEWER_DEFAULTS = {'renew_before_expiry': '30 days', 'deploy_before_expiry': '99 years', 'renewer_enabled': 'yes'}

Defaults for renewer script.

certbot.constants.ENHANCEMENTS = ['redirect', 'http-header', 'ocsp-stapling', 'spdy']

List of possible certbot.interfaces.IInstaller enhancements.

List of expected options parameters: - redirect: None - http-header: TODO - ocsp-stapling: certificate chain file path - spdy: TODO

certbot.constants.ARCHIVE_DIR = 'archive'

Archive directory, relative to IConfig.config_dir.

certbot.constants.CONFIG_DIRS_MODE = 493

Directory mode for .IConfig.config_dir et al.

certbot.constants.ACCOUNTS_DIR = 'accounts'

Directory where all accounts are saved.

certbot.constants.BACKUP_DIR = 'backups'

Directory (relative to IConfig.work_dir) where backups are kept.

certbot.constants.CSR_DIR = 'csr'

See IConfig.csr_dir.

certbot.constants.IN_PROGRESS_DIR = 'IN_PROGRESS'

Directory used before a permanent checkpoint is finalized (relative to IConfig.work_dir).

certbot.constants.KEY_DIR = 'keys'

Directory (relative to IConfig.config_dir) where keys are saved.

certbot.constants.LIVE_DIR = 'live'

Live directory, relative to IConfig.config_dir.

certbot.constants.TEMP_CHECKPOINT_DIR = 'temp_checkpoint'

Temporary checkpoint directory (relative to IConfig.work_dir).

certbot.constants.RENEWAL_CONFIGS_DIR = 'renewal'

Renewal configs directory, relative to IConfig.config_dir.

certbot.constants.FORCE_INTERACTIVE_FLAG = '--force-interactive'

Flag to disable TTY checking in IDisplay.

certbot.constants.EFF_SUBSCRIBE_URI = 'https://supporters.eff.org/subscribe/certbot'

EFF URI used to submit the e-mail address of users who opt-in.

certbot.crypto_util

Certbot client crypto utility functions.

Todo

Make the transition to use PSS rather than PKCS1_v1_5 when the server is capable of handling the signatures.

certbot.crypto_util.init_save_key(key_size, key_dir, keyname='key-certbot.pem')[source]

Initializes and saves a privkey.

Inits key and saves it in PEM format on the filesystem.

Note

keyname is the attempted filename, it may be different if a file already exists at the path.

Parameters:

key_size (int [https://docs.python.org/2/library/functions.html#int]) – RSA key size in bits

key_dir (str [https://docs.python.org/2/library/functions.html#str]) – Key save directory.

keyname (str [https://docs.python.org/2/library/functions.html#str]) – Filename of key

Returns:

Key

Return type:

certbot.util.Key

Raises:

ValueError – If unable to generate the key given key_size.

certbot.crypto_util.init_save_csr(privkey, names, path)[source]

Initialize a CSR with the given private key.

Parameters:

privkey (certbot.util.Key) – Key to include in the CSR

names (set [https://docs.python.org/2/library/stdtypes.html#set]) – str [https://docs.python.org/2/library/functions.html#str] names to include in the CSR

path (str [https://docs.python.org/2/library/functions.html#str]) – Certificate save directory.

Returns:

CSR

Return type:

certbot.util.CSR

certbot.crypto_util.valid_csr(csr)[source]

Validate CSR.

Check if csr is a valid CSR for the given domains.

Parameters: | csr (str [https://docs.python.org/2/library/functions.html#str]) – CSR in PEM.
Returns: | Validity of CSR.
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

certbot.crypto_util.csr_matches_pubkey(csr, privkey)[source]

Does private key correspond to the subject public key in the CSR?

Parameters:

csr (str [https://docs.python.org/2/library/functions.html#str]) – CSR in PEM.

privkey (str [https://docs.python.org/2/library/functions.html#str]) – Private key file contents (PEM)

Returns:

Correspondence of private key to CSR subject public key.

Return type:

bool [https://docs.python.org/2/library/functions.html#bool]

certbot.crypto_util.import_csr_file(csrfile, data)[source]

Import a CSR file, which can be either PEM or DER.

Parameters:

csrfile (str [https://docs.python.org/2/library/functions.html#str]) – CSR filename

data (str [https://docs.python.org/2/library/functions.html#str]) – contents of the CSR file

Returns:

(OpenSSL.crypto.FILETYPE_PEM, util.CSR object representing the CSR, list of domains requested in the CSR)

Return type:

tuple [https://docs.python.org/2/library/functions.html#tuple]

certbot.crypto_util.make_key(bits)[source]

Generate PEM encoded RSA key.

Parameters: | bits (int [https://docs.python.org/2/library/functions.html#int]) – Number of bits, at least 1024.
Returns: | new RSA key in PEM form with specified number of bits
Return type: | str [https://docs.python.org/2/library/functions.html#str]

certbot.crypto_util.valid_privkey(privkey)[source]

Is valid RSA private key?

Parameters: | privkey (str [https://docs.python.org/2/library/functions.html#str]) – Private key file contents in PEM
Returns: | Validity of private key.
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

certbot.crypto_util.verify_renewable_cert(renewable_cert)[source]

For checking that your certs were not corrupted on disk.

Several things are checked:

Signature verification for the cert.

That fullchain matches cert and chain when concatenated.

Check that the private key matches the certificate.

Parameters: | renewable_cert (storage.RenewableCert) – cert to verify
Raises: | errors.Error – If verification fails.

certbot.crypto_util.verify_renewable_cert_sig(renewable_cert)[source]

Verifies the signature of a storage.RenewableCert object.

Parameters: | renewable_cert (storage.RenewableCert) – cert to verify
Raises: | errors.Error – If signature verification fails.

certbot.crypto_util.verify_cert_matches_priv_key(renewable_cert)[source]

Verifies that the private key and cert match.

Parameters: | renewable_cert (storage.RenewableCert) – cert to verify
Raises: | errors.Error – If they don’t match.

certbot.crypto_util.verify_fullchain(renewable_cert)[source]

Verifies that fullchain is indeed cert concatenated with chain.

Parameters: | renewable_cert (storage.RenewableCert) – cert to verify
Raises: | errors.Error – If cert and chain do not combine to fullchain.

certbot.crypto_util.pyopenssl_load_certificate(data)[source]

Load PEM/DER certificate.

Raises:
errors.Error –

certbot.crypto_util.get_sans_from_cert(cert, typ=1)[source]

Get a list of Subject Alternative Names from a certificate.

Parameters:

cert (str [https://docs.python.org/2/library/functions.html#str]) – Certificate (encoded).

typ – OpenSSL.crypto.FILETYPE_PEM or OpenSSL.crypto.FILETYPE_ASN1

Returns:

A list of Subject Alternative Names.

Return type:

list

certbot.crypto_util.get_names_from_cert(csr, typ=1)[source]

Get a list of domains from a cert, including the CN if it is set.

Parameters:

cert (str [https://docs.python.org/2/library/functions.html#str]) – Certificate (encoded).

typ – OpenSSL.crypto.FILETYPE_PEM or OpenSSL.crypto.FILETYPE_ASN1

Returns:

A list of domain names.

Return type:

list

certbot.crypto_util.dump_pyopenssl_chain(chain, filetype=1)[source]

Dump certificate chain into a bundle.

Parameters:
chain (list) – List of OpenSSL.crypto.X509 (or wrapped in acme.jose.ComparableX509).

certbot.crypto_util.notBefore(cert_path)[source]

When does the cert at cert_path start being valid?

Parameters: | cert_path (str [https://docs.python.org/2/library/functions.html#str]) – path to a cert in PEM format
Returns: | the notBefore value from the cert at cert_path
Return type: | datetime.datetime [https://docs.python.org/2/library/datetime.html#datetime.datetime]

certbot.crypto_util.notAfter(cert_path)[source]

When does the cert at cert_path stop being valid?

Parameters: | cert_path (str [https://docs.python.org/2/library/functions.html#str]) – path to a cert in PEM format
Returns: | the notAfter value from the cert at cert_path
Return type: | datetime.datetime [https://docs.python.org/2/library/datetime.html#datetime.datetime]

certbot.crypto_util._notAfterBefore(cert_path, method)[source]

Internal helper function for finding notbefore/notafter.

Parameters:

cert_path (str [https://docs.python.org/2/library/functions.html#str]) – path to a cert in PEM format

method (function) – 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

Return type:

datetime.datetime [https://docs.python.org/2/library/datetime.html#datetime.datetime]

certbot.crypto_util.sha256sum(filename)[source]

Compute a sha256sum of a file.

Parameters: | filename (str [https://docs.python.org/2/library/functions.html#str]) – path to the file whose hash will be computed
Returns: | sha256 digest of the file in hexadecimal
Return type: | str [https://docs.python.org/2/library/functions.html#str]

certbot.display

Certbot display utilities.

certbot.display.util

Certbot display.

certbot.display.util.OK = 'ok'

Display exit code indicating user acceptance.

certbot.display.util.CANCEL = 'cancel'

Display exit code for a user canceling the display.

certbot.display.util.HELP = 'help'

Display exit code when for when the user requests more help.

certbot.display.util.ESC = 'esc'

Display exit code when the user hits Escape

certbot.display.util._wrap_lines(msg)[source]

Format lines nicely to 80 chars.

Parameters: | msg (str [https://docs.python.org/2/library/functions.html#str]) – Original message
Returns: | Formatted message respecting newlines in message
Return type: | str [https://docs.python.org/2/library/functions.html#str]

certbot.display.util.input_with_timeout(prompt=None, timeout=36000.0)[source]

Get user input with a timeout.

Behaves the same as six.moves.input, however, an error is raised if a user doesn’t answer after timeout seconds. The default timeout value was chosen to place it just under 12 hours for users following our advice and running Certbot twice a day.

Parameters:

prompt (str [https://docs.python.org/2/library/functions.html#str]) – prompt to provide for input

timeout (float [https://docs.python.org/2/library/functions.html#float]) – maximum number of seconds to wait for input

Returns:

user response

Return type:

str [https://docs.python.org/2/library/functions.html#str]

:raises errors.Error if no answer is given before the timeout

class certbot.display.util.FileDisplay(outfile, force_interactive)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

File-based display.

notification(message, pause=True, wrap=True, force_interactive=False)[source]

Displays a notification and waits for user acceptance.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – Message to display

pause (bool [https://docs.python.org/2/library/functions.html#bool]) – Whether or not the program should pause for the user’s confirmation

wrap (bool [https://docs.python.org/2/library/functions.html#bool]) – Whether or not the application should wrap text

force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – True if it’s safe to prompt the user because it won’t cause any workflow regressions

menu(message, choices, ok_label='', cancel_label='', help_label='', default=None, cli_flag=None, force_interactive=False, **unused_kwargs)[source]

Display a menu.

Todo

This doesn’t enable the help label/button (I wasn’t sold on any interface I came up with for this). It would be a nice feature

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – title of menu

choices (list of tuples (tag, item) or list of descriptions (tags will be enumerated)) – Menu lines, len must be > 0

default – default value to return (if one exists)

cli_flag (str [https://docs.python.org/2/library/functions.html#str]) – option used to set this value with the CLI

force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – True if it’s safe to prompt the user because it won’t cause any workflow regressions

Returns:

tuple of (code [https://docs.python.org/2/library/code.html#module-code], index) where code [https://docs.python.org/2/library/code.html#module-code] - str display exit code index - int index of the user’s selection

Return type:

tuple [https://docs.python.org/2/library/functions.html#tuple]

input(message, default=None, cli_flag=None, force_interactive=False, **unused_kwargs)[source]

Accept input from the user.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – message to display to the user

default – default value to return (if one exists)

cli_flag (str [https://docs.python.org/2/library/functions.html#str]) – option used to set this value with the CLI

force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – True if it’s safe to prompt the user because it won’t cause any workflow regressions

Returns:

tuple of (code [https://docs.python.org/2/library/code.html#module-code], input) where code [https://docs.python.org/2/library/code.html#module-code] - str display exit code input - str of the user’s input

Return type:

tuple [https://docs.python.org/2/library/functions.html#tuple]

yesno(message, yes_label='Yes', no_label='No', default=None, cli_flag=None, force_interactive=False, **unused_kwargs)[source]

Query the user with a yes/no question.

Yes and No label must begin with different letters, and must contain at least one letter each.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – question for the user

yes_label (str [https://docs.python.org/2/library/functions.html#str]) – Label of the “Yes” parameter

no_label (str [https://docs.python.org/2/library/functions.html#str]) – Label of the “No” parameter

default – default value to return (if one exists)

cli_flag (str [https://docs.python.org/2/library/functions.html#str]) – option used to set this value with the CLI

force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – True if it’s safe to prompt the user because it won’t cause any workflow regressions

Returns:

True for “Yes”, False for “No”

Return type:

bool [https://docs.python.org/2/library/functions.html#bool]

checklist(message, tags, default_status=True, default=None, cli_flag=None, force_interactive=False, **unused_kwargs)[source]

Display a checklist.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – Message to display to user

tags (list) – str [https://docs.python.org/2/library/functions.html#str] tags to select, len(tags) > 0

default_status (bool [https://docs.python.org/2/library/functions.html#bool]) – Not used for FileDisplay

default – default value to return (if one exists)

cli_flag (str [https://docs.python.org/2/library/functions.html#str]) – option used to set this value with the CLI

force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – True if it’s safe to prompt the user because it won’t cause any workflow regressions

Returns:

tuple of (code [https://docs.python.org/2/library/code.html#module-code], tags) where code [https://docs.python.org/2/library/code.html#module-code] - str display exit code tags - list of selected tags

Return type:

tuple [https://docs.python.org/2/library/functions.html#tuple]

_return_default(prompt, default, cli_flag, force_interactive)[source]

Should we return the default instead of prompting the user?

Parameters:

prompt (str [https://docs.python.org/2/library/functions.html#str]) – prompt for the user

default – default answer to prompt

cli_flag (str [https://docs.python.org/2/library/functions.html#str]) – command line option for setting an answer to this question

force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – if interactivity is forced by the IDisplay call

Returns:

True if we should return the default without prompting

Return type:

bool [https://docs.python.org/2/library/functions.html#bool]

_can_interact(force_interactive)[source]

Can we safely interact with the user?

Parameters: | force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – if interactivity is forced by the IDisplay call
Returns: | True if the display can interact with the user
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

directory_select(message, default=None, cli_flag=None, force_interactive=False, **unused_kwargs)[source]

Display a directory selection screen.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – prompt to give the user

default – default value to return (if one exists)

cli_flag (str [https://docs.python.org/2/library/functions.html#str]) – option used to set this value with the CLI

force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – True if it’s safe to prompt the user because it won’t cause any workflow regressions

Returns:

tuple of the form (code [https://docs.python.org/2/library/code.html#module-code], string [https://docs.python.org/2/library/string.html#module-string]) where code [https://docs.python.org/2/library/code.html#module-code] - display exit code string [https://docs.python.org/2/library/string.html#module-string] - input entered by the user

_scrub_checklist_input(indices, tags)[source]

Validate input and transform indices to appropriate tags.

Parameters:

indices (list) – input

tags (list) – Original tags of the checklist

Returns:

valid tags the user selected

Return type:

list of str [https://docs.python.org/2/library/functions.html#str]

_print_menu(message, choices)[source]

Print a menu on the screen.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – title of menu

choices (list of tuples (tag, item) or list of descriptions (tags will be enumerated)) – Menu lines

_get_valid_int_ans(max_)[source]

Get a numerical selection.

Parameters: | max (int [https://docs.python.org/2/library/functions.html#int]) – The maximum entry (len of choices), must be positive
Returns: | tuple of the form (code [https://docs.python.org/2/library/code.html#module-code], selection) where code [https://docs.python.org/2/library/code.html#module-code] - str display exit code (‘ok’ or cancel’) selection - int user’s selection
Return type: | tuple [https://docs.python.org/2/library/functions.html#tuple]

certbot.display.util.assert_valid_call(prompt, default, cli_flag, force_interactive)[source]

Verify that provided arguments is a valid IDisplay call.

Parameters:

prompt (str [https://docs.python.org/2/library/functions.html#str]) – prompt for the user

default – default answer to prompt

cli_flag (str [https://docs.python.org/2/library/functions.html#str]) – command line option for setting an answer to this question

force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – if interactivity is forced by the IDisplay call

class certbot.display.util.NoninteractiveDisplay(outfile, *unused_args, **unused_kwargs)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

An iDisplay implementation that never asks for interactive user input

_interaction_fail(message, cli_flag, extra='')[source]

Error out in case of an attempt to interact in noninteractive mode

notification(message, pause=False, wrap=True, **unused_kwargs)[source]

Displays a notification without waiting for user acceptance.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – Message to display to stdout

pause (bool [https://docs.python.org/2/library/functions.html#bool]) – The NoninteractiveDisplay waits for no keyboard

wrap (bool [https://docs.python.org/2/library/functions.html#bool]) – Whether or not the application should wrap text

menu(message, choices, ok_label=None, cancel_label=None, help_label=None, default=None, cli_flag=None, **unused_kwargs)[source]

Avoid displaying a menu.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – title of menu

choices (list of tuples (tag, item) or list of descriptions (tags will be enumerated)) – Menu lines, len must be > 0

default (int [https://docs.python.org/2/library/functions.html#int]) – the default choice

kwargs (dict [https://docs.python.org/2/library/stdtypes.html#dict]) – absorbs various irrelevant labelling arguments

Returns:

tuple of (code [https://docs.python.org/2/library/code.html#module-code], index) where code [https://docs.python.org/2/library/code.html#module-code] - str display exit code index - int index of the user’s selection

Return type:

tuple [https://docs.python.org/2/library/functions.html#tuple]

Raises:

errors.MissingCommandlineFlag – if there was no default

input(message, default=None, cli_flag=None, **unused_kwargs)[source]

Accept input from the user.

Parameters: | message (str [https://docs.python.org/2/library/functions.html#str]) – message to display to the user
Returns: | tuple of (code [https://docs.python.org/2/library/code.html#module-code], input) where code [https://docs.python.org/2/library/code.html#module-code] - str display exit code input - str of the user’s input
Return type: | tuple [https://docs.python.org/2/library/functions.html#tuple]
Raises: | errors.MissingCommandlineFlag – if there was no default

yesno(message, yes_label=None, no_label=None, default=None, cli_flag=None, **unused_kwargs)[source]

Decide Yes or No, without asking anybody

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – question for the user

kwargs (dict [https://docs.python.org/2/library/stdtypes.html#dict]) – absorbs yes_label, no_label

Raises:

errors.MissingCommandlineFlag – if there was no default

Returns:

True for “Yes”, False for “No”

Return type:

bool [https://docs.python.org/2/library/functions.html#bool]

checklist(message, tags, default=None, cli_flag=None, **unused_kwargs)[source]

Display a checklist.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – Message to display to user

tags (list) – str [https://docs.python.org/2/library/functions.html#str] tags to select, len(tags) > 0

kwargs (dict [https://docs.python.org/2/library/stdtypes.html#dict]) – absorbs default_status arg

Returns:

tuple of (code [https://docs.python.org/2/library/code.html#module-code], tags) where code [https://docs.python.org/2/library/code.html#module-code] - str display exit code tags - list of selected tags

Return type:

tuple [https://docs.python.org/2/library/functions.html#tuple]

directory_select(message, default=None, cli_flag=None, **unused_kwargs)[source]

Simulate prompting the user for a directory.

This function returns default if it is not None, otherwise, an exception is raised explaining the problem. If cli_flag is not None, the error message will include the flag that can be used to set this value with the CLI.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – prompt to give the user

default – default value to return (if one exists)

cli_flag (str [https://docs.python.org/2/library/functions.html#str]) – option used to set this value with the CLI

Returns:

tuple of the form (code [https://docs.python.org/2/library/code.html#module-code], string [https://docs.python.org/2/library/string.html#module-string]) where code [https://docs.python.org/2/library/code.html#module-code] - int display exit code string [https://docs.python.org/2/library/string.html#module-string] - input entered by the user

certbot.display.util.separate_list_input(input_)[source]

Separate a comma or space separated list.

Parameters: | input (str [https://docs.python.org/2/library/functions.html#str]) – input from the user
Returns: | strings
Return type: | list

certbot.display.util._parens_around_char(label)[source]

Place parens around first character of label.

Parameters:
label (str [https://docs.python.org/2/library/functions.html#str]) – Must contain at least one character

certbot.display.ops

Contains UI methods for LE user operations.

certbot.display.ops.get_email(invalid=False, optional=True)[source]

Prompt for valid email address.

Parameters:

invalid (bool [https://docs.python.org/2/library/functions.html#bool]) – True if an invalid address was provided by the user

optional (bool [https://docs.python.org/2/library/functions.html#bool]) – True if the user can use –register-unsafely-without-email to avoid providing an e-mail

Returns:

e-mail address

Return type:

str [https://docs.python.org/2/library/functions.html#str]

Raises:

errors.Error – if the user cancels

certbot.display.ops.choose_account(accounts)[source]

Choose an account.

Parameters:
accounts (list) – Containing at least one Account

certbot.display.ops.choose_names(installer)[source]

Display screen to select domains to validate.

Parameters: | installer (certbot.interfaces.IInstaller) – An installer object
Returns: | List of selected names
Return type: | list of str [https://docs.python.org/2/library/functions.html#str]

certbot.display.ops.get_valid_domains(domains)[source]

Helper method for choose_names that implements basic checks

on domain names

Parameters: | domains (list) – Domain names to validate
Returns: | List of valid domains
Return type: | list

certbot.display.ops._sort_names(FQDNs)[source]

Sort FQDNs by SLD (and if many, by their subdomains)

Parameters: | FQDNs (list) – list of domain names
Returns: | Sorted list of domain names
Return type: | list

certbot.display.ops._filter_names(names)[source]

Determine which names the user would like to select from a list.

Parameters: | names (list) – domain names
Returns: | tuple of the form (code [https://docs.python.org/2/library/code.html#module-code], names) where code [https://docs.python.org/2/library/code.html#module-code] - str display exit code names - list of names selected
Return type: | tuple [https://docs.python.org/2/library/functions.html#tuple]

certbot.display.ops._choose_names_manually(prompt_prefix='')[source]

Manually input names for those without an installer.

Parameters: | prompt_prefix (str [https://docs.python.org/2/library/functions.html#str]) – string to prepend to prompt for domains
Returns: | list of provided names
Return type: | list of str [https://docs.python.org/2/library/functions.html#str]

certbot.display.ops.success_installation(domains)[source]

Display a box confirming the installation of HTTPS.

Parameters:
domains (list) – domain names which were enabled

certbot.display.ops.success_renewal(domains)[source]

Display a box confirming the renewal of an existing certificate.

Parameters:
domains (list) – domain names which were renewed

certbot.display.ops.success_revocation(cert_path)[source]

Display a box confirming a certificate has been revoked.

Parameters:
cert_path (list) – path to certificate which was revoked.

certbot.display.ops._gen_ssl_lab_urls(domains)[source]

Returns a list of urls.

Parameters:
domains (list) – Each domain is a ‘str’

certbot.display.ops._gen_https_names(domains)[source]

Returns a string of the https domains.

Domains are formatted nicely with https:// prepended to each.

Parameters:
domains (list) – Each domain is a ‘str’

certbot.display.ops.validated_input(validator, *args, **kwargs)[source]

Like input, but with validation.

Parameters:

validator (callable [https://docs.python.org/2/library/functions.html#callable]) – A method which will be called on the supplied input. If the method raises a errors.Error, its text will be displayed and the user will be re-prompted.

*args (list) – Arguments to be passed to input.

**kwargs (dict [https://docs.python.org/2/library/stdtypes.html#dict]) – Arguments to be passed to input.

Returns:

as input

Return type:

tuple [https://docs.python.org/2/library/functions.html#tuple]

certbot.display.ops.validated_directory(validator, *args, **kwargs)[source]

Like directory_select, but with validation.

Parameters:

validator (callable [https://docs.python.org/2/library/functions.html#callable]) – A method which will be called on the supplied input. If the method raises a errors.Error, its text will be displayed and the user will be re-prompted.

*args (list) – Arguments to be passed to directory_select.

**kwargs (dict [https://docs.python.org/2/library/stdtypes.html#dict]) – Arguments to be passed to directory_select.

Returns:

as directory_select

Return type:

tuple [https://docs.python.org/2/library/functions.html#tuple]

certbot.display.enhancements

Certbot Enhancement Display

certbot.display.enhancements.ask(enhancement)[source]

Display the enhancement to the user.

Parameters: | enhancement (str [https://docs.python.org/2/library/functions.html#str]) – One of the certbot.CONFIG.ENHANCEMENTS enhancements
Returns: | True if feature is desired, False otherwise
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]
Raises: | errors.Error – if the enhancement provided is not supported

certbot.display.enhancements.redirect_by_default()[source]

Determines whether the user would like to redirect to HTTPS.

Returns: | True if redirect is desired, False otherwise
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

certbot.errors

Certbot client errors.

exception certbot.errors.Error[source]

Bases: exceptions.Exception [https://docs.python.org/2/library/exceptions.html#exceptions.Exception]

Generic Certbot client error.

exception certbot.errors.AccountStorageError[source]

Bases: certbot.errors.Error

Generic AccountStorage error.

exception certbot.errors.AccountNotFound[source]

Bases: certbot.errors.AccountStorageError

Account not found error.

exception certbot.errors.ReverterError[source]

Bases: certbot.errors.Error

Certbot Reverter error.

exception certbot.errors.SubprocessError[source]

Bases: certbot.errors.Error

Subprocess handling error.

exception certbot.errors.CertStorageError[source]

Bases: certbot.errors.Error

Generic CertStorage error.

exception certbot.errors.HookCommandNotFound[source]

Bases: certbot.errors.Error

Failed to find a hook command in the PATH.

exception certbot.errors.SignalExit[source]

Bases: certbot.errors.Error

A Unix signal was received while in the ErrorHandler context manager.

exception certbot.errors.LockError[source]

Bases: certbot.errors.Error

File locking error.

exception certbot.errors.AuthorizationError[source]

Bases: certbot.errors.Error

Authorization error.

exception certbot.errors.FailedChallenges(failed_achalls)[source]

Bases: certbot.errors.AuthorizationError

Failed challenges error.

Variables:
failed_achalls (set [https://docs.python.org/2/library/stdtypes.html#set]) – Failed AnnotatedChallenge instances.

exception certbot.errors.PluginError[source]

Bases: certbot.errors.Error

Certbot Plugin error.

exception certbot.errors.PluginEnhancementAlreadyPresent[source]

Bases: certbot.errors.Error

Enhancement was already set

exception certbot.errors.PluginSelectionError[source]

Bases: certbot.errors.Error

A problem with plugin/configurator selection or setup

exception certbot.errors.NoInstallationError[source]

Bases: certbot.errors.PluginError

Certbot No Installation error.

exception certbot.errors.MisconfigurationError[source]

Bases: certbot.errors.PluginError

Certbot Misconfiguration error.

exception certbot.errors.NotSupportedError[source]

Bases: certbot.errors.PluginError

Certbot Plugin function not supported error.

exception certbot.errors.StandaloneBindError(socket_error, port)[source]

Bases: certbot.errors.Error

Standalone plugin bind error.

exception certbot.errors.ConfigurationError[source]

Bases: certbot.errors.Error

Configuration sanity error.

exception certbot.errors.MissingCommandlineFlag[source]

Bases: certbot.errors.Error

A command line argument was missing in noninteractive usage

certbot

Certbot client.

certbot.interfaces

Certbot client interfaces.

class certbot.interfaces.AccountStorage[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

Accounts storage interface.

find_all()[source]

Find all accounts.

Returns: | All found accounts.
Return type: | list

load(account_id)[source]

Load an account by its id.

Raises:

AccountNotFound – if account could not be found

AccountStorageError – if account could not be loaded

save(account, client)[source]

Save account.

Raises:
AccountStorageError – if account could not be saved

interface certbot.interfaces.IPluginFactory[source]

IPlugin factory.

Objects providing this interface will be called without satisfying any entry point “extras” (extra dependencies) you might have defined for your plugin, e.g (excerpt from setup.py script):

setup(
    ...
    entry_points={
        'certbot.plugins': [
            'name=example_project.plugin[plugin_deps]',
        ],
    },
    extras_require={
        'plugin_deps': ['dep1', 'dep2'],
    }
)

Therefore, make sure such objects are importable and usable without extras. This is necessary, because CLI does the following operations (in order):

loads an entry point,

calls inject_parser_options,

requires an entry point,

creates plugin instance (__call__).

description

Short plugin description

__call__(config, name)

Create new IPlugin.

Parameters:

config (IConfig) – Configuration.

name (str [https://docs.python.org/2/library/functions.html#str]) – Unique plugin name.

inject_parser_options(parser, name)

Inject argument parser options (flags).

1. Be nice and prepend all options and destinations with option_namespace and dest_namespace.

2. Inject options (flags) only. Positional arguments are not allowed, as this would break the CLI.

Parameters:

parser (ArgumentParser) – (Almost) top-level CLI parser.

name (str [https://docs.python.org/2/library/functions.html#str]) – Unique plugin name.

interface certbot.interfaces.IPlugin[source]

Certbot plugin.

prepare()

Prepare the plugin.

Finish up any additional initialization.

Raises:

PluginError – when full initialization cannot be completed.

MisconfigurationError – when full initialization cannot be completed. Plugin will be displayed on a list of available plugins.

NoInstallationError – when the necessary programs/files cannot be located. Plugin will NOT be displayed on a list of available plugins.

NotSupportedError – when the installation is recognized, but the version is not currently supported.

more_info()

Human-readable string to help the user.

Should describe the steps taken and any relevant info to help the user decide which plugin to use.

Rtype str:

interface certbot.interfaces.IAuthenticator[source]

Extends: certbot.interfaces.IPlugin

Generic Certbot Authenticator.

Class represents all possible tools processes that have the ability to perform challenges and attain a certificate.

get_chall_pref(domain)

Return collections.Iterable [https://docs.python.org/2/library/collections.html#collections.Iterable] of challenge preferences.

Parameters:
domain (str [https://docs.python.org/2/library/functions.html#str]) – Domain for which challenge preferences are sought.

Returns:
collections.Iterable [https://docs.python.org/2/library/collections.html#collections.Iterable] of challenge types (subclasses of acme.challenges.Challenge [https://acme-python.readthedocs.io/en/latest/api/challenges.html#acme.challenges.Challenge]) with the most preferred challenges first. If a type is not specified, it means the Authenticator cannot perform the challenge.

Return type:
collections.Iterable [https://docs.python.org/2/library/collections.html#collections.Iterable]

perform(achalls)

Perform the given challenge.

Parameters:
achalls (list) – Non-empty (guaranteed) list of AnnotatedChallenge instances, such that it contains types found within get_chall_pref() only.

Returns:
collections.Iterable [https://docs.python.org/2/library/collections.html#collections.Iterable] of ACME ChallengeResponse [https://acme-python.readthedocs.io/en/latest/api/challenges.html#acme.challenges.ChallengeResponse] instances or if the Challenge [https://acme-python.readthedocs.io/en/latest/api/challenges.html#acme.challenges.Challenge] cannot be fulfilled then:

None

Authenticator can perform challenge, but not at this time.

False

Authenticator will never be able to perform (error).

Return type:
collections.Iterable [https://docs.python.org/2/library/collections.html#collections.Iterable] of acme.challenges.ChallengeResponse [https://acme-python.readthedocs.io/en/latest/api/challenges.html#acme.challenges.ChallengeResponse], where responses are required to be returned in the same order as corresponding input challenges

Raises:
PluginError – If challenges cannot be performed

cleanup(achalls)

Revert changes and shutdown after challenges complete.

This method should be able to revert all changes made by perform, even if perform exited abnormally.

Parameters: | achalls (list) – Non-empty (guaranteed) list of AnnotatedChallenge instances, a subset of those previously passed to perform().
Raises: | PluginError – if original configuration cannot be restored

interface certbot.interfaces.IConfig[source]

Certbot user-supplied configuration.

Warning

The values stored in the configuration have not been filtered, stripped or sanitized.

server

ACME Directory Resource URI.

email

Email used for registration and recovery contact. (default: Ask)

rsa_key_size

Size of the RSA key.

must_staple

Adds the OCSP Must Staple extension to the certificate. Autoconfigures OCSP Stapling for supported setups (Apache version >= 2.3.3 ).

config_dir

Configuration directory.

work_dir

Working directory.

accounts_dir

Directory where all account information is stored.

backup_dir

Configuration backups directory.

csr_dir

Directory where newly generated Certificate Signing Requests (CSRs) are saved.

in_progress_dir

Directory used before a permanent checkpoint is finalized.

key_dir

Keys storage.

temp_checkpoint_dir

Temporary checkpoint directory.

no_verify_ssl

Disable verification of the ACME server’s certificate.

tls_sni_01_port

Port used during tls-sni-01 challenge. This only affects the port Certbot listens on. A conforming ACME server will still attempt to connect on port 443.

tls_sni_01_address

The address the server listens to during tls-sni-01 challenge.

http01_port

Port used in the http-01 challenge. This only affects the port Certbot listens on. A conforming ACME server will still attempt to connect on port 80.

http01_address

The address the server listens to during http-01 challenge.

pref_challs

Sorted user specified preferred challengestype strings with the most preferred challenge listed first

allow_subset_of_names

When performing domain validation, do not consider it a failure if authorizations can not be obtained for a strict subset of the requested domains. This may be useful for allowing renewals for multiple domains to succeed even if some domains no longer point at this system. This is a boolean

strict_permissions

Require that all configuration files are owned by the current user; only needed if your config is somewhere unsafe like /tmp/.This is a boolean

interface certbot.interfaces.IInstaller[source]

Extends: certbot.interfaces.IPlugin

Generic Certbot Installer Interface.

Represents any server that an X509 certificate can be placed.

It is assumed that save() is the only method that finalizes a checkpoint. This is important to ensure that checkpoints are restored in a consistent manner if requested by the user or in case of an error.

Using certbot.reverter.Reverter to implement checkpoints, rollback, and recovery can dramatically simplify plugin development.

get_all_names()

Returns all names that may be authenticated.

Return type:
collections.Iterable [https://docs.python.org/2/library/collections.html#collections.Iterable] of str [https://docs.python.org/2/library/functions.html#str]

deploy_cert(domain, cert_path, key_path, chain_path, fullchain_path)

Deploy certificate.

Parameters:

domain (str [https://docs.python.org/2/library/functions.html#str]) – domain to deploy certificate file

cert_path (str [https://docs.python.org/2/library/functions.html#str]) – absolute path to the certificate file

key_path (str [https://docs.python.org/2/library/functions.html#str]) – absolute path to the private key file

chain_path (str [https://docs.python.org/2/library/functions.html#str]) – absolute path to the certificate chain file

fullchain_path (str [https://docs.python.org/2/library/functions.html#str]) – absolute path to the certificate fullchain file (cert plus chain)

Raises:

PluginError – when cert cannot be deployed

enhance(domain, enhancement, options=None)

Perform a configuration enhancement.

Parameters:

domain (str [https://docs.python.org/2/library/functions.html#str]) – domain for which to provide enhancement

enhancement (str [https://docs.python.org/2/library/functions.html#str]) – An enhancement as defined in ENHANCEMENTS

options – Flexible options parameter for enhancement. Check documentation of ENHANCEMENTS for expected options for each enhancement.

Raises:

PluginError – If Enhancement is not supported, or if an error occurs during the enhancement.

supported_enhancements()

Returns a collections.Iterable [https://docs.python.org/2/library/collections.html#collections.Iterable] of supported enhancements.

Returns: | supported enhancements which should be a subset of ENHANCEMENTS
Return type: | collections.Iterable [https://docs.python.org/2/library/collections.html#collections.Iterable] of str [https://docs.python.org/2/library/functions.html#str]

save(title=None, temporary=False)

Saves all changes to the configuration files.

Both title and temporary are needed because a save may be intended to be permanent, but the save is not ready to be a full checkpoint.

It is assumed that at most one checkpoint is finalized by this method. Additionally, if an exception is raised, it is assumed a new checkpoint was not finalized.

Parameters:

title (str [https://docs.python.org/2/library/functions.html#str]) – The title of the save. If a title is given, the configuration will be saved as a new checkpoint and put in a timestamped directory. title has no effect if temporary is true.

temporary (bool [https://docs.python.org/2/library/functions.html#bool]) – Indicates whether the changes made will be quickly reversed in the future (challenges)

Raises:

PluginError – when save is unsuccessful

rollback_checkpoints(rollback=1)

Revert rollback number of configuration checkpoints.

Raises:
PluginError – when configuration cannot be fully reverted

recovery_routine()

Revert configuration to most recent finalized checkpoint.

Remove all changes (temporary and permanent) that have not been finalized. This is useful to protect against crashes and other execution interruptions.

Raises:
errors.PluginError – If unable to recover the configuration

view_config_changes()

Display all of the LE config changes.

Raises:
PluginError – when config changes cannot be parsed

config_test()

Make sure the configuration is valid.

Raises:
MisconfigurationError – when the config is not in a usable state

restart()

Restart or refresh the server content.

Raises:
PluginError – when server cannot be restarted

interface certbot.interfaces.IDisplay[source]

Generic display.

notification(message, pause, wrap=True, force_interactive=False)

Displays a string message

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – Message to display

pause (bool [https://docs.python.org/2/library/functions.html#bool]) – Whether or not the application should pause for confirmation (if available)

wrap (bool [https://docs.python.org/2/library/functions.html#bool]) – Whether or not the application should wrap text

force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – True if it’s safe to prompt the user because it won’t cause any workflow regressions

menu(message, choices, ok_label='OK', cancel_label='Cancel', help_label='', default=None, cli_flag=None, force_interactive=False)

Displays a generic menu.

When not setting force_interactive=True, you must provide a default value.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – message to display

choices (list of tuple() [https://docs.python.org/2/library/functions.html#tuple] or str [https://docs.python.org/2/library/functions.html#str]) – choices

ok_label (str [https://docs.python.org/2/library/functions.html#str]) – label for OK button

cancel_label (str [https://docs.python.org/2/library/functions.html#str]) – label for Cancel button

help_label (str [https://docs.python.org/2/library/functions.html#str]) – label for Help button

default (int [https://docs.python.org/2/library/functions.html#int]) – default (non-interactive) choice from the menu

cli_flag (str [https://docs.python.org/2/library/functions.html#str]) – to automate choice from the menu, eg “–keep”

force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – True if it’s safe to prompt the user because it won’t cause any workflow regressions

Returns:

tuple of (code [https://docs.python.org/2/library/code.html#module-code], index) where code [https://docs.python.org/2/library/code.html#module-code] - str display exit code index - int index of the user’s selection

Raises:

errors.MissingCommandlineFlag – if called in non-interactive mode without a default set

input(message, default=None, cli_args=None, force_interactive=False)

Accept input from the user.

When not setting force_interactive=True, you must provide a default value.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – message to display to the user

default (str [https://docs.python.org/2/library/functions.html#str]) – default (non-interactive) response to prompt

force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – True if it’s safe to prompt the user because it won’t cause any workflow regressions

Returns:

tuple of (code [https://docs.python.org/2/library/code.html#module-code], input) where code [https://docs.python.org/2/library/code.html#module-code] - str display exit code input - str of the user’s input

Return type:

tuple [https://docs.python.org/2/library/functions.html#tuple]

Raises:

errors.MissingCommandlineFlag – if called in non-interactive mode without a default set

yesno(message, yes_label='Yes', no_label='No', default=None, cli_args=None, force_interactive=False)

Query the user with a yes/no question.

Yes and No label must begin with different letters.

When not setting force_interactive=True, you must provide a default value.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – question for the user

default (str [https://docs.python.org/2/library/functions.html#str]) – default (non-interactive) choice from the menu

cli_flag (str [https://docs.python.org/2/library/functions.html#str]) – to automate choice from the menu, eg “–redirect / –no-redirect”

force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – True if it’s safe to prompt the user because it won’t cause any workflow regressions

Returns:

True for “Yes”, False for “No”

Return type:

bool [https://docs.python.org/2/library/functions.html#bool]

Raises:

errors.MissingCommandlineFlag – if called in non-interactive mode without a default set

checklist(message, tags, default_state, default=None, cli_args=None, force_interactive=False)

Allow for multiple selections from a menu.

When not setting force_interactive=True, you must provide a default value.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – message to display to the user

tags (list) – where each is of type str [https://docs.python.org/2/library/functions.html#str] len(tags) > 0

default_status (bool [https://docs.python.org/2/library/functions.html#bool]) – If True, items are in a selected state by default.

default (str [https://docs.python.org/2/library/functions.html#str]) – default (non-interactive) state of the checklist

cli_flag (str [https://docs.python.org/2/library/functions.html#str]) – to automate choice from the menu, eg “–domains”

force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – True if it’s safe to prompt the user because it won’t cause any workflow regressions

Returns:

tuple of the form (code, list_tags) where code [https://docs.python.org/2/library/code.html#module-code] - int display exit code list_tags - list of str tags selected by the user

Return type:

tuple [https://docs.python.org/2/library/functions.html#tuple]

Raises:

errors.MissingCommandlineFlag – if called in non-interactive mode without a default set

directory_select(self, message, default=None, cli_flag=None, force_interactive=False)

Display a directory selection screen.

When not setting force_interactive=True, you must provide a default value.

Parameters:

message (str [https://docs.python.org/2/library/functions.html#str]) – prompt to give the user

default – the default value to return, if one exists, when using the NoninteractiveDisplay

cli_flag (str [https://docs.python.org/2/library/functions.html#str]) – option used to set this value with the CLI, if one exists, to be included in error messages given by NoninteractiveDisplay

force_interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – True if it’s safe to prompt the user because it won’t cause any workflow regressions

Returns:

tuple of the form (code [https://docs.python.org/2/library/code.html#module-code], string [https://docs.python.org/2/library/string.html#module-string]) where code [https://docs.python.org/2/library/code.html#module-code] - int display exit code string [https://docs.python.org/2/library/string.html#module-string] - input entered by the user

interface certbot.interfaces.IValidator[source]

Configuration validator.

certificate(cert, name, alt_host=None, port=443)

Verifies the certificate presented at name is cert

Parameters:

cert (OpenSSL.crypto.X509) – Expected certificate

name (str [https://docs.python.org/2/library/functions.html#str]) – Server’s domain name

alt_host (bytes) – Host to connect to instead of the IP address of host

port (int [https://docs.python.org/2/library/functions.html#int]) – Port to connect to

Returns:

True if the certificate was verified successfully

Return type:

bool [https://docs.python.org/2/library/functions.html#bool]

redirect(name, port=80, headers=None)

Verify redirect to HTTPS

Parameters:

name (str [https://docs.python.org/2/library/functions.html#str]) – Server’s domain name

port (int [https://docs.python.org/2/library/functions.html#int]) – Port to connect to

headers (dict [https://docs.python.org/2/library/stdtypes.html#dict]) – HTTP headers to include in request

Returns:

True if redirect is successfully enabled

Return type:

bool [https://docs.python.org/2/library/functions.html#bool]

hsts(name)

Verify HSTS header is enabled

Parameters: | name (str [https://docs.python.org/2/library/functions.html#str]) – Server’s domain name
Returns: | True if HSTS header is successfully enabled
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

ocsp_stapling(name)

Verify ocsp stapling for domain

Parameters: | name (str [https://docs.python.org/2/library/functions.html#str]) – Server’s domain name
Returns: | True if ocsp stapling is successfully enabled
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

interface certbot.interfaces.IReporter[source]

Interface to collect and display information to the user.

HIGH_PRIORITY

Used to denote high priority messages

MEDIUM_PRIORITY

Used to denote medium priority messages

LOW_PRIORITY

Used to denote low priority messages

add_message(self, msg, priority, on_crash=True)

Adds msg to the list of messages to be printed.

Parameters:

msg (str [https://docs.python.org/2/library/functions.html#str]) – Message to be displayed to the user.

priority (int [https://docs.python.org/2/library/functions.html#int]) – One of HIGH_PRIORITY, MEDIUM_PRIORITY, or LOW_PRIORITY.

on_crash (bool [https://docs.python.org/2/library/functions.html#bool]) – Whether or not the message should be printed if the program exits abnormally.

print_messages(self)

Prints messages to the user and clears the message queue.

certbot.plugins.common

Plugin common functions.

certbot.plugins.common.option_namespace(name)[source]

ArgumentParser options namespace (prefix of all options).

certbot.plugins.common.dest_namespace(name)[source]

ArgumentParser dest namespace (prefix of all destinations).

class certbot.plugins.common.Plugin(config, name)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

Generic plugin.

classmethod add_parser_arguments(add)[source]

Add plugin arguments to the CLI argument parser.

NOTE: If some of your flags interact with others, you can use cli.report_config_interaction to register this to ensure values are correctly saved/overridable during renewal.

Parameters:
add (callable [https://docs.python.org/2/library/functions.html#callable]) – Function that proxies calls to argparse.ArgumentParser.add_argument [https://docs.python.org/2/library/argparse.html#argparse.ArgumentParser.add_argument] prepending options with unique plugin name prefix.

classmethod inject_parser_options(parser, name)[source]

Inject parser options.

See inject_parser_options for docs.

option_namespace

ArgumentParser options namespace (prefix of all options).

option_name(name)[source]

Option name (include plugin namespace).

dest_namespace

ArgumentParser dest namespace (prefix of all destinations).

dest(var)[source]

Find a destination for given variable var.

conf(var)[source]

Find a configuration value for variable var.

class certbot.plugins.common.Addr(tup, ipv6=False)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

Represents an virtual host address.

Parameters:

addr (str [https://docs.python.org/2/library/functions.html#str]) – addr part of vhost address

port (str [https://docs.python.org/2/library/functions.html#str]) – port number or *, or “”

classmethod fromstring(str_addr)[source]

Initialize Addr from string.

normalized_tuple()[source]

Normalized representation of addr/port tuple

get_addr()[source]

Return addr part of Addr object.

get_port()[source]

Return port.

get_addr_obj(port)[source]

Return new address object with same addr and new port.

_normalize_ipv6(addr)[source]

Return IPv6 address in normalized form, helper function

get_ipv6_exploded()[source]

Return IPv6 in normalized form

_explode_ipv6(addr)[source]

Explode IPv6 address for comparison

class certbot.plugins.common.TLSSNI01(configurator)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

Abstract base for TLS-SNI-01 challenge performers

add_chall(achall, idx=None)[source]

Add challenge to TLSSNI01 object to perform at once.

Parameters:

achall (KeyAuthorizationAnnotatedChallenge) – Annotated TLSSNI01 challenge.

idx (int [https://docs.python.org/2/library/functions.html#int]) – index to challenge in a larger array

get_cert_path(achall)[source]

Returns standardized name for challenge certificate.

Parameters: | achall (KeyAuthorizationAnnotatedChallenge) – Annotated tls-sni-01 challenge.
Returns: | certificate file name
Return type: | str [https://docs.python.org/2/library/functions.html#str]

get_key_path(achall)[source]

Get standardized path to challenge key.

_setup_challenge_cert(achall, cert_key=None)[source]

Generate and write out challenge certificate.

certbot.plugins.common.install_ssl_options_conf(options_ssl, options_ssl_digest, mod_ssl_conf_src, all_ssl_options_hashes)[source]

Copy Certbot’s SSL options file into the system’s config dir if required.

Parameters:

options_ssl (str [https://docs.python.org/2/library/functions.html#str]) – destination path for file containing ssl options

options_ssl_digest (str [https://docs.python.org/2/library/functions.html#str]) – path to save a digest of options_ssl in

mod_ssl_conf_src (str [https://docs.python.org/2/library/functions.html#str]) – path to file containing ssl options found in distribution

all_ssl_options_hashes (list) – hashes of every released version of options_ssl

certbot.plugins.common.dir_setup(test_dir, pkg)[source]

Setup the directories necessary for the configurator.

certbot.plugins.disco

Utilities for plugins discovery and selection.

class certbot.plugins.disco.PluginEntryPoint(entry_point)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

Plugin entry point.

PREFIX_FREE_DISTRIBUTIONS = ['certbot', 'certbot-apache', 'certbot-dns-cloudflare', 'certbot-dns-cloudxns', 'certbot-dns-digitalocean', 'certbot-dns-dnsimple', 'certbot-dns-google', 'certbot-dns-nsone', 'certbot-dns-route53', 'certbot-nginx']

Distributions for which prefix will be omitted.

classmethod entry_point_to_plugin_name(entry_point)[source]

Unique plugin name for an entry_point

description

Description of the plugin.

description_with_name

Description with name. Handy for UI.

long_description

Long description of the plugin.

hidden

Should this plugin be hidden from UI?

ifaces(*ifaces_groups)[source]

Does plugin implements specified interface groups?

initialized

Has the plugin been initialized already?

init(config=None)[source]

Memoized plugin initialization.

verify(ifaces)[source]

Verify that the plugin conforms to the specified interfaces.

prepared

Has the plugin been prepared already?

prepare()[source]

Memoized plugin preparation.

misconfigured

Is plugin misconfigured?

problem

Return the Exception raised during plugin setup, or None if all is well

available

Is plugin available, i.e. prepared or misconfigured?

class certbot.plugins.disco.PluginsRegistry(plugins)[source]

Bases: _abcoll.Mapping

Plugins registry.

classmethod find_all()[source]

Find plugins using setuptools entry points.

init(config)[source]

Initialize all plugins in the registry.

filter(pred)[source]

Filter plugins based on predicate.

visible()[source]

Filter plugins based on visibility.

ifaces(*ifaces_groups)[source]

Filter plugins based on interfaces.

verify(ifaces)[source]

Filter plugins based on verification.

prepare()[source]

Prepare all plugins in the registry.

available()[source]

Filter plugins based on availability.

find_init(plugin)[source]

Find an initialized plugin.

This is particularly useful for finding a name for the plugin (although IPluginFactory.__call__ takes name as one of the arguments, IPlugin.name is not part of the interface):

# plugin is an instance providing IPlugin, initialized
# somewhere else in the code
plugin_registry.find_init(plugin).name

Returns None if plugin is not found in the registry.

certbot.plugins.dns_common

Common code for DNS Authenticator Plugins.

class certbot.plugins.dns_common.DNSAuthenticator(config, name)[source]

Bases: certbot.plugins.common.Plugin

Base class for DNS Authenticators

_setup_credentials()[source]

Establish credentials, prompting if necessary.

_perform(domain, validation_domain_name, validation)[source]

Performs a dns-01 challenge by creating a DNS TXT record.

Parameters:

domain (str [https://docs.python.org/2/library/functions.html#str]) – The domain being validated.

validation_domain_name (str [https://docs.python.org/2/library/functions.html#str]) – The validation record domain name.

validation (str [https://docs.python.org/2/library/functions.html#str]) – The validation record content.

Raises:

errors.PluginError – If the challenge cannot be performed

_cleanup(domain, validation_domain_name, validation)[source]

Deletes the DNS TXT record which would have been created by _perform_achall.

Fails gracefully if no such record exists.

Parameters:

domain (str [https://docs.python.org/2/library/functions.html#str]) – The domain being validated.

validation_domain_name (str [https://docs.python.org/2/library/functions.html#str]) – The validation record domain name.

validation (str [https://docs.python.org/2/library/functions.html#str]) – The validation record content.

_configure(key, label)[source]

Ensure that a configuration value is available.

If necessary, prompts the user and stores the result.

Parameters:

key (str [https://docs.python.org/2/library/functions.html#str]) – The configuration key.

label (str [https://docs.python.org/2/library/functions.html#str]) – The user-friendly label for this piece of information.

_configure_file(key, label, validator=None)[source]

Ensure that a configuration value is available for a path.

If necessary, prompts the user and stores the result.

Parameters:

key (str [https://docs.python.org/2/library/functions.html#str]) – The configuration key.

label (str [https://docs.python.org/2/library/functions.html#str]) – The user-friendly label for this piece of information.

_configure_credentials(key, label, required_variables=None)[source]

As _configure_file, but for a credential configuration file.

If necessary, prompts the user and stores the result.

Always stores absolute paths to avoid issues during renewal.

Parameters:

key (str [https://docs.python.org/2/library/functions.html#str]) – The configuration key.

label (str [https://docs.python.org/2/library/functions.html#str]) – The user-friendly label for this piece of information.

required_variables (dict [https://docs.python.org/2/library/stdtypes.html#dict]) – Map of variable which must be present to error to display.

static _prompt_for_data(label)[source]

Prompt the user for a piece of information.

Parameters: | label (str [https://docs.python.org/2/library/functions.html#str]) – The user-friendly label for this piece of information.
Returns: | The user’s response (guaranteed non-empty).
Return type: | str [https://docs.python.org/2/library/functions.html#str]

static _prompt_for_file(label, validator=None)[source]

Prompt the user for a path.

Parameters:

label (str [https://docs.python.org/2/library/functions.html#str]) – The user-friendly label for the file.

validator (callable [https://docs.python.org/2/library/functions.html#callable]) – A method which will be called to validate the supplied input after it has been validated to be a non-empty path to an existing file. Should throw a PluginError to indicate any issue.

Returns:

The user’s response (guaranteed to exist).

Return type:

str [https://docs.python.org/2/library/functions.html#str]

class certbot.plugins.dns_common.CredentialsConfiguration(filename, mapper=<function <lambda>>)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

Represents a user-supplied filed which stores API credentials.

require(required_variables)[source]

Ensures that the supplied set of variables are all present in the file.

Parameters: | required_variables (dict [https://docs.python.org/2/library/stdtypes.html#dict]) – Map of variable which must be present to error to display.
Raises: | errors.PluginError – If one or more are missing.

conf(var)[source]

Find a configuration value for variable var, as transformed by mapper.

Parameters: | var (str [https://docs.python.org/2/library/functions.html#str]) – The variable to get.
Returns: | The value of the variable.
Return type: | str [https://docs.python.org/2/library/functions.html#str]

certbot.plugins.dns_common.validate_file(filename)[source]

Ensure that the specified file exists.

certbot.plugins.dns_common.validate_file_permissions(filename)[source]

Ensure that the specified file exists and warn about unsafe permissions.

certbot.plugins.dns_common.base_domain_name_guesses(domain)[source]

Return a list of progressively less-specific domain names.

One of these will probably be the domain name known to the DNS provider.

Example:

>>> base_domain_name_guesses('foo.bar.baz.example.com')
['foo.bar.baz.example.com', 'bar.baz.example.com', 'baz.example.com', 'example.com', 'com']

Parameters: | domain (str [https://docs.python.org/2/library/functions.html#str]) – The domain for which to return guesses.
Returns: | The a list of less specific domain names.
Return type: | list

certbot.plugins.dns_common_lexicon

Common code for DNS Authenticator Plugins built on Lexicon.

class certbot.plugins.dns_common_lexicon.LexiconClient[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

Encapsulates all communication with a DNS provider via Lexicon.

add_txt_record(domain, record_name, record_content)[source]

Add a TXT record using the supplied information.

Parameters:

domain (str [https://docs.python.org/2/library/functions.html#str]) – The domain to use to look up the managed zone.

record_name (str [https://docs.python.org/2/library/functions.html#str]) – The record name (typically beginning with ‘_acme-challenge.’).

record_content (str [https://docs.python.org/2/library/functions.html#str]) – The record content (typically the challenge validation).

Raises:

errors.PluginError – if an error occurs communicating with the DNS Provider API

del_txt_record(domain, record_name, record_content)[source]

Delete a TXT record using the supplied information.

Parameters:

domain (str [https://docs.python.org/2/library/functions.html#str]) – The domain to use to look up the managed zone.

record_name (str [https://docs.python.org/2/library/functions.html#str]) – The record name (typically beginning with ‘_acme-challenge.’).

record_content (str [https://docs.python.org/2/library/functions.html#str]) – The record content (typically the challenge validation).

Raises:

errors.PluginError – if an error occurs communicating with the DNS Provider API

_find_domain_id(domain)[source]

Find the domain_id for a given domain.

Parameters: | domain (str [https://docs.python.org/2/library/functions.html#str]) – The domain for which to find the domain_id.
Raises: | errors.PluginError – if the domain_id cannot be found.

certbot.plugins.manual

Manual authenticator plugin

class certbot.plugins.manual.Authenticator(*args, **kwargs)[source]

Bases: certbot.plugins.common.Plugin

Manual authenticator

This plugin allows the user to perform the domain validation challenge(s) themselves. This either be done manually by the user or through shell scripts provided to Certbot.

certbot.plugins.standalone

Standalone Authenticator.

class certbot.plugins.standalone.ServerManager(certs, http_01_resources)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

Standalone servers manager.

Manager for ACMEServer and ACMETLSServer instances.

certs and http_01_resources correspond to acme.crypto_util.SSLSocket.certs and acme.crypto_util.SSLSocket.http_01_resources respectively. All created servers share the same certificates and resources, so if you’re running both TLS and non-TLS instances, HTTP01 handlers will serve the same URLs!

run(port, challenge_type, listenaddr='')[source]

Run ACME server on specified port.

This method is idempotent, i.e. all calls with the same pair of (port, challenge_type) will reuse the same server.

Parameters:

port (int [https://docs.python.org/2/library/functions.html#int]) – Port to run the server on.

challenge_type – Subclass of acme.challenges.Challenge [https://acme-python.readthedocs.io/en/latest/api/challenges.html#acme.challenges.Challenge], either acme.challenge.HTTP01 or acme.challenges.TLSSNI01 [https://acme-python.readthedocs.io/en/latest/api/challenges.html#acme.challenges.TLSSNI01].

listenaddr (str [https://docs.python.org/2/library/functions.html#str]) – (optional) The address to listen on. Defaults to all addrs.

Returns:

DualNetworkedServers instance.

Return type:

ACMEServerMixin

stop(port)[source]

Stop ACME server running on the specified port.

Parameters:
port (int [https://docs.python.org/2/library/functions.html#int]) –

running()[source]

Return all running instances.

Once the server is stopped using stop, it will not be returned.

Returns: | Mapping from port to servers.
Return type: | tuple [https://docs.python.org/2/library/functions.html#tuple]

class certbot.plugins.standalone.SupportedChallengesAction(option_strings, dest, nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None)[source]

Bases: argparse.Action [https://docs.python.org/2/library/argparse.html#argparse.Action]

Action class for parsing standalone_supported_challenges.

_convert_and_validate(data)[source]

Validate the value of supported challenges provided by the user.

References to “dvsni” are automatically converted to “tls-sni-01”.

Parameters: | data (str [https://docs.python.org/2/library/functions.html#str]) – comma delimited list of challenge types
Returns: | validated and converted list of challenge types
Return type: | str [https://docs.python.org/2/library/functions.html#str]

class certbot.plugins.standalone.Authenticator(*args, **kwargs)[source]

Bases: certbot.plugins.common.Plugin

Standalone Authenticator.

This authenticator creates its own ephemeral TCP listener on the necessary port in order to respond to incoming tls-sni-01 and http-01 challenges from the certificate authority. Therefore, it does not rely on any existing server program.

supported_challenges

Challenges supported by this plugin.

certbot.plugins.util

Plugin utilities.

certbot.plugins.util.path_surgery(cmd)[source]

Attempt to perform PATH surgery to find cmd

Mitigates https://github.com/certbot/certbot/issues/1833

Parameters: | cmd (str [https://docs.python.org/2/library/functions.html#str]) – the command that is being searched for in the PATH
Returns: | True if the operation succeeded, False otherwise

certbot.plugins.webroot

Webroot plugin.

class certbot.plugins.webroot.Authenticator(*args, **kwargs)[source]

Bases: certbot.plugins.common.Plugin

Webroot Authenticator.

class certbot.plugins.webroot._WebrootMapAction(option_strings, dest, nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None)[source]

Bases: argparse.Action [https://docs.python.org/2/library/argparse.html#argparse.Action]

Action class for parsing webroot_map.

class certbot.plugins.webroot._WebrootPathAction(*args, **kwargs)[source]

Bases: argparse.Action [https://docs.python.org/2/library/argparse.html#argparse.Action]

Action class for parsing webroot_path.

certbot.plugins.webroot._validate_webroot(webroot_path)[source]

Validates and returns the absolute path of webroot_path.

Parameters: | webroot_path (str [https://docs.python.org/2/library/functions.html#str]) – path to the webroot directory
Returns: | absolute path of webroot_path
Return type: | str [https://docs.python.org/2/library/functions.html#str]

certbot.reporter

Collects and displays information to the user.

class certbot.reporter.Reporter(config)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

Collects and displays information to the user.

Variables:
messages (queue.PriorityQueue) – Messages to be displayed to the user.

HIGH_PRIORITY = 0

High priority constant. See add_message.

MEDIUM_PRIORITY = 1

Medium priority constant. See add_message.

LOW_PRIORITY = 2

Low priority constant. See add_message.

_msg_type

alias of ReporterMsg

add_message(msg, priority, on_crash=True)[source]

Adds msg to the list of messages to be printed.

Parameters:

msg (str [https://docs.python.org/2/library/functions.html#str]) – Message to be displayed to the user.

priority (int [https://docs.python.org/2/library/functions.html#int]) – One of HIGH_PRIORITY, MEDIUM_PRIORITY, or LOW_PRIORITY.

on_crash (bool [https://docs.python.org/2/library/functions.html#bool]) – Whether or not the message should be printed if the program exits abnormally.

print_messages()[source]

Prints messages to the user and clears the message queue.

If there is an unhandled exception, only messages for which on_crash is True are printed.

certbot.reverter

Reverter class saves configuration checkpoints and allows for recovery.

class certbot.reverter.Reverter(config)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

Reverter Class - save and revert configuration checkpoints.

This class can be used by the plugins, especially Installers, to undo changes made to the user’s system. Modifications to files and commands to do undo actions taken by the plugin should be registered with this class before the action is taken.

Once a change has been registered with this class, there are three states the change can be in. First, the change can be a temporary change. This should be used for changes that will soon be reverted, such as config changes for the purpose of solving a challenge. Changes are added to this state through calls to add_to_temp_checkpoint() and reverted when revert_temporary_config() or recovery_routine() is called.

The second state a change can be in is in progress. These changes are not temporary, however, they also have not been finalized in a checkpoint. A change must become in progress before it can be finalized. Changes are added to this state through calls to add_to_checkpoint() and reverted when recovery_routine() is called.

The last state a change can be in is finalized in a checkpoint. A change is put into this state by first becoming an in progress change and then calling finalize_checkpoint(). Changes in this state can be reverted through calls to rollback_checkpoints().

As a final note, creating new files and registering undo commands are handled specially and use the methods register_file_creation() and register_undo_command() respectively. Both of these methods can be used to create either temporary or in progress changes.

Note

Consider moving everything over to CSV format.

Parameters:
config (certbot.interfaces.IConfig) – Configuration.

revert_temporary_config()[source]

Reload users original configuration files after a temporary save.

This function should reinstall the users original configuration files for all saves with temporary=True

Raises:
ReverterError – when unable to revert config

rollback_checkpoints(rollback=1)[source]

Revert ‘rollback’ number of configuration checkpoints.

Parameters: | rollback (int [https://docs.python.org/2/library/functions.html#int]) – Number of checkpoints to reverse. A str num will be cast to an integer. So “2” is also acceptable.
Raises: | ReverterError – if there is a problem with the input or if the function is unable to correctly revert the configuration checkpoints

view_config_changes(for_logging=False, num=None)[source]

Displays all saved checkpoints.

All checkpoints are printed by certbot.interfaces.IDisplay.notification().

Todo

Decide on a policy for error handling, OSError IOError...

Raises:
errors.ReverterError – If invalid directory structure.

add_to_temp_checkpoint(save_files, save_notes)[source]

Add files to temporary checkpoint.

Parameters:

save_files (set [https://docs.python.org/2/library/stdtypes.html#set]) – set of filepaths to save

save_notes (str [https://docs.python.org/2/library/functions.html#str]) – notes about changes during the save

add_to_checkpoint(save_files, save_notes)[source]

Add files to a permanent checkpoint.

Parameters:

save_files (set [https://docs.python.org/2/library/stdtypes.html#set]) – set of filepaths to save

save_notes (str [https://docs.python.org/2/library/functions.html#str]) – notes about changes during the save

_add_to_checkpoint_dir(cp_dir, save_files, save_notes)[source]

Add save files to checkpoint directory.

Parameters:

cp_dir (str [https://docs.python.org/2/library/functions.html#str]) – Checkpoint directory filepath

save_files (set [https://docs.python.org/2/library/stdtypes.html#set]) – set of files to save

save_notes (str [https://docs.python.org/2/library/functions.html#str]) – notes about changes made during the save

Raises:

IOError – if unable to open cp_dir + FILEPATHS file

ReverterError – if unable to add checkpoint

_read_and_append(filepath)[source]

Reads the file lines and returns a file obj.

Read the file returning the lines, and a pointer to the end of the file.

_recover_checkpoint(cp_dir)[source]

Recover a specific checkpoint.

Recover a specific checkpoint provided by cp_dir Note: this function does not reload augeas.

Parameters: | cp_dir (str [https://docs.python.org/2/library/functions.html#str]) – checkpoint directory file path
Raises: | errors.ReverterError – If unable to recover checkpoint

_run_undo_commands(filepath)[source]

Run all commands in a file.

_check_tempfile_saves(save_files)[source]

Verify save isn’t overwriting any temporary files.

Parameters: | save_files (set [https://docs.python.org/2/library/stdtypes.html#set]) – Set of files about to be saved.
Raises: | certbot.errors.ReverterError – when save is attempting to overwrite a temporary file.

register_file_creation(temporary, *files)[source]

Register the creation of all files during certbot execution.

Call this method before writing to the file to make sure that the file will be cleaned up if the program exits unexpectedly. (Before a save occurs)

Parameters:

temporary (bool [https://docs.python.org/2/library/functions.html#bool]) – If the file creation registry is for a temp or permanent save.

*files – file paths (str) to be registered

Raises:

certbot.errors.ReverterError – If call does not contain necessary parameters or if the file creation is unable to be registered.

register_undo_command(temporary, command)[source]

Register a command to be run to undo actions taken.

Warning

This function does not enforce order of operations in terms of file modification vs. command registration. All undo commands are run first before all normal files are reverted to their previous state. If you need to maintain strict order, you may create checkpoints before and after the the command registration. This function may be improved in the future based on demand.

Parameters:

temporary (bool [https://docs.python.org/2/library/functions.html#bool]) – Whether the command should be saved in the IN_PROGRESS or TEMPORARY checkpoints.

command (list of str) – Command to be run.

_get_cp_dir(temporary)[source]

Return the proper reverter directory.

recovery_routine()[source]

Revert configuration to most recent finalized checkpoint.

Remove all changes (temporary and permanent) that have not been finalized. This is useful to protect against crashes and other execution interruptions.

Raises:
errors.ReverterError – If unable to recover the configuration

_remove_contained_files(file_list)[source]

Erase all files contained within file_list.

Parameters: | file_list (str [https://docs.python.org/2/library/functions.html#str]) – file containing list of file paths to be deleted
Returns: | Success
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]
Raises: | certbot.errors.ReverterError – If all files within file_list cannot be removed

finalize_checkpoint(title)[source]

Finalize the checkpoint.

Timestamps and permanently saves all changes made through the use of add_to_checkpoint() and register_file_creation()

Parameters: | title (str [https://docs.python.org/2/library/functions.html#str]) – Title describing checkpoint
Raises: | certbot.errors.ReverterError – when the checkpoint is not able to be finalized.

_checkpoint_timestamp()[source]

Determine the timestamp of the checkpoint, enforcing monotonicity.

_timestamp_progress_dir()[source]

Timestamp the checkpoint.

certbot.storage

Renewable certificates storage.

certbot.storage.renewal_conf_files(config)[source]

Build a list of all renewal configuration files.

Parameters: | config (certbot.interfaces.IConfig) – Configuration object
Returns: | list of renewal configuration files
Return type: | list of str [https://docs.python.org/2/library/functions.html#str]

certbot.storage.renewal_file_for_certname(config, certname)[source]

Return /path/to/certname.conf in the renewal conf directory

certbot.storage.config_with_defaults(config=None)[source]

Merge supplied config, if provided, on top of builtin defaults.

certbot.storage.add_time_interval(base_time, interval, textparser=<parsedatetime.Calendar object>)[source]

Parse the time specified time interval, and add it to the base_time

The interval can be in the English-language format understood by parsedatetime, e.g., ‘10 days’, ‘3 weeks’, ‘6 months’, ‘9 hours’, or a sequence of such intervals like ‘6 months 1 week’ or ‘3 days 12 hours’. If an integer is found with no associated unit, it is interpreted by default as a number of days.

Parameters:

base_time (datetime.datetime [https://docs.python.org/2/library/datetime.html#datetime.datetime]) – The time to be added with the interval.

interval (str [https://docs.python.org/2/library/functions.html#str]) – The time interval to parse.

Returns:

The base_time plus the interpretation of the time interval.

Return type:

datetime.datetime [https://docs.python.org/2/library/datetime.html#datetime.datetime]

certbot.storage.write_renewal_config(o_filename, n_filename, archive_dir, target, relevant_data)[source]

Writes a renewal config file with the specified name and values.

Parameters:

o_filename (str [https://docs.python.org/2/library/functions.html#str]) – Absolute path to the previous version of config file

n_filename (str [https://docs.python.org/2/library/functions.html#str]) – Absolute path to the new destination of config file

archive_dir (str [https://docs.python.org/2/library/functions.html#str]) – Absolute path to the archive directory

target (dict [https://docs.python.org/2/library/stdtypes.html#dict]) – Maps ALL_FOUR to their symlink paths

relevant_data (dict [https://docs.python.org/2/library/stdtypes.html#dict]) – Renewal configuration options to save

Returns:

Configuration object for the new config file

Return type:

configobj.ConfigObj

certbot.storage.rename_renewal_config(prev_name, new_name, cli_config)[source]

Renames cli_config.certname’s config to cli_config.new_certname.

Parameters:
cli_config (NamespaceConfig) – parsed command line arguments

certbot.storage.update_configuration(lineagename, archive_dir, target, cli_config)[source]

Modifies lineagename’s config to contain the specified values.

Parameters:

lineagename (str [https://docs.python.org/2/library/functions.html#str]) – Name of the lineage being modified

archive_dir (str [https://docs.python.org/2/library/functions.html#str]) – Absolute path to the archive directory

target (dict [https://docs.python.org/2/library/stdtypes.html#dict]) – Maps ALL_FOUR to their symlink paths

cli_config (NamespaceConfig) – parsed command line arguments

Returns:

Configuration object for the updated config file

Return type:

configobj.ConfigObj

certbot.storage.get_link_target(link)[source]

Get an absolute path to the target of link.

Parameters: | link (str [https://docs.python.org/2/library/functions.html#str]) – Path to a symbolic link
Returns: | Absolute path to the target of link
Return type: | str [https://docs.python.org/2/library/functions.html#str]

certbot.storage._relevant(option)[source]

Is this option one that could be restored for future renewal purposes? :param str option: the name of the option

Return type:
bool [https://docs.python.org/2/library/functions.html#bool]

certbot.storage.relevant_values(all_values)[source]

Return a new dict containing only items relevant for renewal.

Parameters: | all_values (dict [https://docs.python.org/2/library/stdtypes.html#dict]) – The original values.
Returns: | A new dictionary containing items that can be used in renewal.
Rtype dict: |

certbot.storage.lineagename_for_filename(config_filename)[source]

Returns the lineagename for a configuration filename.

certbot.storage.renewal_filename_for_lineagename(config, lineagename)[source]

Returns the lineagename for a configuration filename.

certbot.storage._relpath_from_file(archive_dir, from_file)[source]

Path to a directory from a file

certbot.storage._full_archive_path(config_obj, cli_config, lineagename)[source]

Returns the full archive path for a lineagename

Uses cli_config to determine archive path if not available from config_obj.

Parameters:

config_obj (configobj.ConfigObj) – Renewal conf file contents (can be None)

cli_config (configuration.NamespaceConfig) – Main config file

lineagename (str [https://docs.python.org/2/library/functions.html#str]) – Certificate name

certbot.storage._full_live_path(cli_config, lineagename)[source]

Returns the full default live path for a lineagename

certbot.storage.delete_files(config, certname)[source]

Delete all files related to the certificate.

If some files are not found, ignore them and continue.

class certbot.storage.RenewableCert(config_filename, cli_config, update_symlinks=False)[source]

Bases: object [https://docs.python.org/2/library/functions.html#object]

Renewable certificate.

Represents a lineage of certificates that is under the management of Certbot, indicated by the existence of an associated renewal configuration file.

Note that the notion of “current version” for a lineage is maintained on disk in the structure of symbolic links, and is not explicitly stored in any instance variable in this object. The RenewableCert object is able to determine information about the current (or other) version by accessing data on disk, but does not inherently know any of this information except by examining the symbolic links as needed. The instance variables mentioned below point to symlinks that reflect the notion of “current version” of each managed object, and it is these paths that should be used when configuring servers to use the certificate managed in a lineage. These paths are normally within the “live” directory, and their symlink targets – the actual cert files – are normally found within the “archive” directory.

Variables:

cert (str [https://docs.python.org/2/library/functions.html#str]) – The path to the symlink representing the current version of the certificate managed by this lineage.

privkey (str [https://docs.python.org/2/library/functions.html#str]) – The path to the symlink representing the current version of the private key managed by this lineage.

chain (str [https://docs.python.org/2/library/functions.html#str]) – The path to the symlink representing the current version of the chain managed by this lineage.

fullchain (str [https://docs.python.org/2/library/functions.html#str]) – The path to the symlink representing the current version of the fullchain (combined chain and cert) managed by this lineage.

configuration (configobj.ConfigObj) – The renewal configuration options associated with this lineage, obtained from parsing the renewal configuration file and/or systemwide defaults.

key_path

Duck type for self.privkey

cert_path

Duck type for self.cert

chain_path

Duck type for self.chain

fullchain_path

Duck type for self.fullchain

target_expiry

The current target certificate’s expiration datetime

Returns: | Expiration datetime of the current target certificate
Return type: | datetime.datetime [https://docs.python.org/2/library/datetime.html#datetime.datetime]

archive_dir

Returns the default or specified archive directory

relative_archive_dir(from_file)[source]

Returns the default or specified archive directory as a relative path

Used for creating symbolic links.

is_test_cert

Returns true if this is a test cert from a staging server.

_check_symlinks()[source]

Raises an exception if a symlink doesn’t exist

_update_symlinks()[source]

Updates symlinks to use archive_dir

_consistent()[source]

Are the files associated with this lineage self-consistent?

Returns: | Whether the files stored in connection with this lineage appear to be correct and consistent with one another.
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

_fix()[source]

Attempt to fix defects or inconsistencies in this lineage.

Todo

Currently unimplemented.

_previous_symlinks()[source]

Returns the kind and path of all symlinks used in recovery.

Returns: | list of (kind, symlink) tuples
Return type: | list

_fix_symlinks()[source]

Fixes symlinks in the event of an incomplete version update.

If there is no problem with the current symlinks, this function has no effect.

current_target(kind)[source]

Returns full path to which the specified item currently points.

Parameters: | kind (str [https://docs.python.org/2/library/functions.html#str]) – the lineage member item (“cert”, “privkey”, “chain”, or “fullchain”)
Returns: | The path to the current version of the specified member.
Return type: | str [https://docs.python.org/2/library/functions.html#str] or None [https://docs.python.org/2/library/constants.html#None]

current_version(kind)[source]

Returns numerical version of the specified item.

For example, if kind is “chain” and the current chain link points to a file named “chain7.pem”, returns the integer 7.

Parameters: | kind (str [https://docs.python.org/2/library/functions.html#str]) – the lineage member item (“cert”, “privkey”, “chain”, or “fullchain”)
Returns: | the current version of the specified member.
Return type: | int [https://docs.python.org/2/library/functions.html#int]

version(kind, version)[source]

The filename that corresponds to the specified version and kind.

Warning

The specified version may not exist in this lineage. There is no guarantee that the file path returned by this method actually exists.

Parameters:

kind (str [https://docs.python.org/2/library/functions.html#str]) – the lineage member item (“cert”, “privkey”, “chain”, or “fullchain”)

version (int [https://docs.python.org/2/library/functions.html#int]) – the desired version

Returns:

The path to the specified version of the specified member.

Return type:

str [https://docs.python.org/2/library/functions.html#str]

available_versions(kind)[source]

Which alternative versions of the specified kind of item exist?

The archive directory where the current version is stored is consulted to obtain the list of alternatives.

Parameters: | kind (str [https://docs.python.org/2/library/functions.html#str]) – the lineage member item ( cert, privkey, chain, or fullchain)
Returns: | all of the version numbers that currently exist
Return type: | list of int [https://docs.python.org/2/library/functions.html#int]

newest_available_version(kind)[source]

Newest available version of the specified kind of item?

Parameters: | kind (str [https://docs.python.org/2/library/functions.html#str]) – the lineage member item (cert, privkey, chain, or fullchain)
Returns: | the newest available version of this member
Return type: | int [https://docs.python.org/2/library/functions.html#int]

latest_common_version()[source]

Newest version for which all items are available?

Returns: | the newest available version for which all members (cert, ``privkey, chain, and fullchain) exist
Return type: | int [https://docs.python.org/2/library/functions.html#int]

next_free_version()[source]

Smallest version newer than all full or partial versions?

Returns: | the smallest version number that is larger than any version of any item currently stored in this lineage
Return type: | int [https://docs.python.org/2/library/functions.html#int]

ensure_deployed()[source]

Make sure we’ve deployed the latest version.

Returns: | False if a change was needed, True otherwise
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

May need to recover from rare interrupted / crashed states.

has_pending_deployment()[source]

Is there a later version of all of the managed items?

Returns: | True if there is a complete version of this lineage with a larger version number than the current version, and False otherwise
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

_update_link_to(kind, version)[source]

Make the specified item point at the specified version.

(Note that this method doesn’t verify that the specified version exists.)

Parameters:

kind (str [https://docs.python.org/2/library/functions.html#str]) – the lineage member item (“cert”, “privkey”, “chain”, or “fullchain”)

version (int [https://docs.python.org/2/library/functions.html#int]) – the desired version

update_all_links_to(version)[source]

Change all member objects to point to the specified version.

Parameters:
version (int [https://docs.python.org/2/library/functions.html#int]) – the desired version

names(version=None)[source]

What are the subject names of this certificate?

(If no version is specified, use the current version.)

Parameters: | version (int [https://docs.python.org/2/library/functions.html#int]) – the desired version number
Returns: | the subject names
Return type: | list of str [https://docs.python.org/2/library/functions.html#str]
Raises: | CertStorageError – if could not find cert file.

autodeployment_is_enabled()[source]

Is automatic deployment enabled for this cert?

If autodeploy is not specified, defaults to True.

Returns: | True if automatic deployment is enabled
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

should_autodeploy(interactive=False)[source]

Should this lineage now automatically deploy a newer version?

This is a policy question and does not only depend on whether there is a newer version of the cert. (This considers whether autodeployment is enabled, whether a relevant newer version exists, and whether the time interval for autodeployment has been reached.)

Parameters: | interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – set to True to examine the question regardless of whether the renewal configuration allows automated deployment (for interactive use). Default False.
Returns: | whether the lineage now ought to autodeploy an existing newer cert version
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

ocsp_revoked(version=None)[source]

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.

Parameters: | version (int [https://docs.python.org/2/library/functions.html#int]) – the desired version number
Returns: | whether the certificate is or will be revoked
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

autorenewal_is_enabled()[source]

Is automatic renewal enabled for this cert?

If autorenew is not specified, defaults to True.

Returns: | True if automatic renewal is enabled
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

should_autorenew(interactive=False)[source]

Should we now try to autorenew the most recent cert version?

This is a policy question and does not only depend on whether the cert is expired. (This considers whether autorenewal is enabled, whether the cert is revoked, and whether the time interval for autorenewal has been reached.)

Note that this examines the numerically most recent cert version, not the currently deployed version.

Parameters: | interactive (bool [https://docs.python.org/2/library/functions.html#bool]) – set to True to examine the question regardless of whether the renewal configuration allows automated renewal (for interactive use). Default False.
Returns: | whether an attempt should now be made to autorenew the most current cert version in this lineage
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

classmethod new_lineage(lineagename, cert, privkey, chain, cli_config)[source]

Create a new certificate lineage.

Attempts to create a certificate lineage – enrolled for potential future renewal – with the (suggested) lineage name lineagename, and the associated cert, privkey, and chain (the associated fullchain will be created automatically). Optional configurator and renewalparams record the configuration that was originally used to obtain this cert, so that it can be reused later during automated renewal.

Returns a new RenewableCert object referring to the created lineage. (The actual lineage name, as well as all the relevant file paths, will be available within this object.)

Parameters:

lineagename (str [https://docs.python.org/2/library/functions.html#str]) – the suggested name for this lineage (normally the current cert’s first subject DNS name)

cert (str [https://docs.python.org/2/library/functions.html#str]) – the initial certificate version in PEM format

privkey (str [https://docs.python.org/2/library/functions.html#str]) – the private key in PEM format

chain (str [https://docs.python.org/2/library/functions.html#str]) – the certificate chain in PEM format

cli_config (NamespaceConfig) – parsed command line arguments

Returns:

the newly-created RenewalCert object

Return type:

storage.renewableCert

save_successor(prior_version, new_cert, new_privkey, new_chain, cli_config)[source]

Save new cert and chain as a successor of a prior version.

Returns the new version number that was created.

Note

this function does NOT update links to deploy this version

Parameters:

prior_version (int [https://docs.python.org/2/library/functions.html#int]) – the old version to which this version is regarded as a successor (used to choose a privkey, if the key has not changed, but otherwise this information is not permanently recorded anywhere)

new_cert (bytes) – the new certificate, in PEM format

new_privkey (bytes) – the new private key, in PEM format, or None, if the private key has not changed

new_chain (bytes) – the new chain, in PEM format

cli_config (NamespaceConfig) – parsed command line arguments

Returns:

the new version number that was created

Return type:

int [https://docs.python.org/2/library/functions.html#int]

certbot.util

Utilities for all Certbot.

class certbot.util.Key(file, pem)

Bases: tuple

_asdict()

Return a new OrderedDict which maps field names to their values

classmethod _make(iterable, new=<built-in method __new__ of type object at 0x93b740>, len=<built-in function len>)

Make a new Key object from a sequence or iterable

_replace(_self, **kwds)

Return a new Key object replacing specified fields with new values

file

Alias for field number 0

pem

Alias for field number 1

class certbot.util.CSR(file, data, form)

Bases: tuple

_asdict()

Return a new OrderedDict which maps field names to their values

classmethod _make(iterable, new=<built-in method __new__ of type object at 0x93b740>, len=<built-in function len>)

Make a new CSR object from a sequence or iterable

_replace(_self, **kwds)

Return a new CSR object replacing specified fields with new values

data

Alias for field number 1

file

Alias for field number 0

form

Alias for field number 2

certbot.util.run_script(params, log=<bound method Logger.error of <logging.Logger object>>)[source]

Run the script with the given params.

Parameters:

params (list) – List of parameters to pass to Popen

log (logging.Logger [https://docs.python.org/2/library/logging.html#logging.Logger]) – Logger to use for errors

certbot.util.exe_exists(exe)[source]

Determine whether path/name refers to an executable.

Parameters: | exe (str [https://docs.python.org/2/library/functions.html#str]) – Executable path or name
Returns: | If exe is a valid executable
Return type: | bool [https://docs.python.org/2/library/functions.html#bool]

certbot.util.lock_dir_until_exit(dir_path)[source]

Lock the directory at dir_path until program exit.

Parameters: | dir_path (str [https://docs.python.org/2/library/functions.html#str]) – path to directory
Raises: | errors.LockError – if the lock is held by another process

certbot.util.set_up_core_dir(directory, mode, uid, strict)[source]

Ensure directory exists with proper permissions and is locked.

Parameters:

directory (str [https://docs.python.org/2/library/functions.html#str]) – Path to a directory.

mode (int [https://docs.python.org/2/library/functions.html#int]) – Directory mode.

uid (int [https://docs.python.org/2/library/functions.html#int]) – Directory owner.

strict (bool [https://docs.python.org/2/library/functions.html#bool]) – require directory to be owned by current user

Raises:

errors.LockError – if the directory cannot be locked

errors.Error – if the directory cannot be made or verified

certbot.util.make_or_verify_dir(directory, mode=493, uid=0, strict=False)[source]

Make sure directory exists with proper permissions.

Parameters:

directory (str [https://docs.python.org/2/library/functions.html#str]) – Path to a directory.

mode (int [https://docs.python.org/2/library/functions.html#int]) – Directory mode.

uid (int [https://docs.python.org/2/library/functions.html#int]) – Directory owner.

strict (bool [https://docs.python.org/2/library/functions.html#bool]) – require directory to be owned by current user

Raises:

errors.Error – if a directory already exists, but has wrong permissions or owner

OSError – if invalid or inaccessible file names and paths, or other arguments that have the correct type, but are not accepted by the operating system.

certbot.util.check_permissions(filepath, mode, uid=0)[source]

Check file or directory permissions.

Parameters:

filepath (str [https://docs.python.org/2/library/functions.html#str]) – Path to the tested file (or directory).

mode (int [https://docs.python.org/2/library/functions.html#int]) – Expected file mode.

uid (int [https://docs.python.org/2/library/functions.html#int]) – Expected file owner.

Returns:

True if mode and uid match, False otherwise.

Return type:

bool [https://docs.python.org/2/library/functions.html#bool]

certbot.util.safe_open(path, mode='w', chmod=None, buffering=None)[source]

Safely open a file.

Parameters:

path (str [https://docs.python.org/2/library/functions.html#str]) – Path to a file.

mode (str [https://docs.python.org/2/library/functions.html#str]) – Same os mode for open [https://docs.python.org/2/library/functions.html#open].

chmod (int [https://docs.python.org/2/library/functions.html#int]) – Same as mode for os.open [https://docs.python.org/2/library/os.html#os.open], uses Python defaults if None.

buffering (int [https://docs.python.org/2/library/functions.html#int]) – Same as bufsize for os.fdopen [https://docs.python.org/2/library/os.html#os.fdopen], uses Python defaults if None.

certbot.util.unique_file(path, chmod=511, mode='w')[source]

Safely finds a unique file.

Parameters:

path (str [https://docs.python.org/2/library/functions.html#str]) – path/filename.ext

chmod (int [https://docs.python.org/2/library/functions.html#int]) – File mode

mode (str [https://docs.python.org/2/library/functions.html#str]) – Open mode

Returns:

tuple of file object and file name

certbot.util.unique_lineage_name(path, filename, chmod=420, mode='w')[source]

Safely finds a unique file using lineage convention.

Parameters:

path (str [https://docs.python.org/2/library/functions.html#str]) – directory path

filename (str [https://docs.python.org/2/library/functions.html#str]) – proposed filename

chmod (int [https://docs.python.org/2/library/functions.html#int]) – file mode

mode (str [https://docs.python.org/2/library/functions.html#str]) – open mode

Returns:

tuple of file object and file name (which may be modified from the requested one by appending digits to ensure uniqueness)

Raises:

OSError – if writing files fails for an unanticipated reason, such as a full disk or a lack of permission to write to specified location.

certbot.util.safely_remove(path)[source]

Remove a file that may not exist.

certbot.util.get_filtered_names(all_names)[source]

Removes names that aren’t considered valid by Let’s Encrypt.

Parameters: | all_names (set [https://docs.python.org/2/library/stdtypes.html#set]) – all names found in the configuration
Returns: | all found names that are considered valid by LE
Return type: | set [https://docs.python.org/2/library/stdtypes.html#set]

certbot.util.get_os_info(filepath='/etc/os-release')[source]

Get OS name and version

Parameters: | filepath (str [https://docs.python.org/2/library/functions.html#str]) – File path of os-release file
Returns: | (os_name, os_version)
Return type: | tuple [https://docs.python.org/2/library/functions.html#tuple] of str [https://docs.python.org/2/library/functions.html#str]

certbot.util.get_os_info_ua(filepath='/etc/os-release')[source]

Get OS name and version string for User Agent

Parameters: | filepath (str [https://docs.python.org/2/library/functions.html#str]) – File path of os-release file
Returns: | os_ua
Return type: | str [https://docs.python.org/2/library/functions.html#str]

certbot.util.get_systemd_os_info(filepath='/etc/os-release')[source]

Parse systemd /etc/os-release for distribution information

Parameters: | filepath (str [https://docs.python.org/2/library/functions.html#str]) – File path of os-release file
Returns: | (os_name, os_version)
Return type: | tuple [https://docs.python.org/2/library/functions.html#tuple] of str [https://docs.python.org/2/library/functions.html#str]

certbot.util.get_systemd_os_like(filepath='/etc/os-release')[source]

Get a list of strings that indicate the distribution likeness to other distributions.

Parameters: | filepath (str [https://docs.python.org/2/library/functions.html#str]) – File path of os-release file
Returns: | List of distribution acronyms
Return type: | list of str [https://docs.python.org/2/library/functions.html#str]

certbot.util._get_systemd_os_release_var(varname, filepath='/etc/os-release')[source]

Get single value from systemd /etc/os-release

Parameters:

varname (str [https://docs.python.org/2/library/functions.html#str]) – Name of variable to fetch

filepath (str [https://docs.python.org/2/library/functions.html#str]) – File path of os-release file

Returns:

requested value

Return type:

str [https://docs.python.org/2/library/functions.html#str]

certbot.util._normalize_string(orig)[source]

Helper function for _get_systemd_os_release_var() to remove quotes and whitespaces

certbot.util.get_python_os_info()[source]

Get Operating System type/distribution and major version using python platform module

Returns: | (os_name, os_version)
Return type: | tuple [https://docs.python.org/2/library/functions.html#tuple] of str [https://docs.python.org/2/library/functions.html#str]

certbot.util.safe_email(email)[source]

Scrub email address before using it.

class certbot.util._ShowWarning(option_strings, dest, nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None)[source]

Bases: argparse.Action [https://docs.python.org/2/library/argparse.html#argparse.Action]

Action to log a warning when an argument is used.

certbot.util.add_deprecated_argument(add_argument, argument_name, nargs)[source]

Adds a deprecated argument with the name argument_name.

Deprecated arguments are not shown in the help. If they are used on the command line, a warning is shown stating that the argument is deprecated and no other action is taken.

Parameters:

add_argument (callable [https://docs.python.org/2/library/functions.html#callable]) – Function that adds arguments to an argument parser/group.

argument_name (str [https://docs.python.org/2/library/functions.html#str]) – Name of deprecated argument.

nargs – Value for nargs when adding the argument to argparse.

certbot.util.enforce_le_validity(domain)[source]

Checks that Let’s Encrypt will consider domain to be valid.

Parameters: | domain (str [https://docs.python.org/2/library/functions.html#str] or unicode [https://docs.python.org/2/library/functions.html#unicode]) – FQDN to check
Returns: | The domain cast to str [https://docs.python.org/2/library/functions.html#str], with ASCII-only contents
Return type: | str [https://docs.python.org/2/library/functions.html#str]
Raises: | ConfigurationError – for invalid domains and cases where Let’s Encrypt currently will not issue certificates

certbot.util.enforce_domain_sanity(domain)[source]

Method which validates domain value and errors out if the requirements are not met.

Parameters: | domain (str [https://docs.python.org/2/library/functions.html#str] or unicode [https://docs.python.org/2/library/functions.html#unicode]) – Domain to check
Raises: | ConfigurationError – for invalid domains and cases where Let’s Encrypt currently will not issue certificates
Returns: | The domain cast to str [https://docs.python.org/2/library/functions.html#str], with ASCII-only contents
Return type: | str [https://docs.python.org/2/library/functions.html#str]

certbot.util.get_strict_version(normalized)[source]

Converts a normalized version to a strict version.

Parameters: | normalized (str [https://docs.python.org/2/library/functions.html#str]) – normalized version string
Returns: | An equivalent strict version
Return type: | distutils.version.StrictVersion

certbot.util.is_staging(srv)[source]

Determine whether a given ACME server is a known test / staging server.

Parameters: | srv (str [https://docs.python.org/2/library/functions.html#str]) – the URI for the ACME server
Returns: | True iff srv is a known test / staging server
Rtype bool: |

certbot.util.atexit_register(func, *args, **kwargs)[source]

Sets func to be called before the program exits.

Special care is taken to ensure func is only called when the process that first imports this module exits rather than any child processes.

Parameters:
func (function) – function to be called in case of an error

Python Module Index

c

| c |
- | certbot |
|     certbot.account |
|     certbot.achallenges |
|     certbot.auth_handler |
|     certbot.client |
|     certbot.configuration |
|     certbot.constants |
|     certbot.crypto_util |
|     certbot.display |
|     certbot.display.enhancements |
|     certbot.display.ops |
|     certbot.display.util |
|     certbot.errors |
|     certbot.interfaces |
|     certbot.plugins.common |
|     certbot.plugins.disco |
|     certbot.plugins.dns_common |
|     certbot.plugins.dns_common_lexicon |
|     certbot.plugins.manual |
|     certbot.plugins.standalone |
|     certbot.plugins.util |
|     certbot.plugins.webroot |
|     certbot.reporter |
|     certbot.reverter |
|     certbot.storage |
|     certbot.util |

Index

_ | A | B | C | D | E | F | G | H | I | K | L | M | N | O | P | Q | R | S | T | U | V | W | Y

_

__call__() (certbot.interfaces.IPluginFactory method)

_add_to_checkpoint_dir() (certbot.reverter.Reverter method)

_asdict() (certbot.util.CSR method)

(certbot.util.Key method)

_can_interact() (certbot.display.util.FileDisplay method)

_challenge_factory() (certbot.auth_handler.AuthHandler method)

_check_symlinks() (certbot.storage.RenewableCert method)

_check_tempfile_saves() (certbot.reverter.Reverter method)

_checkpoint_timestamp() (certbot.reverter.Reverter method)

_choose_challenges() (certbot.auth_handler.AuthHandler method)

_choose_names_manually() (in module certbot.display.ops)

_cleanup() (certbot.plugins.dns_common.DNSAuthenticator method)

_cleanup_challenges() (certbot.auth_handler.AuthHandler method)

_configure() (certbot.plugins.dns_common.DNSAuthenticator method)

_configure_credentials() (certbot.plugins.dns_common.DNSAuthenticator method)

_configure_file() (certbot.plugins.dns_common.DNSAuthenticator method)

_consistent() (certbot.storage.RenewableCert method)

_convert_and_validate() (certbot.plugins.standalone.SupportedChallengesAction method)

_explode_ipv6() (certbot.plugins.common.Addr method)

_filter_names() (in module certbot.display.ops)

_find_domain_id() (certbot.plugins.dns_common_lexicon.LexiconClient method)

_find_dumb_path() (in module certbot.auth_handler)

_find_smart_path() (in module certbot.auth_handler)

_find_updated_challb() (certbot.auth_handler.AuthHandler method)

_fix() (certbot.storage.RenewableCert method)

_fix_symlinks() (certbot.storage.RenewableCert method)

_full_archive_path() (in module certbot.storage)

_full_live_path() (in module certbot.storage)

_gen_https_names() (in module certbot.display.ops)

_gen_ssl_lab_urls() (in module certbot.display.ops)

_generate_failed_chall_msg() (in module certbot.auth_handler)

_get_chall_pref() (certbot.auth_handler.AuthHandler method)

_get_cp_dir() (certbot.reverter.Reverter method)

_get_systemd_os_release_var() (in module certbot.util)

_get_valid_int_ans() (certbot.display.util.FileDisplay method)

_handle_check() (certbot.auth_handler.AuthHandler method)

_interaction_fail() (certbot.display.util.NoninteractiveDisplay method)

_make() (certbot.util.CSR class method)

(certbot.util.Key class method)

_msg_type (certbot.reporter.Reporter attribute)

_normalize_ipv6() (certbot.plugins.common.Addr method)

_normalize_string() (in module certbot.util)

_notAfterBefore() (in module certbot.crypto_util)

_open_pem_file() (in module certbot.client)

_parens_around_char() (in module certbot.display.util)

_perform() (certbot.plugins.dns_common.DNSAuthenticator method)

_poll_challenges() (certbot.auth_handler.AuthHandler method)

_previous_symlinks() (certbot.storage.RenewableCert method)

_print_menu() (certbot.display.util.FileDisplay method)

_prompt_for_data() (certbot.plugins.dns_common.DNSAuthenticator static method)

_prompt_for_file() (certbot.plugins.dns_common.DNSAuthenticator static method)

_read_and_append() (certbot.reverter.Reverter method)

_recover_checkpoint() (certbot.reverter.Reverter method)

_recovery_routine_with_msg() (certbot.client.Client method)

_relevant() (in module certbot.storage)

_relpath_from_file() (in module certbot.storage)

_remove_contained_files() (certbot.reverter.Reverter method)

_replace() (certbot.util.CSR method)

(certbot.util.Key method)

_report_failed_challs() (in module certbot.auth_handler)

_report_no_chall_path() (in module certbot.auth_handler)

_respond() (certbot.auth_handler.AuthHandler method)

_return_default() (certbot.display.util.FileDisplay method)

_rollback_and_restart() (certbot.client.Client method)

_run_undo_commands() (certbot.reverter.Reverter method)

_save_chain() (in module certbot.client)

_scrub_checklist_input() (certbot.display.util.FileDisplay method)

_send_responses() (certbot.auth_handler.AuthHandler method)

_setup_challenge_cert() (certbot.plugins.common.TLSSNI01 method)

_setup_credentials() (certbot.plugins.dns_common.DNSAuthenticator method)

_ShowWarning (class in certbot.util)

_solve_challenges() (certbot.auth_handler.AuthHandler method)

_sort_names() (in module certbot.display.ops)

_timestamp_progress_dir() (certbot.reverter.Reverter method)

_update_link_to() (certbot.storage.RenewableCert method)

_update_symlinks() (certbot.storage.RenewableCert method)

_validate_webroot() (in module certbot.plugins.webroot)

_WebrootMapAction (class in certbot.plugins.webroot)

_WebrootPathAction (class in certbot.plugins.webroot)

_wrap_lines() (in module certbot.display.util)

A

Account (class in certbot.account)

Account.Meta (class in certbot.account)

AccountFileStorage (class in certbot.account)

AccountMemoryStorage (class in certbot.account)

AccountNotFound

accounts_dir (certbot.interfaces.IConfig attribute)

ACCOUNTS_DIR (in module certbot.constants)

AccountStorage (class in certbot.interfaces)

AccountStorageError

acme_from_config_key() (in module certbot.client)

acme_type (certbot.achallenges.DNS attribute)

add_chall() (certbot.plugins.common.TLSSNI01 method)

add_deprecated_argument() (in module certbot.util)

add_message() (certbot.interfaces.IReporter method)

(certbot.reporter.Reporter method)

add_parser_arguments() (certbot.plugins.common.Plugin class method)

add_time_interval() (in module certbot.storage)

add_to_checkpoint() (certbot.reverter.Reverter method)

add_to_temp_checkpoint() (certbot.reverter.Reverter method)

add_txt_record() (certbot.plugins.dns_common_lexicon.LexiconClient method)

Addr (class in certbot.plugins.common)

allow_subset_of_names (certbot.interfaces.IConfig attribute)

AnnotatedChallenge (class in certbot.achallenges)

apply_enhancement() (certbot.client.Client method)

archive_dir (certbot.storage.RenewableCert attribute)

ARCHIVE_DIR (in module certbot.constants)

ask() (in module certbot.display.enhancements)

assert_valid_call() (in module certbot.display.util)

atexit_register() (in module certbot.util)

Authenticator (class in certbot.plugins.manual)

(class in certbot.plugins.standalone)

(class in certbot.plugins.webroot)

AuthHandler (class in certbot.auth_handler)

AuthorizationError

autodeployment_is_enabled() (certbot.storage.RenewableCert method)

autorenewal_is_enabled() (certbot.storage.RenewableCert method)

available (certbot.plugins.disco.PluginEntryPoint attribute)

available() (certbot.plugins.disco.PluginsRegistry method)

available_versions() (certbot.storage.RenewableCert method)

B

backup_dir (certbot.interfaces.IConfig attribute)

BACKUP_DIR (in module certbot.constants)

base_domain_name_guesses() (in module certbot.plugins.dns_common)

C

CANCEL (in module certbot.display.util)

cert_path (certbot.storage.RenewableCert attribute)

certbot (module)

certbot.account (module)

certbot.achallenges (module)

certbot.auth_handler (module)

certbot.client (module)

certbot.configuration (module)

certbot.constants (module)

certbot.crypto_util (module)

certbot.display (module)

certbot.display.enhancements (module)

certbot.display.ops (module)

certbot.display.util (module)

certbot.errors (module)

certbot.interfaces (module)

certbot.plugins.common (module)

certbot.plugins.disco (module)

certbot.plugins.dns_common (module)

certbot.plugins.dns_common_lexicon (module)

certbot.plugins.manual (module)

certbot.plugins.standalone (module)

certbot.plugins.util (module)

certbot.plugins.webroot (module)

certbot.reporter (module)

certbot.reverter (module)

certbot.storage (module)

certbot.util (module)

certificate() (certbot.interfaces.IValidator method)

CertStorageError

chain_path (certbot.storage.RenewableCert attribute)

challb_to_achall() (in module certbot.auth_handler)

check_config_sanity() (in module certbot.configuration)

check_permissions() (in module certbot.util)

checklist() (certbot.display.util.FileDisplay method)

(certbot.display.util.NoninteractiveDisplay method)

(certbot.interfaces.IDisplay method)

choose_account() (in module certbot.display.ops)

choose_names() (in module certbot.display.ops)

cleanup() (certbot.interfaces.IAuthenticator method)

Client (class in certbot.client)

conf() (certbot.plugins.common.Plugin method)

(certbot.plugins.dns_common.CredentialsConfiguration method)

config_dir (certbot.interfaces.IConfig attribute)

CONFIG_DIRS_MODE (in module certbot.constants)

config_test() (certbot.interfaces.IInstaller method)

config_with_defaults() (in module certbot.storage)

ConfigurationError

CredentialsConfiguration (class in certbot.plugins.dns_common)

CSR (class in certbot.util)

csr_dir (certbot.interfaces.IConfig attribute)

CSR_DIR (in module certbot.constants)

csr_matches_pubkey() (in module certbot.crypto_util)

current_target() (certbot.storage.RenewableCert method)

current_version() (certbot.storage.RenewableCert method)

D

data (certbot.util.CSR attribute)

del_txt_record() (certbot.plugins.dns_common_lexicon.LexiconClient method)

delete() (certbot.account.AccountFileStorage method)

delete_files() (in module certbot.storage)

deploy_cert() (certbot.interfaces.IInstaller method)

deploy_certificate() (certbot.client.Client method)

description (certbot.interfaces.IPluginFactory attribute)

(certbot.plugins.disco.PluginEntryPoint attribute)

description_with_name (certbot.plugins.disco.PluginEntryPoint attribute)

dest() (certbot.plugins.common.Plugin method)

dest_namespace (certbot.plugins.common.Plugin attribute)

dest_namespace() (in module certbot.plugins.common)

determine_user_agent() (in module certbot.client)

dir_setup() (in module certbot.plugins.common)

directory_select() (certbot.display.util.FileDisplay method)

(certbot.display.util.NoninteractiveDisplay method)

(certbot.interfaces.IDisplay method)

DNS (class in certbot.achallenges)

DNSAuthenticator (class in certbot.plugins.dns_common)

DummyConfig (class in certbot.client)

dump_pyopenssl_chain() (in module certbot.crypto_util)

E

EFF_SUBSCRIBE_URI (in module certbot.constants)

email (certbot.interfaces.IConfig attribute)

enforce_domain_sanity() (in module certbot.util)

enforce_le_validity() (in module certbot.util)

enhance() (certbot.interfaces.IInstaller method)

enhance_config() (certbot.client.Client method)

ENHANCEMENTS (in module certbot.constants)

ensure_deployed() (certbot.storage.RenewableCert method)

entry_point_to_plugin_name() (certbot.plugins.disco.PluginEntryPoint class method)

Error

ESC (in module certbot.display.util)

exe_exists() (in module certbot.util)

F

FailedChallenges

file (certbot.util.CSR attribute)

(certbot.util.Key attribute)

FileDisplay (class in certbot.display.util)

filter() (certbot.plugins.disco.PluginsRegistry method)

finalize_checkpoint() (certbot.reverter.Reverter method)

find_all() (certbot.interfaces.AccountStorage method)

(certbot.plugins.disco.PluginsRegistry class method)

find_init() (certbot.plugins.disco.PluginsRegistry method)

FORCE_INTERACTIVE_FLAG (in module certbot.constants)

form (certbot.util.CSR attribute)

fromstring() (certbot.plugins.common.Addr class method)

fullchain_path (certbot.storage.RenewableCert attribute)

G

gen_challenge_path() (in module certbot.auth_handler)

get_addr() (certbot.plugins.common.Addr method)

get_addr_obj() (certbot.plugins.common.Addr method)

get_all_names() (certbot.interfaces.IInstaller method)

get_authorizations() (certbot.auth_handler.AuthHandler method)

get_cert_path() (certbot.plugins.common.TLSSNI01 method)

get_chall_pref() (certbot.interfaces.IAuthenticator method)

get_email() (in module certbot.display.ops)

get_filtered_names() (in module certbot.util)

get_ipv6_exploded() (certbot.plugins.common.Addr method)

get_key_path() (certbot.plugins.common.TLSSNI01 method)

get_link_target() (in module certbot.storage)

get_names_from_cert() (in module certbot.crypto_util)

get_os_info() (in module certbot.util)

get_os_info_ua() (in module certbot.util)

get_port() (certbot.plugins.common.Addr method)

get_python_os_info() (in module certbot.util)

get_sans_from_cert() (in module certbot.crypto_util)

get_strict_version() (in module certbot.util)

get_systemd_os_info() (in module certbot.util)

get_systemd_os_like() (in module certbot.util)

get_valid_domains() (in module certbot.display.ops)

H

has_pending_deployment() (certbot.storage.RenewableCert method)

HELP (in module certbot.display.util)

hidden (certbot.plugins.disco.PluginEntryPoint attribute)

HIGH_PRIORITY (certbot.interfaces.IReporter attribute)

(certbot.reporter.Reporter attribute)

HookCommandNotFound

hsts() (certbot.interfaces.IValidator method)

http01_address (certbot.interfaces.IConfig attribute)

http01_port (certbot.interfaces.IConfig attribute)

I

IAuthenticator (interface in certbot.interfaces)

IConfig (interface in certbot.interfaces)

IDisplay (interface in certbot.interfaces)

ifaces() (certbot.plugins.disco.PluginEntryPoint method)

(certbot.plugins.disco.PluginsRegistry method)

IInstaller (interface in certbot.interfaces)

import_csr_file() (in module certbot.crypto_util)

in_progress_dir (certbot.interfaces.IConfig attribute)

IN_PROGRESS_DIR (in module certbot.constants)

init() (certbot.plugins.disco.PluginEntryPoint method)

(certbot.plugins.disco.PluginsRegistry method)

init_save_csr() (in module certbot.crypto_util)

init_save_key() (in module certbot.crypto_util)

initialized (certbot.plugins.disco.PluginEntryPoint attribute)

inject_parser_options() (certbot.interfaces.IPluginFactory method)

(certbot.plugins.common.Plugin class method)

input() (certbot.display.util.FileDisplay method)

(certbot.display.util.NoninteractiveDisplay method)

(certbot.interfaces.IDisplay method)

input_with_timeout() (in module certbot.display.util)

install_ssl_options_conf() (in module certbot.plugins.common)

IPlugin (interface in certbot.interfaces)

IPluginFactory (interface in certbot.interfaces)

IReporter (interface in certbot.interfaces)

is_staging() (in module certbot.util)

is_test_cert (certbot.storage.RenewableCert attribute)

IValidator (interface in certbot.interfaces)

K

Key (class in certbot.util)

key_dir (certbot.interfaces.IConfig attribute)

KEY_DIR (in module certbot.constants)

key_path (certbot.storage.RenewableCert attribute)

KeyAuthorizationAnnotatedChallenge (class in certbot.achallenges)

L

latest_common_version() (certbot.storage.RenewableCert method)

LexiconClient (class in certbot.plugins.dns_common_lexicon)

lineagename_for_filename() (in module certbot.storage)

LIVE_DIR (in module certbot.constants)

load() (certbot.interfaces.AccountStorage method)

lock_dir_until_exit() (in module certbot.util)

LockError

long_description (certbot.plugins.disco.PluginEntryPoint attribute)

LOW_PRIORITY (certbot.interfaces.IReporter attribute)

(certbot.reporter.Reporter attribute)

M

make_key() (in module certbot.crypto_util)

make_or_verify_dir() (in module certbot.util)

MEDIUM_PRIORITY (certbot.interfaces.IReporter attribute)

(certbot.reporter.Reporter attribute)

menu() (certbot.display.util.FileDisplay method)

(certbot.display.util.NoninteractiveDisplay method)

(certbot.interfaces.IDisplay method)

MisconfigurationError

misconfigured (certbot.plugins.disco.PluginEntryPoint attribute)

MissingCommandlineFlag

more_info() (certbot.interfaces.IPlugin method)

must_staple (certbot.interfaces.IConfig attribute)

N

names() (certbot.storage.RenewableCert method)

NamespaceConfig (class in certbot.configuration)

new_lineage() (certbot.storage.RenewableCert class method)

newest_available_version() (certbot.storage.RenewableCert method)

next_free_version() (certbot.storage.RenewableCert method)

no_verify_ssl (certbot.interfaces.IConfig attribute)

NoInstallationError

NoninteractiveDisplay (class in certbot.display.util)

normalized_tuple() (certbot.plugins.common.Addr method)

notAfter() (in module certbot.crypto_util)

notBefore() (in module certbot.crypto_util)

notification() (certbot.display.util.FileDisplay method)

(certbot.display.util.NoninteractiveDisplay method)

(certbot.interfaces.IDisplay method)

NotSupportedError

O

obtain_and_enroll_certificate() (certbot.client.Client method)

obtain_certificate() (certbot.client.Client method)

obtain_certificate_from_csr() (certbot.client.Client method)

ocsp_revoked() (certbot.storage.RenewableCert method)

ocsp_stapling() (certbot.interfaces.IValidator method)

OK (in module certbot.display.util)

OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT (in module certbot.constants)

option_name() (certbot.plugins.common.Plugin method)

option_namespace (certbot.plugins.common.Plugin attribute)

option_namespace() (in module certbot.plugins.common)

P

path_surgery() (in module certbot.plugins.util)

pem (certbot.util.Key attribute)

perform() (certbot.interfaces.IAuthenticator method)

perform_registration() (in module certbot.client)

Plugin (class in certbot.plugins.common)

PluginEnhancementAlreadyPresent

PluginEntryPoint (class in certbot.plugins.disco)

PluginError

PluginSelectionError

PluginsRegistry (class in certbot.plugins.disco)

pref_challs (certbot.interfaces.IConfig attribute)

PREFIX_FREE_DISTRIBUTIONS (certbot.plugins.disco.PluginEntryPoint attribute)

prepare() (certbot.interfaces.IPlugin method)

(certbot.plugins.disco.PluginEntryPoint method)

(certbot.plugins.disco.PluginsRegistry method)

prepared (certbot.plugins.disco.PluginEntryPoint attribute)

print_messages() (certbot.interfaces.IReporter method)

(certbot.reporter.Reporter method)

problem (certbot.plugins.disco.PluginEntryPoint attribute)

pyopenssl_load_certificate() (in module certbot.crypto_util)

Q

QUIET_LOGGING_LEVEL (in module certbot.constants)

R

recovery_routine() (certbot.interfaces.IInstaller method)

(certbot.reverter.Reverter method)

redirect() (certbot.interfaces.IValidator method)

redirect_by_default() (in module certbot.display.enhancements)

register() (in module certbot.client)

register_file_creation() (certbot.reverter.Reverter method)

register_undo_command() (certbot.reverter.Reverter method)

RegistrationResourceWithNewAuthzrURI (class in certbot.account)

relative_archive_dir() (certbot.storage.RenewableCert method)

relevant_values() (in module certbot.storage)

rename_renewal_config() (in module certbot.storage)

RenewableCert (class in certbot.storage)

renewal_conf_files() (in module certbot.storage)

RENEWAL_CONFIGS_DIR (in module certbot.constants)

renewal_file_for_certname() (in module certbot.storage)

renewal_filename_for_lineagename() (in module certbot.storage)

RENEWER_DEFAULTS (in module certbot.constants)

report_new_account() (in module certbot.account)

Reporter (class in certbot.reporter)

require() (certbot.plugins.dns_common.CredentialsConfiguration method)

response_and_validation() (certbot.achallenges.KeyAuthorizationAnnotatedChallenge method)

restart() (certbot.interfaces.IInstaller method)

revert_temporary_config() (certbot.reverter.Reverter method)

Reverter (class in certbot.reverter)

ReverterError

REVOCATION_REASONS (in module certbot.constants)

rollback() (in module certbot.client)

rollback_checkpoints() (certbot.interfaces.IInstaller method)

(certbot.reverter.Reverter method)

rsa_key_size (certbot.interfaces.IConfig attribute)

run() (certbot.plugins.standalone.ServerManager method)

run_script() (in module certbot.util)

running() (certbot.plugins.standalone.ServerManager method)

S

safe_email() (in module certbot.util)

safe_open() (in module certbot.util)

safely_remove() (in module certbot.util)

sample_user_agent() (in module certbot.client)

save() (certbot.interfaces.AccountStorage method)

(certbot.interfaces.IInstaller method)

save_certificate() (certbot.client.Client method)

save_regr() (certbot.account.AccountFileStorage method)

save_successor() (certbot.storage.RenewableCert method)

separate_list_input() (in module certbot.display.util)

server (certbot.interfaces.IConfig attribute)

server_path (certbot.configuration.NamespaceConfig attribute)

ServerManager (class in certbot.plugins.standalone)

set_up_core_dir() (in module certbot.util)

SETUPTOOLS_PLUGINS_ENTRY_POINT (in module certbot.constants)

sha256sum() (in module certbot.crypto_util)

should_autodeploy() (certbot.storage.RenewableCert method)

should_autorenew() (certbot.storage.RenewableCert method)

SignalExit

slug (certbot.account.Account attribute)

StandaloneBindError

stop() (certbot.plugins.standalone.ServerManager method)

strict_permissions (certbot.interfaces.IConfig attribute)

SubprocessError

success_installation() (in module certbot.display.ops)

success_renewal() (in module certbot.display.ops)

success_revocation() (in module certbot.display.ops)

supported_challenges (certbot.plugins.standalone.Authenticator attribute)

supported_enhancements() (certbot.interfaces.IInstaller method)

SupportedChallengesAction (class in certbot.plugins.standalone)

T

target_expiry (certbot.storage.RenewableCert attribute)

temp_checkpoint_dir (certbot.interfaces.IConfig attribute)

TEMP_CHECKPOINT_DIR (in module certbot.constants)

tls_sni_01_address (certbot.interfaces.IConfig attribute)

tls_sni_01_port (certbot.interfaces.IConfig attribute)

TLSSNI01 (class in certbot.plugins.common)

U

ua_flags() (in module certbot.client)

unique_file() (in module certbot.util)

unique_lineage_name() (in module certbot.util)

update_all_links_to() (certbot.storage.RenewableCert method)

update_configuration() (in module certbot.storage)

V

valid_csr() (in module certbot.crypto_util)

valid_privkey() (in module certbot.crypto_util)

validate_file() (in module certbot.plugins.dns_common)

validate_file_permissions() (in module certbot.plugins.dns_common)

validate_key_csr() (in module certbot.client)

validated_directory() (in module certbot.display.ops)

validated_input() (in module certbot.display.ops)

verify() (certbot.plugins.disco.PluginEntryPoint method)

(certbot.plugins.disco.PluginsRegistry method)

verify_authzr_complete() (certbot.auth_handler.AuthHandler method)

verify_cert_matches_priv_key() (in module certbot.crypto_util)

verify_fullchain() (in module certbot.crypto_util)

verify_renewable_cert() (in module certbot.crypto_util)

verify_renewable_cert_sig() (in module certbot.crypto_util)

version() (certbot.storage.RenewableCert method)

view_config_changes() (certbot.interfaces.IInstaller method)

(certbot.reverter.Reverter method)

(in module certbot.client)

visible() (certbot.plugins.disco.PluginsRegistry method)

W

work_dir (certbot.interfaces.IConfig attribute)

write_renewal_config() (in module certbot.storage)

Y

yesno() (certbot.display.util.FileDisplay method)

(certbot.display.util.NoninteractiveDisplay method)

(certbot.interfaces.IDisplay method)

Ciphersuites

Table of Contents

Introduction

Autoupdates

Cryptographic choices

Sources of defaults

Resources for recommendations

Changing your settings

TODO

Feedback

References

RFC 7575

BetterCrypto.org

FF-DHE Internet-Draft

Mozilla

Dutch National Cyber Security Centre

Keylength.com

NIST

ENISA

WeakDH/Logjam

Particular sites’ opinions or configurations

Amazon ELB

U.S. Government 18F

Duraconf

Site scanning or rating tools

Qualys SSL Labs

Dutch NCSC

Java compatibility issue

Introduction

Autoupdates

Within certain limits, TLS server software can choose what kind of cryptography to use when a client connects. These choices can affect security, compatibility, and performance in complex ways. Most of these options are independent of a particular certificate. Certbot tries to provide defaults that we think are most useful to our users.

As described below, Certbot will default to modifying server software’s cryptographic settings to keep these up-to-date with what we think are appropriate defaults when new versions of the Certbot are installed (for example, by an operating system package manager).

When this feature is implemented, this document will be updated to describe how to disable these automatic changes.

Cryptographic choices

Software that uses cryptography must inevitably make choices about what kind of cryptography to use and how. These choices entail assumptions about how well particular cryptographic mechanisms resist attack, and what trade-offs are available and appropriate. The choices are constrained by compatibility issues (in order to interoperate with other software, an implementation must agree to use cryptographic mechanisms that the other side also supports) and protocol issues (cryptographic mechanisms must be specified in protocols and there must be a way to agree to use them in a particular context).

The best choices for a particular application may change over time in response to new research, new standardization events, changes in computer hardware, and changes in the prevalence of legacy software. Much important research on cryptanalysis and cryptographic vulnerabilities is unpublished because many researchers have been working in the interest of improving some entities’ communications security while weakening, or failing to improve, others’ security. But important information that improves our understanding of the state of the art is published regularly.

When enabling TLS support in a compatible web server (which is a separate step from obtaining a certificate), Certbot has the ability to update that web server’s TLS configuration. Again, this is different from the cryptographic particulars of the certificate itself; the certificate as of the initial release will be RSA-signed using one of Let’s Encrypt’s 2048-bit RSA keys, and will describe the subscriber’s RSA public key (“subject public key”) of at least 2048 bits, which is used for key establishment.

Note that the subscriber’s RSA public key can be used in a wide variety of key establishment methods, most of which do not use RSA directly for key exchange, but only for authenticating the server! For example, in DHE and ECDHE key exchanges, the subject public key is just used to sign other parameters for authentication. You do not have to “use RSA” for other purposes just because you’re using an RSA key for authentication.

The certificate doesn’t specify other cryptographic or ciphersuite particulars; for example, it doesn’t say whether or not parties should use a particular symmetric algorithm like 3DES, or what cipher modes they should use. All of these details are negotiated between client and server independent of the content of the ciphersuite. The Let’s Encrypt project hopes to provide useful defaults that reflect good security choices with respect to the publicly-known state of the art. However, the Let’s Encrypt certificate authority does not dictate end-users’ security policy, and any site is welcome to change its preferences in accordance with its own policy or its administrators’ preferences, and use different cryptographic mechanisms or parameters, or a different priority order, than the defaults provided by Certbot.

If you don’t use Certbot to configure your server directly, because the client doesn’t integrate with your server software or because you chose not to use this integration, then the cryptographic defaults haven’t been modified, and the cryptography chosen by the server will still be whatever the default for your software was. For example, if you obtain a certificate using standalone mode and then manually install it in an IMAP or LDAP server, your cryptographic settings will not be modified by the client in any way.

Sources of defaults

Initially, Certbot will configure users’ servers to use the cryptographic defaults recommended by the Mozilla project. These settings are well-reasoned recommendations that carefully consider client software compatibility. They are described at

https://wiki.mozilla.org/Security/Server_Side_TLS

and the version implemented by Certbot will be the version that was most current as of the release date of each client version. Mozilla offers three separate sets of cryptographic options, which trade off security and compatibility differently. These are referred to as the “Modern”, “Intermediate”, and “Old” configurations (in order from most secure to least secure, and least-backwards compatible to most-backwards compatible). The client will follow the Mozilla defaults for the Intermediate configuration by default, at least with regards to ciphersuites and TLS versions. Mozilla’s web site describes which client software will be compatible with each configuration. You can also use the Qualys SSL Labs site, which Certbot will suggest when installing a certificate, to test your server and see whether it will be compatible with particular software versions.

It will be possible to ask Certbot to instead apply (and track) Modern or Old configurations.

The Let’s Encrypt project expects to follow the Mozilla recommendations in the future as those recommendations are updated. (For example, some users have proposed prioritizing a new ciphersuite known as 0xcc13 which uses the ChaCha and Poly1305 algorithms, and which is already implemented by the Chrome browser. Mozilla has delayed recommending 0xcc13 over compatibility and standardization concerns, but is likely to recommend it in the future once these concerns have been addressed. At that point, Certbot would likely follow the Mozilla recommendations and favor the use of this ciphersuite as well.)

The Let’s Encrypt project may deviate from the Mozilla recommendations in the future if good cause is shown and we believe our users’ priorities would be well-served by doing so. In general, please address relevant proposals for changing priorities to the Mozilla security team first, before asking the Certbot developers to change Certbot’s priorities. The Mozilla security team is likely to have more resources and expertise to bring to bear on evaluating reasons why its recommendations should be updated.

The Let’s Encrypt project will entertain proposals to create a very small number of alternative configurations (apart from Modern, Intermediate, and Old) that there’s reason to believe would be widely used by sysadmins; this would usually be a preferable course to modifying an existing configuration. For example, if many sysadmins want their servers configured to track a different expert recommendation, Certbot could add an option to do so.

Resources for recommendations

In the course of considering how to handle this issue, we received recommendations with sources of expert guidance on ciphersuites and other cryptographic parameters. We’re grateful to everyone who contributed suggestions. The recommendations we received are available under Feedback.

Certbot users are welcome to review these authorities to better inform their own cryptographic parameter choices. We also welcome suggestions of other resources to add to this list. Please keep in mind that different recommendations may reflect different priorities or evaluations of trade-offs, especially related to compatibility!

Changing your settings

This will probably look something like

certbot --cipher-recommendations mozilla-secure
certbot --cipher-recommendations mozilla-intermediate
certbot --cipher-recommendations mozilla-old

to track Mozilla’s Secure, Intermediate, or Old recommendations, and

certbot --update-ciphers on

to enable updating ciphers with each new Certbot release, or

certbot --update-ciphers off

to disable automatic configuration updates. These features have not yet been implemented and this syntax may change when they are implemented.

TODO

The status of this feature is tracked as part of issue #1123 in our bug tracker.

https://github.com/certbot/certbot/issues/1123

Prior to implementation of #1123, the client does not actually modify ciphersuites (this is intended to be implemented as a “configuration enhancement”, but the only configuration enhancement implemented so far is redirecting HTTP requests to HTTPS in web servers, the “redirect” enhancement). The changes here would probably be either a new “ciphersuite” enhancement in each plugin that provides an installer, or a family of enhancements, one per selectable ciphersuite configuration.

Feedback

We receive lots of feedback on the type of ciphersuites that Let’s Encrypt supports and list some collated feedback below. This section aims to track suggestions and references that people have offered or identified to improve the ciphersuites that Let’s Encrypt enables when configuring TLS on servers.

Because of the Chatham House Rule applicable to some of the discussions, people are not individually credited for their suggestions, but most suggestions here were made or found by other people, and I thank them for their contributions.

Some people provided rationale information mostly having to do with compatibility of particular user-agents (especially UAs that don’t support ECC, or that don’t support DH groups > 1024 bits). Some ciphersuite configurations have been chosen to try to increase compatibility with older UAs while allowing newer UAs to negotiate stronger crypto. For example, some configurations forego forward secrecy entirely for connections from old UAs, like by offering ECDHE and RSA key exchange, but no DHE at all. (There are UAs that can fail the negotiation completely if a DHE ciphersuite with prime > 1024 bits is offered.)

References

RFC 7575

IETF has published a BCP document, RFC 7525, “Recommendations for Secure Use of Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS)”

https://datatracker.ietf.org/doc/rfc7525/

BetterCrypto.org

BetterCrypto.org, a collaboration of mostly European IT security experts, has published a draft paper, “Applied Crypto Hardening”

https://bettercrypto.org/static/applied-crypto-hardening.pdf

FF-DHE Internet-Draft

Gillmor’s Internet-Draft “Negotiated Discrete Log Diffie-Hellman Ephemeral Parameters for TLS” is being developed at the IETF TLS WG. It advocates using standardized DH groups in all cases, not individually-chosen ones (mostly because of the Triple Handshake attack which can involve maliciously choosing invalid DH groups). The draft provides a list of recommended groups, with primes beginning at 2048 bits and going up from there. It also has a new protocol mechanism for agreeing to use these groups, with the possibility of backwards compatibility (and use of weaker DH groups) for older clients and servers that don’t know about this mechanism.

https://tools.ietf.org/html/draft-ietf-tls-negotiated-ff-dhe-10

Mozilla

Mozilla’s general server configuration guidance is available at https://wiki.mozilla.org/Security/Server_Side_TLS

Mozilla has also produced a configuration generator: https://mozilla.github.io/server-side-tls/ssl-config-generator/

Dutch National Cyber Security Centre

The Dutch National Cyber Security Centre has published guidance on “ICT-beveiligingsrichtlijnen voor Transport Layer Security (TLS)” (“IT Security Guidelines for Transport Layer Security (TLS)”). These are available only in Dutch at

https://www.ncsc.nl/dienstverlening/expertise-advies/kennisdeling/whitepapers/ict-beveiligingsrichtlijnen-voor-transport-layer-security-tls.html

I have access to an English-language summary of the recommendations.

Keylength.com

Damien Giry collects recommendations by academic researchers and standards organizations about keylengths for particular cryptoperiods, years, or security levels. The keylength recommendations of the various sources are summarized in a chart. This site has been updated over time and includes expert guidance from eight sources published between 2000 and 2017.

http://www.keylength.com/

NIST

NISA published its “NIST Special Publication 800-52 Revision 1: Guidelines for the Selection, Configuration, and Use of Transport Layer Security (TLS) Implementations”

http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-52r1.pdf

and its “NIST Special Publication 800-57: Recommendation for Key Management – Part 1: General (Revision 3)”

http://csrc.nist.gov/publications/nistpubs/800-57/sp800-57_part1_rev3_general.pdf

ENISA

ENISA published its “Algorithms, Key Sizes and Parameters Report - 2013”

https://www.enisa.europa.eu/activities/identity-and-trust/library/deliverables/algorithms-key-sizes-and-parameters-report

WeakDH/Logjam

The WeakDH/Logjam research has thrown into question the safety of some existing practice using DH ciphersuites, especially the use of standardized groups with a prime ≤ 1024 bits. The authors provided detailed guidance, including ciphersuite lists, at

https://weakdh.org/sysadmin.html

These lists may have been derived from Mozilla’s recommendations. One of the authors clarified his view of the priorities for various changes as a result of the research at

https://www.ietf.org/mail-archive/web/tls/current/msg16496.html

In particular, he supports ECDHE and also supports the use of the standardized groups in the FF-DHE Internet-Draft mentioned above (which isn’t clear from the group’s original recommendations).

Particular sites’ opinions or configurations

Amazon ELB

Amazon ELB explains its current ciphersuite choices at

https://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/elb-security-policy-table.html

U.S. Government 18F

The 18F site (https://18f.gsa.gov/) is using

ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !MD5 !EXP !DSS !PSK !SRP !kECDH !CAMELLIA !RC4 !SEED';

Duraconf

The Duraconf project collects particular configuration files, with an apparent focus on avoiding the use of obsolete symmetric ciphers and hash functions, and favoring forward secrecy while not requiring it.

https://github.com/ioerror/duraconf

Site scanning or rating tools

Qualys SSL Labs

Qualys offers the best-known TLS security scanner, maintained by Ivan Ristić.

https://www.ssllabs.com/

Dutch NCSC

The Dutch NCSC, mentioned above, has also made available its own site security scanner which indicates how well sites comply with the recommendations.

https://en.internet.nl/

Java compatibility issue

A lot of backward-compatibility concerns have to do with Java hard-coding DHE primes to a 1024-bit limit, accepting DHE ciphersuites in negotiation, and then aborting the connection entirely if a prime > 1024 bits is presented. The simple summary is that servers offering a Java-compatible DHE ciphersuite in preference to other Java-compatible ciphersuites, and then presenting a DH group with a prime > 1024 bits, will be completely incompatible with clients running some versions of Java. (This may also be the case with very old MSIE versions...?) There are various strategies for dealing with this, and maybe we can document the options here.

Challenges

Digital certificates can only be issued to people who are entitled to them. For example, assuming you don’t run google.com, you’re not entitled to a certificate for it. Nor is someone else entitled to receive a certificate for your web site.

In order to receive a certificate from Let’s Encrypt certificate authority (CA), you have to prove your control over each of the domain names that will be listed in the certificate. You can do so by making certain publicly-visible changes, proving that the person who’s requested a particular certificate is the same person who controls the site(s) that the certificate will refer to.

Let’s Encrypt specifies three different ways to prove your control over a domain (each of which Certbot may be able to do for you). These are called “challenges,” because you are being challenged to perform tasks that only someone who controls the domain should be able to accomplish.

When you use Certbot, it will attempt to help you prove control over your domains automatically in a way that’s acceptable to the CA. Especially if this doesn’t work the way you expected, it can be helpful to understand what Certbot is trying to do in each case.

The three ways to prove your control over a domain for the Let’s Encrypt CA are:

Posting a specified file on a web site

This method is called the HTTP-01 challenge. In this challenge, the certificate authority will expect a specified file to be posted in a specified location on a web site. The file will be downloaded using an HTTP request on TCP port 80. Since part of what this challenge shows is the ability to create a file at an arbitrary location, you cannot choose a different location or port number.

Offering a specified certificate on a web site

This method is called the TLS-SNI-01 challenge. In this challenge, the certificate authority will expect a specified digital certificate to be provided by the web server in response to an HTTPS request using a particular made-up domain name. The request will be made using HTTPS on TCP port 443. You cannot choose a different port number.

This certificate is a self-signed certificate created by Certbot. You use it only temporarily to prove your control over a domain name. It’s not the same as the certificate for your site that will later be issued by Let’s Encrypt once you’ve proven that you control the site.

Posting a specified DNS record in the domain name system

This method is called the DNS-01 challenge. In this challenge, the certificate authority will expect a specified DNS record to be present in your DNS zone when queried for. The record will be a TXT record for a specific subdomain of the name you’re proving your control over.

For each kind of challenge, the challenge can potentially be completed automatically (Certbot directly makes the necessary changes itself, or runs another program that does so), or manually (Certbot tells you to make a certain change, and you edit a configuration file of some kind in order to accomplish it). Certbot’s design emphasizes performing challenges automatically, and this is the normal case for most uses of Certbot.

Some Certbot plugins offer the functionality of an authenticator, which simply means that they can satisfy challenges. Different plugins can satisfy different kinds of challenges, as follows:

apache plugin: Can only use TLS-SNI-01. Tries to edit your Apache configuration files in order to temporarily serve a specified Certbot-generated certificate for a specified name. This can work when you’re running Certbot on a web server with an existing installation of Apache that is able to listen on port 443. This makes certain assumptions about your Apache configuration.

nginx plugin: Can only use TLS-SNI-01. Tries to edit your nginx configuration files in order to temporarily serve a specified Certbot-generated certificate for a specified name. This can work when you’re running Certbot on a web server with an existing installation of nginx that is able to listen on port 443. This makes certain assumptions about your nginx configuration.

webroot plugin: Can only use HTTP-01. Tries to place a file into an appropriate place in order for that file to be served over HTTP on port 80 by an existing web server running on your system. This can work when you’re running Certbot on a web server with any existing server application that already listens to web requests on port 80, and that serves files from disk in response.

standalone plugin: Can use either TLS-SNI-01 or HTTP-01. (You can choose with the --preferred-challenges option.) Tries to run its own temporary web server which will speak either HTTP on port 80 (for HTTP-01) or HTTPS on port 443 (for TLS-SNI-01). This can work if either of these ports is free to receive incoming connections at the moment that you run Certbot, because there’s no existing program listening to them or because you’ve temporarily shut down any server application that was listening to them.

manual plugin: Can use either DNS-01 or HTTP-01. May tell you what changes you are expected to make to your configuration. Or, using an external script, can update your DNS records (for DNS-01) or your webroot (for HTTP-01). This can work if you have appropriate technical knowledge of how to make these kinds of changes yourself when asked to do so. Note that this will prevent automated renewal of your certificate using certbot renew. [Can manual also use TLS-SNI-01??]

Common problems with passing different challenges

HTTP-01 challenge: * (With webroot plugin) You aren’t running Certbot on your web server

Most people should install and run Certbot on their web server hosting their website, not on their laptops or some other computer. While you can use Certbot in manual mode on a laptop and then separately set up the appropriate files on your webserver, it’s not likely to be the most convenient way to get a certificate for most users.

A domain name you’re requesting a certificate for isn’t correctly pointed at that web server

In most cases, every name you’re requesting a certificate for should already exist and be pointed to the public IP address of the server where you’re requesting that certificate. (Some alternatives exist for complex network configurations, but they’re the exception rather than the rule.)

A firewall is blocking access to port 80

The certificate authority needs to be able to connect to port 80 of your server in order to confirm that you satisfied the HTTP-01 challenge. So that needs to be publicly reachable from the Internet, and not blocked by a router or firewall.

(With webroot plugin) You specified the webroot directory incorrectly

If you used --webroot, you need to tell Certbot where it can put files in order to have them served by your existing web server. If you said your webroot for example.com was /var/www/example.com, then a file placed in /var/www/example.com/.well-known/acme-challenge/testfile should appear on your web site at http://example.com/.well-known/acme-challenge/testfile (which you can test using a web browser). (A redirection to HTTPS is OK here and should not stop the challenge from working.)

Note that you should not specify the .well-known/acme-challenge directory itself. Instead, you should specify the top level directory that web content is served from.

(With webroot plugin) You don’t have a webroot directory at all

In some web server configurations, all pages are dynamically generated by some kind of framework, usually using a database backend. In this case, there might not be a particular directory that files can be directly served from by the existing web server application. Using the webroot plugin in this case requires making a change to your web server configuration first.

(With manual plugin) You updated the webroot directory incorrectly

If you used --manual, you need to know where you can put files in order to have them served by your existing web server. If you think your webroot for example.com is /var/www/example.com, then a file placed in /var/www/example.com/.well-known/acme-challenge/testfile should appear on your web site at http://example.com/.well-known/acme-challenge/testfile. (A redirection to HTTPS is OK here and should not stop the challenge from working.) You should also make sure that you don’t make a typo in the name of the file when creating it.

Your existing web server’s configuration refuses to serve files from /.well-known/acme-challenge, or doesn’t serve them at the /.well-known/acme-challenge location on your site, or serves them with a header or footer, or serves them with an unusual MIME type.

(With standalone plugin) You tried to use --standalone when there was already some other program on your server listening to port 80

(With webroot plugin) You tried to use --webroot when you don’t have an existing web server listening on port 80

Your DNS records aren’t valid Try checking your DNS records with a tool like the DNSchecker at http://www.dnsstuff.com/ to make sure there are no serious errors. Sometimes a DNS error still allows your site to load in a web browser, but prevents the certificate authority from issuing a certificate.

TLS-SNI-01 challenge: * You aren’t running Certbot on your web server

Most people should install and run Certbot on their web server hosting their website, not on their laptops or some other computer. While you can use Certbot in manual mode on a laptop and then separately set up the appropriate files on your webserver, it’s not likely to be the most convenient way to get a certificate for most users.

A domain name you’re requesting a certificate for isn’t correctly pointed at that web server

In most cases, every name you’re requesting a certificate for should already exist and be pointed to the server where you’re requesting that certificate. (Some alternatives exist for complex network configurations, but they’re the exception rather than the rule.)

You’re using a content delivery network (CDN)

TLS-SNI-01 doesn’t work with CDNs (like CloudFlare and Akamai). You have to use a different challenge type. (This is a special case of the previous problem: the domain name is pointed at the CDN, not directly at your server.)

A firewall is blocking access to port 443

The certificate authority needs to be able to connect to port 443 of your server in order to confirm that you satisfied the TLS-SNI-01 challenge. So that needs to be publicly reachable from the Internet, and not blocked by a router or firewall.

(With apache plugin) Certbot thinks you’re running Apache, but you aren’t running it, or you’re running a different server of some kind on port 443

(With nginx plugin) Certbot thinks you’re running nginx, but you aren’t running it, or you’re running a different server of some kind on port 443

(With apache or nginx plugin) Certbot doesn’t know how to modify your web server configuration correctly

(With standalone plugin) You tried to use --standalone when there was already some other program on your server listening to port 443

Your DNS records aren’t valid Try checking your DNS records with a tool like the DNSchecker at http://www.dnsstuff.com/ to make sure there are no serious errors. Sometimes a DNS error still allows your site to load in a web browser, but prevents the certificate authority from issuing a certificate.

DNS-01 challenge:

(With manual plugin) Your DNS records weren’t correctly updated. You need to be able to make appropriate changes to your DNS zone in order to pass the challenge.

Your DNS records aren’t valid. Try checking your DNS records with a tool like the DNSchecker at http://www.dnsstuff.com/ to make sure there are no serious errors. Sometimes a DNS error still allows your site to load in a web browser, but prevents the certificate authority from issuing a certificate.

All modules for which code is available

acme.challenges

certbot.account

certbot.achallenges

certbot.auth_handler

certbot.client

certbot.configuration

certbot.crypto_util

certbot.display.enhancements

certbot.display.ops

certbot.display.util

certbot.errors

certbot.interfaces

certbot.plugins.common

certbot.plugins.disco

certbot.plugins.dns_common

certbot.plugins.dns_common_lexicon

certbot.plugins.manual

certbot.plugins.standalone

certbot.plugins.util

certbot.plugins.webroot

certbot.reporter

certbot.reverter

certbot.storage

certbot.util

namedtuple_CSR

namedtuple_Key

Source code for certbot.achallenges

"""Client annotated ACME challenges.

Please use names such as ``achall`` to distinguish from variables "of type"
:class:`acme.challenges.Challenge` (denoted by ``chall``)
and :class:`.ChallengeBody` (denoted by ``challb``)::

  from acme import challenges
  from acme import messages
  from certbot import achallenges

  chall = challenges.DNS(token='foo')
  challb = messages.ChallengeBody(chall=chall)
  achall = achallenges.DNS(chall=challb, domain='example.com')

Note, that all annotated challenges act as a proxy objects::

  achall.token == challb.token

"""
import logging

from acme import challenges
from acme import jose

logger = logging.getLogger(__name__)

# pylint: disable=too-few-public-methods

[docs]class AnnotatedChallenge(jose.ImmutableMap):
    """Client annotated challenge.

    Wraps around server provided challenge and annotates with data
    useful for the client.

    :ivar challb: Wrapped `~.ChallengeBody`.

    """
    __slots__ = ('challb',)
    acme_type = NotImplemented

    def __getattr__(self, name):
        return getattr(self.challb, name)

[docs]class KeyAuthorizationAnnotatedChallenge(AnnotatedChallenge):
    """Client annotated `KeyAuthorizationChallenge` challenge."""
    __slots__ = ('challb', 'domain', 'account_key')

[docs]    def response_and_validation(self, *args, **kwargs):
        """Generate response and validation."""
        return self.challb.chall.response_and_validation(
            self.account_key, *args, **kwargs)

[docs]class DNS(AnnotatedChallenge):
    """Client annotated "dns" ACME challenge."""
    __slots__ = ('challb', 'domain')
    acme_type = challenges.DNS

Source code for certbot.reverter

"""Reverter class saves configuration checkpoints and allows for recovery."""
import csv
import glob
import logging
import os
import shutil
import time
import traceback

import six
import zope.component

from certbot import constants
from certbot import errors
from certbot import interfaces
from certbot import util

logger = logging.getLogger(__name__)

[docs]class Reverter(object):
    """Reverter Class - save and revert configuration checkpoints.

    This class can be used by the plugins, especially Installers, to
    undo changes made to the user's system. Modifications to files and
    commands to do undo actions taken by the plugin should be registered
    with this class before the action is taken.

    Once a change has been registered with this class, there are three
    states the change can be in. First, the change can be a temporary
    change. This should be used for changes that will soon be reverted,
    such as config changes for the purpose of solving a challenge.
    Changes are added to this state through calls to
    :func:`~add_to_temp_checkpoint` and reverted when
    :func:`~revert_temporary_config` or :func:`~recovery_routine` is
    called.

    The second state a change can be in is in progress. These changes
    are not temporary, however, they also have not been finalized in a
    checkpoint. A change must become in progress before it can be
    finalized. Changes are added to this state through calls to
    :func:`~add_to_checkpoint` and reverted when
    :func:`~recovery_routine` is called.

    The last state a change can be in is finalized in a checkpoint. A
    change is put into this state by first becoming an in progress
    change and then calling :func:`~finalize_checkpoint`. Changes
    in this state can be reverted through calls to
    :func:`~rollback_checkpoints`.

    As a final note, creating new files and registering undo commands
    are handled specially and use the methods
    :func:`~register_file_creation` and :func:`~register_undo_command`
    respectively. Both of these methods can be used to create either
    temporary or in progress changes.

    .. note:: Consider moving everything over to CSV format.

    :param config: Configuration.
    :type config: :class:`certbot.interfaces.IConfig`

    """
    def __init__(self, config):
        self.config = config

        util.make_or_verify_dir(
            config.backup_dir, constants.CONFIG_DIRS_MODE, os.geteuid(),
            self.config.strict_permissions)

[docs]    def revert_temporary_config(self):
        """Reload users original configuration files after a temporary save.

        This function should reinstall the users original configuration files
        for all saves with temporary=True

        :raises .ReverterError: when unable to revert config

        """
        if os.path.isdir(self.config.temp_checkpoint_dir):
            try:
                self._recover_checkpoint(self.config.temp_checkpoint_dir)
            except errors.ReverterError:
                # We have a partial or incomplete recovery
                logger.fatal("Incomplete or failed recovery for %s",
                             self.config.temp_checkpoint_dir)
                raise errors.ReverterError("Unable to revert temporary config")

[docs]    def rollback_checkpoints(self, rollback=1):
        """Revert 'rollback' number of configuration checkpoints.

        :param int rollback: Number of checkpoints to reverse. A str num will be
           cast to an integer. So "2" is also acceptable.

        :raises .ReverterError:
            if there is a problem with the input or if the function is
            unable to correctly revert the configuration checkpoints

        """
        try:
            rollback = int(rollback)
        except ValueError:
            logger.error("Rollback argument must be a positive integer")
            raise errors.ReverterError("Invalid Input")
        # Sanity check input
        if rollback < 0:
            logger.error("Rollback argument must be a positive integer")
            raise errors.ReverterError("Invalid Input")

        backups = os.listdir(self.config.backup_dir)
        backups.sort()

        if not backups:
            logger.warning(
                "Certbot hasn't modified your configuration, so rollback "
                "isn't available.")
        elif len(backups) < rollback:
            logger.warning("Unable to rollback %d checkpoints, only %d exist",
                           rollback, len(backups))

        while rollback > 0 and backups:
            cp_dir = os.path.join(self.config.backup_dir, backups.pop())
            try:
                self._recover_checkpoint(cp_dir)
            except errors.ReverterError:
                logger.fatal("Failed to load checkpoint during rollback")
                raise errors.ReverterError(
                    "Unable to load checkpoint during rollback")
            rollback -= 1

[docs]    def view_config_changes(self, for_logging=False, num=None):
        """Displays all saved checkpoints.

        All checkpoints are printed by
        :meth:`certbot.interfaces.IDisplay.notification`.

        .. todo:: Decide on a policy for error handling, OSError IOError...

        :raises .errors.ReverterError: If invalid directory structure.

        """
        backups = os.listdir(self.config.backup_dir)
        backups.sort(reverse=True)
        if num:
            backups = backups[:num]
        if not backups:
            logger.info("Certbot has not saved backups of your configuration")

            return
        # Make sure there isn't anything unexpected in the backup folder
        # There should only be timestamped (float) directories
        try:
            for bkup in backups:
                float(bkup)
        except ValueError:
            raise errors.ReverterError(
                "Invalid directories in {0}".format(self.config.backup_dir))

        output = []
        for bkup in backups:
            output.append(time.ctime(float(bkup)))
            cur_dir = os.path.join(self.config.backup_dir, bkup)
            with open(os.path.join(cur_dir, "CHANGES_SINCE")) as changes_fd:
                output.append(changes_fd.read())

            output.append("Affected files:")
            with open(os.path.join(cur_dir, "FILEPATHS")) as paths_fd:
                filepaths = paths_fd.read().splitlines()
                for path in filepaths:
                    output.append("  {0}".format(path))

            if os.path.isfile(os.path.join(cur_dir, "NEW_FILES")):
                with open(os.path.join(cur_dir, "NEW_FILES")) as new_fd:
                    output.append("New Configuration Files:")
                    filepaths = new_fd.read().splitlines()
                    for path in filepaths:
                        output.append("  {0}".format(path))

            output.append(os.linesep)

        if for_logging:
            return os.linesep.join(output)
        zope.component.getUtility(interfaces.IDisplay).notification(
            os.linesep.join(output), force_interactive=True)

[docs]    def add_to_temp_checkpoint(self, save_files, save_notes):
        """Add files to temporary checkpoint.

        :param set save_files: set of filepaths to save
        :param str save_notes: notes about changes during the save

        """
        self._add_to_checkpoint_dir(
            self.config.temp_checkpoint_dir, save_files, save_notes)

[docs]    def add_to_checkpoint(self, save_files, save_notes):
        """Add files to a permanent checkpoint.

        :param set save_files: set of filepaths to save
        :param str save_notes: notes about changes during the save

        """
        # Check to make sure we are not overwriting a temp file
        self._check_tempfile_saves(save_files)
        self._add_to_checkpoint_dir(
            self.config.in_progress_dir, save_files, save_notes)

[docs]    def _add_to_checkpoint_dir(self, cp_dir, save_files, save_notes):
        """Add save files to checkpoint directory.

        :param str cp_dir: Checkpoint directory filepath
        :param set save_files: set of files to save
        :param str save_notes: notes about changes made during the save

        :raises IOError: if unable to open cp_dir + FILEPATHS file
        :raises .ReverterError: if unable to add checkpoint

        """
        util.make_or_verify_dir(
            cp_dir, constants.CONFIG_DIRS_MODE, os.geteuid(),
            self.config.strict_permissions)

        op_fd, existing_filepaths = self._read_and_append(
            os.path.join(cp_dir, "FILEPATHS"))

        idx = len(existing_filepaths)

        for filename in save_files:
            # No need to copy/index already existing files
            # The oldest copy already exists in the directory...
            if filename not in existing_filepaths:
                # Tag files with index so multiple files can
                # have the same filename
                logger.debug("Creating backup of %s", filename)
                try:
                    shutil.copy2(filename, os.path.join(
                        cp_dir, os.path.basename(filename) + "_" + str(idx)))
                    op_fd.write(filename + os.linesep)
                # http://stackoverflow.com/questions/4726260/effective-use-of-python-shutil-copy2
                except IOError:
                    op_fd.close()
                    logger.error(
                        "Unable to add file %s to checkpoint %s",
                        filename, cp_dir)
                    raise errors.ReverterError(
                        "Unable to add file {0} to checkpoint "
                        "{1}".format(filename, cp_dir))
                idx += 1
        op_fd.close()

        with open(os.path.join(cp_dir, "CHANGES_SINCE"), "a") as notes_fd:
            notes_fd.write(save_notes)

[docs]    def _read_and_append(self, filepath):  # pylint: disable=no-self-use
        """Reads the file lines and returns a file obj.

        Read the file returning the lines, and a pointer to the end of the file.

        """
        # Open up filepath differently depending on if it already exists
        if os.path.isfile(filepath):
            op_fd = open(filepath, "r+")
            lines = op_fd.read().splitlines()
        else:
            lines = []
            op_fd = open(filepath, "w")

        return op_fd, lines

[docs]    def _recover_checkpoint(self, cp_dir):
        """Recover a specific checkpoint.

        Recover a specific checkpoint provided by cp_dir
        Note: this function does not reload augeas.

        :param str cp_dir: checkpoint directory file path

        :raises errors.ReverterError: If unable to recover checkpoint

        """
        # Undo all commands
        if os.path.isfile(os.path.join(cp_dir, "COMMANDS")):
            self._run_undo_commands(os.path.join(cp_dir, "COMMANDS"))
        # Revert all changed files
        if os.path.isfile(os.path.join(cp_dir, "FILEPATHS")):
            try:
                with open(os.path.join(cp_dir, "FILEPATHS")) as paths_fd:
                    filepaths = paths_fd.read().splitlines()
                    for idx, path in enumerate(filepaths):
                        shutil.copy2(os.path.join(
                            cp_dir,
                            os.path.basename(path) + "_" + str(idx)), path)
            except (IOError, OSError):
                # This file is required in all checkpoints.
                logger.error("Unable to recover files from %s", cp_dir)
                raise errors.ReverterError(
                    "Unable to recover files from %s" % cp_dir)

        # Remove any newly added files if they exist
        self._remove_contained_files(os.path.join(cp_dir, "NEW_FILES"))

        try:
            shutil.rmtree(cp_dir)
        except OSError:
            logger.error("Unable to remove directory: %s", cp_dir)
            raise errors.ReverterError(
                "Unable to remove directory: %s" % cp_dir)

[docs]    def _run_undo_commands(self, filepath):  # pylint: disable=no-self-use
        """Run all commands in a file."""
        # NOTE: csv module uses native strings. That is, bytes on Python 2 and
        # unicode on Python 3
        with open(filepath, 'r') as csvfile:
            csvreader = csv.reader(csvfile)
            for command in reversed(list(csvreader)):
                try:
                    util.run_script(command)
                except errors.SubprocessError:
                    logger.error(
                        "Unable to run undo command: %s", " ".join(command))

[docs]    def _check_tempfile_saves(self, save_files):
        """Verify save isn't overwriting any temporary files.

        :param set save_files: Set of files about to be saved.

        :raises certbot.errors.ReverterError:
            when save is attempting to overwrite a temporary file.

        """
        protected_files = []

        # Get temp modified files
        temp_path = os.path.join(self.config.temp_checkpoint_dir, "FILEPATHS")
        if os.path.isfile(temp_path):
            with open(temp_path, "r") as protected_fd:
                protected_files.extend(protected_fd.read().splitlines())

        # Get temp new files
        new_path = os.path.join(self.config.temp_checkpoint_dir, "NEW_FILES")
        if os.path.isfile(new_path):
            with open(new_path, "r") as protected_fd:
                protected_files.extend(protected_fd.read().splitlines())

        # Verify no save_file is in protected_files
        for filename in protected_files:
            if filename in save_files:
                raise errors.ReverterError(
                    "Attempting to overwrite challenge "
                    "file - %s" % filename)

[docs]    def register_file_creation(self, temporary, *files):
        r"""Register the creation of all files during certbot execution.

        Call this method before writing to the file to make sure that the
        file will be cleaned up if the program exits unexpectedly.
        (Before a save occurs)

        :param bool temporary: If the file creation registry is for
            a temp or permanent save.
        :param \*files: file paths (str) to be registered

        :raises certbot.errors.ReverterError: If
            call does not contain necessary parameters or if the file creation
            is unable to be registered.

        """
        # Make sure some files are provided... as this is an error
        # Made this mistake in my initial implementation of apache.dvsni.py
        if not files:
            raise errors.ReverterError("Forgot to provide files to registration call")

        cp_dir = self._get_cp_dir(temporary)

        # Append all new files (that aren't already registered)
        new_fd = None
        try:
            new_fd, ex_files = self._read_and_append(os.path.join(cp_dir, "NEW_FILES"))

            for path in files:
                if path not in ex_files:
                    new_fd.write("{0}{1}".format(path, os.linesep))
        except (IOError, OSError):
            logger.error("Unable to register file creation(s) - %s", files)
            raise errors.ReverterError(
                "Unable to register file creation(s) - {0}".format(files))
        finally:
            if new_fd is not None:
                new_fd.close()

[docs]    def register_undo_command(self, temporary, command):
        """Register a command to be run to undo actions taken.

        .. warning:: This function does not enforce order of operations in terms
            of file modification vs. command registration.  All undo commands
            are run first before all normal files are reverted to their previous
            state.  If you need to maintain strict order, you may create
            checkpoints before and after the the command registration. This
            function may be improved in the future based on demand.

        :param bool temporary: Whether the command should be saved in the
            IN_PROGRESS or TEMPORARY checkpoints.
        :param command: Command to be run.
        :type command: list of str

        """
        commands_fp = os.path.join(self._get_cp_dir(temporary), "COMMANDS")
        command_file = None
        try:
            if os.path.isfile(commands_fp):
                command_file = open(commands_fp, "a")
            else:
                command_file = open(commands_fp, "w")

            csvwriter = csv.writer(command_file)
            csvwriter.writerow(command)

        except (IOError, OSError):
            logger.error("Unable to register undo command")
            raise errors.ReverterError(
                "Unable to register undo command.")
        finally:
            if command_file is not None:
                command_file.close()

[docs]    def _get_cp_dir(self, temporary):
        """Return the proper reverter directory."""
        if temporary:
            cp_dir = self.config.temp_checkpoint_dir
        else:
            cp_dir = self.config.in_progress_dir

        util.make_or_verify_dir(
            cp_dir, constants.CONFIG_DIRS_MODE, os.geteuid(),
            self.config.strict_permissions)

        return cp_dir

[docs]    def recovery_routine(self):
        """Revert configuration to most recent finalized checkpoint.

        Remove all changes (temporary and permanent) that have not been
        finalized. This is useful to protect against crashes and other
        execution interruptions.

        :raises .errors.ReverterError: If unable to recover the configuration

        """
        # First, any changes found in IConfig.temp_checkpoint_dir are removed,
        # then IN_PROGRESS changes are removed The order is important.
        # IN_PROGRESS is unable to add files that are already added by a TEMP
        # change.  Thus TEMP must be rolled back first because that will be the
        # 'latest' occurrence of the file.
        self.revert_temporary_config()
        if os.path.isdir(self.config.in_progress_dir):
            try:
                self._recover_checkpoint(self.config.in_progress_dir)
            except errors.ReverterError:
                # We have a partial or incomplete recovery
                logger.fatal("Incomplete or failed recovery for IN_PROGRESS "
                             "checkpoint - %s",
                             self.config.in_progress_dir)
                raise errors.ReverterError(
                    "Incomplete or failed recovery for IN_PROGRESS checkpoint "
                    "- %s" % self.config.in_progress_dir)

[docs]    def _remove_contained_files(self, file_list):  # pylint: disable=no-self-use
        """Erase all files contained within file_list.

        :param str file_list: file containing list of file paths to be deleted

        :returns: Success
        :rtype: bool

        :raises certbot.errors.ReverterError: If
            all files within file_list cannot be removed

        """
        # Check to see that file exists to differentiate can't find file_list
        # and can't remove filepaths within file_list errors.
        if not os.path.isfile(file_list):
            return False
        try:
            with open(file_list, "r") as list_fd:
                filepaths = list_fd.read().splitlines()
                for path in filepaths:
                    # Files are registered before they are added... so
                    # check to see if file exists first
                    if os.path.lexists(path):
                        os.remove(path)
                    else:
                        logger.warning(
                            "File: %s - Could not be found to be deleted %s - "
                            "Certbot probably shut down unexpectedly",
                            os.linesep, path)
        except (IOError, OSError):
            logger.fatal(
                "Unable to remove filepaths contained within %s", file_list)
            raise errors.ReverterError(
                "Unable to remove filepaths contained within "
                "{0}".format(file_list))

        return True

[docs]    def finalize_checkpoint(self, title):
        """Finalize the checkpoint.

        Timestamps and permanently saves all changes made through the use
        of :func:`~add_to_checkpoint` and :func:`~register_file_creation`

        :param str title: Title describing checkpoint

        :raises certbot.errors.ReverterError: when the
            checkpoint is not able to be finalized.

        """
        # Check to make sure an "in progress" directory exists
        if not os.path.isdir(self.config.in_progress_dir):
            return

        changes_since_path = os.path.join(self.config.in_progress_dir, "CHANGES_SINCE")
        changes_since_tmp_path = os.path.join(self.config.in_progress_dir, "CHANGES_SINCE.tmp")

        if not os.path.exists(changes_since_path):
            logger.info("Rollback checkpoint is empty (no changes made?)")
            with open(changes_since_path, 'w') as f:
                f.write("No changes\n")

        # Add title to self.config.in_progress_dir CHANGES_SINCE
        try:
            with open(changes_since_tmp_path, "w") as changes_tmp:
                changes_tmp.write("-- %s --\n" % title)
                with open(changes_since_path, "r") as changes_orig:
                    changes_tmp.write(changes_orig.read())

        # Move self.config.in_progress_dir to Backups directory
            shutil.move(changes_since_tmp_path, changes_since_path)
        except (IOError, OSError):
            logger.error("Unable to finalize checkpoint - adding title")
            logger.debug("Exception was:\n%s", traceback.format_exc())
            raise errors.ReverterError("Unable to add title")

        # rename the directory as a timestamp
        self._timestamp_progress_dir()

[docs]    def _checkpoint_timestamp(self):
        "Determine the timestamp of the checkpoint, enforcing monotonicity."
        timestamp = str(time.time())
        others = glob.glob(os.path.join(self.config.backup_dir, "[0-9]*"))
        others = [os.path.basename(d) for d in others]
        others.append(timestamp)
        others.sort()
        if others[-1] != timestamp:
            timetravel = str(float(others[-1]) + 1)
            logger.warning("Current timestamp %s does not correspond to newest reverter "
                "checkpoint; your clock probably jumped. Time travelling to %s",
                timestamp, timetravel)
            timestamp = timetravel
        elif len(others) > 1 and others[-2] == timestamp:
            # It is possible if the checkpoints are made extremely quickly
            # that will result in a name collision.
            logger.debug("Race condition with timestamp %s, incrementing by 0.01", timestamp)
            timetravel = str(float(others[-1]) + 0.01)
            timestamp = timetravel
        return timestamp

[docs]    def _timestamp_progress_dir(self):
        """Timestamp the checkpoint."""
        # It is possible save checkpoints faster than 1 per second resulting in
        # collisions in the naming convention.

        for _ in six.moves.range(2):
            timestamp = self._checkpoint_timestamp()
            final_dir = os.path.join(self.config.backup_dir, timestamp)
            try:
                os.rename(self.config.in_progress_dir, final_dir)
                return
            except OSError:
                logger.warning("Extreme, unexpected race condition, retrying (%s)", timestamp)

        # After 10 attempts... something is probably wrong here...
        logger.error(
            "Unable to finalize checkpoint, %s -> %s",
            self.config.in_progress_dir, final_dir)
        raise errors.ReverterError(
            "Unable to finalize checkpoint renaming")

Source code for certbot.account

"""Creates ACME accounts for server."""
import datetime
import hashlib
import logging
import os
import shutil
import socket

from cryptography.hazmat.primitives import serialization
import pyrfc3339
import pytz
import six
import zope.component

from acme import fields as acme_fields
from acme import jose
from acme import messages

from certbot import errors
from certbot import interfaces
from certbot import util

logger = logging.getLogger(__name__)

[docs]class Account(object):  # pylint: disable=too-few-public-methods
    """ACME protocol registration.

    :ivar .RegistrationResource regr: Registration Resource
    :ivar .JWK key: Authorized Account Key
    :ivar .Meta: Account metadata
    :ivar str id: Globally unique account identifier.

    """

[docs]    class Meta(jose.JSONObjectWithFields):
        """Account metadata

        :ivar datetime.datetime creation_dt: Creation date and time (UTC).
        :ivar str creation_host: FQDN of host, where account has been created.

        .. note:: ``creation_dt`` and ``creation_host`` are useful in
            cross-machine migration scenarios.

        """
        creation_dt = acme_fields.RFC3339Field("creation_dt")
        creation_host = jose.Field("creation_host")

    def __init__(self, regr, key, meta=None):
        self.key = key
        self.regr = regr
        self.meta = self.Meta(
            # pyrfc3339 drops microseconds, make sure __eq__ is sane
            creation_dt=datetime.datetime.now(
                tz=pytz.UTC).replace(microsecond=0),
            creation_host=socket.getfqdn()) if meta is None else meta

        self.id = hashlib.md5(
            self.key.key.public_key().public_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PublicFormat.SubjectPublicKeyInfo)
        ).hexdigest()
        # Implementation note: Email? Multiple accounts can have the
        # same email address. Registration URI? Assigned by the
        # server, not guaranteed to be stable over time, nor
        # canonical URI can be generated. ACME protocol doesn't allow
        # account key (and thus its fingerprint) to be updated...

    @property
    def slug(self):
        """Short account identification string, useful for UI."""
        return "{1}@{0} ({2})".format(pyrfc3339.generate(
            self.meta.creation_dt), self.meta.creation_host, self.id[:4])

    def __repr__(self):
        return "<{0}({1}, {2}, {3})>".format(
            self.__class__.__name__, self.regr, self.id, self.meta)

    def __eq__(self, other):
        return (isinstance(other, self.__class__) and
                self.key == other.key and self.regr == other.regr and
                self.meta == other.meta)

[docs]def report_new_account(config):
    """Informs the user about their new ACME account."""
    reporter = zope.component.queryUtility(interfaces.IReporter)
    if reporter is None:
        return
    reporter.add_message(
        "Your account credentials have been saved in your Certbot "
        "configuration directory at {0}. You should make a secure backup "
        "of this folder now. This configuration directory will also "
        "contain certificates and private keys obtained by Certbot "
        "so making regular backups of this folder is ideal.".format(
            config.config_dir),
        reporter.MEDIUM_PRIORITY)

[docs]class AccountMemoryStorage(interfaces.AccountStorage):
    """In-memory account storage."""

    def __init__(self, initial_accounts=None):
        self.accounts = initial_accounts if initial_accounts is not None else {}

    def find_all(self):
        return list(six.itervalues(self.accounts))

    def save(self, account, acme):
        # pylint: disable=unused-argument
        if account.id in self.accounts:
            logger.debug("Overwriting account: %s", account.id)
        self.accounts[account.id] = account

    def load(self, account_id):
        try:
            return self.accounts[account_id]
        except KeyError:
            raise errors.AccountNotFound(account_id)

[docs]class RegistrationResourceWithNewAuthzrURI(messages.RegistrationResource):
    """A backwards-compatible RegistrationResource with a new-authz URI.

       Hack: Certbot versions pre-0.11.1 expect to load
       new_authzr_uri as part of the account. Because people
       sometimes switch between old and new versions, we will
       continue to write out this field for some time so older
       clients don't crash in that scenario.
    """
    new_authzr_uri = jose.Field('new_authzr_uri')

[docs]class AccountFileStorage(interfaces.AccountStorage):
    """Accounts file storage.

    :ivar .IConfig config: Client configuration

    """
    def __init__(self, config):
        self.config = config
        util.make_or_verify_dir(config.accounts_dir, 0o700, os.geteuid(),
                                   self.config.strict_permissions)

    def _account_dir_path(self, account_id):
        return os.path.join(self.config.accounts_dir, account_id)

    @classmethod
    def _regr_path(cls, account_dir_path):
        return os.path.join(account_dir_path, "regr.json")

    @classmethod
    def _key_path(cls, account_dir_path):
        return os.path.join(account_dir_path, "private_key.json")

    @classmethod
    def _metadata_path(cls, account_dir_path):
        return os.path.join(account_dir_path, "meta.json")

    def find_all(self):
        try:
            candidates = os.listdir(self.config.accounts_dir)
        except OSError:
            return []

        accounts = []
        for account_id in candidates:
            try:
                accounts.append(self.load(account_id))
            except errors.AccountStorageError:
                logger.debug("Account loading problem", exc_info=True)
        return accounts

    def load(self, account_id):
        account_dir_path = self._account_dir_path(account_id)
        if not os.path.isdir(account_dir_path):
            raise errors.AccountNotFound(
                "Account at %s does not exist" % account_dir_path)

        try:
            with open(self._regr_path(account_dir_path)) as regr_file:
                regr = messages.RegistrationResource.json_loads(regr_file.read())
            with open(self._key_path(account_dir_path)) as key_file:
                key = jose.JWK.json_loads(key_file.read())
            with open(self._metadata_path(account_dir_path)) as metadata_file:
                meta = Account.Meta.json_loads(metadata_file.read())
        except IOError as error:
            raise errors.AccountStorageError(error)

        acc = Account(regr, key, meta)
        if acc.id != account_id:
            raise errors.AccountStorageError(
                "Account ids mismatch (expected: {0}, found: {1}".format(
                    account_id, acc.id))
        return acc

    def save(self, account, acme):
        self._save(account, acme, regr_only=False)

[docs]    def save_regr(self, account, acme):
        """Save the registration resource.

        :param Account account: account whose regr should be saved

        """
        self._save(account, acme, regr_only=True)

[docs]    def delete(self, account_id):
        """Delete registration info from disk

        :param account_id: id of account which should be deleted

        """
        account_dir_path = self._account_dir_path(account_id)
        if not os.path.isdir(account_dir_path):
            raise errors.AccountNotFound(
                "Account at %s does not exist" % account_dir_path)
        shutil.rmtree(account_dir_path)

    def _save(self, account, acme, regr_only):
        account_dir_path = self._account_dir_path(account.id)
        util.make_or_verify_dir(account_dir_path, 0o700, os.geteuid(),
                                self.config.strict_permissions)
        try:
            with open(self._regr_path(account_dir_path), "w") as regr_file:
                regr = account.regr
                with_uri = RegistrationResourceWithNewAuthzrURI(
                    new_authzr_uri=acme.directory.new_authz,
                    body=regr.body,
                    uri=regr.uri,
                    terms_of_service=regr.terms_of_service)
                regr_file.write(with_uri.json_dumps())
            if not regr_only:
                with util.safe_open(self._key_path(account_dir_path),
                                    "w", chmod=0o400) as key_file:
                    key_file.write(account.key.json_dumps())
                with open(self._metadata_path(
                        account_dir_path), "w") as metadata_file:
                    metadata_file.write(account.meta.json_dumps())
        except IOError as error:
            raise errors.AccountStorageError(error)

Source code for certbot.errors

"""Certbot client errors."""

[docs]class Error(Exception):
    """Generic Certbot client error."""

[docs]class AccountStorageError(Error):
    """Generic `.AccountStorage` error."""

[docs]class AccountNotFound(AccountStorageError):
    """Account not found error."""

[docs]class ReverterError(Error):
    """Certbot Reverter error."""

[docs]class SubprocessError(Error):
    """Subprocess handling error."""

[docs]class CertStorageError(Error):
    """Generic `.CertStorage` error."""

[docs]class HookCommandNotFound(Error):
    """Failed to find a hook command in the PATH."""

[docs]class SignalExit(Error):
    """A Unix signal was received while in the ErrorHandler context manager."""

[docs]class LockError(Error):
    """File locking error."""

# Auth Handler Errors

[docs]class AuthorizationError(Error):
    """Authorization error."""

[docs]class FailedChallenges(AuthorizationError):
    """Failed challenges error.

    :ivar set failed_achalls: Failed `.AnnotatedChallenge` instances.

    """
    def __init__(self, failed_achalls):
        assert failed_achalls
        self.failed_achalls = failed_achalls
        super(FailedChallenges, self).__init__()

    def __str__(self):
        return "Failed authorization procedure. {0}".format(
            ", ".join(
                "{0} ({1}): {2}".format(achall.domain, achall.typ, achall.error)
                for achall in self.failed_achalls if achall.error is not None))

# Plugin Errors

[docs]class PluginError(Error):
    """Certbot Plugin error."""

[docs]class PluginEnhancementAlreadyPresent(Error):
    """ Enhancement was already set """

[docs]class PluginSelectionError(Error):
    """A problem with plugin/configurator selection or setup"""

[docs]class NoInstallationError(PluginError):
    """Certbot No Installation error."""

[docs]class MisconfigurationError(PluginError):
    """Certbot Misconfiguration error."""

[docs]class NotSupportedError(PluginError):
    """Certbot Plugin function not supported error."""

[docs]class StandaloneBindError(Error):
    """Standalone plugin bind error."""

    def __init__(self, socket_error, port):
        super(StandaloneBindError, self).__init__(
            "Problem binding to port {0}: {1}".format(port, socket_error))
        self.socket_error = socket_error
        self.port = port

[docs]class ConfigurationError(Error):
    """Configuration sanity error."""

# NoninteractiveDisplay iDisplay plugin error:

[docs]class MissingCommandlineFlag(Error):
    """A command line argument was missing in noninteractive usage"""

Source code for certbot.storage

"""Renewable certificates storage."""
import datetime
import glob
import logging
import os
import re
import stat

import configobj
import parsedatetime
import pytz
import shutil
import six

import certbot
from certbot import cli
from certbot import constants
from certbot import crypto_util
from certbot import errors
from certbot import error_handler
from certbot import util

from certbot.plugins import common as plugins_common
from certbot.plugins import disco as plugins_disco

logger = logging.getLogger(__name__)

ALL_FOUR = ("cert", "privkey", "chain", "fullchain")
README = "README"
CURRENT_VERSION = util.get_strict_version(certbot.__version__)

[docs]def renewal_conf_files(config):
    """Build a list of all renewal configuration files.

    :param certbot.interfaces.IConfig config: Configuration object

    :returns: list of renewal configuration files
    :rtype: `list` of `str`

    """
    return glob.glob(os.path.join(config.renewal_configs_dir, "*.conf"))

[docs]def renewal_file_for_certname(config, certname):
    """Return /path/to/certname.conf in the renewal conf directory"""
    path = os.path.join(config.renewal_configs_dir, "{0}.conf".format(certname))
    if not os.path.exists(path):
        raise errors.CertStorageError("No certificate found with name {0} (expected "
            "{1}).".format(certname, path))
    return path

[docs]def config_with_defaults(config=None):
    """Merge supplied config, if provided, on top of builtin defaults."""
    defaults_copy = configobj.ConfigObj(constants.RENEWER_DEFAULTS)
    defaults_copy.merge(config if config is not None else configobj.ConfigObj())
    return defaults_copy

[docs]def add_time_interval(base_time, interval, textparser=parsedatetime.Calendar()):
    """Parse the time specified time interval, and add it to the base_time

    The interval can be in the English-language format understood by
    parsedatetime, e.g., '10 days', '3 weeks', '6 months', '9 hours', or
    a sequence of such intervals like '6 months 1 week' or '3 days 12
    hours'. If an integer is found with no associated unit, it is
    interpreted by default as a number of days.

    :param datetime.datetime base_time: The time to be added with the interval.
    :param str interval: The time interval to parse.

    :returns: The base_time plus the interpretation of the time interval.
    :rtype: :class:`datetime.datetime`"""

    if interval.strip().isdigit():
        interval += " days"

    # try to use the same timezone, but fallback to UTC
    tzinfo = base_time.tzinfo or pytz.UTC

    return textparser.parseDT(interval, base_time, tzinfo=tzinfo)[0]

[docs]def write_renewal_config(o_filename, n_filename, archive_dir, target, relevant_data):
    """Writes a renewal config file with the specified name and values.

    :param str o_filename: Absolute path to the previous version of config file
    :param str n_filename: Absolute path to the new destination of config file
    :param str archive_dir: Absolute path to the archive directory
    :param dict target: Maps ALL_FOUR to their symlink paths
    :param dict relevant_data: Renewal configuration options to save

    :returns: Configuration object for the new config file
    :rtype: configobj.ConfigObj

    """
    config = configobj.ConfigObj(o_filename)
    config["version"] = certbot.__version__
    config["archive_dir"] = archive_dir
    for kind in ALL_FOUR:
        config[kind] = target[kind]

    if "renewalparams" not in config:
        config["renewalparams"] = {}
        config.comments["renewalparams"] = ["",
                                            "Options used in "
                                            "the renewal process"]

    config["renewalparams"].update(relevant_data)

    for k in config["renewalparams"].keys():
        if k not in relevant_data:
            del config["renewalparams"][k]

    if "renew_before_expiry" not in config:
        default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"]
        config.initial_comment = ["renew_before_expiry = " + default_interval]

    # TODO: add human-readable comments explaining other available
    #       parameters
    logger.debug("Writing new config %s.", n_filename)

    # Ensure that the file exists
    open(n_filename, 'a').close()

    # Copy permissions from the old version of the file, if it exists.
    if os.path.exists(o_filename):
        current_permissions = stat.S_IMODE(os.lstat(o_filename).st_mode)
        os.chmod(n_filename, current_permissions)

    with open(n_filename, "wb") as f:
        config.write(outfile=f)
    return config

[docs]def rename_renewal_config(prev_name, new_name, cli_config):
    """Renames cli_config.certname's config to cli_config.new_certname.

    :param .NamespaceConfig cli_config: parsed command line
        arguments
    """
    prev_filename = renewal_filename_for_lineagename(cli_config, prev_name)
    new_filename = renewal_filename_for_lineagename(cli_config, new_name)
    if os.path.exists(new_filename):
        raise errors.ConfigurationError("The new certificate name "
            "is already in use.")
    try:
        os.rename(prev_filename, new_filename)
    except OSError:
        raise errors.ConfigurationError("Please specify a valid filename "
            "for the new certificate name.")

[docs]def update_configuration(lineagename, archive_dir, target, cli_config):
    """Modifies lineagename's config to contain the specified values.

    :param str lineagename: Name of the lineage being modified
    :param str archive_dir: Absolute path to the archive directory
    :param dict target: Maps ALL_FOUR to their symlink paths
    :param .NamespaceConfig cli_config: parsed command line
        arguments

    :returns: Configuration object for the updated config file
    :rtype: configobj.ConfigObj

    """
    config_filename = renewal_filename_for_lineagename(cli_config, lineagename)
    temp_filename = config_filename + ".new"

    # If an existing tempfile exists, delete it
    if os.path.exists(temp_filename):
        os.unlink(temp_filename)

    # Save only the config items that are relevant to renewal
    values = relevant_values(vars(cli_config.namespace))
    write_renewal_config(config_filename, temp_filename, archive_dir, target, values)
    os.rename(temp_filename, config_filename)

    return configobj.ConfigObj(config_filename)

[docs]def get_link_target(link):
    """Get an absolute path to the target of link.

    :param str link: Path to a symbolic link

    :returns: Absolute path to the target of link
    :rtype: str

    """
    target = os.readlink(link)
    if not os.path.isabs(target):
        target = os.path.join(os.path.dirname(link), target)
    return os.path.abspath(target)

[docs]def _relevant(option):
    """
    Is this option one that could be restored for future renewal purposes?
    :param str option: the name of the option

    :rtype: bool
    """
    from certbot import renewal
    plugins = plugins_disco.PluginsRegistry.find_all()
    namespaces = [plugins_common.dest_namespace(plugin) for plugin in plugins]

    return (option in renewal.CONFIG_ITEMS or
            any(option.startswith(namespace) for namespace in namespaces))

[docs]def relevant_values(all_values):
    """Return a new dict containing only items relevant for renewal.

    :param dict all_values: The original values.

    :returns: A new dictionary containing items that can be used in renewal.
    :rtype dict:

    """
    return dict(
        (option, value)
        for option, value in six.iteritems(all_values)
        if _relevant(option) and cli.option_was_set(option, value))

[docs]def lineagename_for_filename(config_filename):
    """Returns the lineagename for a configuration filename.
    """
    if not config_filename.endswith(".conf"):
        raise errors.CertStorageError(
            "renewal config file name must end in .conf")
    return os.path.basename(config_filename[:-len(".conf")])

[docs]def renewal_filename_for_lineagename(config, lineagename):
    """Returns the lineagename for a configuration filename.
    """
    return os.path.join(config.renewal_configs_dir, lineagename) + ".conf"

[docs]def _relpath_from_file(archive_dir, from_file):
    """Path to a directory from a file"""
    return os.path.relpath(archive_dir, os.path.dirname(from_file))

[docs]def _full_archive_path(config_obj, cli_config, lineagename):
    """Returns the full archive path for a lineagename

    Uses cli_config to determine archive path if not available from config_obj.

    :param configobj.ConfigObj config_obj: Renewal conf file contents (can be None)
    :param configuration.NamespaceConfig cli_config: Main config file
    :param str lineagename: Certificate name
    """
    if config_obj and "archive_dir" in config_obj:
        return config_obj["archive_dir"]
    else:
        return os.path.join(cli_config.default_archive_dir, lineagename)

[docs]def _full_live_path(cli_config, lineagename):
    """Returns the full default live path for a lineagename"""
    return os.path.join(cli_config.live_dir, lineagename)

[docs]def delete_files(config, certname):
    """Delete all files related to the certificate.

    If some files are not found, ignore them and continue.
    """
    renewal_filename = renewal_file_for_certname(config, certname)
    # file exists
    full_default_archive_dir = _full_archive_path(None, config, certname)
    full_default_live_dir = _full_live_path(config, certname)
    try:
        renewal_config = configobj.ConfigObj(renewal_filename)
    except configobj.ConfigObjError:
        # config is corrupted
        logger.warning("Could not parse %s. You may wish to manually "
            "delete the contents of %s and %s.", renewal_filename,
            full_default_live_dir, full_default_archive_dir)
        raise errors.CertStorageError(
            "error parsing {0}".format(renewal_filename))
    finally:
        # we couldn't read it, but let's at least delete it
        # if this was going to fail, it already would have.
        os.remove(renewal_filename)
        logger.debug("Removed %s", renewal_filename)

    # cert files and (hopefully) live directory
    # it's not guaranteed that the files are in our default storage
    # structure. so, first delete the cert files.
    directory_names = set()
    for kind in ALL_FOUR:
        link = renewal_config.get(kind)
        try:
            os.remove(link)
            logger.debug("Removed %s", link)
        except OSError:
            logger.debug("Unable to delete %s", link)
        directory = os.path.dirname(link)
        directory_names.add(directory)

    # if all four were in the same directory, and the only thing left
    # is the README file (or nothing), delete that directory.
    # this will be wrong in very few but some cases.
    if len(directory_names) == 1:
        # delete the README file
        directory = directory_names.pop()
        readme_path = os.path.join(directory, README)
        try:
            os.remove(readme_path)
            logger.debug("Removed %s", readme_path)
        except OSError:
            logger.debug("Unable to delete %s", readme_path)
        # if it's now empty, delete the directory
        try:
            os.rmdir(directory) # only removes empty directories
            logger.debug("Removed %s", directory)
        except OSError:
            logger.debug("Unable to remove %s; may not be empty.", directory)

    # archive directory
    try:
        archive_path = _full_archive_path(renewal_config, config, certname)
        shutil.rmtree(archive_path)
        logger.debug("Removed %s", archive_path)
    except OSError:
        logger.debug("Unable to remove %s", archive_path)

[docs]class RenewableCert(object):
    # pylint: disable=too-many-instance-attributes,too-many-public-methods
    """Renewable certificate.

    Represents a lineage of certificates that is under the management of
    Certbot, indicated by the existence of an associated renewal
    configuration file.

    Note that the notion of "current version" for a lineage is
    maintained on disk in the structure of symbolic links, and is not
    explicitly stored in any instance variable in this object. The
    RenewableCert object is able to determine information about the
    current (or other) version by accessing data on disk, but does not
    inherently know any of this information except by examining the
    symbolic links as needed. The instance variables mentioned below
    point to symlinks that reflect the notion of "current version" of
    each managed object, and it is these paths that should be used when
    configuring servers to use the certificate managed in a lineage.
    These paths are normally within the "live" directory, and their
    symlink targets -- the actual cert files -- are normally found
    within the "archive" directory.

    :ivar str cert: The path to the symlink representing the current
        version of the certificate managed by this lineage.
    :ivar str privkey: The path to the symlink representing the current
        version of the private key managed by this lineage.
    :ivar str chain: The path to the symlink representing the current version
        of the chain managed by this lineage.
    :ivar str fullchain: The path to the symlink representing the
        current version of the fullchain (combined chain and cert)
        managed by this lineage.
    :ivar configobj.ConfigObj configuration: The renewal configuration
        options associated with this lineage, obtained from parsing the
        renewal configuration file and/or systemwide defaults.

    """
    def __init__(self, config_filename, cli_config, update_symlinks=False):
        """Instantiate a RenewableCert object from an existing lineage.

        :param str config_filename: the path to the renewal config file
            that defines this lineage.
        :param .NamespaceConfig: parsed command line arguments

        :raises .CertStorageError: if the configuration file's name didn't end
            in ".conf", or the file is missing or broken.

        """
        self.cli_config = cli_config
        self.lineagename = lineagename_for_filename(config_filename)

        # self.configuration should be used to read parameters that
        # may have been chosen based on default values from the
        # systemwide renewal configuration; self.configfile should be
        # used to make and save changes.
        try:
            self.configfile = configobj.ConfigObj(config_filename)
        except configobj.ConfigObjError:
            raise errors.CertStorageError(
                "error parsing {0}".format(config_filename))
        # TODO: Do we actually use anything from defaults and do we want to
        #       read further defaults from the systemwide renewal configuration
        #       file at this stage?
        self.configuration = config_with_defaults(self.configfile)

        if not all(x in self.configuration for x in ALL_FOUR):
            raise errors.CertStorageError(
                "renewal config file {0} is missing a required "
                "file reference".format(self.configfile))

        conf_version = self.configuration.get("version")
        if (conf_version is not None and
                util.get_strict_version(conf_version) > CURRENT_VERSION):
            logger.warning(
                "Attempting to parse the version %s renewal configuration "
                "file found at %s with version %s of Certbot. This might not "
                "work.", conf_version, config_filename, certbot.__version__)

        self.cert = self.configuration["cert"]
        self.privkey = self.configuration["privkey"]
        self.chain = self.configuration["chain"]
        self.fullchain = self.configuration["fullchain"]
        self.live_dir = os.path.dirname(self.cert)

        self._fix_symlinks()
        if update_symlinks:
            self._update_symlinks()
        self._check_symlinks()

    @property
    def key_path(self):
        """Duck type for self.privkey"""
        return self.privkey

    @property
    def cert_path(self):
        """Duck type for self.cert"""
        return self.cert

    @property
    def chain_path(self):
        """Duck type for self.chain"""
        return self.chain

    @property
    def fullchain_path(self):
        """Duck type for self.fullchain"""
        return self.fullchain

    @property
    def target_expiry(self):
        """The current target certificate's expiration datetime

        :returns: Expiration datetime of the current target certificate
        :rtype: :class:`datetime.datetime`
        """
        return crypto_util.notAfter(self.current_target("cert"))

    @property
    def archive_dir(self):
        """Returns the default or specified archive directory"""
        return _full_archive_path(self.configuration,
            self.cli_config, self.lineagename)

[docs]    def relative_archive_dir(self, from_file):
        """Returns the default or specified archive directory as a relative path

        Used for creating symbolic links.
        """
        return _relpath_from_file(self.archive_dir, from_file)

    @property
    def is_test_cert(self):
        """Returns true if this is a test cert from a staging server."""
        server = self.configuration["renewalparams"].get("server", None)
        if server:
            return util.is_staging(server)
        else:
            return False

[docs]    def _check_symlinks(self):
        """Raises an exception if a symlink doesn't exist"""
        for kind in ALL_FOUR:
            link = getattr(self, kind)
            if not os.path.islink(link):
                raise errors.CertStorageError(
                    "expected {0} to be a symlink".format(link))
            target = get_link_target(link)
            if not os.path.exists(target):
                raise errors.CertStorageError("target {0} of symlink {1} does "
                                              "not exist".format(target, link))

[docs]    def _update_symlinks(self):
        """Updates symlinks to use archive_dir"""
        for kind in ALL_FOUR:
            link = getattr(self, kind)
            previous_link = get_link_target(link)
            new_link = os.path.join(self.relative_archive_dir(link),
                os.path.basename(previous_link))

            os.unlink(link)
            os.symlink(new_link, link)

[docs]    def _consistent(self):
        """Are the files associated with this lineage self-consistent?

        :returns: Whether the files stored in connection with this
            lineage appear to be correct and consistent with one
            another.
        :rtype: bool

        """
        # Each element must be referenced with an absolute path
        for x in (self.cert, self.privkey, self.chain, self.fullchain):
            if not os.path.isabs(x):
                logger.debug("Element %s is not referenced with an "
                             "absolute path.", x)
                return False

        # Each element must exist and be a symbolic link
        for x in (self.cert, self.privkey, self.chain, self.fullchain):
            if not os.path.islink(x):
                logger.debug("Element %s is not a symbolic link.", x)
                return False
        for kind in ALL_FOUR:
            link = getattr(self, kind)
            target = get_link_target(link)

            # Each element's link must point within the cert lineage's
            # directory within the official archive directory
            if not os.path.samefile(os.path.dirname(target), self.archive_dir):
                logger.debug("Element's link does not point within the "
                             "cert lineage's directory within the "
                             "official archive directory. Link: %s, "
                             "target directory: %s, "
                             "archive directory: %s. If you've specified "
                             "the archive directory in the renewal configuration "
                             "file, you may need to update links by running "
                             "certbot update_symlinks.",
                             link, os.path.dirname(target), self.archive_dir)
                return False

            # The link must point to a file that exists
            if not os.path.exists(target):
                logger.debug("Link %s points to file %s that does not exist.",
                             link, target)
                return False

            # The link must point to a file that follows the archive
            # naming convention
            pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind))
            if not pattern.match(os.path.basename(target)):
                logger.debug("%s does not follow the archive naming "
                             "convention.", target)
                return False

            # It is NOT required that the link's target be a regular
            # file (it may itself be a symlink). But we should probably
            # do a recursive check that ultimately the target does
            # exist?
        # XXX: Additional possible consistency checks (e.g.
        #      cryptographic validation of the chain being a chain,
        #      the chain matching the cert, and the cert matching
        #      the subject key)
        # XXX: All four of the targets are in the same directory
        #      (This check is redundant with the check that they
        #      are all in the desired directory!)
        #      len(set(os.path.basename(self.current_target(x)
        #      for x in ALL_FOUR))) == 1
        return True

[docs]    def _fix(self):
        """Attempt to fix defects or inconsistencies in this lineage.

        .. todo:: Currently unimplemented.

        """

        # TODO: Figure out what kinds of fixes are possible.  For
        #       example, checking if there is a valid version that
        #       we can update the symlinks to.  (Maybe involve
        #       parsing keys and certs to see if they exist and
        #       if a key corresponds to the subject key of a cert?)

    # TODO: In general, the symlink-reading functions below are not
    #       cautious enough about the possibility that links or their
    #       targets may not exist.  (This shouldn't happen, but might
    #       happen as a result of random tampering by a sysadmin, or
    #       filesystem errors, or crashes.)

[docs]    def _previous_symlinks(self):
        """Returns the kind and path of all symlinks used in recovery.

        :returns: list of (kind, symlink) tuples
        :rtype: list

        """
        previous_symlinks = []
        for kind in ALL_FOUR:
            link_dir = os.path.dirname(getattr(self, kind))
            link_base = "previous_{0}.pem".format(kind)
            previous_symlinks.append((kind, os.path.join(link_dir, link_base)))

        return previous_symlinks

[docs]    def _fix_symlinks(self):
        """Fixes symlinks in the event of an incomplete version update.

        If there is no problem with the current symlinks, this function
        has no effect.

        """
        previous_symlinks = self._previous_symlinks()
        if all(os.path.exists(link[1]) for link in previous_symlinks):
            for kind, previous_link in previous_symlinks:
                current_link = getattr(self, kind)
                if os.path.lexists(current_link):
                    os.unlink(current_link)
                os.symlink(os.readlink(previous_link), current_link)

        for _, link in previous_symlinks:
            if os.path.exists(link):
                os.unlink(link)

[docs]    def current_target(self, kind):
        """Returns full path to which the specified item currently points.

        :param str kind: the lineage member item ("cert", "privkey",
            "chain", or "fullchain")

        :returns: The path to the current version of the specified
            member.
        :rtype: str or None

        """
        if kind not in ALL_FOUR:
            raise errors.CertStorageError("unknown kind of item")
        link = getattr(self, kind)
        if not os.path.exists(link):
            logger.debug("Expected symlink %s for %s does not exist.",
                         link, kind)
            return None
        return get_link_target(link)

[docs]    def current_version(self, kind):
        """Returns numerical version of the specified item.

        For example, if kind is "chain" and the current chain link
        points to a file named "chain7.pem", returns the integer 7.

        :param str kind: the lineage member item ("cert", "privkey",
            "chain", or "fullchain")

        :returns: the current version of the specified member.
        :rtype: int

        """
        if kind not in ALL_FOUR:
            raise errors.CertStorageError("unknown kind of item")
        pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind))
        target = self.current_target(kind)
        if target is None or not os.path.exists(target):
            logger.debug("Current-version target for %s "
                         "does not exist at %s.", kind, target)
            target = ""
        matches = pattern.match(os.path.basename(target))
        if matches:
            return int(matches.groups()[0])
        else:
            logger.debug("No matches for target %s.", kind)
            return None

[docs]    def version(self, kind, version):
        """The filename that corresponds to the specified version and kind.

        .. warning:: The specified version may not exist in this
           lineage. There is no guarantee that the file path returned
           by this method actually exists.

        :param str kind: the lineage member item ("cert", "privkey",
            "chain", or "fullchain")
        :param int version: the desired version

        :returns: The path to the specified version of the specified member.
        :rtype: str

        """
        if kind not in ALL_FOUR:
            raise errors.CertStorageError("unknown kind of item")
        where = os.path.dirname(self.current_target(kind))
        return os.path.join(where, "{0}{1}.pem".format(kind, version))

[docs]    def available_versions(self, kind):
        """Which alternative versions of the specified kind of item exist?

        The archive directory where the current version is stored is
        consulted to obtain the list of alternatives.

        :param str kind: the lineage member item (
            ``cert``, ``privkey``, ``chain``, or ``fullchain``)

        :returns: all of the version numbers that currently exist
        :rtype: `list` of `int`

        """
        if kind not in ALL_FOUR:
            raise errors.CertStorageError("unknown kind of item")
        where = os.path.dirname(self.current_target(kind))
        files = os.listdir(where)
        pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind))
        matches = [pattern.match(f) for f in files]
        return sorted([int(m.groups()[0]) for m in matches if m])

[docs]    def newest_available_version(self, kind):
        """Newest available version of the specified kind of item?

        :param str kind: the lineage member item (``cert``,
            ``privkey``, ``chain``, or ``fullchain``)

        :returns: the newest available version of this member
        :rtype: int

        """
        return max(self.available_versions(kind))

[docs]    def latest_common_version(self):
        """Newest version for which all items are available?

        :returns: the newest available version for which all members
            (``cert, ``privkey``, ``chain``, and ``fullchain``) exist
        :rtype: int

        """
        # TODO: this can raise CertStorageError if there is no version overlap
        #       (it should probably return None instead)
        # TODO: this can raise a spurious AttributeError if the current
        #       link for any kind is missing (it should probably return None)
        versions = [self.available_versions(x) for x in ALL_FOUR]
        return max(n for n in versions[0] if all(n in v for v in versions[1:]))

[docs]    def next_free_version(self):
        """Smallest version newer than all full or partial versions?

        :returns: the smallest version number that is larger than any
            version of any item currently stored in this lineage
        :rtype: int

        """
        # TODO: consider locking/mutual exclusion between updating processes
        # This isn't self.latest_common_version() + 1 because we don't want
        # collide with a version that might exist for one file type but not
        # for the others.
        return max(self.newest_available_version(x) for x in ALL_FOUR) + 1

[docs]    def ensure_deployed(self):
        """Make sure we've deployed the latest version.

        :returns: False if a change was needed, True otherwise
        :rtype: bool

        May need to recover from rare interrupted / crashed states."""

        if self.has_pending_deployment():
            logger.warn("Found a new cert /archive/ that was not linked to in /live/; "
                        "fixing...")
            self.update_all_links_to(self.latest_common_version())
            return False
        return True

[docs]    def has_pending_deployment(self):
        """Is there a later version of all of the managed items?

        :returns: ``True`` if there is a complete version of this
            lineage with a larger version number than the current
            version, and ``False`` otherwise
        :rtype: bool

        """
        # TODO: consider whether to assume consistency or treat
        #       inconsistent/consistent versions differently
        smallest_current = min(self.current_version(x) for x in ALL_FOUR)
        return smallest_current < self.latest_common_version()

[docs]    def _update_link_to(self, kind, version):
        """Make the specified item point at the specified version.

        (Note that this method doesn't verify that the specified version
        exists.)

        :param str kind: the lineage member item ("cert", "privkey",
            "chain", or "fullchain")
        :param int version: the desired version

        """
        if kind not in ALL_FOUR:
            raise errors.CertStorageError("unknown kind of item")
        link = getattr(self, kind)
        filename = "{0}{1}.pem".format(kind, version)
        # Relative rather than absolute target directory
        target_directory = os.path.dirname(os.readlink(link))
        # TODO: it could be safer to make the link first under a temporary
        #       filename, then unlink the old link, then rename the new link
        #       to the old link; this ensures that this process is able to
        #       create symlinks.
        # TODO: we might also want to check consistency of related links
        #       for the other corresponding items
        os.unlink(link)
        os.symlink(os.path.join(target_directory, filename), link)

[docs]    def update_all_links_to(self, version):
        """Change all member objects to point to the specified version.

        :param int version: the desired version

        """
        with error_handler.ErrorHandler(self._fix_symlinks):
            previous_links = self._previous_symlinks()
            for kind, link in previous_links:
                os.symlink(self.current_target(kind), link)

            for kind in ALL_FOUR:
                self._update_link_to(kind, version)

            for _, link in previous_links:
                os.unlink(link)

[docs]    def names(self, version=None):
        """What are the subject names of this certificate?

        (If no version is specified, use the current version.)

        :param int version: the desired version number
        :returns: the subject names
        :rtype: `list` of `str`
        :raises .CertStorageError: if could not find cert file.

        """
        if version is None:
            target = self.current_target("cert")
        else:
            target = self.version("cert", version)
        if target is None:
            raise errors.CertStorageError("could not find cert file")
        with open(target) as f:
            return crypto_util.get_names_from_cert(f.read())

[docs]    def autodeployment_is_enabled(self):
        """Is automatic deployment enabled for this cert?

        If autodeploy is not specified, defaults to True.

        :returns: True if automatic deployment is enabled
        :rtype: bool

        """
        return ("autodeploy" not in self.configuration or
                self.configuration.as_bool("autodeploy"))

[docs]    def should_autodeploy(self, interactive=False):
        """Should this lineage now automatically deploy a newer version?

        This is a policy question and does not only depend on whether
        there is a newer version of the cert. (This considers whether
        autodeployment is enabled, whether a relevant newer version
        exists, and whether the time interval for autodeployment has
        been reached.)

        :param bool interactive: set to True to examine the question
            regardless of whether the renewal configuration allows
            automated deployment (for interactive use). Default False.

        :returns: whether the lineage now ought to autodeploy an
            existing newer cert version
        :rtype: bool

        """
        if interactive or self.autodeployment_is_enabled():
            if self.has_pending_deployment():
                interval = self.configuration.get("deploy_before_expiry",
                                                  "5 days")
                now = pytz.UTC.fromutc(datetime.datetime.utcnow())
                if self.target_expiry < add_time_interval(now, interval):
                    return True
        return False

[docs]    def ocsp_revoked(self, version=None):
        # pylint: disable=no-self-use,unused-argument
        """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.

        :param int version: the desired version number

        :returns: whether the certificate is or will be revoked
        :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

[docs]    def autorenewal_is_enabled(self):
        """Is automatic renewal enabled for this cert?

        If autorenew is not specified, defaults to True.

        :returns: True if automatic renewal is enabled
        :rtype: bool

        """
        return ("autorenew" not in self.configuration or
                self.configuration.as_bool("autorenew"))

[docs]    def should_autorenew(self, interactive=False):
        """Should we now try to autorenew the most recent cert version?

        This is a policy question and does not only depend on whether
        the cert is expired. (This considers whether autorenewal is
        enabled, whether the cert is revoked, and whether the time
        interval for autorenewal has been reached.)

        Note that this examines the numerically most recent cert version,
        not the currently deployed version.

        :param bool interactive: set to True to examine the question
            regardless of whether the renewal configuration allows
            automated renewal (for interactive use). Default False.

        :returns: whether an attempt should now be made to autorenew the
            most current cert version in this lineage
        :rtype: bool

        """
        if interactive or self.autorenewal_is_enabled():
            # Consider whether to attempt to autorenew this cert now

            # Renewals on the basis of revocation
            if self.ocsp_revoked(self.latest_common_version()):
                logger.debug("Should renew, certificate is revoked.")
                return True

            # Renews some period before expiry time
            default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"]
            interval = self.configuration.get("renew_before_expiry", default_interval)
            expiry = crypto_util.notAfter(self.version(
                "cert", self.latest_common_version()))
            now = pytz.UTC.fromutc(datetime.datetime.utcnow())
            if expiry < add_time_interval(now, interval):
                logger.debug("Should renew, less than %s before certificate "
                             "expiry %s.", interval,
                             expiry.strftime("%Y-%m-%d %H:%M:%S %Z"))
                return True
        return False

    @classmethod

[docs]    def new_lineage(cls, lineagename, cert, privkey, chain, cli_config):
        # pylint: disable=too-many-locals
        """Create a new certificate lineage.

        Attempts to create a certificate lineage -- enrolled for
        potential future renewal -- with the (suggested) lineage name
        lineagename, and the associated cert, privkey, and chain (the
        associated fullchain will be created automatically). Optional
        configurator and renewalparams record the configuration that was
        originally used to obtain this cert, so that it can be reused
        later during automated renewal.

        Returns a new RenewableCert object referring to the created
        lineage. (The actual lineage name, as well as all the relevant
        file paths, will be available within this object.)

        :param str lineagename: the suggested name for this lineage
            (normally the current cert's first subject DNS name)
        :param str cert: the initial certificate version in PEM format
        :param str privkey: the private key in PEM format
        :param str chain: the certificate chain in PEM format
        :param .NamespaceConfig cli_config: parsed command line
            arguments

        :returns: the newly-created RenewalCert object
        :rtype: :class:`storage.renewableCert`

        """

        # Examine the configuration and find the new lineage's name
        for i in (cli_config.renewal_configs_dir, cli_config.default_archive_dir,
                  cli_config.live_dir):
            if not os.path.exists(i):
                os.makedirs(i, 0o700)
                logger.debug("Creating directory %s.", i)
        config_file, config_filename = util.unique_lineage_name(
            cli_config.renewal_configs_dir, lineagename)

        # Determine where on disk everything will go
        # lineagename will now potentially be modified based on which
        # renewal configuration file could actually be created
        lineagename = lineagename_for_filename(config_filename)
        archive = _full_archive_path(None, cli_config, lineagename)
        live_dir = _full_live_path(cli_config, lineagename)
        if os.path.exists(archive):
            raise errors.CertStorageError(
                "archive directory exists for " + lineagename)
        if os.path.exists(live_dir):
            raise errors.CertStorageError(
                "live directory exists for " + lineagename)
        os.mkdir(archive)
        os.mkdir(live_dir)
        logger.debug("Archive directory %s and live "
                     "directory %s created.", archive, live_dir)

        # Put the data into the appropriate files on disk
        target = dict([(kind, os.path.join(live_dir, kind + ".pem"))
                       for kind in ALL_FOUR])
        for kind in ALL_FOUR:
            os.symlink(os.path.join(_relpath_from_file(archive, target[kind]), kind + "1.pem"),
                       target[kind])
        with open(target["cert"], "wb") as f:
            logger.debug("Writing certificate to %s.", target["cert"])
            f.write(cert)
        with open(target["privkey"], "wb") as f:
            logger.debug("Writing private key to %s.", target["privkey"])
            f.write(privkey)
            # XXX: Let's make sure to get the file permissions right here
        with open(target["chain"], "wb") as f:
            logger.debug("Writing chain to %s.", target["chain"])
            f.write(chain)
        with open(target["fullchain"], "wb") as f:
            # assumes that OpenSSL.crypto.dump_certificate includes
            # ending newline character
            logger.debug("Writing full chain to %s.", target["fullchain"])
            f.write(cert + chain)

        # Write a README file to the live directory
        readme_path = os.path.join(live_dir, README)
        with open(readme_path, "w") as f:
            logger.debug("Writing README to %s.", readme_path)
            f.write("This directory contains your keys and certificates.\n\n"
                    "`privkey.pem`  : the private key for your certificate.\n"
                    "`fullchain.pem`: the certificate file used in most server software.\n"
                    "`chain.pem`    : used for OCSP stapling in Nginx >=1.3.7.\n"
                    "`cert.pem`     : will break many server configurations, and "
                                        "should not be used\n"
                    "                 without reading further documentation (see link below).\n\n"
                    "We recommend not moving these files. For more information, see the Certbot\n"
                    "User Guide at https://certbot.eff.org/docs/using.html#where-are-my-"
                                        "certificates.\n")

        # Document what we've done in a new renewal config file
        config_file.close()

        # Save only the config items that are relevant to renewal
        values = relevant_values(vars(cli_config.namespace))

        new_config = write_renewal_config(config_filename, config_filename, archive,
            target, values)
        return cls(new_config.filename, cli_config)

[docs]    def save_successor(self, prior_version, new_cert,
                       new_privkey, new_chain, cli_config):
        """Save new cert and chain as a successor of a prior version.

        Returns the new version number that was created.

        .. note:: this function does NOT update links to deploy this
                  version

        :param int prior_version: the old version to which this version
            is regarded as a successor (used to choose a privkey, if the
            key has not changed, but otherwise this information is not
            permanently recorded anywhere)
        :param bytes new_cert: the new certificate, in PEM format
        :param bytes new_privkey: the new private key, in PEM format,
            or ``None``, if the private key has not changed
        :param bytes new_chain: the new chain, in PEM format
        :param .NamespaceConfig cli_config: parsed command line
            arguments

        :returns: the new version number that was created
        :rtype: int

        """
        # XXX: assumes official archive location rather than examining links
        # XXX: consider using os.open for availability of os.O_EXCL
        # XXX: ensure file permissions are correct; also create directories
        #      if needed (ensuring their permissions are correct)
        # Figure out what the new version is and hence where to save things

        self.cli_config = cli_config
        target_version = self.next_free_version()
        target = dict(
            [(kind,
              os.path.join(self.archive_dir, "{0}{1}.pem".format(kind, target_version)))
             for kind in ALL_FOUR])

        # Distinguish the cases where the privkey has changed and where it
        # has not changed (in the latter case, making an appropriate symlink
        # to an earlier privkey version)
        if new_privkey is None:
            # The behavior below keeps the prior key by creating a new
            # symlink to the old key or the target of the old key symlink.
            old_privkey = os.path.join(
                self.archive_dir, "privkey{0}.pem".format(prior_version))
            if os.path.islink(old_privkey):
                old_privkey = os.readlink(old_privkey)
            else:
                old_privkey = "privkey{0}.pem".format(prior_version)
            logger.debug("Writing symlink to old private key, %s.", old_privkey)
            os.symlink(old_privkey, target["privkey"])
        else:
            with open(target["privkey"], "wb") as f:
                logger.debug("Writing new private key to %s.", target["privkey"])
                f.write(new_privkey)

        # Save everything else
        with open(target["cert"], "wb") as f:
            logger.debug("Writing certificate to %s.", target["cert"])
            f.write(new_cert)
        with open(target["chain"], "wb") as f:
            logger.debug("Writing chain to %s.", target["chain"])
            f.write(new_chain)
        with open(target["fullchain"], "wb") as f:
            logger.debug("Writing full chain to %s.", target["fullchain"])
            f.write(new_cert + new_chain)

        symlinks = dict((kind, self.configuration[kind]) for kind in ALL_FOUR)
        # Update renewal config file
        self.configfile = update_configuration(
            self.lineagename, self.archive_dir, symlinks, cli_config)
        self.configuration = config_with_defaults(self.configfile)

        return target_version

Source code for certbot.auth_handler

"""ACME AuthHandler."""
import logging
import time

import six
import zope.component

from acme import challenges
from acme import messages

from certbot import achallenges
from certbot import errors
from certbot import error_handler
from certbot import interfaces

logger = logging.getLogger(__name__)

[docs]class AuthHandler(object):
    """ACME Authorization Handler for a client.

    :ivar auth: Authenticator capable of solving
        :class:`~acme.challenges.Challenge` types
    :type auth: :class:`certbot.interfaces.IAuthenticator`

    :ivar acme.client.Client acme: ACME client API.

    :ivar account: Client's Account
    :type account: :class:`certbot.account.Account`

    :ivar dict authzr: ACME Authorization Resource dict where keys are domains
        and values are :class:`acme.messages.AuthorizationResource`
    :ivar list achalls: DV challenges in the form of
        :class:`certbot.achallenges.AnnotatedChallenge`
    :ivar list pref_challs: sorted user specified preferred challenges
        type strings with the most preferred challenge listed first

    """
    def __init__(self, auth, acme, account, pref_challs):
        self.auth = auth
        self.acme = acme

        self.account = account
        self.authzr = dict()
        self.pref_challs = pref_challs

        # List must be used to keep responses straight.
        self.achalls = []

[docs]    def get_authorizations(self, domains, best_effort=False):
        """Retrieve all authorizations for challenges.

        :param list domains: Domains for authorization
        :param bool best_effort: Whether or not all authorizations are
             required (this is useful in renewal)

        :returns: List of authorization resources
        :rtype: list

        :raises .AuthorizationError: If unable to retrieve all
            authorizations

        """
        for domain in domains:
            self.authzr[domain] = self.acme.request_domain_challenges(domain)

        self._choose_challenges(domains)
        config = zope.component.getUtility(interfaces.IConfig)
        notify = zope.component.getUtility(interfaces.IDisplay).notification

        # While there are still challenges remaining...
        while self.achalls:
            resp = self._solve_challenges()
            logger.info("Waiting for verification...")
            if config.debug_challenges:
                notify('Challenges loaded. Press continue to submit to CA. '
                       'Pass "-v" for more info about challenges.', pause=True)

            # Send all Responses - this modifies achalls
            self._respond(resp, best_effort)

        # Just make sure all decisions are complete.
        self.verify_authzr_complete()

        # Only return valid authorizations
        retVal = [authzr for authzr in self.authzr.values()
                  if authzr.body.status == messages.STATUS_VALID]

        if not retVal:
            raise errors.AuthorizationError(
                "Challenges failed for all domains")

        return retVal

[docs]    def _choose_challenges(self, domains):
        """Retrieve necessary challenges to satisfy server."""
        logger.info("Performing the following challenges:")
        for dom in domains:
            path = gen_challenge_path(
                self.authzr[dom].body.challenges,
                self._get_chall_pref(dom),
                self.authzr[dom].body.combinations)

            dom_achalls = self._challenge_factory(
                dom, path)
            self.achalls.extend(dom_achalls)

[docs]    def _solve_challenges(self):
        """Get Responses for challenges from authenticators."""
        resp = []
        with error_handler.ErrorHandler(self._cleanup_challenges):
            try:
                if self.achalls:
                    resp = self.auth.perform(self.achalls)
            except errors.AuthorizationError:
                logger.critical("Failure in setting up challenges.")
                logger.info("Attempting to clean up outstanding challenges...")
                raise

        assert len(resp) == len(self.achalls)

        return resp

[docs]    def _respond(self, resp, best_effort):
        """Send/Receive confirmation of all challenges.

        .. note:: This method also cleans up the auth_handler state.

        """
        # TODO: chall_update is a dirty hack to get around acme-spec #105
        chall_update = dict()
        active_achalls = self._send_responses(self.achalls,
                                              resp, chall_update)

        # Check for updated status...
        try:
            self._poll_challenges(chall_update, best_effort)
        finally:
            # This removes challenges from self.achalls
            self._cleanup_challenges(active_achalls)

[docs]    def _send_responses(self, achalls, resps, chall_update):
        """Send responses and make sure errors are handled.

        :param dict chall_update: parameter that is updated to hold
            authzr -> list of outstanding solved annotated challenges

        """
        active_achalls = []
        for achall, resp in six.moves.zip(achalls, resps):
            # 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
            if resp is not None and resp:
                self.acme.answer_challenge(achall.challb, resp)
                # TODO: answer_challenge returns challr, with URI,
                # that can be used in _find_updated_challr
                # comparisons...
                if achall.domain in chall_update:
                    chall_update[achall.domain].append(achall)
                else:
                    chall_update[achall.domain] = [achall]

        return active_achalls

[docs]    def _poll_challenges(
            self, chall_update, best_effort, min_sleep=3, max_rounds=15):
        """Wait for all challenge results to be determined."""
        dom_to_check = set(chall_update.keys())
        comp_domains = set()
        rounds = 0

        while dom_to_check and rounds < max_rounds:
            # TODO: Use retry-after...
            time.sleep(min_sleep)
            all_failed_achalls = set()
            for domain in dom_to_check:
                comp_achalls, failed_achalls = self._handle_check(
                    domain, chall_update[domain])

                if len(comp_achalls) == len(chall_update[domain]):
                    comp_domains.add(domain)
                elif not failed_achalls:
                    for achall, _ in comp_achalls:
                        chall_update[domain].remove(achall)
                # We failed some challenges... damage control
                else:
                    if best_effort:
                        comp_domains.add(domain)
                        logger.warning(
                            "Challenge failed for domain %s",
                            domain)
                    else:
                        all_failed_achalls.update(
                            updated for _, updated in failed_achalls)

            if all_failed_achalls:
                _report_failed_challs(all_failed_achalls)
                raise errors.FailedChallenges(all_failed_achalls)

            dom_to_check -= comp_domains
            comp_domains.clear()
            rounds += 1

[docs]    def _handle_check(self, domain, achalls):
        """Returns tuple of ('completed', 'failed')."""
        completed = []
        failed = []

        self.authzr[domain], _ = self.acme.poll(self.authzr[domain])
        if self.authzr[domain].body.status == messages.STATUS_VALID:
            return achalls, []

        # Note: if the whole authorization is invalid, the individual failed
        #     challenges will be determined here...
        for achall in achalls:
            updated_achall = achall.update(challb=self._find_updated_challb(
                self.authzr[domain], achall))

            # This does nothing for challenges that have yet to be decided yet.
            if updated_achall.status == messages.STATUS_VALID:
                completed.append((achall, updated_achall))
            elif updated_achall.status == messages.STATUS_INVALID:
                failed.append((achall, updated_achall))

        return completed, failed

[docs]    def _find_updated_challb(self, authzr, achall):  # pylint: disable=no-self-use
        """Find updated challenge body within Authorization Resource.

        .. warning:: This assumes only one instance of type of challenge in
            each challenge resource.

        :param .AuthorizationResource authzr: Authorization Resource
        :param .AnnotatedChallenge achall: Annotated challenge for which
            to get status

        """
        for authzr_challb in authzr.body.challenges:
            if type(authzr_challb.chall) is type(achall.challb.chall):  # noqa
                return authzr_challb
        raise errors.AuthorizationError(
            "Target challenge not found in authorization resource")

[docs]    def _get_chall_pref(self, domain):
        """Return list of challenge preferences.

        :param str domain: domain for which you are requesting preferences

        """
        chall_prefs = []
        # Make sure to make a copy...
        plugin_pref = self.auth.get_chall_pref(domain)
        if self.pref_challs:
            plugin_pref_types = set(chall.typ for chall in plugin_pref)
            for typ in self.pref_challs:
                if typ in plugin_pref_types:
                    chall_prefs.append(challenges.Challenge.TYPES[typ])
            if chall_prefs:
                return chall_prefs
            raise errors.AuthorizationError(
                "None of the preferred challenges "
                "are supported by the selected plugin")
        chall_prefs.extend(plugin_pref)
        return chall_prefs

[docs]    def _cleanup_challenges(self, achall_list=None):
        """Cleanup challenges.

        If achall_list is not provided, cleanup all achallenges.

        """
        logger.info("Cleaning up challenges")

        if achall_list is None:
            achalls = self.achalls
        else:
            achalls = achall_list

        if achalls:
            self.auth.cleanup(achalls)
            for achall in achalls:
                self.achalls.remove(achall)

[docs]    def verify_authzr_complete(self):
        """Verifies that all authorizations have been decided.

        :returns: Whether all authzr are complete
        :rtype: bool

        """
        for authzr in self.authzr.values():
            if (authzr.body.status != messages.STATUS_VALID and
                    authzr.body.status != messages.STATUS_INVALID):
                raise errors.AuthorizationError("Incomplete authorizations")

[docs]    def _challenge_factory(self, domain, path):
        """Construct Namedtuple Challenges

        :param str domain: domain of the enrollee

        :param list path: List of indices from `challenges`.

        :returns: achalls, list of challenge type
            :class:`certbot.achallenges.Indexed`
        :rtype: list

        :raises .errors.Error: if challenge type is not recognized

        """
        achalls = []

        for index in path:
            challb = self.authzr[domain].body.challenges[index]
            achalls.append(challb_to_achall(challb, self.account.key, domain))

        return achalls

[docs]def challb_to_achall(challb, account_key, domain):
    """Converts a ChallengeBody object to an AnnotatedChallenge.

    :param .ChallengeBody challb: ChallengeBody
    :param .JWK account_key: Authorized Account Key
    :param str domain: Domain of the challb

    :returns: Appropriate AnnotatedChallenge
    :rtype: :class:`certbot.achallenges.AnnotatedChallenge`

    """
    chall = challb.chall
    logger.info("%s challenge for %s", chall.typ, domain)

    if isinstance(chall, challenges.KeyAuthorizationChallenge):
        return achallenges.KeyAuthorizationAnnotatedChallenge(
            challb=challb, domain=domain, account_key=account_key)
    elif isinstance(chall, challenges.DNS):
        return achallenges.DNS(challb=challb, domain=domain)
    else:
        raise errors.Error(
            "Received unsupported challenge of type: %s", chall.typ)

[docs]def gen_challenge_path(challbs, preferences, combinations):
    """Generate a plan to get authority over the identity.

    .. todo:: This can be possibly be rewritten to use resolved_combinations.

    :param tuple challbs: A tuple of challenges
        (:class:`acme.messages.Challenge`) from
        :class:`acme.messages.AuthorizationResource` to be
        fulfilled by the client in order to prove possession of the
        identifier.

    :param list preferences: List of challenge preferences for domain
        (:class:`acme.challenges.Challenge` subclasses)

    :param tuple combinations: A collection of sets of challenges from
        :class:`acme.messages.Challenge`, each of which would
        be sufficient to prove possession of the identifier.

    :returns: tuple of indices from ``challenges``.
    :rtype: tuple

    :raises certbot.errors.AuthorizationError: If a
        path cannot be created that satisfies the CA given the preferences and
        combinations.

    """
    if combinations:
        return _find_smart_path(challbs, preferences, combinations)
    else:
        return _find_dumb_path(challbs, preferences)

[docs]def _find_smart_path(challbs, preferences, combinations):
    """Find challenge path with server hints.

    Can be called if combinations is included. Function uses a simple
    ranking system to choose the combo with the lowest cost.

    """
    chall_cost = {}
    max_cost = 1
    for i, chall_cls in enumerate(preferences):
        chall_cost[chall_cls] = i
        max_cost += i

    # max_cost is now equal to sum(indices) + 1

    best_combo = []
    # Set above completing all of the available challenges
    best_combo_cost = max_cost

    combo_total = 0
    for combo in combinations:
        for challenge_index in combo:
            combo_total += chall_cost.get(challbs[
                challenge_index].chall.__class__, max_cost)

        if combo_total < best_combo_cost:
            best_combo = combo
            best_combo_cost = combo_total

        combo_total = 0

    if not best_combo:
        _report_no_chall_path()

    return best_combo

[docs]def _find_dumb_path(challbs, preferences):
    """Find challenge path without server hints.

    Should be called if the combinations hint is not included by the
    server. This function either returns a path containing all
    challenges provided by the CA or raises an exception.

    """
    path = []
    for i, challb in enumerate(challbs):
        # supported is set to True if the challenge type is supported
        supported = next((True for pref_c in preferences
                          if isinstance(challb.chall, pref_c)), False)
        if supported:
            path.append(i)
        else:
            _report_no_chall_path()

    return path

[docs]def _report_no_chall_path():
    """Logs and raises an error that no satisfiable chall path exists."""
    msg = ("Client with the currently selected authenticator does not support "
           "any combination of challenges that will satisfy the CA.")
    logger.fatal(msg)
    raise errors.AuthorizationError(msg)

_ERROR_HELP_COMMON = (
    "To fix these errors, please make sure that your domain name was entered "
    "correctly and the DNS A/AAAA record(s) for that domain contain(s) the "
    "right IP address.")

_ERROR_HELP = {
    "connection":
        _ERROR_HELP_COMMON + " Additionally, please check that your computer "
        "has a publicly routable IP address and that no firewalls are preventing "
        "the server from communicating with the client. If you're using the "
        "webroot plugin, you should also verify that you are serving files "
        "from the webroot path you provided.",
    "dnssec":
        _ERROR_HELP_COMMON + " Additionally, if you have DNSSEC enabled for "
        "your domain, please ensure that the signature is valid.",
    "malformed":
        "To fix these errors, please make sure that you did not provide any "
        "invalid information to the client, and try running Certbot "
        "again.",
    "serverInternal":
        "Unfortunately, an error on the ACME server prevented you from completing "
        "authorization. Please try again later.",
    "tls":
        _ERROR_HELP_COMMON + " Additionally, please check that you have an "
        "up-to-date TLS configuration that allows the server to communicate "
        "with the Certbot client.",
    "unauthorized": _ERROR_HELP_COMMON,
    "unknownHost": _ERROR_HELP_COMMON,
}

[docs]def _report_failed_challs(failed_achalls):
    """Notifies the user about failed challenges.

    :param set failed_achalls: A set of failed
        :class:`certbot.achallenges.AnnotatedChallenge`.

    """
    problems = dict()
    for achall in failed_achalls:
        if achall.error:
            problems.setdefault(achall.error.typ, []).append(achall)

    reporter = zope.component.getUtility(interfaces.IReporter)
    for achalls in six.itervalues(problems):
        reporter.add_message(
            _generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY)

[docs]def _generate_failed_chall_msg(failed_achalls):
    """Creates a user friendly error message about failed challenges.

    :param list failed_achalls: A list of failed
        :class:`certbot.achallenges.AnnotatedChallenge` with the same error
        type.

    :returns: A formatted error message for the client.
    :rtype: str

    """
    error = failed_achalls[0].error
    typ = error.typ
    if messages.is_acme_error(error):
        typ = error.code
    msg = ["The following errors were reported by the server:"]

    for achall in failed_achalls:
        msg.append("\n\nDomain: %s\nType:   %s\nDetail: %s" % (
            achall.domain, typ, achall.error.detail))

    if typ in _ERROR_HELP:
        msg.append("\n\n")
        msg.append(_ERROR_HELP[typ])

    return "".join(msg)

Source code for certbot.reporter

"""Collects and displays information to the user."""
from __future__ import print_function

import collections
import logging
import sys
import textwrap

from six.moves import queue  # type: ignore  # pylint: disable=import-error
import zope.interface

from certbot import interfaces
from certbot import util

logger = logging.getLogger(__name__)

@zope.interface.implementer(interfaces.IReporter)

[docs]class Reporter(object):
    """Collects and displays information to the user.

    :ivar `queue.PriorityQueue` messages: Messages to be displayed to
        the user.

    """

    HIGH_PRIORITY = 0
    """High priority constant. See `add_message`."""
    MEDIUM_PRIORITY = 1
    """Medium priority constant. See `add_message`."""
    LOW_PRIORITY = 2
    """Low priority constant. See `add_message`."""

    _msg_type = collections.namedtuple('ReporterMsg', 'priority text on_crash')

    def __init__(self, config):
        self.messages = queue.PriorityQueue()
        self.config = config

[docs]    def add_message(self, msg, priority, on_crash=True):
        """Adds msg to the list of messages to be printed.

        :param str msg: Message to be displayed to the user.

        :param int priority: One of `HIGH_PRIORITY`, `MEDIUM_PRIORITY`,
            or `LOW_PRIORITY`.

        :param bool on_crash: Whether or not the message should be
            printed if the program exits abnormally.

        """
        assert self.HIGH_PRIORITY <= priority <= self.LOW_PRIORITY
        self.messages.put(self._msg_type(priority, msg, on_crash))
        logger.debug("Reporting to user: %s", msg)

[docs]    def print_messages(self):
        """Prints messages to the user and clears the message queue.

        If there is an unhandled exception, only messages for which
        ``on_crash`` is ``True`` are printed.

        """
        bold_on = False
        if not self.messages.empty():
            no_exception = sys.exc_info()[0] is None
            bold_on = sys.stdout.isatty()
            if not self.config.quiet:
                if bold_on:
                    print(util.ANSI_SGR_BOLD)
                print('IMPORTANT NOTES:')
            first_wrapper = textwrap.TextWrapper(
                initial_indent=' - ',
                subsequent_indent=(' ' * 3),
                break_long_words=False,
                break_on_hyphens=False)
            next_wrapper = textwrap.TextWrapper(
                initial_indent=first_wrapper.subsequent_indent,
                subsequent_indent=first_wrapper.subsequent_indent,
                break_long_words=False,
                break_on_hyphens=False)
        while not self.messages.empty():
            msg = self.messages.get()
            if self.config.quiet:
                # In --quiet mode, we only print high priority messages that
                # are flagged for crash cases
                if not (msg.priority == self.HIGH_PRIORITY and msg.on_crash):
                    continue
            if no_exception or msg.on_crash:
                if bold_on and msg.priority > self.HIGH_PRIORITY:
                    if not self.config.quiet:
                        sys.stdout.write(util.ANSI_SGR_RESET)
                        bold_on = False
                lines = msg.text.splitlines()
                print(first_wrapper.fill(lines[0]))
                if len(lines) > 1:
                    print("\n".join(
                        next_wrapper.fill(line) for line in lines[1:]))
        if bold_on and not self.config.quiet:
            sys.stdout.write(util.ANSI_SGR_RESET)

Source code for certbot.interfaces

"""Certbot client interfaces."""
import abc
import zope.interface

# pylint: disable=no-self-argument,no-method-argument,no-init,inherit-non-class
# pylint: disable=too-few-public-methods

[docs]class AccountStorage(object):
    """Accounts storage interface."""

    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod

[docs]    def find_all(self):  # pragma: no cover
        """Find all accounts.

        :returns: All found accounts.
        :rtype: list

        """
        raise NotImplementedError()

    @abc.abstractmethod

[docs]    def load(self, account_id):  # pragma: no cover
        """Load an account by its id.

        :raises .AccountNotFound: if account could not be found
        :raises .AccountStorageError: if account could not be loaded

        """
        raise NotImplementedError()

    @abc.abstractmethod

[docs]    def save(self, account, client):  # pragma: no cover
        """Save account.

        :raises .AccountStorageError: if account could not be saved

        """
        raise NotImplementedError()

[docs]class IPluginFactory(zope.interface.Interface):
    """IPlugin factory.

    Objects providing this interface will be called without satisfying
    any entry point "extras" (extra dependencies) you might have defined
    for your plugin, e.g (excerpt from ``setup.py`` script)::

      setup(
          ...
          entry_points={
              'certbot.plugins': [
                  'name=example_project.plugin[plugin_deps]',
              ],
          },
          extras_require={
              'plugin_deps': ['dep1', 'dep2'],
          }
      )

    Therefore, make sure such objects are importable and usable without
    extras. This is necessary, because CLI does the following operations
    (in order):

      - loads an entry point,
      - calls `inject_parser_options`,
      - requires an entry point,
      - creates plugin instance (`__call__`).

    """

    description = zope.interface.Attribute("Short plugin description")

    def __call__(config, name):
        """Create new `IPlugin`.

        :param IConfig config: Configuration.
        :param str name: Unique plugin name.

        """

    def inject_parser_options(parser, name):
        """Inject argument parser options (flags).

        1. Be nice and prepend all options and destinations with
        `~.common.option_namespace` and `~common.dest_namespace`.

        2. Inject options (flags) only. Positional arguments are not
        allowed, as this would break the CLI.

        :param ArgumentParser parser: (Almost) top-level CLI parser.
        :param str name: Unique plugin name.

        """

[docs]class IPlugin(zope.interface.Interface):
    """Certbot plugin."""

    def prepare():  # type: ignore
        """Prepare the plugin.

        Finish up any additional initialization.

        :raises .PluginError:
            when full initialization cannot be completed.
        :raises .MisconfigurationError:
            when full initialization cannot be completed. Plugin will
            be displayed on a list of available plugins.
        :raises .NoInstallationError:
            when the necessary programs/files cannot be located. Plugin
            will NOT be displayed on a list of available plugins.
        :raises .NotSupportedError:
            when the installation is recognized, but the version is not
            currently supported.

        """

    def more_info():  # type: ignore
        """Human-readable string to help the user.

        Should describe the steps taken and any relevant info to help the user
        decide which plugin to use.

        :rtype str:

        """

[docs]class IAuthenticator(IPlugin):
    """Generic Certbot Authenticator.

    Class represents all possible tools processes that have the
    ability to perform challenges and attain a certificate.

    """

    def get_chall_pref(domain):
        """Return `collections.Iterable` of challenge preferences.

        :param str domain: Domain for which challenge preferences are sought.

        :returns: `collections.Iterable` of challenge types (subclasses of
            :class:`acme.challenges.Challenge`) with the most
            preferred challenges first. If a type is not specified, it means the
            Authenticator cannot perform the challenge.
        :rtype: `collections.Iterable`

        """

    def perform(achalls):
        """Perform the given challenge.

        :param list achalls: Non-empty (guaranteed) list of
            :class:`~certbot.achallenges.AnnotatedChallenge`
            instances, such that it contains types found within
            :func:`get_chall_pref` only.

        :returns: `collections.Iterable` of ACME
            :class:`~acme.challenges.ChallengeResponse` instances
            or if the :class:`~acme.challenges.Challenge` cannot
            be fulfilled then:

            ``None``
              Authenticator can perform challenge, but not at this time.
            ``False``
              Authenticator will never be able to perform (error).

        :rtype: :class:`collections.Iterable` of
            :class:`acme.challenges.ChallengeResponse`,
            where responses are required to be returned in
            the same order as corresponding input challenges

        :raises .PluginError: If challenges cannot be performed

        """

    def cleanup(achalls):
        """Revert changes and shutdown after challenges complete.

        This method should be able to revert all changes made by
        perform, even if perform exited abnormally.

        :param list achalls: Non-empty (guaranteed) list of
            :class:`~certbot.achallenges.AnnotatedChallenge`
            instances, a subset of those previously passed to :func:`perform`.

        :raises PluginError: if original configuration cannot be restored

        """

[docs]class IConfig(zope.interface.Interface):
    """Certbot user-supplied configuration.

    .. warning:: The values stored in the configuration have not been
        filtered, stripped or sanitized.

    """
    server = zope.interface.Attribute("ACME Directory Resource URI.")
    email = zope.interface.Attribute(
        "Email used for registration and recovery contact. (default: Ask)")
    rsa_key_size = zope.interface.Attribute("Size of the RSA key.")
    must_staple = zope.interface.Attribute(
        "Adds the OCSP Must Staple extension to the certificate. "
        "Autoconfigures OCSP Stapling for supported setups "
        "(Apache version >= 2.3.3 ).")

    config_dir = zope.interface.Attribute("Configuration directory.")
    work_dir = zope.interface.Attribute("Working directory.")

    accounts_dir = zope.interface.Attribute(
        "Directory where all account information is stored.")
    backup_dir = zope.interface.Attribute("Configuration backups directory.")
    csr_dir = zope.interface.Attribute(
        "Directory where newly generated Certificate Signing Requests "
        "(CSRs) are saved.")
    in_progress_dir = zope.interface.Attribute(
        "Directory used before a permanent checkpoint is finalized.")
    key_dir = zope.interface.Attribute("Keys storage.")
    temp_checkpoint_dir = zope.interface.Attribute(
        "Temporary checkpoint directory.")

    no_verify_ssl = zope.interface.Attribute(
        "Disable verification of the ACME server's certificate.")
    tls_sni_01_port = zope.interface.Attribute(
        "Port used during tls-sni-01 challenge. "
        "This only affects the port Certbot listens on. "
        "A conforming ACME server will still attempt to connect on port 443.")
    tls_sni_01_address = zope.interface.Attribute(
        "The address the server listens to during tls-sni-01 challenge.")

    http01_port = zope.interface.Attribute(
        "Port used in the http-01 challenge. "
        "This only affects the port Certbot listens on. "
        "A conforming ACME server will still attempt to connect on port 80.")

    http01_address = zope.interface.Attribute(
        "The address the server listens to during http-01 challenge.")

    pref_challs = zope.interface.Attribute(
        "Sorted user specified preferred challenges"
        "type strings with the most preferred challenge listed first")

    allow_subset_of_names = zope.interface.Attribute(
        "When performing domain validation, do not consider it a failure "
        "if authorizations can not be obtained for a strict subset of "
        "the requested domains. This may be useful for allowing renewals for "
        "multiple domains to succeed even if some domains no longer point "
        "at this system. This is a boolean")

    strict_permissions = zope.interface.Attribute(
        "Require that all configuration files are owned by the current "
        "user; only needed if your config is somewhere unsafe like /tmp/."
        "This is a boolean")

[docs]class IInstaller(IPlugin):
    """Generic Certbot Installer Interface.

    Represents any server that an X509 certificate can be placed.

    It is assumed that :func:`save` is the only method that finalizes a
    checkpoint. This is important to ensure that checkpoints are
    restored in a consistent manner if requested by the user or in case
    of an error.

    Using :class:`certbot.reverter.Reverter` to implement checkpoints,
    rollback, and recovery can dramatically simplify plugin development.

    """

    def get_all_names():  # type: ignore
        """Returns all names that may be authenticated.

        :rtype: `collections.Iterable` of `str`

        """

    def deploy_cert(domain, cert_path, key_path, chain_path, fullchain_path):
        """Deploy certificate.

        :param str domain: domain to deploy certificate file
        :param str cert_path: absolute path to the certificate file
        :param str key_path: absolute path to the private key file
        :param str chain_path: absolute path to the certificate chain file
        :param str fullchain_path: absolute path to the certificate fullchain
            file (cert plus chain)

        :raises .PluginError: when cert cannot be deployed

        """

    def enhance(domain, enhancement, options=None):
        """Perform a configuration enhancement.

        :param str domain: domain for which to provide enhancement
        :param str enhancement: An enhancement as defined in
            :const:`~certbot.constants.ENHANCEMENTS`
        :param options: Flexible options parameter for enhancement.
            Check documentation of
            :const:`~certbot.constants.ENHANCEMENTS`
            for expected options for each enhancement.

        :raises .PluginError: If Enhancement is not supported, or if
            an error occurs during the enhancement.

        """

    def supported_enhancements():  # type: ignore
        """Returns a `collections.Iterable` of supported enhancements.

        :returns: supported enhancements which should be a subset of
            :const:`~certbot.constants.ENHANCEMENTS`
        :rtype: :class:`collections.Iterable` of :class:`str`

        """

    def save(title=None, temporary=False):
        """Saves all changes to the configuration files.

        Both title and temporary are needed because a save may be
        intended to be permanent, but the save is not ready to be a full
        checkpoint.

        It is assumed that at most one checkpoint is finalized by this
        method. Additionally, if an exception is raised, it is assumed a
        new checkpoint was not finalized.

        :param str title: The title of the save. If a title is given, the
            configuration will be saved as a new checkpoint and put in a
            timestamped directory. `title` has no effect if temporary is true.

        :param bool temporary: Indicates whether the changes made will
            be quickly reversed in the future (challenges)

        :raises .PluginError: when save is unsuccessful

        """

    def rollback_checkpoints(rollback=1):
        """Revert `rollback` number of configuration checkpoints.

        :raises .PluginError: when configuration cannot be fully reverted

        """

    def recovery_routine():  # type: ignore
        """Revert configuration to most recent finalized checkpoint.

        Remove all changes (temporary and permanent) that have not been
        finalized. This is useful to protect against crashes and other
        execution interruptions.

        :raises .errors.PluginError: If unable to recover the configuration

        """

    def view_config_changes():  # type: ignore
        """Display all of the LE config changes.

        :raises .PluginError: when config changes cannot be parsed

        """

    def config_test():  # type: ignore
        """Make sure the configuration is valid.

        :raises .MisconfigurationError: when the config is not in a usable state

        """

    def restart():  # type: ignore
        """Restart or refresh the server content.

        :raises .PluginError: when server cannot be restarted

        """

[docs]class IDisplay(zope.interface.Interface):
    """Generic display."""
    # pylint: disable=too-many-arguments
    # see https://github.com/certbot/certbot/issues/3915

    def notification(message, pause, wrap=True, force_interactive=False):
        """Displays a string message

        :param str message: Message to display
        :param bool pause: Whether or not the application should pause for
            confirmation (if available)
        :param bool wrap: Whether or not the application should wrap text
        :param bool force_interactive: True if it's safe to prompt the user
            because it won't cause any workflow regressions

        """

    def menu(message, choices, ok_label="OK",
             cancel_label="Cancel", help_label="",
             default=None, cli_flag=None, force_interactive=False):
        """Displays a generic menu.

        When not setting force_interactive=True, you must provide a
        default value.

        :param str message: message to display

        :param choices: choices
        :type choices: :class:`list` of :func:`tuple` or :class:`str`

        :param str ok_label: label for OK button
        :param str cancel_label: label for Cancel button
        :param str help_label: label for Help button
        :param int default: default (non-interactive) choice from the menu
        :param str cli_flag: to automate choice from the menu, eg "--keep"
        :param bool force_interactive: True if it's safe to prompt the user
            because it won't cause any workflow regressions

        :returns: tuple of (`code`, `index`) where
            `code` - str display exit code
            `index` - int index of the user's selection

        :raises errors.MissingCommandlineFlag: if called in non-interactive
            mode without a default set

        """

    def input(message, default=None, cli_args=None, force_interactive=False):
        """Accept input from the user.

        When not setting force_interactive=True, you must provide a
        default value.

        :param str message: message to display to the user
        :param str default: default (non-interactive) response to prompt
        :param bool force_interactive: True if it's safe to prompt the user
            because it won't cause any workflow regressions

        :returns: tuple of (`code`, `input`) where
            `code` - str display exit code
            `input` - str of the user's input
        :rtype: tuple

        :raises errors.MissingCommandlineFlag: if called in non-interactive
            mode without a default set

        """

    def yesno(message, yes_label="Yes", no_label="No", default=None,
              cli_args=None, force_interactive=False):
        """Query the user with a yes/no question.

        Yes and No label must begin with different letters.

        When not setting force_interactive=True, you must provide a
        default value.

        :param str message: question for the user
        :param str default: default (non-interactive) choice from the menu
        :param str cli_flag: to automate choice from the menu, eg "--redirect / --no-redirect"
        :param bool force_interactive: True if it's safe to prompt the user
            because it won't cause any workflow regressions

        :returns: True for "Yes", False for "No"
        :rtype: bool

        :raises errors.MissingCommandlineFlag: if called in non-interactive
            mode without a default set

        """

    def checklist(message, tags, default_state,
                  default=None, cli_args=None, force_interactive=False):
        """Allow for multiple selections from a menu.

        When not setting force_interactive=True, you must provide a
        default value.

        :param str message: message to display to the user
        :param list tags: where each is of type :class:`str` len(tags) > 0
        :param bool default_status: If True, items are in a selected state by default.
        :param str default: default (non-interactive) state of the checklist
        :param str cli_flag: to automate choice from the menu, eg "--domains"
        :param bool force_interactive: True if it's safe to prompt the user
            because it won't cause any workflow regressions

        :returns: tuple of the form (code, list_tags) where
            `code` - int display exit code
            `list_tags` - list of str tags selected by the user
        :rtype: tuple

        :raises errors.MissingCommandlineFlag: if called in non-interactive
            mode without a default set

        """

    def directory_select(self, message, default=None,
                         cli_flag=None, force_interactive=False):
        """Display a directory selection screen.

        When not setting force_interactive=True, you must provide a
        default value.

        :param str message: prompt to give the user
        :param default: the default value to return, if one exists, when
            using the NoninteractiveDisplay
        :param str cli_flag: option used to set this value with the CLI,
            if one exists, to be included in error messages given by
            NoninteractiveDisplay
        :param bool force_interactive: True if it's safe to prompt the user
            because it won't cause any workflow regressions

        :returns: tuple of the form (`code`, `string`) where
            `code` - int display exit code
            `string` - input entered by the user

        """

[docs]class IValidator(zope.interface.Interface):
    """Configuration validator."""

    def certificate(cert, name, alt_host=None, port=443):
        """Verifies the certificate presented at name is cert

        :param OpenSSL.crypto.X509 cert: Expected certificate
        :param str name: Server's domain name
        :param bytes alt_host: Host to connect to instead of the IP
            address of host
        :param int port: Port to connect to

        :returns: True if the certificate was verified successfully
        :rtype: bool

        """

    def redirect(name, port=80, headers=None):
        """Verify redirect to HTTPS

        :param str name: Server's domain name
        :param int port: Port to connect to
        :param dict headers: HTTP headers to include in request

        :returns: True if redirect is successfully enabled
        :rtype: bool

        """

    def hsts(name):
        """Verify HSTS header is enabled

        :param str name: Server's domain name

        :returns: True if HSTS header is successfully enabled
        :rtype: bool

        """

    def ocsp_stapling(name):
        """Verify ocsp stapling for domain

        :param str name: Server's domain name

        :returns: True if ocsp stapling is successfully enabled
        :rtype: bool

        """

[docs]class IReporter(zope.interface.Interface):
    """Interface to collect and display information to the user."""

    HIGH_PRIORITY = zope.interface.Attribute(
        "Used to denote high priority messages")
    MEDIUM_PRIORITY = zope.interface.Attribute(
        "Used to denote medium priority messages")
    LOW_PRIORITY = zope.interface.Attribute(
        "Used to denote low priority messages")

    def add_message(self, msg, priority, on_crash=True):
        """Adds msg to the list of messages to be printed.

        :param str msg: Message to be displayed to the user.

        :param int priority: One of HIGH_PRIORITY, MEDIUM_PRIORITY, or
            LOW_PRIORITY.

        :param bool on_crash: Whether or not the message should be printed if
            the program exits abnormally.

        """

    def print_messages(self):
        """Prints messages to the user and clears the message queue."""

Source code for certbot.configuration

"""Certbot user-supplied configuration."""
import copy
import os

from six.moves.urllib import parse  # pylint: disable=import-error
import zope.interface

from certbot import constants
from certbot import errors
from certbot import interfaces
from certbot import util

@zope.interface.implementer(interfaces.IConfig)

[docs]class NamespaceConfig(object):
    """Configuration wrapper around :class:`argparse.Namespace`.

    For more documentation, including available attributes, please see
    :class:`certbot.interfaces.IConfig`. However, note that
    the following attributes are dynamically resolved using
    :attr:`~certbot.interfaces.IConfig.work_dir` and relative
    paths defined in :py:mod:`certbot.constants`:

      - `accounts_dir`
      - `csr_dir`
      - `in_progress_dir`
      - `key_dir`
      - `temp_checkpoint_dir`

    And the following paths are dynamically resolved using
    :attr:`~certbot.interfaces.IConfig.config_dir` and relative
    paths defined in :py:mod:`certbot.constants`:

      - `default_archive_dir`
      - `live_dir`
      - `renewal_configs_dir`

    :ivar namespace: Namespace typically produced by
        :meth:`argparse.ArgumentParser.parse_args`.
    :type namespace: :class:`argparse.Namespace`

    """

    def __init__(self, namespace):
        object.__setattr__(self, 'namespace', namespace)

        self.namespace.config_dir = os.path.abspath(self.namespace.config_dir)
        self.namespace.work_dir = os.path.abspath(self.namespace.work_dir)
        self.namespace.logs_dir = os.path.abspath(self.namespace.logs_dir)

        # Check command line parameters sanity, and error out in case of problem.
        check_config_sanity(self)

    def __getattr__(self, name):
        return getattr(self.namespace, name)

    def __setattr__(self, name, value):
        setattr(self.namespace, name, value)

    @property
    def server_path(self):
        """File path based on ``server``."""
        parsed = parse.urlparse(self.namespace.server)
        return (parsed.netloc + parsed.path).replace('/', os.path.sep)

    @property
    def accounts_dir(self):  # pylint: disable=missing-docstring
        return os.path.join(
            self.namespace.config_dir, constants.ACCOUNTS_DIR, self.server_path)

    @property
    def backup_dir(self):  # pylint: disable=missing-docstring
        return os.path.join(self.namespace.work_dir, constants.BACKUP_DIR)

    @property
    def csr_dir(self):  # pylint: disable=missing-docstring
        return os.path.join(self.namespace.config_dir, constants.CSR_DIR)

    @property
    def in_progress_dir(self):  # pylint: disable=missing-docstring
        return os.path.join(self.namespace.work_dir, constants.IN_PROGRESS_DIR)

    @property
    def key_dir(self):  # pylint: disable=missing-docstring
        return os.path.join(self.namespace.config_dir, constants.KEY_DIR)

    @property
    def temp_checkpoint_dir(self):  # pylint: disable=missing-docstring
        return os.path.join(
            self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR)

    def __deepcopy__(self, _memo):
        # Work around https://bugs.python.org/issue1515 for py26 tests :( :(
        # https://travis-ci.org/letsencrypt/letsencrypt/jobs/106900743#L3276
        new_ns = copy.deepcopy(self.namespace)
        return type(self)(new_ns)

    @property
    def default_archive_dir(self):  # pylint: disable=missing-docstring
        return os.path.join(self.namespace.config_dir, constants.ARCHIVE_DIR)

    @property
    def live_dir(self):  # pylint: disable=missing-docstring
        return os.path.join(self.namespace.config_dir, constants.LIVE_DIR)

    @property
    def renewal_configs_dir(self):  # pylint: disable=missing-docstring
        return os.path.join(
            self.namespace.config_dir, constants.RENEWAL_CONFIGS_DIR)

[docs]def check_config_sanity(config):
    """Validate command line options and display error message if
    requirements are not met.

    :param config: IConfig instance holding user configuration
    :type args: :class:`certbot.interfaces.IConfig`

    """
    # Port check
    if config.http01_port == config.tls_sni_01_port:
        raise errors.ConfigurationError(
            "Trying to run http-01 and tls-sni-01 "
            "on the same port ({0})".format(config.tls_sni_01_port))

    # Domain checks
    if config.namespace.domains is not None:
        for domain in config.namespace.domains:
            # This may be redundant, but let's be paranoid
            util.enforce_domain_sanity(domain)

Source code for certbot.crypto_util

"""Certbot client crypto utility functions.

.. todo:: Make the transition to use PSS rather than PKCS1_v1_5 when the server
    is capable of handling the signatures.

"""
import hashlib
import logging
import os

import OpenSSL
import pyrfc3339
import six
import zope.component
from cryptography.hazmat.backends import default_backend
from cryptography import x509

from acme import crypto_util as acme_crypto_util
from acme import jose

from certbot import errors
from certbot import interfaces
from certbot import util

logger = logging.getLogger(__name__)

# High level functions

[docs]def init_save_key(key_size, key_dir, keyname="key-certbot.pem"):
    """Initializes and saves a privkey.

    Inits key and saves it in PEM format on the filesystem.

    .. note:: keyname is the attempted filename, it may be different if a file
        already exists at the path.

    :param int key_size: RSA key size in bits
    :param str key_dir: Key save directory.
    :param str keyname: Filename of key

    :returns: Key
    :rtype: :class:`certbot.util.Key`

    :raises ValueError: If unable to generate the key given key_size.

    """
    try:
        key_pem = make_key(key_size)
    except ValueError as err:
        logger.exception(err)
        raise err

    config = zope.component.getUtility(interfaces.IConfig)
    # Save file
    util.make_or_verify_dir(key_dir, 0o700, os.geteuid(),
                            config.strict_permissions)
    if config.dry_run:
        key_path = None
        logger.debug("Generating key (%d bits), not saving to file", key_size)
    else:
        key_f, key_path = util.unique_file(
            os.path.join(key_dir, keyname), 0o600, "wb")
        with key_f:
            key_f.write(key_pem)
        logger.debug("Generating key (%d bits): %s", key_size, key_path)

    return util.Key(key_path, key_pem)

[docs]def init_save_csr(privkey, names, path):
    """Initialize a CSR with the given private key.

    :param privkey: Key to include in the CSR
    :type privkey: :class:`certbot.util.Key`

    :param set names: `str` names to include in the CSR

    :param str path: Certificate save directory.

    :returns: CSR
    :rtype: :class:`certbot.util.CSR`

    """
    config = zope.component.getUtility(interfaces.IConfig)

    csr_pem = acme_crypto_util.make_csr(
        privkey.pem, names, must_staple=config.must_staple)

    # Save CSR
    util.make_or_verify_dir(path, 0o755, os.geteuid(),
                               config.strict_permissions)
    if config.dry_run:
        csr_filename = None
        logger.debug("Creating CSR: not saving to file")
    else:
        csr_f, csr_filename = util.unique_file(
            os.path.join(path, "csr-certbot.pem"), 0o644, "wb")
        with csr_f:
            csr_f.write(csr_pem)
        logger.debug("Creating CSR: %s", csr_filename)

    return util.CSR(csr_filename, csr_pem, "pem")

# WARNING: the csr and private key file are possible attack vectors for TOCTOU
# We should either...
# A. Do more checks to verify that the CSR is trusted/valid
# B. Audit the parsing code for vulnerabilities

[docs]def valid_csr(csr):
    """Validate CSR.

    Check if `csr` is a valid CSR for the given domains.

    :param str csr: CSR in PEM.

    :returns: Validity of CSR.
    :rtype: bool

    """
    try:
        req = OpenSSL.crypto.load_certificate_request(
            OpenSSL.crypto.FILETYPE_PEM, csr)
        return req.verify(req.get_pubkey())
    except OpenSSL.crypto.Error as error:
        logger.debug(error, exc_info=True)
        return False

[docs]def csr_matches_pubkey(csr, privkey):
    """Does private key correspond to the subject public key in the CSR?

    :param str csr: CSR in PEM.
    :param str privkey: Private key file contents (PEM)

    :returns: Correspondence of private key to CSR subject public key.
    :rtype: bool

    """
    req = OpenSSL.crypto.load_certificate_request(
        OpenSSL.crypto.FILETYPE_PEM, csr)
    pkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey)
    try:
        return req.verify(pkey)
    except OpenSSL.crypto.Error as error:
        logger.debug(error, exc_info=True)
        return False

[docs]def import_csr_file(csrfile, data):
    """Import a CSR file, which can be either PEM or DER.

    :param str csrfile: CSR filename
    :param str data: contents of the CSR file

    :returns: (`OpenSSL.crypto.FILETYPE_PEM`,
               util.CSR object representing the CSR,
               list of domains requested in the CSR)
    :rtype: tuple

    """
    PEM = OpenSSL.crypto.FILETYPE_PEM
    load = OpenSSL.crypto.load_certificate_request
    try:
        # Try to parse as DER first, then fall back to PEM.
        csr = load(OpenSSL.crypto.FILETYPE_ASN1, data)
    except OpenSSL.crypto.Error:
        try:
            csr = load(PEM, data)
        except OpenSSL.crypto.Error:
            raise errors.Error("Failed to parse CSR file: {0}".format(csrfile))

    domains = _get_names_from_loaded_cert_or_req(csr)
    # Internally we always use PEM, so re-encode as PEM before returning.
    data_pem = OpenSSL.crypto.dump_certificate_request(PEM, csr)
    return PEM, util.CSR(file=csrfile, data=data_pem, form="pem"), domains

[docs]def make_key(bits):
    """Generate PEM encoded RSA key.

    :param int bits: Number of bits, at least 1024.

    :returns: new RSA key in PEM form with specified number of bits
    :rtype: str

    """
    assert bits >= 1024  # XXX
    key = OpenSSL.crypto.PKey()
    key.generate_key(OpenSSL.crypto.TYPE_RSA, bits)
    return OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)

[docs]def valid_privkey(privkey):
    """Is valid RSA private key?

    :param str privkey: Private key file contents in PEM

    :returns: Validity of private key.
    :rtype: bool

    """
    try:
        return OpenSSL.crypto.load_privatekey(
            OpenSSL.crypto.FILETYPE_PEM, privkey).check()
    except (TypeError, OpenSSL.crypto.Error):
        return False

[docs]def verify_renewable_cert(renewable_cert):
    """For checking that your certs were not corrupted on disk.

    Several things are checked:
        1. Signature verification for the cert.
        2. That fullchain matches cert and chain when concatenated.
        3. Check that the private key matches the certificate.

    :param `.storage.RenewableCert` renewable_cert: cert to verify

    :raises errors.Error: If verification fails.
    """
    verify_renewable_cert_sig(renewable_cert)
    verify_fullchain(renewable_cert)
    verify_cert_matches_priv_key(renewable_cert)

[docs]def verify_renewable_cert_sig(renewable_cert):
    """ Verifies the signature of a `.storage.RenewableCert` object.

    :param `.storage.RenewableCert` renewable_cert: cert to verify

    :raises errors.Error: If signature verification fails.
    """
    try:
        with open(renewable_cert.chain, 'rb') as chain:
            chain, _ = pyopenssl_load_certificate(chain.read())
        with open(renewable_cert.cert, 'rb') as cert:
            cert = x509.load_pem_x509_certificate(cert.read(), default_backend())
        hash_name = cert.signature_hash_algorithm.name
        OpenSSL.crypto.verify(chain, cert.signature, cert.tbs_certificate_bytes, hash_name)
    except (IOError, ValueError, OpenSSL.crypto.Error) as e:
        error_str = "verifying the signature of the cert located at {0} has failed. \
                Details: {1}".format(renewable_cert.cert, e)
        logger.exception(error_str)
        raise errors.Error(error_str)

[docs]def verify_cert_matches_priv_key(renewable_cert):
    """ Verifies that the private key and cert match.

    :param `.storage.RenewableCert` renewable_cert: cert to verify

    :raises errors.Error: If they don't match.
    """
    try:
        with open(renewable_cert.cert) as cert:
            cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert.read())
        with open(renewable_cert.privkey) as privkey:
            privkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey.read())
        context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
        context.use_privatekey(privkey)
        context.use_certificate(cert)
        context.check_privatekey()
    except (IOError, OpenSSL.SSL.Error) as e:
        error_str = "verifying the cert located at {0} matches the \
                private key located at {1} has failed. \
                Details: {2}".format(renewable_cert.cert,
                        renewable_cert.privkey, e)
        logger.exception(error_str)
        raise errors.Error(error_str)

[docs]def verify_fullchain(renewable_cert):
    """ Verifies that fullchain is indeed cert concatenated with chain.

    :param `.storage.RenewableCert` renewable_cert: cert to verify

    :raises errors.Error: If cert and chain do not combine to fullchain.
    """
    try:
        with open(renewable_cert.chain) as chain:
            chain = chain.read()
        with open(renewable_cert.cert) as cert:
            cert = cert.read()
        with open(renewable_cert.fullchain) as fullchain:
            fullchain = fullchain.read()
        if (cert + chain) != fullchain:
            error_str = "fullchain does not match cert + chain for {0}!"
            error_str = error_str.format(renewable_cert.lineagename)
            raise errors.Error(error_str)
    except IOError as e:
        error_str = "reading one of cert, chain, or fullchain has failed: {0}".format(e)
        logger.exception(error_str)
        raise errors.Error(error_str)
    except errors.Error as e:
        raise e

[docs]def pyopenssl_load_certificate(data):
    """Load PEM/DER certificate.

    :raises errors.Error:

    """

    openssl_errors = []

    for file_type in (OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1):
        try:
            return OpenSSL.crypto.load_certificate(file_type, data), file_type
        except OpenSSL.crypto.Error as error:  # TODO: other errors?
            openssl_errors.append(error)
    raise errors.Error("Unable to load: {0}".format(",".join(
        str(error) for error in openssl_errors)))

def _load_cert_or_req(cert_or_req_str, load_func,
                      typ=OpenSSL.crypto.FILETYPE_PEM):
    try:
        return load_func(typ, cert_or_req_str)
    except OpenSSL.crypto.Error as error:
        logger.exception(error)
        raise

def _get_sans_from_cert_or_req(cert_or_req_str, load_func,
                               typ=OpenSSL.crypto.FILETYPE_PEM):
    # pylint: disable=protected-access
    return acme_crypto_util._pyopenssl_cert_or_req_san(_load_cert_or_req(
        cert_or_req_str, load_func, typ))

[docs]def get_sans_from_cert(cert, typ=OpenSSL.crypto.FILETYPE_PEM):
    """Get a list of Subject Alternative Names from a certificate.

    :param str cert: Certificate (encoded).
    :param typ: `OpenSSL.crypto.FILETYPE_PEM` or `OpenSSL.crypto.FILETYPE_ASN1`

    :returns: A list of Subject Alternative Names.
    :rtype: list

    """
    return _get_sans_from_cert_or_req(
        cert, OpenSSL.crypto.load_certificate, typ)

def _get_names_from_cert_or_req(cert_or_req, load_func, typ):
    loaded_cert_or_req = _load_cert_or_req(cert_or_req, load_func, typ)
    return _get_names_from_loaded_cert_or_req(loaded_cert_or_req)

def _get_names_from_loaded_cert_or_req(loaded_cert_or_req):
    common_name = loaded_cert_or_req.get_subject().CN
    # pylint: disable=protected-access
    sans = acme_crypto_util._pyopenssl_cert_or_req_san(loaded_cert_or_req)

    if common_name is None:
        return sans
    else:
        return [common_name] + [d for d in sans if d != common_name]

[docs]def get_names_from_cert(csr, typ=OpenSSL.crypto.FILETYPE_PEM):
    """Get a list of domains from a cert, including the CN if it is set.

    :param str cert: Certificate (encoded).
    :param typ: `OpenSSL.crypto.FILETYPE_PEM` or `OpenSSL.crypto.FILETYPE_ASN1`

    :returns: A list of domain names.
    :rtype: list

    """
    return _get_names_from_cert_or_req(
        csr, OpenSSL.crypto.load_certificate, typ)

[docs]def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):
    """Dump certificate chain into a bundle.

    :param list chain: List of `OpenSSL.crypto.X509` (or wrapped in
        `acme.jose.ComparableX509`).

    """
    # XXX: returns empty string when no chain is available, which
    # shuts up RenewableCert, but might not be the best solution...

    def _dump_cert(cert):
        if isinstance(cert, jose.ComparableX509):
            # pylint: disable=protected-access
            cert = cert.wrapped
        return OpenSSL.crypto.dump_certificate(filetype, cert)

    # assumes that OpenSSL.crypto.dump_certificate includes ending
    # newline character
    return b"".join(_dump_cert(cert) for cert in chain)

[docs]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)

[docs]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)

[docs]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`

    """
    # pylint: disable=redefined-outer-name
    with open(cert_path) as f:
        x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
                                               f.read())
    # pyopenssl always returns bytes
    timestamp = method(x509)
    reformatted_timestamp = [timestamp[0:4], b"-", timestamp[4:6], b"-",
                             timestamp[6:8], b"T", timestamp[8:10], b":",
                             timestamp[10:12], b":", timestamp[12:]]
    timestamp_str = b"".join(reformatted_timestamp)
    # pyrfc3339 uses "native" strings. That is, bytes on Python 2 and unicode
    # on Python 3
    if six.PY3:
        timestamp_str = timestamp_str.decode('ascii')
    return pyrfc3339.parse(timestamp_str)

[docs]def sha256sum(filename):
    """Compute a sha256sum of a file.

    :param str filename: path to the file whose hash will be computed

    :returns: sha256 digest of the file in hexadecimal
    :rtype: str
    """
    sha256 = hashlib.sha256()
    with open(filename, 'rb') as f:
        sha256.update(f.read())
    return sha256.hexdigest()

Source code for certbot.client

"""Certbot client API."""
import logging
import os
import platform

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
import OpenSSL
import zope.component

from acme import client as acme_client
from acme import errors as acme_errors
from acme import jose
from acme import messages

import certbot

from certbot import account
from certbot import auth_handler
from certbot import cli
from certbot import constants
from certbot import crypto_util
from certbot import eff
from certbot import error_handler
from certbot import errors
from certbot import interfaces
from certbot import reverter
from certbot import storage
from certbot import util

from certbot.display import ops as display_ops
from certbot.display import enhancements
from certbot.plugins import selection as plugin_selection

logger = logging.getLogger(__name__)

[docs]def acme_from_config_key(config, key):
    "Wrangle ACME client construction"
    # TODO: Allow for other alg types besides RS256
    net = acme_client.ClientNetwork(key, verify_ssl=(not config.no_verify_ssl),
                                    user_agent=determine_user_agent(config))
    return acme_client.Client(config.server, key=key, net=net)

[docs]def determine_user_agent(config):
    """
    Set a user_agent string in the config based on the choice of plugins.
    (this wasn't knowable at construction time)

    :returns: the client's User-Agent string
    :rtype: `str`
    """

    if config.user_agent is None:
        ua = ("CertbotACMEClient/{0} ({1}; {2}) Authenticator/{3} Installer/{4} "
              "({5}; flags: {6}) Py/{7}")
        ua = ua.format(certbot.__version__, cli.cli_command, util.get_os_info_ua(),
                       config.authenticator, config.installer, config.verb,
                       ua_flags(config), platform.python_version())
    else:
        ua = config.user_agent
    return ua

[docs]def ua_flags(config):
    "Turn some very important CLI flags into clues in the user agent."
    if isinstance(config, DummyConfig):
        return "FLAGS"
    flags = []
    if config.duplicate:
        flags.append("dup")
    if config.renew_by_default:
        flags.append("frn")
    if config.allow_subset_of_names:
        flags.append("asn")
    if config.noninteractive_mode:
        flags.append("n")
    hook_names = ("pre", "post", "renew", "manual_auth", "manual_cleanup")
    hooks = [getattr(config, h + "_hook") for h in hook_names]
    if any(hooks):
        flags.append("hook")
    return " ".join(flags)

[docs]class DummyConfig(object):
    "Shim for computing a sample user agent."
    def __init__(self):
        self.authenticator = "XXX"
        self.installer = "YYY"
        self.user_agent = None
        self.verb = "SUBCOMMAND"

    def __getattr__(self, name):
        "Any config properties we might have are None."
        return None

[docs]def sample_user_agent():
    "Document what this Certbot's user agent string will be like."

    return determine_user_agent(DummyConfig())

[docs]def register(config, account_storage, tos_cb=None):
    """Register new account with an ACME CA.

    This function takes care of generating fresh private key,
    registering the account, optionally accepting CA Terms of Service
    and finally saving the account. It should be called prior to
    initialization of `Client`, unless account has already been created.

    :param .IConfig config: Client configuration.

    :param .AccountStorage account_storage: Account storage where newly
        registered account will be saved to. Save happens only after TOS
        acceptance step, so any account private keys or
        `.RegistrationResource` will not be persisted if `tos_cb`
        returns ``False``.

    :param tos_cb: If ACME CA requires the user to accept a Terms of
        Service before registering account, client action is
        necessary. For example, a CLI tool would prompt the user
        acceptance. `tos_cb` must be a callable that should accept
        `.RegistrationResource` and return a `bool`: ``True`` iff the
        Terms of Service present in the contained
        `.Registration.terms_of_service` is accepted by the client, and
        ``False`` otherwise. ``tos_cb`` will be called only if the
        client action is necessary, i.e. when ``terms_of_service is not
        None``. This argument is optional, if not supplied it will
        default to automatic acceptance!

    :raises certbot.errors.Error: In case of any client problems, in
        particular registration failure, or unaccepted Terms of Service.
    :raises acme.errors.Error: In case of any protocol problems.

    :returns: Newly registered and saved account, as well as protocol
        API handle (should be used in `Client` initialization).
    :rtype: `tuple` of `.Account` and `acme.client.Client`

    """
    # Log non-standard actions, potentially wrong API calls
    if account_storage.find_all():
        logger.info("There are already existing accounts for %s", config.server)
    if config.email is None:
        if not config.register_unsafely_without_email:
            msg = ("No email was provided and "
                   "--register-unsafely-without-email was not present.")
            logger.warning(msg)
            raise errors.Error(msg)
        if not config.dry_run:
            logger.info("Registering without email!")

    # Each new registration shall use a fresh new key
    key = jose.JWKRSA(key=jose.ComparableRSAKey(
        rsa.generate_private_key(
            public_exponent=65537,
            key_size=config.rsa_key_size,
            backend=default_backend())))
    acme = acme_from_config_key(config, key)
    # TODO: add phone?
    regr = perform_registration(acme, config)

    if regr.terms_of_service is not None:
        if tos_cb is not None and not tos_cb(regr):
            raise errors.Error(
                "Registration cannot proceed without accepting "
                "Terms of Service.")
        regr = acme.agree_to_tos(regr)

    acc = account.Account(regr, key)
    account.report_new_account(config)
    account_storage.save(acc, acme)

    eff.handle_subscription(config)

    return acc, acme

[docs]def perform_registration(acme, config):
    """
    Actually register new account, trying repeatedly if there are email
    problems

    :param .IConfig config: Client configuration.
    :param acme.client.Client client: ACME client object.

    :returns: Registration Resource.
    :rtype: `acme.messages.RegistrationResource`
    """
    try:
        return acme.register(messages.NewRegistration.from_data(email=config.email))
    except messages.Error as e:
        if e.code == "invalidEmail" or e.code == "invalidContact":
            if config.noninteractive_mode:
                msg = ("The ACME server believes %s is an invalid email address. "
                       "Please ensure it is a valid email and attempt "
                       "registration again." % config.email)
                raise errors.Error(msg)
            else:
                config.email = display_ops.get_email(invalid=True)
                return perform_registration(acme, config)
        else:
            raise

[docs]class Client(object):
    """Certbot's client.

    :ivar .IConfig config: Client configuration.
    :ivar .Account account: Account registered with `register`.
    :ivar .AuthHandler auth_handler: Authorizations handler that will
        dispatch DV challenges to appropriate authenticators
        (providing `.IAuthenticator` interface).
    :ivar .IAuthenticator auth: Prepared (`.IAuthenticator.prepare`)
        authenticator that can solve ACME challenges.
    :ivar .IInstaller installer: Installer.
    :ivar acme.client.Client acme: Optional ACME client API handle.
       You might already have one from `register`.

    """

    def __init__(self, config, account_, auth, installer, acme=None):
        """Initialize a client."""
        self.config = config
        self.account = account_
        self.auth = auth
        self.installer = installer

        # Initialize ACME if account is provided
        if acme is None and self.account is not None:
            acme = acme_from_config_key(config, self.account.key)
        self.acme = acme

        if auth is not None:
            self.auth_handler = auth_handler.AuthHandler(
                auth, self.acme, self.account, self.config.pref_challs)
        else:
            self.auth_handler = None

[docs]    def obtain_certificate_from_csr(self, domains, csr, authzr=None):
        """Obtain certificate.

        Internal function with precondition that `domains` are
        consistent with identifiers present in the `csr`.

        :param list domains: Domain names.
        :param .util.CSR csr: PEM-encoded Certificate Signing
            Request. The key used to generate this CSR can be different
            than `authkey`.
        :param list authzr: List of
            :class:`acme.messages.AuthorizationResource`

        :returns: `.CertificateResource` and certificate chain (as
            returned by `.fetch_chain`).
        :rtype: tuple

        """
        if self.auth_handler is None:
            msg = ("Unable to obtain certificate because authenticator is "
                   "not set.")
            logger.warning(msg)
            raise errors.Error(msg)
        if self.account.regr is None:
            raise errors.Error("Please register with the ACME server first.")

        logger.debug("CSR: %s, domains: %s", csr, domains)

        if authzr is None:
            authzr = self.auth_handler.get_authorizations(domains)

        certr = self.acme.request_issuance(
            jose.ComparableX509(
                OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, csr.data)),
                authzr)

        notify = zope.component.getUtility(interfaces.IDisplay).notification
        retries = 0
        chain = None

        while retries <= 1:
            if retries:
                notify('Failed to fetch chain, please check your network '
                       'and continue', pause=True)
            try:
                chain = self.acme.fetch_chain(certr)
                break
            except acme_errors.Error:
                logger.debug('Failed to fetch chain', exc_info=True)
                retries += 1

        if chain is None:
            raise acme_errors.Error(
                'Failed to fetch chain. You should not deploy the generated '
                'certificate, please rerun the command for a new one.')

        return certr, chain

[docs]    def obtain_certificate(self, domains):
        """Obtains a certificate from the ACME server.

        `.register` must be called before `.obtain_certificate`

        :param list domains: domains to get a certificate

        :returns: `.CertificateResource`, certificate chain (as
            returned by `.fetch_chain`), and newly generated private key
            (`.util.Key`) and DER-encoded Certificate Signing Request
            (`.util.CSR`).
        :rtype: tuple

        """
        authzr = self.auth_handler.get_authorizations(
                domains,
                self.config.allow_subset_of_names)

        auth_domains = set(a.body.identifier.value for a in authzr)
        domains = [d for d in domains if d in auth_domains]

        # 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.csr_dir)
        certr, chain = self.obtain_certificate_from_csr(
            domains, csr, authzr=authzr)

        return certr, chain, key, csr

    # pylint: disable=no-member

[docs]    def obtain_and_enroll_certificate(self, domains, certname):
        """Obtain and enroll certificate.

        Get a new certificate for the specified domains using the specified
        authenticator and installer, and then create a new renewable lineage
        containing it.

        :param list domains: Domains to request.
        :param plugins: A PluginsFactory object.
        :param str certname: Name of new cert

        :returns: A new :class:`certbot.storage.RenewableCert` instance
            referred to the enrolled cert lineage, False if the cert could not
            be obtained, or None if doing a successful dry run.

        """
        certr, chain, key, _ = self.obtain_certificate(domains)

        if (self.config.config_dir != constants.CLI_DEFAULTS["config_dir"] or
                self.config.work_dir != constants.CLI_DEFAULTS["work_dir"]):
            logger.warning(
                "Non-standard path(s), might not work with crontab installed "
                "by your operating system package manager")

        new_name = certname if certname else domains[0]
        if self.config.dry_run:
            logger.debug("Dry run: Skipping creating new lineage for %s",
                        new_name)
            return None
        else:
            return storage.RenewableCert.new_lineage(
                new_name, OpenSSL.crypto.dump_certificate(
                    OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped),
                key.pem, crypto_util.dump_pyopenssl_chain(chain),
                self.config)

[docs]    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.
        :type certr: :class:`acme.messages.Certificate`

        :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, 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, fullchain_path:
            util.make_or_verify_dir(
                os.path.dirname(path), 0o755, os.geteuid(),
                self.config.strict_permissions)

        cert_pem = OpenSSL.crypto.dump_certificate(
            OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped)

        cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path)

        try:
            cert_file.write(cert_pem)
        finally:
            cert_file.close()
        logger.info("Server issued certificate; certificate written to %s",
                    abs_cert_path)

        if not chain_cert:
            return abs_cert_path, None, None
        else:
            chain_pem = crypto_util.dump_pyopenssl_chain(chain_cert)

            chain_file, abs_chain_path =\
                    _open_pem_file('chain_path', chain_path)
            fullchain_file, abs_fullchain_path =\
                    _open_pem_file('fullchain_path', fullchain_path)

            _save_chain(chain_pem, chain_file)
            _save_chain(cert_pem + chain_pem, fullchain_file)

            return abs_cert_path, abs_chain_path, abs_fullchain_path

[docs]    def deploy_certificate(self, domains, privkey_path,
                           cert_path, chain_path, fullchain_path):
        """Install certificate

        :param list domains: list of domains to install the certificate
        :param str privkey_path: path to certificate private key
        :param str cert_path: certificate file path (optional)
        :param str chain_path: chain file path

        """
        if self.installer is None:
            logger.warning("No installer specified, client is unable to deploy"
                           "the certificate")
            raise errors.Error("No installer available")

        chain_path = None if chain_path is None else os.path.abspath(chain_path)

        msg = ("Unable to install the certificate")
        with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg):
            for dom in domains:
                self.installer.deploy_cert(
                    domain=dom, cert_path=os.path.abspath(cert_path),
                    key_path=os.path.abspath(privkey_path),
                    chain_path=chain_path,
                    fullchain_path=fullchain_path)
                self.installer.save()  # needed by the Apache plugin

            self.installer.save("Deployed ACME Certificate")

        msg = ("We were unable to install your certificate, "
               "however, we successfully restored your "
               "server to its prior configuration.")
        with error_handler.ErrorHandler(self._rollback_and_restart, msg):
            # sites may have been enabled / final cleanup
            self.installer.restart()

[docs]    def enhance_config(self, domains, chain_path):
        """Enhance the configuration.

        :param list domains: list of domains to configure
        :param chain_path: chain file path
        :type chain_path: `str` or `None`

        :raises .errors.Error: if no installer is specified in the
            client.

        """
        if self.installer is None:
            logger.warning("No installer is specified, there isn't any "
                           "configuration to enhance.")
            raise errors.Error("No installer available")

        enhanced = False
        enhancement_info = (
            ("hsts", "ensure-http-header", "Strict-Transport-Security"),
            ("redirect", "redirect", None),
            ("staple", "staple-ocsp", chain_path),
            ("uir", "ensure-http-header", "Upgrade-Insecure-Requests"),)
        supported = self.installer.supported_enhancements()

        for config_name, enhancement_name, option in enhancement_info:
            config_value = getattr(self.config, config_name)
            if enhancement_name in supported:
                if config_name == "redirect" and config_value is None:
                    config_value = enhancements.ask(enhancement_name)
                if config_value:
                    self.apply_enhancement(domains, enhancement_name, option)
                    enhanced = True
            elif config_value:
                logger.warning(
                    "Option %s is not supported by the selected installer. "
                    "Skipping enhancement.", config_name)

        msg = ("We were unable to restart web server")
        if enhanced:
            with error_handler.ErrorHandler(self._rollback_and_restart, msg):
                self.installer.restart()

[docs]    def apply_enhancement(self, domains, enhancement, options=None):
        """Applies an enhancement on all domains.

        :param list domains: list of ssl_vhosts (as strings)
        :param str enhancement: name of enhancement, e.g. ensure-http-header
        :param str options: options to enhancement, e.g. Strict-Transport-Security

            .. note:: When more `options` are needed, make options a list.

        :raises .errors.PluginError: If Enhancement is not supported, or if
            there is any other problem with the enhancement.

        """
        msg = ("We were unable to set up enhancement %s for your server, "
               "however, we successfully installed your certificate."
               % (enhancement))
        with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg):
            for dom in domains:
                try:
                    self.installer.enhance(dom, enhancement, options)
                except errors.PluginEnhancementAlreadyPresent:
                    logger.warning("Enhancement %s was already set.",
                            enhancement)
                except errors.PluginError:
                    logger.warning("Unable to set enhancement %s for %s",
                            enhancement, dom)
                    raise

            self.installer.save("Add enhancement %s" % (enhancement))

[docs]    def _recovery_routine_with_msg(self, success_msg):
        """Calls the installer's recovery routine and prints success_msg

        :param str success_msg: message to show on successful recovery

        """
        self.installer.recovery_routine()
        reporter = zope.component.getUtility(interfaces.IReporter)
        reporter.add_message(success_msg, reporter.HIGH_PRIORITY)

[docs]    def _rollback_and_restart(self, success_msg):
        """Rollback the most recent checkpoint and restart the webserver

        :param str success_msg: message to show on successful rollback

        """
        logger.critical("Rolling back to previous server configuration...")
        reporter = zope.component.getUtility(interfaces.IReporter)
        try:
            self.installer.rollback_checkpoints()
            self.installer.restart()
        except:
            # TODO: suggest letshelp-letsencrypt here
            reporter.add_message(
                "An error occurred and we failed to restore your config and "
                "restart your server. Please submit a bug report to "
                "https://github.com/letsencrypt/letsencrypt",
                reporter.HIGH_PRIORITY)
            raise
        reporter.add_message(success_msg, reporter.HIGH_PRIORITY)

[docs]def validate_key_csr(privkey, csr=None):
    """Validate Key and CSR files.

    Verifies that the client key and csr arguments are valid and correspond to
    one another. This does not currently check the names in the CSR due to
    the inability to read SANs from CSRs in python crypto libraries.

    If csr is left as None, only the key will be validated.

    :param privkey: Key associated with CSR
    :type privkey: :class:`certbot.util.Key`

    :param .util.CSR csr: CSR

    :raises .errors.Error: when validation fails

    """
    # TODO: Handle all of these problems appropriately
    # The client can eventually do things like prompt the user
    # and allow the user to take more appropriate actions

    # Key must be readable and valid.
    if privkey.pem and not crypto_util.valid_privkey(privkey.pem):
        raise errors.Error("The provided key is not a valid key")

    if csr:
        if csr.form == "der":
            csr_obj = OpenSSL.crypto.load_certificate_request(
                OpenSSL.crypto.FILETYPE_ASN1, csr.data)
            csr = util.CSR(csr.file, OpenSSL.crypto.dump_certificate(
                OpenSSL.crypto.FILETYPE_PEM, csr_obj), "pem")

        # If CSR is provided, it must be readable and valid.
        if csr.data and not crypto_util.valid_csr(csr.data):
            raise errors.Error("The provided CSR is not a valid CSR")

        # If both CSR and key are provided, the key must be the same key used
        # in the CSR.
        if csr.data and privkey.pem:
            if not crypto_util.csr_matches_pubkey(
                    csr.data, privkey.pem):
                raise errors.Error("The key and CSR do not match")

[docs]def rollback(default_installer, checkpoints, config, plugins):
    """Revert configuration the specified number of checkpoints.

    :param int checkpoints: Number of checkpoints to revert.

    :param config: Configuration.
    :type config: :class:`certbot.interfaces.IConfig`

    """
    # Misconfigurations are only a slight problems... allow the user to rollback
    installer = plugin_selection.pick_installer(
        config, default_installer, plugins, question="Which installer "
        "should be used for rollback?")

    # No Errors occurred during init... proceed normally
    # If installer is None... couldn't find an installer... there shouldn't be
    # anything to rollback
    if installer is not None:
        installer.rollback_checkpoints(checkpoints)
        installer.restart()

[docs]def view_config_changes(config, num=None):
    """View checkpoints and associated configuration changes.

    .. note:: This assumes that the installation is using a Reverter object.

    :param config: Configuration.
    :type config: :class:`certbot.interfaces.IConfig`

    """
    rev = reverter.Reverter(config)
    rev.recovery_routine()
    rev.view_config_changes(num)

[docs]def _open_pem_file(cli_arg_path, pem_path):
    """Open a pem file.

    If cli_arg_path was set by the client, open that.
    Otherwise, uniquify the file path.

    :param str cli_arg_path: the cli arg name, e.g. cert_path
    :param str pem_path: the pem file path to open

    :returns: a tuple of file object and its absolute file path

    """
    if cli.set_by_cli(cli_arg_path):
        return util.safe_open(pem_path, chmod=0o644, mode="wb"),\
            os.path.abspath(pem_path)
    else:
        uniq = util.unique_file(pem_path, 0o644, "wb")
        return uniq[0], os.path.abspath(uniq[1])

[docs]def _save_chain(chain_pem, chain_file):
    """Saves chain_pem at a unique path based on chain_path.

    :param str chain_pem: certificate chain in PEM format
    :param str chain_file: chain file object

    """
    try:
        chain_file.write(chain_pem)
    finally:
        chain_file.close()

    logger.info("Cert chain written to %s", chain_file.name)

Source code for certbot.util

"""Utilities for all Certbot."""
import argparse
import atexit
import collections
# distutils.version under virtualenv confuses pylint
# For more info, see: https://github.com/PyCQA/pylint/issues/73
import distutils.version  # pylint: disable=import-error,no-name-in-module
import errno
import logging
import os
import platform
import re
import six
import socket
import stat
import subprocess
import sys

import configargparse

from certbot import constants
from certbot import errors
from certbot import lock

try:
    from collections import OrderedDict
except ImportError:  # pragma: no cover
    # OrderedDict was added in Python 2.7
    from ordereddict import OrderedDict  # pylint: disable=import-error

logger = logging.getLogger(__name__)

Key = collections.namedtuple("Key", "file pem")
# Note: form is the type of data, "pem" or "der"
CSR = collections.namedtuple("CSR", "file data form")

# ANSI SGR escape codes
# Formats text as bold or with increased intensity
ANSI_SGR_BOLD = '\033[1m'
# Colors text red
ANSI_SGR_RED = "\033[31m"
# Resets output format
ANSI_SGR_RESET = "\033[0m"

PERM_ERR_FMT = os.linesep.join((
    "The following error was encountered:", "{0}",
    "If running as non-root, set --config-dir, "
    "--work-dir, and --logs-dir to writeable paths."))

# Stores importing process ID to be used by atexit_register()
_INITIAL_PID = os.getpid()
# Maps paths to locked directories to their lock object. All locks in
# the dict are attempted to be cleaned up at program exit. If the
# program exits before the lock is cleaned up, it is automatically
# released, but the file isn't deleted.
_LOCKS = OrderedDict()

[docs]def run_script(params, log=logger.error):
    """Run the script with the given params.

    :param list params: List of parameters to pass to Popen
    :param logging.Logger log: Logger to use for errors

    """
    try:
        proc = subprocess.Popen(params,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                universal_newlines=True)

    except (OSError, ValueError):
        msg = "Unable to run the command: %s" % " ".join(params)
        log(msg)
        raise errors.SubprocessError(msg)

    stdout, stderr = proc.communicate()

    if proc.returncode != 0:
        msg = "Error while running %s.\n%s\n%s" % (
            " ".join(params), stdout, stderr)
        # Enter recovery routine...
        log(msg)
        raise errors.SubprocessError(msg)

    return stdout, stderr

[docs]def exe_exists(exe):
    """Determine whether path/name refers to an executable.

    :param str exe: Executable path or name

    :returns: If exe is a valid executable
    :rtype: bool

    """
    def is_exe(path):
        """Determine if path is an exe."""
        return os.path.isfile(path) and os.access(path, os.X_OK)

    path, _ = os.path.split(exe)
    if path:
        return is_exe(exe)
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            if is_exe(os.path.join(path, exe)):
                return True

    return False

[docs]def lock_dir_until_exit(dir_path):
    """Lock the directory at dir_path until program exit.

    :param str dir_path: path to directory

    :raises errors.LockError: if the lock is held by another process

    """
    if not _LOCKS:  # this is the first lock to be released at exit
        atexit_register(_release_locks)

    if dir_path not in _LOCKS:
        _LOCKS[dir_path] = lock.lock_dir(dir_path)

def _release_locks():
    for dir_lock in six.itervalues(_LOCKS):
        try:
            dir_lock.release()
        except:  # pylint: disable=bare-except
            msg = 'Exception occurred releasing lock: {0!r}'.format(dir_lock)
            logger.debug(msg, exc_info=True)

[docs]def set_up_core_dir(directory, mode, uid, strict):
    """Ensure directory exists with proper permissions and is locked.

    :param str directory: Path to a directory.
    :param int mode: Directory mode.
    :param int uid: Directory owner.
    :param bool strict: require directory to be owned by current user

    :raises .errors.LockError: if the directory cannot be locked
    :raises .errors.Error: if the directory cannot be made or verified

    """
    try:
        make_or_verify_dir(directory, mode, uid, strict)
        lock_dir_until_exit(directory)
    except OSError as error:
        logger.debug("Exception was:", exc_info=True)
        raise errors.Error(PERM_ERR_FMT.format(error))

[docs]def make_or_verify_dir(directory, mode=0o755, uid=0, strict=False):
    """Make sure directory exists with proper permissions.

    :param str directory: Path to a directory.
    :param int mode: Directory mode.
    :param int uid: Directory owner.
    :param bool strict: require directory to be owned by current user

    :raises .errors.Error: if a directory already exists,
        but has wrong permissions or owner

    :raises OSError: if invalid or inaccessible file names and
        paths, or other arguments that have the correct type,
        but are not accepted by the operating system.

    """
    try:
        os.makedirs(directory, mode)
    except OSError as exception:
        if exception.errno == errno.EEXIST:
            if strict and not check_permissions(directory, mode, uid):
                raise errors.Error(
                    "%s exists, but it should be owned by user %d with"
                    "permissions %s" % (directory, uid, oct(mode)))
        else:
            raise

[docs]def check_permissions(filepath, mode, uid=0):
    """Check file or directory permissions.

    :param str filepath: Path to the tested file (or directory).
    :param int mode: Expected file mode.
    :param int uid: Expected file owner.

    :returns: True if `mode` and `uid` match, False otherwise.
    :rtype: bool

    """
    file_stat = os.stat(filepath)
    return stat.S_IMODE(file_stat.st_mode) == mode and file_stat.st_uid == uid

[docs]def safe_open(path, mode="w", chmod=None, buffering=None):
    """Safely open a file.

    :param str path: Path to a file.
    :param str mode: Same os `mode` for `open`.
    :param int chmod: Same as `mode` for `os.open`, uses Python defaults
        if ``None``.
    :param int buffering: Same as `bufsize` for `os.fdopen`, uses Python
        defaults if ``None``.

    """
    # pylint: disable=star-args
    open_args = () if chmod is None else (chmod,)
    fdopen_args = () if buffering is None else (buffering,)
    return os.fdopen(
        os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, *open_args),
        mode, *fdopen_args)

def _unique_file(path, filename_pat, count, chmod, mode):
    while True:
        current_path = os.path.join(path, filename_pat(count))
        try:
            return safe_open(current_path, chmod=chmod, mode=mode),\
                os.path.abspath(current_path)
        except OSError as err:
            # "File exists," is okay, try a different name.
            if err.errno != errno.EEXIST:
                raise
        count += 1

[docs]def unique_file(path, chmod=0o777, mode="w"):
    """Safely finds a unique file.

    :param str path: path/filename.ext
    :param int chmod: File mode
    :param str mode: Open mode

    :returns: tuple of file object and file name

    """
    path, tail = os.path.split(path)
    return _unique_file(
        path, filename_pat=(lambda count: "%04d_%s" % (count, tail)),
        count=0, chmod=chmod, mode=mode)

[docs]def unique_lineage_name(path, filename, chmod=0o644, mode="w"):
    """Safely finds a unique file using lineage convention.

    :param str path: directory path
    :param str filename: proposed filename
    :param int chmod: file mode
    :param str mode: open mode

    :returns: tuple of file object and file name (which may be modified
        from the requested one by appending digits to ensure uniqueness)

    :raises OSError: if writing files fails for an unanticipated reason,
        such as a full disk or a lack of permission to write to
        specified location.

    """
    preferred_path = os.path.join(path, "%s.conf" % (filename))
    try:
        return safe_open(preferred_path, chmod=chmod), preferred_path
    except OSError as err:
        if err.errno != errno.EEXIST:
            raise
    return _unique_file(
        path, filename_pat=(lambda count: "%s-%04d.conf" % (filename, count)),
        count=1, chmod=chmod, mode=mode)

[docs]def safely_remove(path):
    """Remove a file that may not exist."""
    try:
        os.remove(path)
    except OSError as err:
        if err.errno != errno.ENOENT:
            raise

[docs]def get_filtered_names(all_names):
    """Removes names that aren't considered valid by Let's Encrypt.

    :param set all_names: all names found in the configuration

    :returns: all found names that are considered valid by LE
    :rtype: set

    """
    filtered_names = set()
    for name in all_names:
        try:
            filtered_names.add(enforce_le_validity(name))
        except errors.ConfigurationError as error:
            logger.debug('Not suggesting name "%s"', name)
            logger.debug(error)
    return filtered_names

[docs]def get_os_info(filepath="/etc/os-release"):
    """
    Get OS name and version

    :param str filepath: File path of os-release file
    :returns: (os_name, os_version)
    :rtype: `tuple` of `str`
    """

    if os.path.isfile(filepath):
        # Systemd os-release parsing might be viable
        os_name, os_version = get_systemd_os_info(filepath=filepath)
        if os_name:
            return (os_name, os_version)

    # Fallback to platform module
    return get_python_os_info()

[docs]def get_os_info_ua(filepath="/etc/os-release"):
    """
    Get OS name and version string for User Agent

    :param str filepath: File path of os-release file
    :returns: os_ua
    :rtype: `str`
    """

    if os.path.isfile(filepath):
        os_ua = _get_systemd_os_release_var("PRETTY_NAME", filepath=filepath)
        if not os_ua:
            os_ua = _get_systemd_os_release_var("NAME", filepath=filepath)
        if os_ua:
            return os_ua

    # Fallback
    return " ".join(get_python_os_info())

[docs]def get_systemd_os_info(filepath="/etc/os-release"):
    """
    Parse systemd /etc/os-release for distribution information

    :param str filepath: File path of os-release file
    :returns: (os_name, os_version)
    :rtype: `tuple` of `str`
    """

    os_name = _get_systemd_os_release_var("ID", filepath=filepath)
    os_version = _get_systemd_os_release_var("VERSION_ID", filepath=filepath)

    return (os_name, os_version)

[docs]def get_systemd_os_like(filepath="/etc/os-release"):
    """
    Get a list of strings that indicate the distribution likeness to
    other distributions.

    :param str filepath: File path of os-release file
    :returns: List of distribution acronyms
    :rtype: `list` of `str`
    """

    return _get_systemd_os_release_var("ID_LIKE", filepath).split(" ")

[docs]def _get_systemd_os_release_var(varname, filepath="/etc/os-release"):
    """
    Get single value from systemd /etc/os-release

    :param str varname: Name of variable to fetch
    :param str filepath: File path of os-release file
    :returns: requested value
    :rtype: `str`
    """

    var_string = varname+"="
    if not os.path.isfile(filepath):
        return ""
    with open(filepath, 'r') as fh:
        contents = fh.readlines()

    for line in contents:
        if line.strip().startswith(var_string):
            # Return the value of var, normalized
            return _normalize_string(line.strip()[len(var_string):])
    return ""

[docs]def _normalize_string(orig):
    """
    Helper function for _get_systemd_os_release_var() to remove quotes
    and whitespaces
    """
    return orig.replace('"', '').replace("'", "").strip()

[docs]def get_python_os_info():
    """
    Get Operating System type/distribution and major version
    using python platform module

    :returns: (os_name, os_version)
    :rtype: `tuple` of `str`
    """
    info = platform.system_alias(
        platform.system(),
        platform.release(),
        platform.version()
    )
    os_type, os_ver, _ = info
    os_type = os_type.lower()
    if os_type.startswith('linux'):
        info = platform.linux_distribution()
        # On arch, platform.linux_distribution() is reportedly ('','',''),
        # so handle it defensively
        if info[0]:
            os_type = info[0]
        if info[1]:
            os_ver = info[1]
    elif os_type.startswith('darwin'):
        try:
            proc = subprocess.Popen(
                ["/usr/bin/sw_vers", "-productVersion"],
                stdout=subprocess.PIPE,
                universal_newlines=True,
            )
        except OSError:
            proc = subprocess.Popen(
                ["sw_vers", "-productVersion"],
                stdout=subprocess.PIPE,
                universal_newlines=True,
            )
        os_ver = proc.communicate()[0].rstrip('\n')
    elif os_type.startswith('freebsd'):
        # eg "9.3-RC3-p1"
        os_ver = os_ver.partition("-")[0]
        os_ver = os_ver.partition(".")[0]
    elif platform.win32_ver()[1]:
        os_ver = platform.win32_ver()[1]
    else:
        # Cases known to fall here: Cygwin python
        os_ver = ''
    return os_type, os_ver

# Just make sure we don't get pwned... Make sure that it also doesn't
# start with a period or have two consecutive periods <- this needs to
# be done in addition to the regex
EMAIL_REGEX = re.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+$")

[docs]def safe_email(email):
    """Scrub email address before using it."""
    if EMAIL_REGEX.match(email) is not None:
        return not email.startswith(".") and ".." not in email
    else:
        logger.warning("Invalid email address: %s.", email)
        return False

[docs]class _ShowWarning(argparse.Action):
    """Action to log a warning when an argument is used."""
    def __call__(self, unused1, unused2, unused3, option_string=None):
        sys.stderr.write(
            "Use of {0} is deprecated.\n".format(option_string))

[docs]def add_deprecated_argument(add_argument, argument_name, nargs):
    """Adds a deprecated argument with the name argument_name.

    Deprecated arguments are not shown in the help. If they are used on
    the command line, a warning is shown stating that the argument is
    deprecated and no other action is taken.

    :param callable add_argument: Function that adds arguments to an
        argument parser/group.
    :param str argument_name: Name of deprecated argument.
    :param nargs: Value for nargs when adding the argument to argparse.

    """
    if _ShowWarning not in configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE:
        # In version 0.12.0 ACTION_TYPES_THAT_DONT_NEED_A_VALUE was
        # changed from a set to a tuple.
        if isinstance(configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE, set):
            # pylint: disable=no-member
            configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE.add(
                _ShowWarning)
        else:
            configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE += (
                _ShowWarning,)
    add_argument(argument_name, action=_ShowWarning,
                 help=argparse.SUPPRESS, nargs=nargs)

[docs]def enforce_le_validity(domain):
    """Checks that Let's Encrypt will consider domain to be valid.

    :param str domain: FQDN to check
    :type domain: `str` or `unicode`
    :returns: The domain cast to `str`, with ASCII-only contents
    :rtype: str
    :raises ConfigurationError: for invalid domains and cases where Let's
                                Encrypt currently will not issue certificates

    """
    domain = enforce_domain_sanity(domain)
    if not re.match("^[A-Za-z0-9.-]*$", domain):
        raise errors.ConfigurationError(
            "{0} contains an invalid character. "
            "Valid characters are A-Z, a-z, 0-9, ., and -.".format(domain))

    labels = domain.split(".")
    if len(labels) < 2:
        raise errors.ConfigurationError(
            "{0} needs at least two labels".format(domain))
    for label in labels:
        if label.startswith("-"):
            raise errors.ConfigurationError(
                'label "{0}" in domain "{1}" cannot start with "-"'.format(
                    label, domain))
        if label.endswith("-"):
            raise errors.ConfigurationError(
                'label "{0}" in domain "{1}" cannot end with "-"'.format(
                    label, domain))
    return domain

[docs]def enforce_domain_sanity(domain):
    """Method which validates domain value and errors out if
    the requirements are not met.

    :param domain: Domain to check
    :type domain: `str` or `unicode`
    :raises ConfigurationError: for invalid domains and cases where Let's
                                Encrypt currently will not issue certificates

    :returns: The domain cast to `str`, with ASCII-only contents
    :rtype: str
    """
    if isinstance(domain, six.text_type):
        wildcard_marker = u"*."
    else:
        wildcard_marker = b"*."

    # Check if there's a wildcard domain
    if domain.startswith(wildcard_marker):
        raise errors.ConfigurationError(
            "Wildcard domains are not supported: {0}".format(domain))

    # Unicode
    try:
        if isinstance(domain, six.binary_type):
            domain = domain.decode('utf-8')
        domain.encode('ascii')
    except UnicodeError:
        raise errors.ConfigurationError("Non-ASCII domain names not supported. "
            "To issue for an Internationalized Domain Name, use Punycode.")

    domain = domain.lower()

    # Remove trailing dot
    domain = domain[:-1] if domain.endswith(u'.') else domain

    # Separately check for odd "domains" like "http://example.com" to fail
    # fast and provide a clear error message
    for scheme in ["http", "https"]:  # Other schemes seem unlikely
        if domain.startswith("{0}://".format(scheme)):
            raise errors.ConfigurationError(
                "Requested name {0} appears to be a URL, not a FQDN. "
                "Try again without the leading \"{1}://\".".format(
                    domain, scheme
                )
            )

    # Explain separately that IP addresses aren't allowed (apart from not
    # being FQDNs) because hope springs eternal concerning this point
    try:
        socket.inet_aton(domain)
        raise errors.ConfigurationError(
            "Requested name {0} is an IP address. The Let's Encrypt "
            "certificate authority will not issue certificates for a "
            "bare IP address.".format(domain))
    except socket.error:
        # It wasn't an IP address, so that's good
        pass

    # FQDN checks according to RFC 2181: domain name should be less than 255
    # octets (inclusive). And each label is 1 - 63 octets (inclusive).
    # https://tools.ietf.org/html/rfc2181#section-11
    msg = "Requested domain {0} is not a FQDN because".format(domain)
    if len(domain) > 255:
        raise errors.ConfigurationError("{0} it is too long.".format(msg))
    labels = domain.split('.')
    for l in labels:
        if not l:
            raise errors.ConfigurationError("{0} it contains an empty label.".format(msg))
        elif len(l) > 63:
            raise errors.ConfigurationError("{0} label {1} is too long.".format(msg, l))

    return domain

[docs]def get_strict_version(normalized):
    """Converts a normalized version to a strict version.

    :param str normalized: normalized version string

    :returns: An equivalent strict version
    :rtype: distutils.version.StrictVersion

    """
    # strict version ending with "a" and a number designates a pre-release
    # pylint: disable=no-member
    return distutils.version.StrictVersion(normalized.replace(".dev", "a"))

[docs]def is_staging(srv):
    """
    Determine whether a given ACME server is a known test / staging server.

    :param str srv: the URI for the ACME server
    :returns: True iff srv is a known test / staging server
    :rtype bool:
    """
    return srv == constants.STAGING_URI or "staging" in srv

[docs]def atexit_register(func, *args, **kwargs):
    """Sets func to be called before the program exits.

    Special care is taken to ensure func is only called when the process
    that first imports this module exits rather than any child processes.

    :param function func: function to be called in case of an error

    """
    atexit.register(_atexit_call, func, *args, **kwargs)

def _atexit_call(func, *args, **kwargs):
    if _INITIAL_PID == os.getpid():
        func(*args, **kwargs)

Source code for certbot.display.enhancements

"""Certbot Enhancement Display"""
import logging

import zope.component

from certbot import errors
from certbot import interfaces
from certbot.display import util as display_util

logger = logging.getLogger(__name__)

# Define a helper function to avoid verbose code
util = zope.component.getUtility

[docs]def ask(enhancement):
    """Display the enhancement to the user.

    :param str enhancement: One of the
        :class:`certbot.CONFIG.ENHANCEMENTS` enhancements

    :returns: True if feature is desired, False otherwise
    :rtype: bool

    :raises .errors.Error: if the enhancement provided is not supported

    """
    try:
        # Call the appropriate function based on the enhancement
        return DISPATCH[enhancement]()
    except KeyError:
        logger.error("Unsupported enhancement given to ask(): %s", enhancement)
        raise errors.Error("Unsupported Enhancement")

[docs]def redirect_by_default():
    """Determines whether the user would like to redirect to HTTPS.

    :returns: True if redirect is desired, False otherwise
    :rtype: bool

    """
    choices = [
        ("Easy", "Allow both HTTP and HTTPS access to these sites"),
        ("Secure", "Make all requests redirect to secure HTTPS access"),
    ]

    code, selection = util(interfaces.IDisplay).menu(
        "Please choose whether HTTPS access is required or optional.",
        choices, default=0,
        cli_flag="--redirect / --no-redirect", force_interactive=True)

    if code != display_util.OK:
        return False

    return selection == 1

DISPATCH = {
    "redirect": redirect_by_default
}

Source code for certbot.display.ops

"""Contains UI methods for LE user operations."""
import logging
import os

import zope.component

from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.display import util as display_util

logger = logging.getLogger(__name__)

# Define a helper function to avoid verbose code
z_util = zope.component.getUtility

[docs]def get_email(invalid=False, optional=True):
    """Prompt for valid email address.

    :param bool invalid: True if an invalid address was provided by the user
    :param bool optional: True if the user can use
        --register-unsafely-without-email to avoid providing an e-mail

    :returns: e-mail address
    :rtype: str

    :raises errors.Error: if the user cancels

    """
    invalid_prefix = "There seem to be problems with that address. "
    msg = "Enter email address (used for urgent renewal and security notices)"
    unsafe_suggestion = ("\n\nIf you really want to skip this, you can run "
                         "the client with --register-unsafely-without-email "
                         "but make sure you then backup your account key from "
                         "/etc/letsencrypt/accounts\n\n")
    if optional:
        if invalid:
            msg += unsafe_suggestion
            suggest_unsafe = False
        else:
            suggest_unsafe = True
    else:
        suggest_unsafe = False

    while True:
        try:
            code, email = z_util(interfaces.IDisplay).input(
                invalid_prefix + msg if invalid else msg,
                force_interactive=True)
        except errors.MissingCommandlineFlag:
            msg = ("You should register before running non-interactively, "
                   "or provide --agree-tos and --email <email_address> flags.")
            raise errors.MissingCommandlineFlag(msg)

        if code != display_util.OK:
            if optional:
                raise errors.Error(
                    "An e-mail address or "
                    "--register-unsafely-without-email must be provided.")
            else:
                raise errors.Error("An e-mail address must be provided.")
        elif util.safe_email(email):
            return email
        elif suggest_unsafe:
            msg += unsafe_suggestion
            suggest_unsafe = False  # add this message at most once

        invalid = bool(email)

[docs]def choose_account(accounts):
    """Choose an account.

    :param list accounts: Containing at least one
        :class:`~certbot.account.Account`

    """
    # Note this will get more complicated once we start recording authorizations
    labels = [acc.slug for acc in accounts]

    code, index = z_util(interfaces.IDisplay).menu(
        "Please choose an account", labels, force_interactive=True)
    if code == display_util.OK:
        return accounts[index]
    else:
        return None

[docs]def choose_names(installer):
    """Display screen to select domains to validate.

    :param installer: An installer object
    :type installer: :class:`certbot.interfaces.IInstaller`

    :returns: List of selected names
    :rtype: `list` of `str`

    """
    if installer is None:
        logger.debug("No installer, picking names manually")
        return _choose_names_manually()

    domains = list(installer.get_all_names())
    names = get_valid_domains(domains)

    if not names:
        return _choose_names_manually(
            "No names were found in your configuration files. ")

    code, names = _filter_names(names)
    if code == display_util.OK and names:
        return names
    else:
        return []

[docs]def get_valid_domains(domains):
    """Helper method for choose_names that implements basic checks
     on domain names

    :param list domains: Domain names to validate
    :return: List of valid domains
    :rtype: list
    """
    valid_domains = []
    for domain in domains:
        try:
            valid_domains.append(util.enforce_domain_sanity(domain))
        except errors.ConfigurationError:
            continue
    return valid_domains

[docs]def _sort_names(FQDNs):
    """Sort FQDNs by SLD (and if many, by their subdomains)

    :param list FQDNs: list of domain names

    :returns: Sorted list of domain names
    :rtype: list
    """
    return sorted(FQDNs, key=lambda fqdn: fqdn.split('.')[::-1][1:])

[docs]def _filter_names(names):
    """Determine which names the user would like to select from a list.

    :param list names: domain names

    :returns: tuple of the form (`code`, `names`) where
        `code` - str display exit code
        `names` - list of names selected
    :rtype: tuple

    """
    #Sort by domain first, and then by subdomain
    sorted_names = _sort_names(names)

    code, names = z_util(interfaces.IDisplay).checklist(
        "Which names would you like to activate HTTPS for?",
        tags=sorted_names, cli_flag="--domains", force_interactive=True)
    return code, [str(s) for s in names]

[docs]def _choose_names_manually(prompt_prefix=""):
    """Manually input names for those without an installer.

    :param str prompt_prefix: string to prepend to prompt for domains

    :returns: list of provided names
    :rtype: `list` of `str`

    """
    code, input_ = z_util(interfaces.IDisplay).input(
        prompt_prefix +
        "Please enter in your domain name(s) (comma and/or space separated) ",
        cli_flag="--domains", force_interactive=True)

    if code == display_util.OK:
        invalid_domains = dict()
        retry_message = ""
        try:
            domain_list = display_util.separate_list_input(input_)
        except UnicodeEncodeError:
            domain_list = []
            retry_message = (
                "Internationalized domain names are not presently "
                "supported.{0}{0}Would you like to re-enter the "
                "names?{0}").format(os.linesep)

        for i, domain in enumerate(domain_list):
            try:
                domain_list[i] = util.enforce_domain_sanity(domain)
            except errors.ConfigurationError as e:
                invalid_domains[domain] = str(e)

        if len(invalid_domains):
            retry_message = (
                "One or more of the entered domain names was not valid:"
                "{0}{0}").format(os.linesep)
            for domain in invalid_domains:
                retry_message = retry_message + "{1}: {2}{0}".format(
                    os.linesep, domain, invalid_domains[domain])
            retry_message = retry_message + (
                "{0}Would you like to re-enter the names?{0}").format(
                    os.linesep)

        if retry_message:
            # We had error in input
            retry = z_util(interfaces.IDisplay).yesno(retry_message,
                                                      force_interactive=True)
            if retry:
                return _choose_names_manually()
        else:
            return domain_list
    return []

[docs]def success_installation(domains):
    """Display a box confirming the installation of HTTPS.

    :param list domains: domain names which were enabled

    """
    z_util(interfaces.IDisplay).notification(
        "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))),
        pause=False)

[docs]def success_renewal(domains):
    """Display a box confirming the renewal of an existing certificate.

    :param list domains: domain names which were renewed

    """
    z_util(interfaces.IDisplay).notification(
        "Your existing certificate has been successfully renewed, and the "
        "new certificate has been installed.{1}{1}"
        "The new certificate covers the following domains: {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))),
        pause=False)

[docs]def success_revocation(cert_path):
    """Display a box confirming a certificate has been revoked.

    :param list cert_path: path to certificate which was revoked.

    """
    z_util(interfaces.IDisplay).notification(
        "Congratulations! You have successfully revoked the certificate "
        "that was located at {0}{1}{1}".format(
            cert_path,
            os.linesep),
        pause=False)

[docs]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]

[docs]def _gen_https_names(domains):
    """Returns a string of the https domains.

    Domains are formatted nicely with https:// prepended to each.

    :param list domains: Each domain is a 'str'

    """
    if len(domains) == 1:
        return "https://{0}".format(domains[0])
    elif len(domains) == 2:
        return "https://{dom[0]} and https://{dom[1]}".format(dom=domains)
    elif len(domains) > 2:
        return "{0}{1}{2}".format(
            ", ".join("https://%s" % dom for dom in domains[:-1]),
            ", and https://",
            domains[-1])

    return ""

def _get_validated(method, validator, message, default=None, **kwargs):
    if default is not None:
        try:
            validator(default)
        except errors.Error as error:
            logger.debug('Encountered invalid default value "%s" when prompting for "%s"',
                         default,
                         message,
                         exc_info=True)
            raise AssertionError('Invalid default "{0}"'.format(default))

    while True:
        code, raw = method(message, default=default, **kwargs)
        if code == display_util.OK:
            try:
                validator(raw)
                return code, raw
            except errors.Error as error:
                logger.debug('Validator rejected "%s" when prompting for "%s"',
                             raw,
                             message,
                             exc_info=True)
                zope.component.getUtility(interfaces.IDisplay).notification(str(error), pause=False)
        else:
            return code, raw

[docs]def validated_input(validator, *args, **kwargs):
    """Like `~certbot.interfaces.IDisplay.input`, but with validation.

    :param callable validator: A method which will be called on the
        supplied input. If the method raises a `errors.Error`, its
        text will be displayed and the user will be re-prompted.
    :param list `*args`: Arguments to be passed to `~certbot.interfaces.IDisplay.input`.
    :param dict `**kwargs`: Arguments to be passed to `~certbot.interfaces.IDisplay.input`.
    :return: as `~certbot.interfaces.IDisplay.input`
    :rtype: tuple
    """
    return _get_validated(zope.component.getUtility(interfaces.IDisplay).input,
                          validator, *args, **kwargs)

[docs]def validated_directory(validator, *args, **kwargs):
    """Like `~certbot.interfaces.IDisplay.directory_select`, but with validation.

    :param callable validator: A method which will be called on the
        supplied input. If the method raises a `errors.Error`, its
        text will be displayed and the user will be re-prompted.
    :param list `*args`: Arguments to be passed to `~certbot.interfaces.IDisplay.directory_select`.
    :param dict `**kwargs`: Arguments to be passed to
        `~certbot.interfaces.IDisplay.directory_select`.
    :return: as `~certbot.interfaces.IDisplay.directory_select`
    :rtype: tuple
    """
    return _get_validated(zope.component.getUtility(interfaces.IDisplay).directory_select,
                          validator, *args, **kwargs)

Source code for certbot.display.util

"""Certbot display."""
import logging
import os
import select
import sys
import textwrap

import zope.interface

from certbot import constants
from certbot import interfaces
from certbot import errors
from certbot.display import completer

logger = logging.getLogger(__name__)

WIDTH = 72

# Display exit codes
OK = "ok"
"""Display exit code indicating user acceptance."""

CANCEL = "cancel"
"""Display exit code for a user canceling the display."""

HELP = "help"
"""Display exit code when for when the user requests more help."""

ESC = "esc"
"""Display exit code when the user hits Escape"""

[docs]def _wrap_lines(msg):
    """Format lines nicely to 80 chars.

    :param str msg: Original message

    :returns: Formatted message respecting newlines in message
    :rtype: str

    """
    lines = msg.splitlines()
    fixed_l = []

    for line in lines:
        fixed_l.append(textwrap.fill(
            line,
            80,
            break_long_words=False,
            break_on_hyphens=False))

    return os.linesep.join(fixed_l)

[docs]def input_with_timeout(prompt=None, timeout=36000.0):
    """Get user input with a timeout.

    Behaves the same as six.moves.input, however, an error is raised if
    a user doesn't answer after timeout seconds. The default timeout
    value was chosen to place it just under 12 hours for users following
    our advice and running Certbot twice a day.

    :param str prompt: prompt to provide for input
    :param float timeout: maximum number of seconds to wait for input

    :returns: user response
    :rtype: str

    :raises errors.Error if no answer is given before the timeout

    """
    # use of sys.stdin and sys.stdout to mimic six.moves.input based on
    # https://github.com/python/cpython/blob/baf7bb30a02aabde260143136bdf5b3738a1d409/Lib/getpass.py#L129
    if prompt:
        sys.stdout.write(prompt)
        sys.stdout.flush()

    # select can only be used like this on UNIX
    rlist, _, _ = select.select([sys.stdin], [], [], timeout)
    if not rlist:
        raise errors.Error(
            "Timed out waiting for answer to prompt '{0}'".format(prompt))

    line = rlist[0].readline()
    if not line:
        raise EOFError
    return line.rstrip('\n')

@zope.interface.implementer(interfaces.IDisplay)

[docs]class FileDisplay(object):
    """File-based display."""
    # pylint: disable=too-many-arguments
    # see https://github.com/certbot/certbot/issues/3915

    def __init__(self, outfile, force_interactive):
        super(FileDisplay, self).__init__()
        self.outfile = outfile
        self.force_interactive = force_interactive
        self.skipped_interaction = False

[docs]    def notification(self, message, pause=True,
                     wrap=True, force_interactive=False):
        """Displays a notification and waits for user acceptance.

        :param str message: Message to display
        :param bool pause: Whether or not the program should pause for the
            user's confirmation
        :param bool wrap: Whether or not the application should wrap text
        :param bool force_interactive: True if it's safe to prompt the user
            because it won't cause any workflow regressions

        """
        side_frame = "-" * 79
        if wrap:
            message = _wrap_lines(message)
        self.outfile.write(
            "{line}{frame}{line}{msg}{line}{frame}{line}".format(
                line=os.linesep, frame=side_frame, msg=message))
        if pause:
            if self._can_interact(force_interactive):
                input_with_timeout("Press Enter to Continue")
            else:
                logger.debug("Not pausing for user confirmation")

[docs]    def menu(self, message, choices, ok_label="", cancel_label="",
             help_label="", default=None,
             cli_flag=None, force_interactive=False, **unused_kwargs):
        # pylint: disable=unused-argument
        """Display a menu.

        .. todo:: This doesn't enable the help label/button (I wasn't sold on
           any interface I came up with for this). It would be a nice feature

        :param str message: title of menu
        :param choices: Menu lines, len must be > 0
        :type choices: list of tuples (tag, item) or
            list of descriptions (tags will be enumerated)
        :param default: default value to return (if one exists)
        :param str cli_flag: option used to set this value with the CLI
        :param bool force_interactive: True if it's safe to prompt the user
            because it won't cause any workflow regressions

        :returns: tuple of (`code`, `index`) where
            `code` - str display exit code
            `index` - int index of the user's selection

        :rtype: tuple

        """
        if self._return_default(message, default, cli_flag, force_interactive):
            return OK, default

        self._print_menu(message, choices)

        code, selection = self._get_valid_int_ans(len(choices))

        return code, selection - 1

[docs]    def input(self, message, default=None,
              cli_flag=None, force_interactive=False, **unused_kwargs):
        """Accept input from the user.

        :param str message: message to display to the user
        :param default: default value to return (if one exists)
        :param str cli_flag: option used to set this value with the CLI
        :param bool force_interactive: True if it's safe to prompt the user
            because it won't cause any workflow regressions

        :returns: tuple of (`code`, `input`) where
            `code` - str display exit code
            `input` - str of the user's input
        :rtype: tuple

        """
        if self._return_default(message, default, cli_flag, force_interactive):
            return OK, default

        # Trailing space must be added outside of _wrap_lines to be preserved
        message = _wrap_lines("%s (Enter 'c' to cancel):" % message) + " "
        ans = input_with_timeout(message)

        if ans == "c" or ans == "C":
            return CANCEL, "-1"
        else:
            return OK, ans

[docs]    def yesno(self, message, yes_label="Yes", no_label="No", default=None,
              cli_flag=None, force_interactive=False, **unused_kwargs):
        """Query the user with a yes/no question.

        Yes and No label must begin with different letters, and must contain at
        least one letter each.

        :param str message: question for the user
        :param str yes_label: Label of the "Yes" parameter
        :param str no_label: Label of the "No" parameter
        :param default: default value to return (if one exists)
        :param str cli_flag: option used to set this value with the CLI
        :param bool force_interactive: True if it's safe to prompt the user
            because it won't cause any workflow regressions

        :returns: True for "Yes", False for "No"
        :rtype: bool

        """
        if self._return_default(message, default, cli_flag, force_interactive):
            return default

        side_frame = ("-" * 79) + os.linesep

        message = _wrap_lines(message)

        self.outfile.write("{0}{frame}{msg}{0}{frame}".format(
            os.linesep, frame=side_frame, msg=message))

        while True:
            ans = input_with_timeout("{yes}/{no}: ".format(
                yes=_parens_around_char(yes_label),
                no=_parens_around_char(no_label)))

            # Couldn't get pylint indentation right with elif
            # elif doesn't matter in this situation
            if (ans.startswith(yes_label[0].lower()) or
                    ans.startswith(yes_label[0].upper())):
                return True
            if (ans.startswith(no_label[0].lower()) or
                    ans.startswith(no_label[0].upper())):
                return False

[docs]    def checklist(self, message, tags, default_status=True, default=None,
                  cli_flag=None, force_interactive=False, **unused_kwargs):
        # pylint: disable=unused-argument
        """Display a checklist.

        :param str message: Message to display to user
        :param list tags: `str` tags to select, len(tags) > 0
        :param bool default_status: Not used for FileDisplay
        :param default: default value to return (if one exists)
        :param str cli_flag: option used to set this value with the CLI
        :param bool force_interactive: True if it's safe to prompt the user
            because it won't cause any workflow regressions

        :returns: tuple of (`code`, `tags`) where
            `code` - str display exit code
            `tags` - list of selected tags
        :rtype: tuple

        """
        if self._return_default(message, default, cli_flag, force_interactive):
            return OK, default

        while True:
            self._print_menu(message, tags)

            code, ans = self.input("Select the appropriate numbers separated "
                                   "by commas and/or spaces, or leave input "
                                   "blank to select all options shown",
                                   force_interactive=True)

            if code == OK:
                if len(ans.strip()) == 0:
                    ans = " ".join(str(x) for x in range(1, len(tags)+1))
                indices = separate_list_input(ans)
                selected_tags = self._scrub_checklist_input(indices, tags)
                if selected_tags:
                    return code, selected_tags
                else:
                    self.outfile.write(
                        "** Error - Invalid selection **%s" % os.linesep)
            else:
                return code, []

[docs]    def _return_default(self, prompt, default, cli_flag, force_interactive):
        """Should we return the default instead of prompting the user?

        :param str prompt: prompt for the user
        :param default: default answer to prompt
        :param str cli_flag: command line option for setting an answer
            to this question
        :param bool force_interactive: if interactivity is forced by the
            IDisplay call

        :returns: True if we should return the default without prompting
        :rtype: bool

        """
        # assert_valid_call(prompt, default, cli_flag, force_interactive)
        if self._can_interact(force_interactive):
            return False
        elif default is None:
            msg = "Unable to get an answer for the question:\n{0}".format(prompt)
            if cli_flag:
                msg += (
                    "\nYou can provide an answer on the "
                    "command line with the {0} flag.".format(cli_flag))
            raise errors.Error(msg)
        else:
            logger.debug(
                "Falling back to default %s for the prompt:\n%s",
                default, prompt)
            return True

[docs]    def _can_interact(self, force_interactive):
        """Can we safely interact with the user?

        :param bool force_interactive: if interactivity is forced by the
            IDisplay call

        :returns: True if the display can interact with the user
        :rtype: bool

        """
        if (self.force_interactive or force_interactive or
                sys.stdin.isatty() and self.outfile.isatty()):
            return True
        elif not self.skipped_interaction:
            logger.warning(
                "Skipped user interaction because Certbot doesn't appear to "
                "be running in a terminal. You should probably include "
                "--non-interactive or %s on the command line.",
                constants.FORCE_INTERACTIVE_FLAG)
            self.skipped_interaction = True
        return False

[docs]    def directory_select(self, message, default=None, cli_flag=None,
                         force_interactive=False, **unused_kwargs):
        """Display a directory selection screen.

        :param str message: prompt to give the user
        :param default: default value to return (if one exists)
        :param str cli_flag: option used to set this value with the CLI
        :param bool force_interactive: True if it's safe to prompt the user
            because it won't cause any workflow regressions

        :returns: tuple of the form (`code`, `string`) where
            `code` - display exit code
            `string` - input entered by the user

        """
        with completer.Completer():
            return self.input(message, default, cli_flag, force_interactive)

[docs]    def _scrub_checklist_input(self, indices, tags):
        # pylint: disable=no-self-use
        """Validate input and transform indices to appropriate tags.

        :param list indices: input
        :param list tags: Original tags of the checklist

        :returns: valid tags the user selected
        :rtype: :class:`list` of :class:`str`

        """
        # They should all be of type int
        try:
            indices = [int(index) for index in indices]
        except ValueError:
            return []

        # Remove duplicates
        indices = list(set(indices))

        # Check all input is within range
        for index in indices:
            if index < 1 or index > len(tags):
                return []
        # Transform indices to appropriate tags
        return [tags[index - 1] for index in indices]

[docs]    def _print_menu(self, message, choices):
        """Print a menu on the screen.

        :param str message: title of menu
        :param choices: Menu lines
        :type choices: list of tuples (tag, item) or
            list of descriptions (tags will be enumerated)

        """
        # Can take either tuples or single items in choices list
        if choices and isinstance(choices[0], tuple):
            choices = ["%s - %s" % (c[0], c[1]) for c in choices]

        # Write out the message to the user
        self.outfile.write(
            "{new}{msg}{new}".format(new=os.linesep, msg=message))
        side_frame = ("-" * 79) + os.linesep
        self.outfile.write(side_frame)

        # Write out the menu choices
        for i, desc in enumerate(choices, 1):
            msg = "{num}: {desc}".format(num=i, desc=desc)
            self.outfile.write(_wrap_lines(msg))

            # Keep this outside of the textwrap
            self.outfile.write(os.linesep)

        self.outfile.write(side_frame)

[docs]    def _get_valid_int_ans(self, max_):
        """Get a numerical selection.

        :param int max: The maximum entry (len of choices), must be positive

        :returns: tuple of the form (`code`, `selection`) where
            `code` - str display exit code ('ok' or cancel')
            `selection` - int user's selection
        :rtype: tuple

        """
        selection = -1
        if max_ > 1:
            input_msg = ("Select the appropriate number "
                         "[1-{max_}] then [enter] (press 'c' to "
                         "cancel): ".format(max_=max_))
        else:
            input_msg = ("Press 1 [enter] to confirm the selection "
                         "(press 'c' to cancel): ")
        while selection < 1:
            ans = input_with_timeout(input_msg)
            if ans.startswith("c") or ans.startswith("C"):
                return CANCEL, -1
            try:
                selection = int(ans)
                if selection < 1 or selection > max_:
                    selection = -1
                    raise ValueError

            except ValueError:
                self.outfile.write(
                    "{0}** Invalid input **{0}".format(os.linesep))

        return OK, selection

[docs]def assert_valid_call(prompt, default, cli_flag, force_interactive):
    """Verify that provided arguments is a valid IDisplay call.

    :param str prompt: prompt for the user
    :param default: default answer to prompt
    :param str cli_flag: command line option for setting an answer
        to this question
    :param bool force_interactive: if interactivity is forced by the
        IDisplay call

    """
    msg = "Invalid IDisplay call for this prompt:\n{0}".format(prompt)
    if cli_flag:
        msg += ("\nYou can set an answer to "
                "this prompt with the {0} flag".format(cli_flag))
    assert default is not None or force_interactive, msg

@zope.interface.implementer(interfaces.IDisplay)

[docs]class NoninteractiveDisplay(object):
    """An iDisplay implementation that never asks for interactive user input"""

    def __init__(self, outfile, *unused_args, **unused_kwargs):
        super(NoninteractiveDisplay, self).__init__()
        self.outfile = outfile

[docs]    def _interaction_fail(self, message, cli_flag, extra=""):
        "Error out in case of an attempt to interact in noninteractive mode"
        msg = "Missing command line flag or config entry for this setting:\n"
        msg += message
        if extra:
            msg += "\n" + extra
        if cli_flag:
            msg += "\n\n(You can set this with the {0} flag)".format(cli_flag)
        raise errors.MissingCommandlineFlag(msg)

[docs]    def notification(self, message, pause=False, wrap=True, **unused_kwargs):
        # pylint: disable=unused-argument
        """Displays a notification without waiting for user acceptance.

        :param str message: Message to display to stdout
        :param bool pause: The NoninteractiveDisplay waits for no keyboard
        :param bool wrap: Whether or not the application should wrap text

        """
        side_frame = "-" * 79
        if wrap:
            message = _wrap_lines(message)
        self.outfile.write(
            "{line}{frame}{line}{msg}{line}{frame}{line}".format(
                line=os.linesep, frame=side_frame, msg=message))

[docs]    def menu(self, message, choices, ok_label=None, cancel_label=None,
             help_label=None, default=None, cli_flag=None, **unused_kwargs):
        # pylint: disable=unused-argument,too-many-arguments
        """Avoid displaying a menu.

        :param str message: title of menu
        :param choices: Menu lines, len must be > 0
        :type choices: list of tuples (tag, item) or
            list of descriptions (tags will be enumerated)
        :param int default: the default choice
        :param dict kwargs: absorbs various irrelevant labelling arguments

        :returns: tuple of (`code`, `index`) where
            `code` - str display exit code
            `index` - int index of the user's selection
        :rtype: tuple
        :raises errors.MissingCommandlineFlag: if there was no default

        """
        if default is None:
            self._interaction_fail(message, cli_flag, "Choices: " + repr(choices))

        return OK, default

[docs]    def input(self, message, default=None, cli_flag=None, **unused_kwargs):
        """Accept input from the user.

        :param str message: message to display to the user

        :returns: tuple of (`code`, `input`) where
            `code` - str display exit code
            `input` - str of the user's input
        :rtype: tuple
        :raises errors.MissingCommandlineFlag: if there was no default

        """
        if default is None:
            self._interaction_fail(message, cli_flag)
        else:
            return OK, default

[docs]    def yesno(self, message, yes_label=None, no_label=None,
              default=None, cli_flag=None, **unused_kwargs):
        # pylint: disable=unused-argument
        """Decide Yes or No, without asking anybody

        :param str message: question for the user
        :param dict kwargs: absorbs yes_label, no_label

        :raises errors.MissingCommandlineFlag: if there was no default
        :returns: True for "Yes", False for "No"
        :rtype: bool

        """
        if default is None:
            self._interaction_fail(message, cli_flag)
        else:
            return default

[docs]    def checklist(self, message, tags, default=None,
                  cli_flag=None, **unused_kwargs):
        """Display a checklist.

        :param str message: Message to display to user
        :param list tags: `str` tags to select, len(tags) > 0
        :param dict kwargs: absorbs default_status arg

        :returns: tuple of (`code`, `tags`) where
            `code` - str display exit code
            `tags` - list of selected tags
        :rtype: tuple

        """
        if default is None:
            self._interaction_fail(message, cli_flag, "? ".join(tags))
        else:
            return OK, default

[docs]    def directory_select(self, message, default=None,
                         cli_flag=None, **unused_kwargs):
        """Simulate prompting the user for a directory.

        This function returns default if it is not ``None``, otherwise,
        an exception is raised explaining the problem. If cli_flag is
        not ``None``, the error message will include the flag that can
        be used to set this value with the CLI.

        :param str message: prompt to give the user
        :param default: default value to return (if one exists)
        :param str cli_flag: option used to set this value with the CLI

        :returns: tuple of the form (`code`, `string`) where
            `code` - int display exit code
            `string` - input entered by the user

        """
        return self.input(message, default, cli_flag)

[docs]def separate_list_input(input_):
    """Separate a comma or space separated list.

    :param str input_: input from the user

    :returns: strings
    :rtype: list

    """
    no_commas = input_.replace(",", " ")
    # Each string is naturally unicode, this causes problems with M2Crypto SANs
    # TODO: check if above is still true when M2Crypto is gone ^
    return [str(string) for string in no_commas.split()]

[docs]def _parens_around_char(label):
    """Place parens around first character of label.

    :param str label: Must contain at least one character

    """
    return "({first}){rest}".format(first=label[0], rest=label[1:])

Source code for certbot.plugins.dns_common

"""Common code for DNS Authenticator Plugins."""

import abc
import logging
import os
import stat
from time import sleep

import configobj
import zope.interface
from acme import challenges

from certbot import errors
from certbot import interfaces
from certbot.display import ops
from certbot.display import util as display_util
from certbot.plugins import common

logger = logging.getLogger(__name__)

@zope.interface.implementer(interfaces.IAuthenticator)
@zope.interface.provider(interfaces.IPluginFactory)

[docs]class DNSAuthenticator(common.Plugin):
    """Base class for DNS  Authenticators"""

    def __init__(self, config, name):
        super(DNSAuthenticator, self).__init__(config, name)

        self._attempt_cleanup = False

    @classmethod
    def add_parser_arguments(cls, add, default_propagation_seconds=10):  # pylint: disable=arguments-differ
        add('propagation-seconds',
            default=default_propagation_seconds,
            type=int,
            help='The number of seconds to wait for DNS to propagate before asking the ACME server '
                 'to verify the DNS record.')

    def get_chall_pref(self, unused_domain): # pylint: disable=missing-docstring,no-self-use
        return [challenges.DNS01]

    def prepare(self): # pylint: disable=missing-docstring
        pass

    def perform(self, achalls): # pylint: disable=missing-docstring
        self._setup_credentials()

        self._attempt_cleanup = True

        responses = []
        for achall in achalls:
            domain = achall.domain
            validation_domain_name = achall.validation_domain_name(domain)
            validation = achall.validation(achall.account_key)

            self._perform(domain, validation_domain_name, validation)
            responses.append(achall.response(achall.account_key))

        # DNS updates take time to propagate and checking to see if the update has occurred is not
        # reliable (the machine this code is running on might be able to see an update before
        # the ACME server). So: we sleep for a short amount of time we believe to be long enough.
        logger.info("Waiting %d seconds for DNS changes to propagate",
                    self.conf('propagation-seconds'))
        sleep(self.conf('propagation-seconds'))

        return responses

    def cleanup(self, achalls):  # pylint: disable=missing-docstring
        if self._attempt_cleanup:
            for achall in achalls:
                domain = achall.domain
                validation_domain_name = achall.validation_domain_name(domain)
                validation = achall.validation(achall.account_key)

                self._cleanup(domain, validation_domain_name, validation)

    @abc.abstractmethod

[docs]    def _setup_credentials(self):  # pragma: no cover
        """
        Establish credentials, prompting if necessary.
        """
        raise NotImplementedError()

    @abc.abstractmethod

[docs]    def _perform(self, domain, validation_domain_name, validation):  # pragma: no cover
        """
        Performs a dns-01 challenge by creating a DNS TXT record.

        :param str domain: The domain being validated.
        :param str validation_domain_name: The validation record domain name.
        :param str validation: The validation record content.
        :raises errors.PluginError: If the challenge cannot be performed
        """
        raise NotImplementedError()

    @abc.abstractmethod

[docs]    def _cleanup(self, domain, validation_domain_name, validation):  # pragma: no cover
        """
        Deletes the DNS TXT record which would have been created by `_perform_achall`.

        Fails gracefully if no such record exists.

        :param str domain: The domain being validated.
        :param str validation_domain_name: The validation record domain name.
        :param str validation: The validation record content.
        """
        raise NotImplementedError()

[docs]    def _configure(self, key, label):
        """
        Ensure that a configuration value is available.

        If necessary, prompts the user and stores the result.

        :param str key: The configuration key.
        :param str label: The user-friendly label for this piece of information.
        """

        configured_value = self.conf(key)
        if not configured_value:
            new_value = self._prompt_for_data(label)

            setattr(self.config, self.dest(key), new_value)

[docs]    def _configure_file(self, key, label, validator=None):
        """
        Ensure that a configuration value is available for a path.

        If necessary, prompts the user and stores the result.

        :param str key: The configuration key.
        :param str label: The user-friendly label for this piece of information.
        """

        configured_value = self.conf(key)
        if not configured_value:
            new_value = self._prompt_for_file(label, validator)

            setattr(self.config, self.dest(key), os.path.abspath(os.path.expanduser(new_value)))

[docs]    def _configure_credentials(self, key, label, required_variables=None):
        """
        As `_configure_file`, but for a credential configuration file.

        If necessary, prompts the user and stores the result.

        Always stores absolute paths to avoid issues during renewal.

        :param str key: The configuration key.
        :param str label: The user-friendly label for this piece of information.
        :param dict required_variables: Map of variable which must be present to error to display.
        """

        def __validator(filename):
            if required_variables:
                CredentialsConfiguration(filename, self.dest).require(required_variables)

        self._configure_file(key, label, __validator)

        credentials_configuration = CredentialsConfiguration(self.conf(key), self.dest)
        if required_variables:
            credentials_configuration.require(required_variables)

        return credentials_configuration

    @staticmethod

[docs]    def _prompt_for_data(label):
        """
        Prompt the user for a piece of information.

        :param str label: The user-friendly label for this piece of information.
        :returns: The user's response (guaranteed non-empty).
        :rtype: str
        """

        def __validator(i):
            if not i:
                raise errors.PluginError('Please enter your {0}.'.format(label))

        code, response = ops.validated_input(
            __validator,
            'Input your {0}'.format(label),
            force_interactive=True)

        if code == display_util.OK:
            return response
        else:
            raise errors.PluginError('{0} required to proceed.'.format(label))

    @staticmethod

[docs]    def _prompt_for_file(label, validator=None):
        """
        Prompt the user for a path.

        :param str label: The user-friendly label for the file.
        :param callable validator: A method which will be called to validate the supplied input
            after it has been validated to be a non-empty path to an existing file. Should throw a
            `~certbot.errors.PluginError` to indicate any issue.
        :returns: The user's response (guaranteed to exist).
        :rtype: str
        """

        def __validator(filename):
            if not filename:
                raise errors.PluginError('Please enter a valid path to your {0}.'.format(label))

            filename = os.path.expanduser(filename)

            validate_file(filename)

            if validator:
                validator(filename)

        code, response = ops.validated_directory(
            __validator,
            'Input the path to your {0}'.format(label),
            force_interactive=True)

        if code == display_util.OK:
            return response
        else:
            raise errors.PluginError('{0} required to proceed.'.format(label))

[docs]class CredentialsConfiguration(object):
    """Represents a user-supplied filed which stores API credentials."""

    def __init__(self, filename, mapper=lambda x: x):
        """
        :param str filename: A path to the configuration file.
        :param callable mapper: A transformation to apply to configuration key names
        :raises errors.PluginError: If the file does not exist or is not a valid format.
        """
        validate_file_permissions(filename)

        try:
            self.confobj = configobj.ConfigObj(filename)
        except configobj.ConfigObjError as e:
            logger.debug("Error parsing credentials configuration: %s", e, exc_info=True)
            raise errors.PluginError("Error parsing credentials configuration: {0}".format(e))

        self.mapper = mapper

[docs]    def require(self, required_variables):
        """Ensures that the supplied set of variables are all present in the file.

        :param dict required_variables: Map of variable which must be present to error to display.
        :raises errors.PluginError: If one or more are missing.
        """
        messages = []

        for var in required_variables:
            if not self._has(var):
                messages.append('Property "{0}" not found (should be {1}).'
                                .format(self.mapper(var), required_variables[var]))
            elif not self._get(var):
                messages.append('Property "{0}" not set (should be {1}).'
                                .format(self.mapper(var), required_variables[var]))

        if messages:
            raise errors.PluginError(
                'Missing {0} in credentials configuration file {1}:\n * {2}'.format(
                        'property' if len(messages) == 1 else 'properties',
                        self.confobj.filename,
                        '\n * '.join(messages)
                    )
            )

[docs]    def conf(self, var):
        """Find a configuration value for variable `var`, as transformed by `mapper`.

        :param str var: The variable to get.
        :returns: The value of the variable.
        :rtype: str
        """

        return self._get(var)

    def _has(self, var):
        return self.mapper(var) in self.confobj

    def _get(self, var):
        return self.confobj.get(self.mapper(var))

[docs]def validate_file(filename):
    """Ensure that the specified file exists."""

    if not os.path.exists(filename):
        raise errors.PluginError('File not found: {0}'.format(filename))

    if not os.path.isfile(filename):
        raise errors.PluginError('Path is not a file: {0}'.format(filename))

[docs]def validate_file_permissions(filename):
    """Ensure that the specified file exists and warn about unsafe permissions."""

    validate_file(filename)

    permissions = stat.S_IMODE(os.stat(filename).st_mode)
    if permissions & stat.S_IRWXO:
        logger.warning('Unsafe permissions on credentials configuration file: %s', filename)

[docs]def base_domain_name_guesses(domain):
    """Return a list of progressively less-specific domain names.

    One of these will probably be the domain name known to the DNS provider.

    :Example:

    >>> base_domain_name_guesses('foo.bar.baz.example.com')
    ['foo.bar.baz.example.com', 'bar.baz.example.com', 'baz.example.com', 'example.com', 'com']

    :param str domain: The domain for which to return guesses.
    :returns: The a list of less specific domain names.
    :rtype: list
    """

    fragments = domain.split('.')
    return ['.'.join(fragments[i:]) for i in range(0, len(fragments))]

Source code for certbot.plugins.common

"""Plugin common functions."""
import logging
import os
import re
import shutil
import tempfile

import OpenSSL
import pkg_resources
import zope.interface

from acme.jose import util as jose_util

from certbot import constants
from certbot import crypto_util
from certbot import interfaces
from certbot import util

logger = logging.getLogger(__name__)

[docs]def option_namespace(name):
    """ArgumentParser options namespace (prefix of all options)."""
    return name + "-"

[docs]def dest_namespace(name):
    """ArgumentParser dest namespace (prefix of all destinations)."""
    return name.replace("-", "_") + "_"

private_ips_regex = re.compile(
    r"(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|"
    r"(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)")
hostname_regex = re.compile(
    r"^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*[a-z]+$", re.IGNORECASE)

@zope.interface.implementer(interfaces.IPlugin)

[docs]class Plugin(object):
    """Generic plugin."""
    # provider is not inherited, subclasses must define it on their own
    # @zope.interface.provider(interfaces.IPluginFactory)

    def __init__(self, config, name):
        self.config = config
        self.name = name

    @jose_util.abstractclassmethod

[docs]    def add_parser_arguments(cls, add):
        """Add plugin arguments to the CLI argument parser.

        NOTE: If some of your flags interact with others, you can
        use cli.report_config_interaction to register this to ensure
        values are correctly saved/overridable during renewal.

        :param callable add: Function that proxies calls to
            `argparse.ArgumentParser.add_argument` prepending options
            with unique plugin name prefix.

        """

    @classmethod

[docs]    def inject_parser_options(cls, parser, name):
        """Inject parser options.

        See `~.IPlugin.inject_parser_options` for docs.

        """
        # dummy function, doesn't check if dest.startswith(self.dest_namespace)
        def add(arg_name_no_prefix, *args, **kwargs):
            # pylint: disable=missing-docstring
            return parser.add_argument(
                "--{0}{1}".format(option_namespace(name), arg_name_no_prefix),
                *args, **kwargs)
        return cls.add_parser_arguments(add)

    @property
    def option_namespace(self):
        """ArgumentParser options namespace (prefix of all options)."""
        return option_namespace(self.name)

[docs]    def option_name(self, name):
        """Option name (include plugin namespace)."""
        return self.option_namespace + name

    @property
    def dest_namespace(self):
        """ArgumentParser dest namespace (prefix of all destinations)."""
        return dest_namespace(self.name)

[docs]    def dest(self, var):
        """Find a destination for given variable ``var``."""
        # this should do exactly the same what ArgumentParser(arg),
        # does to "arg" to compute "dest"
        return self.dest_namespace + var.replace("-", "_")

[docs]    def conf(self, var):
        """Find a configuration value for variable ``var``."""
        return getattr(self.config, self.dest(var))

# other

[docs]class Addr(object):
    r"""Represents an virtual host address.

    :param str addr: addr part of vhost address
    :param str port: port number or \*, or ""

    """
    def __init__(self, tup, ipv6=False):
        self.tup = tup
        self.ipv6 = ipv6

    @classmethod

[docs]    def fromstring(cls, str_addr):
        """Initialize Addr from string."""
        if str_addr.startswith('['):
            # ipv6 addresses starts with [
            endIndex = str_addr.rfind(']')
            host = str_addr[:endIndex + 1]
            port = ''
            if len(str_addr) > endIndex + 2 and str_addr[endIndex + 1] == ':':
                port = str_addr[endIndex + 2:]
            return cls((host, port), ipv6=True)
        else:
            tup = str_addr.partition(':')
            return cls((tup[0], tup[2]))

    def __str__(self):
        if self.tup[1]:
            return "%s:%s" % self.tup
        return self.tup[0]

[docs]    def normalized_tuple(self):
        """Normalized representation of addr/port tuple
        """
        if self.ipv6:
            return (self._normalize_ipv6(self.tup[0]), self.tup[1])
        return self.tup

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            # compare normalized to take different
            # styles of representation into account
            return self.normalized_tuple() == other.normalized_tuple()

        return False

    def __hash__(self):
        return hash(self.tup)

[docs]    def get_addr(self):
        """Return addr part of Addr object."""
        return self.tup[0]

[docs]    def get_port(self):
        """Return port."""
        return self.tup[1]

[docs]    def get_addr_obj(self, port):
        """Return new address object with same addr and new port."""
        return self.__class__((self.tup[0], port), self.ipv6)

[docs]    def _normalize_ipv6(self, addr):
        """Return IPv6 address in normalized form, helper function"""
        addr = addr.lstrip("[")
        addr = addr.rstrip("]")
        return self._explode_ipv6(addr)

[docs]    def get_ipv6_exploded(self):
        """Return IPv6 in normalized form"""
        if self.ipv6:
            return ":".join(self._normalize_ipv6(self.tup[0]))
        return ""

[docs]    def _explode_ipv6(self, addr):
        """Explode IPv6 address for comparison"""
        result = ['0', '0', '0', '0', '0', '0', '0', '0']
        addr_list = addr.split(":")
        if len(addr_list) > len(result):
            # too long, truncate
            addr_list = addr_list[0:len(result)]
        append_to_end = False
        for i in range(0, len(addr_list)):
            block = addr_list[i]
            if len(block) == 0:
                # encountered ::, so rest of the blocks should be
                # appended to the end
                append_to_end = True
                continue
            elif len(block) > 1:
                # remove leading zeros
                block = block.lstrip("0")
            if not append_to_end:
                result[i] = str(block)
            else:
                # count the location from the end using negative indices
                result[i-len(addr_list)] = str(block)
        return result

[docs]class TLSSNI01(object):
    """Abstract base for TLS-SNI-01 challenge performers"""

    def __init__(self, configurator):
        self.configurator = configurator
        self.achalls = []
        self.indices = []
        self.challenge_conf = os.path.join(
            configurator.config.config_dir, "le_tls_sni_01_cert_challenge.conf")
        # self.completed = 0

[docs]    def add_chall(self, achall, idx=None):
        """Add challenge to TLSSNI01 object to perform at once.

        :param .KeyAuthorizationAnnotatedChallenge achall: Annotated
            TLSSNI01 challenge.

        :param int idx: index to challenge in a larger array

        """
        self.achalls.append(achall)
        if idx is not None:
            self.indices.append(idx)

[docs]    def get_cert_path(self, achall):
        """Returns standardized name for challenge certificate.

        :param .KeyAuthorizationAnnotatedChallenge achall: Annotated
            tls-sni-01 challenge.

        :returns: certificate file name
        :rtype: str

        """
        return os.path.join(self.configurator.config.work_dir,
                            achall.chall.encode("token") + ".crt")

[docs]    def get_key_path(self, achall):
        """Get standardized path to challenge key."""
        return os.path.join(self.configurator.config.work_dir,
                            achall.chall.encode("token") + '.pem')

[docs]    def _setup_challenge_cert(self, achall, cert_key=None):

        """Generate and write out challenge certificate."""
        cert_path = self.get_cert_path(achall)
        key_path = self.get_key_path(achall)
        # Register the path before you write out the file
        self.configurator.reverter.register_file_creation(True, key_path)
        self.configurator.reverter.register_file_creation(True, cert_path)

        response, (cert, key) = achall.response_and_validation(
            cert_key=cert_key)
        cert_pem = OpenSSL.crypto.dump_certificate(
            OpenSSL.crypto.FILETYPE_PEM, cert)
        key_pem = OpenSSL.crypto.dump_privatekey(
            OpenSSL.crypto.FILETYPE_PEM, key)

        # Write out challenge cert and key
        with open(cert_path, "wb") as cert_chall_fd:
            cert_chall_fd.write(cert_pem)
        with util.safe_open(key_path, 'wb', chmod=0o400) as key_file:
            key_file.write(key_pem)

        return response

[docs]def install_ssl_options_conf(options_ssl, options_ssl_digest, mod_ssl_conf_src,
    all_ssl_options_hashes):
    """Copy Certbot's SSL options file into the system's config dir if required.

       :param str options_ssl: destination path for file containing ssl options
       :param str options_ssl_digest: path to save a digest of options_ssl in
       :param str mod_ssl_conf_src: path to file containing ssl options found in distribution
       :param list all_ssl_options_hashes: hashes of every released version of options_ssl
    """
    current_ssl_options_hash = crypto_util.sha256sum(mod_ssl_conf_src)

    def _write_current_hash():
        with open(options_ssl_digest, "w") as f:
            f.write(current_ssl_options_hash)

    def _install_current_file():
        shutil.copyfile(mod_ssl_conf_src, options_ssl)
        _write_current_hash()

    # Check to make sure options-ssl.conf is installed
    if not os.path.isfile(options_ssl):
        _install_current_file()
        return
    # there's already a file there. if it's up to date, do nothing. if it's not but
    # it matches a known file hash, we can update it.
    # otherwise, print a warning once per new version.
    active_file_digest = crypto_util.sha256sum(options_ssl)
    if active_file_digest == current_ssl_options_hash: # already up to date
        return
    elif active_file_digest in all_ssl_options_hashes: # safe to update
        _install_current_file()
    else: # has been manually modified, not safe to update
        # did they modify the current version or an old version?
        if os.path.isfile(options_ssl_digest):
            with open(options_ssl_digest, "r") as f:
                saved_digest = f.read()
            # they modified it after we either installed or told them about this version, so return
            if saved_digest == current_ssl_options_hash:
                return
        # there's a new version but we couldn't update the file, or they deleted the digest.
        # save the current digest so we only print this once, and print a warning
        _write_current_hash()
        logger.warning("%s has been manually modified; updated ssl configuration options "
            "saved to %s. We recommend updating %s for security purposes.",
            options_ssl, mod_ssl_conf_src, options_ssl)

# test utils used by certbot_apache/certbot_nginx (hence
# "pragma: no cover") TODO: this might quickly lead to dead code (also
# c.f. #383)

[docs]def dir_setup(test_dir, pkg):  # pragma: no cover
    """Setup the directories necessary for the configurator."""
    def expanded_tempdir(prefix):
        """Return the real path of a temp directory with the specified prefix

        Some plugins rely on real paths of symlinks for working correctly. For
        example, certbot-apache uses real paths of configuration files to tell
        a virtual host from another. On systems where TMP itself is a symbolic
        link, (ex: OS X) such plugins will be confused. This function prevents
        such a case.
        """
        return os.path.realpath(tempfile.mkdtemp(prefix))

    temp_dir = expanded_tempdir("temp")
    config_dir = expanded_tempdir("config")
    work_dir = expanded_tempdir("work")

    os.chmod(temp_dir, constants.CONFIG_DIRS_MODE)
    os.chmod(config_dir, constants.CONFIG_DIRS_MODE)
    os.chmod(work_dir, constants.CONFIG_DIRS_MODE)

    test_configs = pkg_resources.resource_filename(
        pkg, os.path.join("testdata", test_dir))

    shutil.copytree(
        test_configs, os.path.join(temp_dir, test_dir), symlinks=True)

    return temp_dir, config_dir, work_dir

Source code for certbot.plugins.webroot

"""Webroot plugin."""
import argparse
import collections
import errno
import json
import logging
import os

import six
import zope.component
import zope.interface

from acme import challenges

from certbot import cli
from certbot import errors
from certbot import interfaces
from certbot.display import util as display_util
from certbot.display import ops
from certbot.plugins import common

logger = logging.getLogger(__name__)

@zope.interface.implementer(interfaces.IAuthenticator)
@zope.interface.provider(interfaces.IPluginFactory)

[docs]class Authenticator(common.Plugin):
    """Webroot Authenticator."""

    description = "Place files in webroot directory"

    MORE_INFO = """\
Authenticator plugin that performs http-01 challenge by saving
necessary validation resources to appropriate paths on the file
system. It expects that there is some other HTTP server configured
to serve all files under specified web root ({0})."""

    def more_info(self):  # pylint: disable=missing-docstring,no-self-use
        return self.MORE_INFO.format(self.conf("path"))

    @classmethod
    def add_parser_arguments(cls, add):
        add("path", "-w", default=[], action=_WebrootPathAction,
            help="public_html / webroot path. This can be specified multiple "
                 "times to handle different domains; each domain will have "
                 "the webroot path that preceded it.  For instance: `-w "
                 "/var/www/example -d example.com -d www.example.com -w "
                 "/var/www/thing -d thing.net -d m.thing.net` (default: Ask)")
        add("map", default={}, action=_WebrootMapAction,
            help="JSON dictionary mapping domains to webroot paths; this "
                 "implies -d for each entry. You may need to escape this from "
                 "your shell. E.g.: --webroot-map "
                 '\'{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}\' '
                 "This option is merged with, but takes precedence over, -w / "
                 "-d entries. At present, if you put webroot-map in a config "
                 "file, it needs to be on a single line, like: webroot-map = "
                 '{"example.com":"/var/www"}.')

    def get_chall_pref(self, domain):  # pragma: no cover
        # pylint: disable=missing-docstring,no-self-use,unused-argument
        return [challenges.HTTP01]

    def __init__(self, *args, **kwargs):
        super(Authenticator, self).__init__(*args, **kwargs)
        self.full_roots = {}
        self.performed = collections.defaultdict(set)

    def prepare(self):  # pylint: disable=missing-docstring
        pass

    def perform(self, achalls):  # pylint: disable=missing-docstring
        self._set_webroots(achalls)

        self._create_challenge_dirs()

        return [self._perform_single(achall) for achall in achalls]

    def _set_webroots(self, achalls):
        if self.conf("path"):
            webroot_path = self.conf("path")[-1]
            logger.info("Using the webroot path %s for all unmatched domains.",
                        webroot_path)
            for achall in achalls:
                self.conf("map").setdefault(achall.domain, webroot_path)
        else:
            known_webroots = list(set(six.itervalues(self.conf("map"))))
            for achall in achalls:
                if achall.domain not in self.conf("map"):
                    new_webroot = self._prompt_for_webroot(achall.domain,
                                                           known_webroots)
                    # Put the most recently input
                    # webroot first for easy selection
                    try:
                        known_webroots.remove(new_webroot)
                    except ValueError:
                        pass
                    known_webroots.insert(0, new_webroot)
                    self.conf("map")[achall.domain] = new_webroot

    def _prompt_for_webroot(self, domain, known_webroots):
        webroot = None

        while webroot is None:
            webroot = self._prompt_with_webroot_list(domain, known_webroots)

            if webroot is None:
                webroot = self._prompt_for_new_webroot(domain)

        return webroot

    def _prompt_with_webroot_list(self, domain, known_webroots):
        display = zope.component.getUtility(interfaces.IDisplay)
        path_flag = "--" + self.option_name("path")

        while True:
            code, index = display.menu(
                "Select the webroot for {0}:".format(domain),
                ["Enter a new webroot"] + known_webroots,
                help_label="Help", cli_flag=path_flag, force_interactive=True)
            if code == display_util.CANCEL:
                raise errors.PluginError(
                    "Every requested domain must have a "
                    "webroot when using the webroot plugin.")
            elif code == display_util.HELP:
                display.notification(
                    "To use the webroot plugin, you need to have an "
                    "HTTP server running on this system serving files "
                    "for the requested domain. Additionally, this "
                    "server should be serving all files contained in a "
                    "public_html or webroot directory. The webroot "
                    "plugin works by temporarily saving necessary "
                    "resources in the HTTP server's webroot directory "
                    "to pass domain validation challenges.",
                    force_interactive=True)
            else:  # code == display_util.OK
                return None if index == 0 else known_webroots[index - 1]

    def _prompt_for_new_webroot(self, domain):
        code, webroot = ops.validated_directory(
            _validate_webroot,
            "Input the webroot for {0}:".format(domain),
            force_interactive=True)
        if code == display_util.HELP:
            # Displaying help is not currently implemented
            return None
        elif code == display_util.CANCEL or code == display_util.ESC:
            return None
        else:  # code == display_util.OK
            return _validate_webroot(webroot)

    def _create_challenge_dirs(self):
        path_map = self.conf("map")
        if not path_map:
            raise errors.PluginError(
                "Missing parts of webroot configuration; please set either "
                "--webroot-path and --domains, or --webroot-map. Run with "
                " --help webroot for examples.")
        for name, path in path_map.items():
            self.full_roots[name] = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH)

            logger.debug("Creating root challenges validation dir at %s",
                         self.full_roots[name])

            # Change the permissions to be writable (GH #1389)
            # Umask is used instead of chmod to ensure the client can also
            # run as non-root (GH #1795)
            old_umask = os.umask(0o022)

            try:
                # This is coupled with the "umask" call above because
                # os.makedirs's "mode" parameter may not always work:
                # https://stackoverflow.com/questions/5231901/permission-problems-when-creating-a-dir-with-os-makedirs-python
                os.makedirs(self.full_roots[name], 0o0755)

                # Set owner as parent directory if possible
                try:
                    stat_path = os.stat(path)
                    os.chown(self.full_roots[name], stat_path.st_uid,
                             stat_path.st_gid)
                except OSError as exception:
                    logger.info("Unable to change owner and uid of webroot directory")
                    logger.debug("Error was: %s", exception)

            except OSError as exception:
                if exception.errno != errno.EEXIST:
                    raise errors.PluginError(
                        "Couldn't create root for {0} http-01 "
                        "challenge responses: {1}", name, exception)
            finally:
                os.umask(old_umask)

    def _get_validation_path(self, root_path, achall):
        return os.path.join(root_path, achall.chall.encode("token"))

    def _perform_single(self, achall):
        response, validation = achall.response_and_validation()

        root_path = self.full_roots[achall.domain]
        validation_path = self._get_validation_path(root_path, achall)
        logger.debug("Attempting to save validation to %s", validation_path)

        # Change permissions to be world-readable, owner-writable (GH #1795)
        old_umask = os.umask(0o022)

        try:
            with open(validation_path, "wb") as validation_file:
                validation_file.write(validation.encode())
        finally:
            os.umask(old_umask)

        self.performed[root_path].add(achall)

        return response

    def cleanup(self, achalls):  # pylint: disable=missing-docstring
        for achall in achalls:
            root_path = self.full_roots.get(achall.domain, None)
            if root_path is not None:
                validation_path = self._get_validation_path(root_path, achall)
                logger.debug("Removing %s", validation_path)
                os.remove(validation_path)
                self.performed[root_path].remove(achall)

        for root_path, achalls in six.iteritems(self.performed):
            if not achalls:
                try:
                    os.rmdir(root_path)
                    logger.debug("All challenges cleaned up, removing %s",
                                 root_path)
                except OSError as exc:
                    logger.info(
                        "Unable to clean up challenge directory %s", root_path)
                    logger.debug("Error was: %s", exc)

[docs]class _WebrootMapAction(argparse.Action):
    """Action class for parsing webroot_map."""

    def __call__(self, parser, namespace, webroot_map, option_string=None):
        for domains, webroot_path in six.iteritems(json.loads(webroot_map)):
            webroot_path = _validate_webroot(webroot_path)
            namespace.webroot_map.update(
                (d, webroot_path) for d in cli.add_domains(namespace, domains))

[docs]class _WebrootPathAction(argparse.Action):
    """Action class for parsing webroot_path."""

    def __init__(self, *args, **kwargs):
        super(_WebrootPathAction, self).__init__(*args, **kwargs)
        self._domain_before_webroot = False

    def __call__(self, parser, namespace, webroot_path, option_string=None):
        if self._domain_before_webroot:
            raise errors.PluginError(
                "If you specify multiple webroot paths, "
                "one of them must precede all domain flags")

        if namespace.webroot_path:
            # Apply previous webroot to all matched
            # domains before setting the new webroot path
            prev_webroot = namespace.webroot_path[-1]
            for domain in namespace.domains:
                namespace.webroot_map.setdefault(domain, prev_webroot)
        elif namespace.domains:
            self._domain_before_webroot = True

        namespace.webroot_path.append(_validate_webroot(webroot_path))

[docs]def _validate_webroot(webroot_path):
    """Validates and returns the absolute path of webroot_path.

    :param str webroot_path: path to the webroot directory

    :returns: absolute path of webroot_path
    :rtype: str

    """
    if not os.path.isdir(webroot_path):
        raise errors.PluginError(webroot_path + " does not exist or is not a directory")

    return os.path.abspath(webroot_path)

Source code for certbot.plugins.standalone

"""Standalone Authenticator."""
import argparse
import collections
import logging
import socket

import OpenSSL
import six
import zope.interface

from acme import challenges
from acme import standalone as acme_standalone

from certbot import errors
from certbot import interfaces

from certbot.plugins import common

logger = logging.getLogger(__name__)

[docs]class ServerManager(object):
    """Standalone servers manager.

    Manager for `ACMEServer` and `ACMETLSServer` instances.

    `certs` and `http_01_resources` correspond to
    `acme.crypto_util.SSLSocket.certs` and
    `acme.crypto_util.SSLSocket.http_01_resources` respectively. All
    created servers share the same certificates and resources, so if
    you're running both TLS and non-TLS instances, HTTP01 handlers
    will serve the same URLs!

    """
    def __init__(self, certs, http_01_resources):
        self._instances = {}
        self.certs = certs
        self.http_01_resources = http_01_resources

[docs]    def run(self, port, challenge_type, listenaddr=""):
        """Run ACME server on specified ``port``.

        This method is idempotent, i.e. all calls with the same pair of
        ``(port, challenge_type)`` will reuse the same server.

        :param int port: Port to run the server on.
        :param challenge_type: Subclass of `acme.challenges.Challenge`,
            either `acme.challenge.HTTP01` or `acme.challenges.TLSSNI01`.
        :param str listenaddr: (optional) The address to listen on. Defaults to all addrs.

        :returns: DualNetworkedServers instance.
        :rtype: ACMEServerMixin

        """
        assert challenge_type in (challenges.TLSSNI01, challenges.HTTP01)
        if port in self._instances:
            return self._instances[port]

        address = (listenaddr, port)
        try:
            if challenge_type is challenges.TLSSNI01:
                servers = acme_standalone.TLSSNI01DualNetworkedServers(address, self.certs)
            else:  # challenges.HTTP01
                servers = acme_standalone.HTTP01DualNetworkedServers(
                    address, self.http_01_resources)
        except socket.error as error:
            raise errors.StandaloneBindError(error, port)

        servers.serve_forever()

        # if port == 0, then random free port on OS is taken
        # pylint: disable=no-member
        # both servers, if they exist, have the same port
        real_port = servers.getsocknames()[0][1]
        self._instances[real_port] = servers
        return servers

[docs]    def stop(self, port):
        """Stop ACME server running on the specified ``port``.

        :param int port:

        """
        instance = self._instances[port]
        for sockname in instance.getsocknames():
            logger.debug("Stopping server at %s:%d...",
                         *sockname[:2])
        # Not calling server_close causes problems when renewing multiple
        # certs with `certbot renew` using TLSSNI01 and PyOpenSSL 0.13
        instance.shutdown_and_server_close()
        del self._instances[port]

[docs]    def running(self):
        """Return all running instances.

        Once the server is stopped using `stop`, it will not be
        returned.

        :returns: Mapping from ``port`` to ``servers``.
        :rtype: tuple

        """
        return self._instances.copy()

SUPPORTED_CHALLENGES = [challenges.TLSSNI01, challenges.HTTP01]

[docs]class SupportedChallengesAction(argparse.Action):
    """Action class for parsing standalone_supported_challenges."""

    def __call__(self, parser, namespace, values, option_string=None):
        logger.warning(
            "The standalone specific supported challenges flag is "
            "deprecated. Please use the --preferred-challenges flag "
            "instead.")
        converted_values = self._convert_and_validate(values)
        namespace.standalone_supported_challenges = converted_values

[docs]    def _convert_and_validate(self, data):
        """Validate the value of supported challenges provided by the user.

        References to "dvsni" are automatically converted to "tls-sni-01".

        :param str data: comma delimited list of challenge types

        :returns: validated and converted list of challenge types
        :rtype: str

        """
        challs = data.split(",")

        # tls-sni-01 was dvsni during private beta
        if "dvsni" in challs:
            logger.info(
                "Updating legacy standalone_supported_challenges value")
            challs = [challenges.TLSSNI01.typ if chall == "dvsni" else chall
                      for chall in challs]
            data = ",".join(challs)

        unrecognized = [name for name in challs
                        if name not in challenges.Challenge.TYPES]

        # argparse.ArgumentErrors raised out of argparse.Action objects
        # are caught by argparse which prints usage information and the
        # error that occurred before calling sys.exit.
        if unrecognized:
            raise argparse.ArgumentError(
                self,
                "Unrecognized challenges: {0}".format(", ".join(unrecognized)))

        choices = set(chall.typ for chall in SUPPORTED_CHALLENGES)
        if not set(challs).issubset(choices):
            raise argparse.ArgumentError(
                self,
                "Plugin does not support the following (valid) "
                "challenges: {0}".format(", ".join(set(challs) - choices)))

        return data

@zope.interface.implementer(interfaces.IAuthenticator)
@zope.interface.provider(interfaces.IPluginFactory)

[docs]class Authenticator(common.Plugin):
    """Standalone Authenticator.

    This authenticator creates its own ephemeral TCP listener on the
    necessary port in order to respond to incoming tls-sni-01 and http-01
    challenges from the certificate authority. Therefore, it does not
    rely on any existing server program.
    """

    description = "Spin up a temporary webserver"

    def __init__(self, *args, **kwargs):
        super(Authenticator, self).__init__(*args, **kwargs)

        # one self-signed key for all tls-sni-01 certificates
        self.key = OpenSSL.crypto.PKey()
        self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)

        self.served = collections.defaultdict(set)

        # Stuff below is shared across threads (i.e. servers read
        # values, main thread writes). Due to the nature of CPython's
        # GIL, the operations are safe, c.f.
        # https://docs.python.org/2/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe
        self.certs = {}
        self.http_01_resources = set()

        self.servers = ServerManager(self.certs, self.http_01_resources)

    @classmethod
    def add_parser_arguments(cls, add):
        add("supported-challenges",
            help=argparse.SUPPRESS,
            action=SupportedChallengesAction,
            default=",".join(chall.typ for chall in SUPPORTED_CHALLENGES))

    @property
    def supported_challenges(self):
        """Challenges supported by this plugin."""
        return [challenges.Challenge.TYPES[name] for name in
                self.conf("supported-challenges").split(",")]

    def more_info(self):  # pylint: disable=missing-docstring
        return("This authenticator creates its own ephemeral TCP listener "
               "on the necessary port in order to respond to incoming "
               "tls-sni-01 and http-01 challenges from the certificate "
               "authority. Therefore, it does not rely on any existing "
               "server program.")

    def prepare(self):  # pylint: disable=missing-docstring
        pass

    def get_chall_pref(self, domain):
        # pylint: disable=unused-argument,missing-docstring
        return self.supported_challenges

    def perform(self, achalls):  # pylint: disable=missing-docstring
        return [self._try_perform_single(achall) for achall in achalls]

    def _try_perform_single(self, achall):
        while True:
            try:
                return self._perform_single(achall)
            except errors.StandaloneBindError as error:
                _handle_perform_error(error)

    def _perform_single(self, achall):
        if isinstance(achall.chall, challenges.HTTP01):
            servers, response = self._perform_http_01(achall)
        else:  # tls-sni-01
            servers, response = self._perform_tls_sni_01(achall)
        self.served[servers].add(achall)
        return response

    def _perform_http_01(self, achall):
        port = self.config.http01_port
        addr = self.config.http01_address
        servers = self.servers.run(port, challenges.HTTP01, listenaddr=addr)
        response, validation = achall.response_and_validation()
        resource = acme_standalone.HTTP01RequestHandler.HTTP01Resource(
            chall=achall.chall, response=response, validation=validation)
        self.http_01_resources.add(resource)
        return servers, response

    def _perform_tls_sni_01(self, achall):
        port = self.config.tls_sni_01_port
        addr = self.config.tls_sni_01_address
        servers = self.servers.run(port, challenges.TLSSNI01, listenaddr=addr)
        response, (cert, _) = achall.response_and_validation(cert_key=self.key)
        self.certs[response.z_domain] = (self.key, cert)
        return servers, response

    def cleanup(self, achalls):  # pylint: disable=missing-docstring
        # reduce self.served and close servers if no challenges are served
        for unused_servers, server_achalls in self.served.items():
            for achall in achalls:
                if achall in server_achalls:
                    server_achalls.remove(achall)
        for port, servers in six.iteritems(self.servers.running()):
            if not self.served[servers]:
                self.servers.stop(port)

def _handle_perform_error(error):
    if error.socket_error.errno == socket.errno.EACCES:
        raise errors.PluginError(
            "Could not bind TCP port {0} because you don't have "
            "the appropriate permissions (for example, you "
            "aren't running this program as "
            "root).".format(error.port))
    elif error.socket_error.errno == socket.errno.EADDRINUSE:
        display = zope.component.getUtility(interfaces.IDisplay)
        msg = (
            "Could not bind TCP port {0} because it is already in "
            "use by another process on this system (such as a web "
            "server). Please stop the program in question and "
            "then try again.".format(error.port))
        should_retry = display.yesno(msg, "Retry",
                                     "Cancel", default=False)
        if not should_retry:
            raise errors.PluginError(msg)
    else:
        raise

Source code for certbot.plugins.util

"""Plugin utilities."""
import logging
import os

from certbot import util

logger = logging.getLogger(__name__)

[docs]def path_surgery(cmd):
    """Attempt to perform PATH surgery to find cmd

    Mitigates https://github.com/certbot/certbot/issues/1833

    :param str cmd: the command that is being searched for in the PATH

    :returns: True if the operation succeeded, False otherwise
    """
    dirs = ("/usr/sbin", "/usr/local/bin", "/usr/local/sbin")
    path = os.environ["PATH"]
    added = []
    for d in dirs:
        if d not in path:
            path += os.pathsep + d
            added.append(d)

    if any(added):
        logger.debug("Can't find %s, attempting PATH mitigation by adding %s",
                     cmd, os.pathsep.join(added))
        os.environ["PATH"] = path

    if util.exe_exists(cmd):
        return True
    else:
        expanded = " expanded" if any(added) else ""
        logger.warning("Failed to find executable %s in%s PATH: %s", cmd,
                       expanded, path)
        return False

Source code for certbot.plugins.dns_common_lexicon

"""Common code for DNS Authenticator Plugins built on Lexicon."""

import logging

from requests.exceptions import HTTPError, RequestException

from certbot import errors
from certbot.plugins import dns_common

logger = logging.getLogger(__name__)

[docs]class LexiconClient(object):
    """
    Encapsulates all communication with a DNS provider via Lexicon.
    """

    def __init__(self):
        self.provider = None

[docs]    def add_txt_record(self, domain, record_name, record_content):
        """
        Add a TXT record using the supplied information.

        :param str domain: The domain to use to look up the managed zone.
        :param str record_name: The record name (typically beginning with '_acme-challenge.').
        :param str record_content: The record content (typically the challenge validation).
        :raises errors.PluginError: if an error occurs communicating with the DNS Provider API
        """
        self._find_domain_id(domain)

        try:
            self.provider.create_record(type='TXT', name=record_name, content=record_content)
        except RequestException as e:
            logger.debug('Encountered error adding TXT record: %s', e, exc_info=True)
            raise errors.PluginError('Error adding TXT record: {0}'.format(e))

[docs]    def del_txt_record(self, domain, record_name, record_content):
        """
        Delete a TXT record using the supplied information.

        :param str domain: The domain to use to look up the managed zone.
        :param str record_name: The record name (typically beginning with '_acme-challenge.').
        :param str record_content: The record content (typically the challenge validation).
        :raises errors.PluginError: if an error occurs communicating with the DNS Provider  API
        """
        try:
            self._find_domain_id(domain)
        except errors.PluginError as e:
            logger.debug('Encountered error finding domain_id during deletion: %s', e,
                         exc_info=True)
            return

        try:
            self.provider.delete_record(type='TXT', name=record_name, content=record_content)
        except RequestException as e:
            logger.debug('Encountered error deleting TXT record: %s', e, exc_info=True)

[docs]    def _find_domain_id(self, domain):
        """
        Find the domain_id for a given domain.

        :param str domain: The domain for which to find the domain_id.
        :raises errors.PluginError: if the domain_id cannot be found.
        """

        domain_name_guesses = dns_common.base_domain_name_guesses(domain)

        for domain_name in domain_name_guesses:
            try:
                self.provider.options['domain'] = domain_name

                self.provider.authenticate()

                return  # If `authenticate` doesn't throw an exception, we've found the right name
            except HTTPError as e:
                result = self._handle_http_error(e, domain_name)

                if result:
                    raise result
            except Exception as e:  # pylint: disable=broad-except
                result = self._handle_general_error(e, domain_name)

                if result:
                    raise result

        raise errors.PluginError('Unable to determine zone identifier for {0} using zone names: {1}'
                                 .format(domain, domain_name_guesses))

    def _handle_http_error(self, e, domain_name):
        return errors.PluginError('Error determining zone identifier for {0}: {1}.'
                                  .format(domain_name, e))

    def _handle_general_error(self, e, domain_name):
        if not str(e).startswith('No domain found'):
            return errors.PluginError('Unexpected error determining zone identifier for {0}: {1}'
                                      .format(domain_name, e))

Source code for certbot.plugins.manual

"""Manual authenticator plugin"""
import os

import zope.component
import zope.interface

from acme import challenges

from certbot import interfaces
from certbot import errors
from certbot import hooks
from certbot.plugins import common

@zope.interface.implementer(interfaces.IAuthenticator)
@zope.interface.provider(interfaces.IPluginFactory)

[docs]class Authenticator(common.Plugin):
    """Manual authenticator

    This plugin allows the user to perform the domain validation
    challenge(s) themselves. This either be done manually by the user or
    through shell scripts provided to Certbot.

    """

    description = 'Manual configuration or run your own shell scripts'
    hidden = True
    long_description = (
        'Authenticate through manual configuration or custom shell scripts. '
        'When using shell scripts, an authenticator script must be provided. '
        'The environment variables available to this script are '
        '$CERTBOT_DOMAIN which contains the domain being authenticated, '
        '$CERTBOT_VALIDATION which is the validation string, and '
        '$CERTBOT_TOKEN which 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.')
    _DNS_INSTRUCTIONS = """\
Please deploy a DNS TXT record under the name
{domain} with the following value:

{validation}

Before continuing, verify the record is deployed."""
    _HTTP_INSTRUCTIONS = """\
Create a file containing just this data:

{validation}

And make it available on your web server at this URL:

{uri}
"""

    def __init__(self, *args, **kwargs):
        super(Authenticator, self).__init__(*args, **kwargs)
        self.env = dict()

    @classmethod
    def add_parser_arguments(cls, add):
        add('auth-hook',
            help='Path or command to execute for the authentication script')
        add('cleanup-hook',
            help='Path or command to execute for the cleanup script')
        add('public-ip-logging-ok', action='store_true',
            help='Automatically allows public IP logging (default: Ask)')

    def prepare(self):  # pylint: disable=missing-docstring
        if self.config.noninteractive_mode and not self.conf('auth-hook'):
            raise errors.PluginError(
                'An authentication script must be provided with --{0} when '
                'using the manual plugin non-interactively.'.format(
                    self.option_name('auth-hook')))
        self._validate_hooks()

    def _validate_hooks(self):
        if self.config.validate_hooks:
            for name in ('auth-hook', 'cleanup-hook'):
                hook = self.conf(name)
                if hook is not None:
                    hook_prefix = self.option_name(name)[:-len('-hook')]
                    hooks.validate_hook(hook, hook_prefix)

    def more_info(self):  # pylint: disable=missing-docstring,no-self-use
        return (
            'This plugin allows the user to customize setup for domain '
            'validation challenges either through shell scripts provided by '
            'the user or by performing the setup manually.')

    def get_chall_pref(self, domain):
        # pylint: disable=missing-docstring,no-self-use,unused-argument
        return [challenges.HTTP01, challenges.DNS01]

    def perform(self, achalls):  # pylint: disable=missing-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)
            responses.append(achall.response(achall.account_key))
        return responses

    def _verify_ip_logging_ok(self):
        if not self.conf('public-ip-logging-ok'):
            cli_flag = '--{0}'.format(self.option_name('public-ip-logging-ok'))
            msg = ('NOTE: The IP of this machine will be publicly logged as '
                   "having requested this certificate. If you're running "
                   'certbot in manual mode on a machine that is not your '
                   "server, please ensure you're okay with that.\n\n"
                   'Are you OK with your IP being logged?')
            display = zope.component.getUtility(interfaces.IDisplay)
            if display.yesno(msg, cli_flag=cli_flag, force_interactive=True):
                setattr(self.config, self.dest('public-ip-logging-ok'), True)
            else:
                raise errors.PluginError('Must agree to IP logging to proceed')

    def _perform_achall_with_script(self, achall):
        env = dict(CERTBOT_DOMAIN=achall.domain,
                   CERTBOT_VALIDATION=achall.validation(achall.account_key))
        if isinstance(achall.chall, challenges.HTTP01):
            env['CERTBOT_TOKEN'] = achall.chall.encode('token')
        else:
            os.environ.pop('CERTBOT_TOKEN', None)
        os.environ.update(env)
        _, out = hooks.execute(self.conf('auth-hook'))
        env['CERTBOT_AUTH_OUTPUT'] = out.strip()
        self.env[achall.domain] = env

    def _perform_achall_manually(self, achall):
        validation = achall.validation(achall.account_key)
        if isinstance(achall.chall, challenges.HTTP01):
            msg = self._HTTP_INSTRUCTIONS.format(
                achall=achall, encoded_token=achall.chall.encode('token'),
                port=self.config.http01_port,
                uri=achall.chall.uri(achall.domain), validation=validation)
        else:
            assert isinstance(achall.chall, challenges.DNS01)
            msg = self._DNS_INSTRUCTIONS.format(
                domain=achall.validation_domain_name(achall.domain),
                validation=validation)
        display = zope.component.getUtility(interfaces.IDisplay)
        display.notification(msg, wrap=False, force_interactive=True)

    def cleanup(self, achalls):  # pylint: disable=missing-docstring
        if self.conf('cleanup-hook'):
            for achall in achalls:
                env = self.env.pop(achall.domain)
                if 'CERTBOT_TOKEN' not in env:
                    os.environ.pop('CERTBOT_TOKEN', None)
                os.environ.update(env)
                hooks.execute(self.conf('cleanup-hook'))

Source code for certbot.plugins.disco

"""Utilities for plugins discovery and selection."""
import collections
import itertools
import logging
import pkg_resources
import six

import zope.interface
import zope.interface.verify

from certbot import constants
from certbot import errors
from certbot import interfaces

try:
    from collections import OrderedDict
except ImportError:  # pragma: no cover
    # OrderedDict was added in Python 2.7
    from ordereddict import OrderedDict  # pylint: disable=import-error

logger = logging.getLogger(__name__)

[docs]class PluginEntryPoint(object):
    """Plugin entry point."""

    PREFIX_FREE_DISTRIBUTIONS = [
        "certbot",
        "certbot-apache",
        "certbot-dns-cloudflare",
        "certbot-dns-cloudxns",
        "certbot-dns-digitalocean",
        "certbot-dns-dnsimple",
        "certbot-dns-google",
        "certbot-dns-nsone",
        "certbot-dns-route53",
        "certbot-nginx",
    ]
    """Distributions for which prefix will be omitted."""

    # this object is mutable, don't allow it to be hashed!
    __hash__ = None  # type: ignore

    def __init__(self, entry_point):
        self.name = self.entry_point_to_plugin_name(entry_point)
        self.plugin_cls = entry_point.load()
        self.entry_point = entry_point
        self._initialized = None
        self._prepared = None

    @classmethod

[docs]    def entry_point_to_plugin_name(cls, entry_point):
        """Unique plugin name for an ``entry_point``"""
        if entry_point.dist.key in cls.PREFIX_FREE_DISTRIBUTIONS:
            return entry_point.name
        return entry_point.dist.key + ":" + entry_point.name

    @property
    def description(self):
        """Description of the plugin."""
        return self.plugin_cls.description

    @property
    def description_with_name(self):
        """Description with name. Handy for UI."""
        return "{0} ({1})".format(self.description, self.name)

    @property
    def long_description(self):
        """Long description of the plugin."""
        try:
            return self.plugin_cls.long_description
        except AttributeError:
            return self.description

    @property
    def hidden(self):
        """Should this plugin be hidden from UI?"""
        return getattr(self.plugin_cls, "hidden", False)

[docs]    def ifaces(self, *ifaces_groups):
        """Does plugin implements specified interface groups?"""
        return not ifaces_groups or any(
            all(iface.implementedBy(self.plugin_cls)
                for iface in ifaces)
            for ifaces in ifaces_groups)

    @property
    def initialized(self):
        """Has the plugin been initialized already?"""
        return self._initialized is not None

[docs]    def init(self, config=None):
        """Memoized plugin initialization."""
        if not self.initialized:
            self.entry_point.require()  # fetch extras!
            self._initialized = self.plugin_cls(config, self.name)
        return self._initialized

[docs]    def verify(self, ifaces):
        """Verify that the plugin conforms to the specified interfaces."""
        assert self.initialized
        for iface in ifaces:  # zope.interface.providedBy(plugin)
            try:
                zope.interface.verify.verifyObject(iface, self.init())
            except zope.interface.exceptions.BrokenImplementation as error:
                if iface.implementedBy(self.plugin_cls):
                    logger.debug(
                        "%s implements %s but object does not verify: %s",
                        self.plugin_cls, iface.__name__, error, exc_info=True)
                return False
        return True

    @property
    def prepared(self):
        """Has the plugin been prepared already?"""
        if not self.initialized:
            logger.debug(".prepared called on uninitialized %r", self)
        return self._prepared is not None

[docs]    def prepare(self):
        """Memoized plugin preparation."""
        assert self.initialized
        if self._prepared is None:
            try:
                self._initialized.prepare()
            except errors.MisconfigurationError as error:
                logger.debug("Misconfigured %r: %s", self, error, exc_info=True)
                self._prepared = error
            except errors.NoInstallationError as error:
                logger.debug(
                    "No installation (%r): %s", self, error, exc_info=True)
                self._prepared = error
            except errors.PluginError as error:
                logger.debug("Other error:(%r): %s", self, error, exc_info=True)
                self._prepared = error
            else:
                self._prepared = True
        return self._prepared

    @property
    def misconfigured(self):
        """Is plugin misconfigured?"""
        return isinstance(self._prepared, errors.MisconfigurationError)

    @property
    def problem(self):
        """Return the Exception raised during plugin setup, or None if all is well"""
        if isinstance(self._prepared, Exception):
            return self._prepared
        return None

    @property
    def available(self):
        """Is plugin available, i.e. prepared or misconfigured?"""
        return self._prepared is True or self.misconfigured

    def __repr__(self):
        return "PluginEntryPoint#{0}".format(self.name)

    def __str__(self):
        lines = [
            "* {0}".format(self.name),
            "Description: {0}".format(self.plugin_cls.description),
            "Interfaces: {0}".format(", ".join(
                iface.__name__ for iface in zope.interface.implementedBy(
                    self.plugin_cls))),
            "Entry point: {0}".format(self.entry_point),
        ]

        if self.initialized:
            lines.append("Initialized: {0}".format(self.init()))
            if self.prepared:
                lines.append("Prep: {0}".format(self.prepare()))

        return "\n".join(lines)

[docs]class PluginsRegistry(collections.Mapping):
    """Plugins registry."""

    def __init__(self, plugins):
        # plugins are sorted so the same order is used between runs.
        # This prevents deadlock caused by plugins acquiring a lock
        # and ensures at least one concurrent Certbot instance will run
        # successfully.
        self._plugins = OrderedDict(sorted(six.iteritems(plugins)))

    @classmethod

[docs]    def find_all(cls):
        """Find plugins using setuptools entry points."""
        plugins = {}
        entry_points = itertools.chain(
            pkg_resources.iter_entry_points(
                constants.SETUPTOOLS_PLUGINS_ENTRY_POINT),
            pkg_resources.iter_entry_points(
                constants.OLD_SETUPTOOLS_PLUGINS_ENTRY_POINT),)
        for entry_point in entry_points:
            plugin_ep = PluginEntryPoint(entry_point)
            assert plugin_ep.name not in plugins, (
                "PREFIX_FREE_DISTRIBUTIONS messed up")
            # providedBy | pylint: disable=no-member
            if interfaces.IPluginFactory.providedBy(plugin_ep.plugin_cls):
                plugins[plugin_ep.name] = plugin_ep
            else:  # pragma: no cover
                logger.warning(
                    "%r does not provide IPluginFactory, skipping", plugin_ep)
        return cls(plugins)

    def __getitem__(self, name):
        return self._plugins[name]

    def __iter__(self):
        return iter(self._plugins)

    def __len__(self):
        return len(self._plugins)

[docs]    def init(self, config):
        """Initialize all plugins in the registry."""
        return [plugin_ep.init(config) for plugin_ep
                in six.itervalues(self._plugins)]

[docs]    def filter(self, pred):
        """Filter plugins based on predicate."""
        return type(self)(dict((name, plugin_ep) for name, plugin_ep
                               in six.iteritems(self._plugins) if pred(plugin_ep)))

[docs]    def visible(self):
        """Filter plugins based on visibility."""
        return self.filter(lambda plugin_ep: not plugin_ep.hidden)

[docs]    def ifaces(self, *ifaces_groups):
        """Filter plugins based on interfaces."""
        # pylint: disable=star-args
        return self.filter(lambda p_ep: p_ep.ifaces(*ifaces_groups))

[docs]    def verify(self, ifaces):
        """Filter plugins based on verification."""
        return self.filter(lambda p_ep: p_ep.verify(ifaces))

[docs]    def prepare(self):
        """Prepare all plugins in the registry."""
        return [plugin_ep.prepare() for plugin_ep in six.itervalues(self._plugins)]

[docs]    def available(self):
        """Filter plugins based on availability."""
        return self.filter(lambda p_ep: p_ep.available)

        # successfully prepared + misconfigured

[docs]    def find_init(self, plugin):
        """Find an initialized plugin.

        This is particularly useful for finding a name for the plugin
        (although `.IPluginFactory.__call__` takes ``name`` as one of
        the arguments, ``IPlugin.name`` is not part of the interface)::

          # plugin is an instance providing IPlugin, initialized
          # somewhere else in the code
          plugin_registry.find_init(plugin).name

        Returns ``None`` if ``plugin`` is not found in the registry.

        """
        # use list instead of set because PluginEntryPoint is not hashable
        candidates = [plugin_ep for plugin_ep in six.itervalues(self._plugins)
                      if plugin_ep.initialized and plugin_ep.init() is plugin]
        assert len(candidates) <= 1
        if candidates:
            return candidates[0]
        else:
            return None

    def __repr__(self):
        return "{0}({1})".format(
            self.__class__.__name__, ','.join(
                repr(p_ep) for p_ep in six.itervalues(self._plugins)))

    def __str__(self):
        if not self._plugins:
            return "No plugins"
        return "\n\n".join(str(p_ep) for p_ep in six.itervalues(self._plugins))

Source code for acme.challenges

"""ACME Identifier Validation Challenges."""
import abc
import functools
import hashlib
import logging
import socket

from cryptography.hazmat.primitives import hashes  # type: ignore
import OpenSSL
import requests

from acme import errors
from acme import crypto_util
from acme import fields
from acme import jose

logger = logging.getLogger(__name__)

# pylint: disable=too-few-public-methods

class Challenge(jose.TypedJSONObjectWithFields):
    # _fields_to_partial_json | pylint: disable=abstract-method
    """ACME challenge."""
    TYPES = {}  # type: dict

    @classmethod
    def from_json(cls, jobj):
        try:
            return super(Challenge, cls).from_json(jobj)
        except jose.UnrecognizedTypeError as error:
            logger.debug(error)
            return UnrecognizedChallenge.from_json(jobj)

class ChallengeResponse(jose.TypedJSONObjectWithFields):
    # _fields_to_partial_json | pylint: disable=abstract-method
    """ACME challenge response."""
    TYPES = {}  # type: dict
    resource_type = 'challenge'
    resource = fields.Resource(resource_type)

class UnrecognizedChallenge(Challenge):
    """Unrecognized challenge.

    ACME specification defines a generic framework for challenges and
    defines some standard challenges that are implemented in this
    module. However, other implementations (including peers) might
    define additional challenge types, which should be ignored if
    unrecognized.

    :ivar jobj: Original JSON decoded object.

    """

    def __init__(self, jobj):
        super(UnrecognizedChallenge, self).__init__()
        object.__setattr__(self, "jobj", jobj)

    def to_partial_json(self):
        # pylint: disable=no-member
        return self.jobj

    @classmethod
    def from_json(cls, jobj):
        return cls(jobj)

class _TokenChallenge(Challenge):
    """Challenge with token.

    :ivar bytes token:

    """
    TOKEN_SIZE = 128 / 8  # Based on the entropy value from the spec
    """Minimum size of the :attr:`token` in bytes."""

    # TODO: acme-spec doesn't specify token as base64-encoded value
    token = jose.Field(
        "token", encoder=jose.encode_b64jose, decoder=functools.partial(
            jose.decode_b64jose, size=TOKEN_SIZE, minimum=True))

    # XXX: rename to ~token_good_for_url
    @property
    def good_token(self):  # XXX: @token.decoder
        """Is `token` good?

        .. todo:: acme-spec wants "It MUST NOT contain any non-ASCII
           characters", but it should also warrant that it doesn't
           contain ".." or "/"...

        """
        # TODO: check that path combined with uri does not go above
        # URI_ROOT_PATH!
        return b'..' not in self.token and b'/' not in self.token

class KeyAuthorizationChallengeResponse(ChallengeResponse):
    """Response to Challenges based on Key Authorization.

    :param unicode key_authorization:

    """
    key_authorization = jose.Field("keyAuthorization")
    thumbprint_hash_function = hashes.SHA256

    def verify(self, chall, account_public_key):
        """Verify the key authorization.

        :param KeyAuthorization chall: Challenge that corresponds to
            this response.
        :param JWK account_public_key:

        :return: ``True`` iff verification of the key authorization was
            successful.
        :rtype: bool

        """
        parts = self.key_authorization.split('.')  # pylint: disable=no-member
        if len(parts) != 2:
            logger.debug("Key authorization (%r) is not well formed",
                         self.key_authorization)
            return False

        if parts[0] != chall.encode("token"):
            logger.debug("Mismatching token in key authorization: "
                         "%r instead of %r", parts[0], chall.encode("token"))
            return False

        thumbprint = jose.b64encode(account_public_key.thumbprint(
            hash_function=self.thumbprint_hash_function)).decode()
        if parts[1] != thumbprint:
            logger.debug("Mismatching thumbprint in key authorization: "
                         "%r instead of %r", parts[0], thumbprint)
            return False

        return True

class KeyAuthorizationChallenge(_TokenChallenge):
    # pylint: disable=abstract-class-little-used,too-many-ancestors
    """Challenge based on Key Authorization.

    :param response_cls: Subclass of `KeyAuthorizationChallengeResponse`
        that will be used to generate `response`.

    """
    __metaclass__ = abc.ABCMeta

    response_cls = NotImplemented
    thumbprint_hash_function = (
        KeyAuthorizationChallengeResponse.thumbprint_hash_function)

    def key_authorization(self, account_key):
        """Generate Key Authorization.

        :param JWK account_key:
        :rtype unicode:

        """
        return self.encode("token") + "." + jose.b64encode(
            account_key.thumbprint(
                hash_function=self.thumbprint_hash_function)).decode()

    def response(self, account_key):
        """Generate response to the challenge.

        :param JWK account_key:

        :returns: Response (initialized `response_cls`) to the challenge.
        :rtype: KeyAuthorizationChallengeResponse

        """
        return self.response_cls(
            key_authorization=self.key_authorization(account_key))

    @abc.abstractmethod
    def validation(self, account_key, **kwargs):
        """Generate validation for the challenge.

        Subclasses must implement this method, but they are likely to
        return completely different data structures, depending on what's
        necessary to complete the challenge. Interpretation of that
        return value must be known to the caller.

        :param JWK account_key:
        :returns: Challenge-specific validation.

        """
        raise NotImplementedError()  # pragma: no cover

    def response_and_validation(self, account_key, *args, **kwargs):
        """Generate response and validation.

        Convenience function that return results of `response` and
        `validation`.

        :param JWK account_key:
        :rtype: tuple

        """
        return (self.response(account_key),
                self.validation(account_key, *args, **kwargs))

@ChallengeResponse.register
class DNS01Response(KeyAuthorizationChallengeResponse):
    """ACME dns-01 challenge response."""
    typ = "dns-01"

    def simple_verify(self, chall, domain, account_public_key):
        """Simple verify.

        This method no longer checks DNS records and is a simple wrapper
        around `KeyAuthorizationChallengeResponse.verify`.

        :param challenges.DNS01 chall: Corresponding challenge.
        :param unicode domain: Domain name being verified.
        :param JWK account_public_key: Public key for the key pair
            being authorized.

        :return: ``True`` iff verification of the key authorization was
            successful.
        :rtype: bool

        """
        # pylint: disable=unused-argument
        verified = self.verify(chall, account_public_key)
        if not verified:
            logger.debug("Verification of key authorization in response failed")
        return verified

@Challenge.register  # pylint: disable=too-many-ancestors
class DNS01(KeyAuthorizationChallenge):
    """ACME dns-01 challenge."""
    response_cls = DNS01Response
    typ = response_cls.typ

    LABEL = "_acme-challenge"
    """Label clients prepend to the domain name being validated."""

    def validation(self, account_key, **unused_kwargs):
        """Generate validation.

        :param JWK account_key:
        :rtype: unicode

        """
        return jose.b64encode(hashlib.sha256(self.key_authorization(
            account_key).encode("utf-8")).digest()).decode()

    def validation_domain_name(self, name):
        """Domain name for TXT validation record.

        :param unicode name: Domain name being validated.

        """
        return "{0}.{1}".format(self.LABEL, name)

@ChallengeResponse.register
class HTTP01Response(KeyAuthorizationChallengeResponse):
    """ACME http-01 challenge response."""
    typ = "http-01"

    PORT = 80
    """Verification port as defined by the protocol.

    You can override it (e.g. for testing) by passing ``port`` to
    `simple_verify`.

    """

    WHITESPACE_CUTSET = "\n\r\t "
    """Whitespace characters which should be ignored at the end of the body."""

    def simple_verify(self, chall, domain, account_public_key, port=None):
        """Simple verify.

        :param challenges.SimpleHTTP chall: Corresponding challenge.
        :param unicode domain: Domain name being verified.
        :param JWK account_public_key: Public key for the key pair
            being authorized.
        :param int port: Port used in the validation.

        :returns: ``True`` iff validation with the files currently served by the
            HTTP server is successful.
        :rtype: bool

        """
        if not self.verify(chall, account_public_key):
            logger.debug("Verification of key authorization in response failed")
            return False

        # TODO: ACME specification defines URI template that doesn't
        # allow to use a custom port... Make sure port is not in the
        # request URI, if it's standard.
        if port is not None and port != self.PORT:
            logger.warning(
                "Using non-standard port for http-01 verification: %s", port)
            domain += ":{0}".format(port)

        uri = chall.uri(domain)
        logger.debug("Verifying %s at %s...", chall.typ, uri)
        try:
            http_response = requests.get(uri)
        except requests.exceptions.RequestException as error:
            logger.error("Unable to reach %s: %s", uri, error)
            return False
        logger.debug("Received %s: %s. Headers: %s", http_response,
                     http_response.text, http_response.headers)

        challenge_response = http_response.text.rstrip(self.WHITESPACE_CUTSET)
        if self.key_authorization != challenge_response:
            logger.debug("Key authorization from response (%r) doesn't match "
                         "HTTP response (%r)", self.key_authorization,
                         challenge_response)
            return False

        return True

@Challenge.register  # pylint: disable=too-many-ancestors
class HTTP01(KeyAuthorizationChallenge):
    """ACME http-01 challenge."""
    response_cls = HTTP01Response
    typ = response_cls.typ

    URI_ROOT_PATH = ".well-known/acme-challenge"
    """URI root path for the server provisioned resource."""

    @property
    def path(self):
        """Path (starting with '/') for provisioned resource.

        :rtype: string

        """
        return '/' + self.URI_ROOT_PATH + '/' + self.encode('token')

    def uri(self, domain):
        """Create an URI to the provisioned resource.

        Forms an URI to the HTTPS server provisioned resource
        (containing :attr:`~SimpleHTTP.token`).

        :param unicode domain: Domain name being verified.
        :rtype: string

        """
        return "http://" + domain + self.path

    def validation(self, account_key, **unused_kwargs):
        """Generate validation.

        :param JWK account_key:
        :rtype: unicode

        """
        return self.key_authorization(account_key)

@ChallengeResponse.register
class TLSSNI01Response(KeyAuthorizationChallengeResponse):
    """ACME tls-sni-01 challenge response."""
    typ = "tls-sni-01"

    DOMAIN_SUFFIX = b".acme.invalid"
    """Domain name suffix."""

    PORT = 443
    """Verification port as defined by the protocol.

    You can override it (e.g. for testing) by passing ``port`` to
    `simple_verify`.

    """

    @property
    def z(self):  # pylint: disable=invalid-name
        """``z`` value used for verification.

        :rtype bytes:

        """
        return hashlib.sha256(
            self.key_authorization.encode("utf-8")).hexdigest().lower().encode()

    @property
    def z_domain(self):
        """Domain name used for verification, generated from `z`.

        :rtype bytes:

        """
        return self.z[:32] + b'.' + self.z[32:] + self.DOMAIN_SUFFIX

    def gen_cert(self, key=None, bits=2048):
        """Generate tls-sni-01 certificate.

        :param OpenSSL.crypto.PKey key: Optional private key used in
            certificate generation. If not provided (``None``), then
            fresh key will be generated.
        :param int bits: Number of bits for newly generated key.

        :rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey`

        """
        if key is None:
            key = OpenSSL.crypto.PKey()
            key.generate_key(OpenSSL.crypto.TYPE_RSA, bits)
        return crypto_util.gen_ss_cert(key, [
            # z_domain is too big to fit into CN, hence first dummy domain
            'dummy', self.z_domain.decode()], force_san=True), key

    def probe_cert(self, domain, **kwargs):
        """Probe tls-sni-01 challenge certificate.

        :param unicode domain:

        """
        # TODO: domain is not necessary if host is provided
        if "host" not in kwargs:
            host = socket.gethostbyname(domain)
            logger.debug('%s resolved to %s', domain, host)
            kwargs["host"] = host

        kwargs.setdefault("port", self.PORT)
        kwargs["name"] = self.z_domain
        # TODO: try different methods?
        # pylint: disable=protected-access
        return crypto_util.probe_sni(**kwargs)

    def verify_cert(self, cert):
        """Verify tls-sni-01 challenge certificate.

        :param OpensSSL.crypto.X509 cert: Challenge certificate.

        :returns: Whether the certificate was successfully verified.
        :rtype: bool

        """
        # pylint: disable=protected-access
        sans = crypto_util._pyopenssl_cert_or_req_san(cert)
        logger.debug('Certificate %s. SANs: %s', cert.digest('sha256'), sans)
        return self.z_domain.decode() in sans

    def simple_verify(self, chall, domain, account_public_key,
                      cert=None, **kwargs):
        """Simple verify.

        Verify ``validation`` using ``account_public_key``, optionally
        probe tls-sni-01 certificate and check using `verify_cert`.

        :param .challenges.TLSSNI01 chall: Corresponding challenge.
        :param str domain: Domain name being validated.
        :param JWK account_public_key:
        :param OpenSSL.crypto.X509 cert: Optional certificate. If not
            provided (``None``) certificate will be retrieved using
            `probe_cert`.
        :param int port: Port used to probe the certificate.

        :returns: ``True`` iff client's control of the domain has been
            verified.
        :rtype: bool

        """
        if not self.verify(chall, account_public_key):
            logger.debug("Verification of key authorization in response failed")
            return False

        if cert is None:
            try:
                cert = self.probe_cert(domain=domain, **kwargs)
            except errors.Error as error:
                logger.debug(error, exc_info=True)
                return False

        return self.verify_cert(cert)

@Challenge.register  # pylint: disable=too-many-ancestors
class TLSSNI01(KeyAuthorizationChallenge):
    """ACME tls-sni-01 challenge."""
    response_cls = TLSSNI01Response
    typ = response_cls.typ

    # boulder#962, ietf-wg-acme#22
    #n = jose.Field("n", encoder=int, decoder=int)

    def validation(self, account_key, **kwargs):
        """Generate validation.

        :param JWK account_key:
        :param OpenSSL.crypto.PKey cert_key: Optional private key used
            in certificate generation. If not provided (``None``), then
            fresh key will be generated.

        :rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey`

        """
        return self.response(account_key).gen_cert(key=kwargs.get('cert_key'))

@Challenge.register  # pylint: disable=too-many-ancestors
class DNS(_TokenChallenge):
    """ACME "dns" challenge."""
    typ = "dns"

    LABEL = "_acme-challenge"
    """Label clients prepend to the domain name being validated."""

    def gen_validation(self, account_key, alg=jose.RS256, **kwargs):
        """Generate validation.

        :param .JWK account_key: Private account key.
        :param .JWA alg:

        :returns: This challenge wrapped in `.JWS`
        :rtype: .JWS

        """
        return jose.JWS.sign(
            payload=self.json_dumps(sort_keys=True).encode('utf-8'),
            key=account_key, alg=alg, **kwargs)

    def check_validation(self, validation, account_public_key):
        """Check validation.

        :param JWS validation:
        :param JWK account_public_key:
        :rtype: bool

        """
        if not validation.verify(key=account_public_key):
            return False
        try:
            return self == self.json_loads(
                validation.payload.decode('utf-8'))
        except jose.DeserializationError as error:
            logger.debug("Checking validation for DNS failed: %s", error)
            return False

    def gen_response(self, account_key, **kwargs):
        """Generate response.

        :param .JWK account_key: Private account key.
        :param .JWA alg:

        :rtype: DNSResponse

        """
        return DNSResponse(validation=self.gen_validation(
            account_key, **kwargs))

    def validation_domain_name(self, name):
        """Domain name for TXT validation record.

        :param unicode name: Domain name being validated.

        """
        return "{0}.{1}".format(self.LABEL, name)

@ChallengeResponse.register
class DNSResponse(ChallengeResponse):
    """ACME "dns" challenge response.

    :param JWS validation:

    """
    typ = "dns"

    validation = jose.Field("validation", decoder=jose.JWS.from_json)

    def check_validation(self, chall, account_public_key):
        """Check validation.

        :param challenges.DNS chall:
        :param JWK account_public_key:

        :rtype: bool

        """
        return chall.check_validation(self.validation, account_public_key)

usage:
  certbot [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ...

Certbot can obtain and install HTTPS/TLS/SSL certificates.  By default,
it will attempt to use a webserver both for obtaining and installing the
certificate. The most common SUBCOMMANDS and flags are:

obtain, install, and renew certificates:
    (default) run   Obtain & install a certificate in your current webserver
    certonly        Obtain or renew a certificate, but do not install it
    renew           Renew all previously obtained certificates that are near expiry
   -d DOMAINS       Comma-separated list of domains to obtain a certificate for

  --apache          Use the Apache plugin for authentication & installation
  --standalone      Run a standalone webserver for authentication
  --nginx           Use the Nginx plugin for authentication & installation
  --webroot         Place files in a server's webroot folder for authentication
  --manual          Obtain certificates interactively, or using shell script hooks

   -n               Run non-interactively
  --test-cert       Obtain a test certificate from a staging server
  --dry-run         Test "renew" or "certonly" without saving any certificates to disk

manage certificates:
    certificates    Display information about certificates you have from Certbot
    revoke          Revoke a certificate (supply --cert-path)
    delete          Delete a certificate

manage your account with Let's Encrypt:
    register        Create a Let's Encrypt ACME account
  --agree-tos       Agree to the ACME server's Subscriber Agreement
   -m EMAIL         Email address for important account notifications

optional arguments:
  -h, --help            show this help message and exit
  -c CONFIG_FILE, --config CONFIG_FILE
                        path to config file (default: /etc/letsencrypt/cli.ini
                        and ~/.config/letsencrypt/cli.ini)
  -v, --verbose         This flag can be used multiple times to incrementally
                        increase the verbosity of output, e.g. -vvv. (default:
                        -2)
  -n, --non-interactive, --noninteractive
                        Run without ever asking for user input. This may
                        require additional command line flags; the client will
                        try to explain which ones are required if it finds one
                        missing (default: False)
  --force-interactive   Force Certbot to be interactive even if it detects
                        it's not being run in a terminal. This flag cannot be
                        used with the renew subcommand. (default: False)
  -d DOMAIN, --domains DOMAIN, --domain DOMAIN
                        Domain names to apply. For multiple domains you can
                        use multiple -d flags or enter a comma separated list
                        of domains as a parameter. (default: Ask)
  --cert-name CERTNAME  Certificate name to apply. Only one certificate name
                        can be used per Certbot run. To see certificate names,
                        run 'certbot certificates'. When creating a new
                        certificate, specifies the new certificate's name.
                        (default: None)
  --dry-run             Perform a test run of the client, obtaining test
                        (invalid) certificates but not saving them to disk.
                        This can currently only be used with the 'certonly'
                        and 'renew' subcommands. Note: Although --dry-run
                        tries to avoid making any persistent changes on a
                        system, it is not completely side-effect free: if used
                        with webserver authenticator plugins like apache and
                        nginx, it makes and then reverts temporary config
                        changes in order to obtain test certificates, and
                        reloads webservers to deploy and then roll back those
                        changes. It also calls --pre-hook and --post-hook
                        commands if they are defined because they may be
                        necessary to accurately simulate renewal. --renew-hook
                        commands are not called. (default: False)
  --debug-challenges    After setting up challenges, wait for user input
                        before submitting to CA (default: False)
  --preferred-challenges PREF_CHALLS
                        A sorted, comma delimited list of the preferred
                        challenge to use during authorization with the most
                        preferred challenge listed first (Eg, "dns" or "tls-
                        sni-01,http,dns"). Not all plugins support all
                        challenges. See
                        https://certbot.eff.org/docs/using.html#plugins for
                        details. ACME Challenges are versioned, but if you
                        pick "http" rather than "http-01", Certbot will select
                        the latest version automatically. (default: [])
  --user-agent USER_AGENT
                        Set a custom user agent string for the client. User
                        agent strings allow the CA to collect high level
                        statistics about success rates by OS, plugin and use
                        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/0.15.0 (certbot;
                        Ubuntu 16.04.2 LTS) Authenticator/XXX Installer/YYY
                        (SUBCOMMAND; flags: FLAGS) Py/2.7.12). The flags
                        encoded in the user agent are: --duplicate, --force-
                        renew, --allow-subset-of-names, -n, and whether any
                        hooks are set.

automation:
  Arguments for automating execution & other tweaks

  --keep-until-expiring, --keep, --reinstall
                        If the requested certificate matches an existing
                        certificate, always keep the existing one until it is
                        due for renewal (for the 'run' subcommand this means
                        reinstall the existing certificate). (default: Ask)
  --expand              If an existing certificate is a strict subset of the
                        requested names, always expand and replace it with the
                        additional names. (default: Ask)
  --version             show program's version number and exit
  --force-renewal, --renew-by-default
                        If a certificate already exists for the requested
                        domains, renew it now, regardless of whether it is
                        near expiry. (Often --keep-until-expiring is more
                        appropriate). Also implies --expand. (default: False)
  --renew-with-new-domains
                        If a certificate already exists for the requested
                        certificate name but does not match the requested
                        domains, renew it now, regardless of whether it is
                        near expiry. (default: False)
  --allow-subset-of-names
                        When performing domain validation, do not consider it
                        a failure if authorizations can not be obtained for a
                        strict subset of the requested domains. This may be
                        useful for allowing renewals for multiple domains to
                        succeed even if some domains no longer point at this
                        system. This option cannot be used with --csr.
                        (default: False)
  --agree-tos           Agree to the ACME Subscriber Agreement (default: Ask)
  --duplicate           Allow making a certificate lineage that duplicates an
                        existing one (both can be renewed in parallel)
                        (default: False)
  --os-packages-only    (certbot-auto only) install OS package dependencies
                        and then stop (default: False)
  --no-self-upgrade     (certbot-auto only) prevent the certbot-auto script
                        from upgrading itself to newer released versions
                        (default: Upgrade automatically)
  --no-bootstrap        (certbot-auto only) prevent the certbot-auto script
                        from installing OS-level dependencies (default: Prompt
                        to install OS-wide dependencies, but exit if the user
                        says 'No')
  -q, --quiet           Silence all output except errors. Useful for
                        automation via cron. Implies --non-interactive.
                        (default: False)

security:
  Security parameters & server settings

  --rsa-key-size N      Size of the RSA key. (default: 2048)
  --must-staple         Adds the OCSP Must Staple extension to the
                        certificate. Autoconfigures OCSP Stapling for
                        supported setups (Apache version >= 2.3.3 ). (default:
                        False)
  --redirect            Automatically redirect all HTTP traffic to HTTPS for
                        the newly authenticated vhost. (default: Ask)
  --no-redirect         Do not automatically redirect all HTTP traffic to
                        HTTPS for the newly authenticated vhost. (default:
                        Ask)
  --hsts                Add the Strict-Transport-Security header to every HTTP
                        response. Forcing browser to always use SSL for the
                        domain. Defends against SSL Stripping. (default:
                        False)
  --uir                 Add the "Content-Security-Policy: upgrade-insecure-
                        requests" header to every HTTP response. Forcing the
                        browser to use https:// for every http:// resource.
                        (default: None)
  --staple-ocsp         Enables OCSP Stapling. A valid OCSP response is
                        stapled to the certificate that the server offers
                        during TLS. (default: None)
  --strict-permissions  Require that all configuration files are owned by the
                        current user; only needed if your config is somewhere
                        unsafe like /tmp/ (default: False)

testing:
  The following flags are meant for testing and integration purposes only.

  --test-cert, --staging
                        Use the staging server to obtain or revoke test
                        (invalid) certificates; equivalent to --server https
                        ://acme-staging.api.letsencrypt.org/directory
                        (default: False)
  --debug               Show tracebacks in case of errors, and allow certbot-
                        auto execution on experimental platforms (default:
                        False)
  --no-verify-ssl       Disable verification of the ACME server's certificate.
                        (default: False)
  --tls-sni-01-port TLS_SNI_01_PORT
                        Port used during tls-sni-01 challenge. This only
                        affects the port Certbot listens on. A conforming ACME
                        server will still attempt to connect on port 443.
                        (default: 443)
  --tls-sni-01-address TLS_SNI_01_ADDRESS
                        The address the server listens to during tls-sni-01
                        challenge. (default: )
  --http-01-port HTTP01_PORT
                        Port used in the http-01 challenge. This only affects
                        the port Certbot listens on. A conforming ACME server
                        will still attempt to connect on port 80. (default:
                        80)
  --http-01-address HTTP01_ADDRESS
                        The address the server listens to during http-01
                        challenge. (default: )
  --break-my-certs      Be willing to replace or renew valid certificates with
                        invalid (testing/staging) certificates (default:
                        False)

paths:
  Arguments changing execution paths & servers

  --cert-path CERT_PATH
                        Path to where certificate is saved (with auth --csr),
                        installed from, or revoked. (default: None)
  --key-path KEY_PATH   Path to private key for certificate installation or
                        revocation (if account key is missing) (default: None)
  --fullchain-path FULLCHAIN_PATH
                        Accompanying path to a full certificate chain
                        (certificate plus chain). (default: None)
  --chain-path CHAIN_PATH
                        Accompanying path to a certificate chain. (default:
                        None)
  --config-dir CONFIG_DIR
                        Configuration directory. (default: /etc/letsencrypt)
  --work-dir WORK_DIR   Working directory. (default: /var/lib/letsencrypt)
  --logs-dir LOGS_DIR   Logs directory. (default: /var/log/letsencrypt)
  --server SERVER       ACME Directory Resource URI. (default:
                        https://acme-v01.api.letsencrypt.org/directory)

manage:
  Various subcommands and flags are available for managing your
  certificates:

  certificates          List certificates managed by Certbot
  delete                Clean up all files related to a certificate
  renew                 Renew all certificates (or one specified with --cert-
                        name)
  revoke                Revoke a certificate specified with --cert-path
  update_symlinks       Recreate symlinks in your /etc/letsencrypt/live/
                        directory

run:
  Options for obtaining & installing certificates

certonly:
  Options for modifying how a certificate is obtained

  --csr CSR             Path to a Certificate Signing Request (CSR) in DER or
                        PEM format. Currently --csr only works with the
                        'certonly' subcommand. (default: None)

renew:
  The 'renew' subcommand will attempt to renew all certificates (or more
  precisely, certificate lineages) you have previously obtained if they are
  close to expiry, and print a summary of the results. By default, 'renew'
  will reuse the options used to create obtain or most recently successfully
  renew each certificate lineage. You can try it with `--dry-run` first. For
  more fine-grained control, you can renew individual lineages with the
  `certonly` subcommand. Hooks are available to run commands before and
  after renewal; see https://certbot.eff.org/docs/using.html#renewal for
  more information on these.

  --pre-hook PRE_HOOK   Command to be run in a shell before obtaining any
                        certificates. Intended primarily for renewal, where it
                        can be used to temporarily shut down a webserver that
                        might conflict with the standalone plugin. This will
                        only be called if a certificate is actually to be
                        obtained/renewed. When renewing several certificates
                        that have identical pre-hooks, only the first will be
                        executed. (default: None)
  --post-hook POST_HOOK
                        Command to be run in a shell after attempting to
                        obtain/renew certificates. Can be used to deploy
                        renewed certificates, or to restart any servers that
                        were stopped by --pre-hook. This is only run if an
                        attempt was made to obtain/renew a certificate. If
                        multiple renewed certificates have identical post-
                        hooks, only one will be run. (default: None)
  --renew-hook RENEW_HOOK
                        Command to be run in a shell once for each
                        successfully renewed certificate. For this command,
                        the shell variable $RENEWED_LINEAGE will point to the
                        config live subdirectory (for example,
                        "/etc/letsencrypt/live/example.com") containing the
                        new certificates and keys; the shell variable
                        $RENEWED_DOMAINS will contain a space-delimited list
                        of renewed certificate domains (for example,
                        "example.com www.example.com" (default: None)
  --disable-hook-validation
                        Ordinarily the commands specified for --pre-hook
                        /--post-hook/--renew-hook will be checked for
                        validity, to see if the programs being run are in the
                        $PATH, so that mistakes can be caught early, even when
                        the hooks aren't being run just yet. The validation is
                        rather simplistic and fails if you use more advanced
                        shell constructs, so you can use this switch to
                        disable it. (default: False)

certificates:
  List certificates managed by Certbot

delete:
  Options for deleting a certificate

revoke:
  Options for revocation of certificates

  --reason {keycompromise,affiliationchanged,superseded,unspecified,cessationofoperation}
                        Specify reason for revoking certificate. (default: 0)

register:
  Options for account registration & modification

  --register-unsafely-without-email
                        Specifying this flag enables registering an account
                        with no email address. This is strongly discouraged,
                        because in the event of key loss or account compromise
                        you will irrevocably lose access to your account. You
                        will also be unable to receive notice about impending
                        expiration or revocation of your certificates. Updates
                        to the Subscriber Agreement will still affect you, and
                        will be effective 14 days after posting an update to
                        the web site. (default: False)
  --update-registration
                        With the register verb, indicates that details
                        associated with an existing registration, such as the
                        e-mail address, should be updated, rather than
                        registering a new account. (default: False)
  -m EMAIL, --email EMAIL
                        Email used for registration and recovery contact.
                        (default: Ask)
  --eff-email           Share your e-mail address with EFF (default: None)
  --no-eff-email        Don't share your e-mail address with EFF (default:
                        None)

unregister:
  Options for account deactivation.

  --account ACCOUNT_ID  Account ID to use (default: None)

install:
  Options for modifying how a certificate is deployed

config_changes:
  Options for controlling which changes are displayed

  --num NUM             How many past revisions you want to be displayed
                        (default: None)

rollback:
  Options for rolling back server configuration changes

  --checkpoints N       Revert configuration N number of checkpoints.
                        (default: 1)

plugins:
  Options for for the "plugins" subcommand

  --init                Initialize plugins. (default: False)
  --prepare             Initialize and prepare plugins. (default: False)
  --authenticators      Limit to authenticator plugins only. (default: None)
  --installers          Limit to installer plugins only. (default: None)

update_symlinks:
  Recreates certificate and key symlinks in /etc/letsencrypt/live, if you
  changed them by hand or edited a renewal configuration file

plugins:
  Plugin Selection: Certbot client supports an extensible plugins
  architecture. See 'certbot plugins' for a list of all installed plugins
  and their names. You can force a particular plugin by setting options
  provided below. Running --help <plugin_name> will list flags specific to
  that plugin.

  --configurator CONFIGURATOR
                        Name of the plugin that is both an authenticator and
                        an installer. Should not be used together with
                        --authenticator or --installer. (default: Ask)
  -a AUTHENTICATOR, --authenticator AUTHENTICATOR
                        Authenticator plugin name. (default: None)
  -i INSTALLER, --installer INSTALLER
                        Installer plugin name (also used to find domains).
                        (default: None)
  --apache              Obtain and install certificates using Apache (default:
                        False)
  --nginx               Obtain and install certificates using Nginx (default:
                        False)
  --standalone          Obtain certificates using a "standalone" webserver.
                        (default: False)
  --manual              Provide laborious manual instructions for obtaining a
                        certificate (default: False)
  --webroot             Obtain certificates by placing files in a webroot
                        directory. (default: False)
  --dns-cloudflare      Obtain certificates using a DNS TXT record (if you are
                        using Cloudflare for DNS). (default: False)
  --dns-cloudxns        Obtain certificates using a DNS TXT record (if you are
                        using CloudXNS for DNS). (default: False)
  --dns-digitalocean    Obtain certificates using a DNS TXT record (if you are
                        using DigitalOcean for DNS). (default: False)
  --dns-dnsimple        Obtain certificates using a DNS TXT record (if you are
                        using DNSimple for DNS). (default: False)
  --dns-google          Obtain certificates using a DNS TXT record (if you are
                        using Google Cloud DNS). (default: False)
  --dns-nsone           Obtain certificates using a DNS TXT record (if you are
                        using NS1 for DNS). (default: False)
  --dns-route53         Obtain certificates using a DNS TXT record (if you are
                        using Route53 for DNS). (default: False)

apache:
  Apache Web Server plugin

  --apache-enmod APACHE_ENMOD
                        Path to the Apache 'a2enmod' binary. (default:
                        a2enmod)
  --apache-dismod APACHE_DISMOD
                        Path to the Apache 'a2dismod' binary. (default:
                        a2dismod)
  --apache-le-vhost-ext APACHE_LE_VHOST_EXT
                        SSL vhost configuration extension. (default: -le-
                        ssl.conf)
  --apache-server-root APACHE_SERVER_ROOT
                        Apache server root directory. (default: /etc/apache2)
  --apache-vhost-root APACHE_VHOST_ROOT
                        Apache server VirtualHost configuration root (default:
                        /etc/apache2/sites-available)
  --apache-logs-root APACHE_LOGS_ROOT
                        Apache server logs directory (default:
                        /var/log/apache2)
  --apache-challenge-location APACHE_CHALLENGE_LOCATION
                        Directory path for challenge configuration. (default:
                        /etc/apache2)
  --apache-handle-modules APACHE_HANDLE_MODULES
                        Let installer handle enabling required modules for
                        you.(Only Ubuntu/Debian currently) (default: True)
  --apache-handle-sites APACHE_HANDLE_SITES
                        Let installer handle enabling sites for you.(Only
                        Ubuntu/Debian currently) (default: True)

certbot-route53:auth:
  Obtain certificates using a DNS TXT record (if you are using AWS Route53
  for DNS).

  --certbot-route53:auth-propagation-seconds CERTBOT_ROUTE53:AUTH_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 10)

dns-cloudflare:
  Obtain certificates using a DNS TXT record (if you are using Cloudflare
  for DNS).

  --dns-cloudflare-propagation-seconds DNS_CLOUDFLARE_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 10)
  --dns-cloudflare-credentials DNS_CLOUDFLARE_CREDENTIALS
                        Cloudflare credentials INI file. (default: None)

dns-cloudxns:
  Obtain certificates using a DNS TXT record (if you are using CloudXNS for
  DNS).

  --dns-cloudxns-propagation-seconds DNS_CLOUDXNS_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 30)
  --dns-cloudxns-credentials DNS_CLOUDXNS_CREDENTIALS
                        CloudXNS credentials INI file. (default: None)

dns-digitalocean:
  Obtain certs using a DNS TXT record (if you are using DigitalOcean for
  DNS).

  --dns-digitalocean-propagation-seconds DNS_DIGITALOCEAN_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 10)
  --dns-digitalocean-credentials DNS_DIGITALOCEAN_CREDENTIALS
                        DigitalOcean credentials INI file. (default: None)

dns-dnsimple:
  Obtain certificates using a DNS TXT record (if you are using DNSimple for
  DNS).

  --dns-dnsimple-propagation-seconds DNS_DNSIMPLE_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 30)
  --dns-dnsimple-credentials DNS_DNSIMPLE_CREDENTIALS
                        DNSimple credentials INI file. (default: None)

dns-google:
  Obtain certificates using a DNS TXT record (if you are using Google Cloud
  DNS for DNS).

  --dns-google-propagation-seconds DNS_GOOGLE_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 60)
  --dns-google-credentials DNS_GOOGLE_CREDENTIALS
                        Path to Google Cloud DNS service account JSON file.
                        (See https://developers.google.com/identity/protocols/
                        OAuth2ServiceAccount#creatinganaccount forinformation
                        about creating a service account and
                        https://cloud.google.com/dns/access-
                        control#permissions_and_roles for information about
                        therequired permissions.) (default: None)

dns-nsone:
  Obtain certificates using a DNS TXT record (if you are using NS1 for DNS).

  --dns-nsone-propagation-seconds DNS_NSONE_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 30)
  --dns-nsone-credentials DNS_NSONE_CREDENTIALS
                        NS1 credentials file. (default: None)

dns-route53:
  Obtain certificates using a DNS TXT record (if you are using AWS Route53
  for DNS).

  --dns-route53-propagation-seconds DNS_ROUTE53_PROPAGATION_SECONDS
                        The number of seconds to wait for DNS to propagate
                        before asking the ACME server to verify the DNS
                        record. (default: 10)

manual:
  Authenticate through manual configuration or custom shell scripts. When
  using shell scripts, an authenticator script must be provided. The
  environment variables available to this script are $CERTBOT_DOMAIN which
  contains the domain being authenticated, $CERTBOT_VALIDATION which is the
  validation string, and $CERTBOT_TOKEN which 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.

  --manual-auth-hook MANUAL_AUTH_HOOK
                        Path or command to execute for the authentication
                        script (default: None)
  --manual-cleanup-hook MANUAL_CLEANUP_HOOK
                        Path or command to execute for the cleanup script
                        (default: None)
  --manual-public-ip-logging-ok
                        Automatically allows public IP logging (default: Ask)

nginx:
  Nginx Web Server plugin

  --nginx-server-root NGINX_SERVER_ROOT
                        Nginx server root directory. (default: /etc/nginx)
  --nginx-ctl NGINX_CTL
                        Path to the 'nginx' binary, used for 'configtest' and
                        retrieving nginx version number. (default: nginx)

null:
  Null Installer

standalone:
  Spin up a temporary webserver

webroot:
  Place files in webroot directory

  --webroot-path WEBROOT_PATH, -w WEBROOT_PATH
                        public_html / webroot path. This can be specified
                        multiple times to handle different domains; each
                        domain will have the webroot path that preceded it.
                        For instance: `-w /var/www/example -d example.com -d
                        www.example.com -w /var/www/thing -d thing.net -d
                        m.thing.net` (default: Ask)
  --webroot-map WEBROOT_MAP
                        JSON dictionary mapping domains to webroot paths; this
                        implies -d for each entry. You may need to escape this
                        from your shell. E.g.: --webroot-map
                        '{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}'
                        This option is merged with, but takes precedence over,
                        -w / -d entries. At present, if you put webroot-map in
                        a config file, it needs to be on a single line, like:
                        webroot-map = {"example.com":"/var/www"}. (default:
                        {})