Responding to Challenges

Update : so I see that it's just the provided token concatenated with a SHA-256 of the previously used jwk. That's easy enough, though I'm not sure why not just use a digest like in other transmissions. Anyway, sending junk has apparently derailed this effort, so I'll have to start over, I guess :
"type": "urn:ietf:params:acme:error:orderNotReady",
"detail": "Order's status ("invalid") is not acceptable for finalization",
"status": 403

I've gotten to the point of challenges and I am receiving challenge requests. To the best of my discernment capacity, as gleaned from rfc's, responses from Boulder, etc. I decided to send back the following :

"type": "http-01",
"status": "pending",
"kid" : "<#######>",
"url": "",
"token": <*******>"
Does that make sense?

It does not make sense.

I don't completely understand your question, but it sounds like you've reached the point of telling the ACME server that you are ready to complete the HTTP-01 challenge, are receiving HTTP-01 validation requests from Let's Encrypt's validation perspectives, and are trying to figure out what content to put in your webserver's response body. Is that correct?

If yes, then the specification for HTTP-01 challenges is here: RFC 8555: Automatic Certificate Management Environment (ACME)
. The content of the response simply needs to be a "Key Authorization" constructed from the challenge token and the client's account key as described here: RFC 8555: Automatic Certificate Management Environment (ACME)


Lol, I see. Well, all that I 'heard' about it was that I was to be given a file to 'serve' from port 80. But, I suppose, behind the scenes, it is that the ACME 'client' creates the 'Key Authorization' and puts it into a file for the web server. But, since I'm creating both the web server AND ACME client, I need to create that file per the RFC. So, I'll research that and give it a try. It's actually a bit cryptic though, no pun intended. thanks for your input.

1 Like

It doesn't have to be a β€œfile” (i.e. a file in a filesystem on a disk). Since you're creating your own webserver you can, for example, serve the challenge response directly from your application's memory buffer. I haven't read Certbot's source, but I strongly suspect its standalone plugin works in a very similar fashion.


Here is an example of how, given the token and the account key, one other ACME client constructs the Key Authorization string.

And here is how that same client causes that Key Authorization string to be served at the correct path by writing a file inside an existing webserver's webroot. And this is how a different client serves the same Key Authorization from its own standalone in-memory webserver, as @Nekit suggested.

I dont know what language you're using to write your ACME client, but hopefully these examples will be helpful.


Yes, failing validation (by telling the server that you're ready to fulfill a challenge, but then serving the wrong value) immediately moves the Order into the "invalid" state. The ACME Client has to start over with a new new-order request. This should be easy: the whole point is that ACME clients are fully automated, and creating a new order should be fast and cheap.

That said, if you fail and start over too many times you will start running into rate limits, especially the "New Orders" limit and the "Failed Validations" limit. To help prevent running into these limits, make sure you are developing and testing your client against our staging environment.


Update :
I edited the json to be in lexicographical order and removed any spaces to get :
with a SHA-256 digest of : qSij9TMNZw32coF_FpEdNGXlQgq4MqnSoOXzI13zD6g=

Server doesn't still doesn't like my thumbprint : here's the data that I have if anyone can offer insight into as why, thanks (Reading the RFC, it sounds like the order is important, and I guess that makes sense from the Base64 coding of thing):
public String thumbPrintSHA256() {
n : 25553183770844410905671485977804526104682537963560136021659169992087787427207138311219468247446455669518899619326306701351306460515558569597021606926025547816922621445061649869924503486708426607943880536657576601738655432419937441833698159762915789801589355094201850065330642353342366171775449161142918382821130172377833577780453628433662752477628039460713810545782907830758578617795012876170549253248187144360844526044773827389253804336638769105963928782858203203246647002190816850255818430522300540045006644968751701665672153014284602231619731774661722467824698102672622470210702712128704089110157151103030689016289
e : 65537
json : {"kty":"RSA", "n":"AMprkqi7Fx9R8Hm8pIwYgM9yTgyxG7mUysavO8rhMGw2UpBR1CeqkLKZj35cclUp1PGYA3B_uA7TwVd02ibhxKpKwPJS3wk2I1L1Pzaa_tcVxwbO4n1Yj6CqOkYbQMEsydjXHGiVPdjXykj9W-bTlBtK-sCWO9wHZxssfSq6OgqWtBypi5vtz2DhAl2Q9zpmtKLIrwVdqo1U5rdPGQxcFRmndDPXi5vo64HcdRcMR6U0jzNXr73gq60BJ0F-xLJ3-zPmsi1EN0z3v0-fwcV6-UYF2h_9aKUGiB9A_WEAOcTe6iBdPW7JkqpteNqNFOH5Bk-8LdVSEOx8kEwUhi7IKeE", "e":"AQAB"}
Expected : cTWuxHJb7tbUWP_oIKZWsvdMcavGFwySwpa5pTuWBII
Getting : WBlejWusmrVlRmEFTD8evDg5IU1shiTpEvTfS77Pg7Q=
The code :
String digest = null;
try {
MessageDigest digester = MessageDigest.getInstance("SHA-256");
byte digested = digester.digest();
digest = Base64.getUrlEncoder().encodeToString(digested);
} catch (NoSuchAlgorithmException e1) {
return digest;

When I start with this JWK, the thumbprint that I get is cTWuxHJb7tbUWP_oIKZWsvdMcavGFwySwpa5pTuWBII. You can see my sample code here.

It looks like you have the right idea -- use only the "e", "kty", and "n" parameters of the key, remove all whitespace, compute the SHA256, then compute the base64url of that. I'm not sure where you're going wrong; I won't be able to tell without seeing the full code (e.g. the toJson function and the MessageDigest class) itself.

That said, I think you're going about this wrong. Please, please, do not implement your own code for handling JWKs. Whatever language you're working in should already have a library for handling JWKs, and that library should have functionality for computing the thumbprint of a key. It is very easy to get JWK logic wrong, and you'll be exposing yourself or your other users to horrible security vulnerabilities if and when you do.


Thanks! The code is at the bottom and is plain java with their libraries. I followed through a couple or those libraries of which you speak to confirm what they are using but yes, I am still getting an error.

1 Like

Since you're using Java, here is an example of an ACME client that computes the JWK thumbprint successfully. And here is the code in the underlying JWK library it uses, which you should almost certainly also use.

It seems likely that your error is either in the use of getBytes rather than getBytesUtf8, or that your toJson method (whose source you still haven't shared) doesn't quite do the right thing.


Ok, thanks. Looking at Brian Campbell's library for jose, I see that the offending scenario for my modulus was this :
if ((bigInteger.bitLength() % 8 == 0) && (twosComplementBytes[0] == 0) && twosComplementBytes.length > 1)
magnitude = ByteUtil.subArray(twosComplementBytes, 1, twosComplementBytes.length - 1);
That made me wonder why the other way worked with the RS256 signature, but I suppose that signature is an encode not a digest, so decoding it would work just the same.

1 Like

I got through to finalize but apparently the CSR is insufficient(The web page didn't like the 'NEW's on it but I'm not sure they went through the code submission)
"type": "urn:ietf:params:acme:error:malformed",
"detail": "Error parsing certificate request: asn1: syntax error: sequence truncated",
"status": 400

From RFC 8555, Section 7.4:

The CSR is sent in the base64url-encoded version of the DER format. (Note: Because this field uses base64url, and does not include headers, it is different from PEM.)

Don't send a PEM CSR, and definitely don't send the "BEGIN NEW CERTIFICATE REQUEST" headers. Send a base64url encoded DER CSR.

Also, I'll just warn you right now that your CSR is requesting fields that Let's Encrypt will reject: you are including information in the Subject (country, state, locality, organization, organizational unit) which cannot be contained in a Domain Validated certificate.


I just want to note 2 bits of general advice here:

1- Most of the concepts/steps above should be broken down into discrete functions that have unit tests. You shouldn't be trying to send any of this to an ACME server to iterate or troubleshoot your code - you should have functions that create/read these payloads, and unit tests that ensure they work correctly.

2- When you do need to test things against an ACME server, you should be using a local install of Pebble or Boulder. You should also have unit and integrated tests that run against those servers. You should be passing tests and scenarios locally before testing your client against the public ACME servers.


Well, I did try the web page. And of course, I have functions.

Yeah, I think I gleaned my info from the web page(and perhaps other sources) which did accept my CSR minus the ' NEW ' as posted above. I see in 8555 now that the data is NOT required for ACME. Thanks for offering that info.
Also, just to clarify a bit. I did try to send the csr with the new order request but NOT the finalize, so that alone would cause fatal error in the finalize. It really seemed to me like I read that in 8555 but I see now where it has the csr in the finalize.
Here's a final try, taking your advice into consideration as much as possible, though I haven't yet been able to remove some of the DN attributes. This CSR parses well with everywhere that I've tried:
And the error :
"type": "urn:ietf:params:acme:error:malformed",
"detail": "Error parsing certificate request: asn1: structure error: tags don't match (16 vs {class:1 tag:13 length:73 isCompound:false}) {optional:false explicit:false application:false private:false defaultValue:\u003cnil\u003e tag:\u003cnil\u003e stringType:0 timeType:0 set:false omitEmpty:false} certificateRequest @2",
"status": 400
Noticing that tag# 16 is plain ascii.
So, I dug into the library code creating the csr and found that it was mime Base64 encoding it and adding the tags and returns. And of course, then I was base64Url encoding it so that had to be bad. Now I get :
"type": "urn:ietf:params:acme:error:malformed",
"detail": "Error parsing certificate request: asn1: structure error: length too large",
"status": 400
which might be due to the 4096 key that I bumped it up to.
After switching to 2048, still getting :
"type": "urn:ietf:params:acme:error:malformed",
"detail": "Error parsing certificate request: asn1: structure error: length too large",
"status": 400
"type": "urn:ietf:params:acme:error:rejectedIdentifier",
"detail": "Error finalizing order :: Cannot issue for "sal secrets": Domain name contains an invalid character",
"status": 400
DONE :smiley:
BTW, the data that worked for letsencrypt as CSR would NOT compute with the parser sites.

@salsecrets The CSR text that you posted above is base64 encoded rather than base64url encoded. Somewhat annoyingly, these encodings are different. The base64url encoded version of your CSR is instead something like


What do you mean when you say this? What web page are you referencing? What did you try to do on that page?


Oh thanks! Well, I tried to use the encoder but apparently got missed. I'll try to fix it.
I used this java facility to encode it : Base64.getUrlEncoder().withoutPadding()

1 Like which did seem to require the begin and end of PEM, but with the ' NEW ' removed.