Skip to content

(Advanced) Manual HTTP Challenge Validation

Ryan Bolger edited this page Aug 7, 2018 · 9 revisions

(Advanced) Manual HTTP Challenge Validation

Intro

The beauty of the ACME protocol is that it's a (draft) open standard. And while Posh-ACME primarily targets users who want to avoid understanding all of the protocol complexity, it also exposes functions that allow you to do things a bit closer to the protocol level than just running New-PACertificate and Submit-Renewal. This can enable more advanced automation scenarios and allow you to support additional challenge types that the module doesn't directly support yet. This tutorial will focus on the latter and walk through how to generate a certificate using the http-01 challenge.

From a high level, the ACME conversation looks something like this:

  • Create an account
  • Create a certificate order
  • Prove control of the "identifiers" (names) in the requested cert by answering challenges
  • Finalize the order by submitting a CSR
  • Download the signed certificate

If you're curious about what's going on under the hood during this tutorial, it is advised to append -Verbose to your commands or run $VerbosePreference = 'Continue'. If you really want to get deep, you can also turn on debug logging by running $DebugPreference = 'Continue'. The defaults for both of those preferences are SilentlyContinue if you want to change them back later.

Server Selection

It is always advised not to use the production Let's Encrypt server while testing code. The staging server is the easiest alternative, but still has some rate limits that you can run afoul of if you're not careful. There is also Pebble which is a tiny ACME server you can self-host and is built for testing code against. For simplicity, we'll select the Let's Encrypt staging server.

Set-PAServer LE_STAGE

Account Setup

Requesting a certificate always starts with creating an account on the ACME server which is basically just a public/private key pair that is used to sign the protocol messages you send to the server along with some metadata like one or more email addresses to send expiration notifications to. If you've been previously using the module against the staging server, you likely already have an account. If so, you can either skip this section or create a second account which is also supported.

New-PAAccount -AcceptTOS -Contact 'me@example.com'

Create an Order

The only required parameter for a new order is the set of names you want included in the certificate. Optional parameters include things like -KeyLength to change the private key type/size, -Install which tells Posh-ACME to automatically store the signed cert in the Windows certificate store (requires local admin), and -PfxPass which lets you set the decryption password for the certificate PFX file.

In this example, we're not going to create a wildcard cert because they require using DNS challenge validation and we're going to be dealing with HTTP challenges.

New-PAOrder 'site1.example.com','site2.example.com'

Assuming you didn't use names that were previously validated on this account, you should get output that looks something like this where the status is pending. If the status is ready, create an order with different names that haven't been previously validated.

MainDomain        status  KeyLength SANs              OCSPMustStaple CertExpires
----------        ------  --------- ----              -------------- -----------
site1.example.com pending 2048      site2.example.com False

Authorizations and Challenges

The distinction between an order, authorization, and challenge can be confusing if you're not familiar with the ACME protocol. So let's clarify first. An order is a request for a certificate that contains one or more "identifiers", otherwise known as names like site1.example.com. Each identifier in an order has an authorization object associated with it that indicates whether this account is authorized to get a cert for that name. New authorizations start in a pending state awaiting the client to complete a challenge associated with that authorization. Each authorization can have multiple different challenges (DNS, HTTP, etc) that indicate the different methods the ACME server will accept to prove ownership of the name. You only need to complete one of the offered challenges in order to satisfy an authorization.

Retrieve Auth and Challenge details

Get-PAAuthorizations can be used with the output of Get-PAOrder to retrieve the current set of authorizations (and their challenges) for an order. So lets put those details into a variable and display them.

$auths = Get-PAOrder | Get-PAAuthorizations
$auths

This should give an output that looks something like this. The first status column is the overall status of the authorization. The last two columns are the status of the dns-01 and http-01 challenges. Normally the challenge specific details are buried a bit deeper in the challenges property, but Posh-ACME tries to help by pulling out the important bits into properties on root object.

fqdn              status  Expires              DNS01Status HTTP01Status
----              ------  -------              ----------- ------------
site1.example.com pending 8/13/2018 9:52:23 AM pending     pending
site2.example.com pending 8/13/2018 9:52:23 AM pending     pending

Let's take a look at the full details of one of the authorization objects by running $auths[0] | fl. You should get an output like this:

identifier   : @{type=dns; value=site1.example.com}
status       : pending
expires      : 2018-08-13T16:52:23Z
challenges   : {@{type=dns-01; status=pending; url=https://acme-staging-v02.api.letsencrypt.org/acme/challenge/<AUTH_ID>/<DNS_CHAL_ID>; token=<DNS_TOKEN>},
               @{type=http-01; status=pending; url=https://acme-staging-v02.api.letsencrypt.org/acme/challenge/<AUTH_ID>/<HTTP_CHAL_ID>; token=<HTTP_TOKEN>},
               @{type=tls-alpn-01; status=pending; url=https://acme-staging-v02.api.letsencrypt.org/acme/challenge/<AUTH_ID>/<ALPN_CHAL_ID>; token=<ALPN_TOKEN>}}
DNSId        : site1.mrn.dvolve.net
fqdn         : site1.mrn.dvolve.net
location     : https://acme-staging-v02.api.letsencrypt.org/acme/authz/<AUTH_ID>
DNS01Status  : pending
DNS01Url     : https://acme-staging-v02.api.letsencrypt.org/acme/challenge/<AUTH_ID>/<DNS_CHAL_ID>
DNS01Token   : <DNS_TOKEN>
HTTP01Status : pending
HTTP01Url    : https://acme-staging-v02.api.letsencrypt.org/acme/challenge/<AUTH_ID>/<HTTP_CHAL_ID>
HTTP01Token  : <HTTP_TOKEN>

The things we care about in this example are the HTTP01Url and HTTP01Token properties. The token value is what we're going to use to prove we control this identifier. The URL is how we inform the ACME server that it should perform the validation check for the challenge.