Пример #1
0
    def _report_failed_authzrs(
            self,
            failed_authzrs: List[messages.AuthorizationResource]) -> None:
        """Notifies the user about failed authorizations."""
        if not self.account:
            raise errors.Error("Account is not set.")
        problems: Dict[str, List[achallenges.AnnotatedChallenge]] = {}
        failed_achalls = [
            challb_to_achall(challb, self.account.key,
                             authzr.body.identifier.value)
            for authzr in failed_authzrs for challb in authzr.body.challenges
            if challb.error
        ]

        for achall in failed_achalls:
            problems.setdefault(achall.error.typ, []).append(achall)

        msg = [
            "\nCertbot failed to authenticate some domains "
            f"(authenticator: {self.auth.name})."
            " The Certificate Authority reported these problems:"
        ]

        for _, achalls in sorted(problems.items(), key=lambda item: item[0]):
            msg.append(_generate_failed_chall_msg(achalls))

        # auth_hint will only be called on authenticators that subclass
        # plugin_common.Plugin. Refer to comment on that function.
        if failed_achalls and isinstance(self.auth, plugin_common.Plugin):
            msg.append(f"\nHint: {self.auth.auth_hint(failed_achalls)}\n")

        display_util.notify("".join(msg))
Пример #2
0
 def test_notify(self, mock_util):
     from certbot.display.util import notify
     notify("Hello World")
     mock_util().notification.assert_called_with("Hello World",
                                                 pause=False,
                                                 decorate=False,
                                                 wrap=False)
Пример #3
0
    def _recovery_routine_with_msg(self, success_msg):
        """Calls the installer's recovery routine and prints success_msg

        :param str success_msg: message to show on successful recovery

        """
        self.installer.recovery_routine()
        display_util.notify(success_msg)
Пример #4
0
def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=None):
    """Authenticate and enroll certificate.

    This method finds the relevant lineage, figures out what to do with it,
    then performs that action. Includes calls to hooks, various reports,
    checks, and requests for user input.

    :param config: Configuration object
    :type config: interfaces.IConfig

    :param domains: List of domain names to get a certificate. Defaults to `None`
    :type domains: `list` of `str`

    :param certname: Name of new certificate. Defaults to `None`
    :type certname: str

    :param lineage: Certificate lineage object. Defaults to `None`
    :type lineage: storage.RenewableCert

    :returns: the issued certificate or `None` if doing a dry run
    :rtype: storage.RenewableCert or None

    :raises errors.Error: if certificate could not be obtained

    """
    hooks.pre_hook(config)
    try:
        if lineage is not None:
            # Renewal, where we already know the specific lineage we're
            # interested in
            display_util.notify(
                "{action} for {domains}".format(
                    action="Simulating renewal of an existing certificate"
                            if config.dry_run else "Renewing an existing certificate",
                    domains=display_util.summarize_domain_list(domains or lineage.names())
                )
            )
            renewal.renew_cert(config, domains, le_client, lineage)
        else:
            # TREAT AS NEW REQUEST
            assert domains is not None
            display_util.notify(
                "{action} for {domains}".format(
                    action="Simulating a certificate request" if config.dry_run else
                           "Requesting a certificate",
                    domains=display_util.summarize_domain_list(domains)
                )
            )
            lineage = le_client.obtain_and_enroll_certificate(domains, certname)
            if lineage is False:
                raise errors.Error("Certificate could not be obtained")
            if lineage is not None:
                hooks.deploy_hook(config, lineage.names(), lineage.live_dir)
    finally:
        hooks.post_hook(config)

    return lineage
Пример #5
0
def _determine_account(config):
    """Determine which account to use.

    If ``config.account`` is ``None``, it will be updated based on the
    user input. Same for ``config.email``.

    :param config: Configuration object
    :type config: interfaces.IConfig

    :returns: Account and optionally ACME client API (biproduct of new
        registration).
    :rtype: tuple of :class:`certbot._internal.account.Account` and :class:`acme.client.Client`

    :raises errors.Error: If unable to register an account with ACME server

    """
    def _tos_cb(terms_of_service):
        if config.tos:
            return True
        msg = ("Please read the Terms of Service at {0}. You "
               "must agree in order to register with the ACME "
               "server. Do you agree?".format(terms_of_service))
        obj = zope.component.getUtility(interfaces.IDisplay)
        result = obj.yesno(msg, cli_flag="--agree-tos", force_interactive=True)
        if not result:
            raise errors.Error(
                "Registration cannot proceed without accepting "
                "Terms of Service.")
        return None

    account_storage = account.AccountFileStorage(config)
    acme = None

    if config.account is not None:
        acc = account_storage.load(config.account)
    else:
        accounts = account_storage.find_all()
        if len(accounts) > 1:
            acc = display_ops.choose_account(accounts)
        elif len(accounts) == 1:
            acc = accounts[0]
        else:  # no account registered yet
            if config.email is None and not config.register_unsafely_without_email:
                config.email = display_ops.get_email()
            try:
                acc, acme = client.register(
                    config, account_storage, tos_cb=_tos_cb)
                display_util.notify("Account registered.")
            except errors.MissingCommandlineFlag:
                raise
            except errors.Error:
                logger.debug("", exc_info=True)
                raise errors.Error(
                    "Unable to register an account with ACME server")

    config.account = acc.id
    return acc, acme
Пример #6
0
def success_renewal(unused_domains):
    """Display a box confirming the renewal of an existing certificate.

    :param list domains: domain names which were renewed

    """
    display_util.notify(
        "Your existing certificate has been successfully renewed, and the "
        "new certificate has been installed.")
Пример #7
0
def success_revocation(cert_path):
    """Display a message confirming a certificate has been revoked.

    :param list cert_path: path to certificate which was revoked.

    """
    display_util.notify(
        "Congratulations! You have successfully revoked the certificate "
        "that was located at {0}.".format(cert_path))
Пример #8
0
def success_installation(domains):
    """Display a box confirming the installation of HTTPS.

    :param list domains: domain names which were enabled

    """
    display_util.notify(
        "Congratulations! You have successfully enabled HTTPS on {0}".format(
            _gen_https_names(domains)))
Пример #9
0
    def _recovery_routine_with_msg(self, success_msg: Optional[str]) -> None:
        """Calls the installer's recovery routine and prints success_msg

        :param str success_msg: message to show on successful recovery

        """
        if self.installer:
            self.installer.recovery_routine()
            if success_msg:
                display_util.notify(success_msg)
Пример #10
0
def should_renew(config, lineage):
    """Return true if any of the circumstances for automatic renewal apply."""
    if config.renew_by_default:
        logger.debug("Auto-renewal forced with --force-renewal...")
        return True
    if lineage.should_autorenew():
        logger.info("Certificate is due for renewal, auto-renewing...")
        return True
    if config.dry_run:
        logger.info("Certificate not due for renewal, but simulating renewal for dry run")
        return True
    display_util.notify("Certificate not yet due for renewal")
    return False
Пример #11
0
 def _retry_obtain_certificate(
     self, key: util.Key, csr: util.CSR, domains: List[str],
     successful_domains: List[str]
 ) -> Tuple[bytes, bytes, util.Key, util.CSR]:
     failed_domains = [d for d in domains if d not in successful_domains]
     domains_list = ", ".join(failed_domains)
     display_util.notify(
         "Unable to obtain a certificate with every requested "
         f"domain. Retrying without: {domains_list}")
     if not self.config.dry_run:
         os.remove(key.file)
         os.remove(csr.file)
     return self.obtain_certificate(successful_domains)
Пример #12
0
def delete(config):
    """Delete Certbot files associated with a certificate lineage."""
    certnames = get_certnames(config, "delete", allow_multiple=True)
    msg = ["The following certificate(s) are selected for deletion:\n"]
    for certname in certnames:
        msg.append("  * " + certname)
    msg.append("\nAre you sure you want to delete the above certificate(s)?")
    if not display_util.yesno("\n".join(msg), default=True):
        logger.info("Deletion of certificate(s) canceled.")
        return
    for certname in certnames:
        storage.delete_files(config, certname)
        display_util.notify("Deleted all files relating to certificate {0}."
                            .format(certname))
Пример #13
0
def _report_failure(reason: Optional[str] = None) -> None:
    """Notify the user of failing to sign them up for the newsletter.

    :param reason: a phrase describing what the problem was
        beginning with a lowercase letter and no closing punctuation
    :type reason: `str` or `None`

    """
    msg = ['We were unable to subscribe you the EFF mailing list']
    if reason is not None:
        msg.append(' because ')
        msg.append(reason)
    msg.append('. You can try again later by visiting https://act.eff.org.')
    display_util.notify(''.join(msg))
Пример #14
0
def update_account(config, unused_plugins):
    """Modify accounts on the server.

    :param config: Configuration object
    :type config: interfaces.IConfig

    :param unused_plugins: List of plugins (deprecated)
    :type unused_plugins: `list` of `str`

    :returns: `None` or a string indicating and error
    :rtype: None or str

    """
    # Portion of _determine_account logic to see whether accounts already
    # exist or not.
    account_storage = account.AccountFileStorage(config)
    accounts = account_storage.find_all()

    if not accounts:
        return "Could not find an existing account to update."
    if config.email is None and not config.register_unsafely_without_email:
        config.email = display_ops.get_email(optional=False)

    acc, acme = _determine_account(config)
    cb_client = client.Client(config, acc, None, None, acme=acme)
    # Empty list of contacts in case the user is removing all emails

    acc_contacts = ()  # type: Iterable[str]
    if config.email:
        acc_contacts = ['mailto:' + email for email in config.email.split(',')]
    # We rely on an exception to interrupt this process if it didn't work.
    prev_regr_uri = acc.regr.uri
    acc.regr = cb_client.acme.update_registration(
        acc.regr.update(body=acc.regr.body.update(contact=acc_contacts)))
    # A v1 account being used as a v2 account will result in changing the uri to
    # the v2 uri. Since it's the same object on disk, put it back to the v1 uri
    # so that we can also continue to use the account object with acmev1.
    acc.regr = acc.regr.update(uri=prev_regr_uri)
    account_storage.update_regr(acc, cb_client.acme)

    if config.email is None:
        display_util.notify("Any contact information associated "
                            "with this account has been removed.")
    else:
        eff.prepare_subscription(config, acc)
        display_util.notify("Your e-mail address was updated to {0}.".format(
            config.email))

    return None
Пример #15
0
    def deploy_cert(self,
                    domain,
                    cert_path,
                    key_path,
                    chain_path=None,
                    fullchain_path=None):

        if not fullchain_path:
            raise errors.PluginError(
                "CASTLE Installer plugin requires --fullchain-path to generate a PKCS12 container."
            )
        logger.info("Generating PKCS12 container")
        logger.debug('Loading cert ')
        cert = x509.load_pem_x509_certificate(open(cert_path, 'rb').read())
        logger.debug('Loading key ')
        privkey = serialization.load_pem_private_key(open(key_path,
                                                          'rb').read(),
                                                     password=None)
        logger.debug('Loading chain ')
        chain = x509.load_pem_x509_certificate(open(chain_path, 'rb').read())
        passphrase = None
        if (not self.conf('no-passphrase')):
            if (self.conf('passphrase')):
                passphrase = self.conf('passphrase').encode('utf-8')
            else:
                text = 'A passphrase is needed for protecting the PKCS12 container. '
                display_util.notification(text, pause=False)
                pf = getpass.getpass('Enter passphrase: ')
                vpf = getpass.getpass('Re-enter passphrase: ')
                while (pf != vpf):
                    display_util.notify('Passphrases do not match.')
                    vpf = getpass.getpass('Re-enter passphrase: ')
                passphrase = pf.encode('utf-8')
        algo = serialization.BestAvailableEncryption(
            passphrase) if passphrase else serialization.NoEncryption()
        pfxdata = pkcs12.serialize_key_and_certificates(
            name=domain.encode('utf-8'),
            key=privkey,
            cert=cert,
            cas=[chain],
            encryption_algorithm=algo)
        path, _ = os.path.split(cert_path)
        pfx_f, pfx_filename = util.unique_file(os.path.join(path, 'cert.pfx'),
                                               0o600, "wb")
        with pfx_f:
            pfx_f.write(pfxdata)
        display_util.notification('PKCS12 container generated at ' +
                                  pfx_filename,
                                  pause=False)
Пример #16
0
def report_executed_command(command_name: str, returncode: int, stdout: str, stderr: str) -> None:
    """Display a message describing the success or failure of an executed process (e.g. hook).

    :param str command_name: Human-readable description of the executed command
    :param int returncode: The exit code of the executed command
    :param str stdout: The stdout output of the executed command
    :param str stderr: The stderr output of the executed command

    """
    out_s, err_s = stdout.strip(), stderr.strip()
    if returncode != 0:
        logger.warning("%s reported error code %d", command_name, returncode)
    if out_s:
        display_util.notify(f"{command_name} ran with output:\n{indent(out_s, ' ')}")
    if err_s:
        logger.warning("%s ran with error output:\n%s", command_name, indent(err_s, ' '))
Пример #17
0
def _handle_subset_cert_request(config, domains, cert):
    """Figure out what to do if a previous cert had a subset of the names now requested

    :param config: Configuration object
    :type config: interfaces.IConfig

    :param domains: List of domain names
    :type domains: `list` of `str`

    :param cert: Certificate object
    :type cert: storage.RenewableCert

    :returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname
              action can be: "newcert" | "renew" | "reinstall"
    :rtype: `tuple` of `str`

    """
    existing = ", ".join(cert.names())
    question = (
        "You have an existing certificate that contains a portion of "
        "the domains you requested (ref: {0}){br}{br}It contains these "
        "names: {1}{br}{br}You requested these names for the new "
        "certificate: {2}.{br}{br}Do you want to expand and replace this existing "
        "certificate with the new certificate?").format(
            cert.configfile.filename,
            existing,
            ", ".join(domains),
            br=os.linesep)
    if config.expand or config.renew_by_default or zope.component.getUtility(
            interfaces.IDisplay).yesno(question,
                                       "Expand",
                                       "Cancel",
                                       cli_flag="--expand",
                                       force_interactive=True):
        return "renew", cert
    display_util.notify(
        "To obtain a new certificate that contains these names without "
        "replacing your existing certificate for {0}, you must use the "
        "--duplicate option.{br}{br}"
        "For example:{br}{br}{1} --duplicate {2}".format(existing,
                                                         sys.argv[0],
                                                         " ".join(
                                                             sys.argv[1:]),
                                                         br=os.linesep))
    raise errors.Error(USER_CANCELLED)
Пример #18
0
    def _rollback_and_restart(self, success_msg):
        """Rollback the most recent checkpoint and restart the webserver

        :param str success_msg: message to show on successful rollback

        """
        logger.info("Rolling back to previous server configuration...")
        try:
            self.installer.rollback_checkpoints()
            self.installer.restart()
        except:
            logger.error(
                "An error occurred and we failed to restore your config and "
                "restart your server. Please post to "
                "https://community.letsencrypt.org/c/help "
                "with details about your configuration and this error you received."
            )
            raise
        display_util.notify(success_msg)
Пример #19
0
def delete(config: configuration.NamespaceConfig) -> None:
    """Delete Certbot files associated with a certificate lineage."""
    certnames = get_certnames(config, "delete", allow_multiple=True)
    msg = ["The following certificate(s) are selected for deletion:\n"]
    for certname in certnames:
        msg.append("  * " + certname)
    msg.append(
        "\nWARNING: Before continuing, ensure that the listed certificates are not being used "
        "by any installed server software (e.g. Apache, nginx, mail servers). Deleting a "
        "certificate that is still being used will cause the server software to stop working. "
        "See https://certbot.org/deleting-certs for information on deleting certificates safely."
    )
    msg.append("\nAre you sure you want to delete the above certificate(s)?")
    if not display_util.yesno("\n".join(msg), default=True):
        logger.info("Deletion of certificate(s) canceled.")
        return
    for certname in certnames:
        storage.delete_files(config, certname)
        display_util.notify("Deleted all files relating to certificate {0}."
                            .format(certname))
Пример #20
0
    def deploy_certificate(self, domains: List[str], privkey_path: str,
                           cert_path: str, chain_path: str,
                           fullchain_path: str) -> None:
        """Install certificate

        :param list domains: list of domains to install the certificate
        :param str privkey_path: path to certificate private key
        :param str cert_path: certificate file path (optional)
        :param str fullchain_path: path to the full chain of the certificate
        :param str chain_path: chain file path

        """
        if self.installer is None:
            logger.error("No installer specified, client is unable to deploy"
                         "the certificate")
            raise errors.Error("No installer available")

        chain_path = None if chain_path is None else os.path.abspath(
            chain_path)

        display_util.notify("Deploying certificate")

        msg = "Could not install certificate"
        with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg):
            for dom in domains:
                self.installer.deploy_cert(
                    domain=dom,
                    cert_path=os.path.abspath(cert_path),
                    key_path=os.path.abspath(privkey_path),
                    chain_path=chain_path,
                    fullchain_path=fullchain_path)
                self.installer.save()  # needed by the Apache plugin

            self.installer.save("Deployed ACME Certificate")

        msg = ("We were unable to install your certificate, "
               "however, we successfully restored your "
               "server to its prior configuration.")
        with error_handler.ErrorHandler(self._rollback_and_restart, msg):
            # sites may have been enabled / final cleanup
            self.installer.restart()
Пример #21
0
def unregister(config, unused_plugins):
    """Deactivate account on server

    :param config: Configuration object
    :type config: interfaces.IConfig

    :param unused_plugins: List of plugins (deprecated)
    :type unused_plugins: `list` of `str`

    :returns: `None`
    :rtype: None

    """
    account_storage = account.AccountFileStorage(config)
    accounts = account_storage.find_all()

    if not accounts:
        return "Could not find existing account to deactivate."
    yesno = zope.component.getUtility(interfaces.IDisplay).yesno
    prompt = ("Are you sure you would like to irrevocably deactivate "
              "your account?")
    wants_deactivate = yesno(prompt,
                             yes_label='Deactivate',
                             no_label='Abort',
                             default=True)

    if not wants_deactivate:
        return "Deactivation aborted."

    acc, acme = _determine_account(config)
    cb_client = client.Client(config, acc, None, None, acme=acme)

    # delete on boulder
    cb_client.acme.deactivate_registration(acc.regr)
    account_files = account.AccountFileStorage(config)
    # delete local account files
    account_files.delete(config.account)

    display_util.notify("Account deactivated.")
    return None
Пример #22
0
    def perform(self, achalls):  # pylint: disable=missing-function-docstring
        self._setup_credentials()

        self._attempt_cleanup = True

        responses = []
        for achall in achalls:
            domain = achall.domain
            validation_domain_name = achall.validation_domain_name(domain)
            validation = achall.validation(achall.account_key)

            self._perform(domain, validation_domain_name, validation)
            responses.append(achall.response(achall.account_key))

        # DNS updates take time to propagate and checking to see if the update has occurred is not
        # reliable (the machine this code is running on might be able to see an update before
        # the ACME server). So: we sleep for a short amount of time we believe to be long enough.
        display_util.notify("Waiting %d seconds for DNS changes to propagate" %
                            self.conf('propagation-seconds'))
        sleep(self.conf('propagation-seconds'))

        return responses