Beispiel #1
0
def get_link_target(link):
    """Get an absolute path to the target of link.

    :param str link: Path to a symbolic link

    :returns: Absolute path to the target of link
    :rtype: str

    :raises .CertStorageError: If link does not exists.

    """
    try:
        target = os.readlink(link)
    except OSError:
        raise errors.CertStorageError(
            "Expected {0} to be a symlink".format(link))

    if not os.path.isabs(target):
        target = os.path.join(os.path.dirname(link), target)
    return os.path.abspath(target)
Beispiel #2
0
    def available_versions(self, kind):
        """Which alternative versions of the specified kind of item exist?

        The archive directory where the current version is stored is
        consulted to obtain the list of alternatives.

        :param str kind: the lineage member item (
            ``cert``, ``privkey``, ``chain``, or ``fullchain``)

        :returns: all of the version numbers that currently exist
        :rtype: `list` of `int`

        """
        if kind not in ALL_FOUR:
            raise errors.CertStorageError("unknown kind of item")
        where = os.path.dirname(self.current_target(kind))
        files = os.listdir(where)
        pattern = re.compile(r"^{0}([0-9]+)\.pem$".format(kind))
        matches = [pattern.match(f) for f in files]
        return sorted([int(m.groups()[0]) for m in matches if m])
Beispiel #3
0
    def new_lineage(cls, lineagename, cert, privkey, chain, cli_config):
        # pylint: disable=too-many-locals
        """Create a new certificate lineage.

        Attempts to create a certificate lineage -- enrolled for
        potential future renewal -- with the (suggested) lineage name
        lineagename, and the associated cert, privkey, and chain (the
        associated fullchain will be created automatically). Optional
        configurator and renewalparams record the configuration that was
        originally used to obtain this cert, so that it can be reused
        later during automated renewal.

        Returns a new RenewableCert object referring to the created
        lineage. (The actual lineage name, as well as all the relevant
        file paths, will be available within this object.)

        :param str lineagename: the suggested name for this lineage
            (normally the current cert's first subject DNS name)
        :param str cert: the initial certificate version in PEM format
        :param str privkey: the private key in PEM format
        :param str chain: the certificate chain in PEM format
        :param .RenewerConfiguration cli_config: parsed command line
            arguments

        :returns: the newly-created RenewalCert object
        :rtype: :class:`storage.renewableCert`

        """

        # Examine the configuration and find the new lineage's name
        for i in (cli_config.renewal_configs_dir,
                  cli_config.default_archive_dir, cli_config.live_dir):
            if not os.path.exists(i):
                os.makedirs(i, 0o700)
                logger.debug("Creating directory %s.", i)
        config_file, config_filename = util.unique_lineage_name(
            cli_config.renewal_configs_dir, lineagename)
        if not config_filename.endswith(".conf"):
            raise errors.CertStorageError(
                "renewal config file name must end in .conf")

        # Determine where on disk everything will go
        # lineagename will now potentially be modified based on which
        # renewal configuration file could actually be created
        lineagename = os.path.basename(config_filename)[:-len(".conf")]
        archive = os.path.join(cli_config.default_archive_dir, lineagename)
        live_dir = os.path.join(cli_config.live_dir, lineagename)
        if os.path.exists(archive):
            raise errors.CertStorageError("archive directory exists for " +
                                          lineagename)
        if os.path.exists(live_dir):
            raise errors.CertStorageError("live directory exists for " +
                                          lineagename)
        os.mkdir(archive)
        os.mkdir(live_dir)
        logger.debug("Archive directory %s and live "
                     "directory %s created.", archive, live_dir)

        # Put the data into the appropriate files on disk
        target = dict([(kind, os.path.join(live_dir, kind + ".pem"))
                       for kind in ALL_FOUR])
        for kind in ALL_FOUR:
            os.symlink(os.path.join(archive, kind + "1.pem"), target[kind])
        with open(target["cert"], "wb") as f:
            logger.debug("Writing certificate to %s.", target["cert"])
            f.write(cert)
        with open(target["privkey"], "wb") as f:
            logger.debug("Writing private key to %s.", target["privkey"])
            f.write(privkey)
            # XXX: Let's make sure to get the file permissions right here
        with open(target["chain"], "wb") as f:
            logger.debug("Writing chain to %s.", target["chain"])
            f.write(chain)
        with open(target["fullchain"], "wb") as f:
            # assumes that OpenSSL.crypto.dump_certificate includes
            # ending newline character
            logger.debug("Writing full chain to %s.", target["fullchain"])
            f.write(cert + chain)

        # Document what we've done in a new renewal config file
        config_file.close()

        # Save only the config items that are relevant to renewal
        values = relevant_values(vars(cli_config.namespace))

        new_config = write_renewal_config(config_filename, config_filename,
                                          archive, target, values)
        return cls(new_config.filename, cli_config)
Beispiel #4
0
    def new_lineage(cls, lineagename, cert, privkey, chain, cli_config):
        # pylint: disable=too-many-locals
        """Create a new certificate lineage.

        Attempts to create a certificate lineage -- enrolled for
        potential future renewal -- with the (suggested) lineage name
        lineagename, and the associated cert, privkey, and chain (the
        associated fullchain will be created automatically). Optional
        configurator and renewalparams record the configuration that was
        originally used to obtain this cert, so that it can be reused
        later during automated renewal.

        Returns a new RenewableCert object referring to the created
        lineage. (The actual lineage name, as well as all the relevant
        file paths, will be available within this object.)

        :param str lineagename: the suggested name for this lineage
            (normally the current cert's first subject DNS name)
        :param str cert: the initial certificate version in PEM format
        :param str privkey: the private key in PEM format
        :param str chain: the certificate chain in PEM format
        :param .NamespaceConfig cli_config: parsed command line
            arguments

        :returns: the newly-created RenewalCert object
        :rtype: :class:`storage.renewableCert`

        """

        # Examine the configuration and find the new lineage's name
        for i in (cli_config.renewal_configs_dir, cli_config.default_archive_dir,
                  cli_config.live_dir):
            if not os.path.exists(i):
                os.makedirs(i, 0o700)
                logger.debug("Creating directory %s.", i)
        config_file, config_filename = util.unique_lineage_name(
            cli_config.renewal_configs_dir, lineagename)

        # Determine where on disk everything will go
        # lineagename will now potentially be modified based on which
        # renewal configuration file could actually be created
        lineagename = lineagename_for_filename(config_filename)
        archive = full_archive_path(None, cli_config, lineagename)
        live_dir = _full_live_path(cli_config, lineagename)
        if os.path.exists(archive):
            raise errors.CertStorageError(
                "archive directory exists for " + lineagename)
        if os.path.exists(live_dir):
            raise errors.CertStorageError(
                "live directory exists for " + lineagename)
        os.mkdir(archive)
        os.mkdir(live_dir)
        logger.debug("Archive directory %s and live "
                     "directory %s created.", archive, live_dir)

        # Put the data into the appropriate files on disk
        target = dict([(kind, os.path.join(live_dir, kind + ".pem"))
                       for kind in ALL_FOUR])
        for kind in ALL_FOUR:
            os.symlink(os.path.join(_relpath_from_file(archive, target[kind]), kind + "1.pem"),
                       target[kind])
        with open(target["cert"], "wb") as f:
            logger.debug("Writing certificate to %s.", target["cert"])
            f.write(cert)
        with open(target["privkey"], "wb") as f:
            logger.debug("Writing private key to %s.", target["privkey"])
            f.write(privkey)
            # XXX: Let's make sure to get the file permissions right here
        with open(target["chain"], "wb") as f:
            logger.debug("Writing chain to %s.", target["chain"])
            f.write(chain)
        with open(target["fullchain"], "wb") as f:
            # assumes that OpenSSL.crypto.dump_certificate includes
            # ending newline character
            logger.debug("Writing full chain to %s.", target["fullchain"])
            f.write(cert + chain)

        # Write a README file to the live directory
        readme_path = os.path.join(live_dir, README)
        with open(readme_path, "w") as f:
            logger.debug("Writing README to %s.", readme_path)
            f.write("This directory contains your keys and certificates.\n\n"
                    "`privkey.pem`  : the private key for your certificate.\n"
                    "`fullchain.pem`: the certificate file used in most server software.\n"
                    "`chain.pem`    : used for OCSP stapling in Nginx >=1.3.7.\n"
                    "`cert.pem`     : will break many server configurations, and "
                                        "should not be used\n"
                    "                 without reading further documentation (see link below).\n\n"
                    "WARNING: DO NOT MOVE THESE FILES!\n"
                    "         Certbot expects these files to remain in this location in order\n"
                    "         to function properly!\n\n"
                    "We recommend not moving these files. For more information, see the Certbot\n"
                    "User Guide at https://certbot.eff.org/docs/using.html#where-are-my-"
                                        "certificates.\n")

        # Document what we've done in a new renewal config file
        config_file.close()

        # Save only the config items that are relevant to renewal
        values = relevant_values(vars(cli_config.namespace))

        new_config = write_renewal_config(config_filename, config_filename, archive,
            target, values)
        return cls(new_config.filename, cli_config)
Beispiel #5
0
def delete_files(config, certname):
    """Delete all files related to the certificate.

    If some files are not found, ignore them and continue.
    """
    renewal_filename = renewal_file_for_certname(config, certname)
    # file exists
    full_default_archive_dir = full_archive_path(None, config, certname)
    full_default_live_dir = _full_live_path(config, certname)
    try:
        renewal_config = configobj.ConfigObj(renewal_filename)
    except configobj.ConfigObjError:
        # config is corrupted
        logger.warning("Could not parse %s. You may wish to manually "
            "delete the contents of %s and %s.", renewal_filename,
            full_default_live_dir, full_default_archive_dir)
        raise errors.CertStorageError(
            "error parsing {0}".format(renewal_filename))
    finally:
        # we couldn't read it, but let's at least delete it
        # if this was going to fail, it already would have.
        os.remove(renewal_filename)
        logger.debug("Removed %s", renewal_filename)

    # cert files and (hopefully) live directory
    # it's not guaranteed that the files are in our default storage
    # structure. so, first delete the cert files.
    directory_names = set()
    for kind in ALL_FOUR:
        link = renewal_config.get(kind)
        try:
            os.remove(link)
            logger.debug("Removed %s", link)
        except OSError:
            logger.debug("Unable to delete %s", link)
        directory = os.path.dirname(link)
        directory_names.add(directory)

    # if all four were in the same directory, and the only thing left
    # is the README file (or nothing), delete that directory.
    # this will be wrong in very few but some cases.
    if len(directory_names) == 1:
        # delete the README file
        directory = directory_names.pop()
        readme_path = os.path.join(directory, README)
        try:
            os.remove(readme_path)
            logger.debug("Removed %s", readme_path)
        except OSError:
            logger.debug("Unable to delete %s", readme_path)
        # if it's now empty, delete the directory
        try:
            os.rmdir(directory) # only removes empty directories
            logger.debug("Removed %s", directory)
        except OSError:
            logger.debug("Unable to remove %s; may not be empty.", directory)

    # archive directory
    try:
        archive_path = full_archive_path(renewal_config, config, certname)
        shutil.rmtree(archive_path)
        logger.debug("Removed %s", archive_path)
    except OSError:
        logger.debug("Unable to remove %s", archive_path)
Beispiel #6
0
def renewal_file_for_certname(config, certname):
    """Return /path/to/certname.conf in the renewal conf directory"""
    path = os.path.join(config.renewal_configs_dir, "{0}.conf".format(certname))
    if not os.path.exists(path):
        raise errors.CertStorageError("No certificate found with name {0}.".format(certname))
    return path
Beispiel #7
0
    def new_lineage(cls, lineagename, cert, privkey, chain, cli_config):
        """Create a new certificate lineage.

        Attempts to create a certificate lineage -- enrolled for
        potential future renewal -- with the (suggested) lineage name
        lineagename, and the associated cert, privkey, and chain (the
        associated fullchain will be created automatically). Optional
        configurator and renewalparams record the configuration that was
        originally used to obtain this cert, so that it can be reused
        later during automated renewal.

        Returns a new RenewableCert object referring to the created
        lineage. (The actual lineage name, as well as all the relevant
        file paths, will be available within this object.)

        :param str lineagename: the suggested name for this lineage
            (normally the current cert's first subject DNS name)
        :param str cert: the initial certificate version in PEM format
        :param str privkey: the private key in PEM format
        :param str chain: the certificate chain in PEM format
        :param .NamespaceConfig cli_config: parsed command line
            arguments

        :returns: the newly-created RenewalCert object
        :rtype: :class:`storage.renewableCert`

        """

        # Examine the configuration and find the new lineage's name
        for i in (cli_config.renewal_configs_dir,
                  cli_config.default_archive_dir, cli_config.live_dir):
            if not os.path.exists(i):
                filesystem.makedirs(i, 0o700)
                logger.debug("Creating directory %s.", i)
        config_file, config_filename = util.unique_lineage_name(
            cli_config.renewal_configs_dir, lineagename)
        base_readme_path = os.path.join(cli_config.live_dir, README)
        if not os.path.exists(base_readme_path):
            _write_live_readme_to(base_readme_path, is_base_dir=True)

        # Determine where on disk everything will go
        # lineagename will now potentially be modified based on which
        # renewal configuration file could actually be created
        lineagename = lineagename_for_filename(config_filename)
        archive = full_archive_path(None, cli_config, lineagename)
        live_dir = _full_live_path(cli_config, lineagename)
        if os.path.exists(archive) and (not os.path.isdir(archive)
                                        or os.listdir(archive)):
            config_file.close()
            raise errors.CertStorageError("archive directory exists for " +
                                          lineagename)
        if os.path.exists(live_dir) and (not os.path.isdir(live_dir)
                                         or os.listdir(live_dir)):
            config_file.close()
            raise errors.CertStorageError("live directory exists for " +
                                          lineagename)
        for i in (archive, live_dir):
            if not os.path.exists(i):
                filesystem.makedirs(i)
                logger.debug("Creating directory %s.", i)

        # Put the data into the appropriate files on disk
        target = {
            kind: os.path.join(live_dir, kind + ".pem")
            for kind in ALL_FOUR
        }
        archive_target = {
            kind: os.path.join(archive, kind + "1.pem")
            for kind in ALL_FOUR
        }
        for kind in ALL_FOUR:
            os.symlink(_relpath_from_file(archive_target[kind], target[kind]),
                       target[kind])
        with open(target["cert"], "wb") as f:
            logger.debug("Writing certificate to %s.", target["cert"])
            f.write(cert)
        with util.safe_open(archive_target["privkey"],
                            "wb",
                            chmod=BASE_PRIVKEY_MODE) as f:
            logger.debug("Writing private key to %s.", target["privkey"])
            f.write(privkey)
            # XXX: Let's make sure to get the file permissions right here
        with open(target["chain"], "wb") as f:
            logger.debug("Writing chain to %s.", target["chain"])
            f.write(chain)
        with open(target["fullchain"], "wb") as f:
            # assumes that OpenSSL.crypto.dump_certificate includes
            # ending newline character
            logger.debug("Writing full chain to %s.", target["fullchain"])
            f.write(cert + chain)

        # Write a README file to the live directory
        readme_path = os.path.join(live_dir, README)
        _write_live_readme_to(readme_path)

        # Document what we've done in a new renewal config file
        config_file.close()

        # Save only the config items that are relevant to renewal
        values = relevant_values(vars(cli_config.namespace))

        new_config = write_renewal_config(config_filename, config_filename,
                                          archive, target, values)
        return cls(new_config.filename, cli_config)
Beispiel #8
0
def duplicate_lineage(config, certname, new_certname):
    """Create a duplicate of certname with name new_certname

    :raises .CertStorageError: for storage errors
    :raises .ConfigurationError: for cli and renewal configuration errors
    :raises IOError: for filename errors
    :raises OSError: for OS errors
    """

    # copy renewal config file
    prev_filename = renewal_filename_for_lineagename(config, certname)
    new_filename = renewal_filename_for_lineagename(config, new_certname)
    if os.path.exists(new_filename):
        raise errors.ConfigurationError("The new certificate name "
            "is already in use.")
    try:
        shutil.copy2(prev_filename, new_filename)
    except (OSError, IOError):
        raise errors.ConfigurationError("Please specify a valid filename "
            "for the new certificate name.")
    logger.debug("Copied %s to %s", prev_filename, new_filename)

    # load config file
    try:
        renewal_config = configobj.ConfigObj(new_filename)
    except configobj.ConfigObjError:
        # config is corrupted
        logger.warning("Could not parse %s. Only the certificate has been renamed.",
            new_filename)
        raise errors.CertStorageError(
            "error parsing {0}".format(new_filename))

    def copy_to_new_dir(prev_dir):
        """Replace certname with new_certname in prev_dir"""
        new_dir = prev_dir.replace(certname, new_certname)
        # make dir iff it doesn't exist
        shutil.copytree(prev_dir, new_dir, symlinks=True)
        logger.debug("Copied %s to %s", prev_dir, new_dir)
        return new_dir

    # archive dir
    prev_archive_dir = _full_archive_path(renewal_config, config, certname)
    new_archive_dir = prev_archive_dir
    if not certname in prev_archive_dir:
        raise errors.CertStorageError("Archive directory does not conform to defaults: "
            "{0} not in {1}", certname, prev_archive_dir)
    else:
        new_archive_dir = copy_to_new_dir(prev_archive_dir)

    # live dir
    # if things aren't in their default places, don't try to change things.
    prev_live_dir = _full_live_path(config, certname)
    prev_links = dict((kind, renewal_config.get(kind)) for kind in ALL_FOUR)
    if (certname not in prev_live_dir or
            len(set(os.path.dirname(renewal_config.get(kind)) for kind in ALL_FOUR)) != 1):
        raise errors.CertStorageError("Live directory does not conform to defaults.")
    else:
        copy_to_new_dir(prev_live_dir)
        new_links = dict((k, prev_links[k].replace(certname, new_certname)) for k in prev_links)

    # Update renewal config file
    def _update_and_write(renewal_config, temp_filename):
        renewal_config["archive_dir"] = new_archive_dir
        renewal_config["version"] = certbot.__version__
        for kind in ALL_FOUR:
            renewal_config[kind] = new_links[kind]
        with open(temp_filename, "wb") as f:
            renewal_config.write(outfile=f)
    _modify_config_with_tempfile(new_filename, _update_and_write)

    # Update symlinks
    return RenewableCert(new_filename, config, update_symlinks=True)
 def test_no_renewal_file(self, mock_renewal_conf_file, mock_make_or_verify_dir):
     mock_renewal_conf_file.side_effect = errors.CertStorageError()
     from certbot._internal import cert_manager
     self.assertEqual(cert_manager.lineage_for_certname(self.config, "example.com"), None)
     self.assertTrue(mock_make_or_verify_dir.called)