ACME: secondary subdomain certificate request problem

Hi,

We are investigating an issue using ACME (acme4j libraries using our own Java server) with certificate generation for secondary subdomains.
No problem when generating a certificate for a domain like xxx.mydomain.com. The request failed when requesting a certificate for yyy.xxx.mydomain.com with a urn:ietf:params:acme:error:malformed error from the server.
Is there any limitation or added constrains on the CSR that need to be filled when building the CSR for a secondary subdomain?

We are building the CSR using only domain name:
CSRBuilder csrb = new CSRBuilder();
csrb.addDomain(domain);
csrb.sign(domainKeyPair);

The CSR produced is the following:

-----BEGIN CERTIFICATE REQUEST-----
MIICrDCCAZQCAQAwKjEoMCYGA1UEAwwfanVsaWVuLmdlbnltb3Rpb24uc2hha2Fw
ZWFrcy5mcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKFqP9zEYGi1
llqC1Mm8KtrfE0tYob4vOGtcKtK5pdVIM83DORMs9hA3oOx4m24w6g4fOJm5u3O3
OfmLSLU2ne7I1lFngP8VF6Ovg5BnJbdHSkp8W79A7I1+6pXj/wjjUMwVJI8okDn7
2C0Fn+LKyKPkb3MyrjzSWLvBVqp+5OPwj5zZrypZM5eNUcLKBG+3dpL/WU6bWoyC
CHgzXt+KjjS/6Bd3MX7m7o0X3IoLbpWlrJ1Yu7f/+7JoB8AaynVTLBxo41ponqKJ
JMvYMGQO3u/QzHpsZ7IF2mvQPvluqZWAtJevc/Wm2/84J+Z8Sm8OA9mtklYqeyfG
GYdvOPGbIIkCAwEAAaA9MDsGCSqGSIb3DQEJDjEuMCwwKgYDVR0RBCMwIYIfanVs
aWVuLmdlbnltb3Rpb24uc2hha2FwZWFrcy5mcjANBgkqhkiG9w0BAQsFAAOCAQEA
cnEKrrSDRf1E7M8631IRB3dU/ZTvShcjLM0Vu5uG/ZJME5hbRyQMB8GoigPokviY
WhIdjua1uwdBVv4C89yw1ls8SQRNx/sI0gbbgueQT3+N5N++4vmn9VIeTJHjQpSt
ueoUcScQxAUOY/Zpc7uAKJpM0D8v7CKDW4cOlBAuDQqfy7bPgpZ2BBmLPMy6WQWS
8y4B7njre4Re4mWKAanfTbtBKM7q1b8iTSPjpPAr5ZRNtR7RH2d7u8ifGt7fGLXp
FV4BmR6/Juy976mOVcwRH0NzH79F3yJlk38aX4NGHk4hHBVEd83xr4kHtpJFDYpc
kfOgiB6uB259T3xaFffy4Q==
-----END CERTIFICATE REQUEST-----

The server replies with this error:
{"type":"urn:ietf:params:acme:error:malformed","detail":"Error unmarshaling finalize order request","status":400}

Note that when you submit your CSR to Let's Encrypt, it should be the Base64 encoded version of the CSR in DER format, without padding. So convert the CSR to DER (bytes), then get the base64 encoded version (without padding).

2 Likes

The content of the request sent to the server looks like this:
{"protected":"eyJ1cmwiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2ZpbmFsaXplLzE3MDIwNzQ4LzE5OTU5MDU4MSIsImtpZCI6Imh0dHBzOi8vYWNtZS1zdGFnaW5nLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2FjbWUvYWNjdC8xNzAyMDc0OCIsIm5vbmNlIjoiMDAwNC0teWtHcXRJVGJHekpvTGdyN3M1VFRjVGphOVdFbjNJRm5CS2dicTgtTkEiLCJhbGciOiJSUzI1NiJ9","payload":"eyJjc3IiOiJNSUlDeURDQ0FiQUNBUUF3UmpFb01DWUdBMVVFQXd3ZmFuVnNhV1Z1TG1kbGJubHRiM1JwYjI0dWMyaGhhMkZ3WldGcmN5NW1jakVhXG5NQmdHQTFVRUNnd1JSMlZ1ZVcxdmRHbHZiaUJFWlhacFkyVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCXG5BUUNoYWpfY3hHQm90WlphZ3RUSnZDcmEzeE5MV0tHLUx6aHJYQ3JTdWFYVlNEUE53emtUTFBZUU42RHNlSnR1TU9vT0h6aVp1YnR6XG50em41aTBpMU5wM3V5TlpSWjREX0ZSZWpyNE9RWnlXM1IwcEtmRnVfUU95TmZ1cVY0XzhJNDFETUZTU1BLSkE1LTlndEJaX2l5c2lqXG41Rzl6TXE0ODBsaTd3VmFxZnVUajhJLWMyYThxV1RPWGpWSEN5Z1J2dDNhU18xbE9tMXFNZ2doNE0xN2ZpbzQwdi1nWGR6Ri01dTZOXG5GOXlLQzI2VnBheWRXTHUzX191eWFBZkFHc3AxVXl3Y2FPTmFhSjZpaVNUTDJEQmtEdDd2ME14NmJHZXlCZHByMEQ3NWJxbVZnTFNYXG5yM1AxcHR2X09DZm1mRXB2RGdQWnJaSldLbnNueGhtSGJ6anhteUNKQWdNQkFBR2dQVEE3QmdrcWhraUc5dzBCQ1E0eExqQXNNQ29HXG5BMVVkRVFRak1DR0NIMnAxYkdsbGJpNW5aVzU1Ylc5MGFXOXVMbk5vWVd0aGNHVmhhM011Wm5Jd0RRWUpLb1pJaHZjTkFRRUxCUUFEXG5nZ0VCQURHb1laTERaNWx1cmpTaXF3OUJlVW0zWmxnS2JRS1JtV1JPREtuVVZOQ3hsUTMxUTJva3JZaW5mcHoyQTB0bTJ2b3F5Nl9rXG5ZYkNsVGYzcmVLdEdCZ3RKbjlIalZWOVdpWDVtOVdxTHZjMXJjT0hxT1Z2cmRxbkc4dXZLWGZlOGNWQzlTMXZhWE1na3FTZnZIMlNPXG53RVlRQUE5YUF1YUkza3lBQUcwNFZnb0k3SGtuc0syeXRIOE1aRGRpLThWZmlfaXdFWVlPVm40NDFtWEZZdWctbUFmWFFtandTa2ZOXG40ai1uNG9KQlMyR0k2UU5kLUhGWDVHNm1DSklUS2xjSWZrV1hSbDhrYWlKbExsemZ6eFN4NHpjSHRKZTVkSURwZ1FHc0kzQkZ0NEpWXG5NZVN1azJSR3hhUEVFZVFsRUkxNzI4MXZfcDRLV3hXSlFzTHZBU2tUS3BzXG4ifQ","signature":"MTeOEM1Q5bDeDaz4iihwLqIjw8HNahiM3HQSc5iDVvkMs_ylyLT0TG5_pPgIYajdmtu37_4N7FDP3-PUmqu8eX7D3PeEXbzBPHVHR4UKyqtav9-z4-Vkm94r_183U2dPgI5JSVxJUscMdnoDvgVQGQyKX-FA3FXAMeEuoZnIM4bES4jzoqEhAYGP7APhg4AYyE2I2wgn_bphTHEic5_A2EhBPb2sAHTHo94DPG7L4Mxs-aRa8mWPidE-gu-TxDAdL_emr1tMmbzy-XdISv9kyVtB3Q1CykfYoXeG0qjiDYZnWN71cAkAeYe82aBVY5xrHd89rtm56gV6Qcug4OkJ-A"}

The csr field of your payload contains encoded newlines (\n):

{"csr":"MIICyDCCAbACAQAwRjEoMCYGA1UEAwwfanVsaWVuLmdlbnltb3Rpb24uc2hha2FwZWFrcy5mcjEa\nMBgGA1UECgwRR2VueW1vdGlvbiBEZXZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB\nAQChaj_cxGBotZZagtTJvCra3xNLWKG-LzhrXCrSuaXVSDPNwzkTLPYQN6DseJtuMOoOHziZubtz\ntzn5i0i1Np3uyNZRZ4D_FRejr4OQZyW3R0pKfFu_QOyNfuqV4_8I41DMFSSPKJA5-9gtBZ_iysij\n5G9zMq480li7wVaqfuTj8I-c2a8qWTOXjVHCygRvt3aS_1lOm1qMggh4M17fio40v-gXdzF-5u6N\nF9yKC26VpaydWLu3__uyaAfAGsp1UywcaONaaJ6iiSTL2DBkDt7v0Mx6bGeyBdpr0D75bqmVgLSX\nr3P1ptv_OCfmfEpvDgPZrZJWKnsnxhmHbzjxmyCJAgMBAAGgPTA7BgkqhkiG9w0BCQ4xLjAsMCoG\nA1UdEQQjMCGCH2p1bGllbi5nZW55bW90aW9uLnNoYWthcGVha3MuZnIwDQYJKoZIhvcNAQELBQAD\nggEBADGoYZLDZ5lurjSiqw9BeUm3ZlgKbQKRmWRODKnUVNCxlQ31Q2okrYinfpz2A0tm2voqy6_k\nYbClTf3reKtGBgtJn9HjVV9WiX5m9WqLvc1rcOHqOVvrdqnG8uvKXfe8cVC9S1vaXMgkqSfvH2SO\nwEYQAA9aAuaI3kyAAG04VgoI7HknsK2ytH8MZDdi-8Vfi_iwEYYOVn441mXFYug-mAfXQmjwSkfN\n4j-n4oJBS2GI6QNd-HFX5G6mCJITKlcIfkWXRl8kaiJlLlzfzxSx4zcHtJe5dIDpgQGsI3BFt4JV\nMeSuk2RGxaPEEeQlEI17281v_p4KWxWJQsLvASkTKps\n"}

I think maybe you are using some weird way of generating this encoding. The right way would be to get the raw DER bytes (use a proper PEM decoder if necessary) and then maybe use acme4j's AcmeUtils.base64UrlEncode method to encode it.

We are relying in acme4j to build the request and send it to the server. CSR is encoded in base64 here:

I just put some debugging to get a dump of the request here:

Okay, that would suggest that the problem is with the data you're passing for the byte[] csr parameter. acme4j doesn't call any code which would be resulting in these newlines.

I think webprofusion is on the money and you're not providing the DER-encoded CSR.

1 Like

Ok I found the issue:
We replaced the java.util.Base64 function by Android Base64 encoder for older Android versions using Java 7.

The default implementation does not remove LF and we just need to add the NO_WRAP flag.

That was really weird that the CSR was failing with only domains with secondary subdomains, but it is just a length problem. We didn't face problem with shorted domains because the first LF was just placed after the field containing the domain name on the first Base64 line ....

Thanks you for your help pointing to the \n,
Julien

1 Like

So then it was actually always "broken" and had only "worked" by a lucky coincidence.
[some times luck lands in our favor :slight_smile:]

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.