Issue with Domain Verification Using ACME v1 in Custom Client

Hello,

I’m experiencing an issue with domain verification while using a custom ACME client based on the acme-tiny library. The client has been functioning correctly, but it suddenly started failing during the verification process. Below are the details of the error and the relevant code snippets.

Error Details

Order created! {'status': 'pending', 'expires': '2024-12-02T05:59:03Z', 'identifiers': [{'type': 'dns', 'value': ab.com'}], 'authorizations': ['https://acme-staging-v02.api.letsencrypt.org/acme/authz/XXX/xxx'], 'finalize': 'https://acme-staging-v02.api.letsencrypt.org/acme/finalize/XXX/XXX'}

When attempting to verify the domain, I receive the following error:

Response Code: 404
Response: {'type': 'urn:ietf:params:acme:error:malformed', 'detail': 'No such challenge', 'status': 404}

Code Snippet

Here is the relevant part of the code where the domain verification occurs:

def post_verify_domain(self, domain, path, keyauthorization, challenge, is_apex=False, is_cdn=False):
    authoritative_ip, auth_cname = get_domain_ip_authoritative_nameserver(domain, allow_multiple_ips=is_cdn)

    host = authoritative_ip
    wellknown_url = "http://{}{}".format(host, path)
    resp_data = None
    try:
        session = requests.Session()
        session.mount('http://', HostHeaderSSLAdapter(custom_domain=domain, domain_to_replace=authoritative_ip))
        session.mount('https://', HostHeaderSSLAdapter(custom_domain=domain, domain_to_replace=authoritative_ip))
        resp_data = session.get(wellknown_url, headers={"User-Agent": "Custom Client"}, verify=False, timeout=5).text.strip()

        if resp_data != keyauthorization:
            raise AssertionError()
    except (IOError, AssertionError):
        raise TokenComparisonFailedException(
            "Failed token comparison; Expected token: {} - Received token: {}".format(keyauthorization, resp_data or "No token due to Request Exception")
        )

    # Submit the challenge
    try:
        self._send_signed_request(challenge['url'], {}, "Error submitting challenges: {0}".format(domain))
    except Exception as e:
        raise LetsEncryptVerificationFailedException("{}".format(e))

    # Check authorization status
    auth_url = challenge['url'].split('/')[:-1]
    auth_url[-2] = 'authz-v3'
    auth_url = '/'.join(auth_url)

    try:
        authorization = self._poll_until_not(auth_url, ["pending"], "Error checking challenge status for {0}".format(domain))
        if authorization['status'] != 'valid':
            raise LetsEncryptVerificationFailedException("Challenge did not pass for {0}: {1}".format(domain, authorization))
    except TimeoutError:
        raise LetsEncryptVerificationFailedException("Timeout for challenge of domain {0}: pending too long".format(domain))

Additional Information

  • The client is using ACME v02, and I have verified that the DNS records are correctly set up.
  • The challenge URL is being constructed from the authorization response, but it seems to be returning a 404 error.

Request for Help

I would appreciate any insights or suggestions on how to troubleshoot this issue further. Are there any common pitfalls with domain verification in ACME that I should be aware of?

Thank you for your assistance!

1 Like

The 404 "not found" error from the ACME server you've shown at the top of your post is not related to the actual challenge, but somewhere else in the process: you're trying to "access" an order on the ACME server that's not there.

2 Likes

You're making invalid assumptions about the structure of the URLs that we return. There is no guarantee that there will be a relationship like this between the challenge url and the authz url. You need to use exactly the URLs provided to you by ACME, not manipulate them like this.

Some of our URLs just changed, but that would have been invisible to you if your client was simply using them blindly. In particular, I think this diagnosis is incorrect:

I believe what is actually happening is that your manipulation of the challenge URL to create the authz URL is failing, so when you attempt to poll the authz URL you're actually polling a mangled challenge URL, and that's where the error is coming from.

9 Likes