Api Acme /finalize

Hi everyone,
I'm creating an ssl generator for my website using only acme api.
I'm at /finalize (https://acme-staging02.api.letsencrypt.org/acme/finalize/userId/orderId)
I dont understand what i need to put in the payload.csr in the jose+json

PS : I'm coding in java using java spring boot.

If you made it that far, you probably know about RFC 8555.

What is it exactly you don't understand? Can you please be (a lot) more verbose?

7 Likes

Also, can you clarify if you're using any existing libraries, and if not why not (just as an academic exercise, or in an attempt to solve some problem the existing libraries don't, or something else?)

I would have expected more options to already exist, but the ACME Client List does point out one existing library that might be helpful, called acme4j.

7 Likes

I don't excatly know what i need to put in the payload part.
I did :

Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
StringBuilder sb = new StringBuilder("C=France, ST=IleDeFrance, L=Paris, O=Almuminune CN=").append(url);
X500Principal subject = new X500Principal(sb.toString());
PKCS10CertificationRequest pkcs10 = new PKCS10CertificationRequest(
    "SHA1withRSA",
    subject,
    keyPair.getPublic(),
    null,
    keyPair.getPrivate()
);
String payload = """
    {
        "csr": "%s"
    }
""".formatted(
    encoder.encodeToString(pkcs10.getEncoded())
);

But the request return me :
400 Bad Request: "{ "type": "urn:ietf:params:acme:error:malformed", "detail": "Error parsing certificate request: asn1: syntax error: sequence truncated", "status": 400}"

First comment: You should be getting these errors via a local unit test - not against an ACME server.

Second comment: The formatted part of the payload (csr value in the json) should be base64 encoded.

In Python, this would look like:

I have no idea what the java equivalent is

IMHO you should write a function that can encode this payload, then write a unit test to correctly encode the output, compare it to an expected value, and then correctly decode the output to ensure a "round trip" is possible. Once that is done, then you can test against a local ACME server like GitHub - letsencrypt/pebble: A miniature version of Boulder, Pebble is a small RFC 8555 ACME test server not suited for a production certificate authority.

7 Likes

Well, RFC 8555, section 7.4: Applying for Certificate Issuance is pretty clear to me:

Once the client believes it has fulfilled the server's requirements,
it should send a POST request to the order resource's finalize URL.
The POST body MUST include a CSR:

csr (required, string): A CSR encoding the parameters for the
certificate being requested [RFC2986]. 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.)

   POST /acme/order/TOlocE8rfgo/finalize HTTP/1.1
   Host: example.com
   Content-Type: application/jose+json

   {
     "protected": base64url({
       "alg": "ES256",
       "kid": "https://example.com/acme/acct/evOfKhNU60wg",
       "nonce": "MSF2j2nawWHPxxkE3ZJtKQ",
       "url": "https://example.com/acme/order/TOlocE8rfgo/finalize"
     }),
     "payload": base64url({
       "csr": "MIIBPTCBxAIBADBFMQ...FS6aKdZeGsysoCo4H9P",
     }),
     "signature": "uOrUfIIk5RyQ...nw62Ay1cl6AB"
   }

The CSR encodes the client's requests with regard to the content of
the certificate to be issued. The CSR MUST indicate the exact same
set of requested identifiers as the initial newOrder request.
Identifiers of type "dns" MUST appear either in the commonName
portion of the requested subject name or in an extensionRequest
attribute [RFC2985] requesting a subjectAltName extension, or both.
(These identifiers may appear in any sort order.) Specifications
that define new identifier types must specify where in the
certificate signing request these identifiers can appear.

That said, I have no clue what so ever how that would be working in Java.

But the following error:

Suggests your CSR is not conforming to the ASN1 standard, at least not according to the Go ASN1 library used by Boulder.

You should debug the output of your code with ASN1 linters et cetera.

6 Likes

I try to generate the csr with openssl but that doesn't work too :
"{ "type": "urn:ietf:params:acme:error:malformed", "detail": "Error unmarshaling finalize order request", "status": 400}"

The commande OPENSSL :

C:\Users\Mascret\IdeaProjects\devops>openssl req -new -key private.key -out request.csr

Country Name (2 letter code) [AU]:FR
State or Province Name (full name) [Some-State]:I-F
Locality Name (eg, city) []:Paris
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Almuminune
Organizational Unit Name (eg, section) []:AlmuminuneDev
Common Name (e.g. server FQDN or YOUR name) []:al-muminune.org
Email Address []:mmascret94@gmail.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

PS :
al-muminune.org is the url i want to certificate

The private.key file is the private key i generate to create the account.

cznQMuv5AvoFHkjOxf759g3xSHNvyrKn-8Sv9Pjf-vM.qoadYk9jHQmUBzwVU6gxtMDKBzf4wOKnCm5NvmiUscw is the token that's i return in my challenge (token+ . + static_part_of_account), should i use it ?

The private key used for the CSR should be the same private key as the public key used for the certificate, not the accounts private key.

The token has nothing to do with the CSR.

You shouldn't use any challenge password. At least, I'm never asked about it. Your CSR should either contain a CommonName with the hostname or use a SAN. The "Organization Name" is irrelevant and should not be the place you put the hostname.

3 Likes

I don't know what to do, i tried this :

Create a csr with org.shredzone.acme4j
Create a csr with openssl
Create a der file
Using pem file
org.bouncycastle.jce

None of this is working

Hi @protypangel,

I'm sorry that you're having so much difficulty getting the Finalize request to work. I have a few pieces of advice to move forward from here:

  1. If at all possible, switch away from writing your own ACME client and instead use one of the many ACME client libraries that already exist. I know that ACME4j's "finalize" implementation only takes the keypair as input, and then builds the CSR itself so that you don't have to.

  2. If you truly want to proceed down the path of creating your own ACME client, then you need to treat it like science: change only one thing at a time, and carefully document and share both your inputs and your outputs.

For example, saying that you have tried five different methods of creating a CSR is useful, as it suggests that something other than the CSR itself is the issue here. But what would be more useful is sharing everything: all of the commands that you've used to create CSRs, all of the CSR files that have been created as a result of those commands, all of the code that you've used to try to submit those CSRs to an ACME API, and all of the error messages you've gotten in return. We can't help when all we know is "it didn't work". In order to help, we need to know in excruciating detail exactly what didn't work.

6 Likes

There is a comma missing between the O= and CN= part. The rest looks good so far at first glance.

2 Likes

While true, it is worth noting that Let's Encrypt cannot issue a certificate containing any of these fields except for CN. LE only issues Domain Validated certificates, which cannot contain other Subject attributes, because validation of those other fields cannot be automated.

6 Likes

Wouldn't the missing comma blur the "CN=" in with the "O=" [and then LE wouldn't actually see it]?

3 Likes

And how would Boulder parse the generated CSR? Successfully or error out with one of the errors from this thread?

1 Like

The comma doesnt still change the problem

Please see @aarongable excellent post above, especially the "In order to help, we need to know in excruciating detail exactly what didn't work." part. Just saying "still doesn't change the problem" won't get you anywhere fast.

3 Likes

I need to only put the CN variable ?
Like this ?

StringBuilder sb = new StringBuilder("CN=").append(url);

When i do it that mark the same error : 400 Bad Request: "{ "type": "urn:ietf:params:acme:error:malformed", "detail": "Error parsing certificate request: asn1: syntax error: sequence truncated", "status": 400}"

@Osiris I use the api of LE and i just want to finish an order which i succed and after it download the certificat, so i need to generate a valide array of char (string) that's can be sended to the api.
I tried to generate by many way like PKCS10CertificationRequest / openssl / ... but that still doesnt work (the csr part of the api)

That all was already mentioned in this thread. What is lacking however is "working" (i.e.: compiling/running) pieces of code, complete logs and/or complete send CSRs et cetera.

2 Likes

I'm using the api, i just need to know what i need to put in the csr field.
The error of LE api is 400 Bad Request: "{ "type": "urn:ietf:params:acme:error:malformed", "detail": "Error parsing certificate request: asn1: syntax error: sequence truncated", "status": 400}"

I'm coding from craft so you don't need my global code.
I just need help to understand how to create the csr field.
Like i said either package advice of openssl work.
So the problem may come to the api at this stage.

If you want the body to test :

{
    "protected": "ewogICAgInVybCI6ICJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2ZpbmFsaXplLzE0MzE1NTYwNC8xNTc0MjM4MTA0NCIsCiAgICAibm9uY2UiOiAielI1emkxMEpyVF91YU9XLWkzeXduX25jRlJoNzVEUnFfZWd0eS0tNFBsRVBJM19DRDRrIiwKICAgICJraWQiOiAiaHR0cHM6Ly9hY21lLXN0YWdpbmctdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9hY2N0LzE0MzE1NTYwNCIsCiAgICAiYWxnIjogIlJTMjU2Igp9Cg",
    "payload": "ewogICAgICAgICJjc3IiOiAiTUlJQ1hUQ0NBVVVDQVFBd0dqRVlNQllHQTFVRUF4TVBZV3d0YlhWdGFXNTFibVV1YjNKbk1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBNHNTMm5KeGstYm5xd3ZNZjFHSWN4UmgtdWZGSXZGX0xvRDJlNEpJY1VFakpidUJaX1YybGRqbDhWcU5FMHBqaUlTWENydHlMSWlVc29UdlN0bEU3TU1GYlBWMjZoUzhDTmpLUDR4OTNaT2tFTFpNZFBwRTRJSXduMU5Da1lVMlhTSkV6LTZVMXVGbFRYUC0yYmc1MlZxUUkzcGQ4YU5yeVVzS0ZXQms0dmxuQ1UySDMyN2VXcnZJRzV0ODJ6N1hIc3VubF82MlBxVXhWdDY0c092MzhRQzU5TlFMVkN4cXF4WnZCci1lUnV4SWhxSzNvbFdrNnRLVXNQLUJqQ0hfR3pMZmNqLVJXcWhTZTFuOXQ0Mnl4SVp2dHMySmphVXZyVmlncVlNV1NzUTYya0JIcVNFSWs2RUVTY1ZIVjJ2d1pralZEZGdyN2hjSU5FUmJHX0RQaEZ3SURBUUFCTUEwR0NTcUdTSWIzRFFFQkJRVUFBNElCQVFEZ3k1RG5USEdfbUtLV2VkNkFEWF80eElXRE5keFhaZmRBV3hiSUJLd2lXSkJmQUE4U3FpdXVDNEhMdE9CbXJraUZpbS1iMlA0emQ3dUNINWI5OW9VZ0w5Q1RteW40WC1fZ1c0RVlUYWlzU1BaXzEwWm5XUkV6VWlrd3lzUkFHTU5PZ0NWYllvSUhyTVk2dG9ic0lnaGpBanJBdXhSUXh3dW15OFltdUxlY1VhNWs5WmpRVmt1MnlkbkN6QTMwTFNKUjd5blZreFFmMVcwS1lBT0NfNmhRMWhoSWFFeWEtVTJob1Y1VEg0anMyOFNXa0kxdUs5VTQyZUdPdDlmNkZ1X21LbGpaandpYXFoM0h1ZkhrYThSZ0tUaDBQVlpmSmJzczR3eVRsNkR2NnR1SXY0ejZ3cmF3LTJ3YnNpV2p1RFQyUDlQcnJ4ZnBwREcxcXFCWmZmUFoiCn0K",
    "signature": "joo14YnkXFFpqq4BgWO70hdquFEVw_AigN-y-i8tVnTzFeuS4Vi3eQiNeVkkkD31MxAwscoY3H33PMeyg74o7ippnXzq2RaBuOXGzCltL2h7_G61mV-th2oVzFHmI7VmnMPTCLrfu6ONwxfpf3AjE20hdQcI41bmV3KoGBGfJfncYaL7f7o8dcG1ym84UFMPQ9dpFiXaQd85HcKvZKL54TG-JuE7INHEOyNlJH4GVZlaeOX1XpKu8RP6j-Gdz-EkMbQtA_eWpqF5iC56CyB-gQku2Tj-eKCxjRCFPWFiLcunt90eveEMLWNvKtFZwgsQDi-lBxwU3qG-zdwkXyyWcg"
}

The url i used : https://acme-staging-v02.api.letsencrypt.org/acme/finalize/143155604/15742395884
public_key.pem (460 Bytes)

Protected :

{
    "url": "https://acme-staging-v02.api.letsencrypt.org/acme/finalize/143155604/15742395884",
    "nonce": "zR5zi10Jn6-St_j-Aanhc30DFZLAW_m6ngFxx-PDyEI6Pzllocw",
    "kid": "https://acme-staging-v02.api.letsencrypt.org/acme/acct/143155604",
    "alg": "RS256"
}

Protector :

{
        "csr": "MIICXTCCAUUCAQAwGjEYMBYGA1UEAxMPYWwtbXVtaW51bmUub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4sS2nJxk-bnqwvMf1GIcxRh-ufFIvF_LoD2e4JIcUEjJbuBZ_V2ldjl8VqNE0pjiISXCrtyLIiUsoTvStlE7MMFbPV26hS8CNjKP4x93ZOkELZMdPpE4IIwn1NCkYU2XSJEz-6U1uFlTXP-2bg52VqQI3pd8aNryUsKFWBk4vlnCU2H327eWrvIG5t82z7XHsunl_62PqUxVt64sOv38QC59NQLVCxqqxZvBr-eRuxIhqK3olWk6tKUsP-BjCH_GzLfcj-RWqhSe1n9t42yxIZvts2JjaUvrVigqYMWSsQ62kBHqSEIk6EEScVHV2vwZkjVDdgr7hcINERbG_DPhFwIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQDgy5DnTHG_mKKWed6ADX_4xIWDNdxXZfdAWxbIBKwiWJBfAA8SqiuuC4HLtOBmrkiFim-b2P4zd7uCH5b99oUgL9CTmyn4X-_gW4EYTaisSPZ_10ZnWREzUikwysRAGMNOgCVbYoIHrMY6tobsIghjAjrAuxRQxwumy8YmuLecUa5k9ZjQVku2ydnCzA30LSJR7ynVkxQf1W0KYAOC_6hQ1hhIaEya-U2hoV5TH4js28SWkI1uK9U42eGOt9f6Fu_mKljZjwiaqh3HufHka8RgKTh0PVZfJbss4wyTl6Dv6tuIv4z6wraw-2wbsiWjuDT2P9PrrxfppDG1qqBZffPZ"
}