mirror of
https://github.com/certbot/certbot.git
synced 2026-05-28 04:34:11 -04:00
Turn DVSNI into module, add more appropriate challenges/api
This commit is contained in:
parent
eb99571a98
commit
05d803ddd3
5 changed files with 274 additions and 236 deletions
|
|
@ -1,9 +1,7 @@
|
|||
"""Apache Configuration based off of Augeas Configurator."""
|
||||
import logging
|
||||
import os
|
||||
import pkg_resources
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
@ -117,6 +115,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
self.vhosts = self.get_virtual_hosts()
|
||||
# Add name_server association dict
|
||||
self.assoc = dict()
|
||||
# Add number of outstanding challenges
|
||||
self.chall_out = 0
|
||||
|
||||
# Enable mod_ssl if it isn't already enabled
|
||||
# This is Let's Encrypt... we enable mod_ssl on initialization :)
|
||||
|
|
@ -125,11 +125,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
# on initialization
|
||||
self._prepare_server_https()
|
||||
|
||||
# Note: initialization doesn't check to see if the config is correct
|
||||
# by Apache's standards. This should be done by the client (client.py)
|
||||
# if it is desired. There may be instances where correct configuration
|
||||
# isn't required on startup.
|
||||
|
||||
def deploy_cert(self, vhost, cert, key, cert_chain=None):
|
||||
"""Deploys certificate to specified virtual host.
|
||||
|
||||
|
|
@ -929,186 +924,41 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
###########################################################################
|
||||
# Challenges Section
|
||||
###########################################################################
|
||||
|
||||
# TODO: Change list_sni_tuple to namedtuple. Also include key within tuple.
|
||||
# This allows the keys to be different for each SNI challenge
|
||||
|
||||
def perform(self, chall_dict):
|
||||
def perform(self, chall_list):
|
||||
"""Perform the configuration related challenge.
|
||||
|
||||
:param dict chall_dict: Dictionary representing a challenge.
|
||||
This function currently assumes all challenges will be fulfilled.
|
||||
If this turns out not to be the case in the future. Cleanup and
|
||||
outstanding challenges will have to be designed better.
|
||||
|
||||
:param dict chall_list: List of challenges to be
|
||||
fulfilled by configurator.
|
||||
|
||||
"""
|
||||
self.chall_out += len(chall_list)
|
||||
responses = [None] * len(chall_list)
|
||||
apache_dvsni = dvsni.ApacheDVSNI(self)
|
||||
|
||||
if chall_dict.get("type", "") == 'dvsni':
|
||||
return self.dvsni_perform(chall_dict)
|
||||
return None
|
||||
for i, chall in enumerate(chall_list):
|
||||
if isinstance(chall, challenge_util.DVSNI_Chall):
|
||||
apache_dvsni.add_chall(chall, i)
|
||||
|
||||
def dvsni_perform(self, chall_dict):
|
||||
"""Peform a DVSNI challenge.
|
||||
|
||||
`chall_dict` composed of:
|
||||
|
||||
`type`: `dvsni` (`str`)
|
||||
|
||||
`dvsni_chall`:
|
||||
List of DVSNI_Chall namedtuples
|
||||
(:class:`letsencrypt.client.client.Client.DVSNI_Chall`)
|
||||
where DVSNI_Chall tuples have the following fields
|
||||
`domain` (`str`), `r_b64` (base64 `str`), `nonce` (hex `str`)
|
||||
`key` (:class:`letsencrypt.client.client.Client.Key`)
|
||||
|
||||
:param dict chall_dict: dvsni challenge - see documentation
|
||||
|
||||
"""
|
||||
# Save any changes to the configuration as a precaution
|
||||
# About to make temporary changes to the config
|
||||
self.save()
|
||||
|
||||
# Do weak validation that challenge is of expected type
|
||||
if "dvsni_chall" not in chall_dict:
|
||||
logging.fatal("Incorrect parameter given to Apache DVSNI challenge")
|
||||
logging.fatal("Chall dict: %s", chall_dict)
|
||||
sys.exit(1)
|
||||
|
||||
addresses = []
|
||||
default_addr = "*:443"
|
||||
for chall in chall_dict["dvsni_chall"]:
|
||||
vhost = self.choose_virtual_host(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.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 chall_dict["dvsni_chall"]:
|
||||
cert_path = self.dvsni_get_cert_file(chall.nonce)
|
||||
self.register_file_creation(cert_path)
|
||||
s_b64 = challenge_util.dvsni_gen_cert(
|
||||
cert_path, chall.domain, chall.r_b64, chall.nonce, chall.key)
|
||||
|
||||
responses.append({"type": "dvsni", "s": s_b64})
|
||||
|
||||
# Setup the configuration
|
||||
self.dvsni_mod_config(chall_dict["dvsni_chall"], addresses)
|
||||
|
||||
# Save reversible changes and restart the server
|
||||
self.save("SNI Challenge", True)
|
||||
sni_response = apache_dvsni.perform()
|
||||
# Must restart in order to activate the challenges.
|
||||
# Handled here because we may be able to load up other challenge types
|
||||
self.restart()
|
||||
|
||||
for i, resp in enumerate(sni_response):
|
||||
responses[apache_dvsni.indices[i]] = resp
|
||||
|
||||
return responses
|
||||
|
||||
def cleanup(self):
|
||||
def cleanup(self, chall_list):
|
||||
"""Revert all challenges."""
|
||||
|
||||
self.revert_challenge_config()
|
||||
self.restart()
|
||||
|
||||
# TODO: Variable names
|
||||
def dvsni_mod_config(self, dvsni_chall, ll_addrs):
|
||||
"""Modifies Apache config files to include challenge vhosts.
|
||||
|
||||
Result: Apache config includes virtual servers for issued challs
|
||||
|
||||
:param list dvsni_chall: list of
|
||||
:class:`letsencrypt.client.client.Client.DVSNI_Chall`
|
||||
|
||||
:param list ll_addrs: list of list of
|
||||
:class:`letsencrypt.client.apache.obj.Addr` to apply
|
||||
|
||||
"""
|
||||
# WARNING: THIS IS A POTENTIAL SECURITY VULNERABILITY
|
||||
# THIS SHOULD BE HANDLED BY THE PACKAGE MANAGER
|
||||
# AND TAKEN OUT BEFORE RELEASE, INSTEAD
|
||||
# SHOWING A NICE ERROR MESSAGE ABOUT THE PROBLEM
|
||||
|
||||
# Check to make sure options-ssl.conf is installed
|
||||
# pylint: disable=no-member
|
||||
if not os.path.isfile(CONFIG.OPTIONS_SSL_CONF):
|
||||
dist_conf = pkg_resources.resource_filename(
|
||||
__name__, os.path.basename(CONFIG.OPTIONS_SSL_CONF))
|
||||
shutil.copyfile(dist_conf, CONFIG.OPTIONS_SSL_CONF)
|
||||
|
||||
# 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(
|
||||
dvsni_chall[idx].nonce, lis, dvsni_chall[idx].key.file)
|
||||
config_text += "</IfModule>\n"
|
||||
|
||||
self.dvsni_conf_include_check(self.parser.loc["default"])
|
||||
self.register_file_creation(True, CONFIG.APACHE_CHALLENGE_CONF)
|
||||
|
||||
with open(CONFIG.APACHE_CHALLENGE_CONF, 'w') as new_conf:
|
||||
new_conf.write(config_text)
|
||||
|
||||
def dvsni_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.parser.find_dir(
|
||||
parser.case_i("Include"), CONFIG.APACHE_CHALLENGE_CONF)) == 0:
|
||||
# print "Including challenge virtual host(s)"
|
||||
self.parser.add_dir(parser.get_aug_path(main_config),
|
||||
"Include", CONFIG.APACHE_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.parser.loc["ssl_options"] + "\n"
|
||||
"SSLCertificateFile " + self.dvsni_get_cert_file(nonce) + "\n"
|
||||
"SSLCertificateKeyFile " + dvsni_key_file + "\n"
|
||||
"\n"
|
||||
"DocumentRoot " + self.direc["config"] + "challenge_page/\n"
|
||||
"</VirtualHost>\n\n")
|
||||
|
||||
def dvsni_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.direc["work"] + nonce + ".crt"
|
||||
self.chall_out -= len(chall_list)
|
||||
if self.chall_out <= 0:
|
||||
self.revert_challenge_config()
|
||||
self.restart()
|
||||
|
||||
|
||||
def enable_mod(mod_name):
|
||||
|
|
@ -1217,3 +1067,6 @@ def get_file_path(vhost_path):
|
|||
continue
|
||||
break
|
||||
return avail_fp
|
||||
|
||||
|
||||
from letsencrypt.client.apache import dvsni
|
||||
|
|
|
|||
193
letsencrypt/client/apache/dvsni.py
Normal file
193
letsencrypt/client/apache/dvsni.py
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
"""ApacheDVSNI"""
|
||||
import logging
|
||||
import os
|
||||
import pkg_resources
|
||||
import shutil
|
||||
|
||||
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 DVSNI_Chall 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.DVSNI_Chall`
|
||||
|
||||
"""
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.dvsni_chall = []
|
||||
self.indices = []
|
||||
# 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.DVSNI_Chall`
|
||||
|
||||
: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 dict()
|
||||
# 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_virtual_host(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:
|
||||
cert_path = self.get_cert_file(chall.nonce)
|
||||
self.config.register_file_creation(cert_path)
|
||||
s_b64 = challenge_util.dvsni_gen_cert(
|
||||
cert_path, chall.domain, chall.r_b64, chall.nonce, chall.key)
|
||||
|
||||
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 chall_complete(self, chall):
|
||||
# """Used by Authenticator to notify the DVSNI challenge.
|
||||
|
||||
# :param chall: Challenge info
|
||||
# :type chall: :class:`letsencrypt.client.client.Client.DVSNI_Chall`
|
||||
|
||||
# """
|
||||
# self.completed += 1
|
||||
# if self.completed < len(self.dvsni_chall):
|
||||
# return False
|
||||
# return True
|
||||
|
||||
# TODO: Variable names
|
||||
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
|
||||
|
||||
"""
|
||||
# WARNING: THIS IS A POTENTIAL SECURITY VULNERABILITY
|
||||
# THIS SHOULD BE HANDLED BY THE PACKAGE MANAGER
|
||||
# AND TAKEN OUT BEFORE RELEASE, INSTEAD
|
||||
# SHOWING A NICE ERROR MESSAGE ABOUT THE PROBLEM
|
||||
|
||||
# Check to make sure options-ssl.conf is installed
|
||||
# pylint: disable=no-member
|
||||
if not os.path.isfile(CONFIG.OPTIONS_SSL_CONF):
|
||||
dist_conf = pkg_resources.resource_filename(
|
||||
__name__, os.path.basename(CONFIG.OPTIONS_SSL_CONF))
|
||||
shutil.copyfile(dist_conf, CONFIG.OPTIONS_SSL_CONF)
|
||||
|
||||
# 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.register_file_creation(True, CONFIG.APACHE_CHALLENGE_CONF)
|
||||
|
||||
with open(CONFIG.APACHE_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"), CONFIG.APACHE_CHALLENGE_CONF)) == 0:
|
||||
# print "Including challenge virtual host(s)"
|
||||
self.config.parser.add_dir(parser.get_aug_path(main_config),
|
||||
"Include", CONFIG.APACHE_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"
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
"""Challenge specific utility functions."""
|
||||
import collections
|
||||
import hashlib
|
||||
|
||||
from Crypto import Random
|
||||
|
|
@ -8,6 +9,9 @@ from letsencrypt.client import crypto_util
|
|||
from letsencrypt.client import le_util
|
||||
|
||||
|
||||
DVSNI_Chall = collections.namedtuple("DVSNI_Chall", "domain, r_b64, nonce, key")
|
||||
|
||||
|
||||
# DVSNI Challenge functions
|
||||
def dvsni_gen_cert(filepath, name, r_b64, nonce, key):
|
||||
"""Generate a DVSNI cert and save it to filepath.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import zope.component
|
|||
|
||||
from letsencrypt.client import acme
|
||||
from letsencrypt.client import challenge
|
||||
from letsencrypt.client import challenge_util
|
||||
from letsencrypt.client import CONFIG
|
||||
from letsencrypt.client import crypto_util
|
||||
from letsencrypt.client import errors
|
||||
|
|
@ -47,7 +48,6 @@ class Client(object):
|
|||
"""
|
||||
Key = collections.namedtuple("Key", "file pem")
|
||||
CSR = collections.namedtuple("CSR", "file data form")
|
||||
DVSNI_Chall = collections.namedtuple("DVSNI_Chall", "domain, r_b64, nonce, key")
|
||||
|
||||
def __init__(self, server, names, authkey, auth, installer):
|
||||
"""Initialize a client."""
|
||||
|
|
@ -80,10 +80,10 @@ class Client(object):
|
|||
challenge_msg = self.acme_challenge()
|
||||
|
||||
# Perform Challenges
|
||||
responses, challenge_objs = self.verify_identity(challenge_msg)
|
||||
responses, auth_c, client_c = self.verify_identity(challenge_msg)
|
||||
|
||||
# Get Authorization
|
||||
self.acme_authorization(challenge_msg, challenge_objs, responses)
|
||||
self.acme_authorization(challenge_msg, auth_c, client_c, responses)
|
||||
|
||||
# Retrieve certificate
|
||||
certificate_dict = self.acme_certificate(csr.data)
|
||||
|
|
@ -108,7 +108,7 @@ class Client(object):
|
|||
return self.network.send_and_receive_expected(
|
||||
acme.challenge_request(self.names[0]), "challenge")
|
||||
|
||||
def acme_authorization(self, challenge_msg, chal_objs, responses):
|
||||
def acme_authorization(self, challenge_msg, auth_c, client_c, responses):
|
||||
"""Handle ACME "authorization" phase.
|
||||
|
||||
:param dict challenge_msg: ACME "challenge" message.
|
||||
|
|
@ -132,7 +132,7 @@ class Client(object):
|
|||
"Failed Authorization procedure - cleaning up challenges")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
self.cleanup_challenges(chal_objs)
|
||||
self.cleanup_challenges(auth_c, client_c)
|
||||
|
||||
def acme_certificate(self, csr_der):
|
||||
"""Handle ACME "certificate" phase.
|
||||
|
|
@ -243,19 +243,16 @@ class Client(object):
|
|||
# # TODO enable OCSP Stapling
|
||||
# continue
|
||||
|
||||
def cleanup_challenges(self, challenges):
|
||||
def cleanup_challenges(self, auth_c, client_c):
|
||||
"""Cleanup configuration challenges
|
||||
|
||||
:param dict challenges: challenges from a challenge message
|
||||
|
||||
"""
|
||||
logging.info("Cleaning up challenges...")
|
||||
for chall in challenges:
|
||||
if chall["type"] in CONFIG.CONFIG_CHALLENGES:
|
||||
self.auth.cleanup()
|
||||
else:
|
||||
# Handle other cleanup if needed
|
||||
pass
|
||||
self.auth.cleanup(auth_c)
|
||||
# should cleanup client_c
|
||||
assert not client_c
|
||||
|
||||
def verify_identity(self, challenge_msg):
|
||||
"""Verify identity.
|
||||
|
|
@ -275,45 +272,37 @@ class Client(object):
|
|||
# challenges in the master list the challenge object satisfies
|
||||
# Single Challenge objects that can satisfy multiple server challenges
|
||||
# mess up the order of the challenges, thus requiring the indices
|
||||
challenge_objs, indices = self.challenge_factory(
|
||||
auth_c, auth_i, client_c, client_i = self.challenge_factory(
|
||||
self.names[0], challenge_msg["challenges"], path)
|
||||
|
||||
responses = ["null"] * len(challenge_msg["challenges"])
|
||||
|
||||
# Perform challenges
|
||||
for i, c_obj in enumerate(challenge_objs):
|
||||
resp = "null"
|
||||
if c_obj["type"] in CONFIG.CONFIG_CHALLENGES:
|
||||
resp = self.auth.perform(c_obj)
|
||||
else:
|
||||
# Handle RecoveryToken type challenges
|
||||
pass
|
||||
|
||||
self._assign_responses(resp, indices[i], responses)
|
||||
# Do client centric challenges here...
|
||||
# Since this isn't implemented yet...
|
||||
assert not client_i
|
||||
auth_resp = self.auth.perform(auth_c)
|
||||
self._assign_responses(auth_resp, auth_i, responses)
|
||||
|
||||
logging.info(
|
||||
"Configured Apache for challenges; waiting for verification...")
|
||||
|
||||
return responses, challenge_objs
|
||||
return responses, auth_c, client_c
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def _assign_responses(self, resp, index_list, responses):
|
||||
"""Assign chall_response to appropriate places in response list.
|
||||
|
||||
:param resp: responses from a challenge
|
||||
:type resp: list of dicts or dict
|
||||
:type resp: list of dicts
|
||||
|
||||
:param list index_list: respective challenges resp satisfies
|
||||
:param list responses: master list of responses
|
||||
|
||||
"""
|
||||
if isinstance(resp, list):
|
||||
assert len(resp) == len(index_list)
|
||||
for j, index in enumerate(index_list):
|
||||
responses[index] = resp[j]
|
||||
else:
|
||||
for index in index_list:
|
||||
responses[index] = resp
|
||||
assert len(resp) == len(index_list)
|
||||
for j, index in enumerate(index_list):
|
||||
responses[index] = resp[j]
|
||||
|
||||
|
||||
def store_cert_key(self, cert_file, encrypt=False):
|
||||
"""Store certificate key.
|
||||
|
|
@ -392,10 +381,10 @@ class Client(object):
|
|||
vhost.add(host)
|
||||
return vhost
|
||||
|
||||
def challenge_factory(self, name, challenges, path):
|
||||
def challenge_factory(self, domain, challenges, path):
|
||||
"""
|
||||
|
||||
:param name: TODO
|
||||
:param str domain: domain of the enrollee
|
||||
|
||||
:param list challenges: A list of challenges from ACME "challenge"
|
||||
server message to be fulfilled by the client in order to prove
|
||||
|
|
@ -407,27 +396,27 @@ class Client(object):
|
|||
:rtype: tuple
|
||||
|
||||
"""
|
||||
sni_todo = []
|
||||
auth_chall = []
|
||||
# Since a single invocation of SNI challenge can satisfy multiple
|
||||
# challenges. We must keep track of all the challenges it satisfies
|
||||
sni_satisfies = []
|
||||
auth_satisfies = []
|
||||
|
||||
challenge_objs = []
|
||||
challenge_obj_indices = []
|
||||
client_chall = []
|
||||
client_satisfies = []
|
||||
for index in path:
|
||||
chall = challenges[index]
|
||||
|
||||
if chall["type"] == "dvsni":
|
||||
logging.info(" DVSNI challenge for name %s.", name)
|
||||
sni_satisfies.append(index)
|
||||
sni_todo.append(Client.DVSNI_Chall(
|
||||
str(name), str(chall["r"]),
|
||||
logging.info(" DVSNI challenge for name %s.", domain)
|
||||
auth_satisfies.append(index)
|
||||
auth_chall.append(challenge_util.DVSNI_Chall(
|
||||
str(domain), str(chall["r"]),
|
||||
str(chall["nonce"]), self.authkey))
|
||||
|
||||
elif chall["type"] == "recoveryToken":
|
||||
logging.info("\tRecovery Token Challenge for name: %s.", name)
|
||||
challenge_obj_indices.append(index)
|
||||
challenge_objs.append({
|
||||
logging.info(" Recovery Token Challenge for name: %s.", domain)
|
||||
client_satisfies.append(index)
|
||||
client_chall.append({
|
||||
type: "recoveryToken",
|
||||
})
|
||||
|
||||
|
|
@ -435,17 +424,7 @@ class Client(object):
|
|||
logging.fatal("Challenge not currently supported")
|
||||
sys.exit(82)
|
||||
|
||||
if sni_todo:
|
||||
# SNI_Challenge can satisfy many sni challenges at once so only
|
||||
# one "challenge object" is issued for all sni_challenges
|
||||
challenge_objs.append({
|
||||
"type": "dvsni",
|
||||
"dvsni_chall": sni_todo
|
||||
})
|
||||
challenge_obj_indices.append(sni_satisfies)
|
||||
logging.debug(sni_todo)
|
||||
|
||||
return challenge_objs, challenge_obj_indices
|
||||
return auth_chall, auth_satisfies, client_chall, client_satisfies
|
||||
|
||||
|
||||
def validate_key_csr(privkey, csr):
|
||||
|
|
|
|||
|
|
@ -11,17 +11,26 @@ class IAuthenticator(zope.interface.Interface):
|
|||
ability to perform challenges and attain a certificate.
|
||||
|
||||
"""
|
||||
def perform(chall_dict):
|
||||
"""Perform the given challenge"""
|
||||
def perform(chall_list):
|
||||
"""Perform the given challenge.
|
||||
|
||||
def cleanup():
|
||||
:param list chall_list: List of challenge types defined in client.py
|
||||
|
||||
:returns: List of responses
|
||||
If the challenge cant be completed...
|
||||
None - Authenticator can perform challenge, but can't at this time
|
||||
False - Authenticator will never be able to perform (error)
|
||||
:rtype: `list` of dicts
|
||||
|
||||
"""
|
||||
def cleanup(chall_list):
|
||||
"""Revert changes and shutdown after challenges complete."""
|
||||
|
||||
|
||||
class IChallenge(zope.interface.Interface):
|
||||
"""Let's Encrypt challenge."""
|
||||
|
||||
def perform(quiet=True):
|
||||
def perform():
|
||||
"""Perform the challenge.
|
||||
|
||||
:param bool quiet: TODO
|
||||
|
|
|
|||
Loading…
Reference in a new issue