ぼちぼち日記

おそらくプロトコルネタを書いていることが多いんじゃないかと思います。

書評:プロフェッショナルSSL/TLS

ごめんなさい、書いてたら長くなってしまいました。長文嫌いな方は避けて下さい。

鹿野さんの名前を間違えてました。大変失礼しました。(_O_)

1. はじめに。日本語翻訳版刊行によせて、

昨年10月、Vさんから

「 Bulletproof SSL and TLS 翻訳本のレビューします?時雨堂が出資してる出版会社が翻訳権を勝ち取ったのです。」

とお誘いを受けたのが、そもそもの始まりでした。

「とうとうあれの日本語翻訳が出るのか、でもホント大丈夫か? 内容の濃さもさることながらあの分量とクオリティ、記述の正確さや厳密さに対して特に高いものが要求されるセキュリティ分野。しかも初心者向けではないエキスパート向け。この本の翻訳を出すとは… なんと大胆、少し無謀なことではないか?」

との思いが正直頭をよぎりました。

自分もちょうど17年半勤めた前職を辞めて転職した直後。新任早々こんなことしても許されるのか? 恐る恐る遠慮がちに上司に伺ったところあっさりOKの返事。さすがっ、理解のある上司で良かった、ホント感謝します。

編集の鹿野さんからの依頼は、

「レビューについては、一文一文を集中的にではなく、ざっと見ていただくようなものを」

と控え目なもの。

「いやいや、この本をざっくりレビューじゃ失礼です。じっくり見させていただきます。」

と、少し自分にプレッシャーをかけつつ読み始めました。

そして一晩作業してみて私からの返事。

「第一印象として、まだ日本語訳としてこなれてない部分が多いなと思いました。特に2章で技術的に難しい記述になると英文の表現に引きずられて日本語の文意が取りづらいなと感じる部分が多くなりました。 日本語訳を読みながら詰まったり、気になったところにコメントを差し込んだのですが、結局29ページで100個近くのコメントになってしまいました。」

まぁ正直な感想です。でも、これじゃ全然先に進まない。

幸いに、技術的に間違って訳している箇所はかなり少なく、ちゃんと内容を理解して翻訳がされているのがわかります。これならきっと原書のクオリティを保ったまま翻訳本にできるはず、そう確信しました。

しかしその後、何章か同じペースでコメントを入れ進めたのですが、そのうち本業の方が立て込んでしまい途中でレビューが止まってしまいました。すみません。

その間、鹿野さんから私のコメントに対する返事を頂いたのですが、驚いたことに監訳の方に丸投げするわけではなく、ちゃんとご自身で判断されて修正対応されてます。私のコメントが間違っていることもしばしばで、逆に鹿野さんから指摘を受ける始末。恥ずかしい。あれっ?エンジニアの方が編集者してるんじゃないか?マジそう思いました。

そんなまま数ヶ月経った後、「原稿を更新しました」とのご連絡。うぅ、うっかりメール見落としてた。直ちに未レビュー章を片付けないとまずい、早速レビュー開始。すると、

「あれっ、めちゃくちゃ読みやすい。この章のレビューコメントがゼロ。」

「うそ、以前ならそんなことはないはず。おかしい。」

「時間をおいてもう一度読み直そう。」

翌日、「やっぱりゼロだ。何が起きたんだ?」

鹿野さん曰く、「思い切った編集方針に切り替えました。」

すごーい! 読んで進む、進む。あかん、このままじゃ本当にレビューしているのか疑われてしまう。そういう心配してしまうくらいの素晴らしい出来になりました。

まぁそんなこんなギリギリまでドタバタのレビューがかかってしまい、ご迷惑をおかけしました。そして無事「プロフェッショナル SSL/TLS」が刊行されました。めでたいことです。

余談はこのぐらいにして早速本の中身について書いてみます。

2. この本の扱う範囲

私は過去2年(2015/16)、セキュリティ・キャンプ全国大会でTLSを教える講義を担当してきました。各地から集まる若く優秀な学生さんに対してTLSをどう教えるのか、最も悩むところです。

結局はいくら悩んでも、TLSのハンドシェイクの仕組みを学んでもらうことに落ち着いてしまいます。やっぱりTLSは難しい。

TLS仕様(RFC5246)自体は、ハンドシェイクやプロトコルフォーマットを規定するだけで、実はX.509証明書などPKIやAES、RSAなど暗号技術の仕様については、TLSで具体的な中身はほとんど含まれていません。これらは他への関連仕様として参照されており、IETFで扱うWGも違います。

後ろ髪引かれるのは、狭義的(RFC5246)な見方では、キャンプではそれらを外部仕様としてTLSを支える土台としてスコープ外にせざる得ないことです。時間的にも、キャパ的にも、全部盛り込むのは無理があります。なので最終的にはこのスライドでごまかしています。 f:id:jovi0608:20170317015704j:plain それに対し本書では、これら全部引っくるめてTLSセキュリティを解説しています。いやさすがです。

本書では、上図の各項目に対応する章として、

TLSのセキュリティ:

「第2章プロトコル」、「第6章実装の問題」、「第7章プロトコルに対する攻撃」

暗号技術:

「第1章SSL/TLS と暗号技術」、「第6章実装の問題」、「第7章プロトコルに対する攻撃」

乱数生成:

「第6章実装の問題」、「第7章プロトコルに対する攻撃」

PKI:

「第3章公開鍵基盤」、「第4章PKIへの攻撃」

秘密鍵の管理:

「第8章デプロイ」

な関係になります。

全部を網羅するので扱う技術領域の幅が一気に増え、それぞれが深い内容を含みます。 まぁどんな専門家でも頭でわかっているつもりなだけで、いざちゃんとした成果物まで仕上げるとなると並大抵の労力ではすまないでしょう。

しかも、さらに私の講義の範囲の図にも入っていない、

上位レイヤー(HTTPS)のセキュリティ:

第5章HTTPブラウザ問題、10章、HSTS、CSP、ピニング

性能:

第9章 パフォーマンス最適化

までカバーしている。あぁもう凄いですね、と感服するしかありません。

既に本書を購入し読み始めた方は、その広大な技術領域と膨大な量に圧倒される人も多いかと思います。

個人的には、これから1章から読み始めることにしても、必ずTLSの技術領域に関する土地勘を意識することが大切だと考えます。ざっと読む部分・深く精読する部分などを決めて、ある程度メリハリのある読み方をすることをお勧めします。

3. この本の凄いところ

話がそれますが、実は先日社内から依頼を受けて「技術のスキルアップ、私のやり方」というセミナーを内部で開催しました。

自分のこれまでの取り組みを振り返りながらあれこれメンバーと共に話をした実に楽しい時間でした。その中で「アウトプット方法 ブログの書き方」というセッションを設け、某メンバーのブログのビフォー・アフターを紹介しながらブログの書き方やアウトプットの重要性などの話をしました。

その時のスライドの一部がこれです。 f:id:jovi0608:20170317015711j:plain f:id:jovi0608:20170317015718j:plain 自分が思うに、今回の本はまさにこれなんですよね。

この本に対して私のスライドを比較するのはほんと失礼だと思いますが、この本の凄いところをこの図に関連付けて書いてみます。

3.1 凝縮された内容

序文で著者自身がこの本の目的を

「著者が時間をかけた分読者の時間は節約できるよう、著者が知っていることの中でも特に重要な内容を詰め込み、僅かな時間で同じ内容を理解してもらうことである。」

と書いています。

まさに「特に重要な内容」を詰め込んでおり、上図の様におそらく著者はこの10倍、もしくはそれ以上の分量を調べ上げているはずです。その中から著書のコンテキストに合わせて絞り込み、体系化した内容にして書いているのだと思います。

この作業自体は他の本でも行われており、特別なことでもないでしょうが、TLSPKIなど過去20年分しっちゃかめっちゃかした技術領域を広く網羅した範囲で行ったのは凄いです。

この本を購入することは、TLS/PKIに関連に重要な情報に辿り着くまでの調査とそれを理解するための時間を買っていると思って良いです。ただ本当のエキスパートを目指す人は、ここに書いてあることが全てではなく、この裏に広大な技術領域が広がっていると思って下さい。

また11章以降は各実装の使い方になっていますが、これは「実際に検証して確認(エピソード記憶)」に該当する作業です。これまで学んだことが実際どう設定に反映されるのかここで結びつけることができます。私が思う本当に理想的なアウトプットだなと感心します。

3.2 何事にも代えがたい一次資料へのポインター

私は原著を所有していますが、実はこれまであまり中身を通して読んだことがありませんでした。使う時は、なにか調べ物をする時です。

新しい脆弱性情報が公開されると全て新規のものは稀で、大概新しい手法に過去の脆弱性を組み合わせたり、改良したりしたものが多いです。その際はこの本が大活躍します。関連するインシデントや脆弱性を楽に探すことができ、その一次リンクが脚注に数多く掲載されているからです。

日々発生する脆弱性やインシデント情報。いくらブックマークしていても少し経つところっと忘れてしまいます。検索で探し当てるにしてもS/N比が悪く効率的ではありません。すっかり忘れてしまった自分のブログに助けられることもしばしば。

この本は本当に一次情報にこだわっています。書いてある内容・図・データも、その多くが一次情報からのエビデンスがあるものということがはっきりわかります。この一次資料へのポインターは、何事にも代えがたい情報です。

これに加え、最新のTLS/PKIの動向や脆弱性の情報は、Bulletproof TLS Newsletter で受け取ることができます。近うちに商品ページからリンクが貼られるようです。毎月1回程度TLS/PKI/暗号技術などの最新情報に関する簡単な 解説やリンクがメルマガのニュースレターとして配信されてきます。普段いろいろアンテナを張っているつもりでも結構見逃しているものが多々あり、このニュースレターで助けられることも多いです。この本を読んで知識を得た方は、是非これで最新情報を得て下さい。

4. 個人的に思う注意点

良い事ばかり書いても書評にならないので、いくつかレビューしていて気づいた注意点を。

4.1 11章以降の実装バージョン

11章以降の実装で解説しているソフトウェアは最新のバージョンに追随していないものがあります。

特に OpenSSL は 1.0.1 をベースとしており、1.0.1 は昨年末にサポートが切れています。手元で試すならぜひ 1.0.2 を使いましょう(実は一部OSディストリビューションではそのまま自社サポートの範囲内で継続利用しているところもありますが、個人的にはあまりお勧めしません)。

ここで書かれているopensslコマンドや出力には、1.0.2でも大部分は違いはありませんが、cipher suite系は異なっている場合があります。

特に本文で解説されているFREAK攻撃などにより輸出グレードCipherは全てdisableされています。さらにSLOTH攻撃の影響でSSLv2も完全に削除されています。 最近では LOW cipher もなくなっており、本書で記述されている出力結果と異なる場合もありますので注意して下さい。

翻訳版は原著のフォークをせず、原著の更新を待つというポリシーですので、しばらくは更新を待ちましょう。

4.2 文書による解説の限界

ここ数年 Inria と Microsoft Research のジョイントFREAK, Logjam, SLOTH などTLSに対する非常に高度な脆弱性の発見と公開がされてきました。

私は原論文を読んでいたので、翻訳文を読んでいるときでも「あぁこの部分はこういう記述にしたのか」とか「これはこのことを指しているな」といったことを思いながら読むことができ、それほど違和感を感じませんでした。その記述は正確性を犠牲にせず、どこまでわかりやすく書こうとしているか、著者の工夫が見られるからです。

しかし、ふと訳文だけしか読んでいない読者だとどこまで理解ができるのかな?と少々不安に思いました。

特に「7.6節 トリプルハンドシェイク攻撃」は、理解するのに最難関の部類に入るものです。

これは翻訳の出来、不出来のレベルではなく、実は容易な文言で解説するにはこの辺が限界じゃないかと思えてしまうほどです。

Face-to-Faceの講義や動画アニメーションなど駆使すれば、なんとか読者にも理解してもらえるかもしれませんが、文書だけではどうでしょう? これ以上もっとわかりやすく書いて読者に伝えることができるか?自分でも全く自信がありません。

これらの部分は、一度読んでもわからないからと言ってあきらめず、是非原論文にでもあたって欲しいなと思います。

4.3 秘伝のタレのような内容

原著は、発行後も更新されています。なので記載の時期によって微妙に記述の仕方が変わっている部分もあります。

もちろん技術的な整合性は取れているので問題はないのですが、RC4の危殆化やBEAST攻撃に関する部分など前半と後半で微妙にニュアンスが違っているなと読んでいて感じるところもありました。

他にも、著者の過去ブラウザの挙動の改善にいろいろ取り組んだ時の経緯で、ブラウザのインターフェイスには結構厳しい表現で書いているところも見られました。この点、著者の努力の甲斐があってかこの領域、最近ではブラウザベンダー側の改善が著しい分野です。特に「5.7 セキュリティインジケーター」で記載されているセキュリティアイコンの変更に関しては、翻訳本では最新ブラウザの画像を使っています(本文の更新自体は原著通りです)。これも今後の改訂が期待されるところです。

頭から読んでいると文書の更新時期が想像でき、まるで継ぎ足しのタレを味わっているようで読んでいて味わい深いです。

5. TLS1.3の改訂に向けて

17章のまとめの文章は、私にとっても非常に考えさせられる文章です。まとめの章は、商品ページに全部が掲載されていますので購入前でも読むことができます。

著者は、

TLSはこれまで欠陥が多く修正が重ねられてきたが、もともと完璧なプロトコルなどはなく、どんなものも同様の状況になりえる、これまで普及して成功を収めたプロトコルに希望を持とう」

と書いています。

TLSはこれまでの数多くの技術負債を抱え、かつ最大の後方互換が求められ安定的に動作することが求められるプロトコルです。つい先日 SHA-1 の衝突耐性が破られました。 md5の歴史から2nd-preimage耐性が破られるまでは時間の問題でしょう。SHA-1証明書が今後どういう命運をたどるのか、md5の衝突をついた「4.5 偽造RapidSSL証明書」を読めば予想できます。多大な社会的コストを払ってSHA-2の証明書に移行を進めたのはこういう過去の教訓を踏まえてのことです。今では md5 は Flame マルウェア内で衝突計算が可能になるまでになっているようです。

TLS1.3では、様々な機能の廃止や見直しが行われています。中身はほぼメジャーバージョンアップレベルであるため、バージョン名をTLS2.0やTLS4にするか、大きな議論に発展し最終的にTLS1.3のままで決着しました。通常その仕様を読んでもどうしてこのような仕様になったのか、その議論の経緯や理由が明確にかつ詳細に書かれていることは少ないです。

本書を読めば、ここに書いてある脆弱性や攻撃を教訓としてTLS1.3の仕様が決められていることがわかるはずです。更新版が来れば、全てがこのTLSの技術負債を(完全ではないが)かなり一掃するTLS1.3の仕様につながっている、と理解する日が来るでしょう。

最後に、

以上、あまり書評にもならないことをつらつら書いてしまいましたが、実際この本が翻訳本として日本語で読めることは日本のエンジニアにとって本当に喜ばしいことだと思います。

先に書いた通り、ここに書いて有ることは本当に重要なことに絞り込んだ内容であり、それ以外の部分がその裏に隠されています。

ということはこの本を使えば、残りの90%をさぐる非常に良い足がかかりになります。 なのでこの本を教材とした勉強会を近いうちに内部で開始するつもりです。さぁ、メンバーが残り9割をこれからどこまで探ることができるのか、今から楽しみです。

OpenSSLの脆弱性(CVE-2017-3733)に見られる仕様とcastの落とし穴

0. 短いまとめ

  • OpenSSL-1.1.0dに脆弱性(CVE-2017-3733)が見つかり、Encrypt-Then-Mac と renegotiation を組み合わせて crashさせることができました。
  • この脆弱性は、仕様の準拠不足や不適切な変数の cast などが原因でした。
  • TLS1.3ではこういう落とし穴が少なくなるよう機能の根本的な見直しが行われています。

1. はじめに

先週 OpenSSL-1.1.0d に対してセキュリティアップデートがあり、 Encrypt-Then-Mac renegotiation crash (CVE-2017-3733)という脆弱性(Severity: High)が公開されました。 対象となった 1.1.0 は、昨年2016年8月にリリースされたOpenSSLの新しいリリースブランチです。1.1.0ではAPIの大幅変更もあり、まだあまり普及していないため影響を受けた方は比較的少なかったのではと予想します。 しかし今回の脆弱性、その原因をよくよく探ってみるとなかなか趣深いものがあります。

そこで Encrypto-then-Macとは何か、Renegotiationとはどういうものか、はたまた何故Highにまで影響するような脆弱性になっちゃったのか、その仕組みを書いてみたいと思います。

2. MtE(Mac-then-Encrypt) や EtM(Encrypt-then-MAC) と AEAD(Authenticated Encryption with Associated Data)

インターネット上でセキュアな通信を行うには、暗号化によってデータの盗聴を防ぐ機密性の確保を行うだけでは不十分です。暗号化の有無に関わらずデータの改ざんを検知し完全性を確保することも必要です。 従来、改ざんを検知するにはデータのMAC(Message Authentication Code)を計算し、その値をデータに付与してチェックを行ってきました。

暗号化とMACの計算、どっちを先にやるのか。その手順の安全性に関して古くから議論が行われてきました。代表的には、MACを先に行うMtE(Mac-then-Encrypt)と暗号化を先に行うEtM(Encrypt-then-Mac)の2つのやり方が挙げられます。TLSSSLの時代から 、ブロック暗号(DES/AES)とCBCモードを利用する際にMACを先に行うMtE方式を採用してきました。しかしこの方式を利用していると、復号化してからデータのチェックを行うためパディングオラクル攻撃の対象となり、これまでソフトウェアの実装不備を突いた攻撃手法がいくつも公表されてきました。中でも2013年の Lucky Thirteen 攻撃CBCモードの実装不備を突いた非常に有名な攻撃手法です。

最近になっても2015年に amazon の s2n に対するLucky Microsecondsや、2016年もOpenSSLのAES-NIの実装不備をついたLuckyNegative20などの脆弱性が公表されています。このようにMtEの安全性を確保するソフトウェアの実装を行うためには、高度なセキュリティや計算機科学の知識と実装能力が必要とされます。個人的には素人が手を出せる領域ではないなと感じています。

そんななか、TLS1.2からAEAD(Authenticated Encryption with Associated Data)という暗号化手法が採用されました。これは内部的にEtMを使いつつも、同時に認証用の高速なMACも合わせて計算するといった方式で、その安全性は利用する対称暗号やMAC 方式に依存するということが数学的に証明されています。しかもAEADは、暗号対象となるデータ以外のデータ(平文のヘッダデータなど)の認証も合わせて行うこともできます。何よりAEADの中でAES-GCM方式は、Intel AES-NIやARMv8のAES拡張機能などハードウェア処理機能が提供されていて、他の方式より格段に高速な処理が実現できるといったメリットがあります。

簡単にMtE, EtM, AES-GCM(AEAD)の方式の違いを表したのが以下の図です。 f:id:jovi0608:20170220113615j:plain 現在のTLSでは、まずAES-GCMのAEAD暗号方式使った通信の利用を中心に考えて良いことは間違いないことでしょう。

3. RFC7366: Encrypt-then-MAC for TLS and DTLS

そうは言っても、まだ広く使われているAES-CBCはこのままでいいのか、TLS1.0や1.1もなんとかしないとあかん、ということから、TLSの暗号通信を従来のMtEからEtMに変更できる仕様 RFC7366: Encrypt-then-MAC for TLS and DTLS が2014年に標準化されました。MtEとEtM共に混在することができないことから、EtM用のCipherSuiteを別に用意するということも考えられたのですが、CipherSuiteの数が多くなりすぎるため、ハンドシェイクのClientHello/ServerHelloの拡張を使ってEtM方式の利用を合意する方式が採用されました。やり方としてはクライアントがEtMをサポートしていることを伝えるEtM拡張をClientHelloに付与し、ServerがEtM方式が可能なCipherSuiteを選択したらEtM拡張をServerHelloに付けてクライアントに返せば完了です。もしサーバがAEADなどEtMを必要としていない暗号方式を使う場合は、ServerHelloにEtM拡張を付けずに返します。簡単に書くと下図のようなやりとりです。 f:id:jovi0608:20170220113626j:plain この方式なら比較的簡単にEtM対応が可能になるだろうという見込みを持って仕様化されましたが、やっぱり今回みたいに落とし穴がありました。仕様はホント注意深く読み込まないといけません。

4 Renegotiation

今回の脆弱性は、EtMとRenegotiationを組み合わせたものです。ここではTLSのRenegotiationについて簡単に書いてみます。

TLSは、最初ハンドシェイクを行った後に再度ハンドシェイク(Renegotiation)を行うことができます。2回目以降は既にハンドシェイクが完了しているので暗号化通信上でRenegotiationが行われます。 これが必要なのは、当初サーバ認証でTLSの通信を行っている後にクライアント認証が必要なリソースにアクセスすることが必要になった場合などです。サーバからの合図でRenegotiationを開始し、クライアント証明書のチェックを行うことによって、サー バ認証のTLS接続後もクライアント認証にシームレスに移行することが可能になります。 f:id:jovi0608:20170220113632j:plain 他の用途として、長時間TLSの通信を行っている時に対称暗号の鍵をアップデートをする際にもRenegotiationを使うことがあります。Renegotiation自体は一見何ら問題ないように見えますが、Renegotiation前後で同一のセキュリティが確保できているか、 処理コストが高いのでDoSっぽいことをやられる恐れはないかとか、これまでもRenegotiationを踏み台にした攻撃手法もいくつか公表されたこともあり、その利用価値は次第に小さくなってきています。

今回の脆弱性は、MtEの実装でRenegotiation時の挙動をちゃんと対処できなかったことが原因でした。やっぱりRenegotiation機能はTLSの状態を非常に複雑にし、いろんな落とし穴の一因になっていると言われても仕方ないでしょう。

5. CVE-2017-3733

5.1 CVE-2017-3733 脆弱性の再現

まずは今回の脆弱性を再現させてみましょう。OpenSSL-1.1.0では default でEtM拡張が有効になっています。今回の脆弱性修正パッチから探ると、最初のハンドシェイクでAEADを利用しRenegotiationでEtMを使った暗号に変更すると crash してしまうようです。OpenSSLの s_clientコマンドでは Renegotiation をサポートしていますが、その際暗号方式を変えることができないので少し改造してみます。

下記パッチを使うと s_client で接続後、標準入力で S を入れると AES128-SHAで Renegotiation を行うようになります。脆弱性のある 1.1.0dを使うとクライアントが先に crashしてしまうので修正された1.1.0eの s_client にパッチを当ててみます。

--- a/apps/s_client.c
+++ b/apps/s_client.c
@@ -2440,6 +2440,12 @@ int s_client_main(int argc, char **argv)
                 SSL_renegotiate(con);
                 cbuf_len = 0;
             }
+            if ((!c_ign_eof) && (cbuf[0] == 'S' && cmdletters)) {
+                BIO_printf(bio_err, "RENEGOTIATING for CVE-2017-3733\n");
+                SSL_set_cipher_list(con, "AES128-SHA");
+                SSL_renegotiate(con);
+                cbuf_len = 0;
+            }

先に OpenSSL-1.1.0dでTLSサーバを立ち上げておき、このクライントで接続します。AES128-GCM-SHA256(AEAD)で接続してからコマンドSを入力してAES128-SHAにRenegotiationしてみましょう。

~/openssl-1.1.0e$ ./apps/openssl s_client -connect localhost:8443 -cipher AES128-GCM-SHA256
CONNECTED(00000003)
(中略)
    Extended master secret: yes
---
S
RENEGOTIATING for CVE-2017-3733
(中略)
write:errno=104

なんかエラー出てます。サーバ側がどうなっているのか見てみます。

~/openssl-1.1.0d$ ./apps/openssl s_server -cert ~/tmp/certs/server.cert -key ~/tmp/certs/server.key -accept 8443
Using default temp DH parameters
ACCEPT
(中略)
CIPHER is AES128-GCM-SHA256
Secure Renegotiation IS supported
ssl/record/ssl3_record.c:352: OpenSSL internal error: assertion failed: mac_size <= EVP_MAX_MD_SIZE
Aborted (core dumped)

うわっ、エラー吐いてTLSサーバが abort しています。たった一つのTLSセッションでTLSサーバを落とすことができました。

5.2 CVE-2017-3733 の原因

なんでこんなことになってしまったのか、その原因を探ってみましょう。

OpenSSL-1.1.0dのEtM実装ではサーバは ClientHello のEtM拡張と選択するCipherSuiteを見てEtMを使うか判断し、EtM拡張付きのServerHelloを返すと共にEtM利用のFlag(TLS1_FLAGS_ENCRYPT_THEN_MAC)を立てます。

最初のハンドシェイクでは、 Change Cipher Spec(CCS)の送受信が行われるまで平文通信です。CCSによりEtMの暗号化開始はサーバ・クライアント共に同期が取れていて問題ありません。 ところが Renegotiation は暗号化通信上で行われるハンドシェイクです。暗号化通信上でこのClientHelo/ServerHelloの送受信タイミングでEtM利用のFlagが立ってしまったらどうなるでしょうか?

本来は CCS の送受信のタイミングで暗号方式が変わります、このタイミングでEtMの利用を開始するのは早すぎるのです。

先の脆弱性の再現例では最初のハンドシェイクは AES-GCM でした。サーバ側は EtMのフラグがOnになっているのでAES-GCMで暗号化されたデータをEtM方式で復号化しようとします。まずMACチェックを行いますが、AES-GCMはMACを使いません。本来ありえないAEADのEtMの復号処理、その時点でそのTLSセッションの処理は止まってしまいます。 f:id:jovi0608:20170220113639j:plain 普通1つのTLSセッションのエラーがサーバ全体に波及することはありません。そこにはもう一つ落とし穴がありました。

5.3 int -> unsigned int へ、castの悲劇

じゃこのエラー時、どんな処理がされるのでしょうか? 該当するコードは以下のところです。

    if (SSL_USE_ETM(s) && s->read_hash) {
        unsigned char *mac;
        mac_size = EVP_MD_CTX_size(s->read_hash);
        OPENSSL_assert(mac_size <= EVP_MAX_MD_SIZE);

SSL_USE_ETMが有効化されているのでmac_sizeを取得しに行きます。AES128-GCM-SHA256の場合はAEADなのでMACが定義されておらず mac_size に -1 が返ります。

現状のTLSではMACの最大はSHA512の64バイト、 -1 <= 64 だから assert 問題ないです。しかし、

    short version;
    unsigned mac_size;
    unsigned int num_recs = 0;

あー、mac_sizeは unsigned にキャストされています。 -1 は、4294967295(=232-1) です。AES-GCMのMACサイズはなんと4Gバイト超の巨大な値とみなされます。

OPENSSL_assert(4294967295 <= 64);

これで assert チェックにひっかかり、しかもOPENSSL_assert は abort() まで行きます。 TLSサーバは見事ここで crash です。 この脆弱性は、RedHatのエンジニアからの報告だったようですが、よく見つけたものです。

5.4 修正方法

根本的な問題は、ClientHello/ServerHelloの送受信時にEtM利用を開始したことでした。そこで修正はCCSの送受信時にREAD/WRITEの2つのEtM利用のフラグを使うようにしました。 https://github.com/openssl/openssl/commit/4ad93618d26a3ea23d36ad5498ff4f59eff3a4d2 f:id:jovi0608:20170220113646j:plain 実はこれ、RFC7366の仕様にちゃんと注意事項として書いてありました。

3.1.  Rehandshake Issues
   (中略)
   If an upgrade from MAC-then-encrypt to encrypt-then-MAC is negotiated
   as per the second line in the table above, then the change will take
   place in the first message that follows the Change Cipher Spec (CCS)
   message.

「再ハンドシェイク時のEtMの切り替えはCCS後に変更を行うこと」まさにこれです。もう言い訳ききません。

OpenSSL-1.1.0eでは、今回の破壊的な結果を引き起こした unsigned 変数のキャストやOPENSSL_assert()の処理も修正されました。 https://github.com/openssl/openssl/commit/60747ea22f8b25b2a7e54e7fe4ad47dfe8f93383

実は master の OpenSSL-1.1.1-dev では、 mac_size をちゃんと int で受けて範囲チェックを行い、 size_t にキャストするよう変更されていました。 そのためエラーは発生するものの crash まで行くことはありません。最新ブランチには地道なコードの見直しがちゃんとされているようです。

6. TLS1.3とOpenSSL-1.1.1

OpenSSL-1.1.0では default で使えるようになっているEtM拡張ですが、BoringSSLやNSSで実装する動きはまだありません。すなわちChromeFirefoxなどのブラウザーでのサポート見込みはありません。 TLS1.2でAES-GCMやChaCha20-Poly1305などAEADが使えるようになっているので、わざわざ対応する必要はないということでしょう。

次期TLS1.3では根本的な機能の見直しが行われており、今回の要因となったTLSの機能を廃止・変更しています。

  • Renegotiationを廃止して Post-handshakeを新設。
  • Change Cipher Spec を廃止して、鍵交換後は即暗号化開始。
  • CBCモードの利用廃止、CipherSuiteはAEADのみ利用可に。

よってTLS1.3ではEtM自体が意味のない機能になっています。OpenSSL-1.1.1ではTLS1.3が実装されており、近く正式リリースされるのではないかと期待されています。OpenSSLの開発者が所属する akamai では、4月にTLS1.3を rollout するようです。 TLS1.3の仕様化完了とOpenSSL-1.1.1のリリースが待ち遠しいです。

錆びついたTLSを滑らかに、GoogleによるGREASE試験

0. 短いまとめ

  • 長い間、TLSのクライアント・サーバ間で使用するTLSバージョンを合意する際に、 不完全なサーバ実装によって version intolerance が発生することが問題になっていました。
  • TLS1.3ではこの version intolerance の影響を最小化するため、新しい version negotiation の仕組みを取り入れました。
  • Googleは、GREASE(Generate Random Extensions And Sustain Extensibility)という仕様をChromeに実装し、TLSサーバのバグで通らない拡張やフィールド値で問題が発生しないか試験を始めました。
  • パケットキャプチャが好きな人は、Chromeが 0x[0-f]a0x[0-f]a の見慣れない値をCipherSuiteやTLS拡張に使っているのを見つけても驚かないよう気をつけて下さい。

1. はじめに

 確か半年前も同じような事を言っていましたが、TLS1.3の仕様策定が大詰めを迎えています。
しかし大詰めの段階に来ているにも関わらず、最近のドラフト(draft-15/16)になって、これまでのTLS仕組みを大きく変えるような変更がいくつか導入されました。

この大きな変更は、Netscape社のSSL3時代から20年近く、これまでずっと積み重なってきた技術負債をできるだけここで一掃したいという思いの現れです。しかし一方で、ガチガチにミドルボックスで縛られた現状のインターネットと不完全な実装を持つ多数のTLSサーバの影響を考えるとそうそう一筋縄ではいきません。事情や経緯を知らない人がこの変更点を見るとなかなか理解しがたい仕様になっているでしょう。

今回は、TLS1.3で採用された大きな変更のうち、 Version Negotiation について取り上げたいと思います*1。そしてGoogleは、こんな苦労をもうしないよう、将来のTLSバージョンアップや機能拡張に備えるべくGREASEという仕様ドラフトを提出しました。既にBoringSSLやChromeに実装され、先週よりChrome Canaryで試験運用を始めました。これについても詳しく紹介します。

2. TLS Version Intolerance

 TLSのクライアントとサーバでどのTLSのバージョンを使うのか? この合意(Version Negotiation)は、TLSハンドシェイクの初期で行う非常に重要なステップです。しかし、これほど基本的で重要な機能なのに、現実のTLSの Version Negotiation は不完全で、随分昔(2003年以前ぐらい)から問題が発生していることが知られています。これを Version Intolerance と呼んでいます。日本語だと「バージョン不耐性」でしょうか?なんかよい訳が思い浮かびません。

ちょうど昨日Mozilla のTim Taubert氏のブログ

TLS version intolerance - Working around bugs in legacy TLS stacks - Tim Taubert

で version intoleranceが詳しく取り上げられました。そこで紹介されている Bulletproof TLS Newsletterを書かれているHanno Böck氏のプレゼン資料

TLS 1.3 and Version Intolerance

にも詳しく解説されています。ここに敢えて私が付け加えるようなことはないのですが、後の説明がわかりやすくなるよう簡単に図にして解説してみます。

2.1 TLSの正常な Version Negotiation

 TLSのVersion Negotiationは、ClientHello/ServerHelloのハンドシェイクの最初のやり取りで行います。クライアントは自身がサポートしている最高のTLSバージョンをサーバに伝えます。サーバは、自身がサポートしているTLSバージョンからクライアントがサポートしているものを選んで返します。

TLS1.2のクライアントとTLS1.2のサーバ間では簡単で、ClientHelloとServerHello供にTLS1.2のバージョンをやり取りして合意して完了です。

f:id:jovi0608:20161002085404j:plain

一方、クライアントとサーバ側のサポートが違う場合(サーバ側がTLS1.1までサポートしていない時)、サーバはClientHelloでTLS1.2を受信しますが、TLS1.2をサポートしていないので、ServerHelloでTLS1.1をクライアントに返します。クライアントはそれを受けて、クライアント・サーバ間でTLS1.1を合意します。

f:id:jovi0608:20161002085419j:plain


実はこれをちゃんと実装していないサーバが多く存在していて問題になっています。

2.2 不完全な実装のTLSサーバによる Version Intolerance

 何がちゃんと実装されていないのでどういう問題が起きるのかは様々ですが、一例としてよく挙げられているのは、サーバが自身がサポートしていないTLSバージョンのClientHelloを受信すると接続を切断してしまうというという実装の問題です。

Fallbackをサポートしているクライアントは、Version Intoleranceの問題でClientHelloが切断されるとサーバにオファーするバージョンを下げてもう一回接続に行きます。これを繰り返してサーバがサポートしているバージョンまで合致すると、やっとハンドシェイクが進みます。

f:id:jovi0608:20161002085436j:plain

最終的に接続できるから良いかと思うかもしれませんが、何回も初期接続を試みるのでTLS通信が確立するまで時間がかかります。なによりクライアントは本当にサーバが切断したのか判断つかないので、もし中間攻撃者によって意図的に切断されてダウングレードさせられているのかもしれません。もし脆弱性のあるTLSバージョンまでダウングレードさせられると問題です。

TLS1.2が仕様化された後、このような状況がいくつか見られました。その結果、幾つかのブラウザベンダーは、TLS1.2の対応サーバがある程度普及するまでデフォルトでTLS1.2の通信をオフにしなければなりませんでした。

3. TLS1.3はやっぱり通らない

 TLS1.3でも同じ状況が起こることが懸念されてました。SSLLabでは2012年4月よりTLS protocol torelance を測定しています。その詳しい内容が、

TLS Version Intolerance in SSL Pulse – Network Security Blog | Qualys, Inc.

で公開されています。ここでその図を引用させてもらいます。

f:id:jovi0608:20161002085451j:plain

当初SSLlabは、TLSのRecord LayerとClientHelloの両方共に同じバージョンでサーバに対してTLS1.3とTLS2.152のProtocol Intoleranceをチェックする試験を行っていました。TLS1.3では12%、TLS2.152では60%以上のサーバが接続を拒否しています。この数値は悲惨です。

しかし2015年5月に record layer のバージョンをTLS1.0に固定した試験に変更すると急激に下がりました。2016年7月時点ではTLS1.3で3.2%のサーバが version intolerant であると統計結果が出ています。実はClientHelloのrecord layer中のバージョン指定には明確な規定がないですが、record layerのバージョンのtoleranceは大変厳しいのがわかります。TLS1.3では record layer自体はもう意味がなく、互換性のためだけに付けておく無駄な5バイトになっています。なのでTLS1.3の record layerのバージョンはTLS1.0(0x0301)に固定されました。

Googleも独自に go 実装で Alexaのトップ100万のサイトを調査しました。その結果、1.63%のサイトがTLS1.3のClientHelloを切断したということです。そうなると3.2%〜1.6%がTLS1.3の version intoleranceの見積もりになります。この数字が大きいか小さいか、判断が別れるところです。

その数パーセントの中に大規模なアクセスを受けているサイトがあれば、ユーザへの影響は大きいでしょう。TLS1.2までは問題なく動作していたということで、TLS1.3にしてまず真っ先に問題を疑われるのはクライアント側になります。

このままではTLS1.2の時と同様にTLS1.3の仕様化完了してもしばらくは default off にし、時間を掛けてTLS1.3を deploy していかないといけないかもしれません。できればそれは避けたいところです。

4. 新しいTLS1.3のVersion Negotiation

 そこで TLS1.3では、新しい Version Negotiationの仕組みを採用しました。

それは従来のClientHelloで指定するバージョン番号を legacy_versionとしてTLS1.2(0x0303)で固定し、その代わり新しいTLS拡張でSupported Versionsを規定してそこにTLS1.3のクライアントがサポートするTLSバージョンのリストを記載します。TLS1.3のサーバは、ClientHelloのProtocol Versionフィールドを無視して、Supported VersionsのTLS拡張の方を見てTLSのバージョンを選びます。

f:id:jovi0608:20161002085504j:plain

従来のTLS1.2までのサーバは、Supported Versions拡張を知らないので、これまでのTLS1.2のClientHelloが来たと思って騙されて処理を継続します。これならTLSの新バージョンの導入による version intoleranceの影響を最小限にすることができます。
Googleの測定ではこの方式にすると 0.017% までintoleranceが落ちるという結果がでました*2。これなら十分いけます。

 

しかし、TLS1.3の仕様をよくよく見返してみると、TLS1.3の ClientHelloフィールドのうち半分が実際には不要で互換性維持のためだけに存在する固定値になってしまいました。あぁデザインがきれいじゃない。

f:id:jovi0608:20161002085517j:plain

まぁなんとも言えないもどかしさです。しかしClientHelloのフォーマットを維持してデータ形式上TLS1.xとの互換を保たないとTLS透過性はもっと悲惨なものになるでしょう。そしてClientHelloのProtocolVersionを固定化し、従来のTLSのVersion Negotiationを捨て去ることは、TLS1.3一回限りではなく今後TLS1.4以降でもずっと続く話になります。大きな決断をすることになりました。

5. 実際のTLS1.3のハンドシェイク

 ということで Supported Versionsを使ったTLS1.3のハンドシェイクを見てみます。既にChrome Canary で draft-15なんですがSupported Versionsが実装されたので実際のパケットを見てみましょう。
なお TLS1.3(0x0304)が使えるのは、最終的にTLS1.3の仕様化が完了した後からです。draft段階での相互接続試験においては、使用するバージョンは1オクテット目を0x7f、2オクテット目をドラフト番号にしたバージョン番号を使います。今回Chromeは、まだ draft-15 なので 0x7f0e がバージョン番号になります。まずは ClientHelloから、

f:id:jovi0608:20161002085532j:plain

Record LayerのバージョンはTLS1.0です。ClientHelloのProtocol Versionは、TLS1.2。これで従来のTLS1.2までのサーバを騙します。Supported Versions拡張は43番が割り当てられています。そこにクライアントがサポートするTLSバージョンのリストが5つ記載されます。最初の 0x1a1a は後述するGREASEの値。それから draft-15のバージョン 0x7f0e, TLS1.2 0x0303, TLS1.1 0x0302, TLS1.0 0x0301 が続きます。一応記載順には意味を持たせないことになっています。

続いてServerHello、

f:id:jovi0608:20161002085548j:plain

Record LayerはTLS1.0で固定化したままです。Supported Versionsのリストからサーバが draft-15(07f0e)を選択して Protocol Versionに記載してクライアントに返します。これで TLS1.3の Version Negotiationの完了です。

6. バグで錆びたTLSを滑らかにするGREASE

こんな悲しいTLSの状況で頼みの綱はTLS拡張しかない。しかし楽観視できない、TLS拡張の処理にもバグが固定化して錆びついてしまう。

Googleの Adam Langlay氏は

ImperialViolet - Cryptographic Agility

において

There's a lesson in all this: have one joint and keep it well oiled.
(これについて解決法は、どこか一つを繋げて十分に潤滑させておくことしかない)

と書いています。そこでGoogleのエンジニアDavid Benjamin氏が、GREASE(Generate Random Extensions And Sustain Extensibility)

https://datatracker.ietf.org/doc/draft-davidben-tls-grease/

というドラフトを少し前に提出しました。日本語に訳すと「ランダムな拡張を生成して、拡張性を維持する」ということでしょうか。

これは、2バイトの0x[0-f]a[0-f]aの16個データからランダムにいくつか抽出し、毎回ClientHelloのCipherSuiteやTLS拡張や値にこのランダム値を入れ込んでサーバに送ってしまおうという仕様です。

現状のドラフトでは、CipherSuite値、ALPN拡張の値、supported group(TLS1.2ではEC group)の値、2つのTLS拡張(0バイト長、1バイト長)、supported versionsの値(TLS1.3のみ)の6領域を対象としています。

本来はTLSサーバが知らない未定義の拡張やフィールド値を受信しても基本無視して処理するのがTLSの仕様で求められる挙動です。GREASEは、未定義値を毎回ランダムに送信することによって、TLSサーバのバグを早期に発見し、将来拡張で利用する時に問題が発生するのを未然に防ごうという狙いです。

 

文字通りGREASEは、TLSの拡張やフィールド値の利用がバグ実装の固定化で錆びつかないようグリースを塗り続けるという比喩を表しています。

 

これも既に Chrome Canaryに実装されているので、実際にパケットを見てみるとよくわかります。Chome CanaryのTLS1.2のClientHelloを見てみましょう。

f:id:jovi0608:20161002085602j:plain

CipherSuiteの先頭に0x3a3aの見慣れない値が入っています。これがGREASEです。

 

ちなみに、続く0x16で始まるUnknownな CipherSuite は、Googleが只今絶賛検証中の耐量子コンピュータの鍵交換 CECPQ1 を使った CipherSuite です。これは、djb の考案した楕円関数 curve25519を使ったECDHE(x25519)と new hope というring-LWE方式の格子暗号を組み合わせた鍵交換方式です。これはこれですごく面白いのですが、解説するとめちゃくちゃ長くなるので、いつかの機会に。

 

続いてTLS拡張に入っているGREASEを見てみます。

f:id:jovi0608:20161002085614j:plain

0バイト長の0x2a2aの拡張が頭に1バイト長の0x1a1aのタイプ値を持ったTLS拡張が追加されています。他にクライアントがサポートする楕円関数の種類を広報する EC group 拡張の先頭にも 0xaaaa のGREASE値が入っています。
ALPNへのGREASEの実装はドラフトに記載されていますが、まだのようです。Supported Versionsに関しては、先の TLS1.3のCLientHelloを見てみれば Supported Versions拡張の先頭に0x1a1aのGREASEが入っているのがわかります。 

BoringSSLの実装では、これらのGREASE値をClientのRandomフィールドの頭の1バイトずつをシードにして生成しています(頭の4bit値)。よって、ClientRandomが変わると毎回値が変わることになります。

GoogleはGREASEによってどの程度問題が発生しているのか統計を取って、今から将来のTLSのバージョンアップや機能拡張に備えている試験を始めたわけです。

最後に、全国数X万人のパケットキャプチャ好きなエンジニアの諸君へ

今後ChromeTLSパケットに 0x[0-f]a[0-f]aの見慣れないUnknownフィールドや値を見かけたとしても驚かないように。それはGoogleが、インターネット中のTLSサーバが錆びつかないようグリースを塗っている様子なんです。

*1:この他にもCipherSuiteの構造も大きく変わっています。

*2:Googleは、ChromeのTLS1.3の事前試験でTLS1.3で必須となっている署名アルゴリズム(RSA-PSS)のTLS拡張値でNSSのバグを踏んでハンドシェイクが失敗することを見つけました。0.017%にはこのNSSのバグの影響によるものがいくつか含まれているようです。

JPNICの証明書失効の障害とCertificate Transparency(正解編)

前回のエントリーJPNICの証明書失効の障害事故について事実認定と推測記事を書きましたが、本日報告書が公開されました。

サーバ証明書が意図せずに失効されたことによるJPNIC Webの閲覧不可についてのご報告

文面自体は短いのですが、正解は

  • 新たに発行された3ヶ月弱の期間の証明書は正式に再発行を受けたものであった。
  • 失効の連絡がJPNICに来ず、再発行の証明書を入れ替える前に失効手続きが取られてしまった。

ということであったようです。

前回のエントリーで書いた、期限設定のミスなどはなかったようです。プレ証明書によるシステム的な問題は考えすぎでした。

再発行後、以前の証明書の失効を行う際に事前に連絡確認を怠ったというのが障害発生理由です。

発行からちょうど2週間後に失効されてしまったのは、下記に書いてある規定によるものではないかと思われます。

担当者変更/再発行/解約について

利用中の証明書失効期間について
再発行を行うために利用中の証明書を失効する期限を選択いただきます。発行手続きが完了した後に変更することはできませんので、お間違いないようご注意ください。

後日失効
証明書の入れ替えする期間を考慮し、証明書を再発行した日から2週間後に前証明書が失効されます。
即日失効
証明書の失効と再発行が同時に行われます。

なぜ3ヶ月弱の残り期間で更新を待たず再発行が必要だったのか(かつそんな状況で再発行後2週間経っても入れ替えをしなかったのか)というのは書かれていないのですが、いろいろ事情があったのでしょう。

以上、推測記事の訂正と正解?について書きました。

JPNICの証明書失効の障害とCertificate Transparency

0. Disclaimer

先日JPNICのサイトの証明書が誤って失効してしまったという障害が発生しました。

「障害報告:サーバ証明書が意図せずに失効されたことによるJPNIC Webの閲覧不可につ いて(第一報)」

週末にもかかわらず迅速に復旧対応を行った関係者の方々のご尽力は大変なものであったろうと察します。

筆者は本件と全く関わりはありませんが、公開されている情報から推測すると障害の発生経緯に Certificate Transparency とその周辺のPKI技術に深い関連があるように思えました。
正式な報告書が公開される前ですが、twitterで中途半端に推測をツイートしてしまい拡散されてしまったので、事実認定と推測をちゃんと分けて書いたほうが良かったかなと少々後悔しています。

そこで、正式な報告書が公開されたら記述を追記する予定ですが、現時点で自分が把握している客観的な事実と個人的な推測を分けて書いておきます。

2016年8月4日 20:49(JST)追記: JPNICより正式な報告書がリリースされました。

サーバ証明書が意図せずに失効されたことによるJPNIC Webの閲覧不可についてのご報告

取り急ぎそちらを参照してください。
あわせて 正解記事を書きました。そちらもお読みください。
追記終わり

もし関係者の方で、内容が不適切であると思われた場合は、その旨ご連絡をお願いします。記載内容の修正、もしくは全面的に削除を行います。

1. 外部公開されている情報からの事実認定

1.1: 2015/9/30 09:15 GMT (18:15 JST)

JPNIC利用していた証明書発行
https://crt.sh/?id=9952316

シリアル: 17:87:31:52:fa:44:cd:a8:00:00:00:00:53:fa:f1:c5
Validity
            Not Before: Sep 30 09:15:05 2015 GMT
            Not After : Sep 29 14:59:59 2016 GMT

その前に2年あったSHA1証明書から残り1年でSHA2への入れ替えを行ったようです。これが今回誤失効されてしまった証明書です。

1.2: 2016/7/7 2:05 GMT (11:05 JST)

プレ証明書発行、CTログに登録
https://crt.sh/?id=24044898

シリアル: 34:f7:fc:ef:84:24:28:76:00:00:00:00:53:fb:1a:85
Validity
            Not Before: Jul  7 02:05:21 2016 GMT
            Not After : Sep 29 14:59:59 2016 GMT

なんと1.1と同じExpire期限です。3ヶ月弱有効のもの。

1.3: 2016/7/22 3:00 GMT (12:00JST)

1.1で発行されたJPNIC利用中の証明書が失効

    Serial Number: 17873152FA44CDA80000000053FAF1C5
        Revocation Date: Jul 22 03:00:22 2016 GMT
        CRL entry extensions:
            X509v3 CRL Reason Code:
                Unspecified

これが障害発生の直接的な原因です。

1.4: 2016/7/23 9:19 JST

ユーザが発見, twitterで報告

1.5: 2016/7/23 10:49(JST)

復旧
JPNICの障害報告書による復旧時間です。

1.6: 2016/7/24 11:06 GMT (20:06 JST)

正式発行証明書がCTログに登録される。
https://crt.sh/?id=25317179
2016/7/29現在JPNICのサイトが利用している証明書がこれです。

2. CT(Certificate Transparency)対応EV証明書発行の流れ

JPNICが利用しているEV証明書は、CT(Certificate Transparency)に対応した証明書です。CTについては漆嶌さんの資料が非常に詳しいです。

Certificate TransparencyによるSSLサーバー証明書公開監査情報とその課題の議論

今回1.2に書かれているプレ証明書というのはCT対応の証明書の発行手続きで必要なものです。漆嶌さんの資料からスライドを引用すると図中の丸1に該当します。

これを見ると、EV対応によって認証局内部の証明書発行業務も従来のフローから変更せざる得なかったのではないかと思われます。

3. 障害発生原因の推測

(注意) ここからはあくまで個人的な推測の範囲での記載になります。正式な報告書がリリースされたらそちらを参照して下さい。

3.1 2つのミスが重なったのか?

1.2で発行されたプレ証明書の期限が3ヶ月弱で以前から利用している証明書と同じExpireの期日で、更新が数ヶ月後に迫っているのに非常に不自然です。
また、誤失効がプレ証明書の発行後で正式証明書がCTログに登録される前です。障害対応後に正式証明書がCTログに登録されています。

このことから推測するに、

  • 何らかの理由でプレ証明書の期限設定を間違えて発行してしまったのではないか。
  • 期限設定を間違えたプレ証明書を失効しようとして何らかの理由で利用中の証明書を間違えて失効してしまったのではないか。

といった可能性が考えられます。繰り返しますが第1報以降が公開される前ですのであくまで個人的な推測です。この推測が当たっているかどうか次の報告を待ちたいと思います。

なお、一度失効されてしまった証明書の失効を取り消すことは現実的に難しいようです。EV証明書の運用ポリシーを定めた規定CA/Browser Forum Baseline Requirements Certificate Policyの4.10.1には
「CRLやOCSPの失効エントリは失効された証明書の有効期限以前に削除してはいけない」
と記載されています。

今回誤失効された証明書はExpireするまで削除することはできないため、おそらく今回発行したプレ証明書から作った正式登録証明書に入れ替えて復旧対応したものと推測されます。本当にご苦労様でした。

3.2 Certificate Transparency の闇(プレ証明書)

もし今回の障害がCT対応証明書の発行処理際のプレ証明書の扱いに関連するのであれば、漆嶌さんが見事に予想されていました。

(漆嶌さんのスライドより引用)
現実的にCTによって誤発行の発見などブラウザベンダ側にメリットをもたらしましたが、その副作用で別のリスクが増えてしまったのはなんとも残念です。

今回、漆嶌さんと少し議論して最後に的確なコメントをいただきました。

2016/7/29 20:14 漆嶌さんの漢字が間違ってましたので修正しました。ごめんなさい。ごめんなさい。指摘していただいた林さんありがとうございました。

HTTP/2とTLSの間でapacheがハマった脆弱性(CVE-2016-4979)

0. 短いまとめ

  • 昨晩apache2.4でHTTP/2利用時にTLSクライアント認証をバイパスする脆弱性(CVE-2016-4979)が公表され、対策版がリリースされました。
  • 実際に試すと Firefoxで認証バイパスができることが確認できました。
  • HTTP/2でTLSのクライアント認証を利用するには仕様上大きな制限があり、SPDY時代から長年の課題となっています。
  • この課題を解決するため、Secondary Certificate Authentication in HTTP/2という拡張仕様が現在IETFで議論中です。

1. はじめに

ちょうど昨晩、apache2.4のhttpdサーバの脆弱性(CVE-2016-4979)が公開され、セキュリティリリースが行われました。

CVE-2016-4979: X509 Client certificate based authentication can be bypassed when HTTP/2 is used

リリース文を読んでみると、なにやらapache-2.4でHTTP/2の利用時にTLSクライアント認証がバイパスされる脆弱性とのこと。CVSS3スコアも7.5でHIGH。
はてさてHTTP/2仕様(RFC7540)ではTLSクライアント認証の利用は非常に限定的であまり実用的ではないと思っていましたが、得てして脆弱性とはあまり利用されないところで起こるものです。修正パッチもわずか1行でした。

実はちょうど今IETFのhttpbis WGでクライアント認証を可能にする新しい拡張仕様ドラフトが議論中です。この分野はまさにホットなトピック。HTTP/2 やTLSの仕様と照らしあわせなら実装を見てみると、この脆弱性は新しい仕様の背景の説明ネタとしてはピッタリです。

実際に試すとHTTP/2利用時にクライアント認証をバイパスする脆弱性も再現できました。ということで、この脆弱性通じてTLSとHTTP/2の仕様と課題を理解できないか、新しいエントリーを書いてみます。

2. TLSクライアント認証をバイパスするapache-2.4の脆弱性(CVE-2016-4979)とは?

この脆弱性の中身は、実際に見てみるのが一番でしょう。脆弱性のあるapache-2.4.20でHTTP/2のサーバをたててみます。

/client_auth にアクセスするとクライアント認証が必要なようにapacheのconfigを設定し、アクセスするブラウザはクライアント証明書を何も入れてないFirefox47を使いました。後述するようChromeではこの脆弱性は再現しません。

まずトップページ / を見てみます。青いイナズマが出てちゃんとHTTP/2でページが見えます。コンテンツは、/client_auth へのリンクが貼ってあります 。

リンクを辿って /client_auth にアクセスするとFirefoxにクライアント証明書を全く入れてないのでエラーになります。クライアント認証はちゃんと効いています。

せっかく再試行ボタンが見えているので、再読み込みしてみましょう。

あらー、/client_auth のページが見えちゃいました。こりゃアカンです。一度認証エラーになっても再読み込みをするとアクセスできてしまう、それがこの脆弱性の正体です。

apache httpd修正コミットは、

--- a/modules/ssl/ssl_engine_kernel.c
+++ b/modules/ssl/ssl_engine_kernel.c
@@ -727,6 +727,7 @@ int ssl_hook_Access(request_rec *r)
                      * on this connection.
                      */
                     apr_table_setn(r->notes, "ssl-renegotiate-forbidden", "verify-client");
+                    SSL_set_verify(ssl, verify_old, ssl_callback_SSLVerify);
                     return HTTP_FORBIDDEN;
                 }
                 /* optimization */

のわずか一行。mod_sslの処理においてサーバのSSLインスタンスがクライアントの認証が必要かどうか判断するフラグ verify_mode の処理にバグがあったようです。

3. HTTP/2のTLSクライアント認証の課題

HTTP/2の大きな特徴は、一つのTCP/TLS接続で複数のHTTPリクエスト・レスポンスを多重化してやりとりできることです。これによって、HTTP/1.1で性能のボトルネックになり得る HTTP Head of Line Blocking の解消を実現します。

一方、TLSクライアント認証は、あるリソースへのアクセスをサーバが指定したクライアント証明書を持つ端末に制限する機能です。HTTPサーバは、クライアント認証が必要なリクエストが来るとクライアントにHelloRequestを送ってRenegotiation(TLSの再ハンドシェイク)を発生させます。サーバは、Renegotiationを通じてクライアント証明書と署名データを検証し、正当であればアクセスを許可します。TLSクライアント認証の詳しい解説は、「パンドラの箱?TLS鍵交換の落とし穴、KCI攻撃とは何か」に記載していますので、あまり馴染みのない方は一度お読みください。

HTTP/1.1時代は、一つのTCP/TLS接続を一つのリクエストが専有していたのでこのやり方で問題なかったのですが、HTTP/2では複数のHTTPリクエストが一つのTCP/TLS接続を共有しているので困ってしまいます。

HTTP/2上でHTTP/1.1と同じように renegotiation を行うと、クライアント認証が必要のないリクエストまで巻き添えを食らってしまいます。

そのためHTTP/2仕様では、HTTPリクエストが発生する前の初期接続時のみクライアント認証を許可し、それ以降では禁止しています。従って接続始めからHTTP/2接続全体に認証を行うケースでしかクライアント認証を利用できませんでした。

4. HTTP/2接続後のTLSクライアント認証

このままではHTTP/2を使うとWebのリソースの一部をクライアント認証することができずに困ってしまいます。そこで、あまりスマートなやり方ではありませんが、クライアント認証が必要なリクエストをHTTP/1.1に逃がすことでまぁなんとか対応可能に落とし込みました。

HTTP/2のリクエストがクライアント認証が 必要なリソースへアクセスしたらサーバ側は、そのストリームをHTTP1.1 requiredのエラーコードでリセットします。クライアントはHTTP/1.1 requiredのリセット通知を受けると新たにHTTP/1.1の接続を張り、そのハンドシェイクを通じてサーバはクライアント証明書の検証を行ってアクセスを判断します。 これでなんとか回避可能です。

5. CVE-2016-4979の原因

CVE-2016-4979は、このHTTP/1.1の接続に逃がす際に verify_mode を元に戻すことを忘れてしまったのが原因です。

サーバのSSLインスタンスに保持されているverify_modeは、クライアント認証するかどうかの状態を保持しています。最初はサーバ認証だけなので SSL_VERIFY_NONE が入っています。mod_sslでは一度既存の接続の verify_mode を verify_old にバックアップしておき、新しいリクエストが verify_modeの変更が入るかどうか検証します 。変更があれば verify_mode を新しい値(SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT:クライアントの証明書を検証してダメならエラー)に置き換えて renegotiation を強制させる処理を行います。

しかしHTTP/2では途中での renegotiation は禁止されているので、代わりにRST_STREAMが送信してHTTP/2のTLS接続を維持します。SSLインスタンスとverify_modeは、新しい値に更新されたままです。これが脆弱性を引き起こしました。
先の修正パッチのちょっと前の部分(下記)が該当します。

        /* remember old state */
        verify_old = SSL_get_verify_mode(ssl);
        /* configure new state */
        verify = SSL_VERIFY_NONE;
        (中略)

        /* TODO: this seems premature since we do not know if there
         *       are any changes required.
         */
        SSL_set_verify(ssl, verify, ssl_callback_SSLVerify);
        SSL_set_verify_result(ssl, X509_V_OK);

        /* determine whether we've to force a renegotiation */
        if (!renegotiate && verify != verify_old) {
        (中略)
        }

まさに TODO に書いてある通り、この処理が不十分になってしまい脆弱性を引き起こしたのは皮肉なことです。

FirefoxはHTTP/2ストリームのリセット通知を受け、新たにHTTP/1.1接続を試みますが、正当なクライアント証明書を持ってないため、接続はエラーになります。

再現試験のように続けて再読み込みを行うと、Firefoxは既存の維持されているHTTP/2接続を使って /client_auth にアクセスします。その際サーバのSSLインスタンスの verify_mode が同じであるため、サーバは既にクライアント認証済と判断し /client_auth へのアクセスを許可してしまいました。認証バイパスの成功です。先の試験でもHTTP/2の青い稲妻状態でアクセスできていることからもわかります。

先の1行修正パッチは、HTTP/2接続で renegotiationをHTTP/1.1の新規接続に逃がす際に verify_mode を以前の値SSL_VERIFY_NONEに戻す処理を加えたものです。これによって再読み込み時でもHTTP/1.1の新規接続への退避が繰り返し行われることになり、認証バイパスの脆弱性は回避されました。

Chrome(51のStable)ではこの脆弱性は発生しません。調べてみるとChromeは、http/1.1 requiredのRST_STREAMを受け取ると既存のHTTP/2接続を切断してしまうようです。そのため認証エラー後の再読み込みに新規のHTTP/1.1接続を張るため、この脆弱性の影響を受けなくなっていました。この辺の処理は実装依存なのでまぁ良かったということでしょう。

6. Secondary Certificate Authentication in HTTP/2

HTTP/2上でのクライアント認証の課題は、TLSとHTTP/2の両方の仕組みに関連する本質的なものです。実はSPDY時代からこのことは課題認識されており、SPDY/3ではクライアント認証に対応するためCREDENTIALというクライアント証明書と署名データをやり取りするフレームが規定されていました。

しかしGoogleは実際にクライアント認証を利用していなかったため、結局一度もChromeにCREDENTIALが実装されることはありませんでした。HTTP/2でも当初はスコープ外として早々に機能削除されました。

HTTP/2の仕様完了後、本格的なクライアント認証の機能が欲しいというユーザの声を受け、クライアント認証の拡張仕様の検討が始まりました。当初MSとMozillaの2つのドラフトが提出されていましたが、先のBAのIETFで一本化する方針が決まり、先日Secondary Certificate Authentication in HTTP/2 というドラフトが提出されました。今月のベルリンのIETFでも議論される予定です。

話が長くなるので新しいドラフトの解説は止めておきますが、クライアント認証に限らずこれまでTLSのレイヤで行ってきた証明書のやり取りと検証をHTTP/2のレイヤでも追加で行えるよう拡張するもので、なかなかしびれる機能になりそうです。

こうやって一つの脆弱性から技術プロトコルの仕組みと課題、その解決に向けての検討状況が見えてくるのは非常に楽しいですね。