def _auth_from_domains(le_client, config, domains, plugins): """Authenticate and enroll certificate.""" # Note: This can raise errors... caught above us though. lineage = _treat_as_renewal(config, domains) if lineage is not None: # TODO: schoen wishes to reuse key - discussion # https://github.com/letsencrypt/letsencrypt/pull/777/files#r40498574 new_certr, new_chain, new_key, _ = le_client.obtain_certificate(domains) # TODO: Check whether it worked! <- or make sure errors are thrown (jdk) lineage.save_successor( lineage.latest_common_version(), OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, new_certr.body), new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain)) lineage.update_all_links_to(lineage.latest_common_version()) # TODO: Check return value of save_successor # TODO: Also update lineage renewal config with any relevant # configuration values from this attempt? <- Absolutely (jdkasten) else: # TREAT AS NEW REQUEST lineage = le_client.obtain_and_enroll_certificate(domains, plugins) if not lineage: raise errors.Error("Certificate could not be obtained") _report_new_cert(lineage.cert) reporter_util = zope.component.getUtility(interfaces.IReporter) reporter_util.add_message( "Your certificate will expire on {0}. To obtain a new version of the " "certificate in the future, simply run this client again.".format( lineage.notafter().date()), reporter_util.MEDIUM_PRIORITY) return lineage
def obtain_and_enroll_certificate(self, domains): """Obtain and enroll certificate. Get a new certificate for the specified domains using the specified authenticator and installer, and then create a new renewable lineage containing it. :param list domains: Domains to request. :param plugins: A PluginsFactory object. :returns: A new :class:`letsencrypt.storage.RenewableCert` instance referred to the enrolled cert lineage, False if the cert could not be obtained, or None if doing a successful dry run. """ certr, chain, key, _ = self.obtain_certificate(domains) if (self.config.config_dir != constants.CLI_DEFAULTS["config_dir"] or self.config.work_dir != constants.CLI_DEFAULTS["work_dir"]): logger.warning( "Non-standard path(s), might not work with crontab installed " "by your operating system package manager") if self.config.dry_run: logger.info("Dry run: Skipping creating new lineage for %s", domains[0]) return None else: return storage.RenewableCert.new_lineage( domains[0], OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped), key.pem, crypto_util.dump_pyopenssl_chain(chain), configuration.RenewerConfiguration(self.config.namespace))
def _auth_from_domains(le_client, config, domains): """Authenticate and enroll certificate.""" # Note: This can raise errors... caught above us though. lineage = _treat_as_renewal(config, domains) if lineage is not None: # TODO: schoen wishes to reuse key - discussion # https://github.com/letsencrypt/letsencrypt/pull/777/files#r40498574 new_certr, new_chain, new_key, _ = le_client.obtain_certificate(domains) # TODO: Check whether it worked! <- or make sure errors are thrown (jdk) lineage.save_successor( lineage.latest_common_version(), OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, new_certr.body), new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain)) lineage.update_all_links_to(lineage.latest_common_version()) # TODO: Check return value of save_successor # TODO: Also update lineage renewal config with any relevant # configuration values from this attempt? <- Absolutely (jdkasten) else: # TREAT AS NEW REQUEST lineage = le_client.obtain_and_enroll_certificate(domains) if not lineage: raise errors.Error("Certificate could not be obtained") _report_new_cert(lineage.cert, lineage.fullchain) return lineage
def renew(cert, old_version): """Perform automated renewal of the referenced cert, if possible. :param letsencrypt.storage.RenewableCert cert: The certificate lineage to attempt to renew. :param int old_version: The version of the certificate lineage relative to which the renewal should be attempted. :returns: A number referring to newly created version of this cert lineage, or ``False`` if renewal was not successful. :rtype: `int` or `bool` """ # TODO: handle partial success (some names can be renewed but not # others) # TODO: handle obligatory key rotation vs. optional key rotation vs. # requested key rotation if "renewalparams" not in cert.configfile: # TODO: notify user? return False renewalparams = cert.configfile["renewalparams"] if "authenticator" not in renewalparams: # TODO: notify user? return False # Instantiate the appropriate authenticator plugins = plugins_disco.PluginsRegistry.find_all() config = configuration.NamespaceConfig(_AttrDict(renewalparams)) # XXX: this loses type data (for example, the fact that key_size # was an int, not a str) config.rsa_key_size = int(config.rsa_key_size) config.dvsni_port = int(config.dvsni_port) try: authenticator = plugins[renewalparams["authenticator"]] except KeyError: # TODO: Notify user? (authenticator could not be found) return False authenticator = authenticator.init(config) authenticator.prepare() acc = account.AccountFileStorage(config).load( account_id=renewalparams["account"]) le_client = client.Client(config, acc, authenticator, None) with open(cert.version("cert", old_version)) as f: sans = crypto_util.get_sans_from_cert(f.read()) new_certr, new_chain, new_key, _ = le_client.obtain_certificate(sans) if new_chain: # XXX: Assumes that there was no key change. We need logic # for figuring out whether there was or not. Probably # best is to have obtain_certificate return None for # new_key if the old key is to be used (since save_successor # already understands this distinction!) return cert.save_successor( old_version, OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, new_certr.body), new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain)) # TODO: Notify results else: # TODO: Notify negative results return False
def _auth_from_domains(le_client, config, domains, lineage=None): """Authenticate and enroll certificate.""" # Note: This can raise errors... caught above us though. This is now # a three-way case: reinstall (which results in a no-op here because # although there is a relevant lineage, we don't do anything to it # inside this function -- we don't obtain a new certificate), renew # (which results in treating the request as a renewal), or newcert # (which results in treating the request as a new certificate request). # If lineage is specified, use that one instead of looking around for # a matching one. if lineage is None: # This will find a relevant matching lineage that exists action, lineage = _treat_as_renewal(config, domains) else: # Renewal, where we already know the specific lineage we're # interested in action = "renew" if action == "reinstall": # The lineage already exists; allow the caller to try installing # it without getting a new certificate at all. return lineage, "reinstall" elif action == "renew": original_server = lineage.configuration["renewalparams"]["server"] _avoid_invalidating_lineage(config, lineage, original_server) # TODO: schoen wishes to reuse key - discussion # https://github.com/letsencrypt/letsencrypt/pull/777/files#r40498574 new_certr, new_chain, new_key, _ = le_client.obtain_certificate( domains) # TODO: Check whether it worked! <- or make sure errors are thrown (jdk) if config.dry_run: logger.info("Dry run: skipping updating lineage at %s", os.path.dirname(lineage.cert)) else: lineage.save_successor( lineage.latest_common_version(), OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, new_certr.body.wrapped), new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain), configuration.RenewerConfiguration(config.namespace)) lineage.update_all_links_to(lineage.latest_common_version()) # TODO: Check return value of save_successor # TODO: Also update lineage renewal config with any relevant # configuration values from this attempt? <- Absolutely (jdkasten) elif action == "newcert": # TREAT AS NEW REQUEST lineage = le_client.obtain_and_enroll_certificate(domains) if lineage is False: raise errors.Error("Certificate could not be obtained") if not config.dry_run and not config.verb == "renew": _report_new_cert(lineage.cert, lineage.fullchain) return lineage, action
def obtain_and_enroll_certificate( self, domains, authenticator, installer, plugins): """Obtain and enroll certificate. Get a new certificate for the specified domains using the specified authenticator and installer, and then create a new renewable lineage containing it. :param list domains: Domains to request. :param authenticator: The authenticator to use. :type authenticator: :class:`letsencrypt.interfaces.IAuthenticator` :param installer: The installer to use. :type installer: :class:`letsencrypt.interfaces.IInstaller` :param plugins: A PluginsFactory object. :returns: A new :class:`letsencrypt.storage.RenewableCert` instance referred to the enrolled cert lineage, or False if the cert could not be obtained. """ certr, chain, key, _ = self.obtain_certificate(domains) # TODO: remove this dirty hack self.config.namespace.authenticator = plugins.find_init( authenticator).name if installer is not None: self.config.namespace.installer = plugins.find_init(installer).name # XXX: We clearly need a more general and correct way of getting # options into the configobj for the RenewableCert instance. # This is a quick-and-dirty way to do it to allow integration # testing to start. (Note that the config parameter to new_lineage # ideally should be a ConfigObj, but in this case a dict will be # accepted in practice.) params = vars(self.config.namespace) config = {} cli_config = configuration.RenewerConfiguration(self.config.namespace) if (cli_config.config_dir != constants.CLI_DEFAULTS["config_dir"] or cli_config.work_dir != constants.CLI_DEFAULTS["work_dir"]): logger.warning( "Non-standard path(s), might not work with crontab installed " "by your operating system package manager") lineage = storage.RenewableCert.new_lineage( domains[0], OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, certr.body), key.pem, crypto_util.dump_pyopenssl_chain(chain), params, config, cli_config) self._report_renewal_status(lineage) return lineage
def _auth_from_domains(le_client, config, domains, lineage=None): """Authenticate and enroll certificate.""" # Note: This can raise errors... caught above us though. This is now # a three-way case: reinstall (which results in a no-op here because # although there is a relevant lineage, we don't do anything to it # inside this function -- we don't obtain a new certificate), renew # (which results in treating the request as a renewal), or newcert # (which results in treating the request as a new certificate request). # If lineage is specified, use that one instead of looking around for # a matching one. if lineage is None: # This will find a relevant matching lineage that exists action, lineage = _treat_as_renewal(config, domains) else: # Renewal, where we already know the specific lineage we're # interested in action = "renew" if action == "reinstall": # The lineage already exists; allow the caller to try installing # it without getting a new certificate at all. return lineage, "reinstall" elif action == "renew": original_server = lineage.configuration["renewalparams"]["server"] _avoid_invalidating_lineage(config, lineage, original_server) # TODO: schoen wishes to reuse key - discussion # https://github.com/letsencrypt/letsencrypt/pull/777/files#r40498574 new_certr, new_chain, new_key, _ = le_client.obtain_certificate(domains) # TODO: Check whether it worked! <- or make sure errors are thrown (jdk) if config.dry_run: logger.info("Dry run: skipping updating lineage at %s", os.path.dirname(lineage.cert)) else: lineage.save_successor( lineage.latest_common_version(), OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, new_certr.body.wrapped), new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain), configuration.RenewerConfiguration(config.namespace)) lineage.update_all_links_to(lineage.latest_common_version()) # TODO: Check return value of save_successor # TODO: Also update lineage renewal config with any relevant # configuration values from this attempt? <- Absolutely (jdkasten) elif action == "newcert": # TREAT AS NEW REQUEST lineage = le_client.obtain_and_enroll_certificate(domains) if lineage is False: raise errors.Error("Certificate could not be obtained") if not config.dry_run and not config.verb == "renew": _report_new_cert(lineage.cert, lineage.fullchain) return lineage, action
def save_certificate(self, certr, chain_cert, cert_path, chain_path): # pylint: disable=no-self-use """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. :returns: cert_path, chain_path (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: le_util.make_or_verify_dir( os.path.dirname(path), 0o755, os.geteuid(), self.config.strict_permissions) # try finally close cert_chain_abspath = None cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644) # TODO: Except cert_pem = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, certr.body) try: cert_file.write(cert_pem) finally: cert_file.close() logger.info("Server issued certificate; certificate written to %s", act_cert_path) if chain_cert: chain_file, act_chain_path = le_util.unique_file( chain_path, 0o644) # TODO: Except chain_pem = crypto_util.dump_pyopenssl_chain(chain_cert) try: chain_file.write(chain_pem) finally: chain_file.close() logger.info("Cert chain written to %s", act_chain_path) # This expects a valid chain file cert_chain_abspath = os.path.abspath(act_chain_path) return os.path.abspath(act_cert_path), cert_chain_abspath
def _main(domains=[], email=None, hosts=[]): ns = ConfigNamespace(email) config = NamespaceConfig(ns) zope.component.provideUtility(config) ams = AccountMemoryStorage() acc, acme = register(config, ams) authenticator = RpaasLeAuthenticator(hosts=hosts, config=config, name='') installer = None lec = Client(config, acc, authenticator, installer, acme) certr, chain, key, _ = lec.obtain_certificate(domains) return ( OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, certr.body), crypto_util.dump_pyopenssl_chain(chain), key.pem )
def renew_cert(config, domains, le_client, lineage): "Renew a certificate lineage." original_server = lineage.configuration["renewalparams"]["server"] _avoid_invalidating_lineage(config, lineage, original_server) new_certr, new_chain, new_key, _ = le_client.obtain_certificate(domains) if config.dry_run: logger.info("Dry run: skipping updating lineage at %s", os.path.dirname(lineage.cert)) else: prior_version = lineage.latest_common_version() new_cert = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, new_certr.body.wrapped) new_chain = crypto_util.dump_pyopenssl_chain(new_chain) renewal_conf = configuration.RenewerConfiguration(config.namespace) lineage.save_successor(prior_version, new_cert, new_key.pem, new_chain, renewal_conf) lineage.update_all_links_to(lineage.latest_common_version()) hooks.renew_hook(config, domains, lineage.live_dir)
def obtain_and_enroll_certificate(self, domains): """Obtain and enroll certificate. Get a new certificate for the specified domains using the specified authenticator and installer, and then create a new renewable lineage containing it. :param list domains: Domains to request. :param plugins: A PluginsFactory object. :returns: A new :class:`letsencrypt.storage.RenewableCert` instance referred to the enrolled cert lineage, False if the cert could not be obtained, or None if doing a successful dry run. """ certr, chain, key, _ = self.obtain_certificate(domains) # XXX: We clearly need a more general and correct way of getting # options into the configobj for the RenewableCert instance. # This is a quick-and-dirty way to do it to allow integration # testing to start. (Note that the config parameter to new_lineage # ideally should be a ConfigObj, but in this case a dict will be # accepted in practice.) params = vars(self.config.namespace) config = {} cli_config = configuration.RenewerConfiguration(self.config.namespace) if (cli_config.config_dir != constants.CLI_DEFAULTS["config_dir"] or cli_config.work_dir != constants.CLI_DEFAULTS["work_dir"]): logger.warning( "Non-standard path(s), might not work with crontab installed " "by your operating system package manager") if cli_config.dry_run: logger.info("Dry run: Skipping creating new lineage for %s", domains[0]) return None else: return storage.RenewableCert.new_lineage( domains[0], OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped), key.pem, crypto_util.dump_pyopenssl_chain(chain), params, config, cli_config)
def run(args, config, plugins): # pylint: disable=too-many-branches,too-many-locals """Obtain a certificate and install.""" if args.configurator is not None and (args.installer is not None or args.authenticator is not None): return ("Either --configurator or --authenticator/--installer" "pair, but not both, is allowed") if args.authenticator is not None or args.installer is not None: installer = display_ops.pick_installer( config, args.installer, plugins) authenticator = display_ops.pick_authenticator( config, args.authenticator, plugins) else: # TODO: this assumes that user doesn't want to pick authenticator # and installer separately... authenticator = installer = display_ops.pick_configurator( config, args.configurator, plugins) if installer is None or authenticator is None: return "Configurator could not be determined" domains = _find_domains(args, installer) treat_as_renewal = False # Considering the possibility that the requested certificate is # related to an existing certificate. (config.duplicate, which # is set with --duplicate, skips all of this logic and forces any # kind of certificate to be obtained with treat_as_renewal = False.) if not config.duplicate: identical_names_cert, subset_names_cert = _find_duplicative_certs( domains, config, configuration.RenewerConfiguration(config)) # I am not sure whether that correctly reads the systemwide # configuration file. question = None if identical_names_cert is not None: question = ( "You have an existing certificate that contains exactly the " "same domains you requested (ref: {0})\n\nDo you want to " "renew and replace this certificate with a newly-issued one?" ).format(identical_names_cert.configfile.filename) elif subset_names_cert is not None: question = ( "You have an existing certificate that contains a portion of " "the domains you requested (ref: {0})\n\nIt contains these " "names: {1}\n\nYou requested these names for the new " "certificate: {2}.\n\nDo you want to replace this existing " "certificate with the new certificate?" ).format(subset_names_cert.configfile.filename, ", ".join(subset_names_cert.names()), ", ".join(domains)) if question is None: # We aren't in a duplicative-names situation at all, so we don't # have to tell or ask the user anything about this. pass elif zope.component.getUtility(interfaces.IDisplay).yesno( question, "Replace", "Cancel"): treat_as_renewal = True else: reporter_util = zope.component.getUtility(interfaces.IReporter) reporter_util.add_message( "To obtain a new certificate that {0} an existing certificate " "in its domain-name coverage, you must use the --duplicate " "option.\n\nFor example:\n\n{1} --duplicate {2}".format( "duplicates" if identical_names_cert is not None else "overlaps with", sys.argv[0], " ".join(sys.argv[1:])), reporter_util.HIGH_PRIORITY) return 1 # Attempting to obtain the certificate # TODO: Handle errors from _init_le_client? le_client = _init_le_client(args, config, authenticator, installer) if treat_as_renewal: lineage = identical_names_cert if identical_names_cert is not None else subset_names_cert # TODO: Use existing privkey instead of generating a new one new_certr, new_chain, new_key, _ = le_client.obtain_certificate(domains) # TODO: Check whether it worked! lineage.save_successor( lineage.latest_common_version(), OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, new_certr.body), new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain)) lineage.update_all_links_to(lineage.latest_common_version()) # TODO: Check return value of save_successor # TODO: Also update lineage renewal config with any relevant # configuration values from this attempt? le_client.deploy_certificate( domains, lineage.privkey, lineage.cert, lineage.chain) display_ops.success_renewal(domains) else: # TREAT AS NEW REQUEST lineage = le_client.obtain_and_enroll_certificate( domains, authenticator, installer, plugins) if not lineage: return "Certificate could not be obtained" # TODO: This treats the key as changed even when it wasn't # TODO: We also need to pass the fullchain (for Nginx) le_client.deploy_certificate( domains, lineage.privkey, lineage.cert, lineage.chain) le_client.enhance_config(domains, args.redirect) display_ops.success_installation(domains)