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