Writing a plugin to get wild certificates Let's Encrypt

Hello again, dear reader. The second chapter on the adventures of Let's Encrypt in the ISPmanager panel is declared open. In the previous article, we discussed the plugin for ACME v01. In this talk about its evolution from the point of view of the logic of working with the user and, of course, about the ACME v02 protocol with support for wildcard certificates.



Excessive care


Trying to surround the user with care, you can go far. So far that he will not be able to work with functionality at all. And the first part of our story is just about that.

In developing the module, we wanted to save the client from a long preparation for issuing a certificate. For this, two restrictions were imposed: they allowed to order SSL only for web domains registered in the panel and only for those web domain aliases that the panel knows about.

Both restrictions seemed logical. The first did not allow to order certificates for non-existent domains and produce "dead" entities - certificates that will not be issued, because there is no place to put verification tokens. The second also eliminated unnecessary entities, but still did not allow ordering certificates for "*." - pseudonyms - at that time LE simply did not support such certificates.

Everything was fine until one day in LE the domain verification feature appeared through DNS records and the ability to order a certificate for the mail domain. Then, when ordering for a mail domain, we decided to add the following to pseudonyms: “mail”, “pop”, “smtp” - after all, certificates are most often connected to them. As a result, it turned out badly: there were users who initially set up their mail servers to completely different aliases. Due to our limitations when ordering, they could not add the required names.

Fortunately, we quickly realized and corrected the error, allowing users to specify the necessary data when ordering a certificate. Still, sometimes there is too much care :).

Wildcard


Now let's talk about the transition to ACME v02 , because only in this version of the LE protocol there is support for wildcard certificates. Let's start with a new, or rather, a changed directory:

curl -o- 'https://acme-v02.api.letsencrypt.org/directory' { "keyChange": "https://acme-v02.api.letsencrypt.org/acme/key-change", "mIU2Y2m2FsA": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417", "meta": { "caaIdentities": [ "letsencrypt.org" ], "termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf", "website": "https://letsencrypt.org" }, "newAccount": "https://acme-v02.api.letsencrypt.org/acme/new-acct", "newNonce": "https://acme-v02.api.letsencrypt.org/acme/new-nonce", "newOrder": "https://acme-v02.api.letsencrypt.org/acme/new-order", "revokeCert": "https://acme-v02.api.letsencrypt.org/acme/revoke-cert" } 

The first and most obvious difference is the keys are different :). In my opinion, they have become much more intuitive. The second difference is a separate URL for Replay-Nonce. This is done now like this:

 curl -LD - 'https://acme-v02.api.letsencrypt.org/acme/new-nonce' HTTP/1.1 204 No Content Server: nginx Replay-Nonce: QQgdAERh1MLQ6LHC0SVmB9OJXBcEWnwGB53CP0V4JlQ X-Frame-Options: DENY Strict-Transport-Security: max-age=604800 Expires: Sat, 02 Jun 2018 09:49:47 GMT Cache-Control: max-age=0, no-cache, no-store Pragma: no-cache Date: Sat, 02 Jun 2018 09:49:47 GMT Connection: keep-alive 

Nonce we, of course, still come in handy - and more than once.

Now let's talk about the unobvious changes that the transition to ACME v02 entails.

Just in case, let me remind you what our old POST request looked like to communicate with the first version of ACME :

 { "header": jws, // JSON Web Signature "protected": Base64Url(jws + Replay-Nonce), // Nonce —    "payload": Base64Url(payload), //  "signature": Base64Url(sign(protected.payload, private.pem)) //  } 

Now the general data structure will be different:

 { "protected": Base64Url(protected), "payload": Base64Url(payload), // "signature": Base64Url(sign(protected.payload, private.pem)) } 

As you can see, the "header" field is abolished. The preparatory stage, to the great joy of "lovers" of cryptography like me, has not changed at all: we will need all the same rsa keys, JWK and JWS (more on this in the first part ).

check in


To register a user, you only need to accept the user agreement and send a request to the "newAccount" from the directory.

 payload = {"termsOfServiceAgreed": true} 

And make up the correct protected:

 { "alg" : "RS256", "jwk" : jwk, \\ JSON Web Key “url” : url, \\       “nonce” : Replay-Nonce \\    } 

We form the request body, send it and ... we are not in a hurry! Accurately and carefully handle the response headers from ACME . Find a header called Location and save its contents. This is the so-called KID - the identification key of the newly registered user. All subsequent requests will have to keep this value protected in place of the JWK . Be careful : if you continue to send requests under the old scheme, only error messages will be the answer.

Here is our subsequent protected:

 { "alg" : "RS256", "kid" : kid, \\    “url” : url, \\        “nonce” : Replay-Nonce \\    } 

Certificate order


Getting ready to send a request to the directory ["newOrder"]. We add to the payload all the aliases of our web domain for which we are going to issue a certificate:

 payload ={ "identifiers":[ { "type":"dns", "value":"name1" }, ... { "type":"dns", "value":"nameN" } ] } 

Keep in mind that if you want to issue a wildcard certificate, then the names must contain only the main name and “*.” - an alias. The presence of any other names will result in a release error.

In response, we will receive JSON containing methods for confirming domain ownership and the URL that will be used to complete the issuance of the certificate.

 { "status":"pending", "expires":"2018-06-08T08:05:49.437251947Z", "identifiers":[ { "type":"dns", "value":"name1" }, { "type":"dns", "value":"www.name1" } ], "authorizations":[ //    "https://acme-v02.api.letsencrypt.org/acme/authz/Xp0a_...", "https://acme-v02.api.letsencrypt.org/acme/authz/o3Bvy..." ], "finalize":"https://acme-v02.api.letsencrypt.org/acme/finalize/..." //   } 

Further we receive detailed instructions on checks:

 curl -o- 'https://acme-v02.api.letsencrypt.org/acme/authz/Xp0a_...' { "identifier":{ "type":"dns", "value":"name1" }, "status":"pending", "expires":"2018-06-08T08:05:49Z", "challenges":[ { "type":"http-01", "status":"pending", "url":"https://acme-v02.api.letsencrypt.org/acme/challenge/Xp0a_.../4906756205", "token":"Me_cKM2Stu3iyCJQWEssho8Kj2nvRKuSJvIPF5tRyko" }, { "type":"dns-01", "status":"pending", "url":"https://acme-v02.api.letsencrypt.org/acme/challenge/Xp0a_.../4906756206", "token":"p-0xyySPQClTXVlgTxwJUvVOQtdHmNPpFht95bWrq8s" } ] } 

The confirmation process is completely different from what was implemented for ACME v01 . Please note: for a wildcard certificate, be sure to select the “dns-01” confirmation.

Getting a certificate


After the confirmation procedure, it remains to call the Finalize URL. Minor delays are possible, so a GET request to this address should be done until we get the following in the response:

 { "status": "valid", ///<    "expires": "2018-06-11T10:39:24Z", "identifiers": [ { "type": "dns", "value": "name1" }, { "type": "dns", "value": "name2" } ], "authorizations": [ "https://acme-v02.api.letsencrypt.org/acme/authz/Xp0a_...", "https://acme-v02.api.letsencrypt.org/acme/authz/o3Bvy..." ], "finalize": "https://acme-v02.api.letsencrypt.org/acme/finalize/...", "certificate": "https://acme-v02.api.letsencrypt.org/acme/cert/..." ///<  } 

The certificate will already contain a chain, so it is fully ready for work.

Compared with the first, the second version of ACME has become much more convenient and understandable. Writing integration has become even easier, given that the "cryptography" itself has not changed. I will be watching with interest the development of this amazing tool and will certainly come back here with fresh information if there are any important and useful changes.

Source: https://habr.com/ru/post/413429/


All Articles