この記事の概要
- Automatic Certificate Management Environment(ACME)で証明書を発行するプロセスを理解する為のメモ書きです。
- ACMEサーバーを実際に立ち上げ手を動かすことで理解を深めたいと思います。
- この記事では、ACMEのアカウントを作成するところまで書きます。
ACMEについて
- ACMEは、RFC 8555で定められた証明書発行に関する仕組みです。
- 様々な認証局で、アドホックに行われていたサーバー証明書発行手順を標準化する目的で作られています。
- Let's EncryptなどのサービスがACMEを利用しています。
RFC 8555 - Automatic Certificate Management Environment (ACME)
進め方
同RFCの22ページ目(7.1 Resources)に、次の通りフローが記載されていましたので、これに沿って進めてみます。
The following table illustrates a typical sequence of requests required to establish a new account with the server, prove control of an identifier, issue a certificate, and fetch an updated certificate some time after issuance. The "->" is a mnemonic for a Location header field pointing to a created resource.
Action | Request | Response |
---|---|---|
Get Directory | GET directory | 200 |
Get nonce | HEAD newNonce | 200 |
Create account | POST newAccount | 201 -> account |
Submit order | POST newOrder | 201 -> order |
Fetch challenges | POST-as-GET order's authorization urls | 200 |
Respond to challenges | POST authorization challenge urls | 200 |
Poll for status | POST-as-GET order | 200 |
Finalize order | POST order's finalize url | 200 |
Poll for status | POST-as-GET order | 200 |
Download certificate | POST-as-GET order's certificate url | 200 |
準備
ACMEに対応した認証局システムを準備します。 Let's Encryptより、ACMEプロトコルのテストベッドとして、Pebbleというシステムが提供されているので、 それを動かしてみます。
$ go get -u github.com/letsencrypt/pebble/... $ cd $GOPATH/src/github.com/letsencrypt/pebble && go install ./...
pebbleを起動しておきます。
$ pebble -config ./test/config/pebble-config.json Pebble 2020/10/10 15:16:39 Starting Pebble ACME server 省略 Pebble 2020/10/10 15:16:39 Listening on: 0.0.0.0:14000 Pebble 2020/10/10 15:16:39 ACME directory available at: https://0.0.0.0:14000/dir
やってみる
Get Directory
まずは、Get Directoryを行うことで、ACMEで行う各種操作のためのURLを取得します。 DirectoryのURL自体は、事前にクライアントに設定しておく必要があります。 Pebbleであれば、起動時にURLが出力されていました。
$ curl --insecure https://localhost:14000/dir { "keyChange": "https://0.0.0.0:14000/rollover-account-key", "newAccount": "https://0.0.0.0:14000/sign-me-up", "newNonce": "https://0.0.0.0:14000/nonce-plz", "newOrder": "https://0.0.0.0:14000/order-plz", "revokeCert": "https://0.0.0.0:14000/revoke-cert", "meta": { "externalAccountRequired": false, "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" }, }
応答として以下の6つとmeta
というフィールドが定義されています。
- newNonce
- newAccount
- newOrder
- newAuthz
- revokeCert
- keyChange
newAuthz
については、ACMEサーバー側がpre-authorization
という仕組みに対応しない場合は、省略されます。
上記応答を見ると、pebbleでは対応していないようです。
更に、meta
フィールドの中身については、次のフィールドがあります。
- termsOfService
- website
- caaIdentities
- externalAccountRequired
上記4つのmetaフィールドの使用例は以下の通りです。
"meta": { "termsOfService": "https://example.com/acme/terms/2017-5-30", "website": "https://www.example.com/", "caaIdentities": ["example.com"], "externalAccountRequired": false }
Get nonce
ACMEクライアントが、サーバーにPOSTでアクセスする際にはnonceを送信する必要があります。 既存のnonceが有効でなくなってしまったり、初めてサーバーにアクセスする際に、nonceを取得する必要があります。
先ほどのDirectoryのnewNonce
のURLに対してHEADメソッドで要求すると、nonceを取得できます。
nonceはReplay-Nonce
フィールドに記載されています。
$ curl --insecure --head https://localhost:14000/nonce-plz HTTP/2 200 cache-control: public, max-age=0, no-cache link: <https://localhost:14000/dir>;rel="index" replay-nonce: wUoLKrGFi1i8rmbHn40Wyg date: Sat, 10 Oct 2020 14:20:54 GMT
取得したnonceは、リクエストボディのJWSの中に記載します。 具体的には以下のような形で使用します。
POST /acme/new-account HTTP/1.1 Host: example.com Content-Type: application/jose+json {"protected": base64url( {"alg": "ES256", "jwk": {...}, "nonce": "6S8IqOGY7eL2lsGoTZYifg", "url": "https://example.com/acme/new-account" }), "payload": base64url( {"termsOfServiceAgreed": true, "contact": ["mailto:cert-admin@example.org", "mailto:admin@example.org"]}), "signature": "RZPOnYoPs1PhjszF...-nh6X1qtOFPB519I" }
Create account
ディレクトリのnewAccount
にて取得したURLに対して、以下のようにJWSメッセージをPOSTすることでアカウントを作成します。
$ cat createaccount_message | jq . { "protected": "eyJhbGciOiJSUzI1NiIsIm5vbmNlIjoiWjBMeVpkUWtlNzlURndmdDRYMExJdyIsInVybCI6Imh0dHBzOi8vbG9jYWxob3N0OjE0MDAwL3NpZ24tbWUtdXAiLCJqd2siOnsibiI6IndRcFJQVDZPN1RNSkExWURtazdVLVhsSnZ6TElMSTl3U0ZKSmQ2Z1ZmTzBuOVRUU2VGbHlMYXBVMWZYeU4xU2xJM3dhWm9pTElSSnN3ME9OMUd3NkFLVDhvd1BVZ0lPWTFVLXZRTHF2OTVKOXJVcUlnRGdMQ3dsQmNiVzZJUV9Dckdlb2RuR3VnWFZKYXRoTmt4NmJIS2xOT2ZHUmpMTTJFOWZvbWhReDVIWkRvUTV2R3NtUUVsX01SOUVkd042SzNnVGx5YzBTb1FxU0dwOTJEZ19XODdhelg0eF9hU0pNUDFrLXJSU0V5anc2cVVyVUlKcGdZazdYY3BiS01ZTGR0Si1QdmtlM2x6c0pHdGV3YnRRSWhmZzczZF90WU1Kd3ZfMHlZQ3kySGRaekVkRU43RjZZQjczaFBjQVB1MUdhMjNjVzRZNUFHZFJfSlNRbDFEWTRuUSIsImUiOiJBUUFCIiwia3R5IjoiUlNBIn19", "signature": "VrYli0NVuAY4YeX8X97fBPHP4a6KQv0fLph4WB6e4J1tu-aCaZtrWpB2vICCjfjQH9rjnjTO5cdNjZAJSn_C-rYH2WIuIcfqH4k73aU6YPJnLHKB8CBTOLUWGtS3bSlOCuA7sKT294lpcc7VOE30yRbit65OxaPW7m45junVtOQ4SdZveP-11Kiw0QPlhFXgcjT4y4n23g4AOw3JBtjPirUYUnAiF3L15wqMWSIr8tUrbj4rNCO37esxvOKQO8gXpI6udSGZ2D9u1jQhSEocFP0tEGG4YMG5b_D_H56YPWDeJxpZx-zqU7wwnV2wYv5R3aiRvV4K6m_ewrdRr3OHAg", "payload": "eyJ0ZXJtc09mU2VydmljZUFncmVlZCI6dHJ1ZSwiY29udGFjdCI6WyJtYWlsdG86Y2VydC1hZG1pbjFAZXhhbXBsZS5vcmciXX0" } $ curl -v --insecure -H'Content-Type: application/jose+json' -X POST --data-binary @createaccount_message https://localhost:14000/sign-me-up < HTTP/2 201 < cache-control: public, max-age=0, no-cache < content-type: application/json; charset=utf-8 < link: <https://localhost:14000/dir>;rel="index" < location: https://localhost:14000/my-account/1 < replay-nonce: 9IQ52Zb5rNG7J0AwAoV-pA < content-length: 552 < date: Sun, 11 Oct 2020 17:19:26 GMT < { "status": "valid", "contact": [ "mailto:cert-admin1@example.org" ], "orders": "https://localhost:14000/list-orderz/1", "key": { "kty": "RSA", "n": "wQpRPT6O7TMJA1YDmk7U-XlJvzLILI9wSFJJd6gVfO0n9TTSeFlyLapU1fXyN1SlI3waZoiLIRJsw0ON1Gw6AKT8owPUgIOY1U-vQLqv95J9rUqIgDgLCwlBcbW6IQ_CrGeodnGugXVJathNkx6bHKlNOfGRjLM2E9fomhQx5HZDoQ5vGsmQEl_MR9EdwN6K3gTlyc0SoQqSGp92Dg_W87azX4x_aSJMP1k-rRSEyjw6qUrUIJpgYk7XcpbKMYLdtJ-Pvke3lzsJGtewbtQIhfg73d_tYMJwv_0yYCy2HdZzEdEN7F6YB73hPcAPu1Ga23cW4Y5AGdR_JSQl1DY4nQ", "e": "AQAB" } }
上記JWSのメッセージは、Pythonのauthlibを利用して作成しました。
ちなみにACMEクライアントのJWSメッセージの全てに共通ですが、以下のフィールドは必須となります。
- alg
- nonce
- url
更に、jwk
または、kid
のどちらかが必要です。newAccount
の場合は、jwk
が必要になります。