1
0
mirror of https://github.com/certbot/certbot.git synced 2026-01-26 07:41:33 +03:00
Files
certbot/letsencrypt/client/apache/dvsni.py
2015-01-26 22:25:08 -08:00

191 lines
6.8 KiB
Python

"""ApacheDVSNI"""
import logging
import os
from letsencrypt.client import challenge_util
from letsencrypt.client import CONFIG
from letsencrypt.client.apache import parser
class ApacheDvsni(object):
"""Class performs DVSNI challenges within the Apache configurator.
:ivar config: ApacheConfigurator object
:type config: :class:`letsencrypt.client.apache.configurator`
:ivar dvsni_chall: Data required for challenges.
where DvsniChall tuples have the following fields
`domain` (`str`), `r_b64` (base64 `str`), `nonce` (hex `str`)
`key` (:class:`letsencrypt.client.client.Client.Key`)
:type dvsni_chall: `list` of
:class:`letsencrypt.client.challenge_util.DvsniChall`
:param list indicies: Meant to hold indices of challenges in a
larger array. ApacheDvsni is capable of solving many challenges
at once which causes an indexing issue within ApacheConfigurator
who must return all responses in order. Imagine ApacheConfigurator
maintaining state about where all of the SimpleHttps Challenges,
Dvsni Challenges belong in the response array. This is an optional
utility.
:param str challenge_conf: location of the challenge config file
"""
def __init__(self, config):
self.config = config
self.dvsni_chall = []
self.indices = []
self.challenge_conf = os.path.join(
config.direc["config"], "le_dvsni_cert_challenge.conf")
# self.completed = 0
def add_chall(self, chall, idx=None):
"""Add challenge to DVSNI object to perform at once.
:param chall: DVSNI challenge info
:type chall: :class:`letsencrypt.client.challenge_util.DvsniChall`
:param int idx: index to challenge in a larger array
"""
self.dvsni_chall.append(chall)
if idx is not None:
self.indices.append(idx)
def perform(self):
"""Peform a DVSNI challenge."""
if not self.dvsni_chall:
return None
# Save any changes to the configuration as a precaution
# About to make temporary changes to the config
self.config.save()
addresses = []
default_addr = "*:443"
for chall in self.dvsni_chall:
vhost = self.config.choose_vhost(chall.domain)
if vhost is None:
logging.error(
"No vhost exists with servername or alias of: %s",
chall.domain)
logging.error("No _default_:443 vhost exists")
logging.error("Please specify servernames in the Apache config")
return None
# TODO - @jdkasten review this code to make sure it makes sense
self.config.make_server_sni_ready(vhost, default_addr)
for addr in vhost.addrs:
if "_default_" == addr.get_addr():
addresses.append([default_addr])
break
else:
addresses.append(list(vhost.addrs))
responses = []
# Create all of the challenge certs
for chall in self.dvsni_chall:
s_b64 = self._setup_challenge_cert(chall)
responses.append({"type": "dvsni", "s": s_b64})
# Setup the configuration
self._mod_config(addresses)
# Save reversible changes
self.config.save("SNI Challenge", True)
return responses
def _setup_challenge_cert(self, chall):
"""Generate and write out challenge certificate."""
cert_path = self.get_cert_file(chall.nonce)
# Register the path before you write out the file
self.config.reverter.register_file_creation(True, cert_path)
cert_pem, s_b64 = challenge_util.dvsni_gen_cert(
chall.domain, chall.r_b64, chall.nonce, chall.key)
# Write out challenge cert
with open(cert_path, 'w') as cert_chall_fd:
cert_chall_fd.write(cert_pem)
return s_b64
def _mod_config(self, ll_addrs):
"""Modifies Apache config files to include challenge vhosts.
Result: Apache config includes virtual servers for issued challs
:param list ll_addrs: list of list of
:class:`letsencrypt.client.apache.obj.Addr` to apply
"""
# TODO: Use ip address of existing vhost instead of relying on FQDN
config_text = "<IfModule mod_ssl.c>\n"
for idx, lis in enumerate(ll_addrs):
config_text += self._get_config_text(
self.dvsni_chall[idx].nonce, lis,
self.dvsni_chall[idx].key.file)
config_text += "</IfModule>\n"
self._conf_include_check(self.config.parser.loc["default"])
self.config.reverter.register_file_creation(True, self.challenge_conf)
with open(self.challenge_conf, 'w') as new_conf:
new_conf.write(config_text)
def _conf_include_check(self, main_config):
"""Adds DVSNI challenge conf file into configuration.
Adds DVSNI challenge include file if it does not already exist
within mainConfig
:param str main_config: file path to main user apache config file
"""
if len(self.config.parser.find_dir(
parser.case_i("Include"), self.challenge_conf)) == 0:
# print "Including challenge virtual host(s)"
self.config.parser.add_dir(parser.get_aug_path(main_config),
"Include", self.challenge_conf)
def _get_config_text(self, nonce, ip_addrs, dvsni_key_file):
"""Chocolate virtual server configuration text
:param str nonce: hex form of nonce
:param list ip_addrs: addresses of challenged domain
:class:`list` of type :class:`letsencrypt.client.apache.obj.Addr`
:param str dvsni_key_file: Path to key file
:returns: virtual host configuration text
:rtype: str
"""
ips = " ".join(str(i) for i in ip_addrs)
return ("<VirtualHost " + ips + ">\n"
"ServerName " + nonce + CONFIG.INVALID_EXT + "\n"
"UseCanonicalName on\n"
"SSLStrictSNIVHostCheck on\n"
"\n"
"LimitRequestBody 1048576\n"
"\n"
"Include " + self.config.parser.loc["ssl_options"] + "\n"
"SSLCertificateFile " + self.get_cert_file(nonce) + "\n"
"SSLCertificateKeyFile " + dvsni_key_file + "\n"
"\n"
"DocumentRoot " + self.config.direc["config"] + "dvsni_page/\n"
"</VirtualHost>\n\n")
def get_cert_file(self, nonce):
"""Returns standardized name for challenge certificate.
:param str nonce: hex form of nonce
:returns: certificate file name
:rtype: str
"""
return self.config.direc["work"] + nonce + ".crt"