Let's EncryptのルートCA期限切れで OpenSSL 1.0.2が思わぬ事故を起こす件
ISRG Root X1(2015年〜)ならとっくに信頼してるはずと思った?残念!
2021年10月1日 嶋田大貴
これは、Let's Encryptを支えるこの二人のルートCAと OpenSSLの物語である。
- DST Root CA X3 (2000-2021)
- ISRG Root X1 (2015-2035)
〜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年くらいまででソフトウェアの更新が止まっているクライアントが接続してこれなくなります。とはいえそれでも良い場合も多いと思うので、それでもよければ。
1月時点で Let’s Encrypt からアナウンスされてた件すよね。https://t.co/EZzmBM4iik
— Yoshiki Kato (@burnworks) October 1, 2021
ちなみに Cartbot 側で対処するなら(古い Android とか知らねっていう環境なら)「--preferred-chain "ISRG Root X1"」オプションでいけると思いますよ。https://t.co/Kc1qAlWfAd
2021-10-3追記
「失効」は用語の割り当て的には Revokeされることで、Expireしてる場合を含めて表すのには適切ではないので Invalidの意味で「無効」という表現に訂正しました。
今回のLE騒動の仕組みを分かりやすく解説してる記事。
— Yoshiaki Kawazu🐸ずん (@kawaz) October 2, 2021
ただ『期限切れ』の意味で『失効』と書いてるのが気になった。PKI用語で失効はCRLへの登録=Revocationの意味で使われてて期限切れとは明確に違うので。後学を無駄に惑わせない為にも期限切れに書き換えた方が良いなーと。https://t.co/v9SQRki9Mt
なお翌日の話: Let's EncryptのルートCA期限切れ問題を直してもらえない人が WordPressを改造して切り抜ける方法
2021年10月1日 嶋田大貴