Exemple #1
0
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
Exemple #2
0
    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))
Exemple #3
0
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
Exemple #4
0
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
Exemple #5
0
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
Exemple #6
0
    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
Exemple #7
0
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
Exemple #8
0
    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
Exemple #9
0
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
        )
Exemple #10
0
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)
Exemple #11
0
    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)
Exemple #12
0
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)