Let's EncryptのルートCA期限切れで OpenSSL 1.0.2が思わぬ事故を起こす件

ISRG Root X1(2015年〜)ならとっくに信頼してるはずと思った?残念!

2021年10月1日 嶋田大貴

これは、Let's Encryptを支えるこの二人のルートCAと OpenSSLの物語である。

〜2021年1月〜

ISRG Root X1「いままで一緒にやってきたDST Root CA X3さんの寿命が間近・・・このままだと僕を信頼してくれていないベテランの(具体的にいうと2016年くらいまでの)古いクライアントたちは Let's Encryptさんを信用してくれなくなっちゃう・・・どうしよう」

DST Root CA X3「どれ、わしが死ぬ前に(有効期限が切れる前に)お前が信頼に値する旨を一筆書いて残せばいいじゃろう。サラサラ」

Issuer: O = Digital Signature Trust Co., CN = DST Root CA X3
Validity
    Not Before: Jan 20 19:14:03 2021 GMT
    Not After : Sep 30 18:14:03 2024 GMT
Subject: C = US, O = Internet Security Research Group, CN = ISRG Root X1

(日本語訳: 証文(しょうもん) - 2021年1月20日、この私 DST Root CA X3は ISRG Root X1を信頼に値する者と認めます。有効期限 2024年9月30日)

ISRG Root X1「ありがとうございます!これで3年間はなんとかなる!3年もあればめんどくさいベテランはだいたい死ぬから大丈夫!Let's Encryptさん、これを使って!」

〜これがすべての始まりだった〜

〜2021年9月30日〜

DST Root CA X3「さらばじゃ!ガクーッ」

ISRG Root X1「DST Root CA X3さーん!ううう・・・でもこの証文さえあれば」

〜2021年10月1日〜

ISRG Root X1「DST Root CA X3さんが生前に書いた証文があるので僕を信頼してください!」

OpenSSL 1.1.0以降「ま、いいだろ」1

ISRG Root X1「ほっ」

OpenSSL 1.0.2「死んだ奴の書いた証文持ってくる奴なんか信頼できるか!死ね!

VERIFY ERROR: depth=3, error=certificate has expired: O=Digital Signature Trust Co., CN=DST Root CA X3

ISRG Root X1「えーっ!!!!ギャー!!!」

かなり理不尽なエラーが発生!!

何が問題だったのか

OpenSSL 1.0.xは信頼チェーンの間に現時点で無効な証明書があってかつそれが信頼済みCAとして登録済みだと信頼性の検証を失敗扱いにするようです。バグか仕様かはしらん2

たとえ新参のISRG Root X1が既に信頼済みのルートCAとして登録されていても、そいつの署名した証明書と一緒に持ち込まれた中間証明書に署名している他のルートCAが無効な(ややこしいな)場合はせっかくの信頼をご破産にしてしまうんですね。OpenSSL 1.0.2的な価値観のもとでは 間接的な無効が直接的な信頼に勝るということです。信頼とはかくも簡単に壊れるものですな。

Old Let’s Encrypt Root Certificate Expiration and OpenSSL 1.0.2

CentOS 7あたりはまだ OpenSSL 1.0.2なんで、ca-certificatesパッケージが最新になっていないとこの問題を踏みます。外部のURLにcurlではアクセスできるのに wgetだと失敗するという状況の人は yum update してください。(curlはNSSを使っているがwgetはOpenSSLを使っているためwgetだけ引っかかる)

対策

1. クライアント側でOpenSSLのバージョンを上げる

それができるなら苦労しないよね。普通に互換性ないんで無理やり OpenSSLだけ上書きしたら全員死亡します。

2. クライアント側で DST Root CA X3 を信頼済みCA一覧から削除する

死んだCAを最初から信頼してなかったことにすれば、死者の書いた証文は単純に無視してくれるようです。これのやり方はその場面での OpenSSLの使われ方次第なので、こうすればいいよっていう具体的な操作を示すのは難しいです。わかる人はこれでどうぞ。

追記: RHEL/CentOS 7の場合は 最新のOSアップデートでこの対策が適用されることを確認しました。

追記2: 少し検証してみたのですが、OpenSSL 1.0.1だとこの方法でも回避できない気がします。2020年11月いっぱいでメンテナンスが打ち切りとなり今はもう使われていないことになっているはずの CentOS 6ではこのバージョンが使用されています

3. サーバ側で死者の書いた証明書を排除する

Let's Encryptの中間証明書ファイル /etc/letsencrypt/live/YOUR_DOMAIN_DOT_COM/chain.pem に証明書がふたつ連結されていたら、ひとつめは ISRG Root X1 が Let's Encryptを信頼するよという証明書、ふたつめが今回問題になっている DST Root CA X3 が生前にしたためた ISRG Root X1 を信頼するよという証明書です。後者をなくせば OpenSSL 1.0.2がへそを曲げることがなくなります。ただし、2016年くらいまででソフトウェアの更新が止まっているクライアントが接続してこれなくなります。とはいえそれでも良い場合も多いと思うので、それでもよければ。

2021-10-3追記

「失効」は用語の割り当て的には Revokeされることで、Expireしてる場合を含めて表すのには適切ではないので Invalidの意味で「無効」という表現に訂正しました。

なお翌日の話: Let's EncryptのルートCA期限切れ問題を直してもらえない人が WordPressを改造して切り抜ける方法


  1. ストーリーのわかりやすさのために端折って書いてますが、2016年以前のような古い環境でない限りISRGは信頼されてるのでそもそもこの証文は不要、っていうか死んだ奴の証文は OpenSSL 1.1では単に無視されるようです(なので死んだやつの証文を信頼してくれてるわけではない)。参考 

  2. セキュリティを取り扱うものなので、少しでも怪しいところがあれば拒絶するっていう動作が仕様であると言われれば納得せざるを得ないかもしれない。むしろ遺言のような証明書で信頼を延命しようというセコい判断の方に疑問が持たれるのではないだろうか。 

2021年10月1日 嶋田大貴

記事一覧へ戻る