IP SAN error: "CSR contains IP address in Common Name"

I am trying out the IP address certificate issuance in Staging. I am using dehydrated version 0.7.2 as the client. It supports ACME profiles and IP SANs.

Out-of-the-box it almost works, and the challenges validate, but it fails at this final step:

{
  "type": "urn:ietf:params:acme:error:badCSR",
  "detail": "Error finalizing order :: CSR contains IP address in Common Name",
  "status": 400
}

Internally, dehydrated uses openssl req to create the CSR. It rejects a subject of empty string or //. If I omit the -subj argument it prompts me interactively for an X500-style one (country, locality, city etc), and if I do that, the certificate is issued successfully. The resulting certificate has an empty subject:

openssl x509 -in /etc/dehydrated-ipsan/certs/ipsan/cert.pem -noout -text
...
        Issuer: C = US, O = (STAGING) Let's Encrypt, CN = (STAGING) False Fennel E6
        Validity
            Not Before: Jul  3 09:57:48 2025 GMT
            Not After : Jul 10 01:57:47 2025 GMT
        Subject:
        Subject Public Key Info:
...
            X509v3 Subject Alternative Name: critical
                IP Address:xxx.xxx.xxx.193, IP Address:xxx:xxx:xxx:xxx:0:0:0:62

Hence I can fix the problem in dehydrated by setting a garbage subject:

--- dehydrated.orig	2025-07-03 10:04:07.747841033 +0000
+++ dehydrated	2025-07-03 10:56:08.896896518 +0000
@@ -1554,7 +1554,7 @@
       fi
     done
     if [[ "${domain}" =~ ^ip: ]]; then
-      SUBJ="/CN=${domain:3}/"
+      SUBJ="/DC=example/"
     else
       SUBJ="/CN=${domain}/"
     fi

My question is: given that Letsencrypt does not use the supplied Subject as part of the final certificate, why is it rejecting CSRs which contain CN=ip.ad.dr.ess but accepting all other subjects?

EDIT: I can generate a CSR with empty subject using -subj / (not -subj //) and that works fine. Link to dehydrated#783.

The CN field has been deprecated for years (see Certificate Common Name). Clients should never set the CN field in a CSR. Let's Encrypt omits the CN in their newer issuance profiles, such as tlsserver. I believe IP address certificates all fall under the new profile, so no CN present. The client should thus also omit the CN from the CSR.

I should note that I couldn't find technical or global policy restrictions that forbid this. RFC5280 doesn't really care what you put into the CN, and the baseline requirements explicitly allow an IP address in the CN field (BR section 7.1.4.3). However, it is deprecated practice to do so.

2 Likes

Indeed. It just seems strange to accept some garbage subjects, e.g. /DC=example/, but reject /CN=1.2.3.4/

2 Likes

Often times, situations like this are for an explicit purpose.

Error codes like that can usually be found in the Boulder source if you search for the string after :::

Git blame shows this line was from a PR a few days ago: Reject all CSRs with an IP in the CN (#8282) · letsencrypt/boulder@a1a7a7f · GitHub

Here is the commit message:

Reject all CSRs with an IP in the CN (#8282)
Although #8231 fixed
csr.CNFromCSR to ignore Common Names that are valid IPs, that didn't
fully solve our issue: identifier.FromCSR still extracts the CN and
assumes that it is a dnsName, leading to a mismatch between the CSR's
identifiers and the Order's identifiers.

Instead, let's outright reject all CSRs which carry an IP in their
Subject Common Name. Although this doesn't have the elegance of
rejecting such CNs on a profile-by-profile basis, it matches our ongoing
effort to do away with CNs entirely.
6 Likes

Thank you. That raises the question of what would have happened if the CSR contained something that looked like a domain name, like /CN=example.com/ or /CN=xyzzy.com/. The answer is, the request for that domain is rejected:

{
  "type": "urn:ietf:params:acme:error:rejectedIdentifier",
  "detail": "Error finalizing order :: Cannot issue for \"example.com\": The ACME server refuses to issue a certificate for this domain name, because it is forbidden by policy",
  "status": 400
}
{
  "type": "urn:ietf:params:acme:error:unauthorized",
  "detail": "Error finalizing order :: CSR does not specify same identifiers as Order",
  "status": 403
}

That now makes more sense. In short, your IP certificate request will be rejected if the subject has any commonName (which doesn't match a DNS SAN in the same CSR). If it contains something irrelevant like DC (domainComponent) then that will be ignored.

4 Likes

Something to be somewhat wary of, is the exact error type is not guaranteed and subject to change as the codebase changes and a more specific error type gets raised earlier in the logic.

There are a limited number of registered error type and they're often re-used (see boulder/probs/probs.go at main · letsencrypt/boulder · GitHub for a list of codes; note they can be raised under multiple circumstances ).

Different ACME servers may raise a different error type for the same situation; and few share the same detail.

5 Likes

The most important message IMO comes from the comment added in the code:

We want to get rid of CNs entirely anyway, and IP addresses are a new feature, so don't let clients get in the habit of including them in the CN.

While technically allowed, LE simply doesn't want to include it due to its deprecated status for this new feature that nobody has gotten change to get used to yet.

5 Likes

We initially tried to ignore an IP in the CN, but it turned a bit hairy actually implementing it. So we went for a more restrictive option to allow launching.

An IP address can be put in a CN so it is a valid “request” for us. And we do use the CSR CN to choose what to put in the cert CN, for profiles which have a CN enabled.

Perhaps once we inevitably fully remove CN support we can just entirely ignore the CSR field, but until then this is a bit of a paper cut unfortunately.

5 Likes