ぼちぼち日記

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

パンドラの箱?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 が身近になるよう取り組みたいと思っています。できるだけ皆さんのフィードバックやご協力をお願いします。

華麗なる因数分解:FREAK攻撃の仕組み

1. はじめに

ちょうど今朝 OpenSSLをはじめとした様々なTLS実装の脆弱性の詳細が公表されました。

この InriaとMSRのグループは以前からTLSのセキュリティに関して非常にアクティブに調査・検証をしているグループで、今回も驚きの内容でした。
このグループは、TLSのハンドシェイク時の状態遷移を厳密にチェックするツールを開発し、様々なTLS実装の脆弱性を発見・報告を行っていたようです。
特にFREAKと呼ばれるOpenSSLの脆弱性(CVE-2015-0204)に関しては、ちょうど修正直後の1月初めに
Only allow ephemeral RSA keys in export ciphersuites
で見ていましたが、具体的にどのように攻撃するのかさっぱりイメージできず、あのグループだからまた超絶変態な手法だろうが、まぁそれほど深刻じゃないだろうと見込んでいました。
今回、その詳細が論文で発表されました。いろいろ報告されていますが、自分で攻撃手法を見つけられなかった反省を踏まえ、FREAKについて少し解説してみたいと思います。
まずは脆弱性の背景から。

2. 昔々の米国暗号輸出規制とSSL

その昔、国家安全保障上の理由で昔から暗号技術や暗号を使った製品などの利用や持ち出しは各国で規制されてきました。 Phil ZimmermannがPGPのコードを書籍化して合法的に米国外に持ち出したことは有名な話です。SSLも規制の対象で90年代後半は、日本からは低強度の暗号(40bitや56bit)しか使えない米国輸出版のブラウザーをダウンロードするよう制限されていたり、商用OSやアプリ等は、高強度対応の暗号ライブラリが削除されたものしか使えない状況でした。(たまにftp サイトで米国内専用のが置いてあったりしましたが)
その後2000年ぐらいに規制が緩和され、米国内外に関わらず最高暗号強度の通信が行えるようになりました。

米国外用に使える暗号にはEXPの記号が付いていて、今でも以下のように OpenSSL 実装されたままになっています。

$ openssl ciphers -v |grep EXP
EXP-EDH-RSA-DES-CBC-SHA SSLv3 Kx=DH(512)  Au=RSA  Enc=DES(40)   Mac=SHA1 export
EXP-EDH-DSS-DES-CBC-SHA SSLv3 Kx=DH(512)  Au=DSS  Enc=DES(40)   Mac=SHA1 export
EXP-DES-CBC-SHA         SSLv3 Kx=RSA(512) Au=RSA  Enc=DES(40)   Mac=SHA1 export
EXP-RC2-CBC-MD5         SSLv3 Kx=RSA(512) Au=RSA  Enc=RC2(40)   Mac=MD5  export
EXP-RC4-MD5             SSLv3 Kx=RSA(512) Au=RSA  Enc=RC4(40)   Mac=MD5  export

なかなか古い仕様の機能を廃止できない後方互換性重視が今回の脆弱性の要因の一つでした。

3. ephemeral RSA(一時的RSA)

時はまだ米国暗号輸出規制が厳しかった時代、DESやRC4等の共通鍵暗号方式はSSLのハンドシェイクで動的に決定するので使える強度の暗号をクライアントで利用制限していました。一方、RSA等の公開鍵暗号の制限(512bit)はやっかいなものでした。

そのままでは、サーバ側で米国内からのクライアント向けには1024bit RSA、米国外からは512bit RSAと2種類のサーバ証明書を用意して使い分けないといけなくなります。まぁ面倒です。そこで使われたのが ephemeral RSA(一時的RSA)という方式です。

EXPが付いた輸出向けの暗号方式を利用する場合には、サーバの証明書(通常は1024bit)のRSA公開鍵を使わず、一時的に生成した512bit長の公開鍵をサーバからクライアントに送信して利用するというものです(送るときにはサーバの公開鍵で署名)。
クライアント側は、サーバから送られた512bit長の一時的RSA公開鍵を使って pre_master_secret を送り、両者で同じ master_secret を共 有することになります。
米国内のクライアントからはEXPでない暗号方式を利用するため通常と同じくサーバ証明書RSA公開鍵(1024bit)を使って pre_master_secretのやり取りを行います。これでサーバ証明書1枚だけで米国の暗号輸出規制に適合です。

本来この512bit長の一時的RSAによる鍵交換は、EXPの暗号方式だけに適応されるものでした。しかしOpenSSLでは、EXPでない暗号方式でも利用ができるよう独自に拡張を行っていました。今回のFREAK攻撃は、OpenSSLのこの独自拡張を突いたものでした。

この ephemeral な鍵交換方式は、現在 PFS(Perfect Forward SecurecySecrecy) としてTLSサーバの利用が推奨されています。もっともRSAではなくDH(DHE)やECDH(ECDHE)を利用する方式ですが、今回のFREAK攻撃ではPFSとして低強度RSAを使うことが、脆弱性につながってしまったという なんとも皮肉なものです。PFSの鍵長に関しては、DHEで512bitや1024bitを利用している場合もリスクが高く、2048bit以上が推奨されています。自分が使っているTLSサーバの暗号の鍵長は時代遅れになっていないか常に留意しておきましょう。

4. FREAK攻撃とは

FREAK攻撃は、Factoring attack on RSA-EXPORT Keys の略、輸出向けのRSA因数分解する攻撃です。
以下の4つの条件が必要です。

  1. MiTM(中間者攻撃) ができること
  2. サーバ側でEXPの暗号方式で ephemeral RSA(512bit)が使えること
  3. サーバ側で ephemeral RSAの鍵ペアが使いまわされていること
  4. クライアント側でEXP以外の暗号方式でも ephemeral RSA(512bit)が使えるよう拡張されていること(1.0.1kより前のOpenSSL)

3番目の条件ですが、RFC2246,D. 実装上の注意,D.1. 一時的 RSA 鍵には

512 ビットの RSA 鍵はそれほど安全ではないので、一時的 RSA 鍵はときどき変更するべきである。 典型的な電子商 取引アプリケーションにおいては、その鍵は一日ごと、または 500 トランザクションごと、できればそれ以上の頻度で変更することを提案する。

となっていますが、現状多くのサーバでは立ち上がるとずっと同じ一時的 RSA 鍵を使い続けるようです。nginxでは、
https://github.com/nginx/nginx/blob/master/src/event/ngx_event_openssl.c#L740-L742
にあるよう最初のコールバックで生成したものをずっと使い続けています。

また、TLS1.0の時代では一日ごとの更新が推奨でしたが、今回のFREAKでは EC2 を使って7時間で512bitのRSA鍵を解いてしまったようです。時代は変わってしまいました。

それでは FREAK攻撃をステップ毎に見てきましょう。

ステップ1 事前に一時的RSA因数分解

使いまわされることを前提に事前に一時的RSAの公開鍵を入手しておきます。後は力技、512bitの数字を因数分解して2つの素数を見つけます。

ステップ2 サーバ側を輸出用暗号方式でハンドシェイクするよう改ざん

クライアントとサーバの間に入り込み MiTM攻撃を仕掛けます。
ClientHelloで要求する CipherSuite を輸出用のものに置き換え、サーバ側が一時的RSAで鍵交換するよう仕向けます。
サーバ側は使いまわしの一時的RSA公開鍵を送ります。クライアントは、独自拡張してしまっているので輸出用暗号じゃなくても一時的RSAを使えるようになってます。

ステップ3 pre_master_secretの入手

クライアントから一時的RSAの公開鍵で暗号化した pre_master_secretが送られてきます。既に秘密鍵が事前に計算できているので中身が丸見えです。攻撃者は pre_master_secretからサーバ・クライアントと同じ master_secretを直ちに生成します。

ステップ4 Finishedのハッシュデータの改ざん

TLSハンドシェイクの完了は、Finishedのメッセージに含まれるハンドシェイクデータと master_secretを合わせたハッシュ値を見て、ハン ドシェイクが改ざんされてないことを確認します。攻撃者は master_secretを持っているので自由にハッシュを改ざんして、クライアント・サーバの両者をだまします。

いやー、ほんと見事で感心してしまいます。この他、いろいろTLSハンドシェイクの状態遷移の実装バグをついた脆弱性がいろいろ報告され ています。 Inria と MSRチームのTLSの信頼性を向上させる取り組みはホントすごいなと思います。今後このような不備が根本的に解決されるような新しいプロトコルの取り組みでも始まらないかなと願ったりもします。

ES6時代のGoogle的Good Parts: V8のstrong modeを試す

1. 新しいGoogleのV8実験プロジェクト

巷ではIEの asm.js サポートのアナウンスが話題を集めていますが、実は先月末のTC39の会合でGoogleが今年新しくV8に2つのJavaScript機能の試験実装を進めていることがプレゼンされていました(すっかり見落としてた)。

Experimental New Directions for JavaScript, Andreas Rossberg, V8/Google

このV8実験プロジェクトは、資料によると

SaneScript (strong mode)
より安全なセマンティクスと性能向上が図れるよう一部機能を削減したJSサブセット。
SoundScript*1
TypeScriptをベースとしたより堅固で効率的な型システムの導入。

の2つです。ちょうど今朝知ったのでV8のソースを見てみると、今まさに strong mode の実装が進めれられている最中でした。そこで、本日(2/19)時点で実装されている strong mode の機能がどんなのか知るため、実際に試してみました(現状4つ実装されてます)。

2. V8の strong mode を試す

今日の V8(4.2.0 candidate)をビルドして早速ためしてみます。

2.1. var の禁止
$ ./out/x64.release/d8 --strong_mode
V8 version 4.2.0 (candidate) [console: dumb]
d8> "use strong"; var a;
(d8):1: SyntaxError: Please don't use 'var' in strong mode, use 'let' or 'const' instead
"use strong"; var a;

var の代わりに let や constを使えとのこと。

2.2. オブジェクトプロパティの delete 禁止
d8> "use strong"; const o = {a:1}; delete o.a;
(d8):1: SyntaxError: Please don't use 'delete' in strong mode, use maps or sets instead
"use strong"; const o = {a:1}; delete o.a;

プロパティを削除しちゃうぐらいならMap/Setを使えとのこと。

2.3. 型変換する比較演算子の禁止
d8> "use strong"; 1==1;
(d8):1: SyntaxError: Please don't use '==' or '!=' in strong mode, use '===' or '!==' instead
"use strong"; 1==1;

暗黙の型変換する比較演算子の問題については昔からよく言われてますね。ただtruthy/falthyへの変換(ToBoolean)は許される見込みのようです。

2.4. ステートメントを省いた条件式の禁止
d8> "use strong"; while(true);
(d8):1: SyntaxError: Please don't use empty sub-statements in strong mode, make them explicit with '{}' instead
"use strong"; while(true);

あまり自分で使った覚えがないけど、{}を付けない条件式はダメとのこと。

3. 今後は?

その他に資料には、

スコープ
declaration前に変数を利用することを禁止。ただし相互に参照している再帰関数などでの利用は許可
Class
オブジェクトのfreezeやインスタンスのseal化等々
Array
要素の歯抜け禁止、accesssorメソッドの禁止、lengthを超えた要素のアクセス禁止等々
Function
argumentsオブジェクトの禁止、呼び出し引数の厳密化等々

などの機能変更が挙げられています。まさにES6時代のGoogle的Good Parts集だと思います。

strong-mode を利用するにはフラグ(--strong_mode もしくは --use_strong)が必要ですが、Chromeのリリースに合わせてV8がブランチカットされれば、少し後に io.js のCanary版(予定)で利用できるようになります。
まだ実験初期段階で今後どこまで変わってどう標準化にフィードバックされるのか全く未知数です。でも今後ES6のコードを書く場合には、ここはMap/Setが使えるか、var使わなくてもいいのかなど、少し頭の隅にいれておくといいかもしれません。

SoundScript の方はまだ資料だけですが、Q3/Q4でどう実装されるのがこれから楽しみです(時間がないのでこの話題はまたの機会に)。

*1:soundは、「〔構造が〕堅固な、安定した, 〔考えなどが〕理にかなった、正当な 」の意味じゃないかと思います。日本人には音楽的な意味に思えてちょっと紛らわしいです。