ぼちぼち日記

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

RPCに特化したGoogleのセキュリティ通信ALTSとは何か

はじめに

昨年、Googleから Google Cloud Platform に関するWhitePaperがいくつか公開されました。その中でGoogleのサービス内部で使われている新しいALTSというプロトコルを説明した文書「Application Layer Transport Security」は、読んでみると非常に面白く、セキュアなサービス間通信には本当に何が必要なのか、といったことを改めて深く考えさせられるものでした。物理的なマシンからサービス運用まで、ALTSがカバーする範囲は幅広い領域に渡り、あの巨大なGoogleのサービスをよくここまでまとめ上げたものだとホント感心させられます。

以前から、Googleはデータセンタ内のサービス通信までも暗号化を進めていると言われていました。それは、2013年にエドワード・スノーデンが暴露した資料が、Googleのデータセンタ内部の通信データがNSAによって盗聴されていることをうかがわせるものだったからです(「NSA、グーグルと米ヤフーのクラウドに「思いのまま」アクセスか--米報道」)。

一方、Googleのサービス内通信では、RPC(当時はStubby、後のgRPC)が使われていることはよく知られていました。Googleがサービス内通信の暗号化を進めると聞いても、おそらくRPC over TLSを使うようになったんだろうなぁ、と漠然と想像するだけでした。しかし蓋を開けてみると、GoogleはRPCに特化したALTSプロトコルを独自に開発し、それを全面的に利用していたのでした。しかも幅広いレイヤーの認証を実現する独自PKIシステムと共に... ホント驚きです。

Googleが10年かけてフルスクラッチで作り上げてきたALTSとは、いったいどんなものなのか?詳細はまだ公開されていませんが、WhitePaperから読み取れるALTSの姿を書いてみたいと思います。

Disclaimer

本記事は、公開されている資料から筆者が読み取った内容を記載しています。ALTSの詳細な仕様や実装はまだ公開されていません。筆者の想定違いや読み間違いの部分もあるでしょう。筆者は、Google社とは一切関係なく、元社員や現社員からの情報も持ち合わせていません。従いまして、本記事の内容は、一切保証されませんので、お読みになる際はご注意下さい。

Google サービスのネットワーク構成

まずは、Googleサービスのネットワーク構成を見てみましょう。「Encryption in Transit in Google Cloud」のWhitePaperにその概要が記載されています。また、そこに書かれていないGoogleデータセンター外部(各ISPに張り出しているところ)について、QUICの論文「The QUIC Transport Protocol: Design and Internet-Scale Deployment」に少し記載されており、そちらも参考になります。

これらの情報を合わせて、今回関係ある部分だけを抜き出すと、以下のネットワーク構成のようになっていると思われます。 f:id:jovi0608:20180116020131p:plain エンドユーザのクライアントからの接続は、各ISP内に設置してあるREL(restriced edge location)を通ります。Googleドメイン宛の接続の多くは、まずRELでTCPハンドシェイクの終端をして、その後のTLSのハンドシェイクをGFE(Google Front End)で行うといった2段構成になっています。

UDP通信のQUICは、REL上のUDP Proxyを経由して、GFEで直接ハンドシェイクを行います。論文によると、2016年7月にUDP ProxyによるQUIC接続に構成を変えたところ、検索サービスのレイテンシが4%から7%以上も向上し、TCPを上回る性能改善だったと報告されています。WhitePaperには、GFEがTCPの終端も行うことも記載されているので、全てのクライアントがRELを通るわけではなく、クライアントから直接GFEに接続する構成も存在しているものと思われます。

TLSとQUICの終端を担うGFEでは、DDoS防御の後にバックエンドのサービスへ負荷分散やルーティングを行います。図では、GFEからGCE(Google Compute Engine)のVM(Virtual Machine)、もしくはGoogle Cloud Serviceの2つにルーティングされています。

GFEから Google Cloud Serviceへの通信は、今回の主題であるALTS(Application Layer Transport Security)が使われています。ALTSは、アプリケーション層(Layer 7)で行うRPCの暗号化、双方向認証、完全性の確保を実現するセキュリティ通信で、GFEとGoogle Cloud serviceの間、もしくはGoogle Cloud service間の通信でALTSが使われています。

ALTSは、暗号通信とメッセージ認証だけ行う平文通信の2種類をサポートしています。Googleのデータセンタ間をつなぐWANを経由する場合、ALTS通信は暗号化通信ですが、Googleが管理しているデータセンタ内では、暗号化されずメッセージ認証だけの通信が行われます。これらは全て自動で判断されます。

もう一方のGFEからGCE VM宛の通信では、Google Cloud's virtual network authenticationによるLayer3の認証が行われています。この仕組みの詳細は公開されていませんが、(SDNの?)Control Planeにおいて、送信元がSecurity Tokenを付与し、受信側のホストでそのTokenの検証を行って認証を実現していると記載されています。 f:id:jovi0608:20180116020142p:plain Security Tokenは、Token KeyとHost Secretの2つから生成されています。Token Keyは、senderの物理IP 、VMのネットワークID、仮想VMの送信IPを合わせたもの、Host Secretは受信ホストの物理IPとControl Plain間で共有するPhysical boundary secretを合わせたものから作成します。Physical boundary secretは、2時間毎にControl Plane間で再ネゴシエーションして更新されます。このSecurity Tokenによって送信ホストを認証し、IP Spoofingなどに対する防御を実現しています。

ALTSの特徴

ALTSは、2007年よりGoogle内部で開発が始まりました。当時TLSはそれほどセキュアではなく(TLS1.2は2008年仕様化)、TLSの機能も十分でなかったため、Googleのサービス要件に合わせるためにスクラッチからサービス間通信のプロトコルを開発する道を選んだようです。

TLSで不十分な点は、サーバ名といった名前とその命名スキームに強く機能が結びついてしまうことでした。それに対しALTSでは、複数の名前スキームが使えるようにして、マイクロサービスに適した柔軟性と簡潔性を実現しました。TLSで使うX.509証明書は、ASN.1エンコードでしたが、ALTSではハンドシェイクメッセージや証明書のシリアライズにProtocol Buffer形式を採用しました。Protocol Bufferは、Googleサービス内部で多く使われています。

ALTSの柔軟性は、ホスト名ではなくidentityをベースに認証を行うことにあります。このidentityは、従業員、物理マシン、プロダクションサービス、アプリ実行するWorkload等、ネットワーク上のあらゆるレイヤーのもの全てに割り当てを行い、通信の相互認証を可能としました。しかも、Googleは独自内部PKIシステムも構築しました。内部CAからこれらのidentityにALTS用の証明書をほぼ自動で配布し、初期化時に使えるようにセットアップし、頻繁に証明書の更新や失効操作も自動で行っています。いやはや、これすごいです。

他にもALTSの特徴として、以下の項目が挙げられます。

  • アプリ的には全く透過。開発者はALTSを意識しなくていい。
  • サーバ・クライアント双方でセッション再接続(resumption)が可能で、複数のサーバ・クライアント間でセッション引き継ぎができる。
  • アップデートで最新の暗号方式の導入やプロトコルのバージョンアップなどが容易にできる。

ALTSのトラストモデル

ALTSのすごいことの一つに、物理ホストからアプリ・運用開発者まで含めたトラストモデルを構築していることにあります。ここでは、そのトラストモデルの仕組みについて書きます。

ALTS証明書の種類と役割

ALTSのトラストモデルは、Signing Serviceと Master Certificate、Handshake Certificateの3つのレベルの証明書で構成されています。 Signing Serviceは、root CAを持つ認証局のようなもので、その詳細は不明です。ALTSの証明書は、Singing ServiceからMaster証明書、Handshake証明書へchainがつながり、Handshake証明書が、実際のALTSハンドシェイクで利用されるものです。 f:id:jovi0608:20180116020150p:plain 証明書はidentityベースで発行されます。ALTS証明書発行の特徴として、signer(署名者)とissuer(発行者)の役割が別れていることです。

Master証明書が、Handshake証明書を発行する際、まずSigning ServiceにCSRを送ります。Signing Serviceは、CSRにあるissuerとidentityの関係を署名します。Master証明書は、それにALTSハンドシェイクに必要なパラメータ(ECDH公開鍵、暗号方式、失効IDなど)を付与し、自身の証明書も含めてHandshake証明書を署名・発行します。ALTS証明書は、TLSで使われているX.509証明書と全く異なる内容になっています。

さらにALTSの証明書は、発行するidentityの対象によってHuman, Machine, Workloadの3種類に別れています。それぞれGoogle従業員向け、ホスト向け、アプリ向けの証明書を表します。

Human Certificate

Human証明書は、Google従業員がRPCを使う際に利用する証明書です。Googleの内部CAでユーザ名、パスワード、2FAで認証が通ると、申請者のidentityを持つ20時間有効なHuman Handshake証明書が発行されます。接続先のホストには、あらかじめATLS Policyが配布されており、ATLSハンドシェイク中に証明書のissuerやidentityに応じてアクセス許可が判断されます。 f:id:jovi0608:20180116020158p:plain もし、Google従業員の1人が、どこかのMaster証明書からNetwork Adminなど不正な権限を持つHuman Handshake証明書を発行したとしても、このATLS Policyでissuerやidentityをチェックしており、不正な権限によるALTS接続を拒否するようになっています。

Machine Certificate

Googleのデータセンターに設置されている全てのプロダクション機器は、Machine Master証明書を持っています。Machine Master証明書は、マシン管理デーモンなどのコアアプリを使うため、Machine Handshake証明書を生成します。Machine Handshake証明書のidentityは用途によって別けられており、一つのマシンは異なる複数のidentityを持つことができます。 f:id:jovi0608:20180116020212p:plain Machine Master証明書は、検証されたソフトウェアスタック上でのみ利用でき、一部では物理サーバに付随しているTitanと呼ばれるセキュリティチップからMachine Master証明書の署名検証を行う構成も存在しているとのことです。

Workload Certificate

Workload証明書は、Googleで使われているクラスタ管理システムBorgと連携してアプリケーション間のRPC通信で利用します。このWorkload証明書が、アプリケーションによるサービス間のセキュア通信を実現する証明書です。 f:id:jovi0608:20180116020218p:plain Borgの構成では、BorgmasterがBorgletを通じてアプリケーションを実行するWorkloadを作成します。最初にBorgmasterでは、ALTSdというデーモンによってMachine Master証明書からBorgmasterのHandshake証明書を生成します。これによって、BorgmasterはALTS通信が可能となります。

次にBorkmasterは、Sining Serviceから署名されたBase Workload Master証明書を生成します。これがアプ リケーションに必要なWorkload Handshake証明書を作成・管理します。Borglet経由してWorkload Hnadshake証明書と秘密鍵のペアがBorg Workloadマシンに送付されると、Workloadのアプリは実行時にALTS通信が使えるようになります。これら一連の流れが、Borgの仕組みと連動して実現されています。

証明書の失効と更新

ALTSでは、ハンドシェイク時に一時共有鍵を生成するForward Secrecyを利用していません(機能はサポートされていますが無効化されています)。その代わり、証明書に記載された固定の鍵情報を用いた鍵交換を行います。その際、証明書を頻繁に更新し、同一の鍵を長期に使わないようにしてリスク回避をしています。そのため、証明書の更新期間は非常に短く、以下のように種類によって異なっています。全て自動化されているからできる技です。 f:id:jovi0608:20180116020224p:plain ALTSでは、Human証明書だけ明示的な有効期限を持っています(20時間)。それ以外は、時刻同期の問題によって失効機能の障害が発生するのを避けるため有効期限を記載せず、CRL(証明書失効リスト)を使って直接失効管理しています。

失効情報は、Revocation IDによって管理されます。Master証明書は、事前にRevocation IDのレンジを確保し、Handshake証明書発行時にID割り当ててCRL Serviceに登録します。証明書の更新や事故時に明示的にCRLに登録して証明書を失効させます。このCRLのデータは、全てのサーバに配布されています。膨大なものと思われそうですが、数百メガのデータが圧縮によって数メガ程度になっているそうです。

ALTSハンドシェイク

ALTSのハンドシェイクは、双方向認証のセキュア通信に特化するためTLSのハンドシェイクを効率化し、非常に簡略化したものです。しかも、再接続にはクライアント、サーバそれぞれでチケットを用いたセッションの引き継ぎができる拡張機能も付加されています。

新規接続

ALTSハンドシェイクは、ClientInit/ServerInitとClientFinished/ServerFinishedの4種類だけです。ハンドシェイクの詳細な仕様やフォーマットは公開されていません。 f:id:jovi0608:20180116020230p:plain ClientInit/ServerInitには、各種ハンドシェイクパラメータと証明書の情報が含まれます。Forward Secrecyが使われていないので、鍵交換はClient/Server双方のHandshake証明書に記載されている公開鍵(ECDH:curve25519)を使って行います。

ALTSは双方向認証を実現するべく、サーバ・クライアントは、それぞれの証明書を受信すると、失効情報やポリシーのチェックを行います。

鍵交換で導出された共通鍵は、TLS1.3と同様にHKDF-Expand/Extract(RFC5869)を使って鍵スケジュールを行います。KDF(Key Derive Function)によって、ペイロードメッセージの暗号化するレコードプロトコルの暗号シークレット、完全性をチェックする認証シークレット、次回再接続の際に利用するResumptionシークレットの3種類を導出します。

最後のServerFinished/ClientFinishedは、ハンドシェイクが改ざんされてないことを確認するためのものです。これは認証シークレットとHMACを使って実現します。再接続用のチケットも送付します(後述)。クライアントはServerFinishedを受信したら直ちに暗号化通信を開始できるので、ALTSは1-RTTでアプリケーションデータを送付できることになります。

ALTSのフレームフォーマットは、4バイトの長さフィールド、4バイトのデータタイプのフィールドに続いてペイロードが続きます。ペイロードの最大長は現在1Mバイトで、将来もっと小さくする予定だそうです。TLSと同様にペイロードのreply攻撃に備えるため、サーバ・クライアント双方でシーケンス番号の管理も行われています。

ペイロードの暗号化の場合はAES-GCMとAES-VCM、完全性確保だけの通信ではAES-GMACとAES-VMACのいずれかが利用されます。AES-VCMはあまりなじみがありませんが、2007年頃に提案された暗号方式で、VHASHというユニバーサルハッシュを利用したAEADです。AES-VMACは、VHASHをメッセージ認証に使ったものです。

クライアントResumption

ALTSには、クラスタリング利用を前提とした再接続(Resumption)の仕組みが備わっています。TLSと同様のClient Resumptionです。 f:id:jovi0608:20180116020236p:plain 再接続用にサーバ#1からチケットをクライアントに送付します。チケットは、クライアントのidentityとResumption Secretを暗号化したものです。チケットの暗号化は、Resumption Keyを使って行われます。サーバはクラスタリングされており、同一Resumption IDを持つサーバ間で同一のResumption Keyを共有しています。

クライアントは、同一のResumption IDを持つ別のサーバ#2に再接続すると、あらかじめサーバからもらったチケットを付与してハンドシェイクを行います。サーバ#2はResumption Keyを共有しているので、チケットからResumptionシークレットを復号することが可能です。これを使ってフルハンドシェイクをせずにALTS通信を続けることができます。

サーバResumption

ALTSでは、クライアントResumptionの逆、サーバResumptionの機能も用意されています。 f:id:jovi0608:20180116020242p:plain クライアントもResumption IDでクラスタリングされており、複数クライアント間で同じResumption Keyを共有しています。今度は、最初のハンドシェイクでクライアント#1からチケットをサーバに送っておきます。同一Resumption IDのクライアント#2からサーバに新規接続があった場合、サーバはチケットをクライアント#2に送ります。クライアント#2は、Resumption Keyを共有しているのでチケットからResumption Secretを複合することができます。クライアント#2とは、新規接続だけどフルハンドシェイクなしにALTS通信を継続できることになりました。

サーバResumptionは、複数の負荷分散装置で切り替えやルーティングの変更があった場合でも、配下のサーバへの再接続が新規接続にならないといったメリットを持つことができます。。

ALTSの制限事項

ALTSには、その仕組み上いくつか制限事項があります。

KCI攻撃

ALTSは、KCI(Key Compromised Impersonation: 鍵の危殆化による成りすまし)攻撃を受ける可能性があります。これは前節で説明した通り、Resumption IDが同一の複数のホスト間でResumption Keyを共有しているため、もしどこか一つのホストの秘密鍵やResumption Keyが漏洩して危殆化すると、他のホストに成りすましが可能になってしまうということです。Resumptionの利点を得るためであり、リスクを受容するしかないです。

ハンドシェイクのプライバシー

TLSと同様にALTSのハンドシェイクは平文でやり取りされるため、どのidentityがどこに接続しにいっているのかネットワーク中間者は見ることができます。

Forward Secrecy

先述の通りALTSは、Forward Secrecyを無効にしています。証明書を頻繁に更新することでそのリスクを抑えていますが、更新期間の間はForward Secrecyを維持できません。

0-RTT

ALTSでもTLS1.3と同様に、0-RTTでアプリケーションデータの送付を実現することは可能です。しかし0-RTT で送られるデータはForward Secrecyではなく、reply攻撃に対する脆弱性も持っています。ALTSはRTTが小さい環境で使われるので、あえて0-RTTを使う必要はないと判断し、0-RTTの機能を除いています。

最後に

このようにALTSの設計や機能は、TLSに慣れていた目から見ると非常に気づきが多いものでした。

今後、ALTSの詳細な仕様やオープンソース実装が公開され、標準化の動きが出てくることを期待したいです。もっともその時にはGoogleは既に次の新しいものに移行しているでしょう。

書評:プロフェッショナル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のバグの影響によるものがいくつか含まれているようです。