def init_save_csr(privkey, names, path): """Initialize a CSR with the given private key. :param privkey: Key to include in the CSR :type privkey: :class:`certbot.util.Key` :param set names: `str` names to include in the CSR :param str path: Certificate save directory. :returns: CSR :rtype: :class:`certbot.util.CSR` """ config = zope.component.getUtility(interfaces.IConfig) csr_pem = acme_crypto_util.make_csr( privkey.pem, names, must_staple=config.must_staple) # Save CSR util.make_or_verify_dir(path, 0o755, os.geteuid(), config.strict_permissions) csr_f, csr_filename = util.unique_file( os.path.join(path, "csr-certbot.pem"), 0o644, "wb") with csr_f: csr_f.write(csr_pem) logger.debug("Creating CSR: %s", csr_filename) return util.CSR(csr_filename, csr_pem, "pem")
def init_save_key(key_size, key_dir, keyname="key-certbot.pem"): """Initializes and saves a privkey. Inits key and saves it in PEM format on the filesystem. .. note:: keyname is the attempted filename, it may be different if a file already exists at the path. :param int key_size: RSA key size in bits :param str key_dir: Key save directory. :param str keyname: Filename of key :returns: Key :rtype: :class:`certbot.util.Key` :raises ValueError: If unable to generate the key given key_size. """ try: key_pem = make_key(key_size) except ValueError as err: logger.exception(err) raise err config = zope.component.getUtility(interfaces.IConfig) # Save file util.make_or_verify_dir(key_dir, 0o700, os.geteuid(), config.strict_permissions) key_f, key_path = util.unique_file( os.path.join(key_dir, keyname), 0o600, "wb") with key_f: key_f.write(key_pem) logger.debug("Generating key (%d bits): %s", key_size, key_path) return util.Key(key_path, key_pem)
def _save(self, account, acme, regr_only): account_dir_path = self._account_dir_path(account.id) util.make_or_verify_dir(account_dir_path, 0o700, misc.os_geteuid(), self.config.strict_permissions) try: with open(self._regr_path(account_dir_path), "w") as regr_file: regr = account.regr # If we have a value for new-authz, save it for forwards # compatibility with older versions of Certbot. If we don't # have a value for new-authz, this is an ACMEv2 directory where # an older version of Certbot won't work anyway. if hasattr(acme.directory, "new-authz"): regr = RegistrationResourceWithNewAuthzrURI( new_authzr_uri=acme.directory.new_authz, body={}, uri=regr.uri) else: regr = messages.RegistrationResource( body={}, uri=regr.uri) regr_file.write(regr.json_dumps()) if not regr_only: with util.safe_open(self._key_path(account_dir_path), "w", chmod=0o400) as key_file: key_file.write(account.key.json_dumps()) with open(self._metadata_path( account_dir_path), "w") as metadata_file: metadata_file.write(account.meta.json_dumps()) except IOError as error: raise errors.AccountStorageError(error)
def _find_duplicative_certs(config, domains): """Find existing certs that duplicate the request.""" identical_names_cert, subset_names_cert = None, None cli_config = configuration.RenewerConfiguration(config) configs_dir = cli_config.renewal_configs_dir # Verify the directory is there util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid()) for renewal_file in renewal.renewal_conf_files(cli_config): try: candidate_lineage = storage.RenewableCert(renewal_file, cli_config) except (errors.CertStorageError, IOError): logger.warning("Renewal conf file %s is broken. Skipping.", renewal_file) logger.debug("Traceback was:\n%s", traceback.format_exc()) continue # TODO: Handle these differently depending on whether they are # expired or still valid? candidate_names = set(candidate_lineage.names()) if candidate_names == set(domains): identical_names_cert = candidate_lineage elif candidate_names.issubset(set(domains)): # This logic finds and returns the largest subset-names cert # in the case where there are several available. if subset_names_cert is None: subset_names_cert = candidate_lineage elif len(candidate_names) > len(subset_names_cert.names()): subset_names_cert = candidate_lineage return identical_names_cert, subset_names_cert
def _search_lineages(cli_config, func, initial_rv, *args): """Iterate func over unbroken lineages, allowing custom return conditions. Allows flexible customization of return values, including multiple return values and complex checks. :param `configuration.NamespaceConfig` cli_config: parsed command line arguments :param function func: function used while searching over lineages :param initial_rv: initial return value of the function (any type) :returns: Whatever was specified by `func` if a match is found. """ configs_dir = cli_config.renewal_configs_dir # Verify the directory is there util.make_or_verify_dir(configs_dir, mode=0o755, uid=misc.os_geteuid()) rv = initial_rv for renewal_file in storage.renewal_conf_files(cli_config): try: candidate_lineage = storage.RenewableCert(renewal_file, cli_config) except (errors.CertStorageError, IOError): logger.debug("Renewal conf file %s is broken. Skipping.", renewal_file) logger.debug("Traceback was:\n%s", traceback.format_exc()) continue rv = func(candidate_lineage, rv, *args) return rv
def _get_cp_dir(self, temporary): """Return the proper reverter directory.""" if temporary: cp_dir = self.config.temp_checkpoint_dir else: cp_dir = self.config.in_progress_dir util.make_or_verify_dir(cp_dir, constants.CONFIG_DIRS_MODE, os.geteuid(), self.config.strict_permissions) return cp_dir
def lineage_for_certname(cli_config, certname): """Find a lineage object with name certname.""" configs_dir = cli_config.renewal_configs_dir # Verify the directory is there util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid()) renewal_file = storage.renewal_file_for_certname(cli_config, certname) try: return storage.RenewableCert(renewal_file, cli_config) except (errors.CertStorageError, IOError): logger.debug("Renewal conf file %s is broken.", renewal_file) logger.debug("Traceback was:\n%s", traceback.format_exc()) return None
def save(self, account): account_dir_path = self._account_dir_path(account.id) util.make_or_verify_dir(account_dir_path, 0o700, os.geteuid(), self.config.strict_permissions) try: with open(self._regr_path(account_dir_path), "w") as regr_file: regr_file.write(account.regr.json_dumps()) with util.safe_open(self._key_path(account_dir_path), "w", chmod=0o400) as key_file: key_file.write(account.key.json_dumps()) with open(self._metadata_path(account_dir_path), "w") as metadata_file: metadata_file.write(account.meta.json_dumps()) except IOError as error: raise errors.AccountStorageError(error)
def save_certificate(self, certr, chain_cert, cert_path, chain_path, fullchain_path): """Saves the certificate received from the ACME server. :param certr: ACME "certificate" resource. :type certr: :class:`acme.messages.Certificate` :param list chain_cert: :param str cert_path: Candidate path to a certificate. :param str chain_path: Candidate path to a certificate chain. :param str fullchain_path: Candidate path to a full cert chain. :returns: cert_path, chain_path, and fullchain_path as absolute paths to the actual files :rtype: `tuple` of `str` :raises IOError: If unable to find room to write the cert files """ for path in cert_path, chain_path, fullchain_path: util.make_or_verify_dir( os.path.dirname(path), 0o755, os.geteuid(), self.config.strict_permissions) cert_pem = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped) cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path) try: cert_file.write(cert_pem) finally: cert_file.close() logger.info("Server issued certificate; certificate written to %s", abs_cert_path) if not chain_cert: return abs_cert_path, None, None else: chain_pem = crypto_util.dump_pyopenssl_chain(chain_cert) chain_file, abs_chain_path =\ _open_pem_file('chain_path', chain_path) fullchain_file, abs_fullchain_path =\ _open_pem_file('fullchain_path', fullchain_path) _save_chain(chain_pem, chain_file) _save_chain(cert_pem + chain_pem, fullchain_file) return abs_cert_path, abs_chain_path, abs_fullchain_path
def make_or_verify_core_dir(directory, mode, uid, strict): """Make sure directory exists with proper permissions. :param str directory: Path to a directory. :param int mode: Directory mode. :param int uid: Directory owner. :param bool strict: require directory to be owned by current user :raises .errors.Error: if the directory cannot be made or verified """ try: util.make_or_verify_dir(directory, mode, uid, strict) except OSError as error: raise errors.Error(_PERM_ERR_FMT.format(error))
def main(cli_args=sys.argv[1:]): """Command line argument parsing and main script execution.""" sys.excepthook = functools.partial(_handle_exception, config=None) plugins = plugins_disco.PluginsRegistry.find_all() # note: arg parser internally handles --help (and exits afterwards) args = cli.prepare_and_parse_args(plugins, cli_args) config = configuration.NamespaceConfig(args) zope.component.provideUtility(config) # Setup logging ASAP, otherwise "No handlers could be found for # logger ..." TODO: this should be done before plugins discovery for directory in config.config_dir, config.work_dir: util.make_or_verify_dir( directory, constants.CONFIG_DIRS_MODE, os.geteuid(), "--strict-permissions" in cli_args) # TODO: logs might contain sensitive data such as contents of the # private key! #525 util.make_or_verify_dir( config.logs_dir, 0o700, os.geteuid(), "--strict-permissions" in cli_args) setup_logging(config, _cli_log_handler, logfile='letsencrypt.log') cli.possible_deprecation_warning(config) logger.debug("certbot version: %s", certbot.__version__) # do not log `config`, as it contains sensitive data (e.g. revoke --key)! logger.debug("Arguments: %r", cli_args) logger.debug("Discovered plugins: %r", plugins) sys.excepthook = functools.partial(_handle_exception, config=config) # Displayer if config.quiet: config.noninteractive_mode = True displayer = display_util.NoninteractiveDisplay(open(os.devnull, "w")) elif config.noninteractive_mode: displayer = display_util.NoninteractiveDisplay(sys.stdout) elif config.text_mode: displayer = display_util.FileDisplay(sys.stdout) else: displayer = display_util.NcursesDisplay() zope.component.provideUtility(displayer) # Reporter report = reporter.Reporter(config) zope.component.provideUtility(report) atexit.register(report.atexit_print_messages) return config.func(config, plugins)
def _add_to_checkpoint_dir(self, cp_dir, save_files, save_notes): """Add save files to checkpoint directory. :param str cp_dir: Checkpoint directory filepath :param set save_files: set of files to save :param str save_notes: notes about changes made during the save :raises IOError: if unable to open cp_dir + FILEPATHS file :raises .ReverterError: if unable to add checkpoint """ util.make_or_verify_dir( cp_dir, constants.CONFIG_DIRS_MODE, os.geteuid(), self.config.strict_permissions) op_fd, existing_filepaths = self._read_and_append( os.path.join(cp_dir, "FILEPATHS")) idx = len(existing_filepaths) for filename in save_files: # No need to copy/index already existing files # The oldest copy already exists in the directory... if filename not in existing_filepaths: # Tag files with index so multiple files can # have the same filename logger.debug("Creating backup of %s", filename) try: shutil.copy2(filename, os.path.join( cp_dir, os.path.basename(filename) + "_" + str(idx))) op_fd.write(filename + os.linesep) # http://stackoverflow.com/questions/4726260/effective-use-of-python-shutil-copy2 except IOError: op_fd.close() logger.error( "Unable to add file %s to checkpoint %s", filename, cp_dir) raise errors.ReverterError( "Unable to add file {0} to checkpoint " "{1}".format(filename, cp_dir)) idx += 1 op_fd.close() with open(os.path.join(cp_dir, "CHANGES_SINCE"), "a") as notes_fd: notes_fd.write(save_notes)
def _search_lineages(cli_config, func, initial_rv): """Iterate func over unbroken lineages, allowing custom return conditions. Allows flexible customization of return values, including multiple return values and complex checks. """ configs_dir = cli_config.renewal_configs_dir # Verify the directory is there util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid()) rv = initial_rv for renewal_file in storage.renewal_conf_files(cli_config): try: candidate_lineage = storage.RenewableCert(renewal_file, cli_config) except (errors.CertStorageError, IOError): logger.debug("Renewal conf file %s is broken. Skipping.", renewal_file) logger.debug("Traceback was:\n%s", traceback.format_exc()) continue rv = func(candidate_lineage, rv) return rv
def save_certificate(self, cert_pem, chain_pem, cert_path, chain_path, fullchain_path): """Saves the certificate received from the ACME server. :param str cert_pem: :param str chain_pem: :param str cert_path: Candidate path to a certificate. :param str chain_path: Candidate path to a certificate chain. :param str fullchain_path: Candidate path to a full cert chain. :returns: cert_path, chain_path, and fullchain_path as absolute paths to the actual files :rtype: `tuple` of `str` :raises IOError: If unable to find room to write the cert files """ for path in cert_path, chain_path, fullchain_path: util.make_or_verify_dir( os.path.dirname(path), 0o755, misc.os_geteuid(), self.config.strict_permissions) cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path) try: cert_file.write(cert_pem) finally: cert_file.close() logger.info("Server issued certificate; certificate written to %s", abs_cert_path) chain_file, abs_chain_path =\ _open_pem_file('chain_path', chain_path) fullchain_file, abs_fullchain_path =\ _open_pem_file('fullchain_path', fullchain_path) _save_chain(chain_pem, chain_file) _save_chain(cert_pem + chain_pem, fullchain_file) return abs_cert_path, abs_chain_path, abs_fullchain_path
def make_or_verify_needed_dirs(config): """Create or verify existence of config, work, and hook directories. :param config: Configuration object :type config: interfaces.IConfig :returns: `None` :rtype: None """ util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE, compat.os_geteuid(), config.strict_permissions) util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE, compat.os_geteuid(), config.strict_permissions) hook_dirs = (config.renewal_pre_hooks_dir, config.renewal_deploy_hooks_dir, config.renewal_post_hooks_dir,) for hook_dir in hook_dirs: util.make_or_verify_dir(hook_dir, uid=compat.os_geteuid(), strict=config.strict_permissions)
def _save(self, account, acme, regr_only): account_dir_path = self._account_dir_path(account.id) util.make_or_verify_dir(account_dir_path, 0o700, os.geteuid(), self.config.strict_permissions) try: with open(self._regr_path(account_dir_path), "w") as regr_file: regr = account.regr with_uri = RegistrationResourceWithNewAuthzrURI( new_authzr_uri=acme.directory.new_authz, body=regr.body, uri=regr.uri, terms_of_service=regr.terms_of_service) regr_file.write(with_uri.json_dumps()) if not regr_only: with util.safe_open(self._key_path(account_dir_path), "w", chmod=0o400) as key_file: key_file.write(account.key.json_dumps()) with open(self._metadata_path( account_dir_path), "w") as metadata_file: metadata_file.write(account.meta.json_dumps()) except IOError as error: raise errors.AccountStorageError(error)
def _verify_setup(self): """Verify the setup to ensure safe operating environment. Make sure that files/directories are setup with appropriate permissions Aim for defensive coding... make sure all input files have permissions of root. """ uid = os.geteuid() util.make_or_verify_dir(self.config.work_dir, core_constants.CONFIG_DIRS_MODE, uid) util.make_or_verify_dir(self.config.backup_dir, core_constants.CONFIG_DIRS_MODE, uid) util.make_or_verify_dir(self.config.config_dir, core_constants.CONFIG_DIRS_MODE, uid)
def __init__(self, config): self.config = config util.make_or_verify_dir(config.accounts_dir, 0o700, self.config.strict_permissions)
def __init__(self, config): self.config = config util.make_or_verify_dir( config.backup_dir, constants.CONFIG_DIRS_MODE, os.geteuid(), self.config.strict_permissions)
def _call(self, directory, mode): from certbot.util import make_or_verify_dir return make_or_verify_dir(directory, mode, self.uid, strict=True)
def _prepare(self, account): # type: (Account) -> str account_dir_path = self._account_dir_path(account.id) util.make_or_verify_dir(account_dir_path, 0o700, self.config.strict_permissions) return account_dir_path
def __init__(self, config): self.config = config util.make_or_verify_dir(config.accounts_dir, 0o700, misc.os_geteuid(), self.config.strict_permissions)
def __init__(self, config): self.config = config util.make_or_verify_dir(config.backup_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions)
def __init__(self, config: configuration.NamespaceConfig) -> None: self.config = config util.make_or_verify_dir(config.accounts_dir, 0o700, self.config.strict_permissions)