ぼちぼち日記

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

新しいTLSの暗号方式ChaCha20-Poly1305

Disclaimer

本エントリは、近々IETFで標準化される予定の新しいTLSの暗号方式 ChaCha20-Poly1305 について解説したものです。

本来なら、新しい暗号方式を紹介するいうことは、その暗号の安全性についてもちゃんと解説しないといけないかもしれません。しかし一般的に暗号の安全性評価はとても難しく、専門家でない者が暗号の安全性について軽々しく書くわけにはいかないなとも思いました。いろいろ悩みましたが、結局無用な誤解を避けるため、本エントリーでは ChaCha20-Poly1305 の安全性に関する記載を最小限に留めています。

今回紹介する ChaCha20-Poly1305 は、これまでも様々な暗号研究者の評価を受けている暗号方式ですが、まだNISTの標準や某国の推奨暗号リストに掲載されるといった、いわゆる特定機関のお墨付きをもった暗号方式ではありません。記載内容が中途半端で申し訳ありませんが、ChaCha20-Poly1305 の暗号利用の安全性について心配な方は、本エントリーをお読みにならないようお願い致します。またこういった内容を留意していただいた上で、本記事をお読みくださるようお願いします。

2016年6月23日追記:
RFC7905「ChaCha20-Poly1305 Cipher Suites for Transport Layer Security (TLS)」 が発行されました。

0. 短いまとめ

記事が長いです。長文を読むのが苦痛な方は、この短いまとめだけ読んでください。

  1. 新しいTLSの暗号方式 ChaCha20-Poly1305 が仕様化されます。
  2. 新仕様版はChrome49/Firefox47/OpenSSL-1.1.0(beta)などで既に利用可能です。
  3. AES-GCMより転送サイズが小さくなり、AES-NIのないARM環境では3倍程度性能が高くなります。
  4. OpenSSLなどでcipher server preferenceと両立して運用するには現時点で課題があります。equal preference cipher groups機能待ちです。

(2016年5月16日 訂正: 記事中 TLSのAES-GCMのタグ長が12バイトと記載されていましたが、16バイトの誤りです。図表と記載を修正しました。id:kazuhooku さんのご指摘ありがとうございました。)

1. TLSに新しい暗号方式が必要となった背景

常時HTTPS化は、これからのWebには避けられない流れです。その中核となる通信プロトコルTLSには、現在いろいろなリスクや課題が存在します。

1.1 AESがTLSのSPOFになっている

その課題一つに現在広く利用されているAESを使った暗号方式に関するものがあります。

  • 現在実用的に広く使えるTLSの対称暗号が実質AESの一択しかない。

AES以外の暗号で普及しているという意味ではまだ3DESも健在ですが、性能がAESに比べて1/4〜1/10に落ちてしまいます。AES/3DES以外にも性能的に見劣りしない対称暗号もありますが、世界的に広く使われているとは言い難いです。

こんな状況でもし今、AESに重大な問題が見つかったらとしたらどうなるか? TLSの運用者は非常に厳しい選択に迫られます。対称暗号以外ではTLS1.2において、認証は RSA/ECDSA, 鍵交換は DHE/ECDHEと2つ以上の仕組みが存在します。リスク管理の観点から、現実的に代用できるAESのバックアップを持つことが今のTLSに必要です。

1.2 AES-NIのないARM環境でも安全で高速な暗号が欲しい
  • AESはハードウェア処理(AES-NI)が使える環境では非常に高性能だが、ソフトウェア処理だけではそれほど性能がでない。またAESはキャッシュタイミングなどサイドチャネル攻撃を避ける必要がある。そのため実装が複雑になりがちである。

IntelAMDのCPUで実装されているAES-NIは、非常に強力な機能です。AES-NIを使うだけで暗号処理能力は数倍に跳ね上がります。しかしモバイルやIOTの環境でよく利用されるARMにはAES-NIなど入っておらず、非力なCPUの上でソフトウェアで頑張らないといけません。その上、やみくもな最適化はキャッシュタイミングによるサイドチャネル攻撃を受ける可能性があります。

シンプルな実装で、なおかつAES-NIのない環境でも十分に性能が確保できる暗号方式が求められています。

1.3 新しい暗号方式 ChaCha20-Poly1305 の導入

ChaCha20はストリーム暗号、 Poly1305はメッセージ認証(MAC)の機能を実現します。共に著名な D. J. Bernstein (djb)氏が考案しました。djbは当初SalSa20という暗号を公開し、後にChaCha20という改良版を発表しました。いずれもラテンダンスの名前にちなんでいるようですが、はっきりと名付け由来が書いてある記述を探し当てることはできませんでした。どなたか由来を知っている人がいらっしゃいましたら教えて下さい。

4/5追記: id:bottomzlife さんより情報を頂きました。ありがとうございます。

Poly1305に関しては、最初 dbj djb はhash127という2^127-1の素数を使ってMACを計算する方式を発表していましたが、後により計算効率が良い2^130-5の素数を使ったPoly1305を2005年に発表しました。どちらも鍵の生成に対称暗号と組み合わせることが必要で、論文ではAESと組み合わせたPoly1305-AESが使われていました。Poly1305の仕組み上、AES以外の対称暗号とも組み合わせられることが可能です。

RFC7539では、Poly1305がChaCha20と組み合わせた形で仕様化されました。ChaCha20/Poly1305のどちらも知的財産権などの問題もないと言われています。ChaCha20とPoly1305は、ともに非常に簡潔なアルゴリズムで規定されており、ソフトウェア処理に向いています。これを使って先に述べたTLSの課題を解決できるものと期待されています。

1.4 ChaCha20-Poly1305のこれまでの歩み

ChaCha20-Poly1305のこれまでの歩みをざっと表にしました。

TLSに関連する動きが出てきたのが2013年から。このあたりで、Chacha20とSHA1を組み合わせたり、Salsa20とUMAC(RFC4418)と組み合わせたドラフトなどが公開されていました。動きが本格化したのは、2013/09 にGoogleのセキュリティ専門家 Adam Langlay (agl)氏が、ChaCha20とPoly1305を組み合わせた暗号方式のドラフトを提出してからです。

ちょうど2013/06にEdward Snowden事件でNSAによる広範囲の盗聴・改ざん行為が明らかになり、Dual_EC_DRBGのバックドア問題などNSAが関与しているNIST標準の暗号方式に対する疑義が高まった時分でした。過去米国政府と暗号技術に関して何回も法廷闘争を繰り広げたdjbの暗号は、安全性に対する信頼は他とは違うぞと、この時期の新しい暗号方式の提案にはそんな象徴的な意味合いを個人的に感じてしまいます。

早速ChaCha20-Poly1305は、2013/11にChromeに先行実装され、現在まで既にGoogleのサービスで利用されています。この時期の先行実装は、現在のLastCall 版の仕様と若干異なっており、互換性はないものでした(Googleは既に最新仕様に移行しています)。

Google以外の実装では、2014/04に発覚したHeartBleed脆弱性の余波でLibreSSL/BoringSSLがOpenSSLからフォークされたことを受け、これらのフォークが直ちにChaCha20-Poly1305をサポートし始めました。これらの実装の広がりを受け、IETFTLS WGの要請を受けCFRG(Crypto Forum Research Group)で議論が進み、2015/05にChaCha20-Poly1305の仕様がRFC7539として発行されました。

このRFC7539で規定されたChaCha20-Poly1305をTLSで使えるようにするのが、 draft-ietf-tls-chacha20-poly1305-04 です。2016/3/22にIETF Last Callが開始され、ちょうど明日(4/5)に終わります。IESGとIANAのレビューが無事通れば、近いうちに晴れてRFCになる予定です。この仕様はTLS/DTLSの両方の利用を規定していますが、本エントリーはTLS向けの仕様だけを解説します。

1.5 TLSで使えるChaCha20-Poly1305の種類

今回新たにTLS仕様化されるのは以下の7つの CipherSuiteです。いずれもAEAD(認証付き暗号)であるため、AEADの規定がないTLS1.0/1.1では仕様上利用できません。使うにはTLS1.2以上が必要です。

TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 O, F, C
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 O, F, C
TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 O, Fコメント欄参照
TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 O
TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 O
TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 O
TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 O

(O: OpenSSL-1.1.0, F: Firefox, C: Chrome)

PSK(PreSharedKey)のCipherSuiteが4つ。ECDHEの鍵交換はRSA/ECDSA認証の2つ、残る1つはDHE/RSAのものです。master secretを生成するPRFにはすべてSHA256を利用します。個人的には Chrome49, Firefox47, OpenSSL-1.1.0-beta1 で既に実装されていること確認していますが、LibreSSLやその他のTLS実装はどの程度対応しているのかまで見ていません。各実装がサポートしているCipherSuiteを表の2カラム目(O, F, C)として記載しておきます。

2. ChaCha20とは何か?

先述した通りChaCha20は非常にシンプルなアルゴリズムを使った暗号です。せっかくだからその仕組みを解説したいと思います。まずはAESと比較してみてChaCha20の特徴を見てみます。

2.1 AESとChaCha20の比較


AESはブロック暗号、ChaCha20はストリーム暗号と分類されます。しかしAES-GCMなどカウンターモードでのAESの利用は、実質ストリーム暗号として機能します。AEADの観点から比較するとストリーム/ブロックでの両者の暗号の違いはほとんどありません。ChaCha20は、入力にNonceと初期カウンター値が必要 なのが特徴です。暗号の安全性を確保するため、Nonceは同じ値が使われないことが必要です。Nonceサイズはオリジナルの djb の論文と異なっています。これはAEADの仕様(RFC5116)の推奨サイズに合わせたためです。

暗号実装では両者は大きく違います。AESでは、剰余計算を高速に行うために事前計算テー ブルを作成したり、要素を非線形に変換するSBOX処理が必要ですが、ChaCha20は後述する通り加算剰余、XOR、ビットローテーションという単純な計算処理だけです。ChaCha20は、SIMDを使うとより高速なソフトウェア処理が可能になります。

2.2 ChaCha 1/4ラウンド演算

先程から繰り返し書いている通り、ChaChaの暗号化を担う処理は非常にシンプルなものです。この処理を行う中心的な関数を1/4ラウンド演算(QuarterRound)と呼びます。ChaChaは内部に4バイトx16個の状態を持っています。このQuarterRoundを使って内部状態の中の4つの要素(a, b, c, d)を混ぜあわせ、新しい要素の値に更新します。

QuarterRoundは、上図の通り4つの演算から成り立っています。 x + y は (x+y) mod 2^32, ^ は XOR, <<< n はnビット左ローテションを表します。注目すべきは乗算がないことです。一般的に暗号技術は、乗算処理が含まれることが多いのです。ChaChaは乗算処理がないため、演算中の桁上り伝搬を心配 する必要がほとんどありません。ビットローテーションも固定長なので、容易に Constant Time の演算処理が実現できます。これがサイドチャンネル攻撃を受けにくい理由です。

ChaChaのQuarterRoundは、1回の処理で4つの要素が2回ずつ更新されています。ここはSalsa20から改良された点で、ChaChaがSalsaより安全性が高くなっていると見られている点です。

2.3 2種類のChaChaラウンド

ChaCha20は、4バイトx16個の初期状態をQuaterRoundを使って状態を変えていきます。QuaterRoundを適応させるやり方として、列の要素4つ対する列ラウンドと、対角的に並ぶ要素に対する対角ラウンド、この2種類のChaChaラウンドを規定しています。これを使い16個の要素全部に対して偏りなく演算を行っています。

2.4 ChaCha20 Stream State

ChaChaの初期の状態はどうやって決まるでしょうか? 最初の4バイトx4は定数値です。これは expand 32-byte k という固定文字列が入っています(このmagic wordの由来もわかりませんでした)。

次にChaCha20の鍵データ、32バイトが続きます。その後に通常1から始まるカウンター値、12バイトのNonceが入ります。これを 列ラウンド+対角ラウンドの演算を10回繰り返します。結果1/4ラウンドを20回繰り返すことから ChaCha20 ということになります。20はラウンド数を表しています。

ChaCha20は、20ラウンドの演算を経て最終的に生成した ChaCha20 State を使って平文から暗号文を生成します。

2.5 ChaCha20による平文の暗号化

平文から暗号文の生成は簡単です。平文を64バイトのブロックに分割します。それぞれのブロックに対してカウンター値を上げながら、それぞれのChaCha20 Stateを計算します。出来上がった ChaCha20 State と64バイトブロックの平文との XOR をとり、暗号文を作ります。

暗号文から平文に戻す手順もほぼ 同一手順です。同じ鍵, Nonce, カウンター値からChaCha20 Stateを作り、暗号文とXORを作れば平文が生成できます。このようにChaCha20ではEncrypt/Decrypt が全く同じ処理でできます。これはソフトウェア処理がシンプルになる大きなメリットです。

AES単体のDecryptは、Encryptの逆関数を用意しなければならず、ChaChaほど簡単にいきません。

3. Poly1305とは何か?

次はPoly1305です。まずは AES-GCMで使われているメッセージ認証GHASHと比較してみます。

3.1 GHASHとPoly1305の比較


GHASHとPoly1305のどちらも Wgman-Carter Constructionという方法でメッセージ認証データを生成します。GHASHはガロア拡大体、Poly1305は素体の有限体上で演算を行います。

仕様的にGHASHは明示的な鍵長制限を持ってなさそうですが(間違っていたら誰か教えて下さい)、AESと組み合わせたAES-GCMの場合は、128bitsの鍵を利用 します。Poly1305は、256bit長の鍵を使います。後で説明しますが、256bit長の鍵を2つの鍵に分割して利用します。
GHASHとPoly1305、ともに生成するMACデータ長はどちらも128bitsです。ただしGHASHの方は、用途に合わせて小さく切り詰めて利用され、AES-GCMでは96bit128bit長にしています。GHASHの利用は、セキュリティの観点から64bits以上であることが求められています。

GHASHの特徴はなんとってもハードウェア処理。Intel/AMDが提供するPCLMULQDQ命令セットを使うと非常に高速に演算できます。
Poly1305の方はChaChaと同様、非常にシンプルなアルゴリズムを持ち、ソフトウェア処理が得意です。
しかも2^130-5という128bitsを少し超えた素数上での演算であるため、計算効率が非常に高くなります。また8bitから64bitまでのサイズの変数を使った最適実装を作ることができ、様々なアーキテクチャ上で高速に動作させることができます。さらにPoly1305は、剰余算を高速化させるために通常行う事前計算テーブ ルの作成が不要です。ただし剰余を求める際にタイミング攻撃に合わないよう Constant timeな実装が求められます。

3.2 Polynomial evalation(多項式評価)

Poly1305のPolyは、Polynomialの略です。MACによって任意の長さのメッセージが改ざんされていないか認証するためには、認証するメーッセージを何かの形で評価することが必要です。
Poly1305は、メッセージを128bits長のブロックに分割します。各ブロックを係数とした多項式f(x)を作成し、xの値が鍵データrの時のf(r)の値でメッセージを評価します。この多項式には0次の定数項がないのが特徴です。もし定数項があるとメッセージ評価値の差分を取ることによって鍵情報が部分的に漏洩してしまうからです。多項式評価を使うメリットは、ホーナー法によって高次の乗算計算を減らして加算の再帰として計算できるからです。ホーナー法を 利用することによって高次の事前計算を避けることができ、ソフトウェア実装がし易くなっています。

3.3 Wegman-Carter Construction

GCMとPoly1305、ともにメッセージ認証コードの生成は、Wegman-Carter Constructionという方式を利用します。

Webman-Carter Constructionとはメッセージのユニバーサルハッシュに一時鍵を加えた値をメッセージ認証値とする方法です。数学的に強度が証明できていることが特徴です。なにより高速です。 ユニバーサルハッシュは、暗号だけでなく誤り訂正などにでも利用される高速なデータハッシュ手法ですが、SHA-1/2のような原像攻撃に耐性があるようなわけではなく、暗号ハッシュほどの強度はありません。しかし Wegman-Carter Construction によってメッセージ改ざんを検知するに十分な強度を得ることができるためTLSのAEADでの利用が進んでいます。

3.4 Poly1305のアルゴリズム

Poly1305のアルゴリズムも単純です。tweet(140字以内)で表現可能といわれるぐらいです。

Poly1305のMAC計算は、認証するデータを16バイト長で分割し、頭に0x01を付加して17バイト長のブロックにします。16バイトに満たないブロックは頭に0x01をつけた後0パディングして17バイト長に揃えます。

16バイト長の鍵を2つ r と s を用意し、鍵rの方は128bit全部利用せず、途中のビットを0にして22bit分間引きます(ここがdjbのすごいところです)。
生成したメッセージを多項式評価として鍵rの値で評価し、素数2^130-5の剰余をユニバーサルハッシュを求めます。一時鍵sを加えれば、強度の高いWgman-CarterのMACデータが出来上がりです。しかしこのままだと最大130ビット長となって扱いにくいので、頭の2bit分を取り除き16バイト長のメッセージ認証 データにします。

Poly1350で利用する鍵 r, s は ChaCha20を使って生成します。カウンター0のChaCha20 Stateを求め、上位16バイトをs 下位16バイトをrに割り当てます。

2^130-5という128bitよりちょい大きい絶妙なサイズの素数を利用しているため16バイトという大きいブロックでメッセージ分割した多項式評価が行える。22bit分間引いた鍵rを使用しているため部分剰余のサイズが一定の範囲に納まり、ホーナー法を使いながら各ブロック毎に逐次計算がしやすくなります。

4. ChaCha20-Poly1305とは何か?

これまでChaCha20とPoly1305を別々に見てきましたが、TLSではChaCha20とPoly1305を組み合わせてAEAD(認証つき暗号)として利用します。従来のMac-then-Encrypt(平文のMACを取ってから暗号化を行う方法)では過去いろいろな脆弱性の影響を受ける可能性があるため、ChaCha20-Poly1305はAEAD一択です。

AES-GCMとChaCha20-Poly1305を比較してみます。

4.1 AES-GCMとChaCha20-Poly1305の比較


AES-GCMとChaCha20-Poly1305の大きな違いは、ChaCha20-Poly1305の鍵交換はECDHE/DHEだけしかサポートしないことです。TLS1.2といえでも、もはやForward Securecyのない鍵交換はサポートしません。

またChaCha20-Poly1305では明示的なIV(Nonce)データを必要としません。明示的IVは、TLS1.0でBEAST攻撃に対応するためTLS1.1に導入された項目ですが、次期TLS1.3では廃止される予定です。ChaCha20-Poly1305は、そのTLS1.3仕様を部分的に先取りしたものになっています。

ChaCha20-Poly1305はTLSの暗号化通信を行う際に双方で持つシーケンス番号(8バイト)のデータをNonceに利用します。シーケンス番号を0でパディングし、master secret から生成される12バイト分のWriteIVとXORを取ってNonceとして利用します。こうすることによって、わざわざ8 バイトのIVを明示的に送信することがなくなり、その分通信データ量を削減することができます。またバグなどでIVが再利用されてしまうといった間違いも避けることができるので、より安全な実装になることが期待されています。

4.2 ChaCha20-Poly1305のAEAD処理フロー

AES-GCM仕様のフローに似せてChaCha20-Poly1305によるAEAD生成(暗号化)のフローを書くとこのようになります。

初期カウンター0でPoly1305用の鍵(r,s)を作り、初期カウンター1に増加させてChaCha20 Key Streamを生成し、平文から暗号文に変換させます。
認証を行うデータと暗号文それぞれを0でパディングして16バイト長に揃えます。平文(8バイト長)と暗号文の長さ(8バイト長)を結合して、全体のデ ータのMACをPoly1305で計算し、128bitのMACデータ(認証タグ)を取得します。TLS1.2では認証を行うデータは、TLSレコード層のデータとシーケンス番号を合わせたデータです。最後に暗号文に認証タグをつけて暗号化されたTLSのアプリケーションデータとして相手に送信します。

4.3 ChaCha20-Poly1305とAES-GCMのサイズ

AES-GCMとChaCha20-Poly1305で1バイトの平文をAEADで暗号化した場合の比較をしてみましょう。

どちらもストリーム暗号として機能するので暗号文は1バイトです。タグのサイズはChaCha20-Poly1305が16バイト、AES-GCMが12バイトです。 タグのサイズはChaCha20-Poly1305とAES-GCMともに16バイトです。
ただAES-GCMは明示的IVの8バイト分先頭に必要です。レコード層の5バイト分は共通ですからChaCha20-Poly1305は22バイト、AES-GCMは26バイト30バイト。TLS1.2ではChaCha20-Poly1305の方が、AES-GCMより小さいサイズに収まります。

5. ChaCha20-Poly1305 vs AES-GCMの性能比較

本当に ChaCha20-Poly1305 は、ARM環境では速いのか? AES-GCMと性能比較してみます。

EC2 c3.large (Intel Xeon E5-2666 Haswell)とRaspberry Pi2 (ARM Cortex-A7)上で openssl speed コマンドによるベンチマークを行いました。ベンチを行った時期がちょっと前だったのでOpenSSLは 3/1 時点での master head( OpenSSL-1.1.0-pre4-dev master HEAD on 2016/03/01)を使っています。その後ChaCha20-Poly1305のアセンブラ関連のバグ修正などされていますが、性能自体はそれほど変わっていないはずです。

Haswellを搭載するc3.largeは、現時点でのEC2の最速CPUで、AES-NI/AVX/AVX2とハードウェアの力で暗号処理を高速化させる機能が満載です。OpenSSLもAES-NI/AVX/AVX2に対応するようビルドしています 。Raspberry Pi2 の ARM の Cortex-A7 には、 NEON によるベクトル処理がサポートされています。しかし2016/04/04時点でまだopenssl-1.1.0にバグが残っており、正常に Raspi でビルドできませんので、実際にベンチを試される方は注意して下さい。

グラフでは 8Kバイトのスループットを比較してみます。

まずは Intel Haswell上での比較から。

うん、AES-GCMはやっぱり速いです。3.1Gbpsのスループットを叩き出しています。ChaCha20-Poly1305はAES-GCMのおよそ半分 1.6Gbpsのスループットがでます。逆にいうとAES-NIという強力なハード処理の助けがなくてもここまで性能が出せるのは健闘でしょう。

ARM上ではどうなる?

おぉ! 確かに ChaCha20-Poly1305の方がAES-GCMより3倍程度速いです。絶対的なスループット性能は Intel Haswellには全くかないませんが、AES-NIのないARM環境では AES-GCMより ChaCha20-Poly1305 の暗号方式を利用するのが性能的に良いことが明らかです。

6. TLSの運用はどうなる? 現状の問題点とは。

6.1 ブラウザは ChaCha20-Poly1305 をどう使う?

TLSで利用する CipherSuite は、TLSのハンドシェイクで決まります。クライアントがハンドシェイクの最初に送信する ClientHello に CipherSuite リストが含まれ、そのリストからサーバが暗号方式を決定し ServerHello で返します。ClientHelloのCipherSuiteのリストは、クライアントが優先して欲しい順番で記載されています。実は ChaCha20-Poly1305の導入に合わせ、Chromeは動作環境によってサーバに送信するCipherSuiteリストを変えるように機能が追加されています。。

Chrome は、端末がAES-NIとAVXをサポートしている時のみChaCha20-Poly1305よりAES-GCMを優先します。

Issue 91913002: net: boost AES-GCM ciphers if the machine has AES-NI.

つまりdefaultは、ChaCha20-Poly1305が最優先で、AES-GCMの性能が高いと判断できるときのみAES-GCMをCipherSuiteのリストの先頭に持ってきています。

Firefoxは、ARM環境の場合の時のみ AES-GCMより ChaCha20-Poly1305を優先させる予定です。

Bug 1126830 - Prioritize ChaCha20/Poly1305 ciphers over AES-GCM for ARM builds

ただこのパッチ、一度 Land されてましたがリリース直前だったため差し戻しを受け、2016/04/04時点でまだ未適応です。

6.2 サーバ側の暗号選択の難しさ

このようにブラウザ側がクライアントの環境に合わせて ChaCha20-Poly1305と AES-GCM のCipherSuiteリストの優先度を変えてTLSのハンドシェイクを開始してくれます。サーバ側はクライアントの環境を直接わからないのでこれは嬉しいことです。

サーバ側はクライアントから送られてくるCipherSuiteリストの順番を見て、単純に優先しているのを決めれば良いかというとそう単純ではありません。通常サーバは、サーバ側のセキュリティポリシーに従い、優先度付きのCipherSuiteリストを保持しています。これと相性が悪いのです。

先ほど述べた通り、TLSのハンドシェイクではサーバはクライアントから送られたCipherSuiteとリストとサーバ側が決めているリストとを比べ、1つの暗 号方式を選択します。サーバ・クライアントともに独立して優先度付きリストを持っているので、選択する方法はクライアントの優先度に従うか、サーバ の優先度に従うか2通りしかありません。クライアントが常にサーバ側のポリシーにあった正しい優先リストで送られてくるのかわかりません 。

一般的なTLSサーバの推奨設定では、サーバ側でセキュリティポリシーで暗号選択を行う設定(Cipher Server Preference)を入れる場合が多いです。

nginxでは、 ssl_prefer_server_ciphers on 、 apacheでは、 SSLHonorCipherOrder on といった設定に該当します。しかしこの場合、 ChaCha20-Poly1305とAES-GCMの選択に問題が生じます。 サーバ側の優先度だけで判断してしまうので、せっかくブラウザがクライアント側の環境に応じて変えた暗号方式の優先度情報が生かされなくなってしまいます。 サーバが ChaCha20-Poly1305の方を優先する設定にすると、ARMやAES-NIのどちらのクライアントも ChaCha20-Poly1305を選択してしまいます。

6.3 OpenSSL-1.1.0に足りないequal preference cipher groups機能

ではGoogleはどうしているのでしょうか? 実は、ChaCha20-Poly1305を実装する際 BoringSSLには equal preference cipher groups機能を実装していました。

Equal preference cipher groups

これは、[]で複数のCipherSuiteをグループ化し、この中の CipherSuiteはクライアントからの優先順に従うというものです。

Googleのサービス GFE(Google Front End)に対して cipher1:cipher2 の2つのリストでどちらが選択されるのか、試してみました。

結果からGoogleは、

[ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-CHACHA20-POLY1305]:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256

なリストで運用しているのではないかと予想が付きます。

この equal preference cipher groups機能は便利ですし、是非ChaCha20-Poly1305の導入と共に使いたいところです。

実は、OpenSSLでは今年の1月ぐらいに同様のリクエストが issue で登録されていました。

Support bracketed equal-preference groups in SSL_CTX_set_cipher_list

今回もうすぐOpenSSL-1.1.0がリリースされるので、どうなんだろうと要望を書き加えたところ、OpenSSLチームの一人から「私も欲しいけど、もう時間切れ」とのこと。あぁ OpenSSL-1.1.0はもう feature freeze に入っています。時すでに遅し。パッチ出したとしても1.1.1 以降まで待たないといけないでしょう。

結局今のところ OpenSSLで ChaCha20-Poly1305 をAES-NI/ARMのクライアント環境で共に最適に利用するには Cipher Server Preference を利用しないようにするしかないです。この設定が受け入れられないところも多いでしょうね。うぅ、なんともったいない・・・

ということで、今後普及が見込まれるTLSの新しい暗号方式 ChaCha20-Poly1305 について解説しました。まだまだ課題はありますが、次期TLS1.3では AES-GCMとChaCha20-Poly1305 がMTI(Must To Implement)として規定されています。この2つのデファクトはこれからしばらく続く可能性が高いので、きちんとした理解を持ってより安全なTLSの運用を目指しましょう。

ハッシュ衝突でTLSを破るSLOTH攻撃(CVE-2015-7575)とは何か

0. 簡単なSLOTH攻撃のまとめ

最初に簡単なまとめを書いておきます。長文になりそうなので、読むのが大変な方はここだけ見ておいてください。

  • MD5ハッシュは既に安全ではなく、証明書の署名方式での利用は停止されていたが、後方互換のためハンドシェイクデータの署名方式にRSA-MD5が今でも利用できるTLS実装が幾つか存在していた(Firefox NSS, Java等)。
  • 先週、INRIAグループからハッシュ衝突を利用して実際にTLSを破る攻撃(SLOTH)が公開された。それを受け、いくつかの実装でRSA-MD5を完全に利用不能にする修正が行われた(CVE-2015-7575)。
  • SLOTHでは、SHA1TLS、IKE、SSHに対する攻撃についても評価を行い、幾つかは全く現実的に不可能なレベルではないことが示された。MD5SHA-1TLSハンドシェイクの完全性を担保しているTLS1.0/1.1への脅威は大きくなった。

1. SLOTHとは何か?

これまでFREAKやTriple Handshake, Logjam 攻撃などTLSに対する様々な攻撃を発見してきたINRIAグループが、先週また新たなTLSの攻撃手法(SLOTH)を公開しました。

Security Losses from Obsolete and Truncated Transcript Hashes (CVE-2015-7575)

タイトルを日本語訳すると、「古く廃止されたTruncated Transcriptハッシュによるセキュリティ低下」でしょうか? 何を言っているのかわかりませんね。この攻撃を受ける脆弱性には、CVE-2015-7575も割り当てられており、今のところ先月に対応されたNSS(Firefox)のセキュリティアップデート情報が掲載されています。幸いOpenSSLは、2年ほど前に Don't use RSA+MD5 with TLS 1.2で修正を行っていたため、難を逃れました。OpenSSL-1.0.1f以降なら問題ありませんが、古いRHELで影響を受けるようです。JavaやGnuTLS等その他のTLSライブラリの状況に関しては、INRIAのサイトでまとめられています。
この攻撃の概要は、先週行われたReal World Cryptography Conference 2016で発表されましたが、この論文が採択された来月のNDSS Symposium 2016でもさらに詳細が発表されるでしょう。

このSLOTH攻撃は、TLS実装がRSA-MD5等既に安全でない署名方式が有効になっている場合に、あるハンドシェイクデータと同一のハッシュ値を持つ別のデータを計算し、それを偽造データにして中間者攻撃を仕掛けてTLSの安全性を破る攻撃です。

今回攻撃で使われたMD5は、かなり前から安全ではないことがわかっていましたが、現実的な攻撃としてMD5のハッシュ衝突を利用した偽造証明書の作成に成功したのは2008年末のことでした(MD5 considered harmful today)。これが非常にセンセーショナルな出来事だったため、その反省からブラウザベンダーや認証局は、現実的な攻撃が世に出る前に急いでSHA-1の証明書の移行・廃止を現在進めているわけです。

証明書の署名方式が、このような素早い廃止対応を進めているにもかかわらず、これまでTLSは全面的にMD5を利用不能にしていませんでした。それは静的な証明書データの攻撃に比べ、TLSハンドシェイクは接続時に毎回データ異なるため、現実的にハッシュ衝突の攻撃は難しいだろうと考えられていたからです。しかもMD5は、TLS1.0や1.1でmaster secret等を生成するPRF(擬似乱数生成関数)にも使われているので、完全にMD5アルゴリズムを実装から削除するためには、TLS1.2への全面移行が必要です。これは非常に大変な作業です。

しかし、ついにMD5ハッシュ衝突の計算処理の効率化とTLS仕様の隙間を組み合わせることによって、TLSのハンドシェイクに対する現実的で具体的な攻撃手法が編み出されました。

SLOTHは、TLSハンドシェイクで中間者攻撃が自由に改変できる部分を利用します。攻撃者はハッシュ衝突が起こる偽造データを途中で計算し、エンドポイントのTLSデータの署名検証を欺きます。その結果、中間攻撃者によるTLS通信のなりすましや改ざんなどTLSの認証を破ることに成功しました。

SLOTHの論文では、TLSだけでなくIKE, SSHに対してハッシュ衝突を利用した様々な種類の攻撃を検証しています。TLSに対する攻撃として、

  1. RSA-MD5が有効になっているエンドポイント間のTLSクライアント認証への攻撃
  2. RSA-MD5が有効になっているTLSサーバ認証への攻撃
  3. TLS1.3のサーバ認証やクライアント認証への攻撃
  4. TLS1.0/1.0のハンドシェイクデータを改ざんして弱い暗号方式にDowngardeする攻撃
  5. TLSのハンドシェイクハッシュ値を偽造してアプリが利用するチャネルバインディングに対する攻撃

の5つが挙げられています。

論文を読むとまぁいつものごとく、見事な攻撃なのでホント感心させられます。ここで自分の理解を残しておくために、この中で一番計算量が少なく(2^39)、現実的に攻撃ができることを実証した1のTLSクライアント認証に対する攻撃について詳しく書いてみます。

2. Chosen-Prefix 衝突とは?

前述の通り、SLOTHはハッシュ衝突を計算して偽造データ作成するのがキモの一つです。しかも改変できるのは任意の部分でなく、主に頭の部分でデータ構造や書式の制限がついています。このような条件の元でハッシュ衝突を求めることを Chosen-Prefix 衝突と呼びます。

Chosen-Prefix 衝突でどのように偽造するか、MD5の攻撃で使われた本物と偽造証明書の比較を見てみるとわかりやすいでしょう。

同一の署名データをもつ偽造証明書は、正当なX509証明書としての構造を持っていないといけません。グレーの部分は一部本物のデータと一致していたり、偽造用に変更されたりする部分ですが、その書式や構造は仕様に合ったもので証明書の偽造目的に応じて固定化される部分です。公開鍵や拡張情報は比較的改変できる自由度が高いため、攻撃者はこの部分を調整して計算を行い、両者のハッシュ値が同一になるよう仕組みます。

頭の固定化されている部分を持っていることから Chosen-Prefix 衝突と呼びます。この Chosen-Prefix衝突の計算方式はこれまでいろいろ研究されていますが、まだかなりの計算リソースが必要です。SLOTHでは過去に開発された衝突探索ツールを改良して性能向上を図ったということです。

3. TLS1.2クライアント認証の復習

今回、SLOTHでTLS1.2のクライアント認証を破ることを解説しますので、TLSクライアント認証の復習です。細かいところは過去のエントリーを読んでください。
TLSのハンドシェイクではClientHelloからFinishedまでクライアント・サーバ間で様々なハンドシェイクデータのやり取りを行います。

TLSのハンドシェイクで利用する署名方式は、ClientHello/ServerHello で signature extension をやり取りして決めます。このTLS拡張によって、サーバ・クライアント間で共通に利用する署名方式が合意されます。現在多くのTLS実装では、明示的にRSA-MD5を有効になっていませんが、後方互換のため安全でないRSA-MD5署名方式で合意してしまうようなTLSサーバが数多く残っています。論文のサーベイでは、30%程度のTLSサーバがRSA-MD5署名を受け入れるようです。

ClientHello/ServerHelloでやり取りするTLS拡張は、signature以外にも様々なものがあります。各実装でサポートしている拡張機能はバラバラなので、サポートしていない拡張フィールドが送られてきてもエンドポイントは無視することになっています。皮肉なことに、この拡張フィールドの自由さに攻撃者がハッシュ値を調整できる余地が隠されていました。

TLSクライアント認証は、CertificateRequest/ClientCertificate/ClientCertificateVerify の3つで実現します。

CertificateRequestは、サーバからクライアントへ証明書を送付を要求するハンドシェイクタイプです。ここでは、後にクライアントが利用する署名方式やサーバが必要とするクライアント証明書の認証局情報(dn)などを指定してクライアント側に送ります。
このdn情報は、複数記載することが可能であり、該当するものがなければクライアントはデータ内容を無視します。ということは、悪意のあるエンドポイントがこの部分を自由に使い、データを埋め込むことができます。ここにもハッシュ衝突攻撃につけ入れられるTLS仕様の隙間が隠されていました。

最後のClientCertificateVerifyは、TLSクライアント認証の重要な部分です。クライアント証明書は秘匿されているものではなく、外部に公開されても構わないものです。そのため第3者がクライアント証明書を流用して成りすますことを防止しなければなりません。

クライアントは、ClientCertificateで証明書をサーバに送付した後に、ClientCertificateVerifyでそれまでサーバとやり取りした全ハンドシェイクデータのハッシュ値秘密鍵で署名してサーバに送付します。サーバはClientCertificateVerifyを受け取ったら、クライアント証明書の公開鍵を利用して相手側のハッシュ値を求め、自身が持つ全ハンドシェイクデータのハッシュ値と比較し署名検証を行います。このステップを踏むことで、サーバはクライアントが正当な証明書と秘密鍵のペアを持つエンドポイントであることを確認します。

4. SLOTHによるRSA-MD5を使ったTLSクライアント認証への攻撃

SLOTHは、中間攻撃者がTLSハンドシェイクデータを細工して正当なクライアントになりすまし、TLSのハンドシェイクに成功する攻撃です。驚くことにこの攻撃によって、攻撃者はクライアントの秘密鍵の情報を盗む必要はありません。攻撃者は、秘密鍵情報にアクセスすることなく署名検証を成功させ、なりすましを行うことが可能になります。この攻撃は、サーバ・クライアント間で交換する一連のTLSハンドシェイクデータ(Transcript)のハッシュ衝突を突く攻撃であるため、Transcript Collision Attackとも呼ばれています。

SLOTHによるTLSクライアント認証の攻撃が成功するには、以下の条件が必要です。

  • クライアント・サーバの両者でRSA-MD5署名方式を利用できる。
  • サーバがTLSクライアント認証を行い、対象サーバ以外(攻撃者のサーバ)にも同一のクライアント証明書を送付している。
  • クライアントがDHE鍵交換をサポートしており、クライアントが素数でないDHパラメータも受け入れてしまう。
  • サーバ側から送られてくるTLSハンドシェイクの長さが予測できる(データの中身ではない)。

ではSLOTH攻撃をステップ毎に見ていきます。

4.1 ステップ1: クライアント・攻撃者間でTLSハンドシェイク(途中まで)

まず攻撃者は、なんらかの方法でクライアントを誘導して中間攻撃者向けにTLSハンドシェイクを開始させます。この際ターゲットサーバへのDNSの改ざんしてサーバIPを偽造する必要はなく、攻撃者自身のドメインと証明書によるTLS接続で構いません。

攻撃者は、クライアントと攻撃者間の鍵交換はDHEで行います。サーバからクライアントに送るDHパラメータp, gは、p=g^2-g を指定します(gは普通2を使う)。pは本来素数であるべきですが、p=g^2-g=g*(g-1)なので素数ではありません。クライアントが厳密にチェックしなければ使えてしまいます*1。この仕込みがどう生きてくるか後のステップ7で説明します。

攻撃者は、CertificateRequestの前でハンドシェイクを一旦ストップさせます。

4.2 ステップ2: ハッシュ衝突の計算

次に攻撃者は、これまでクライアントとやり取りしたハンドシェイクデータと、次に送信する CertificateRequest のデータ、そしてそこに追加されるCertificateRequestのパラメータの一部(dn情報長さ)を予測してデータAを決めます。この長さ情報の予測が後のステップで生きてきます。

攻撃を成功させるために、このデータAと、中間攻撃者がサーバ側とのTLSハンドシェイクするデータのハッシュ値が一致するよう、データBを計算して探し当てます。

実際どのような Chosen-Prefix のデータ列になるのか、TLSハンドシェイクデータを並べて見てみるとわかりやすいです。

上図のグレー領域が固定化された(Chosen-Prefix)部分、赤い部分がハッシュ衝突を調整するために計算されたデータの部分です。dn lenの部分は今後サーバから送られてくるハンドシェイクデータ長を予測して決めます。TLSデータは書式が固定化されている部分が多いので、データ長だけを予測するのは難しくないと思います。
攻撃者・サーバ側のデータは、ClientHelloの拡張領域に衝突を発生させるデータを埋め込みます。前述したようにTLSサーバは知らない拡張は無視するため、攻撃者にとって自由なデータを埋め込むことができ、ハッシュ衝突を調整するには都合が良くなっています。

サーバ側に送り込むClientHelloには、明示的にRSA-MD5署名方式のみでサーバ側とハンドシェイクを行うようにsignature extensionを埋め込みます。前述した通り、後方互換重視のサーバはこの署名方式でハンドシェイクを受け入れてしまいます。

最後にここまでの両者のデータ長をハッシュ方式のバイト長境界(MD5なら16バイト)に合わせます。これによって一度2つのデータのハッシュ値を一致させると、次に同じデータをそれぞれに結合してもハッシュ値が同一に保たれます。

このChosen-Prefixの衝突計算は、効率化による省メモリ化や並列処理の改善によって現在48コアで1時間程度で計算が可能なようです。その1時間の間、攻撃者はクライアントにアラートを送り続けてハンドシェイクセッションを維持しておきます。

4.3 ステップ3: 攻撃者・サーバ間でTLSハンドシェイクの開始

MD5(A)=MD5(B)となるChosen-Prefix衝突の計算が完了したら、攻撃者はサーバ向けにTLSハンドシェイクを開始します。

攻撃者・サーバ間でRSA-MD5署名方式で合意できたら、ここまで中間攻撃者を介した2つのTLSハンドシェイクデータのハッシュ値は一致することになります。

4.4 ステップ4: 攻撃者のハンドシェイクデータの偽造

攻撃者からのClientHelloと偽造用の拡張データを受け取ったサーバは、それに応える一連のTLSハンドシェイクの応答(ServerHello,Certificate,CertificateRequest)を返します。

攻撃者は、このデータ結合したもの(ServerHello|Certificate|CertificateRequest)をクライアント側に偽のdn情報と見せかけ、CertificateRequestのパラメータの最後に付加してクライアントに送付します(|はデータ結合を表します)。

両者のハッシュ MD5(A|C) は MD5(B|C) と同一のままです。

Chosen-Prefix中のdn len には、あらかじめ青色のCのデータ長まで含んだ長さを予測して含んでいたため、単純に最後に結合することが可能となりました。

4.5 ステップ5: クライアント・攻撃者・サーバ間でハンドシェイクの同期

この後攻撃者は、ハンドシェイクデータを素通しします。


素通ししたハンドシェイクデータDは、どちらの接続にも追加されるため、両者のハッシュ値 MD(A|C|D) と MD(B|C|D) は同一のままです。

4.6 ステップ6: 中間者攻撃下の署名検証の成功

いよいよSLOTHの攻撃のクライマックスです。クライアントは自身の正当性を証明するためにそれまでやり取りしたTLSのハンドシェイクデータ A|C|D のハッシュ MD(A|C|D) を自身の秘密鍵で署名し、ClientCertVerifyとして送付します。

攻撃者は、これまでと同様にクライアントからのClientCertVerifyをサーバへ素通しで転送します。

ClientCertVerifyを受け取ったサーバは、クライアント証明書の公開鍵を使ってクライアントのハンドシェイクデータのハッシュ値MD(A|C|D)を取得します。 続いて、これまで攻撃者とやり取りしたハンドシェイクデータ B|C|D のハッシュ値MD5(B|C|D)の値と比べます。

これまでのステップによるデータの偽造により両者のハッシュ値は一致するため、見事に署名検証成功です。

4.7 ステップ7: MAC偽造によるハンドシェイク完了、なりすまし成功

署名検証の成功でクライアントの正当性が確認されると、クライアント・サーバ間でChangeCipherSpecをトリガーにして暗号化通信が開始されます。
このままでは、中間攻撃者は自身を介した2つのハンドシェイクの完了を成功させることはできたけれども、共通鍵がわからないので暗号化されたデータの中身を見ることができなくなります。ここでステップ1で仕込んだDHEの不正な素数パラメータがようやく生きてきます。

ステップ1で攻撃者は、DHEのパラメータをp=g^2-gで送付しました。その場合、クライアントが秘密鍵 x を使って公開鍵を作成しても g^x mod p = (g^(x-2)+...+1)*(g^2 -g) + g) mod p = g なので、クライアントの公開鍵は秘密鍵xによらず g のままです。ClientKeyExhangeでそのクライアントの公開鍵はサーバと交換されており、サーバから送られた公開鍵が g^y とすると、共有鍵は g^xy mod p = (g^x mod p)^y = g^y となり、サーバ側の公開鍵だけで共通鍵を計算できてしまうことになります。従って中間攻撃者に共有鍵がバレバレな状態に持っていくことができます。
この仕込みのおかげで攻撃者も同一の共有鍵の計算を行い、暗号化データの操作や交換するFinishedのHMACデータと一致させることも可能となります。

これで、攻撃者とサーバ間のTLSクライアント認証を伴ったTLSのハンドシェイクの完了です。

無事、攻撃者はTLSクライアントのなりすましに成功しました。
実際書いてみると、いろいろ大変な手続きが必要な攻撃ですが、秘密鍵の漏洩がなくてもなりすましが成功するとは、いやぁー見事です。

5. 危なかったTLS1.3

論文ではTLSサーバのなりすます攻撃も記述しています。TLS1.2のサーバ認証では、ServerKeyExchangeの署名検証を偽造させて成功させないといけませんが、中間攻撃者が自由にいじれるのはClientHelloの乱数値(32バイト分)だけです。この様な制限下ではハッシュ衝突攻撃を行うのはまだ大変困難です。まずは一安心。

TLS1.3では、サーバ認証をクライアント認証と同様のServerCerticateVerifyをTranscriptハッシュの署名を利用するよう仕様変更が行われました。これはクライアント認証同様の手法でサーバのなりすましができるようになり大問題になります。TLS1.3では、INRIAグループからの指摘を受け、仕様からMD5SHA-1アルゴリズムを全面的に削除し、該当パラメータをReservedに変更しました。サーバ認証が破られるとその影響は多大なものになるので、TLS1.3は本当に危ないところでした。

6. TLS1.0やTLS1.1はどうなる?

SLOTHの公開を受けてIETFTLS WGでもMD5をどうするか、TLS1.0/1.1をどうするか、の議論が始まりました。
SHA-1衝突を見つける計算量はまだまだ膨大で、MD5SHA-1と組み合わせたPRFに対する具体的な衝突がまだ見つかっていません。また、ChromeIEの統計からTLS1.0/1.1の利用率は現在でも3〜5%存在するため、現時点ではTLS1.0/1.1を直ちに deprecate するという話にはならないようです。

でも確実にSHA-1に対する攻撃の脅威は大きくなってきています。RC4の危殆化とBEAST攻撃でTLS1.0は崖っぷちです。いつまたPOODLEのようなプロトコル上の脆弱性が見つかり、プロトコル自体の廃止が早急に進められる可能性も否定できません。来るべき将来のX-Dayのために、日頃からTLSサーバの構成を注意して備えておきましょう。

*1:多くの実装では使えることが多いそうです。偶数・奇数のチェックを行っているのもあるようですが、その場合は p=g^2-1を利用することができます。ただ g^x mod p が 1 か g^2 のいずれかになるため、確率1/2で当てることになりそうです。

パンドラの箱?TLS鍵交換の落とし穴、KCI攻撃とは何か

1. 初参加のセキュリティキャンプ

先週ですが、講師としてセキュリティキャンプに初めて参加しました。
担当したのは高レイヤーのセッションで、TLSとHTTP/2の講義を合計6時間、まぁ大変でした。講義の時間配分、分量などの検討が十分でなかったため、本番では事前に準備していた講義内容の一部しかできず、ホント反省しきりです。せめての救いは、今回作った講義資料にたくさんのfavを頂いたことです。ありがとうございました。

講義では、学生の方々が短い時間ながら難しい演習に真面目に取り組んでくれました。質疑なども皆受け答えがしっかりしていて、技術的にもレベルが高い回答も多く、非常に驚きました。これだけ優秀な10代、20代の若者が、全国各地から毎年50人も集まるのを実際に見ると、彼らの将来が楽しみです。これまで10年以上継続してこのような活動を続けきた成果でしょう。私自身、とても良い経験をさせていただきました。セッションオーナーの西村さん、はせがわさん、講義を手伝っていただいたチューター、運営スタッフの方々、本当にありがとうございました。

2. TLSに対するKCI(Key Compromise Impersonation)攻撃とは何か?

講義準備に追われていた8月10日、ワシントンでセキュリティに関するワークショップ USENIX WOOT'15 が開かれていました。
その会議では、一つ面白い発表と論文「Prying Open Pandora's Box: KCI Attacks against TLS (開いたパンドラの箱を覗く: TLSに対するKCI攻撃)」が公開されました。 パンドラの箱を覗くとは、何やら意味深なタイトルです。

https://www.usenix.org/conference/woot15/workshop-program/presentation/hlauschek

KCI攻撃は、 Key Compromise Impersonation攻撃の略です。日本語で「危殆化鍵による成りすまし攻撃」と訳すのでしょうか。このKCI攻撃の論文、読んでみるとなかなか非常に面白い。一つ穴を踏ませることに成功すれば、見事にTLSの中間者攻撃が成功します。幸いこの攻撃で使われる機能をサポートしているTLSクライアントが現状少ないため、今のところ世間であまり大騒ぎになっていません(古いMacOSSafariが影響受けます)。これまで気づかれていなかったTLSの鍵交換仕様のすき間を突いた攻撃であることから、「パンドラの箱を覗く」というタイトルがついたのではないかと個人的に思っています。

この攻撃を理解すると、TLSクライアント認証とはどういうものか、TLS鍵交換とはどういうものなのか、その仕組みが明確になります。そこで今回「SSL/TLSの基礎」講義の補習エントリーとして、このKCI攻撃がどういうものなのか、解説を書いてみたいと思います。

2.1 TLSクライアント認証のしくみ

セキュリティキャンプでの講義「SSL/TLSの基礎と最新動向」では、説明が複雑になるのでTLSクライアント認証は説明の対象外にしていました。

一般的に広く使われているhttpsサーバのTLS通信は、サーバ証明書を使ってTLSサーバの正当性を検証する一方向のものです。この場合、どのクライアントから接続を許すかどうかTLSレイヤーでは認証・認可を行っていません。一方TLSのクライアント認証は、サーバ・クライアント双方向でTLS接続の認証・認可を行う方法です。

TLSクライアント認証は、非常に強力なセキュリティ通信ですが、証明書の発行・更新手続きが煩雑であったり、クライアント環境(OS・ブラウザ)で証明書管理の仕組みが統一されていないなど導入や運用のハードルが高く、現状それほど広く利用されていません。また、TLSの接続途中でクライアント認証に移行するような場合、TLSハンドシェイクのrenegotiationが必要となり、複数のTLS接続を集約するSPDYやHTTP/2の通信とはあまり相性が良くありません。
ただ、SSL3.0からある古い機能であるため、基本的な機能はどのブラウザー環境でも使えるようになっています。

2.2 クライアント認証時のTLSハンドシェイク

通常のサーバ認証だけのTLS通信と、クライアント認証をする通信とではどう違うのでしょうか?
TLSハンドシェイクのシーケンスを見てみると違いがわかります。クライアント認証を行う前提として、TLS通信を行う前にクライアント側に認証局から発行されたクライアント証明書と秘密鍵の鍵ペアがクライアントにインストールされていることが必要です。
クライアント認証時に必要なTLSハンドシェイクメッセージを赤字で示します。

サーバ側は、クライアント証明書が必要であることをCertificateRequestでクライアントに伝えます。クライアントはその要求を受け、自身が持つ証明書をサーバに送信します。さらに送信した証明書が自身が保持する正当なものであることをサーバに示すため、それまでのハンドシェイクデータを秘密鍵で署名し、CertificateVerifyでサーバに送信します。サーバはクライアントの証明書とCertificateVerifyの値を検証し、クライアントが正当な鍵ペアを持つ通信相手であることを確認します。

サーバは、CertificateRequestを使ってクライアントに証明書を要求する際、鍵交換手法に応じて必要な証明書のタイプを指定します。TLS1.2では、図中の表の通り7種類規定されています。今回KCI攻撃で利用されるのは、fixed が含まれる証明書タイプです。このタイプを選択すると、サーバ・クライアント双方の証明書に含まれている(固定的で変わらない)公開鍵を用いて鍵交換を行います。その仕組みの詳細について次節で説明します。

2.3 fixed_ecdhによる鍵交換

前述した通りサーバがfixedタイプのクライアント証明書を要求すると、証明書に記載されている固定の公開鍵を利用して鍵交換(DH/ECDH)を行います。他方、最後にE(Ephemeral:一時的)が付くDHE/ECDHE は、セッション毎に毎回異なる鍵を共有する方式です(PFS:Perfect Forward Securecy)。

論文では、www.facebook.comを使った攻撃が例示されています。そこでfacebookの証明書で公開鍵の情報を見てみましょう。

この赤枠で囲った部分が、証明書の公開鍵の情報を表している部分です。楕円曲線の種類と65バイトの公開鍵情報が書かれています。これを見ると*.facebook.comは、楕円暗号を用いたECDSA証明書です。その公開鍵はECDHの鍵交換で利用可能です。本来この証明書を鍵交換に使うためには、 X509 KeyUsage のフィールドにKeyAgreementフラグが立ってないと使えません。しかし、現実にそこまで厳密にチェックするクライアントは少ないようです。論文では www.facebook.com の証明書にKeyAgreementのフラグが入っているとの指摘がありましたが、今回確認したところこの証明書にはそのフラグはたっていませんでした。

fixed_ecdhによる鍵交換では、下図の通りTLSハンドシェイクの途中で相互の証明書を交換し、証明書に掲載されている公開鍵とそれぞれが持つ秘密鍵を組み合わせて、同一のPreMasterSecretを生成します。

その後、この相互で共有されたPreMasterSecretとClient/ServerHelloで交換した乱数と合わせて、実際のアプリデータを暗号化する共通鍵やMAC、初期ベクトルで利用する鍵を作ります。
このPreMasterSecretをいかに安全に共有できているかが、TLS通信のセキュリティを確保するキモの部分であると言えるでしょう。

3. KCI攻撃の仕組み

KCI(危殆化鍵による成りすまし)攻撃は、文字通り危殆化されたクライアント認証鍵を利用してサーバに成りすまし、中間者攻撃を成功させる方法です。そのステップを見ていきましょう。

攻撃によって成りすましの対象となるサーバは、必ずしもクライアント認証を使っている必要はありません。ただしサーバ証明書はDSA/ECDSA証明書で、クライアント側は fixed の証明書タイプを利用したTLSクライアント認証をサポートしていることが条件です。

そして、悪意のある中間者(Evil)が生成したクライアント証明書を何らかの方法でクライアントにインストールするトラップが必要です(後述)。これが成功した時点で、攻撃者がクライアント証明書の秘密鍵を持っていることになるので、「危殆化された鍵」と名付けられているわけです。

次にステップ毎に見事なKCI攻撃を見ていきます。ここでは論文に合わせて https://www.facebook.com/ サーバを題材にしてみます。4つのステップで攻撃を成功させます。

3.1 攻撃者がサーバ証明書を入手

これは簡単です。公開されているサーバの証明書はTLS接続すればすぐ入手できます。

サーバの秘密鍵は、厳重に守られているので簡単に入手できませんが、今回の攻撃はサーバの秘密鍵が入手できなくても、公開されているサーバ証明書だけで正当なサーバに成りすまして中間者攻撃が成功するものです。

3.2 クライアント証明書の入れ込み

次に攻撃者は、あらかじめ攻撃用のクライアント証明書と秘密鍵のペアを生成しておきます。この証明書は、後で攻撃者が要求するものなので、特に正式な認証局から発行された証明書である必要はありません。ただし後で鍵交換で使うサーバ証明書と同じ形式(DSA/ECDSA)であることが必要です。

この攻撃用のクライアント証明書の鍵ペアを如何にクライアントに意識せずにインストールさせることができるのか、ここがハードル高いステップです。論文では、iOSではメールで簡単にインストールできたり、Androidアプリなどからも比較的容易にインストールできるトラップが紹介されています。クライアント証明書のインストールはあまり普段使わない機能なので、まだまだ未知の穴があるかもしれません。

この仕込みが成功し、中間者攻撃の前までになんらかの方法でクライアント証明書と秘密鍵がクライアントにインストールされていることが攻撃の条件になります。

3.3 正当なサーバに成りすましてTLSハンドシェイク

さぁ、ここから本番です。
クライアントは 、攻撃者が作成した不正なクライアント証明書がインストールされているものとは知らず、https://www.facebook.com/にアクセスします。攻撃者はネットワーク経路途中で中間者攻撃を仕掛けます。

まず www.facebook.com に成りすましてTLSハンドシェイクを行います。その際サーバ側がServerHelloで選択する暗号方式は、証明書の固定鍵を使う DH または ECDH の鍵交換を行うものを選択します。後述する通り、DHE/ECDHEではサーバの秘密鍵が必要なため攻撃は成功しません。攻撃者は、事前に入手した www.facebook.com のサーバ証明書を送信し、fixed_ecdhタイプのクライアント証明書を要求します。通常 www.facebook.com はクライアント認証することはありませんが、この時点ではクライアントにはわかりません。

クライアントは、攻撃者からの要求に応じてクライアント証明書を送信し、その秘密鍵で鍵交換を行います。おそらくクライアントは、サーバの秘密鍵を持ってなければこの後のTLSハンドシェイクは失敗するはずだと思い込んでいることでしょう。

3.4 同一PreMasterSecretの生成

ところがどっこい、攻撃者はクライアントの秘密鍵を保持しています。
攻撃者は、クライアント証明書の公開鍵とサーバの秘密鍵を使って鍵交換を行うという真面目なことはせずに、既に保持しているサーバ証明書とクライアントの秘密鍵を使ってクライアントと同じ手順で全く同一のPreMasterSecretを生成します。

クライアントと攻撃者間で同一のPreMasterSecretが共有できていれば、後はいかようにもTLSハンドシェイクを偽造して完了することができます。結果、サーバの秘密鍵を使うことをせずに www.facebook.com の成りすまし、中間者攻撃に成功です。

いやぁー、見事できれいです。
前述の通り、危殆化されているクライアント証明書の鍵ペアをどういうトラップでクライアントに入れ込むかというのが課題ですが、TLSの鍵交換しているようでしていない、PreMasterSecretさえ共有できちゃえば勝ちというKCI攻撃の手法は、ホント目から鱗でした。

幸い fixed_ecdh/dhに対応したクライアントが現状ほとんどないので、この攻撃は現状それほど脅威として騒がれていません。論文では、Mac OS X 10.5.3 以前のSafariで攻撃に成功したと報告されていますが、現在は機能が無効化されています。しかし論文ではこの攻撃が知られていないと将来的に実装されて危なくなると警告しています。本当は攻撃を再現した状況を実際に試して載せたかったのですが、攻撃の影響を受けるクライアントが手元になく残念です。今後このような合わせ技攻撃の組み合わせによって思いっきりTLSの穴ができるような別の攻撃手法が出てくるかもしれません。

4. TLS1.3ではどうなる?

講義でも後半触れましたが、現在仕様策定中のTLS1.3ではどうなるでしょうか?

まず鍵交換は、一時鍵を使うDHE/ECDHEのPFSしかサポートされないのでKCI攻撃は無理です。PFSでは、ServerKeyExchangeで送信する公開鍵にサーバが署名するのでサーバの秘密鍵がないと署名検証でクライアントに攻撃がばれます。

またTLS1.3ではサーバ側も常に CertificateVerifyを送る Server Sign の要件が新たに入る予定です。これによって、ハンドシェイク途中でサーバ証明書の鍵ペア所有の検証が行われることになり、サーバの秘密鍵なしに中間者攻撃が成立するといった類似の攻撃がTLS1.2より困難になるでしょう。

個人的には、総当たりっぽく平文を判定するOracle攻撃なんかより、このような一つのほころびで全体のセキュリティを台無しにさせるこのKCI攻撃手法は、ある意味美しさを感じてしまいます。これで皆さんのTLSクライアント認証と鍵交換の仕組みの理解が少しでも深まれば幸いです。

5. 明日は YAPC

TLSとは全然関係ないですが、明日(8/22) YAPC Asia Tokyo 2015で「どうしてこうなった? Node.jsとio.jsの分裂と統合の行方。これからどう進化していくのか? 」のセッションの後半で古川さんと対談します。この半年余り、Node.jsプロジェクトの分裂と統合という流れの真っただ中にいた身として、今回の騒動でオープンソースのガバナンスの姿、その一つの形が見えてきた気がします。なかなか経験できない貴重な体験を皆さんにお伝えできたらと思っています。朝10時からと若干早い時間からのスタートですが、皆さんの参加をお待ちしています。

以上、セキュリティキャンプの補習講義でした。

HTTP/2時代のバックエンド通信検討メモ

1. はじめに、

今朝、こんな返事を元に kazuho さんとIPsec/TLS等バックエンド通信について議論する機会を得ました。

せっかくだから現時点での自分の考えを整理してメモとして残しておきます。
普段ちゃんとしたストーリをもったエントリーしか書いていないのですが、今回は時間がないのでちゃんとした論理的文章になっていないメモ程度のものです、あしからず。

以下、フロント側に HTTP/2 を導入した場合のバックエンド通信をどう考えるかのメモです。

2. 性能的観点

フロントにHTTP/2を導入したということは、ブラウザのHTTP HoLブロック解消が目的の一つだと思う。HTTP/2の多重化通信によってクライアントからこれまで以上の同時リクエストをさばかないといけない(だいたい初期値は同時100接続ぐらいに制限されていると思う)。

他方バックエンド側通信は、クライアント側がブラウザではないので同時接続数の制限は運用で自由に変えられる。なのでHTTP/1.1のままうまくチューニングして運用できる余地もある。でもスケーラビリティを考え、将来HoLが発生して性能ボトルネックになる可能性を想定し、HTTP/2にして多重化するのも選択肢として大いにあるだろう。

3. セキュリティ的観点

HTTP/2の実装仕様の検討が始まりつつあった2013年5月、スノーデン事件による漏洩情報から米国NSAによる pervasive surveillance の事実が明るみに出された。これをきっかけにインターネット通信のベースをTLS化する機運が一気に広まった。

また同年11月には、NSA(米国)/GCHQ(英国)によって通信会社の光ファイバーからデータをタップしてGoogleYahoo!のDC内通信を盗聴するMUSCULAR (DS-200B)プロジェクトの情報も明るみにでた。これでHTTP/2の導入を行うようなサービス大手は、MUSCULARの盗聴対象となっている可能性が高いことから、内部通信を暗号化する動きが加速化した。

現状でのユースケース(パターンか?)はこんなところだろう。

ユースケース1:
HTTP/2, SPDYを導入するような大規模サービスの運営組織 =
NSA/GCHQによるMUSCULARの盗聴対象 = 内部ネットワーク通信の暗号化対策

ということで、このユースケースに該当するような組織は、内部通信をTLS化してHTTP/2対応するのは自然の流れである。

当然のことだが、DC内のセキュリティは当然内部通信の暗号化だけでは守れない。
侵入対策、データ保護、認証・認可の管理等々も必要で、TLSによる暗号化対策は、双方向認証が必要になるような src の偽造、乗っ取り、新規追加ができるようなノードに侵入された攻撃からは無力であろう。

尚SPDYでは事実上できなかったTLSのクライアント認証は、HTTP/2ならHTTP/2通信が始まる直前に renegotiation してできるように仕様が定義された。ただ実際にできる実装があるかは個人的に知らない。

RFC7540 9.2.1 TLS 1.2 Features
An endpoint MAY use renegotiation to provide confidentiality protection for client credentials offered in the handshake, but any renegotiation MUST occur prior to sending the connection preface. A server SHOULD request a client certificate if it sees a renegotiation request immediately after establishing a connection.

4. 私的見地から Pros & Cons

まとめてみた。

当然日本国内で、3のユースケース1に当てはまるところはほんのわずかであろう。ならば100システムあれば100通りの判断なり、方針があるはず。もちろん幾つかのユースケースに集約されれば良いが、それを期待するにはまだ時期早尚だろう。

当面は、こういった Pros & Cons を作成して、コスト・メリット・AsIS・ToBe を考えながら各自が方針を決めることになるだろう。

OpenSSLの脆弱性(CVE-2015-1793)によるAltチェーン証明書偽造の仕組み

TL;DR やっぱり書いていたら長文になってしまいました。あまりちゃんと推敲する気力がないので、変な文章になっているかもしれません。ご了承いただける方のみお読みください。

1. はじめに

昨晩未明にOpenSSL-1.0.2d, 1.0.1pがリリースされました。事前に予告されていた通り深刻度高の脆弱性CVE-2015-1793が修正されています。Advisoryを見ると、この脆弱性がiojs/Nodeに影響があるということが判明したので直ちにiojs/Nodeのアップデートを行い、今朝未明に無事脆弱性対応版をリリースしました。

今回が初めてではありませんが、深夜に日欧米のエンジニアがgithub上で互いに連携しながら速やかにセキュリティ対策のリリース作業を行うことは何回やってもなかなかしびれる経験です。時差もありなかなか体力的には辛いものがありますが、世界の超一流のエンジニアと共同でリアルタイムにプロジェクトが進めることができる環境はエンジニア冥利に尽きます。

さて今回の脆弱性Alternative chains certificate forgery は、日本語に訳すと「代替えチェーン証明書の偽造」になるんでしょうか? この Alternative chains certificate の機能(以下 Alt Cert Chainと書きます)は、実はなかなか自分と因縁めいた関係があります。一番最初は、昨年末の「Node-v0.10.34がはまったクロスルート証明書とOpenSSLの落とし穴」の出来事からでした(もしまだ読んでいない方は是非)。

その時は OpenSSLに修正が入らず、Node側で1024bitの証明書を復活対応して issue を回避しました。その後 openssl の master(1.1.0系) に alt cert chain の機能が実装されました。その時に1.0.2へのバックポートするかどうか聞いたのですが、機能変更になるのでバックポートはしないという返事をもらったため、iojsでは独自にopensslにパッチをあてて運用してました。

その後やっぱり1024bitの証明書廃止の流れに負けてか、OpenSSLプロジェクトが1.0.2/1.0.1系へalt cert chainのバックポートが行われました。もう独自パッチをあてる必要がなくなるので喜んだのですが、よく見るとなんかいらないものまでバックポートされている。そこで問い合わせたところ間違ってパッチを入れ込んだということで 'Revert "Fix verify algorithm." 'の修正が入りました。このおかげでOpenSSLのコミットログにわざわざクレジットを入れてもらい、とても嬉しかったです。

まずは、今回の修正コミット "Fix alternate chains certificate forgery issue" を見てみましょう。

diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c
index 8ce41f9..33896fb 100644
--- a/crypto/x509/x509_vfy.c
+++ b/crypto/x509/x509_vfy.c
@@ -389,8 +389,8 @@ int X509_verify_cert(X509_STORE_CTX *ctx)
                         xtmp = sk_X509_pop(ctx->chain);
                         X509_free(xtmp);
                         num--;
-                        ctx->last_untrusted--;
                     }
+                    ctx->last_untrusted = sk_X509_num(ctx->chain);
                     retry = 1;
                     break;
                 }

いやはや、わずか2行です。得てしてこういうものかもしれません。これまでこのパッチ部分は結構読み込んでいたにも関わらず、今回のは全く見つけることができませんでした。まぁ無念です。バグを探し出したのは Google BoringSSL の agl さんらのチーム。やっぱ流石です。

そういう反省を踏まえつつ、今回の Alt cert chain の脆弱性がどういう理由で行ったのか、少し解説してみたいと思います。

2. 証明書検証のキホン

まずは、一般的にTLS接続で証明書がどう検証されているかの説明です(図1)。

クライアントにはあらかじめルート証明書等自分が信頼する証明書がOSなりアプリなりにインストールされています。クライアントがTLSサーバに接続すると初期のハンドシェイクでサーバから、サーバ証明書や中間証明書が送信されてきます。証明書には自分自身を表す Subject と発行者を表す Issuer が記載されており、クライアントは、接続するTLSサーバ証明書から Issuerをたどっていき、最終的にルート証明書までのチェーンが作られます。

そこで、それぞれの署名検証や証明書フィールドのチェックを経て、最終的に正当なサーバ証明書であることを判断します。
この証明書チェーンの正当性の検証は、TLS接続の安全性を確保する根本的な仕組みです。ここにほころびがあるともうダメです。そのため今回の脆弱性は、深刻度高にカテゴライズされました。

また、ここで出てくるルート証明書や中間証明書は誰でも発行できるものではありません。ポリシー的な制限もありますが、証明書の X509v3 Basic Constraints が CA:TRUE になっている必要があります。

今回の脆弱性は、CA:FALSEとなっている通常中間証明書として使えない証明書を、中間証明書と偽造し、正当な証明書チェーンとして検証をパスさせるものです。

例えば、自分のサーバ証明書をCAとして使って勝手にいろんなサーバ証明書を発行して利用したとしても、なんの問題なく使えてしまうということです。これはTLS通信の信頼性の根本を揺るがす問題です。

3. クロス証明書とは

今回問題となった Alt Cert Chain は、クロス証明書と一緒に使われる機能です。クロス証明書について簡単に触れます。

以前はルート証明書は1024bitsのが主流でしたが、計算機資源の発達によりもはや1024bitでは安全でなくなってきました。そこで本格的に危なくなる前に2048bitsのルート証明書へ更新が行われることとなりました。ただ古い端末では2048bitsの証明書を扱うことができず、クロス証明書をいれることによって互換性を持たせながら運用していくことが行われています(図2)。

Alt Cert Chainとは、このクロス証明書が使われている場合に古いルート証明書へのパスが作れなくなった時にもう一方の証明書チェーンを作って正当性の検証を行う機能を指します(図3)。

OpenSSLの場合、最初にサーバから送られた証明書リストを元にチェーンを作成するため、どうしても左側の長い方が最初に検証されることになります。

4. OpenSSLによる署名検証と Alt Cert Chain の作成のやり方

OpenSSLは、どうやって署名検証や Alt Cert Chainを作っているのでしょうか?

細かい部分を省くと図4の通り単純なスタック構造に入れ込み、サーバから送られて来た証明書を untrusted なものとして色分けしています。

図の場合では、サーバから送られたuntrusted な証明書は8つ、クライアントに保存されている最後のルート証明書が trusted で1つ、合計9つのスタックによる Cert Chain の出来上がりです。

Alt Cert Chainは、サーバから送られた一旦このスタックを検証してから作成します。ルート証明書までのパスが検証できないので上から順番に保存している trusted の証明書の中から該当するものがないか探しに行きます(図5)。

図の場合は、中間証明書Dのところでルート証明書Eが見つかりました。そこで中間証明書Dより上の部分を捨て去って Alt Cert Chainを作成します(図6)。

この場合、untrusted certの数を再計算するのですが、4つ取り除いたので 8-4=4 で4の untrusted とルートの合計5つの Alt Cert Chainスタックの完成です。これで署名検証が成功すれば正当性が無事保証されます。この検証を行う場合、untrusted で番号2以上のものは中間証明書であるため CA:TRUE であるかのチェックが行われます。

実は、このuntrustedの証明書を求める引き算にバグがあったのです。

5.CVE-2015-1793による証明書偽造のやり方

CVE-2015-1793で問題となった証明書チェーンを図7に示します。

これまでと違うのは、最初のチェーンで trustedな中間証明書Dが存在すること。そして中間証明書BのCA:FALSEになっているところです。このチェーンは、中間証明書Dの issuerのルート証明書がないので検証は失敗します。そこで Alt Cert Chainを探しに行きます。

中間証明書Bのところで Alt Cert Chain ができるので、再作成してみると図8の様になります。

証明書を2つスタックから捨てたので untrusted の数は、3-2=1 です。おっと、でもサーバから送られた untrusted証明書はAとBの2つです。AからCまでの証明書チェーンの検証は正当なため成功します。しかし untrusted な証明書の数が1であるため、証明書Bに対して CA:TRUE のチェックが行われません。なので本来中間証明書として使うことができない証明書Bを中間証明書と偽造することに成功しているわけです。

どうしてこういうことが起きたのか?

それは untrusted の計算方法にありました。

捨てた証明書2つですが証明書Dは trusted なものです。 trustedを捨てたのに untrusted の数を引き算するため数が合わなくなる、そういうバグに起因した脆弱性でした。最初の方に記載したパッチを見てもらえればわかりますが、untrusted の decrement をやめて、untrusted はルート証明書Cがのっかる前のスタック数(=2)を代入するよう変更しています。こうすれば最終的に untrusted の数のつじつまが合います。

6. 実際に CVE-2015-1793を試す。

実際にためしてみましょう。https://github.com/openssl/openssl/tree/master/test/certs脆弱性を試験する一連の証明書があります。それを流用してみます。

//
// Test for CVE-2015-1793 (Alternate Chains Certificate Forgery)
//
// RootCA(missing)
//   |
// interCA
//   |
// subinterCA       subinterCA (self-signed)
//   |                   |
// leaf(CA:false)----------------
//   |
//  bad(CA:false)

var tls = require('tls');
var fs = require('fs');
var bad = fs.readFileSync('./bad.pem');
var bad_key = fs.readFileSync('./bad.key');
var interCA = fs.readFileSync('./interCA.pem');
var subinterCA = fs.readFileSync('./subinterCA.pem');
var subinterCA_ss = fs.readFileSync('./subinterCA-ss.pem');
var leaf = fs.readFileSync('./leaf.pem');

var opts = {
  cert: bad,
  key: bad_key,
  ca: [leaf, subinterCA]
};
var server = tls.createServer(opts);
server.listen(8443, function() {
  var opts = {
    host: 'bad',
    port: 8443,
    ca: [interCA, subinterCA_ss]
  };
  var client = tls.connect(opts, function() {
    console.log('connected');
    client.end();
    server.close();
  });
});

脆弱性のある iojs-2.3.1では、

ohtsu@ubuntu:~/tmp/CVE-2015-1793$ ~/tmp/oldiojs/iojs-v2.3.1/iojs alt-cert-test.js
connected

途中に CA:FALSEの中間証明書が挟まっているのに正常に接続できてしまってます。

脆弱性対応版では、

ohtsu@ubuntu:~/tmp/CVE-2015-1793$ ~/github/io.js/iojs alt-cert-test.js
events.js:141
      throw er; // Unhandled 'error' event
            ^
Error: unsupported certificate purpose
    at Error (native)
    at TLSSocket.<anonymous> (_tls_wrap.js:989:38)
    at emitNone (events.js:67:13)
    at TLSSocket.emit (events.js:166:7)
    at TLSSocket._finishInit (_tls_wrap.js:566:8)

おー、ちゃんとチェックされています。

実際にこの脆弱性を突く証明書の構成が現状可能かどうかまでは調べていませんが、TLS接続の根本にかかわる証明書検証をバイパスする穴は本当に危険です。ホントちょっとしたバグですが、セキュリティに関わる部分は本当に致命的な欠陥につながるなと改めて思いました。

HTTP/2は流行らないけど広く使われるものだと思う。

まずは Disclaimer、
「あくまでも個人の感想であり、HTTP/2の効能を保証するものではありませんw」

1. はじめに、

先日、HTTP/2, HPACKのRFC(7540,7541)が無事発行されました。2年余りHTTP/2の標準化活動に参加してきたのですが、もうすっかり昔の事のような感じがします。

今日、Scutumの開発をされている金床さんの「HTTP/2のRFCを読んだ感想」のエントリーが公開され、読ませて頂きました。今回初めてHTTP/2の仕様書を読まれた感想ということで、長くかかわってきた立場から見ると非常に新鮮な内容でした。

実は「HTTP/2が流行らない」という指摘は、1年半ほど前に私も同じことを書いていました。
HTTP/2.0がもたらす Webサービスの進化(後半)

また、偶然なのかわかりませんが、同じ Proxy製品 vanish varnish *1 の開発者(およびFreeBSDの主要開発者)の PHK さんも同様な指摘をしています。「HTTP/2.0 ― The IETF is Phoning It In Bad protocol, bad politics」 もっとも、彼の記事では技術的な点だけでなく仕様策定のガバナンスへの批判も含まれていますが。

流行る、流行らないの議論で言うと、現状の利用用途に限れば中小規模のサービスや個人の用途でHTTP/2のサーバが広く使われるようにならないでしょう。ただ現状 Google/Twitter/Facebook/Yahoo.com 等が既に HTTP/2やSPDY をサポートしており、Chrome/Firefox/Safari/IEを通じて非常に多くのユーザが意識せず利用しています。それは現状のインターネットトラフィックのかなりの割合です。

なので私の予想は、
「HTTP/2は流行らないけど広く使われるもの」
です。

でも個人的には、将来HTTP/2固有の機能を使った中小サービスでも使いたくなるようなHTTP/2のユースケースが出てきて流行って欲しいなと願っています。

2. 各記述に対するコメント

予備知識なしで全く初めてHTTP/2の仕様書を読まれた金床さんの記事中の感想は、おそらく他の方が読まれても同様なことを感じるはずです。個々の感想に反論するつもりは全くありませんが、仕様策定に携わった立場でフォローをすれば、両者の記事を読んだ方々に対してHTTP/2に対する理解が深まることになればと期待してコメントを書いてみます。

できるだけ前後の文脈を壊さない程度で1・2センテンスを引用していますが、不適切な部分があればご連絡下さい。

HTTP/2が出た

サーバ側を作っているのもGoogle、ブラウザ(Chrome)を作っているのもGoogleで、そんなに高速化したいならUDPでも使えばよいのでは...などと、とりあえず様子を見ていました。

他の方も指摘しておられますが、GoogleQUIC, a multiplexed stream transport over UDPを開発し、現在全サービスで試験利用しています。TCP+TLS相当の機能+αを備えたQUICは、 HTTP/2 とは比べ物にならないぐらい複雑です。導入するには、HTTP/2より遥かに高い技術ハードルを超えないといけないでしょう。

RFCを読んだ最初の印象

HTTP/2はHTTP/1.0やHTTP/1.1を入れ替えるものだろうと思っていたのですが、まったくそうではなく、従来のHTTP/1.1はそのままに、その外側にさらにかぶせて使うものだったのです。

HTTP/2の仕様策定を開始するにあたりchartersが定義されました。その中に「HTTP/1.1のセマンティクスを保持する」の一項目が含まれています。できる限り既存のHTTP/1.1との親和性を保つのがHTTP/2の大きな方向性です。

PHKは、SPDYがHTTP/2のベース仕様として採択された時 「Why HTTP/2.0 does not seem interesting」 という記事で「現状のHTTP/1.1には改善すべき点がいくつかあり、HTTP/2はスクラッチから始めるべきだ」と主張しました。結局彼の主張は受け入れられず、今のインターネットサービス大手が直面している課題を解決するべく現実路線を進む判断がされました。

そして、中間会議や相互接続試験を頻繁に行い、異例なスピードで仕様策定が進められました。その結果、仕様候補決定からわずか2年あまりで仕様化完了し、それと共に今あるようブラウザのHTTP/2実装の大規模デプロイまでたどり着いたわけです(当初は昨年4月完了予定でしたが)。

新しい標準を迅速に出し、短期間で大規模にデプロイする。この目標は仕様策定に中心的にかかわったメンバー全てに共有できていた意識です。素早い進化が求められる現在のインターネット技術にあった方針だと思います。

  • 並列性のサポート

しかし例えば「HTTP/1.1より3倍速くなった!」ということは難しいでしょうから、普通の開発者やユーザからしたら「ちょっと速くなったね」程度の話で、果たして新しいプロトコルを定義するほどのメリットがあるのか疑問に思います。

HTTP/2の並列性のサポートの大きな目的は、HTTP HoL(Head of Line) Blocking の回避です。現状のHTTP/1.1の利用では、一つのクライアントから同時に4〜6の接続に制限され、多くのリソースを同時にリクエストするようなサービスでは、レスポンスの遅延に伴うリクエストのブロックが全体の表示速度に大きな影響を与えます。

HTTP/2の導入を検討するような大規模サービスでは、この HTTP HoL Blocking が発生している状況か?、 HTTP HoL Blocking の解消によってページの表示速度が改善するか? といったことを見極めるのが大事です。

  • ヘッダの圧縮

ヘッダの圧縮も並列性のサポートと同様で、もちろん良いだろうとは思いますが、新規にプロトコルを定義するほどのメリットがあるとは思えません。大きなHTMLやJavaScript等を、従来の枠組みの中で可能であるgzip圧縮すればよいだけだろうという気がします。

引用ではHTMLやJavaScriptgzip圧縮を代替え案として挙げていますが、HTTPヘッダのデータをgzip圧縮をすることも指していると想定してコメントします。

HTTP/2の前身のSPDYでは、HTTPヘッダのgzip圧縮が行われていました。しかしCRIMEという攻撃手法が公開され、TLS で暗号化された通信上でも圧縮データのサイズを判別することでヘッダ情報が漏えいするといった脆弱性が発見されました。そのため現在のSPDYではクライアントからのリクエストヘッダの圧縮は無効化されています。

しかしHTTPは、クッキーやユーザーエージェント等の冗長で繰り返し送受信されるヘッダ情報が多く、ヘッダデータの圧縮による通信量削減は効果が大きいです。

HTTP/2では、HPACK というHTTPヘッダに特化した圧縮技術が開発されました。全く新しい仕組みであるため途中仕様が右往左往しましたが、最終的に平均で3割程度の圧縮率が達成できています。

ただ肝心のCRIMEの脆弱性対策ですが、gzip よりも困難になりましたが、完全に対策できているものにはなりませんでした。結局フレームにパディングを入れてサイズをごまかしたり、機密度の高いヘッダ情報は圧縮用にインデックスしないなど HTTP/2 時代でも CRIME攻撃を意識してHPACKを使わないといけない状況です。この辺、まだまだ技術的な改良の余地が今後あるでしょう。

  • ステートフルすぎる

HTTP/2では複数のストリームやプライオリティ、依存ツリーなど、とにかく大量の状態を管理する必要があります。これは「リクエストが来たらレスポンスを返す」だけだったHTTP/1.1と比べると非常に大きな違いで、実装は今までのHTTP/1.1と比べ、とてつもなく複雑化すると思われます。

これは同意です。特にプライオリティ実装は非常に大変です。幸いなのは機能的にオプション扱いでまだこれから実験段階の機能であるということです。ただ仕様書を注意深く読み込まないとこの辺の判断はできないと思いますね。

  • 他のレイヤーとクロスオーバーしすぎている

HTTPのRFCであるのにTLSに言及している箇所があったり、データの転送について、まるでTCPのようにフロー制御の機能があったりと、きれいに「そのレイヤー」に収まっていない印象を受けました。

ここはTLS・フロー制御の2つ項目が挙げられていますが、別々の理由があります。

  • TLSの(厳しい)要件の導入

ここは大きな議論がありました。HTTP/2に必須なALPNによるネゴシエーションだけでなく、暗号強度や種類など通常のTLS利用より厳しいTLS要件が記載されているからです。

これは当初、既存のTLS1.2までは後方互換重視でつぎはぎ的な技術対応がされており、TLS上での利用が大部分を占めるHTTP/2では、できるだけ安全な条件の上で通信を行うべきであるという方針からです。他方、下のレイヤーのTLSはどんなものであろうと受け入れるべきであるとの意見もあり、ラストコール直前で大きな議論に発展しました。

結果的に今の仕様のように200以上の暗号のブラックリストを加えることになってしまいました。本来 TLS1.3 の仕様化が完了していれば、単にHTTP/2はTLS1.3上のみサポートということになっていたでしょう。

  • フロー制御の導入

フロー制御は当初のSPDY/2ではなかった機能です。1本のTCP内で複数のストリームが帯域の取り合いし、競合してしまうのでSPDY/3から導入された機能です。そして全体の帯域を有効活用できるようコネクションレベルのフロー制御がSPDY/3.1から導入されました。詳しくは、 twitterが spdy/3.1 の試験を始めてます を読んで下さい。

  • 複雑すぎる

全体的に、それほど大きなメリットがなさそうなのにやたら複雑になってしまっている印象を受けます。

  • 単に追加するだけで何も減らせていない

HTTP/1.1から無駄な部分(個人的には思いつきませんが)を極力減らし、シンプルにした上で出てくるのであればまだしも、単に追加だけしてしまっているので、開発者の負担を増やす方向にのみ向かってしまっています。

複雑さと機能追加の議論は本当に揺れ動きました。

これでもラストコール直前に様々な機能が拡張機能に追い出され、だいぶスリム化が図られたものです。
ただ複雑さは高度な運用ノウハウが必要になるものと思われます。ここ数年でHTTP/2を本当に使いこなせるようになるところはそれほど多くないでしょう。

  • これは流行らないかも

HTTP/1.1にとても大きな不満がなければ、HTTP/2を積極的に導入しようとするモチベーションは湧きません。

これは完全に同意します。HTTP/1.1をdeprecateしてHTTP/2への移行するということはないので、現状のHTTP/1.1の利用で大きな不満がなければ、継続的に使い続けるのがよろしいかと思います。

  • WAF開発者としての視点から

具体的には、PINGやPUSH_PROMISEなどによってWAF側から積極的にクライアントに対して働きかけ、反応を見ることで実装のフィンガープリンティングがやりやすくなりそうです。

この視点は新鮮でした。例えばFirefoxは接続を切られないよう1分毎にPINGを送ってきます。他にも、仕様では Connection Error/Stream Error の2種類のエラーとその発生条件が定義されていますが、各実装によって微妙に違うような感じがします。もしかするとエラー時の挙動の違いでクライアントやサーバの特定ができる可能性があるかもしれません。

と、以上ずらずら書いていたら思わず長文になってしまいました。少しでも皆さんの HTTP/2 に対する理解が深まれば幸いです。

*1:typo修正しました。khtokage さん、ご指摘ありがとうございました。

io.jsのTechnical Committeeに推薦されました

1. はじめに

「こんな私がXXXに!?」の宣伝文句ではありませんが、こんな私がio.jsプロジェクトのTechnical Commitee(TC)に推薦されました。
Nominating Shigeki Ohtsu @shigeki to the TC

まずは見習いとして数週間オブザーバーとしてTC meetingに参加、その後TCメンバーの投票を経て晴れてTCメンバーです。favや応援メッセージをいただいた方、ありがとうございました。

TC meetingは毎週木曜の早朝朝5時、Googleハングアウトで行います。会議の様子はライブ配信され、youtubeで録画公開されてます。議事録も随時公開されています。https://github.com/iojs/io.js/tree/master/doc/tc-meetings
コミュニケーションは当然全部英語。大変です。昨日の早朝に初めて参加しましたが、やっぱり議論に全然ついていけません(涙)。まだまだ修行が足りません。

私は、2月頭から io.jsの Collaboratorとしてプロジェクトに参加してきました。もっぱら開発に集中するためガバナンスやポリシー等の議論などほとんど追っていなかったので、今回慌てて io.js プロジェクト運用などのドキュメントを見直すはめに…

そこで今回、自分自身の復習を兼ねて、現状の io.js プロジェクトがどう運営されているか、この2か月半を振り返りながら書いてみたいと思います。

このエントリーを通じて、これまであまり io.js についてなじみがなかった方も io.js がどういうプロジェクトか知っていただければ幸いです。ちなみに現在交渉中である Joyent Node との統合話は今回のエントリーでは触れないことにします。

2. io.jsの プロジェクト運営

本日時点での io.jsプロジェクトの概要を図にしてみました。github organizationに280アカウント登録されています。

次に、それぞれの役割について記述します。

2.1 Technical Committee

io.js プロジェクトのハイレベルの意思決定を行う機関です。現在9名で構成されています。そのうち6〜7人がJoyent Nodeの現/旧コアメンバー経験者です。他にもV8やJS関連でGoogleの Domenic Denicola さんもオブザーバーとしてTC meetingに参加されています。

現在 Rod と mscdex と私がオブザーバー身分で、最終的には12名になる予定です。TCの人数は、9〜12名程度を想定してますが、明確な定員や任期は決められていません。ですが、これ以上になると集まって合意を取るのが大変だなと話も出てました。またTCは特定企業の色をなくすため、同一組織(正確には同一雇用者)のメンバーの割合を1/3以下にするという規定を設けています。超えちゃった場合は誰かが辞めることになるので、これからは転職先には気を付けないといけませんw。

TCの役割は、以下の通りです。

技術的な方針決定
影響度の大きい変更や意見が分かれるような修正等はTCで議論され、最終的に方針決定されます。
プロジェクトの統制やプロセス管理
今回改めて読み返したところです。https://github.com/iojs/io.js/blob/master/GOVERNANCE.md プロジェクトメンバーの役割や手続き等を決めます。
プロジェクトへの貢献ポリシーの策定
このポリシーは、プロジェクトメンバーだけでなく一般の方々からの io.jsの開発に協力していただくための規定です。 https://github.com/iojs/io.js/blob/master/CONTRIBUTING.md Nodeの頃からPRを出していたので、これまでほとんど目を通していませんでした。 コミットログの書き方やPRの出し方だけでなく、DCO (Developer’s Certificate of Origin:原作者証明書)やCode of Conduct(行動規範)も記載されていたとは、知りませんでした。
Githubリポジトリの管理
これは organization やレポジトリの管理の事じゃないかと思います。実際TCメンバーになるとどういう権限が与えられるかまで知りません。当然事前にTC meetingでの合意が必要でしょうが。
Collaboratorの人選、管理
Collaborator(後述)を推薦して、承認します。Joyent Nodeでは少数精鋭のコアチームでしたが、io.js は、比較的緩いというか幅広く多くの開発者を Collaborator として協力してもらう方針です。自薦・他薦からTCのメンバーがCollaboratorを推薦し、TC meetingで承認します。状況を見てある程度バッチ的に行っています。資格喪失の条件は今のところ見当たらないですね。人数が多くなって活動してない人が増えたりするとその辺整備されるのかもしれません。
2.2 Collaborator

日常的に github で作業を行います。TCメンバーも含み現在30名が登録されており、日本からは古川さんと私が参加しています。

Collaboratorになると github レポジトリのアクセス権や issue/pull requestの管理権限がもらえます。TC meetingで承認されると最初にGoogleハングアウトでガイダンスを受けます。もっともガイダンスと言っても1時間ほどCONTRIBUTINGのやり方を聞くぐらいで、あれこれ細かく言われるわけではありません。
https://github.com/iojs/io.js/blob/master/COLLABORATOR_GUIDE.md
Collaboratorでも自分のPRをマージするには、必ず他のCollaboratorのレビューを受けることが必須です。またCollaborator間で意見の相違がありまとまらなかったら、TCに最終的な判断を仰ぎます。

通常のエンジニアとして技術的な良心?を持って対応をしていけばほとんど大丈夫です。ただ将来規模が大きくなりすぎて収集つかなくなったらどうなるのかなとちょっと心配になったりもします。そうなる前にまた別のプロセスができるんじゃないかと思いますが。

繰り返しますが、io.js のCollaboratorは比較的広く門戸が開かれています。英語でのやり取りは必須ですが、やる気のある方はどしどしgithubでio.jsの開発に参加してください。

2.3 Working Group

io.jsでは、特定の活動を行うためのワーキンググループ(WG)を設けています。現在9つのWGが設立され、活動を開始しています。
https://github.com/iojs/io.js/blob/master/WORKING_GROUPS.md
私はどこにも所属しておらず各WGの詳細を把握していないのですが、機会があれば参加してみたいと思います。
驚くのは、i18nのWGに34もの言語コミュニティが設立されていることです。各国語への io.js 公開情報の翻訳やtwitterアカウントでの活動など、各国のコミュニティの中心としての活動が期待されています。

3. 中から見たio.js

2月の頭から io.jsの Collaboratorとして主にTLS/crypt周り(OpenSSLのバージョンアップ等)の開発を行いました。ここではこれまでを少し振り返って気づいたことを書いてみます。

3.1 本当に速い開発速度

現在の最新リリースは、 iojs-v1.8.1 です。 レポジトリには24のリリースタグが付いていますが、いくつか重複やスキップなどあり io.js-1.0.0 リリース以降わずか約3か月で約20回ほどのリリースが行われた換算です。

ほぼ毎日コミットがマージされタイムゾーン関係なく24時間開発が回っているような印象を受けます。自分がプルリクエストを出すと即レビューされ、レビューコメントを書くそばから修正や返答が返ってきます。まるで世界の超一流エンジニアとgithub経由してペアプロしている感覚。まぁほんと普通のエンジニアからスーパーサイヤ人に変身しないとそのスピードに付いていけない気分ですw

近々では V8 Ver42の導入に伴う iojs-2.0 の開発・リリースが予定されています。io.js 初のメジャーバージョンアップです。

3.2 Semver重視、安定性大事、性能も大切

以前の Node は、安定版リリースに伴い大幅な変更が入り、ユーザのマイグレーションコストが高いと批判を受けていました。それ受け io.js は、互換性の客観的な指標として Semver を非常に重視して短サイクルのリリースを行っています。ソフトウェアが進化する以上変更は避けられませんが、変更指標を厳格に適応して、できるだけユーザがバージョンアップの判断を明確にできるようにするのが狙いです。

先週私が openssl-1.0.2aへバージョンアップを行いましたが、iojs-1.8.0をリリースした直後、opensslのABI非互換であることを前提としてネィティブアドオンの動作を非互換にする修正をリリース時に入れたことが判明しました。本来なら iojs-2.0 にメジャーバージョンを上げる修正となったので、週末の土曜日にどう対応するかかんかんがくがくの大議論が発生しました。結局私がopensslのABI互換性を確認し、なんとか事なき得ましたが io.js が Semver を重視する一つのエピソードでした。

安定性や性能についても重視されています。PRに合わせてCIでビルド・テストを行うようにし、毎回800を超えるテストスクリプトのジョブの完了を確認しています。サーバの過負荷やタイミングで失敗するテストもまだありますが、この数週間でCIとテスト環境は大きく改善されています。まだまだ十分ではありませんが、確実に安定性は向上しつつあると感じています。

3.3 幅広いアーキテクチャサポート

現在 io.jsがサポートする cpu は arm, arm64, ia32, mips, mipsel, x32, x64 の7種類、OSは win, mac, solaris, freebsd, openbsd, linux, androidの7種です。全ての cpu x OS の組み合わせ、49通りが全部動作するわけではありませんが、V8がサポートするアーキテクチャ上はできるだけサポートする方向になっています。

CIでは、このうち22種類のアーキテクチャでビルドとテストを行っています。
https://jenkins-iojs.nodesource.com/job/iojs+any-pr+multi/

特に arm 関連は、ビルドもテストも時間がかかって大変ですが、リリース時は armv7l用のビルドバイナリーを配布して、非力なマシンでユーザがわざわざビルドしなくても使えるよう配慮しています。armv8もCI環境用のサーバの寄付を受け、先日全てのテストが通るようになりました。これだけ幅広くアーキテクチャをサポートするのは開発者として本当に大変なことですが、できるだけいろんな環境で io.js を使ってもらいたいの一心で作業しています。こんなところで io.js が使われるようになったとの報告も待っていますのでお願いします。

以上いろいろ書きましたが、これまで以上に io.js が身近になるよう取り組みたいと思っています。できるだけ皆さんのフィードバックやご協力をお願いします。