x/crypto/acme: support standards compliant order finalization
要約
概要
x/crypto/acme パッケージにおいて、ACME(RFC 8555)の証明書発行フロー(Order Finalization)を仕様準拠の方法で実行できる新APIを追加する提案です。既存の CreateOrderCert が一部CAの非標準的な挙動に依存している問題を解消します。
ステータス変更
**(新規) → active
2026-08-20の提案レビュー会議で active カラムに追加され、今後は毎週のレビュー会議で継続的に議論されることになりました。提案者(cpu、Let's Encrypt/Boulder関連の知見を持つ人物)による初期提案と、それに対するコアチーム(neild)からのフィードバックを経て、API名についてほぼ合意が得られた段階での「active」入りです。
技術的背景
現状の問題点
x/crypto/acme の Client.CreateOrderCert は、finalization URL(文字列)のみを引数に取り、CSR(証明書署名要求)をそのURLにPOSTします。しかしRFC 8555の仕様上、finalizeレスポンスに Location ヘッダーが含まれることは保証されていません(実際、RFC 8555 §7.4の「typical sequence of requests」表でも finalize order のアクションには Location を示す -> が付いていません)。
にもかかわらず、Let's EncryptのCA実装であるBoulderがこのヘッダーを常に返していたため、x/crypto/acme はそれに依存する実装になってしまっていました。ACMEのテストサーバーであるPebbleが最近この非標準ヘッダーを意図的に削除したことで(pebble#509)、Pebbleの新バージョンとの統合テストが失敗するようになり、さらにBuypassのような一部のCAでは元々このヘッダーを返していないため、x/crypto/acme が仕様準拠のCAと相互運用できないという実問題が顕在化しました。
提案された解決策
後方互換性を保つため既存の CreateOrderCert はそのまま残しつつ、Godocに「このメソッドはfinalizationレスポンスに Location ヘッダーを返すCAでのみ動作する」旨の警告を追記します。
その上で、新メソッド CreateCertFromOrder(ctx, order *Order, csr []byte, bundle bool) を追加します。これはURL文字列の代わりに AuthorizeOrder() で生成済みの *Order を受け取り、内部的には Order.FinalizeURL をfinalization POST先として使い、Order.URI(Order作成時のレスポンスの Location ヘッダーから取得され、こちらはRFC 8555で保証されている)をポーリング対象URLとして使います。これにより Location ヘッダーへの依存を finalization レスポンスからorder作成レスポンスへ移すことができ、仕様準拠のCAでも正しく動作するようになります。
なおメソッド名については議論の末、CreateCertForOrder や CreateCertWithOrder などの候補から、finalizationがOrderに対する「終端操作」であることを表す CreateCertFromOrder が有力案としてまとまりました。
これによって何ができるようになるか
これまでBoulder(Let's Encrypt)以外のRFC 8555準拠CA(例: Buypass)を使う場合に発生していた「finalization後のポーリングが空URLへのPOST-as-GETで失敗する」という問題を回避できます。ACMEクライアントの相互運用性が向上し、Pebbleの新バージョンを使ったテストや、Boulder以外のCA実装を利用する開発者にとってメリットがあります。
コード例
// Before: CreateOrderCert はURL文字列を渡すため、
// finalizeレスポンスにLocationヘッダーがないCAでは失敗する
order, err := client.AuthorizeOrder(ctx, ids)
// ...
der, certURL, err := client.CreateOrderCert(ctx, order.FinalizeURL, csr, true)
// -> finalizeレスポンスにLocationがないCAでは、
// 内部で order.URI が "" になりポーリングが失敗する
// After: CreateCertFromOrder は *Order を直接渡すため、
// AuthorizeOrder時に取得済みの order.URI (Location準拠) を使ってポーリングできる
order, err := client.AuthorizeOrder(ctx, ids)
// ...
der, certURL, err := client.CreateCertFromOrder(ctx, order, csr, true)
議論のハイライト
- 提案の技術的な内容自体には大きな異論は出ず、議論の中心はもっぱら新メソッドの命名でした。
CreateCertForOrder、CreateCertWithOrder、CreateCertFromOrderが候補に挙がりました。 - neild(コアチーム)は「名前がやや分かりにくいが、他に良い案も見当たらない」とコメントしつつ、提案自体はおおむね妥当と評価しました。
- 提案者cpuは、finalizationがOrderに対する不可逆な終端操作である点(一度finalizeしたOrderから再度別の証明書を作ることはできない)を理由に
Fromを推し、これが有力案としてまとまりました。 - 既存の
CreateOrderCertは後方互換性のため削除せず維持しつつ、Godocに非推奨的な注意書き(RFC 8555準拠CAではCreateCertFromOrderを推奨する旨)を追加する方針が確認されました。 - 提案者は
autocertパッケージ内のCreateOrderCert呼び出し箇所はすべて手元に*Orderがあるため、問題なく新APIへ移行できると述べています。 - 実装CL(go.dev/cl/817000)がすでに提出されており、議論と並行して実装が進んでいます。