From bd7b64f1e7932901cfb407e68c76d12de3b7637f Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Tue, 27 Jan 2026 20:42:52 -0800 Subject: [PATCH] Fix HTTP01.uri for IPv6 addresses (#10548) IPv6 addresses in URLs should be enclosed in square brackets. Note: I chose to fix this by parsing the identifier rather than changing the method signature. The obvious choice to change the method signature would be to take `messages.Identifier`. We could even do this backwards compatibly by taking `str | messages.Identifier`. However, `messages` imports `challenges`, so referencing `messages.Identifier` here would require an import loop. I also considered implementing a new method on, e.g. messages.Authorization that would take a challenge as a parameter. But this would be suboptimal because the `uri` method really is specific to the http-01 challenge type, so it's nice to have it implemented only on the relevant class. --- acme/src/acme/_internal/tests/challenges_test.py | 6 ++++++ acme/src/acme/challenges.py | 14 +++++++++++--- newsfragments/10548.fixed | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 newsfragments/10548.fixed diff --git a/acme/src/acme/_internal/tests/challenges_test.py b/acme/src/acme/_internal/tests/challenges_test.py index f95fecfb2..44b3f5dd2 100644 --- a/acme/src/acme/_internal/tests/challenges_test.py +++ b/acme/src/acme/_internal/tests/challenges_test.py @@ -244,6 +244,12 @@ class HTTP01Test(unittest.TestCase): assert 'http://example.com/.well-known/acme-challenge/' \ 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA' == \ self.msg.uri('example.com') + assert 'http://1.2.3.4/.well-known/acme-challenge/' \ + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA' == \ + self.msg.uri('1.2.3.4') + assert 'http://[::1]/.well-known/acme-challenge/' \ + 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA' == \ + self.msg.uri('::1') def test_to_partial_json(self): assert self.jmsg == self.msg.to_partial_json() diff --git a/acme/src/acme/challenges.py b/acme/src/acme/challenges.py index 03398d8a5..6b979e906 100644 --- a/acme/src/acme/challenges.py +++ b/acme/src/acme/challenges.py @@ -2,6 +2,7 @@ import abc import functools import hashlib +import ipaddress import logging from typing import Any from typing import cast @@ -365,17 +366,24 @@ class HTTP01(KeyAuthorizationChallenge): """ return '/' + self.URI_ROOT_PATH + '/' + self.encode('token') - def uri(self, domain: str) -> str: + def uri(self, identifier: str) -> str: """Create an URI to the provisioned resource. Forms an URI to the HTTPS server provisioned resource (containing :attr:`~SimpleHTTP.token`). - :param str domain: Domain name being verified. + :param str identifier: Domain name or IP address being verified. :rtype: str """ - return "http://" + domain + self.path + try: + # https://datatracker.ietf.org/doc/html/rfc2732#section-2 + # IPv6 addresses in URLs should be enclosed in brackets. + ipaddress.IPv6Address(identifier) + identifier = "[" + identifier + "]" + except ipaddress.AddressValueError: + pass + return "http://" + identifier + self.path def validation(self, account_key: jose.JWK, **unused_kwargs: Any) -> str: """Generate validation. diff --git a/newsfragments/10548.fixed b/newsfragments/10548.fixed new file mode 100644 index 000000000..b971d51a9 --- /dev/null +++ b/newsfragments/10548.fixed @@ -0,0 +1 @@ +The HTTP01.uri method will now properly enclose IPv6 addresses in square brackets.