クライアント証明書を生成する

2024/07/03

年に数回、 X.509のクライアント証明書を生成する作業が発生するのだが、ロストテクノロジーになりかけていたため、一度関連動向を含めてリサーチした。

TLSのクライアント証明書認証は、究極はBasic認証と似たようなものではある。ただBasic認証はURLにセットできてしまう。セットアップ経路の手間の分だけ証明書の方がセキュアであろうと評価している。

他の技術と独立してTLSレイヤに防壁を1枚追加できるメリットのため、しぶとく使い続けている。ただし、いざ使おうと思ったときに体系的な情報がなく、非常に難解である。過去何度か撤廃しようかと考えたこともある。

暗号技術には改廃があるのだが、クライアント証明書はいつまでもRSAのままで良いのか、といった点が判然としない。

使い捨てCA

これまで、古いOpenSSL由来の CA.shを手動で実行していた。履歴には1996年と書かれており、最近のバージョンにはもはや含まれていないのだがopensslのサブコマンドに大きな変更がないため、引き続き動作している。

CA.shを読んでいくと結局ラッパーにすぎないことが分かるため、クライアント証明書生成の手順に沿って再構成した。
素朴に実行すると、随所で秘密鍵のパスフレーズを求められる点がとても分かりにくい。

見通しを良くするために概要を述べると、-batchオプションを付けることでパスフレーズの無い秘密鍵を生成し、-configオプションで資格情報をファイルから供給すると対話プロンプトが出なくなる。
ただし、これが常に良い方法というわけではなく、利用上の制約はある。

パスフレーズ無しについては方式を確認した結果として、 CAを継続運用せず、一期一会の証明書セットのみ利用する使い方に限定することでセキュリティレベルを上げられると考えた。

CAは本来、多くの証明書を発行する前提で設計されており、秘密鍵アクセスを秘匿し、失効リストを追跡更新する必要がある。
公開鍵暗号方式は、秘密鍵にアクセスできなければ有効な署名を生成できないことを前提としており、パスフレーズは鍵アクセスをガードしている。
パスフレーズはコンピュータ草創期から主要な認証方式ではあるが、もはや絶対視できるほどの耐性も期待できない。秘密鍵そのものが存在しない方がよりセキュアだろう。

-configオプションは、カスタムの openssl.cnfを指定するよくあるオプションだ。
適切な設定には、 x509v3_configの書式を理解する必要があるが、コア機能設定はデフォルトで動作する。req_distinguished_nameセクションが証明書の掲載設定であり、個別に設定が必要だ。

openssl.cnfの理解しづらい点は、opensslのサブコマンドごとに参照セクションを使い分けており、その対応が不明な点だろう。

生成ステップ

opensslコマンドを使用したCA運用は単なるリファレンス実装でありフル機能を満たすものではないことが、openssl caのmanの末尾を確認すると分かる。長期的には別の手順が必要になることは間違いないが証明書じたいには問題がなく、使い捨てCAとしては機能する。

CAは、秘密鍵作成( openssl genpkey)、CA証明書リクエスト作成( openssl req)、CA作成( openssl ca -extensions v3_ca -selfsign)の順に実行する。

この時点で生成されるCA証明書はサーバーにセットアップし、アクセス時の信頼チェーン検証に用いる。

クライアント証明書は、秘密鍵作成(openssl genpkey)、署名リクエスト(openssl req)、署名(openssl ca -policy)の順に実行する。

最終的に、署名済みクライアント証明書をPKCS#12フォーマットに変換( openssl pkcs12)する。pkcs12もパスワードを求められるがオプションでスクリプトから指定できる。
どうやらChromeが秘密鍵を同梱したPKCS#12フォーマットを要求している。x509形式では秘密鍵を付けたり署名したりしてもインポートに失敗した。

このプロセスでは中間ファイルが大量に生成されるが、最終的な証明書2種を入手したら残りのファイルは削除する。

各サブコマンドの挙動を理解することや妥当性の評価は難しい。これはopensslの既存実装に難があるものの、代替実装も永らく定まっていないことから来ている。

秘密鍵生成の分離

参照したCA.shではopenssl reqコマンドで秘密鍵を生成している。これをRSA以外のオプション指定のためopenssl genpkeyに分離した。
将来変わっていく部分は主に秘密鍵に集約されるため、一定の柔軟性を確保できるだろう。

ここで、証明書の暗号アルゴリズムに何を使えるのかが不明であるという難点がある。TLSのクライアント証明書認証の規格動向が判然とせず、どうやら実装先行であるように見える。

ミニマム構成の証明書は、クライアント証明書とCA証明書の2種類存在する。結果的に、それぞれの暗号アルゴリズムは一致していなくても動作している。署名が任意のバイト列を受けつけることを考えれば自明とも言える。ただ、プロトコルの想定に沿っているのか否かは確認できていない。

また、個別実装がどのプロトコルを扱えるのかも判然としない。証明書に関するドキュメントがおおむね存在せず、たまたま動作する組み合わせを探すより他ない。

今回のトライでは、Chromeは楕円曲線暗号の証明書をインポートできず、サーバーは楕円曲線暗号の証明書を扱えるものがあることが分かった。
クライアント証明書認証では署名の耐性が重要なので、CAをRSAから置き換えられたことによって現時点では強度向上を期待できる。

残る課題

暗号アルゴリズムのトレンド変遷には対応できるようになったが、認証の利用モデルが変わった場合には依然として備えられていない。これには代替実装が定まってくる必要がある。

証明書の生成プロセスは自動化できたので、更新デプロイまで自動化する道が拓けた。デプロイを自動化すると更新頻度を上げやすくなるため、よりセキュアになるだろう。
クラウドストレージに一定の疑念を持って考えれば、証明書ファイルを特定のユーザーにだけ配布するプロセスに課題が残っている。

また本質的な課題として、標準規格のドキュメントがあるなら探索した方が良い。
カジュアルな説明はおそらく誤りを含んでいて問題がある。たとえばブラウザはインポート時に秘密鍵を要求し保持しているのだが、鍵の用途は不明だ。認証時に用いているとすればチャレンジレスポンスのステップがあるはずだが、そのような説明は見当たらない。

いま、QUICのようにTCPプロトコルをUDP上に再実装する状況になっており、TLSもその前提で整理が進んでいるように見ている。 過去の経緯はundocumentedな部分が多かったのだろうが、これからの実装については一定の合意が公開ドキュメントに残るのではないかと期待している。

⁋ 2024/07/03↻ 2024/07/04