mirror of
https://github.com/certbot/certbot.git
synced 2026-06-06 07:12:54 -04:00
Merge pull request #52 from kuba/pep8
PEP-8, code base cleanup, bug fixes
This commit is contained in:
commit
c260232f61
18 changed files with 618 additions and 677 deletions
|
|
@ -1,3 +1,5 @@
|
|||
import os.path
|
||||
|
||||
# CA hostname
|
||||
# If you create your own server... change this line
|
||||
# Note: the server certificate must be trusted in order to avoid
|
||||
|
|
@ -10,24 +12,24 @@ CONFIG_DIR = "/etc/letsencrypt/"
|
|||
# Working directory for letsencrypt
|
||||
WORK_DIR = "/var/lib/letsencrypt/"
|
||||
# Directory where configuration backups are stored
|
||||
BACKUP_DIR = WORK_DIR + "backups/"
|
||||
BACKUP_DIR = os.path.join(WORK_DIR, "backups/")
|
||||
# Replaces MODIFIED_FILES, directory where temp checkpoint is created
|
||||
TEMP_CHECKPOINT_DIR = WORK_DIR + "temp_checkpoint/"
|
||||
TEMP_CHECKPOINT_DIR = os.path.join(WORK_DIR, "temp_checkpoint/")
|
||||
# Directory used before a permanent checkpoint is finalized
|
||||
IN_PROGRESS_DIR = BACKUP_DIR + "IN_PROGRESS/"
|
||||
IN_PROGRESS_DIR = os.path.join(BACKUP_DIR, "IN_PROGRESS/")
|
||||
# Directory where all certificates/keys are stored - used for easy revocation
|
||||
CERT_KEY_BACKUP = WORK_DIR + "keys-certs/"
|
||||
CERT_KEY_BACKUP = os.path.join(WORK_DIR, "keys-certs/")
|
||||
# Where all keys should be stored
|
||||
KEY_DIR = SERVER_ROOT + "ssl/"
|
||||
KEY_DIR = os.path.join(SERVER_ROOT, "ssl/")
|
||||
# Certificate storage
|
||||
CERT_DIR = SERVER_ROOT + "certs/"
|
||||
CERT_DIR = os.path.join(SERVER_ROOT, "certs/")
|
||||
|
||||
# Contains standard Apache SSL directives
|
||||
OPTIONS_SSL_CONF = CONFIG_DIR + "options-ssl.conf"
|
||||
OPTIONS_SSL_CONF = os.path.join(CONFIG_DIR, "options-ssl.conf")
|
||||
# Let's Encrypt SSL vhost configuration extension
|
||||
LE_VHOST_EXT = "-le-ssl.conf"
|
||||
# Temporary file for challenge virtual hosts
|
||||
APACHE_CHALLENGE_CONF = CONFIG_DIR + "le_dvsni_cert_challenge.conf"
|
||||
APACHE_CHALLENGE_CONF = os.path.join(CONFIG_DIR, "le_dvsni_cert_challenge.conf")
|
||||
|
||||
# Byte size of S and Nonce
|
||||
S_SIZE = 32
|
||||
|
|
@ -37,13 +39,13 @@ NONCE_SIZE = 16
|
|||
RSA_KEY_SIZE = 2048
|
||||
|
||||
# bits of hashcash to generate
|
||||
difficulty = 23
|
||||
DIFFICULTY = 23
|
||||
|
||||
# Let's Encrypt cert and chain files
|
||||
CERT_PATH = CERT_DIR + "cert-letsencrypt.pem"
|
||||
CHAIN_PATH = CERT_DIR + "chain-letsencrypt.pem"
|
||||
|
||||
#Invalid Extension
|
||||
# Invalid Extension
|
||||
INVALID_EXT = ".acme.invalid"
|
||||
|
||||
# Challenge Preferences Dict for currently supported challenges
|
||||
|
|
@ -56,4 +58,5 @@ EXCLUSIVE_CHALLENGES = [set(["dvsni", "simpleHttps"])]
|
|||
CONFIG_CHALLENGES = {"dvsni", "simpleHttps"}
|
||||
|
||||
# Rewrite rule arguments used for redirections to https vhost
|
||||
REWRITE_HTTPS_ARGS = ["^.*$", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,R=permanent]"]
|
||||
REWRITE_HTTPS_ARGS = [
|
||||
"^.*$", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,R=permanent]"]
|
||||
|
|
|
|||
51
letsencrypt/client/acme.py
Executable file → Normal file
51
letsencrypt/client/acme.py
Executable file → Normal file
|
|
@ -1,33 +1,48 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# acme.py
|
||||
# validate JSON objects as ACME protocol messages
|
||||
|
||||
import json, jsonschema
|
||||
"""Validate JSON objects as ACME protocol messages."""
|
||||
import json
|
||||
import pkg_resources
|
||||
|
||||
schemata = {schema: json.load(open(pkg_resources.resource_filename(
|
||||
__name__, "schemata/%s.json" % schema))) for schema in [
|
||||
"authorization", "authorizationRequest", "certificate", "certificateRequest",
|
||||
"challenge", "challengeRequest", "defer", "error", "revocation",
|
||||
"revocationRequest", "statusRequest"]
|
||||
import jsonschema
|
||||
|
||||
|
||||
SCHEMATA = {
|
||||
schema: json.load(open(pkg_resources.resource_filename(
|
||||
__name__, "schemata/%s.json" % schema))) for schema in [
|
||||
"authorization",
|
||||
"authorizationRequest",
|
||||
"certificate",
|
||||
"certificateRequest",
|
||||
"challenge",
|
||||
"challengeRequest",
|
||||
"defer",
|
||||
"error",
|
||||
"revocation",
|
||||
"revocationRequest",
|
||||
"statusRequest"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def acme_object_validate(j):
|
||||
"""Validate a JSON object against the ACME protocol using JSON Schema.
|
||||
|
||||
Success will return None; failure to validate will raise a
|
||||
jsonschema.ValidationError exception describing the reason that the
|
||||
object could not be validated successfully."""
|
||||
object could not be validated successfully.
|
||||
"""
|
||||
j = json.loads(j)
|
||||
if not isinstance(j, dict):
|
||||
raise jsonschema.ValidationError("this is not a dictionary object")
|
||||
if "type" not in j:
|
||||
raise jsonschema.ValidationError("missing type field")
|
||||
if j["type"] not in schemata:
|
||||
if j["type"] not in SCHEMATA:
|
||||
raise jsonschema.ValidationError("unknown type %s" % j["type"])
|
||||
jsonschema.validate(j, schemata[j["type"]])
|
||||
jsonschema.validate(j, SCHEMATA[j["type"]])
|
||||
|
||||
def pretty(s):
|
||||
"""Return a pretty-printed version of any JSON string (useful when
|
||||
printing out protocol messages for debugging purposes."""
|
||||
return json.dumps(json.loads(s), indent=4)
|
||||
|
||||
def pretty(json_string):
|
||||
"""Return a pretty-printed version of any JSON string.
|
||||
|
||||
Useful when printing out protocol messages for debugging purposes.
|
||||
"""
|
||||
return json.dumps(json.loads(json_string), indent=4)
|
||||
|
|
|
|||
|
|
@ -1,30 +1,23 @@
|
|||
import augeas
|
||||
import subprocess
|
||||
import re
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
import time
|
||||
import pkg_resources
|
||||
import re
|
||||
import shutil
|
||||
from pkg_resources import Requirement, resource_filename
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
from letsencrypt.client.CONFIG import SERVER_ROOT, BACKUP_DIR
|
||||
from letsencrypt.client.CONFIG import REWRITE_HTTPS_ARGS, CONFIG_DIR, WORK_DIR
|
||||
from letsencrypt.client.CONFIG import TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR
|
||||
from letsencrypt.client.CONFIG import OPTIONS_SSL_CONF, LE_VHOST_EXT
|
||||
from letsencrypt.client import logger, le_util, augeas_configurator
|
||||
from letsencrypt.client import crypto_util
|
||||
|
||||
# Challenge specific imports
|
||||
import binascii, hashlib
|
||||
from Crypto import Random
|
||||
from letsencrypt.client.CONFIG import S_SIZE, APACHE_CHALLENGE_CONF, INVALID_EXT
|
||||
|
||||
options_ssl_conf = resource_filename(__name__, os.path.basename(OPTIONS_SSL_CONF))
|
||||
from letsencrypt.client import augeas_configurator
|
||||
from letsencrypt.client import CONFIG
|
||||
from letsencrypt.client import crypto_util
|
||||
from letsencrypt.client import le_util
|
||||
from letsencrypt.client import logger
|
||||
|
||||
#from CONFIG import SERVER_ROOT, BACKUP_DIR, REWRITE_HTTPS_ARGS, CONFIG_DIR,
|
||||
#from CONFIG import WORK_DIR, TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR, OPTIONS_SSL_CONF, LE_VHOST_EXT
|
||||
#import logger, le_util
|
||||
|
||||
options_ssl_conf = pkg_resources.resource_filename(__name__, os.path.basename(CONFIG.OPTIONS_SSL_CONF))
|
||||
|
||||
|
||||
# Configurator should be turned into a Singleton
|
||||
|
|
@ -92,7 +85,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
so that other Configurators (like Nginx) can be developed and interoperate
|
||||
with the client.
|
||||
"""
|
||||
def __init__(self, server_root=SERVER_ROOT):
|
||||
def __init__(self, server_root=CONFIG.SERVER_ROOT):
|
||||
super(ApacheConfigurator, self).__init__()
|
||||
|
||||
self.server_root = server_root
|
||||
|
|
@ -566,14 +559,14 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
def make_vhost_ssl(self, nonssl_vhost):
|
||||
"""
|
||||
Duplicates vhost and adds default ssl options
|
||||
New vhost will reside as (nonssl_vhost.path) + LE_VHOST_EXT
|
||||
New vhost will reside as (nonssl_vhost.path) + CONFIG.LE_VHOST_EXT
|
||||
"""
|
||||
avail_fp = nonssl_vhost.file
|
||||
# Copy file
|
||||
if avail_fp.endswith(".conf"):
|
||||
ssl_fp = avail_fp[:-(len(".conf"))] + LE_VHOST_EXT
|
||||
ssl_fp = avail_fp[:-(len(".conf"))] + CONFIG.LE_VHOST_EXT
|
||||
else:
|
||||
ssl_fp = avail_fp + LE_VHOST_EXT
|
||||
ssl_fp = avail_fp + CONFIG.LE_VHOST_EXT
|
||||
|
||||
# First register the creation so that it is properly removed if
|
||||
# configuration is rolled back
|
||||
|
|
@ -678,7 +671,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
return False, general_v
|
||||
#Add directives to server
|
||||
self.add_dir(general_v.path, "RewriteEngine", "On")
|
||||
self.add_dir(general_v.path, "RewriteRule", REWRITE_HTTPS_ARGS)
|
||||
self.add_dir(general_v.path, "RewriteRule", CONFIG.REWRITE_HTTPS_ARGS)
|
||||
self.save_notes += 'Redirecting host in %s to ssl vhost in %s\n' % (general_v.file, ssl_vhost.file)
|
||||
self.save()
|
||||
return True, general_v
|
||||
|
|
@ -704,9 +697,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
if not rewrite_path:
|
||||
# "No existing redirection for virtualhost"
|
||||
return False, -1
|
||||
if len(rewrite_path) == len(REWRITE_HTTPS_ARGS):
|
||||
if len(rewrite_path) == len(CONFIG.REWRITE_HTTPS_ARGS):
|
||||
for idx, m in enumerate(rewrite_path):
|
||||
if self.aug.get(m) != REWRITE_HTTPS_ARGS[idx]:
|
||||
if self.aug.get(m) != CONFIG.REWRITE_HTTPS_ARGS[idx]:
|
||||
# Not a letsencrypt https rewrite
|
||||
return True, 2
|
||||
# Existing letsencrypt https rewrite rule is in place
|
||||
|
|
@ -979,7 +972,7 @@ LogLevel warn \n\
|
|||
def save_apache_config(self):
|
||||
# Not currently used
|
||||
# Should be safe because it is a protected directory
|
||||
shutil.copytree(self.server_root, BACKUP_DIR + "apache2-" + str(time.time()))
|
||||
shutil.copytree(self.server_root, CONFIG.BACKUP_DIR + "apache2-" + str(time.time()))
|
||||
|
||||
|
||||
def verify_setup(self):
|
||||
|
|
@ -988,9 +981,9 @@ LogLevel warn \n\
|
|||
Aim for defensive coding... make sure all input files
|
||||
have permissions of root
|
||||
'''
|
||||
le_util.make_or_verify_dir(CONFIG_DIR, 0755)
|
||||
le_util.make_or_verify_dir(WORK_DIR, 0755)
|
||||
le_util.make_or_verify_dir(BACKUP_DIR, 0755)
|
||||
le_util.make_or_verify_dir(CONFIG.CONFIG_DIR, 0755)
|
||||
le_util.make_or_verify_dir(CONFIG.WORK_DIR, 0755)
|
||||
le_util.make_or_verify_dir(CONFIG.BACKUP_DIR, 0755)
|
||||
|
||||
def standardize_excl(self):
|
||||
"""
|
||||
|
|
@ -1112,7 +1105,7 @@ LogLevel warn \n\
|
|||
addresses.append(vhost.addrs)
|
||||
|
||||
# Generate S
|
||||
s = Random.get_random_bytes(S_SIZE)
|
||||
s = Random.get_random_bytes(CONFIG.S_SIZE)
|
||||
# Create all of the challenge certs
|
||||
for t in chall_dict["listSNITuple"]:
|
||||
# Need to decode from base64
|
||||
|
|
@ -1140,7 +1133,7 @@ LogLevel warn \n\
|
|||
nonce: string - hex
|
||||
result: returns certificate file name
|
||||
"""
|
||||
return WORK_DIR + nonce + ".crt"
|
||||
return CONFIG.WORK_DIR + nonce + ".crt"
|
||||
|
||||
def __getConfigText(self, nonce, ip_addrs, key):
|
||||
"""
|
||||
|
|
@ -1153,7 +1146,7 @@ LogLevel warn \n\
|
|||
result: returns virtual host configuration text
|
||||
"""
|
||||
configText = "<VirtualHost " + " ".join(ip_addrs) + "> \n \
|
||||
ServerName " + nonce + INVALID_EXT + " \n \
|
||||
ServerName " + nonce + CONFIG.INVALID_EXT + " \n \
|
||||
UseCanonicalName on \n \
|
||||
SSLStrictSNIVHostCheck on \n \
|
||||
\n \
|
||||
|
|
@ -1163,7 +1156,7 @@ Include " + options_ssl_conf + " \n \
|
|||
SSLCertificateFile " + self.dvsni_get_cert_file(nonce) + " \n \
|
||||
SSLCertificateKeyFile " + key + " \n \
|
||||
\n \
|
||||
DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
|
||||
DocumentRoot " + CONFIG.CONFIG_DIR + "challenge_page/ \n \
|
||||
</VirtualHost> \n\n "
|
||||
|
||||
return configText
|
||||
|
|
@ -1187,8 +1180,8 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
|
|||
configText += "</IfModule> \n"
|
||||
|
||||
self.dvsni_conf_include_check(mainConfig)
|
||||
self.register_file_creation(True, APACHE_CHALLENGE_CONF)
|
||||
newConf = open(APACHE_CHALLENGE_CONF, 'w')
|
||||
self.register_file_creation(True, CONFIG.APACHE_CHALLENGE_CONF)
|
||||
newConf = open(CONFIG.APACHE_CHALLENGE_CONF, 'w')
|
||||
newConf.write(configText)
|
||||
newConf.close()
|
||||
|
||||
|
|
@ -1203,9 +1196,9 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
|
|||
|
||||
result: User Apache configuration includes chocolate sni challenge file
|
||||
"""
|
||||
if len(self.find_directive(self.case_i("Include"), APACHE_CHALLENGE_CONF)) == 0:
|
||||
if len(self.find_directive(self.case_i("Include"), CONFIG.APACHE_CHALLENGE_CONF)) == 0:
|
||||
#print "Including challenge virtual host(s)"
|
||||
self.add_dir("/files" + mainConfig, "Include", APACHE_CHALLENGE_CONF)
|
||||
self.add_dir("/files" + mainConfig, "Include", CONFIG.APACHE_CHALLENGE_CONF)
|
||||
|
||||
def dvsni_create_chall_cert(self, name, ext, nonce, key):
|
||||
"""
|
||||
|
|
@ -1219,7 +1212,7 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
|
|||
"""
|
||||
|
||||
self.register_file_creation(True, self.dvsni_get_cert_file(nonce))
|
||||
cert_pem = crypto_util.make_ss_cert(key, [nonce + INVALID_EXT, name, ext])
|
||||
cert_pem = crypto_util.make_ss_cert(key, [nonce + CONFIG.INVALID_EXT, name, ext])
|
||||
with open(self.dvsni_get_cert_file(nonce), 'w') as f:
|
||||
f.write(cert_pem)
|
||||
|
||||
|
|
@ -1230,16 +1223,16 @@ DocumentRoot " + CONFIG_DIR + "challenge_page/ \n \
|
|||
r: byte array
|
||||
s: byte array
|
||||
|
||||
result: returns z + INVALID_EXT
|
||||
result: returns z + CONFIG.INVALID_EXT
|
||||
"""
|
||||
h = hashlib.new('sha256')
|
||||
h.update(r)
|
||||
h.update(s)
|
||||
|
||||
return h.hexdigest() + INVALID_EXT
|
||||
return h.hexdigest() + CONFIG.INVALID_EXT
|
||||
|
||||
def main():
|
||||
config = Configurator()
|
||||
config = ApacheConfigurator()
|
||||
logger.setLogger(logger.FileLogger(sys.stdout))
|
||||
logger.setLogLevel(logger.DEBUG)
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
import abc, os, sys, shutil, time
|
||||
from letsencrypt.client.configurator import Configurator
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import time
|
||||
|
||||
import augeas
|
||||
from letsencrypt.client import le_util, logger
|
||||
from letsencrypt.client.CONFIG import TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR
|
||||
from letsencrypt.client.CONFIG import BACKUP_DIR
|
||||
|
||||
class AugeasConfigurator(Configurator):
|
||||
from letsencrypt.client import CONFIG
|
||||
from letsencrypt.client import configurator
|
||||
from letsencrypt.client import le_util
|
||||
from letsencrypt.client import logger
|
||||
|
||||
|
||||
class AugeasConfigurator(configurator.Configurator):
|
||||
|
||||
def __init__(self):
|
||||
super(AugeasConfigurator, self).__init__()
|
||||
|
|
@ -16,54 +21,6 @@ class AugeasConfigurator(Configurator):
|
|||
self.aug = augeas.Augeas(flags=augeas.Augeas.NONE)
|
||||
self.save_notes = ""
|
||||
|
||||
def deploy_cert(self, vhost, cert, key , cert_chain=None):
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
|
||||
def choose_virtual_host(self, name):
|
||||
"""
|
||||
Chooses a virtual host based on a given domain name
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def get_all_names(self):
|
||||
"""
|
||||
Return all names found in the Configuration
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def enable_redirect(self, ssl_vhost):
|
||||
"""
|
||||
Makes all traffic redirect to the given ssl_vhost
|
||||
ie. port 80 => 443
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def enable_hsts(self, ssl_vhost):
|
||||
"""
|
||||
Enable HSTS on the given ssl_vhost
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def enable_ocsp_stapling(self, ssl_vhost):
|
||||
"""
|
||||
Enable OCSP stapling on given ssl_vhost
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def get_all_certs_keys(self):
|
||||
"""
|
||||
Retrieve all certs and keys set in configuration
|
||||
return list of tuples with form [(cert, key, path)]
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def enable_site(self, vhost):
|
||||
"""
|
||||
Enable the site at the given vhost
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def check_parsing_errors(self, lens):
|
||||
"""
|
||||
This function checks to see if Augeas was unable to parse any of the
|
||||
|
|
@ -78,15 +35,17 @@ class AugeasConfigurator(Configurator):
|
|||
# As aug.get may return null
|
||||
if lens_path and lens in lens_path:
|
||||
# Strip off /augeas/files and /error
|
||||
logger.error('There has been an error in parsing the file: %s' % e[13:len(e) - 6])
|
||||
logger.error('There has been an error in parsing the file: '
|
||||
'%s' % e[13:len(e) - 6])
|
||||
logger.error(self.aug.get(e + '/message'))
|
||||
|
||||
|
||||
def save(self, title=None, temporary=False):
|
||||
"""
|
||||
Saves all changes to the configuration files
|
||||
"""Saves all changes to the configuration files.
|
||||
|
||||
This function is not transactional
|
||||
TODO: Instead rely on challenge to backup all files before modifications
|
||||
|
||||
TODO: Instead rely on challenge to backup all files before
|
||||
modifications
|
||||
|
||||
title: string - The title of the save. If a title is given, the
|
||||
configuration will be saved as a new checkpoint
|
||||
|
|
@ -105,11 +64,12 @@ class AugeasConfigurator(Configurator):
|
|||
except:
|
||||
# Check for the root of save problems
|
||||
new_errs = self.aug.match("/augeas//error")
|
||||
logger.error("During Save - " + mod_conf)
|
||||
# logger.error("During Save - " + mod_conf)
|
||||
# Only print new errors caused by recent save
|
||||
for err in new_errs:
|
||||
if err not in ex_errs:
|
||||
logger.error("Unable to save file - %s" % err[13:len(err)-6])
|
||||
logger.error("Unable to save file - "
|
||||
"%s" % err[13:len(err)-6])
|
||||
logger.error("Attempted Save Notes")
|
||||
logger.error(self.save_notes)
|
||||
# Erase Save Notes
|
||||
|
|
@ -138,20 +98,18 @@ class AugeasConfigurator(Configurator):
|
|||
|
||||
# Create Checkpoint
|
||||
if temporary:
|
||||
self.add_to_checkpoint(TEMP_CHECKPOINT_DIR, save_files)
|
||||
self.add_to_checkpoint(CONFIG.TEMP_CHECKPOINT_DIR, save_files)
|
||||
else:
|
||||
self.add_to_checkpoint(IN_PROGRESS_DIR, save_files)
|
||||
self.add_to_checkpoint(CONFIG.IN_PROGRESS_DIR, save_files)
|
||||
|
||||
|
||||
if title and not temporary and os.path.isdir(IN_PROGRESS_DIR):
|
||||
success = self.__finalize_checkpoint(IN_PROGRESS_DIR, title)
|
||||
if title and not temporary and os.path.isdir(CONFIG.IN_PROGRESS_DIR):
|
||||
success = self.__finalize_checkpoint(CONFIG.IN_PROGRESS_DIR, title)
|
||||
if not success:
|
||||
# This should never happen
|
||||
# This will be hopefully be cleaned up on the recovery
|
||||
# routine startup
|
||||
sys.exit(9)
|
||||
|
||||
|
||||
self.aug.set("/augeas/save", save_state)
|
||||
self.save_notes = ""
|
||||
self.aug.save()
|
||||
|
|
@ -163,19 +121,19 @@ class AugeasConfigurator(Configurator):
|
|||
This function should reload the users original configuration files
|
||||
for all saves with reversible=True
|
||||
"""
|
||||
if os.path.isdir(TEMP_CHECKPOINT_DIR):
|
||||
result = self.__recover_checkpoint(TEMP_CHECKPOINT_DIR)
|
||||
if os.path.isdir(CONFIG.TEMP_CHECKPOINT_DIR):
|
||||
result = self.__recover_checkpoint(CONFIG.TEMP_CHECKPOINT_DIR)
|
||||
changes = True
|
||||
if result != 0:
|
||||
# We have a partial or incomplete recovery
|
||||
logger.fatal("Incomplete or failed recovery for %s" % TEMP_CHECKPOINT_DIR)
|
||||
logger.fatal("Incomplete or failed recovery for "
|
||||
"%s" % CONFIG.TEMP_CHECKPOINT_DIR)
|
||||
sys.exit(67)
|
||||
# Remember to reload Augeas
|
||||
self.aug.load()
|
||||
|
||||
|
||||
def rollback_checkpoints(self, rollback = 1):
|
||||
""" Revert 'rollback' number of configuration checkpoints """
|
||||
def rollback_checkpoints(self, rollback=1):
|
||||
"""Revert 'rollback' number of configuration checkpoints."""
|
||||
try:
|
||||
rollback = int(rollback)
|
||||
except:
|
||||
|
|
@ -185,14 +143,15 @@ class AugeasConfigurator(Configurator):
|
|||
logger.error("Rollback argument must be a positive integer")
|
||||
return
|
||||
|
||||
backups = os.listdir(BACKUP_DIR)
|
||||
backups = os.listdir(CONFIG.BACKUP_DIR)
|
||||
backups.sort()
|
||||
|
||||
if len(backups) < rollback:
|
||||
logger.error("Unable to rollback %d checkpoints, only %d exist" % (rollback, len(backups)))
|
||||
logger.error(("Unable to rollback %d checkpoints, only "
|
||||
"%d exist") % (rollback, len(backups)))
|
||||
|
||||
while rollback > 0 and backups:
|
||||
cp_dir = BACKUP_DIR + backups.pop()
|
||||
cp_dir = CONFIG.BACKUP_DIR + backups.pop()
|
||||
result = self.__recover_checkpoint(cp_dir)
|
||||
if result != 0:
|
||||
logger.fatal("Failed to load checkpoint during rollback")
|
||||
|
|
@ -208,32 +167,33 @@ class AugeasConfigurator(Configurator):
|
|||
script found in the constructor, before this function would ever be
|
||||
called
|
||||
"""
|
||||
backups = os.listdir(BACKUP_DIR)
|
||||
backups = os.listdir(CONFIG.BACKUP_DIR)
|
||||
backups.sort(reverse=True)
|
||||
|
||||
if not backups:
|
||||
print "Letsencrypt has not saved any backups of your apache configuration"
|
||||
print ("Letsencrypt has not saved any backups of your "
|
||||
"apache configuration")
|
||||
# Make sure there isn't anything unexpected in the backup folder
|
||||
# There should only be timestamped (float) directories
|
||||
try:
|
||||
for bu in backups:
|
||||
float(bu)
|
||||
except:
|
||||
assert False, "Invalid files in %s" % BACKUP_DIR
|
||||
assert False, "Invalid files in %s" % CONFIG.BACKUP_DIR
|
||||
|
||||
for bu in backups:
|
||||
print time.ctime(float(bu))
|
||||
with open(BACKUP_DIR + bu + "/CHANGES_SINCE") as f:
|
||||
with open(CONFIG.BACKUP_DIR + bu + "/CHANGES_SINCE") as f:
|
||||
print f.read()
|
||||
|
||||
print "Affected files:"
|
||||
with open(BACKUP_DIR + bu + "/FILEPATHS") as f:
|
||||
with open(CONFIG.BACKUP_DIR + bu + "/FILEPATHS") as f:
|
||||
filepaths = f.read().splitlines()
|
||||
for fp in filepaths:
|
||||
print " %s" % fp
|
||||
|
||||
try:
|
||||
with open(BACKUP_DIR + bu + "/NEW_FILES") as f:
|
||||
with open(CONFIG.BACKUP_DIR + bu + "/NEW_FILES") as f:
|
||||
print "New Configuration Files:"
|
||||
filepaths = f.read().splitlines()
|
||||
for fp in filepaths:
|
||||
|
|
@ -247,12 +207,12 @@ class AugeasConfigurator(Configurator):
|
|||
Add title to cp_dir CHANGES_SINCE
|
||||
Move cp_dir to Backups directory and rename with timestamp
|
||||
"""
|
||||
final_dir = BACKUP_DIR + str(time.time())
|
||||
final_dir = CONFIG.BACKUP_DIR + str(time.time())
|
||||
try:
|
||||
with open(cp_dir + "CHANGES_SINCE.tmp", 'w') as ft:
|
||||
ft.write("-- %s --\n" % title)
|
||||
with open(cp_dir + "CHANGES_SINCE", 'r') as f:
|
||||
ft.write(f.read())
|
||||
ft.write(f.read())
|
||||
shutil.move(cp_dir + "CHANGES_SINCE.tmp", cp_dir + "CHANGES_SINCE")
|
||||
except:
|
||||
logger.error("Unable to finalize checkpoint - adding title")
|
||||
|
|
@ -260,7 +220,8 @@ class AugeasConfigurator(Configurator):
|
|||
try:
|
||||
os.rename(cp_dir, final_dir)
|
||||
except:
|
||||
logger.error("Unable to finalize checkpoint, %s -> %s" % cp_dir, final_dir)
|
||||
logger.error("Unable to finalize checkpoint, %s -> %s" %
|
||||
(cp_dir, final_dir))
|
||||
return False
|
||||
return True
|
||||
|
||||
|
|
@ -282,7 +243,8 @@ class AugeasConfigurator(Configurator):
|
|||
# Tag files with index so multiple files can
|
||||
# have the same filename
|
||||
logger.debug("Creating backup of %s" % filename)
|
||||
shutil.copy2(filename, cp_dir + os.path.basename(filename) + "_" + str(idx))
|
||||
shutil.copy2(filename, cp_dir + os.path.basename(filename)
|
||||
+ "_" + str(idx))
|
||||
op_fd.write(filename + '\n')
|
||||
idx += 1
|
||||
op_fd.close()
|
||||
|
|
@ -290,8 +252,6 @@ class AugeasConfigurator(Configurator):
|
|||
with open(cp_dir + "CHANGES_SINCE", 'a') as notes_fd:
|
||||
notes_fd.write(self.save_notes)
|
||||
|
||||
|
||||
|
||||
def __recover_checkpoint(self, cp_dir):
|
||||
"""
|
||||
Recover a specific checkpoint provided by cp_dir
|
||||
|
|
@ -305,7 +265,8 @@ class AugeasConfigurator(Configurator):
|
|||
with open(cp_dir + "/FILEPATHS") as f:
|
||||
filepaths = f.read().splitlines()
|
||||
for idx, fp in enumerate(filepaths):
|
||||
shutil.copy2(cp_dir + '/' + os.path.basename(fp) + '_' + str(idx), fp)
|
||||
shutil.copy2(cp_dir + '/' + os.path.basename(fp)
|
||||
+ '_' + str(idx), fp)
|
||||
except:
|
||||
# This file is required in all checkpoints.
|
||||
logger.error("Unable to recover files from %s" % cp_dir)
|
||||
|
|
@ -323,28 +284,28 @@ class AugeasConfigurator(Configurator):
|
|||
return 0
|
||||
|
||||
def check_tempfile_saves(self, save_files, temporary):
|
||||
temp_path = "%sFILEPATHS" % TEMP_CHECKPOINT_DIR
|
||||
temp_path = "%sFILEPATHS" % CONFIG.TEMP_CHECKPOINT_DIR
|
||||
if os.path.isfile(temp_path):
|
||||
with open(temp_path, 'r') as protected_fd:
|
||||
protected_files = protected_fd.read().splitlines()
|
||||
for filename in protected_files:
|
||||
if filename in save_files:
|
||||
return False, "Attempting to overwrite challenge file - %s" % filename
|
||||
return False, ("Attempting to overwrite challenge "
|
||||
"file - %s" % filename)
|
||||
|
||||
return True, "Successful"
|
||||
|
||||
|
||||
def register_file_creation(self, temporary, *files):
|
||||
"""
|
||||
This is used to register the creation of all files during Letsencrypt
|
||||
execution. Call this method before writing to the file to make sure
|
||||
that the file will be cleaned up if the program exits unexpectedly.
|
||||
"""Register the creation of all files during Letsencrypt 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)
|
||||
"""
|
||||
if temporary:
|
||||
cp_dir = TEMP_CHECKPOINT_DIR
|
||||
cp_dir = CONFIG.TEMP_CHECKPOINT_DIR
|
||||
else:
|
||||
cp_dir = IN_PROGRESS_DIR
|
||||
cp_dir = CONFIG.IN_PROGRESS_DIR
|
||||
|
||||
le_util.make_or_verify_dir(cp_dir)
|
||||
try:
|
||||
|
|
@ -354,29 +315,29 @@ class AugeasConfigurator(Configurator):
|
|||
except:
|
||||
logger.error("ERROR: Unable to register file creation")
|
||||
|
||||
|
||||
def recovery_routine(self):
|
||||
"""
|
||||
Revert all previously modified files. First, any changes found in
|
||||
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.
|
||||
"""Revert all previously modified files.
|
||||
|
||||
First, any changes found in CONFIG.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_challenge_config()
|
||||
if os.path.isdir(IN_PROGRESS_DIR):
|
||||
result = self.__recover_checkpoint(IN_PROGRESS_DIR)
|
||||
if os.path.isdir(CONFIG.IN_PROGRESS_DIR):
|
||||
result = self.__recover_checkpoint(CONFIG.IN_PROGRESS_DIR)
|
||||
if result != 0:
|
||||
# We have a partial or incomplete recovery
|
||||
# Not as egregious
|
||||
# TODO: Additional tests? recovery
|
||||
logger.fatal("Incomplete or failed recovery for %s" % IN_PROGRESS_DIR)
|
||||
logger.fatal("Incomplete or failed recovery for %s" %
|
||||
CONFIG.IN_PROGRESS_DIR)
|
||||
sys.exit(68)
|
||||
|
||||
# Need to reload configuration after these changes take effect
|
||||
self.aug.load()
|
||||
|
||||
|
||||
def __remove_contained_files(self, file_list):
|
||||
"""
|
||||
Erase any files contained within the text file, file_list
|
||||
|
|
@ -389,36 +350,18 @@ class AugeasConfigurator(Configurator):
|
|||
with open(file_list, 'r') as f:
|
||||
filepaths = f.read().splitlines()
|
||||
for fp in filepaths:
|
||||
# Files are registered before they are added... so check to see if file
|
||||
# exists first
|
||||
# Files are registered before they are added... so
|
||||
# check to see if file exists first
|
||||
if os.path.lexists(fp):
|
||||
os.remove(fp)
|
||||
else:
|
||||
logger.warn("File: %s - Could not be found to be deleted\nProgram was probably shut down unexpectedly, in which case this is not a problem" % fp)
|
||||
logger.warn((
|
||||
"File: %s - Could not be found to be deleted\n"
|
||||
"Program was probably shut down unexpectedly, "
|
||||
"in which case this is not a problem") % fp)
|
||||
except IOError:
|
||||
logger.fatal("Unable to remove filepaths contained within %s" % file_list)
|
||||
logger.fatal(
|
||||
"Unable to remove filepaths contained within %s" % file_list)
|
||||
sys.exit(41)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
def config_test(self):
|
||||
"""
|
||||
Make sure the configuration is valid
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def restart(self):
|
||||
"""
|
||||
Restart or refresh the server content
|
||||
"""
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def perform(self, challenge):
|
||||
""" Perform the challenge """
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
||||
def cleanup(self):
|
||||
""" Clean up any challenge configurations """
|
||||
raise Exception("Error: augeas Configurator class")
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
from letsencrypt.client import logger
|
||||
#import logger
|
||||
|
||||
class Challenge(object):
|
||||
|
||||
def __init__(self, configurator):
|
||||
self.config = configurator
|
||||
|
||||
def perform(self, quiet=True):
|
||||
logger.error("Error - base class challenge.perform()")
|
||||
raise NotImplementedError()
|
||||
|
||||
def generate_response(self):
|
||||
logger.error("Error - base class challenge.generate_response()")
|
||||
def clean(self):
|
||||
logger.error("Error - base class challenge.clean()")
|
||||
raise NotImplementedError()
|
||||
|
||||
def cleanup(self):
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
110
letsencrypt/client/client.py
Executable file → Normal file
110
letsencrypt/client/client.py
Executable file → Normal file
|
|
@ -1,21 +1,25 @@
|
|||
#!/usr/bin/env python
|
||||
import csv
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import string
|
||||
import sys
|
||||
import time
|
||||
|
||||
import M2Crypto
|
||||
import json
|
||||
import os, time, sys, shutil
|
||||
|
||||
import csv
|
||||
|
||||
import requests
|
||||
|
||||
from letsencrypt.client.acme import acme_object_validate
|
||||
from letsencrypt.client import configurator, apache_configurator
|
||||
from letsencrypt.client import logger, display
|
||||
from letsencrypt.client import le_util, crypto_util
|
||||
from letsencrypt.client.CONFIG import RSA_KEY_SIZE, CERT_PATH
|
||||
from letsencrypt.client.CONFIG import CHAIN_PATH, SERVER_ROOT, KEY_DIR, CERT_DIR
|
||||
from letsencrypt.client.CONFIG import CERT_KEY_BACKUP, EXCLUSIVE_CHALLENGES
|
||||
from letsencrypt.client.CONFIG import CHALLENGE_PREFERENCES, CONFIG_CHALLENGES
|
||||
from letsencrypt.client import acme
|
||||
from letsencrypt.client import apache_configurator
|
||||
from letsencrypt.client import CONFIG
|
||||
from letsencrypt.client import crypto_util
|
||||
from letsencrypt.client import display
|
||||
from letsencrypt.client import le_util
|
||||
from letsencrypt.client import logger
|
||||
|
||||
|
||||
# it's weird to point to chocolate servers via raw IPv6 addresses, and such
|
||||
# addresses can be %SCARY in some contexts, so out of paranoia let's disable
|
||||
# them by default
|
||||
|
|
@ -35,7 +39,7 @@ class Client(object):
|
|||
# TODO: Can probably figure out which configurator to use without
|
||||
# special packaging based on system info
|
||||
# Command line arg or client function to discover
|
||||
self.config = apache_configurator.ApacheConfigurator(SERVER_ROOT)
|
||||
self.config = apache_configurator.ApacheConfigurator(CONFIG.SERVER_ROOT)
|
||||
|
||||
self.server = ca_server
|
||||
|
||||
|
|
@ -168,8 +172,8 @@ class Client(object):
|
|||
self.list_certs_keys()
|
||||
|
||||
def remove_cert_key(self, c):
|
||||
list_file = CERT_KEY_BACKUP + "LIST"
|
||||
list_file2 = CERT_KEY_BACKUP + "LIST.tmp"
|
||||
list_file = CONFIG.CERT_KEY_BACKUP + "LIST"
|
||||
list_file2 = CONFIG.CERT_KEY_BACKUP + "LIST.tmp"
|
||||
with open(list_file, 'rb') as orgfile:
|
||||
csvreader = csv.reader(orgfile)
|
||||
with open(list_file2, 'wb') as newfile:
|
||||
|
|
@ -187,10 +191,10 @@ class Client(object):
|
|||
|
||||
|
||||
def list_certs_keys(self):
|
||||
list_file = CERT_KEY_BACKUP + "LIST"
|
||||
list_file = CONFIG.CERT_KEY_BACKUP + "LIST"
|
||||
certs = []
|
||||
|
||||
if not os.path.isfile(CERT_KEY_BACKUP + "LIST"):
|
||||
if not os.path.isfile(CONFIG.CERT_KEY_BACKUP + "LIST"):
|
||||
logger.info("You don't have any certificates saved from letsencrypt")
|
||||
return
|
||||
|
||||
|
|
@ -206,8 +210,8 @@ class Client(object):
|
|||
for row in csvreader:
|
||||
c = crypto_util.get_cert_info(row[1])
|
||||
|
||||
b_k = CERT_KEY_BACKUP + os.path.basename(row[2]) + "_" + row[0]
|
||||
b_c = CERT_KEY_BACKUP + os.path.basename(row[1]) + "_" + row[0]
|
||||
b_k = CONFIG.CERT_KEY_BACKUP + os.path.basename(row[2]) + "_" + row[0]
|
||||
b_c = CONFIG.CERT_KEY_BACKUP + os.path.basename(row[1]) + "_" + row[0]
|
||||
|
||||
c["orig_key_file"] = row[2]
|
||||
c["orig_cert_file"] = row[1]
|
||||
|
|
@ -246,7 +250,7 @@ class Client(object):
|
|||
|
||||
def install_certificate(self, certificate_dict, vhost):
|
||||
cert_chain_abspath = None
|
||||
cert_fd, self.cert_file = le_util.unique_file(CERT_PATH, 644)
|
||||
cert_fd, self.cert_file = le_util.unique_file(CONFIG.CERT_PATH, 644)
|
||||
cert_fd.write(
|
||||
crypto_util.b64_cert_to_pem(certificate_dict["certificate"]))
|
||||
cert_fd.close()
|
||||
|
|
@ -254,7 +258,7 @@ class Client(object):
|
|||
self.cert_file)
|
||||
|
||||
if certificate_dict.get("chain", None):
|
||||
chain_fd, chain_fn = le_util.unique_file(CHAIN_PATH, 644)
|
||||
chain_fd, chain_fn = le_util.unique_file(CONFIG.CHAIN_PATH, 644)
|
||||
for c in certificate_dict.get("chain", []):
|
||||
chain_fd.write(crypto_util.b64_cert_to_pem(c))
|
||||
chain_fd.close()
|
||||
|
|
@ -308,7 +312,7 @@ class Client(object):
|
|||
def cleanup_challenges(self, challenge_objs):
|
||||
logger.info("Cleaning up challenges...")
|
||||
for c in challenge_objs:
|
||||
if c["type"] in CONFIG_CHALLENGES:
|
||||
if c["type"] in CONFIG.CONFIG_CHALLENGES:
|
||||
self.config.cleanup()
|
||||
else:
|
||||
#Handle other cleanup if needed
|
||||
|
|
@ -377,7 +381,7 @@ class Client(object):
|
|||
# Perform challenges
|
||||
for i, c_obj in enumerate(challenge_objs):
|
||||
response = "null"
|
||||
if c_obj["type"] in CONFIG_CHALLENGES:
|
||||
if c_obj["type"] in CONFIG.CONFIG_CHALLENGES:
|
||||
response = self.config.perform(c_obj)
|
||||
else:
|
||||
# Handle RecoveryToken type challenges
|
||||
|
|
@ -411,7 +415,7 @@ class Client(object):
|
|||
"""
|
||||
chall_cost = {}
|
||||
max_cost = 0
|
||||
for i, chall in enumerate(CHALLENGE_PREFERENCES):
|
||||
for i, chall in enumerate(CONFIG.CHALLENGE_PREFERENCES):
|
||||
chall_cost[chall] = i
|
||||
max_cost += i
|
||||
|
||||
|
|
@ -423,8 +427,9 @@ class Client(object):
|
|||
for combo in combos:
|
||||
for c in combo:
|
||||
combo_total += chall_cost.get(challenges[c]["type"], max_cost)
|
||||
if combo_total < best_combo_total:
|
||||
if combo_total < best_combo_cost:
|
||||
best_combo = combo
|
||||
best_combo_cost = combo_total
|
||||
combo_total = 0
|
||||
|
||||
if not best_combo:
|
||||
|
|
@ -443,7 +448,7 @@ class Client(object):
|
|||
# Add logic for a crappy server
|
||||
# Choose a DV
|
||||
path = []
|
||||
for pref_c in CHALLENGE_PREFERENCES:
|
||||
for pref_c in CONFIG.CHALLENGE_PREFERENCES:
|
||||
for i, offered_c in enumerate(challenges):
|
||||
if (pref_c == offered_c["type"] and
|
||||
self.is_preferred(offered_c["type"], path)):
|
||||
|
|
@ -454,7 +459,7 @@ class Client(object):
|
|||
|
||||
def is_preferred(self, offered_c_type, path):
|
||||
for tup in path:
|
||||
for s in EXCLUSIVE_CHALLENGES:
|
||||
for s in CONFIG.EXCLUSIVE_CHALLENGES:
|
||||
# Second part is in case we eventually allow multiple names
|
||||
# to be challenges at the same time
|
||||
if (tup[1] in s and offered_c_type in s and
|
||||
|
|
@ -466,14 +471,14 @@ class Client(object):
|
|||
def send(self, json_obj):
|
||||
try:
|
||||
json_encoded = json.dumps(json_obj)
|
||||
acme_object_validate(json_encoded)
|
||||
acme.acme_object_validate(json_encoded)
|
||||
response = requests.post(
|
||||
self.server_url,
|
||||
data=json_encoded,
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
body = response.content
|
||||
acme_object_validate(body)
|
||||
acme.acme_object_validate(body)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
logger.fatal("Send() failed... may have lost connection to server")
|
||||
|
|
@ -488,8 +493,8 @@ class Client(object):
|
|||
|
||||
|
||||
def store_cert_key(self, encrypt = False):
|
||||
list_file = CERT_KEY_BACKUP + "LIST"
|
||||
le_util.make_or_verify_dir(CERT_KEY_BACKUP, 0700)
|
||||
list_file = CONFIG.CERT_KEY_BACKUP + "LIST"
|
||||
le_util.make_or_verify_dir(CONFIG.CERT_KEY_BACKUP, 0700)
|
||||
idx = 0
|
||||
|
||||
if encrypt:
|
||||
|
|
@ -511,10 +516,10 @@ class Client(object):
|
|||
csvwriter.writerow(["0", self.cert_file, self.key_file])
|
||||
|
||||
shutil.copy2(self.key_file,
|
||||
CERT_KEY_BACKUP + os.path.basename(self.key_file) +
|
||||
CONFIG.CERT_KEY_BACKUP + os.path.basename(self.key_file) +
|
||||
"_" + str(idx))
|
||||
shutil.copy2(self.cert_file,
|
||||
CERT_KEY_BACKUP + os.path.basename(self.cert_file) +
|
||||
CONFIG.CERT_KEY_BACKUP + os.path.basename(self.cert_file) +
|
||||
"_" + str(idx))
|
||||
|
||||
|
||||
|
|
@ -553,7 +558,7 @@ class Client(object):
|
|||
|
||||
elif challenges[c]["type"] == "recoveryToken":
|
||||
logger.info("\tRecovery Token Challenge for name: %s." % name)
|
||||
challenge_objs_indicies.append(c)
|
||||
challenge_obj_indicies.append(c)
|
||||
challenge_objs.append({type:"recoveryToken"})
|
||||
|
||||
else:
|
||||
|
|
@ -581,11 +586,11 @@ class Client(object):
|
|||
key_pem = None
|
||||
csr_pem = None
|
||||
if not self.key_file:
|
||||
key_pem = crypto_util.make_key(RSA_KEY_SIZE)
|
||||
key_pem = crypto_util.make_key(CONFIG.RSA_KEY_SIZE)
|
||||
# Save file
|
||||
le_util.make_or_verify_dir(KEY_DIR, 0700)
|
||||
le_util.make_or_verify_dir(CONFIG.KEY_DIR, 0700)
|
||||
key_f, self.key_file = le_util.unique_file(
|
||||
KEY_DIR + "key-letsencrypt.pem", 0600)
|
||||
CONFIG.KEY_DIR + "key-letsencrypt.pem", 0600)
|
||||
key_f.write(key_pem)
|
||||
key_f.close()
|
||||
logger.info("Generating key: %s" % self.key_file)
|
||||
|
|
@ -599,9 +604,9 @@ class Client(object):
|
|||
if not self.csr_file:
|
||||
csr_pem, csr_der = crypto_util.make_csr(self.key_file, self.names)
|
||||
# Save CSR
|
||||
le_util.make_or_verify_dir(CERT_DIR, 0755)
|
||||
le_util.make_or_verify_dir(CONFIG.CERT_DIR, 0755)
|
||||
csr_f, self.csr_file = le_util.unique_file(
|
||||
CERT_DIR + "csr-letsencrypt.pem", 0644)
|
||||
CONFIG.CERT_DIR + "csr-letsencrypt.pem", 0644)
|
||||
csr_f.write(csr_pem)
|
||||
csr_f.close()
|
||||
logger.info("Creating CSR: %s" % self.csr_file)
|
||||
|
|
@ -619,16 +624,16 @@ class Client(object):
|
|||
return key_pem, csr_pem
|
||||
|
||||
|
||||
def choice_of_ca(self):
|
||||
choices = self.get_cas()
|
||||
message = "Pick a Certificate Authority. They're all unique and special!"
|
||||
in_txt = "Enter the number of a Certificate Authority (c to cancel): "
|
||||
code, selection = display.generic_menu(message, choices, in_txt)
|
||||
# def choice_of_ca(self):
|
||||
# choices = self.get_cas()
|
||||
# message = "Pick a Certificate Authority. They're all unique and special!"
|
||||
# in_txt = "Enter the number of a Certificate Authority (c to cancel): "
|
||||
# code, selection = display.generic_menu(message, choices, in_txt)
|
||||
|
||||
if code != display.OK:
|
||||
sys.exit(0)
|
||||
# if code != display.OK:
|
||||
# sys.exit(0)
|
||||
|
||||
return selection
|
||||
# return selection
|
||||
|
||||
# Legacy Code: Although I would like to see a free and open marketplace
|
||||
# in the future. The Let's Encrypt Client will not have this feature at
|
||||
|
|
@ -695,15 +700,13 @@ class Client(object):
|
|||
Do enough to avoid shellcode from the environment. There's
|
||||
no need to do more.
|
||||
"""
|
||||
import string as s
|
||||
allowed = s.ascii_letters + s.digits + "-." # hostnames & IPv4
|
||||
allowed = string.ascii_letters + string.digits + "-." # hostnames & IPv4
|
||||
if all([c in allowed for c in hostname]):
|
||||
return True
|
||||
|
||||
if not allow_raw_ipv6_server: return False
|
||||
|
||||
# ipv6 is messy and complicated, can contain %zoneindex etc.
|
||||
import socket
|
||||
try:
|
||||
# is this a valid IPv6 address?
|
||||
socket.getaddrinfo(hostname,443,socket.AF_INET6)
|
||||
|
|
@ -741,7 +744,8 @@ def renew(config):
|
|||
cert = M2Crypto.X509.load_cert(tup[0])
|
||||
issuer = cert.get_issuer()
|
||||
if recognized_ca(issuer):
|
||||
generate_renewal_req()
|
||||
pass
|
||||
# generate_renewal_req()
|
||||
|
||||
# Wait for response, act accordingly
|
||||
gen_req_from_cert()
|
||||
|
|
|
|||
|
|
@ -1,83 +1,55 @@
|
|||
# Note: abc requires python 2.6 so we may remove this before
|
||||
# launch. This should help in the creation of other configurators while
|
||||
# we develop though
|
||||
"""Configurator."""
|
||||
|
||||
import abc
|
||||
|
||||
class Configurator(object):
|
||||
"""
|
||||
"""Generic Let's Encrypt configurator.
|
||||
|
||||
Class represents all possible webservers and configuration editors
|
||||
This includes the generic webserver which wont have configuration
|
||||
files at all, but instead create a new process to handle the DVSNI
|
||||
and other challenges.
|
||||
"""
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def __init__(self):
|
||||
return
|
||||
def deploy_cert(self, vhost, cert, key, cert_chain=None):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def deploy_cert(self, vhost, cert, key , cert_chain=None):
|
||||
return
|
||||
|
||||
@abc.abstractmethod
|
||||
def choose_virtual_host(self, name):
|
||||
"""
|
||||
Chooses a virtual host based on a given domain name
|
||||
"""
|
||||
return
|
||||
"""Chooses a virtual host based on a given domain name."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_all_names(self):
|
||||
"""
|
||||
Returns all names found in the Configuration
|
||||
"""
|
||||
return
|
||||
"""Returns all names found in the configuration."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def enable_redirect(self, ssl_vhost):
|
||||
"""
|
||||
Makes all traffic redirect to the given ssl_vhost
|
||||
ie. port 80 => 443
|
||||
"""
|
||||
return
|
||||
"""Redirect all traffic to the given ssl_vhost (port 80 => 443)."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def enable_hsts(self, ssl_vhost):
|
||||
"""
|
||||
Enable HSTS on the given ssl_vhost
|
||||
"""
|
||||
return
|
||||
"""Enable HSTS on the given ssl_vhost."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def enable_ocsp_stapling(self, ssl_vhost):
|
||||
"""
|
||||
Enable OCSP stapling on given ssl_vhost
|
||||
"""
|
||||
return
|
||||
"""Enable OCSP stapling on given ssl_vhost."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_all_certs_keys(self):
|
||||
"""
|
||||
Retrieve all certs and keys set in configuration
|
||||
"""Retrieve all certs and keys set in configuration.
|
||||
|
||||
returns: list of tuples with form [(cert, key, path)]
|
||||
"""
|
||||
return
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def enable_site(self, vhost):
|
||||
"""
|
||||
Enable the site at the given vhost
|
||||
"""
|
||||
return
|
||||
"""Enable the site at the given vhost."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def save(self, 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
|
||||
"""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
|
||||
|
||||
title: string - The title of the save. If a title is given, the
|
||||
configuration will be saved as a new checkpoint
|
||||
|
|
@ -86,49 +58,32 @@ class Configurator(object):
|
|||
temporary: boolean - Indicates whether the changes made will be
|
||||
quickly reversed in the future (challenges)
|
||||
"""
|
||||
return
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def revert_challenge_config(self):
|
||||
"""
|
||||
This function should reload the users original configuration files
|
||||
"""
|
||||
return
|
||||
"""Reload the users original configuration files."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def rollback_checkpoints(self, rollback = 1):
|
||||
"""
|
||||
Revert `rollback` number of configuration checkpoints
|
||||
"""
|
||||
return
|
||||
def rollback_checkpoints(self, rollback=1):
|
||||
"""Revert `rollback` number of configuration checkpoints."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def display_checkpoints(self):
|
||||
"""
|
||||
Display the saved configuration checkpoints
|
||||
"""
|
||||
return
|
||||
"""Display the saved configuration checkpoints."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def config_test(self):
|
||||
"""
|
||||
Make sure the configuration is valid
|
||||
"""
|
||||
return
|
||||
"""Make sure the configuration is valid."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def restart(self):
|
||||
"""
|
||||
Restart or refresh the server content
|
||||
"""
|
||||
return
|
||||
def restart(self, quiet=False):
|
||||
"""Restart or refresh the server content."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def perform(self, chall_type, tup):
|
||||
""" Perform the given challenge"""
|
||||
return
|
||||
"""Perform the given challenge"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def cleanup(self):
|
||||
""" Cleanup configuration changes from challenge """
|
||||
return
|
||||
"""Cleanup configuration changes from challenge."""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -1,59 +1,72 @@
|
|||
import M2Crypto
|
||||
import time, binascii
|
||||
import binascii
|
||||
import hashlib
|
||||
from Crypto.Random import get_random_bytes
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Signature import PKCS1_v1_5
|
||||
from Crypto.Hash import SHA256
|
||||
from M2Crypto import EVP, X509, ASN1
|
||||
import time
|
||||
|
||||
from letsencrypt.client.CONFIG import NONCE_SIZE, RSA_KEY_SIZE
|
||||
from Crypto import Random
|
||||
import Crypto.Hash.SHA256
|
||||
import Crypto.PublicKey.RSA
|
||||
import Crypto.Signature.PKCS1_v1_5
|
||||
|
||||
import M2Crypto
|
||||
|
||||
from letsencrypt.client import CONFIG
|
||||
from letsencrypt.client import le_util
|
||||
|
||||
def b64_cert_to_pem(b64_der_cert):
|
||||
x = M2Crypto.X509.load_cert_der_string(le_util.b64_url_dec(b64_der_cert))
|
||||
return x.as_pem()
|
||||
|
||||
def create_sig(msg, key_file, signer_nonce = None, signer_nonce_len = NONCE_SIZE):
|
||||
def b64_cert_to_pem(b64_der_cert):
|
||||
return M2Crypto.X509.load_cert_der_string(
|
||||
le_util.b64_url_dec(b64_der_cert)).as_pem()
|
||||
|
||||
|
||||
def create_sig(msg, key_file, signer_nonce=None,
|
||||
signer_nonce_len=CONFIG.NONCE_SIZE):
|
||||
# DOES prepend signer_nonce to message
|
||||
# TODO: Change this over to M2Crypto... PKey
|
||||
# Protect against crypto unicode errors... is this sufficient? Do I need to escape?
|
||||
# Protect against crypto unicode errors... is this sufficient?
|
||||
# Do I need to escape?
|
||||
msg = str(msg)
|
||||
key = RSA.importKey(open(key_file).read())
|
||||
key = Crypto.PublicKey.RSA.importKey(open(key_file).read())
|
||||
if signer_nonce is None:
|
||||
signer_nonce = get_random_bytes(signer_nonce_len)
|
||||
h = SHA256.new(signer_nonce + msg)
|
||||
signer = PKCS1_v1_5.new(key)
|
||||
signature = signer.sign(h)
|
||||
signer_nonce = Random.get_random_bytes(signer_nonce_len)
|
||||
hashed = Crypto.Hash.SHA256.new(signer_nonce + msg)
|
||||
signer = Crypto.Signature.PKCS1_v1_5.new(key)
|
||||
signature = signer.sign(hashed)
|
||||
#print "signing:", signer_nonce + msg
|
||||
#print "signature:", signature
|
||||
n, e = key.n, key.e
|
||||
n_bytes = binascii.unhexlify(leading_zeros(hex(n)[2:].replace("L", "")))
|
||||
e_bytes = binascii.unhexlify(leading_zeros(hex(e)[2:].replace("L", "")))
|
||||
n_bytes = binascii.unhexlify(leading_zeros(hex(key.n)[2:].replace("L", "")))
|
||||
e_bytes = binascii.unhexlify(leading_zeros(hex(key.e)[2:].replace("L", "")))
|
||||
n_encoded = le_util.b64_url_enc(n_bytes)
|
||||
e_encoded = le_util.b64_url_enc(e_bytes)
|
||||
signer_nonce_encoded = le_util.b64_url_enc(signer_nonce)
|
||||
sig_encoded = le_util.b64_url_enc(signature)
|
||||
jwk = { "kty": "RSA", "n": n_encoded, "e": e_encoded }
|
||||
signature = { "nonce": signer_nonce_encoded, "alg": "RS256", "jwk": jwk, "sig": sig_encoded }
|
||||
jwk = {"kty": "RSA", "n": n_encoded, "e": e_encoded}
|
||||
signature = {
|
||||
"nonce": signer_nonce_encoded,
|
||||
"alg": "RS256",
|
||||
"jwk": jwk,
|
||||
"sig": sig_encoded
|
||||
}
|
||||
# return json.dumps(signature)
|
||||
return (signature)
|
||||
return signature
|
||||
|
||||
def leading_zeros(s):
|
||||
if len(s) % 2:
|
||||
return "0" + s
|
||||
return s
|
||||
|
||||
def sha256(m):
|
||||
return hashlib.sha256(m).hexdigest()
|
||||
def leading_zeros(arg):
|
||||
if len(arg) % 2:
|
||||
return "0" + arg
|
||||
return arg
|
||||
|
||||
|
||||
def sha256(arg):
|
||||
return hashlib.sha256(arg).hexdigest()
|
||||
|
||||
|
||||
# based on M2Crypto unit test written by Toby Allsopp
|
||||
def make_key(bits=RSA_KEY_SIZE):
|
||||
def make_key(bits=CONFIG.RSA_KEY_SIZE):
|
||||
"""
|
||||
Returns new RSA key in PEM form with specified bits
|
||||
"""
|
||||
#Python Crypto module doesn't produce any stdout
|
||||
key = RSA.generate(bits)
|
||||
key = Crypto.PublicKey.RSA.generate(bits)
|
||||
#rsa = M2Crypto.RSA.gen_key(bits, 65537)
|
||||
#key_pem = rsa.as_pem(cipher=None)
|
||||
#rsa = None # should not be freed here
|
||||
|
|
@ -67,12 +80,12 @@ def make_csr(key_file, domains):
|
|||
"""
|
||||
assert domains, "Must provide one or more hostnames for the CSR."
|
||||
rsa_key = M2Crypto.RSA.load_key(key_file)
|
||||
pk = EVP.PKey()
|
||||
pk.assign_rsa(rsa_key)
|
||||
pubkey = M2Crypto.EVP.PKey()
|
||||
pubkey.assign_rsa(rsa_key)
|
||||
|
||||
x = X509.Request()
|
||||
x.set_pubkey(pk)
|
||||
name = x.get_subject()
|
||||
csr = M2Crypto.X509.Request()
|
||||
csr.set_pubkey(pubkey)
|
||||
name = csr.get_subject()
|
||||
name.C = "US"
|
||||
name.ST = "Michigan"
|
||||
name.L = "Ann Arbor"
|
||||
|
|
@ -80,72 +93,81 @@ def make_csr(key_file, domains):
|
|||
name.OU = "University of Michigan"
|
||||
name.CN = domains[0]
|
||||
|
||||
extstack = X509.X509_Extension_Stack()
|
||||
ext = X509.new_extension('subjectAltName', ", ".join(["DNS:%s" % d for d in domains]))
|
||||
extstack = M2Crypto.X509.X509_Extension_Stack()
|
||||
ext = M2Crypto.X509.new_extension(
|
||||
'subjectAltName', ", ".join(["DNS:%s" % d for d in domains]))
|
||||
|
||||
extstack.push(ext)
|
||||
x.add_extensions(extstack)
|
||||
x.sign(pk,'sha256')
|
||||
assert x.verify(pk)
|
||||
pk2 = x.get_pubkey()
|
||||
assert x.verify(pk2)
|
||||
return x.as_pem(), x.as_der()
|
||||
csr.add_extensions(extstack)
|
||||
csr.sign(pubkey, 'sha256')
|
||||
assert csr.verify(pubkey)
|
||||
pubkey2 = csr.get_pubkey()
|
||||
assert csr.verify(pubkey2)
|
||||
return csr.as_pem(), csr.as_der()
|
||||
|
||||
|
||||
def make_ss_cert(key_file, domains):
|
||||
"""
|
||||
Returns new self-signed cert in PEM form using key_file containing all domains
|
||||
"""Returns new self-signed cert in PEM form.
|
||||
|
||||
Uses key_file and contains all domains.
|
||||
"""
|
||||
assert domains, "Must provide one or more hostnames for the CSR."
|
||||
rsa_key = M2Crypto.RSA.load_key(key_file)
|
||||
pk = EVP.PKey()
|
||||
pk.assign_rsa(rsa_key)
|
||||
pubkey = M2Crypto.EVP.PKey()
|
||||
pubkey.assign_rsa(rsa_key)
|
||||
|
||||
x = X509.X509()
|
||||
x.set_pubkey(pk)
|
||||
x.set_serial_number(1337)
|
||||
x.set_version(2)
|
||||
cert = M2Crypto.X509.X509()
|
||||
cert.set_pubkey(pubkey)
|
||||
cert.set_serial_number(1337)
|
||||
cert.set_version(2)
|
||||
|
||||
t = long(time.time())
|
||||
current = ASN1.ASN1_UTCTIME()
|
||||
current.set_time(t)
|
||||
expire = ASN1.ASN1_UTCTIME()
|
||||
expire.set_time((7 * 24 * 60 * 60) + t)
|
||||
x.set_not_before(current)
|
||||
x.set_not_after(expire)
|
||||
current_ts = long(time.time())
|
||||
current = M2Crypto.ASN1.ASN1_UTCTIME()
|
||||
current.set_time(current_ts)
|
||||
expire = M2Crypto.ASN1.ASN1_UTCTIME()
|
||||
expire.set_time((7 * 24 * 60 * 60) + current_ts)
|
||||
cert.set_not_before(current)
|
||||
cert.set_not_after(expire)
|
||||
|
||||
name = x.get_subject()
|
||||
name = cert.get_subject()
|
||||
name.C = "US"
|
||||
name.ST = "Michigan"
|
||||
name.L = "Ann Arbor"
|
||||
name.O = "University of Michigan and the EFF"
|
||||
name.CN = domains[0]
|
||||
x.set_issuer(x.get_subject())
|
||||
cert.set_issuer(cert.get_subject())
|
||||
|
||||
x.add_ext(X509.new_extension('basicConstraints', 'CA:FALSE'))
|
||||
#x.add_ext(X509.new_extension('extendedKeyUsage', 'TLS Web Server Authentication'))
|
||||
x.add_ext(X509.new_extension('subjectAltName', ", ".join(["DNS:%s" % d for d in domains])))
|
||||
cert.add_ext(M2Crypto.X509.new_extension('basicConstraints', 'CA:FALSE'))
|
||||
#cert.add_ext(M2Crypto.X509.new_extension(
|
||||
# 'extendedKeyUsage', 'TLS Web Server Authentication'))
|
||||
cert.add_ext(M2Crypto.X509.new_extension(
|
||||
'subjectAltName', ", ".join(["DNS:%s" % d for d in domains])))
|
||||
|
||||
x.sign(pk, 'sha256')
|
||||
assert x.verify(pk)
|
||||
assert x.verify()
|
||||
cert.sign(pubkey, 'sha256')
|
||||
assert cert.verify(pubkey)
|
||||
assert cert.verify()
|
||||
#print check_purpose(,0
|
||||
return x.as_pem()
|
||||
return cert.as_pem()
|
||||
|
||||
|
||||
def get_cert_info(filename):
|
||||
d = {}
|
||||
# M2Crypto Library only supports RSA right now
|
||||
x = M2Crypto.X509.load_cert(filename)
|
||||
d["not_before"] = x.get_not_before().get_datetime()
|
||||
d["not_after"] = x.get_not_after().get_datetime()
|
||||
d["subject"] = x.get_subject().as_text()
|
||||
d["cn"] = x.get_subject().CN
|
||||
d["issuer"] = x.get_issuer().as_text()
|
||||
d["fingerprint"] = x.get_fingerprint(md='sha1')
|
||||
try:
|
||||
d["san"] = x.get_ext("subjectAltName").get_value()
|
||||
except:
|
||||
d["san"] = ""
|
||||
cert = M2Crypto.X509.load_cert(filename)
|
||||
|
||||
try:
|
||||
san = cert.get_ext("subjectAltName").get_value()
|
||||
except:
|
||||
san = ""
|
||||
|
||||
return {
|
||||
"not_before": cert.get_not_before().get_datetime(),
|
||||
"not_after": cert.get_not_after().get_datetime(),
|
||||
"subject": cert.get_subject().as_text(),
|
||||
"cn": cert.get_subject().CN,
|
||||
"issuer": cert.get_issuer().as_text(),
|
||||
"fingerprint": cert.get_fingerprint(md='sha1'),
|
||||
"san": san,
|
||||
"serial": cert.get_serial_number(),
|
||||
"pub_key": "RSA " + str(cert.get_pubkey().size() * 8),
|
||||
}
|
||||
|
||||
d["serial"] = x.get_serial_number()
|
||||
d["pub_key"] = "RSA " + str(x.get_pubkey().size() * 8)
|
||||
return d
|
||||
|
|
|
|||
|
|
@ -1,167 +1,141 @@
|
|||
import textwrap
|
||||
|
||||
import dialog
|
||||
|
||||
|
||||
WIDTH = 72
|
||||
HEIGHT = 20
|
||||
|
||||
|
||||
class SingletonD(object):
|
||||
_instance = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if not cls._instance:
|
||||
cls._instance = super(SingletonD, cls).__new__(
|
||||
cls, *args, **kwargs)
|
||||
cls, *args, **kwargs)
|
||||
return cls._instance
|
||||
|
||||
|
||||
class Display(SingletonD):
|
||||
def generic_notification(self, message, width = WIDTH, height = HEIGHT):
|
||||
raise Exception("Error no display defined")
|
||||
def generic_menu(self, message, choices, input_text = "", width = WIDTH, height = HEIGHT):
|
||||
raise Exception("Error no display defined")
|
||||
"""Generic display."""
|
||||
|
||||
def generic_notification(self, message, width=WIDTH, height=HEIGHT):
|
||||
raise NotImplementedError()
|
||||
|
||||
def generic_menu(self, message, choices, input_text="",
|
||||
width=WIDTH, height=HEIGHT):
|
||||
raise NotImplementedError()
|
||||
|
||||
def generic_input(self, message):
|
||||
raise Exception("Error no display defined")
|
||||
def generic_yesno(self, message, yes_label = "Yes", no_label = "No"):
|
||||
raise Exception("Error no display defined")
|
||||
raise NotImplementedError()
|
||||
|
||||
def generic_yesno(self, message, yes_label="Yes", no_label="No"):
|
||||
raise NotImplementedError()
|
||||
|
||||
def filter_names(self, names):
|
||||
raise Exception("Error no display defined")
|
||||
raise NotImplementedError()
|
||||
|
||||
def success_installation(self, domains):
|
||||
raise Exception("Error no display defined")
|
||||
|
||||
def gen_https_names(self, domains):
|
||||
"""
|
||||
Returns a string of the domains formatted nicely with https:// prepended
|
||||
to each
|
||||
"""
|
||||
result = ""
|
||||
if len(domains) > 2:
|
||||
for i in range(len(domains)-1):
|
||||
result = result + "https://" + domains[i] + ", "
|
||||
result = result + "and "
|
||||
if len(domains) == 2:
|
||||
return "https://" + domains[0] + " and https://" + domains[1]
|
||||
if domains:
|
||||
result = result + "https://" + domains[len(domains)-1]
|
||||
|
||||
return result
|
||||
raise NotImplementedError()
|
||||
|
||||
def display_certs(self, certs):
|
||||
raise Exception("Error no display define")
|
||||
raise NotImplementedError()
|
||||
|
||||
def confirm_revocation(self, cert):
|
||||
raise Exception("Error no display defined")
|
||||
|
||||
def cert_info_frame(self, cert):
|
||||
text = "-" * (WIDTH - 4) + "\n"
|
||||
text += self.cert_info_string(cert)
|
||||
text += "-" * (WIDTH - 4)
|
||||
return text
|
||||
|
||||
def cert_info_string(self, cert):
|
||||
text = "Subject: %s\n" % cert["subject"]
|
||||
text += "SAN: %s\n" % cert["san"]
|
||||
text += "Issuer: %s\n" % cert["issuer"]
|
||||
text += "Public Key: %s\n" % cert["pub_key"]
|
||||
text += "Not Before: %s\n" % str(cert["not_before"])
|
||||
text += "Not After: %s\n" % str(cert["not_after"])
|
||||
text += "Serial Number: %s\n" % cert["serial"]
|
||||
text += "SHA1: %s\n" % cert["fingerprint"]
|
||||
text += "Installed: %s\n" % cert["installed"]
|
||||
return text
|
||||
raise NotImplementedError()
|
||||
|
||||
def more_info_cert(self, cert):
|
||||
raise Exception("Error no display defined")
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class NcursesDisplay(Display):
|
||||
import dialog
|
||||
|
||||
def __init__(self):
|
||||
self.d = dialog.Dialog()
|
||||
self.dialog = dialog.Dialog()
|
||||
|
||||
def generic_notification(self, message, w = WIDTH, h = HEIGHT):
|
||||
self.d.msgbox(message, width = w, height = h)
|
||||
def generic_notification(self, message, w=WIDTH, h=HEIGHT):
|
||||
self.dialog.msgbox(message, width=w, height=h)
|
||||
|
||||
def generic_menu(self, message, choices, input_text = "", width = WIDTH,
|
||||
height = HEIGHT):
|
||||
def generic_menu(self, message, choices, input_text="", width=WIDTH,
|
||||
height=HEIGHT):
|
||||
# Can accept either tuples or just the actual choices
|
||||
if choices and isinstance(choices[0], tuple):
|
||||
c, selection = self.d.menu(message, choices = choices,
|
||||
width = WIDTH, height = HEIGHT)
|
||||
return c, str(selection)
|
||||
code, selection = self.dialog.menu(
|
||||
message, choices=choices, width=WIDTH, height=HEIGHT)
|
||||
return code, str(selection)
|
||||
else:
|
||||
choices = [((i + 1), c) for c in choices]
|
||||
code, s = self.d.menu(message, choices = choices,
|
||||
width = WIDTH, height = HEIGHT)
|
||||
choices = list(enumerate(choices, 1))
|
||||
code, tag = self.dialog.menu(
|
||||
message, choices=choices, width=WIDTH, height=HEIGHT)
|
||||
|
||||
return code (int(s) - 1)
|
||||
return code(int(tag) - 1)
|
||||
|
||||
def generic_input(self, message):
|
||||
return self.d.inputbox(message)
|
||||
return self.dialog.inputbox(message)
|
||||
|
||||
def generic_yesno(self, message, yes = "Yes", no = "No"):
|
||||
a = self.d.yesno(message, HEIGHT, WIDTH, yes_label = yes, no_label = no)
|
||||
|
||||
return a == self.d.DIALOG_OK
|
||||
def generic_yesno(self, message, yes="Yes", no="No"):
|
||||
return self.dialog.DIALOG_OK == self.dialog.yesno(
|
||||
message, HEIGHT, WIDTH, yes_label=yes, no_label=no)
|
||||
|
||||
def filter_names(self, names):
|
||||
choices = [(n, "", 0) for n in names]
|
||||
c, names = self.d.checklist("Which names would you like to activate \
|
||||
HTTPS for?", choices=choices)
|
||||
|
||||
return c, [str(s) for s in names]
|
||||
|
||||
code, names = self.dialog.checklist(
|
||||
"Which names would you like to activate HTTPS for?",
|
||||
choices=choices)
|
||||
return code, [str(s) for s in names]
|
||||
|
||||
def success_installation(self, domains):
|
||||
self.d.msgbox("\nCongratulations! You have successfully enabled "
|
||||
+ self.gen_https_names(domains) + "!", width=WIDTH)
|
||||
self.dialog.msgbox(
|
||||
"\nCongratulations! You have successfully enabled "
|
||||
+ gen_https_names(domains) + "!", width=WIDTH)
|
||||
|
||||
def display_certs(self, certs):
|
||||
list_choices = [
|
||||
(str(i+1),
|
||||
"%s | %s | %s" %
|
||||
(str(c["cn"].ljust(WIDTH - 39)),
|
||||
c["not_before"].strftime("%m-%d-%y"),
|
||||
"Installed" if c["installed"] else ""))
|
||||
(str(i+1), "%s | %s | %s" %
|
||||
(str(c["cn"].ljust(WIDTH - 39)),
|
||||
c["not_before"].strftime("%m-%d-%y"),
|
||||
"Installed" if c["installed"] else ""))
|
||||
for i, c in enumerate(certs)]
|
||||
|
||||
code, s = self.d.menu("Which certificates would you like to revoke?",
|
||||
choices = list_choices, help_button=True,
|
||||
help_label="More Info", ok_label="Revoke",
|
||||
width=WIDTH, height=HEIGHT)
|
||||
if not s:
|
||||
s = -1
|
||||
return code, (int(s) - 1)
|
||||
code, tag = self.dialog.menu(
|
||||
"Which certificates would you like to revoke?",
|
||||
choices=list_choices, help_button=True,
|
||||
help_label="More Info", ok_label="Revoke",
|
||||
width=WIDTH, height=HEIGHT)
|
||||
if not tag:
|
||||
tag = -1
|
||||
return code, (int(tag) - 1)
|
||||
|
||||
def confirm_revocation(self, cert):
|
||||
text = "Are you sure you would like to revoke the following \
|
||||
certificate:\n"
|
||||
text += self.cert_info_frame(cert)
|
||||
text = ("Are you sure you would like to revoke the following "
|
||||
"certificate:\n")
|
||||
text += cert_info_frame(cert)
|
||||
text += "This action cannot be reversed!"
|
||||
a = self.d.yesno(text, width=WIDTH, height=HEIGHT)
|
||||
return a == self.d.DIALOG_OK
|
||||
return self.dialog.DIALOG_OK == self.dialog.yesno(
|
||||
text, width=WIDTH, height=HEIGHT)
|
||||
|
||||
def more_info_cert(self, cert):
|
||||
text = "Certificate Information:\n"
|
||||
text += self.cert_info_frame(cert)
|
||||
text += cert_info_frame(cert)
|
||||
print text
|
||||
self.d.msgbox(text, width=WIDTH, height=HEIGHT)
|
||||
self.dialog.msgbox(text, width=WIDTH, height=HEIGHT)
|
||||
|
||||
textwrap = None
|
||||
|
||||
class FileDisplay(Display):
|
||||
global textwrap
|
||||
import textwrap
|
||||
|
||||
def __init__(self, outfile):
|
||||
self.outfile = outfile
|
||||
|
||||
def generic_notification(self, message, width = WIDTH, height = HEIGHT):
|
||||
def generic_notification(self, message, width=WIDTH, height=HEIGHT):
|
||||
side_frame = '-' * (79)
|
||||
wm = textwrap.fill(message, 80)
|
||||
text = "\n%s\n%s\n%s\n" % (side_frame, wm, side_frame)
|
||||
self.outfile.write(text)
|
||||
raw_input("Press Enter to Continue")
|
||||
|
||||
def generic_menu(self, message, choices, input_text = "", width = WIDTH, height = HEIGHT):
|
||||
def generic_menu(self, message, choices, input_text="",
|
||||
width=WIDTH, height=HEIGHT):
|
||||
# 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]
|
||||
|
|
@ -170,13 +144,14 @@ class FileDisplay(Display):
|
|||
side_frame = '-' * (79)
|
||||
self.outfile.write("%s\n" % side_frame)
|
||||
|
||||
for i, c in enumerate(choices):
|
||||
wc = textwrap.fill("%d: %s" % (i + 1, c), 80)
|
||||
self.outfile.write("%s\n" % wc)
|
||||
for i, choice in enumerate(choices, 1):
|
||||
self.outfile.write(textwrap.fill(
|
||||
"%d: %s" % (i, choice), 80) + '\n')
|
||||
|
||||
self.outfile.write("%s\n" % side_frame)
|
||||
|
||||
code, selection = self.__get_valid_int_ans("%s (c to cancel): " % input_text)
|
||||
code, selection = self.__get_valid_int_ans(
|
||||
"%s (c to cancel): " % input_text)
|
||||
|
||||
return code, (selection - 1)
|
||||
|
||||
|
|
@ -188,19 +163,18 @@ class FileDisplay(Display):
|
|||
else:
|
||||
return OK, ans
|
||||
|
||||
def generic_yesno(self, message, yes_label = "Yes", no_label = "No"):
|
||||
def generic_yesno(self, message, yes_label="Yes", no_label="No"):
|
||||
self.outfile.write("\n%s\n" % textwrap.fill(message, 80))
|
||||
ans = raw_input("y/n: ")
|
||||
return ans.startswith('y') or ans.startswith('Y')
|
||||
|
||||
def filter_names(self, names):
|
||||
c, s = self.generic_menu(
|
||||
code, tag = self.generic_menu(
|
||||
"Choose the names would you like to upgrade to HTTPS?",
|
||||
names,
|
||||
"Select the number of the name: ")
|
||||
names, "Select the number of the name: ")
|
||||
|
||||
# Make sure to return a list...
|
||||
return c, [names[s]]
|
||||
return code, [names[tag]]
|
||||
|
||||
def display_certs(self, certs):
|
||||
menu_choices = [(str(i+1), str(c["cn"]) + " - " + c["pub_key"] +
|
||||
|
|
@ -208,12 +182,11 @@ class FileDisplay(Display):
|
|||
for i, c in enumerate(certs)]
|
||||
|
||||
self.outfile.write("Which certificate would you like to revoke?\n")
|
||||
for c in menu_choices:
|
||||
wm = textwrap.fill("%s: %s - %s Signed (UTC): %s\n" %
|
||||
(c[0], c[1], c[2], c[3]))
|
||||
self.outfile.write(wm)
|
||||
for choice in menu_choices:
|
||||
self.outfile.write(textwrap.fill(
|
||||
"%s: %s - %s Signed (UTC): %s\n" % choice[:4]))
|
||||
|
||||
return (self.__get_valid_int_ans("Revoke Number (c to cancel): ") - 1)
|
||||
return self.__get_valid_int_ans("Revoke Number (c to cancel): ") - 1
|
||||
|
||||
def __get_valid_int_ans(self, input_string):
|
||||
valid_ans = False
|
||||
|
|
@ -229,7 +202,7 @@ class FileDisplay(Display):
|
|||
else:
|
||||
try:
|
||||
selection = int(ans)
|
||||
#TODO add check to make sure it is liess than max
|
||||
# TODO add check to make sure it is liess than max
|
||||
if selection < 0:
|
||||
self.outfile.write(e_msg)
|
||||
continue
|
||||
|
|
@ -240,25 +213,24 @@ class FileDisplay(Display):
|
|||
|
||||
return code, selection
|
||||
|
||||
|
||||
def success_installation(self, domains):
|
||||
s_f = '*' * (79)
|
||||
wm = textwrap.fill(("Congratulations! You have successfully " +
|
||||
"enabled %s!") % self.gen_https_names(domains))
|
||||
"enabled %s!") % gen_https_names(domains))
|
||||
msg = "%s\n%s\n%s\n"
|
||||
self.outfile.write(msg % (s_f, wm, s_f))
|
||||
|
||||
def confirm_revocation(self, cert):
|
||||
self.outfile.write("Are you sure you would like to revoke \
|
||||
the following certificate:\n")
|
||||
self.outfile.write(self.cert_info_frame(cert))
|
||||
self.outfile("This action cannot be reversed!\n");
|
||||
self.outfile.write("Are you sure you would like to revoke "
|
||||
"the following certificate:\n")
|
||||
self.outfile.write(cert_info_frame(cert))
|
||||
self.outfile("This action cannot be reversed!\n")
|
||||
ans = raw_input("y/n")
|
||||
return ans.startswith('y') or ans.startswith('Y')
|
||||
|
||||
def more_info_cert(self, cert):
|
||||
self.outfile.write("\nCertificate Information:\n")
|
||||
self.outfile.write(self.cert_info_frame(cert))
|
||||
self.outfile.write(cert_info_frame(cert))
|
||||
|
||||
display = None
|
||||
OK = "ok"
|
||||
|
|
@ -266,38 +238,77 @@ CANCEL = "cancel"
|
|||
HELP = "help"
|
||||
|
||||
|
||||
|
||||
def setDisplay(display_inst):
|
||||
def set_display(display_inst):
|
||||
global display
|
||||
display = display_inst
|
||||
|
||||
def generic_notification(message, width = WIDTH, height = HEIGHT):
|
||||
|
||||
def generic_notification(message, width=WIDTH, height=HEIGHT):
|
||||
display.generic_notification(message, width, height)
|
||||
|
||||
def generic_menu(message, choices, input_text = "", width = WIDTH, height = HEIGHT):
|
||||
|
||||
def generic_menu(message, choices, input_text="", width=WIDTH, height=HEIGHT):
|
||||
return display.generic_menu(message, choices, input_text, width, height)
|
||||
|
||||
|
||||
def generic_input(message):
|
||||
return display.generic_message(message)
|
||||
|
||||
def generic_yesno(message, yes_label = "Yes", no_label = "No"):
|
||||
|
||||
def generic_yesno(message, yes_label="Yes", no_label="No"):
|
||||
return display.generic_yesno(message, yes_label, no_label)
|
||||
|
||||
|
||||
def filter_names(names):
|
||||
return display.filter_names(names)
|
||||
|
||||
|
||||
def display_certs(certs):
|
||||
return display.display_certs(certs)
|
||||
|
||||
|
||||
def cert_info_frame(cert):
|
||||
text = "-" * (WIDTH - 4) + "\n"
|
||||
text += cert_info_string(cert)
|
||||
text += "-" * (WIDTH - 4)
|
||||
return text
|
||||
|
||||
|
||||
def cert_info_string(cert):
|
||||
return display.cert_info_string(cert)
|
||||
text = "Subject: %s\n" % cert["subject"]
|
||||
text += "SAN: %s\n" % cert["san"]
|
||||
text += "Issuer: %s\n" % cert["issuer"]
|
||||
text += "Public Key: %s\n" % cert["pub_key"]
|
||||
text += "Not Before: %s\n" % str(cert["not_before"])
|
||||
text += "Not After: %s\n" % str(cert["not_after"])
|
||||
text += "Serial Number: %s\n" % cert["serial"]
|
||||
text += "SHA1: %s\n" % cert["fingerprint"]
|
||||
text += "Installed: %s\n" % cert["installed"]
|
||||
return text
|
||||
|
||||
|
||||
def gen_https_names(domains):
|
||||
return display.gen_https_names(domains)
|
||||
"""Returns a string of the https domains.
|
||||
|
||||
Domains are formatted nicely with https:// prepended to each.
|
||||
"""
|
||||
result = ""
|
||||
if len(domains) > 2:
|
||||
for i in range(len(domains)-1):
|
||||
result = result + "https://" + domains[i] + ", "
|
||||
result = result + "and "
|
||||
if len(domains) == 2:
|
||||
return "https://" + domains[0] + " and https://" + domains[1]
|
||||
if domains:
|
||||
result = result + "https://" + domains[len(domains)-1]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def success_installation(domains):
|
||||
return display.success_installation(domains)
|
||||
|
||||
|
||||
def redirect_by_default():
|
||||
choices = [
|
||||
("Easy", "Allow both HTTP and HTTPS access to these sites"),
|
||||
|
|
@ -307,17 +318,18 @@ def redirect_by_default():
|
|||
"is required or optional.",
|
||||
choices,
|
||||
"Please enter the appropriate number",
|
||||
width = WIDTH)
|
||||
width=WIDTH)
|
||||
|
||||
if result[0] != OK:
|
||||
return False
|
||||
|
||||
# different answer for each type of display
|
||||
return (str(result[1]) == "Secure" or result[1] == 1)
|
||||
return str(result[1]) == "Secure" or result[1] == 1
|
||||
|
||||
|
||||
def confirm_revocation(cert):
|
||||
return display.confirm_revocation(cert)
|
||||
|
||||
|
||||
def more_info_cert(cert):
|
||||
return display.more_info_cert(cert)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
from letsencrypt.client.challenge import Challenge
|
||||
import textwrap
|
||||
|
||||
import dialog
|
||||
|
||||
from letsencrypt.client import challenge
|
||||
|
||||
|
||||
###########################################################
|
||||
# Interactive challenge displays the string sent by the CA
|
||||
# formatted to fit on the screen of the client
|
||||
|
|
@ -8,7 +12,7 @@ import textwrap
|
|||
# client should continue the letsencrypt process
|
||||
###########################################################
|
||||
|
||||
class Interactive_Challenge(Challenge):
|
||||
class Interactive_Challenge(challenge.Challenge):
|
||||
BOX_SIZE = 70
|
||||
|
||||
def __init__(self, string):
|
||||
|
|
@ -16,16 +20,16 @@ class Interactive_Challenge(Challenge):
|
|||
|
||||
def perform(self, quiet=True):
|
||||
if quiet:
|
||||
dialog.Dialog().msgbox(get_display_string(), width=BOX_SIZE)
|
||||
dialog.Dialog().msgbox(self.get_display_string(), width=self.BOX_SIZE)
|
||||
else:
|
||||
print get_display_string()
|
||||
print self.get_display_string()
|
||||
raw_input('')
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def get_display_string(self):
|
||||
return textwrap.fill(self.string, width=BOX_SIZE) + "\n\nPlease Press Enter to Continue"
|
||||
return textwrap.fill(self.string, width=self.BOX_SIZE) + "\n\nPlease Press Enter to Continue"
|
||||
|
||||
def formatted_reasons(self):
|
||||
return "\n\t* %s\n", self.reason
|
||||
# def formatted_reasons(self):
|
||||
# return "\n\t* %s\n", self.reason
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
# This file will contain functions useful for all Letsencrypt Classes
|
||||
import errno
|
||||
import stat
|
||||
import os, pwd, grp
|
||||
import time
|
||||
"""Utilities for all Let's Encrypt."""
|
||||
import base64
|
||||
from letsencrypt.client import logger
|
||||
#import logger
|
||||
import grp
|
||||
import errno
|
||||
import os
|
||||
import pwd
|
||||
import stat
|
||||
import sys
|
||||
|
||||
from letsencrypt.client import logger
|
||||
|
||||
|
||||
def make_or_verify_dir(directory, permissions=0755, uid=0):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import logger
|
||||
import textwrap
|
||||
import time
|
||||
|
||||
import dialog
|
||||
|
||||
from letsencrypt.client import display
|
||||
|
||||
|
||||
class Singleton(object):
|
||||
_instance = None
|
||||
def __new__(cls, *args, **kwargs):
|
||||
|
|
@ -36,11 +42,8 @@ class Logger(Singleton):
|
|||
t = time.time()
|
||||
return time.strftime("%b %d %Y %H:%M:%S", time.localtime(t)) + ('%.03f' % (t - int(t)))[1:]
|
||||
|
||||
textwrap = None
|
||||
|
||||
class FileLogger(Logger):
|
||||
global textwrap
|
||||
import textwrap
|
||||
|
||||
def __init__(self, outfile):
|
||||
self.outfile = outfile
|
||||
|
|
@ -51,7 +54,7 @@ class FileLogger(Logger):
|
|||
wm = textwrap.fill(msg, 80)
|
||||
self.outfile.write("%s\n" % wm)
|
||||
|
||||
import dialog
|
||||
|
||||
class NcursesLogger(Logger):
|
||||
|
||||
def __init__(self,
|
||||
|
|
@ -143,28 +146,27 @@ def none(data):
|
|||
|
||||
if __name__ == "__main__":
|
||||
# Unit test/example usage:
|
||||
import logger
|
||||
|
||||
# Set the logging type you want to use (stdout logging):
|
||||
#logger.setLogger(FileLogger(sys.stdout))
|
||||
logger.setLogger(NcursesLogger())
|
||||
setLogger(NcursesLogger())
|
||||
|
||||
# Set the most verbose you want to log (TRACE, DEBUG, INFO, WARN, ERROR, FATAL, NONE)
|
||||
logger.setLogLevel(logger.TRACE)
|
||||
setLogLevel(logger.TRACE)
|
||||
|
||||
# Log a message:
|
||||
#logger.log(logger.INFO, "logger!")
|
||||
|
||||
time.sleep(0.01)
|
||||
logger.info("This is a long line, it's pretty long, butitalso hasbig wordsthat areprobably hardtobreak oninan easywayforthe ncurseslib, sowhatdoes itdo then?")
|
||||
logger.info("aa " + "a"*70 + "B")
|
||||
info("This is a long line, it's pretty long, butitalso hasbig wordsthat areprobably hardtobreak oninan easywayforthe ncurseslib, sowhatdoes itdo then?")
|
||||
info("aa " + "a"*70 + "B")
|
||||
|
||||
for i in range(20):
|
||||
logger.info("iteration #%d/20" % i)
|
||||
info("iteration #%d/20" % i)
|
||||
time.sleep(0.3)
|
||||
|
||||
|
||||
# Alternatively, use
|
||||
logger.error("errrrr")
|
||||
error("errrrr")
|
||||
|
||||
logger.trace("some trace data: %d - %f - %s" % (5, 8.3, 'cows'))
|
||||
trace("some trace data: %d - %f - %s" % (5, 8.3, 'cows'))
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
from letsencrypt.client.CONFIG import SERVER_ROOT, BACKUP_DIR
|
||||
from letsencrypt.client.CONFIG import REWRITE_HTTPS_ARGS, CONFIG_DIR, WORK_DIR
|
||||
from letsencrypt.client.CONFIG import TEMP_CHECKPOINT_DIR, IN_PROGRESS_DIR
|
||||
from letsencrypt.client.CONFIG import OPTIONS_SSL_CONF, LE_VHOST_EXT
|
||||
from letsencrypt.client import logger, le_util, configurator
|
||||
from letsencrypt.client import CONFIG
|
||||
from letsencrypt.client import augeas_configurator
|
||||
|
||||
|
||||
# This might be helpful... but feel free to use whatever you want
|
||||
|
|
@ -21,9 +18,10 @@ from letsencrypt.client import logger, le_util, configurator
|
|||
# def add_name(self, name):
|
||||
# self.names.append(name)
|
||||
|
||||
class NginxConfigurator(AugeasConfigurator):
|
||||
|
||||
def __init__(self, server_root=SERVER_ROOT):
|
||||
class NginxConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
|
||||
def __init__(self, server_root=CONFIG.SERVER_ROOT):
|
||||
super(NginxConfigurator, self).__init__()
|
||||
self.server_root = server_root
|
||||
|
||||
# See if any temporary changes need to be recovered
|
||||
|
|
@ -34,27 +32,16 @@ class NginxConfigurator(AugeasConfigurator):
|
|||
# Check for errors in parsing files with Augeas
|
||||
# TODO - insert nginx lens info here???
|
||||
#self.check_parsing_errors("httpd.aug")
|
||||
|
||||
|
||||
def deploy_cert(self, vhost, cert, key, cert_chain=None):
|
||||
"""
|
||||
Deploy cert in nginx
|
||||
"""
|
||||
return
|
||||
"""Deploy cert in nginx"""
|
||||
|
||||
def choose_virtual_host(self, name):
|
||||
"""
|
||||
Chooses a virtual host based on the given domain name
|
||||
"""
|
||||
return None
|
||||
"""Chooses a virtual host based on the given domain name"""
|
||||
|
||||
def get_all_names(self):
|
||||
"""
|
||||
Returns all names found in the nginx configuration
|
||||
"""
|
||||
all_names = set()
|
||||
|
||||
return all_names
|
||||
"""Returns all names found in the nginx configuration"""
|
||||
return set()
|
||||
|
||||
# Might be helpful... I know nothing about nginx lens
|
||||
# def get_include_path(self, cur_dir, arg):
|
||||
|
|
@ -75,7 +62,7 @@ class NginxConfigurator(AugeasConfigurator):
|
|||
# # would create a race condition between the check and this input
|
||||
|
||||
# # TODO: Fix this
|
||||
# # Check to make sure only expected characters are used <- maybe remove
|
||||
# # Check to make sure only expected characters are used, maybe remove
|
||||
# # validChars = re.compile("[a-zA-Z0-9.*?_-/]*")
|
||||
# # matchObj = validChars.match(arg)
|
||||
# # if matchObj.group() != arg:
|
||||
|
|
@ -90,7 +77,8 @@ class NginxConfigurator(AugeasConfigurator):
|
|||
# arg = self.server_root + arg[5:]
|
||||
# # TODO: Test if Apache allows ../ or ~/ for Includes
|
||||
|
||||
# # Attempts to add a transform to the file if one does not already exist
|
||||
# # Attempts to add a transform to the file if one does not already
|
||||
# # exist
|
||||
# self.parse_file(arg)
|
||||
|
||||
# # Argument represents an fnmatch regular expression, convert it
|
||||
|
|
@ -103,8 +91,9 @@ class NginxConfigurator(AugeasConfigurator):
|
|||
# # * and ? are the two special fnmatch characters
|
||||
# if "*" in split or "?" in split:
|
||||
# # Turn it into a augeas regex
|
||||
# # TODO: Can this instead be an augeas glob instead of regex
|
||||
# splitArg[idx] = "* [label()=~regexp('%s')]" % self.fnmatch_to_re(split)
|
||||
# # TODO: Can this be an augeas glob instead of regex
|
||||
# splitArg[idx] = ("* [label()=~regexp('%s')]" %
|
||||
# self.fnmatch_to_re(split)
|
||||
# # Reassemble the argument
|
||||
# arg = "/".join(splitArg)
|
||||
|
||||
|
|
@ -113,7 +102,6 @@ class NginxConfigurator(AugeasConfigurator):
|
|||
# return "/files" + arg[:len(arg)-1]
|
||||
# return "/files"+arg
|
||||
|
||||
|
||||
def enable_redirect(self, ssl_vhost):
|
||||
"""
|
||||
Adds Redirect directive to the port 80 equivalent of ssl_vhost
|
||||
|
|
@ -123,7 +111,6 @@ class NginxConfigurator(AugeasConfigurator):
|
|||
"""
|
||||
return
|
||||
|
||||
|
||||
def enable_ocsp_stapling(self, ssl_vhost):
|
||||
return False
|
||||
|
||||
|
|
@ -159,9 +146,7 @@ class NginxConfigurator(AugeasConfigurator):
|
|||
# return avail_fp
|
||||
|
||||
def enable_site(self, vhost):
|
||||
"""
|
||||
Enables an available site, Apache restart required
|
||||
"""
|
||||
"""Enables an available site, Apache restart required"""
|
||||
return False
|
||||
|
||||
# Might be a usefule reference
|
||||
|
|
@ -172,31 +157,29 @@ class NginxConfigurator(AugeasConfigurator):
|
|||
# """
|
||||
# # Test if augeas included file for Httpd.lens
|
||||
# # Note: This works for augeas globs, ie. *.conf
|
||||
# incTest = self.aug.match("/augeas/load/Httpd/incl [. ='" + file_path + "']")
|
||||
# incTest = self.aug.match(
|
||||
# "/augeas/load/Httpd/incl [. ='" + file_path + "']")
|
||||
# if not incTest:
|
||||
# # Load up files
|
||||
# #self.httpd_incl.append(file_path)
|
||||
# #self.aug.add_transform("Httpd.lns", self.httpd_incl, None, self.httpd_excl)
|
||||
# #self.aug.add_transform(
|
||||
# # "Httpd.lns", self.httpd_incl, None, self.httpd_excl)
|
||||
# self.__add_httpd_transform(file_path)
|
||||
# self.aug.load()
|
||||
|
||||
|
||||
# Helpful reference?
|
||||
# def verify_setup(self):
|
||||
# '''
|
||||
# Make sure that files/directories are setup with appropriate permissions
|
||||
# Aim for defensive coding... make sure all input files
|
||||
# """
|
||||
# Make sure that files/directories are setup with appropriate
|
||||
# permissions. Aim for defensive coding... make sure all input files
|
||||
# have permissions of root
|
||||
# '''
|
||||
# le_util.make_or_verify_dir(CONFIG_DIR, 0755)
|
||||
# le_util.make_or_verify_dir(WORK_DIR, 0755)
|
||||
# le_util.make_or_verify_dir(BACKUP_DIR, 0755)
|
||||
|
||||
# """
|
||||
# le_util.make_or_verify_dir(CONFIG.CONFIG_DIR, 0755)
|
||||
# le_util.make_or_verify_dir(CONFIG.WORK_DIR, 0755)
|
||||
# le_util.make_or_verify_dir(CONFIG.BACKUP_DIR, 0755)
|
||||
|
||||
def restart(self, quiet=False):
|
||||
"""
|
||||
Restarts nginx server
|
||||
"""
|
||||
return
|
||||
"""Restarts nginx server"""
|
||||
|
||||
# May be of use?
|
||||
# def __add_httpd_transform(self, incl):
|
||||
|
|
@ -209,12 +192,10 @@ class NginxConfigurator(AugeasConfigurator):
|
|||
# self.aug.set("/augeas/load/Httpd/incl[last()]", incl)
|
||||
|
||||
def config_test(self):
|
||||
""" Check Configuration """
|
||||
"""Check Configuration"""
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import requests
|
||||
|
||||
from letsencrypt.client.challenge import Challenge
|
||||
from letsencrypt.client import logger
|
||||
from letsencrypt.client.CONFIG import RECOVERY_TOKEN_EXT
|
||||
import dialog
|
||||
import requests
|
||||
import time
|
||||
|
||||
class RecoveryContact(Challenge):
|
||||
from letsencrypt.client import challenge
|
||||
|
||||
|
||||
class RecoveryContact(challenge.Challenge):
|
||||
|
||||
def __init__(self, activationURL = "", successURL = "", contact = "", poll_delay = 3):
|
||||
self.token = ""
|
||||
|
|
@ -27,7 +27,7 @@ class RecoveryContact(Challenge):
|
|||
|
||||
else:
|
||||
print self.get_display_string()
|
||||
if successURL:
|
||||
if self.successURL:
|
||||
return self.poll(10, quiet)
|
||||
else:
|
||||
self.token = raw_input("Enter the recovery token:")
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
from letsencrypt.client.challenge import Challenge
|
||||
import dialog
|
||||
|
||||
class RecoveryToken(Challenge):
|
||||
from letsencrypt.client import challenge
|
||||
|
||||
def __init__(self):
|
||||
|
||||
class RecoveryToken(challenge.Challenge):
|
||||
|
||||
def __init__(self, configurator):
|
||||
super(RecoveryToken, self).__init__(configurator)
|
||||
self.token = ""
|
||||
|
||||
def perform(self, quiet = True):
|
||||
|
||||
cancel, self.token = dialog.generic_input("Please Input Recovery Token: ")
|
||||
def perform(self, quiet=True):
|
||||
cancel, self.token = dialog.generic_input(
|
||||
"Please Input Recovery Token: ")
|
||||
if cancel == 1:
|
||||
return False
|
||||
|
||||
|
|
@ -17,4 +21,4 @@ class RecoveryToken(Challenge):
|
|||
pass
|
||||
|
||||
def generate_response(self):
|
||||
return {"type":"recoveryToken", "token":self.token}
|
||||
return {"type": "recoveryToken", "token": self.token}
|
||||
|
|
|
|||
0
letsencrypt/client/setup.sh
Executable file → Normal file
0
letsencrypt/client/setup.sh
Executable file → Normal file
|
|
@ -1,12 +1,14 @@
|
|||
class Validator(object):
|
||||
"""
|
||||
This Class will contain an API to validate configurations.
|
||||
"""
|
||||
def redirect(name):
|
||||
return
|
||||
def ocsp_stapling(name):
|
||||
return
|
||||
def https(names):
|
||||
return
|
||||
def hsts(name):
|
||||
return
|
||||
"""Configuration validator."""
|
||||
|
||||
def redirect(self, name):
|
||||
raise NotImplementedError()
|
||||
|
||||
def ocsp_stapling(self, name):
|
||||
raise NotImplementedError()
|
||||
|
||||
def https(self, names):
|
||||
raise NotImplementedError()
|
||||
|
||||
def hsts(self, name):
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# This file parses the command line and calls the appropriate functions
|
||||
|
||||
"""Parse command line and call the appropriate functions."""
|
||||
import getopt
|
||||
import os
|
||||
import sys
|
||||
|
||||
from letsencrypt.client import apache_configurator
|
||||
from letsencrypt.client import CONFIG
|
||||
from letsencrypt.client import client
|
||||
from letsencrypt.client import display
|
||||
from letsencrypt.client.CONFIG import ACME_SERVER
|
||||
from letsencrypt.client import logger
|
||||
|
||||
|
||||
def main():
|
||||
# Check to make sure user is root
|
||||
|
|
@ -49,7 +50,6 @@ def main():
|
|||
elif o == "--server":
|
||||
server = a
|
||||
elif o == "--rollback":
|
||||
from letsencrypt.client import apache_configurator, logger
|
||||
logger.setLogger(logger.FileLogger(sys.stdout))
|
||||
logger.setLogLevel(logger.INFO)
|
||||
config = apache_configurator.ApacheConfigurator()
|
||||
|
|
@ -57,7 +57,6 @@ def main():
|
|||
config.restart()
|
||||
sys.exit(0)
|
||||
elif o == "--view-checkpoints":
|
||||
from letsencrypt.client import apache_configurator, logger
|
||||
logger.setLogger(logger.FileLogger(sys.stdout))
|
||||
logger.setLogLevel(logger.INFO)
|
||||
config = apache_configurator.ApacheConfigurator()
|
||||
|
|
@ -79,12 +78,12 @@ def main():
|
|||
continue
|
||||
|
||||
if curses:
|
||||
display.setDisplay(display.NcursesDisplay())
|
||||
display.set_display(display.NcursesDisplay())
|
||||
else:
|
||||
display.setDisplay(display.FileDisplay(sys.stdout))
|
||||
display.set_display(display.FileDisplay(sys.stdout))
|
||||
|
||||
if not server:
|
||||
server = ACME_SERVER
|
||||
server = CONFIG.ACME_SERVER
|
||||
|
||||
c = client.Client(server, csr, privkey, curses)
|
||||
if flag_revoke:
|
||||
|
|
|
|||
Loading…
Reference in a new issue