Skip to content

Commit

Permalink
validate lineage name (#9644)
Browse files Browse the repository at this point in the history
Fixes #6127.

* Added lineage name validity check

* Verify lineage name validity before obtaining certificate

* Added linage name limitation to cli help

* Update documentation on certificate name

* Added lineage name validation to changelog

* Use filepath seperators to determine lineagename validity

* Add unittest for private choose_lineagename method

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
  • Loading branch information
NiekPeeters and bmw committed Apr 17, 2023
1 parent 996cc20 commit f416739
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 7 deletions.
3 changes: 2 additions & 1 deletion certbot/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).

### Changed

*
* Lineage name validity is performed for new lineages. `--cert-name` may no longer contain
filepath separators (i.e. `/` or `\`, depending on the platform).

### Fixed

Expand Down
2 changes: 2 additions & 0 deletions certbot/certbot/_internal/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ def prepare_and_parse_args(plugins: plugins_disco.PluginsRegistry, args: List[st
metavar="CERTNAME", default=flag_default("certname"),
help="Certificate name to apply. This name is used by Certbot for housekeeping "
"and in file paths; it doesn't affect the content of the certificate itself. "
"Certificate name cannot contain filepath separators (i.e. '/' or '\\', depending "
"on the platform). "
"To see certificate names, run 'certbot certificates'. "
"When creating a new certificate, specifies the new certificate's name. "
"(default: the first provided domain or the name of an existing "
Expand Down
37 changes: 32 additions & 5 deletions certbot/certbot/_internal/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ def obtain_and_enroll_certificate(self, domains: List[str], certname: Optional[s
referred to the enrolled cert lineage, or None if doing a successful dry run.
"""
new_name = self._choose_lineagename(domains, certname)
cert, chain, key, _ = self.obtain_certificate(domains)

if (self.config.config_dir != constants.CLI_DEFAULTS["config_dir"] or
Expand All @@ -521,8 +522,6 @@ def obtain_and_enroll_certificate(self, domains: List[str], certname: Optional[s
"Non-standard path(s), might not work with crontab installed "
"by your operating system package manager")

new_name = self._choose_lineagename(domains, certname)

if self.config.dry_run:
logger.debug("Dry run: Skipping creating new lineage for %s", new_name)
return None
Expand Down Expand Up @@ -559,13 +558,41 @@ def _choose_lineagename(self, domains: List[str], certname: Optional[str]) -> st
:returns: lineage name that should be used
:rtype: str
:raises errors.Error: If the chosen lineage name is invalid.
"""
# Remember chosen name for new lineage
lineagename = None
if certname:
return certname
lineagename = certname
elif util.is_wildcard_domain(domains[0]):
# Don't make files and directories starting with *.
return domains[0][2:]
return domains[0]
lineagename = domains[0][2:]
else:
lineagename = domains[0]
# Verify whether chosen lineage is valid
if self._is_valid_lineagename(lineagename):
return lineagename
else:
raise errors.Error(
"The provided certname cannot be used as a lineage name because it contains "
"an illegal character (i.e. filepath separator)." if certname else
"Cannot use domain name as lineage name because it contains an illegal "
"character (i.e. filepath separator). Specify an explicit lineage name "
"with --cert-name.")

def _is_valid_lineagename(self, name: str) -> bool:
"""Determines whether the provided name is a valid lineagename. A lineagename
is invalid when it contains filepath separators.
:param name: the lineage name to determine validity for
:type name: `str`
:returns: Whether the provided string constitutes a valid lineage name.
:rtype: bool
"""
return os.path.sep not in name

def save_certificate(self, cert_pem: bytes, chain_pem: bytes,
cert_path: str, chain_path: str, fullchain_path: str
Expand Down
19 changes: 19 additions & 0 deletions certbot/certbot/_internal/tests/client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,25 @@ def test_deploy_certificate_restart_failure2(self, mock_get_utility, mock_logger
installer.rollback_checkpoints.assert_called_once_with()
assert installer.restart.call_count == 1

def test_choose_lineage_name(self):
sep = os.path.sep
invalid_domains = [f"exam{sep}ple.com"]
valid_domains = ["example.com"]
invalid_certname = f"foo{sep}.bar"
valid_certname = "foo.bar"
invalid_wildcard_domain = [f"*.exam{sep}ple.com"]
# Verify errors are raised when invalid lineagename is chosen.
with pytest.raises(errors.Error):
self.client._choose_lineagename(invalid_domains, None)
with pytest.raises(errors.Error):
self.client._choose_lineagename(invalid_domains, invalid_certname)
with pytest.raises(errors.Error):
self.client._choose_lineagename(valid_domains, invalid_certname)
with pytest.raises(errors.Error):
self.client._choose_lineagename(invalid_wildcard_domain, None)
# Verify no error is raised when invalid domain is overriden by valid certname.
self.client._choose_lineagename(invalid_domains, valid_certname)


class EnhanceConfigTest(ClientTestCommon):
"""Tests for certbot._internal.client.Client.enhance_config."""
Expand Down
4 changes: 3 additions & 1 deletion certbot/docs/using.rst
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,9 @@ This returns information in the following format::

``Certificate Name`` shows the name of the certificate. Pass this name
using the ``--cert-name`` flag to specify a particular certificate for the ``run``,
``certonly``, ``certificates``, ``renew``, and ``delete`` commands. Example::
``certonly``, ``certificates``, ``renew``, and ``delete`` commands. The certificate
name cannot contain filepath separators (i.e. '/' or '\\', depending on the platform).
Example::

certbot certonly --cert-name example.com

Expand Down

0 comments on commit f416739

Please sign in to comment.