C ネットワーク

libcurlのHTTPS通信でSSL/TLSセッションを再利用する

投稿日:

仕事で小一時間ひっかかったのでメモとして残します。

背景

HTTPSは、HTTPに対してSSL/TLSによる通信路暗号化を行うことでセキュリティを高めています。
HTTPSではセッション確率時、TCPのシーケンスに加え、おおまかに以下のシーケンス(ハンドシェイク)が追加されます。

  1. 暗号化方式の提案と採用
  2. サーバ証明書の送受信
  3. プレマスタ・シークレットの送受信

これらはHTTPSのセッション確立時に毎回行われることになります。したがって、セッション毎に上記のシーケンスでデータを受け渡しします。
すなわち、小さいデータを何回も送信するような場合、本データを何回も送受信することになります。

SIM等を使用している場合は、通信データ量に対して課金する方式が採用されることが多いため、上記のデータ通信が重くのしかかってきます。
以下は、試験環境下で行ったHTTPS通信時のSSL/TLSハンドシェイクをtsharkで抽出したものです。(IPはhost/destに記述されています

 8   0.042467 host -> dest   SSL 362 Client Hello

10   0.080564 dest -> host   TLSv1.2 2902 Server Hello

12   0.083183 dest -> host   TLSv1.2 428 Certificate

14   0.090718 host -> dest   TLSv1.2 192 Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message
15   0.128850 dest -> host   TLSv1.2 117 Change Cipher Spec, Hello Request, Hello Request

SSLやTLSv1.2のとなりにかかれている数値が、ハンドシェイクでやりとりされるデータ量(byte)になります。
本環境では、一回のハンドシェイクにおいて、計4k近くのデータを通信しています。
先ほどのシーケンスに当てはめて特徴的なのは、10、12番のServer Hello及びCertificateで行っている「サーバ証明書の送受信」です。
このシーケンスだけで全体のデータ量の75%ほどを使用しています。

今回は、SSL/TLSのセッションを再利用することで、「サーバ証明書の送受信」を省略して、SSL/TLSハンドシェイクでやりとりされる通信データ量を削減したいと思います。

要件と仕様、制約

要件は以下です。

  • HTTPS通信を行うテストコードを作成する
  • SSL/TLSハンドシェイクのデータ量を削減する
  • 実装はC or C++で行う

仕様は以下です。

  • libcurlを使用する
  • サーバ証明書の送受信を削減するべく、セッションIDを用いたSSL/TLSセッションの再利用をおこなう
  • Cで実装する

制約は以下です。

  • サーバ側はSSL/TLSセッションキャッシュ機能が有効にされているものとする

設計・実装

まず、今回使うlibcurl-devをapt等でインストールしてください。

apt-get install libcurl-dev

その後、以下のように実装を行います。

#include <stdio.h>
#include <curl/curl.h>

int main(void) {
  CURL *curl = curl_easy_init();
  CURL *curl2 = curl_easy_init(); /* a second handle */
  CURLcode ret;
  if(curl) {
    CURLSH *shobject = curl_share_init();
    curl_share_setopt(shobject, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
   
    curl_easy_setopt(curl, CURLOPT_URL, "https://www.google.com/");
    curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
    curl_easy_setopt(curl, CURLOPT_SHARE, shobject);
    ret = curl_easy_perform(curl);
    curl_easy_cleanup(curl);
              
    curl_easy_setopt(curl2, CURLOPT_URL, "https://www.google.com/");
    curl_easy_setopt(curl2, CURLOPT_COOKIEFILE, "");
    curl_easy_setopt(curl2, CURLOPT_SHARE, shobject);
    ret = curl_easy_perform(curl2);
    curl_easy_cleanup(curl2);
                           
    curl_share_cleanup(shobject);
  }
  return 0;
}

libcurlの大まかな使い方は今回は省略します。

セッション再利用時の特徴は、9、10行目のshobject、curl_share_setoptです。
複数個のcurlハンドラに共有させるオブジェクト(shobject)を生成し、curl_share_setoptで度の設定を共有させるかを指定します。
今回はSSL/TLSセッションを共有したいので、該当するlibcurlのCURL_LOCK_DATA_SSL_SESSIONを指定します。

あとは別々のcurlハンドラを使用して通信をするだけです。

テスト

上記プログラムを実行時のtsharkの実行結果は以下になります。

  8   0.042662   host -> dest SSL 362 Client Hello

 10   0.081060 dest -> host   TLSv1.2 2902 Server Hello

 12   0.083672 dest -> host   TLSv1.2 429 Certificate

 14   0.088096   host -> dest TLSv1.2 192 Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message
 15   0.126683 dest -> host   TLSv1.2 117 Change Cipher Spec, Hello Request, Hello Request
 16   0.127026   host -> dest TLSv1.2 148 Application Data
 17   0.165834 dest -> host   TLSv1.2 765 Application Data
 18   0.168058  host -> dest TLSv1.2 97 Encrypted Alert
(2回め通信開始)
 30   0.211377   1host -> dest SSL 394 Client Hello

 32   0.249727 dest -> host   TLSv1.2 209 Server Hello, Change Cipher Spec, Hello Request, Hello Request

 34   0.250114   host -> dest TLSv1.2 117 Change Cipher Spec, Encrypted Handshake Message

 36   0.328033   host -> dest TLSv1.2 148 Application Data

 38   0.366630 dest -> host   TLSv1.2 765 Application Data
 39   0.367397   host -> dest TLSv1.2 97 Encrypted Alert

2回め以降では、Certificateが省略されており、ServerHelloのパケットサイズが減っている(=証明書の送受信が削減されている)ことが分かります。

実際にセッションが再利用されたかどうか見てみましょう。SSL/TLSセッション再利用は、前回使用したSSLのセッションIDをサーバ・クライアントがキャッシュし、再接続時にクライアントがセッションIDを再送信することで行います。
今回のtsharkの結果では、
フレームID10が一回目接続時のサーバのセッションID伝達部、
フレームID30が二回目接続時のクライアントからのセッションID伝達部
フレームID32が二回目接続時のサーバのセッションID照合結果伝達部になっています。

フレームID10(サーバからのセッションID伝達)のSSL部分のプロトコルの中身を見てみましょう。

Secure Sockets Layer
    TLSv1.2 Record Layer: Handshake Protocol: Server Hello
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 87
        Handshake Protocol: Server Hello
    (省略)
            Session ID Length: 32
            Session ID: 7deb96~~~~...

サーバからセッションIDが送られてきたことが分かります。

フレーム30(再接続時のクライアントからのセッションID伝達)のSSL部分のプロトコルの中身を見てみましょう。

Secure Sockets Layer
    SSL Record Layer: Handshake Protocol: Client Hello
        Content Type: Handshake (22)
        Version: TLS 1.0 (0x0301)
        Length: 323
        Handshake Protocol: Client Hello
    (省略)
            Session ID Length: 32
            Session ID: 7deb96~~~~...

クライアント側でキャッシュされたセッションIDがサーバ側に送信されたことが確認できました。

最後に、フレームID32で、クライアント側で送信されたセッションIDをサーバが認識したかを確認しましょう。

Secure Sockets Layer
    TLSv1.2 Record Layer: Handshake Protocol: Server Hello
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 87
        Handshake Protocol: Server Hello
    (省略)
            Session ID Length: 32
            Session ID: 7deb96~~~~...

クライアント側で送信されたセッションIDをサーバが認識し、それを再利用しています。

終わりに

今回は、libcurlでのSSL/TLSのセッション再利用の方法として、セッションIDを利用したもの紹介しました。

このほかにも、セッションチケットを使用したセッション再利用方法もありますが、そちらは今回は紹介しません。機会がある時に解説記事を書こうと思います。

-C, ネットワーク
-,

執筆者:


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

関連記事

pthread_cancelすると何が行われるのか

仕事でのメモ。 目次1 pthread_cancel2 pthread_cancelで何が行われるのか2.1 シグナル送出2.2 注意2.3 ソースコードを読む2.4 pthread_cancel時の …