📝

『プロフェッショナルSSL/TLS』読書会メモ(第2章)

2021/05/17に公開

前:第1章 次:第3章

『プロフェッショナルSSL/TLS』読書会

@kdnaktさんが主催してくだっている、社内の読書会/勉強会。
この記事は、その読書会での内容を簡単に箇条書きにした自分用のメモです。

@kdnaktさんが書かれている同読書会のスクラップ記事のほうが詳しくかつ分かりやすいので、そちらも読むことをお薦めします。

全てを1つの記事にすると長くなりすぎるので、章ごとに分けます。

読む本について

第2章 プロトコル

  • TLSは上位層のプロトコルがなんであるかを問わず、接続志向(データを確実に相手に送信できる。UDPは接続志向ではない)のネットワーク通信を安全に行うための暗号化プロトコル。
  • Recordプロトコル:TLSの実質的な本体。コネクション上でやり取りされる低レベルのメッセージ転送をすべて担う。
  • TLSレコード=ヘッダ+メッセージデータ
  • ヘッダ:コンテントタイプ+バージョン+レコード長
  • メッセージの転送:別のレイヤから渡される、書式が決まっていないデータのバッファを転送する。レコードは最長2^{14}=16,384バイト。これより長いメッセージは分割される。
  • TLSでは最初の接続における一連のメッセージ群(e.g. ハンドシェイク)は暗号化されない。
  • 暗号スイートによっては暗号化は利用せず、完全性の検証のみを行うものもある。
  • TLSの圧縮機能は現在は利用されていない。HTTP等ですでに圧縮されていることが多く、また2012年のCRIME攻撃で大打撃を受けたため。
  • Recordプロトコルが担うのはデータ転送と暗号化処理。他の機能はすべてサブプロトコルで行う。
  • サブプロトコルは追加が容易であり、そのためTLSは拡張が可能。
  • サブプロトコルはネゴシエーションされたパラメータを使って自動的に保護される。
  • TLSメインには4つのサブプロトコルが規定されている。
    • Handshake
    • ChangeCipherSpec
    • ApplicationData
    • Alert
  • Handshakeプロトコル:データ送信に先立って接続を確立するために、TLS接続で使うパラメータのネゴシエーションと認証を行う。通常6~10メッセージが必要。このプロトコルのメッセージ先頭には1バイトの種類+3バイトのメッセージを示すヘッダがつく。
  • フルハンドシェイク
    1. 接続で利用したいパラメータをクライアントとサーバの双方が提示→合意
    2. 証明書等を提示して認証を行う
    3. セッション保護のためのマスターシークレットを共有する
    4. メッセージ群が書き換えられていないことを検証する。
  • 具体的には
    1. ClientHello(C->S)
    2. ServerHello(C<-S)
    3. (Certificate)(C<-S)[1]
    4. (ServerKeyExchange)(C<-S)[1:1]
    5. ServerHelloDone(C<-S)
    6. ClientKeyExchange(C->S)
    7. ChangeCipherSpec(C->S)[2]
    8. Finished(C->S)
    9. ChangeCipherSpec(C<-S)[2:1]
    10. Finished(C<-S)

フルハンドシェイクの内容を細かく見てみよう

  • JavaでSSL/TLSをデバッグしたいときはこのページが参考になる
    • 実行時引数に-Djavax.net.debug=allを付けるとハンドシェイクまで含めたログを吐いてくれる

ClientHello

  • 新規のハンドシェイクで常に最初に送信される
  • クライアントが希望するパラメータ群とその優先度をサーバに通知する
  • クライアントがこのメッセージを送信するのは下記の3パターン
    • 新規にコネクションを開始する
    • 再ネゴシエーションする
    • サーバ起因の再ネゴシエーション要求に応える
  • Version: クライアントがサポートする最良のバージョン
  • Random: 28バイトのランダム値+4バイトのクライアント時刻
    • ランダム値が容易に推定される致命的な脆弱性が1994年にNetScapeで発見されたため、貧弱な乱数生成に対する防御として現在時刻を付加するようになった。
    • ただし実際の時刻を挿入するのはブラウザのフィンガープリントになりうるので、ブラウザによっては時刻を少し変化させたり、完全にランダムにしたりする。
    • このランダム値は下記の役割を果たす。
      • 毎回のハンドシェイクを一意にする
      • リプレイ攻撃を防止する
      • 交換するデータの完全性を検証する
  • Session ID: 最初は新規セッションであることを示すために空。以降の接続では再開したいセッションID。このIDはランダムな32バイトで、それ自体に特に意味はない。
  • Cipher Suites: クライアントが対応可能な暗号スイートが列挙される。
  • Compression methods: クライアントが対応している圧縮方法を列挙する。デフォルトはnull(圧縮しない)
  • Extensions: 付加的なデータを運ぶ拡張

ServerHello

サーバが決定したパラメータを送り返す。構造はClientHelloと同じ。

Certificate

サーバからクライアントへとX.509証明書チェーンを運ぶ。
サーバ証明書が先頭にきて、あとは中間証明書が続く。最後にルート証明書が来るが、ルート証明書は省略可。

ServerKeyExchange

鍵交換に必要な付加的データを運ぶ。暗号スイートによっては省略される。

ServerHelloDone

サーバが「こっちが送りたいハンドシェイクは全部送ったよ」という合図。
以降、サーバはクライアントからのメッセージを待機する。

ClientKeyExchange

鍵交換に必要なデータをクライアントから運ぶためのメッセージ。必須。

ChangeCipherSpec

  • サーバとクライアントの両方が送りうる。
  • 中身は1が1バイトだけ。
  • 下記を伝える
    • 接続のためのパラメータ組み立てに充分な情報を手に入れた
    • 暗号鍵を生成した
    • これから暗号処理に移行する
  • ハンドシェイクメッセージではないため、完全性保証の対象外となっている。
    • このことが原因で2014年にOpenSSLが攻撃を受けやすい状態になっていた。

Finished

  • ハンドシェイクが完了した合図
  • このメッセージ自体は暗号化されている
  • verify_data
    • そのメッセージを含まない、それまでの受信したハンドシェイクメッセージの全てをハッシュ化し、それとマスターシークレットを組み合わせて算出
    • これにより能動的な攻撃者による捏造や改竄が不可能になる
  • TLS1.2ではデフォルトで12バイト
    • 暗号スイートによってはもっと長くなる

クライアント認証

  • CertificateRequest: サーバからクライアントに対する認証の要求と、許容できる証明書の公開鍵および署名アルゴリズムの伝達
  • CertificateVerify: 事前に送ったクライアント証明書に含まれる公開鍵に対応する秘密鍵を持っていることをクライアントが証明する

セッションリザンプション

  • フルハンドシェイクはメッセージの往復が多く、計算コストも高い。
  • 既にセッションが確立されていたら、省略版のハンドシェイクで済ませる。
    • サーバはSession IDをセッション確立時のServerHelloで送っておく
    1. 次にクライアントがセッションを再開するときはClientHelloにSession IDを含めて送る。
    2. サーバはSession IDを含めたServerHello、新しい暗号鍵、その暗号鍵で暗号化されたFinishedを送る。
    3. クライアントはセッションが再開されたことを確認したら、暗号化されたFinishedを送る。
    4. こうしてセッション再開時は1往復でハンドシェイクが完了される
  • 上記の方法ではサーバがSession IDをキャッシュする必要があるが、クライアントで全てを保持する方法もある
    • session ticketという方法

鍵交換

To Be Continued...

前:第1章 次:第3章

脚注
  1. 必須ではないメッセージ ↩︎ ↩︎

  2. ChangeCipherSpecプロトコルのメッセージ ↩︎ ↩︎

GitHubで編集を提案

Discussion