🔐

Let's EncryptのDST Root X3ルート証明書の期限切れとOpenSSLの影響についていろいろ試してみた

2021/10/08に公開

Let's Encryptでこれまで長く使用されてきたIdentrust社発行のDST Root X3ルート証明書が、日本時間2021年9月30日23時1分15秒に期限切れになりました。十分時間を取って事前に移行計画や影響範囲、救える環境、救えない環境などアナウンスをしてきましたが、やはり、期限切れ以降、様々なサービスや製品で接続できないといった声が上がってきました。

特にOpenSSLに関しては、OpenSSL 1.0.2以前に影響があると9月13日に事前の注意喚起がOpenSSL公式ブログであったにもかかわらず、製品やサービスの奥底で使われていて気づかなかったのか、様々なOSや製品で古いものが組み込みで使われていたために影響が広かったように思います。

OpenSSLからの注意喚起の概要

2021年9月30日にLet's EncryptのDST Root X3ルート証明書の期限が切れるにあたって
OpenSSLから出た事前注意喚起のポイントはこんな感じでした。

  • OpenSSL 1.1系より前(1.0系)のSSLクライアントとしての利用は影響がある。1.1以降影響なし。
  • OpenSSL 1.0.2 SSLクライアント利用での問題回避策は3つ
    • 回避策1: SSLクライアント側のトラストストアからDST Root X3を削除し、なければISRG Root X1を追加
    • 回避策2: SSLクライアントで-trusted_first(または同等のAPI)オプションを指定する
    • 回避策3: SSLサーバー側でDST Root X3→ISRG Root X1の中間CA証明書を送る設定を削除

OpenSSLのトラストストア

トラストストアとは、一般には「信頼しているルート認証局の格納場所やリスト」で、設定されていれば主要なルート証明書が格納されています。前述の回避策1のところでトラストストアは/etc/pkiにありupdate-ca-trustコマンドを実行すれば更新されるみたいなことを書いていますが、そんな単純な話ではなくて、OSやディストリビューションやビルドなどに依存してOpenSSLのデフォルトのトラストストアは様々です。

そもそもUbuntuには/etc/pkiupdate-ca-trustなんてないし、Mac PortsのOpenSSLなんて/opt/local/etc/opensslですからね。

使っているOpenSSLのトラストストアを確認するにはopenssl version -aコマンドを使ってOPENSSLDIRを確認するのが良いように思います。

% openssl version -a
OpenSSL 1.1.1k  25 Mar 2021
中略
OPENSSLDIR: "/opt/local/etc/openssl"
...

トラストストアには2つの形式があります。

  • ディレクトリ形式 $OPENSSLDIR/certs/
    • ディレクトリ中に個々のPEM形式ルート証明書やリンクが置かれる形式
  • ファイル形式 $OPENSSLDIR/cert.pem
    • cert.pemの一つのファイルにPEM形式ルート証明書が連結されている形式

ディレクトリ形式では、ディレクトリにルート証明書を置いて、c_rehash .コマンドを実行すると、証明書の名前(識別名)のハッシュを元にIDが作られます。ディレクトリ形式で回避策1を行う場合、dstrootx3.pemのファイル名はいろいろ違うでしょうが、シンボリックリンク名はどんな場合でも同じなので、上の2つの名前{12d55845,2e5ac55d}.@を消して、合わせてシンボリックリンク先を消します。

lrwxrwxrwx 1 hoge2   13 Oct  7 04:24 12d55845.0 -> dstrootx3.pem
lrwxrwxrwx 1 hoge2   13 Oct  7 04:24 2e5ac55d.0 -> dstrootx3.pem
-rw-r--r-- 1 hoge2 1200 Oct  7 00:08 dstrootx3.pem

合わせて、ディレクトリにISRG Root X1ルート証明書がなければ、それを置いて、c_rehash .すると、追加で2つのシンボリックリンクができているはずです。

lrwxrwxrwx 1 hoge2   14 Oct  7 04:22 4042bcee.0 -> isrgrootx1.pem
lrwxrwxrwx 1 hoge2   14 Oct  7 04:22 6187b673.0 -> isrgrootx1.pem
-rw-r--r-- 1 hoge2 1939 Oct  7 00:09 isrgrootx1.pem

ファイル形式であれば、ファイルの中に

MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/

で始まり

Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ

で終わる証明書があればDST Root X3ルート証明書なので、その部分を消してファイルを保存します。

OpenSSLコマンドによるSSLクライアント接続テスト(s_client)の基本

Let's EncryptのDST Root X3ルート証明書期限切れのOpenSSLの影響について、いろいろテストして確認してみようと思いますが、証明書チェーンの検証するにはs_clientverifyのコマンドが使えます。テストサイトさえあれば簡単に試せるので、今回はs_clientでテストしてみようと思います。

基本的なs_clientの使い方はこんな感じです。

echo|openssl s_client -connect 接続ホスト名:443

大きなサイトでは仮想ホスト名使っていることも多いので、意図したホストに対して検証するように心配なら-servernameをつけておくといいでしょう。

echo|openssl s_client -connect 接続ホスト名:443 -servername 接続ホスト名

早速、helloworld.letsencrypt.orgに接続してみましょう。

% echo|openssl s_client -connect helloworld.letsencrypt.org:443 -servername helloworld.letsencrypt.org
CONNECTED(00000005)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = helloworld.letsencrypt.org
verify return:1
---
Certificate chain
 0 s:CN = helloworld.letsencrypt.org
   i:C = US, O = Let's Encrypt, CN = R3
 1 s:C = US, O = Let's Encrypt, CN = R3
   i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
 2 s:C = US, O = Internet Security Research Group, CN = ISRG Root X1
   i:O = Digital Signature Trust Co., CN = DST Root CA X3
---
中略
    Start Time: 1633593033
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: no
---
DONE

と出力されます。Verify return code: 0 (ok)と表示されれば問題なく接続できたということです。但し、このオプションでは証明書チェーン中の各証明書の失効検証はしません。

openssl s_clientの証明書チェーン関連の検証オプション

今回のLet's Encryptルート移行に関連して、トラストストアの明示的な指定など、幾つかの証明書チェーンの検証のオプションを指定して検証します。今回のテストで必要となる証明書検証に関するs_clientのオプションを紹介しましょう。

オプション 説明
-CAfile PEMファイル トラストストアに追加するルート証明書の連結されたファイルを指定します。CRL失効検証の際にダウンロードしたCRLを追加するのにも使います。
-CApath ディレクトリ ディレクトリ形式のトラストストアを追加します。ディレクトリには失効検証で使うCRLも含めることができます。
-no-CAfile デフォルトのファイル形式のトラストストアを使わないようにします。OpenSSL 1.1系以降で指定可能です。
-no-CApath デフォルトのディレクトリ形式のトラストストアを使わないようにしますOpenSSL 1.1系以降で指定可能です。
-untrusted PEMファイル 検証に必要となる中間CA証明書ファイルを追加するのに使用します。中間証CA証明書に-CApath、-CAfileを使わないように注意してください。
-trusted_first トラストストアのファイルを優先して使用するとマニュアルには書いていますが、簡単に言うと、複数の証明書チェーンが候補にある場合、短い証明書チェーンを優先して検証するOpenSSL 1.0.2で導入されたオプションです。1.1系以降ではこれがデフォルトでONになっており、無効化はできず指定の必要がありません。今回は回避策2で使用します。

(チェーン1) Let's Encryptが提供するデフォルトチェーン

これまでのルートCA移行、ルート証明書期限切れに関し、Let's Encryptのテスト用サイトが2つ使えるようです。まずはcertbotを使って入手できるデフォルトの証明書チェーンを返すサイトとして、どこでもいいですが

helloworld.letsencrypt.org
があります。帰ってくるチェーンは

  1. helloworld < Let's Encrypt R3 ICA: SSLサーバー証明書
  2. Let's Encrypt R3 ICA < ISRG Root X1: 中間CA証明書
  3. ISRG Root X1 < DST Root X3: 中間CA証明書(クロスルート証明書)
    です。これは、新しめの多くの環境でも、古いAndroidでも問題が少なく接続できるためのデフォルトのチェーンになっています。

(チェーン2) Let's Encryptの短いチェーン

また短いDST Rootを含まないチェーンを返すサイトとして

valid-isrgrootx1.letsencrypt.org
があります。帰ってくるチェーンは

  1. valid-isrgrootx1 < Let's Encrypt R3 ICA: SSLサーバー証明書
  2. Let's Encrypt R3 ICA < ISRG Root X1: 中間CA証明書
    です。古いAndroidでは接続できませんが、OpenSSL 1.1系より前でも接続できるようにする回避策3を試せるチェーンになっています。

いずれのケースも-servername ホスト名をつけて検証してください。つけないと別のチェーンが帰ってくることあるので。

(チェーン3) Scott Helmeさんの期限切れテストサイト

PKIで有名なScott Helmeさんは9月20日にルート期限切れに関するブログを書いてくれ、テストサイト

expired-r3-test.scotthelme.co.uk

も用意してくれたんだけども、これはLet's Encrypt R3中間CA証明書期限切れのテストサイトでDST Root X3ルート証明書の期限切れテストサイトであることに注意してください。チェーンはこんな感じ

  1. expired-r3-test < Let's Encrypt R3 ICA: SSLサーバー証明書
  2. Let's Encrypt R3 ICA < DST Root X3: 中間CA証明書

勘違いしている人も結構いたようなので。Let's Encryptの今回のゴタゴタに関連する証明書発行の全体像はこんな感じになって、紺色矢印が証明書なんですが、
全体図
expired-r3-testは、ホスト名の通りDST Root X3からLet's Encryptに発行され9/30 04:20までの有効期限の中間CA証明書の期限切れのテストサイトであって、DST Root X3ルート証明書の期限切れをテストするものではないんですよね。
expire-r3-testサイトのチェーン

3つのテスト用チェーンの図

チェーンが比較しやすいように図にしてみました。
チェーン比較
赤い矢印がSSLサーバーから送られてくる証明書です。

テスト内容

さて、準備は整ったのでテストしていきましょう。

  • 前述の3つのチェーンについて
  • 幾つかのOpenSSLのバージョンで
  • OpenSSL 1.0.2については-trusted_firstオプションの有無も含め
  • トラストストア設定も比較しながら
    openssl s_clientコマンドで接続テストをしてみます。

テスト環境

下記のOpenSSLの幾つかのバージョン、異なるトラストアンカ、SSLサイトのチェーン設定でテストしてみました。

  • OpenSSL
    • OpenSSL 3.0.0 スタティックリンク自前ビルド
    • OpenSSL 1.1.1 Ubuntuパッケージ(ダイナミックリンク)
    • OpenSSL 1.0.2u スタティックリンク自前ビルド
    • OpenSSL 1.0.1u スタティックリンク自前ビルド
    • ※ トラストアンカは明示的に指定
    • ※ ダイナミックリンクの影響を受けないようになるべくスタティックリンクのものを使う
  • OS: Ubuntu 18.04.06 LTS (on Microsoft Azure)
  • テスト実施日: 2021年10月5日

テストしたトラストストア

トラストストア略称 説明
DSTX3 DST Root X3ルート証明書だけがトラストストアに入っている場合
ISRGX1 ISRG Root X1ルート証明書だけがトラストストアに入っている場合
ComboDI ファイル形式トラストストアでDST、ISRGの順に2枚ルートが入っている場合
ComboID ファイル形式トラストストアでISRG、DSTの順に2枚ルートが入っている場合

テスト結果

ホスト OpenSSL DSTX3 ISRGX1 ComboDI ComboID
チェーン1 デフォルト 1.0.1u 10expire 0ok 10expire 10expire
チェーン1 デフォルト 1.0.2u 10expire 0ok 10expire 10expire
チェーン1 デフォルト 1.0.2u+tf 10expire 0ok 0ok 0ok
チェーン1 デフォルト 1.1.1 10expire 0ok 0ok 0ok
チェーン1 デフォルト 3.0.0 10expire 0ok 0ok 0ok
チェーン2 短いの 1.0.1u 20noissuer 0ok 0ok 0ok
チェーン2 短いの 1.0.2u 20noissuer 0ok 0ok 0ok
チェーン2 短いの 1.0.2u+tf 20noissuer 0ok 0ok 0ok
チェーン2 短いの 1.1.1 20noissuer 0ok 0ok 0ok
チェーン2 短いの 3.0.0 20noissuer 0ok 0ok 0ok
チェーン3 Helmeさんの 1.0.1u 10expire 20noissuer 10expire 10expire
チェーン3 Helmeさんの 1.0.2u 10expire 20noissuer 10expire 10expire
チェーン3 Helmeさんの 1.1.1 10expire 20noissuer 10expire 10expire
チェーン3 Helmeさんの 3.0.0 10expire 20noissuer 10expire 10expire
  • テスト結果略称
    • 0ok: Verify return code: 0 (ok)
    • 10expire: Verify return code: 10 (certificate has expired)
    • 20noissuer: Verify return code: 20 (unable to get local issuer certificate)
    • 1.0.2u+tf: 1.0.2uのopenssl s_clientコマンドで-trusted_firstオプションを追加

テスト結果からわかったこと

  • OpenSSLは全てのバージョンでルート証明書の有効期限を確認する(=Androidと異なる)
  • OpenSSLは1.1系以降ではデフォルトで-trusted_firstオプションを付けたのと同等になっており、トラストアンカにDSTとISRGの両方のルートがあって複数のチェーンが候補があった場合でも(実質的に)短いチェーンが選択されISRG Root X1ルート証明書をトラストアンカとするパスが構築されるためパス検証成功しSSL接続できる
  • OpenSSL 1.0.2であっても-trusted_firstオプションをつければ複数のチェーンがあった場合でも(実質的に)短いチェーンが選択されISRG Root X1ルート証明書をトラストアンカとするパスが構築されるためパス検証成功しSSL接続できる
  • トラストストアから選択されるトラストアンカは、証明書の格納順序には依存しない (ComboDI, ComboIDより)
    ※ トラストストアの中から証明書チェーンの検証で使われたルート証明書をトラストアンカと呼びます。

おわりに

OpenSSLはいろんな製品でこっそりと内部で使われていることも多いので、やはりDST Root X3期限切れの影響は結構出てたみたいですね。OpenSSLが示してくれたOpenSSL 1.0.2に対する問題回避策が実際どうなのか実際にいろいろ検証して、確認できてよかったです。結局、-trusted_firstが今回の問題のキモだったわけですよね。

ではでは。

関連リンク

DST Root X3ルート証明書の期限切れ問題関連でブログポストしたり、セミナー講演をしました。9月30日以降の結果とは違っていることもありますが、参考になることもあると思います。よかったらご覧ください。

Discussion