あどけない話

Internet technologies

APIから見たTLS 1.2の同期性と1.3の非同期性

プロトコル自体を比べるとTLS 1.2よりもTLS 1.3の方が簡潔で洗練されている。しかし、APIの視点から見ると、TLS 1.3は非同期性が高くなる。同期性的なTLS 1.2のプロトコル設計には、妥当性があると気づいたので記録を残す。

以下のような API を考える

  • new -- コンフィグを与えるとコンテキストを返す
  • handshake -- コンテキストを与えると、ハンドシェイクを試みてTLSコネクションを作成する
  • sendData -- コンテキストとデータを与えると、データを送信する
  • recvData -- コンテキストを与えると、データを受信する
  • bye -- アラートを送って、TLSコネクションを終了する

クライアントの疑似コードは、こんな感じ:

ctx = new(コンフィグ);
handshake(ctx);
sendData(ctx, "Hello world!");
rc = recvData(ctx);
bye(ctx);

クライアント認証とNewSessionTicket

TLS 1.2のフルハンドシェイクでは、クライアントがサーバからFinishedを受け取って終了する。

         Client                                               Server

handshake: ClientHello
           (empty SessionTicket extension)-------->                  : handshake
                                                          ServerHello
                                      (empty SessionTicket extension)
                                                         Certificate*
                                                   ServerKeyExchange*
                                                  CertificateRequest*
                                       <--------      ServerHelloDone
           Certificate*
           ClientKeyExchange
           CertificateVerify*
           [ChangeCipherSpec]
           Finished                    -------->
                                                     NewSessionTicket
                                                   [ChangeCipherSpec]
                                       <--------             Finished
sendData:  Application Data            -------->                     : recvData
recvData:                              <--------     Application Data: sendData
bye :  Alert                       -------->

TLS 1.2 のクライアントのhandshakeは、サーバのFinishedを待てるから:

  • クライアントの handshake は NewSessionTicket を受け取れる
  • クライアントの handshake はクライアント認証が失敗した際、Alert を受け取れる

クライアント認証が必要なくなるセッションの再開では、クライアントが Finished を送ることでハンドシェイクが完了する。

         Client                                                Server
handshake: ClientHello
           (SessionTicket extension)    -------->                    : handshake
                                                          ServerHello
                                      (empty SessionTicket extension)
                                                     NewSessionTicket
                                                   [ChangeCipherSpec]
                                       <--------             Finished
           [ChangeCipherSpec]
           Finished                    -------->
sendData:  Application Data            -------->                     : recvData
recvData:                              <--------     Application Data: sendData
bye     :  Alert                       -------->

実に同期的だ。非同期な要素としては、recvDataが正常終了や異常終了のアラートを受け取るぐらいである。

一方、TLS 1.3のハンドシェイクは以下の通り:

       Client                                           Server

handshake: ClientHello
           + key_share*
           + signature_algorithms*
           + psk_key_exchange_modes*
           + pre_shared_key*    -------->
                                                  ServerHello: handshake
                                                 + key_share*
                                            + pre_shared_key*
                                          EncryptedExtensions
                                          CertificateRequest*
                                                 Certificate*
                                           CertificateVerify*
                              <---------             Finished
           Certificate*
           CertificateVerify*
           Finished            -------->
sendData:  Application Data    -------->                     : recvData
recvData:                      <--------    Application Data : sendData
                                            NewSessionTicket
bye:       Alert               -------->

クライアントの handshake は、Finishedを送って終わるので、NewSessionTicketを受け取れない。このため、recvDataやbyeにNewSessionTicketを受け取る非同期的な工夫が必要となる。

同様にクライアントのhandshakeは、普通に実装したら、クライアント認証が失敗した際のアラートを受け取れない。handshakeがエラーを返さずに戻ってきたのに、recvDataがクライアント認証失敗のアラートを受け取るようなAPIは、とても使いずらい。よって、クライアントhandshakeがCertificateを送った場合は、一定期間アラートが戻ってこないか見張るべきだ。その間に、通常のデータを受信したら保存しておき、次にrecvDataが呼ばれた際に、そのデータを返すべきである。

余談だが、TLSレベルのクライアント認証は何かと問題が多いので、HTTPのレベルで証明書を使ったクライアント認証を実現しようという動きがある。

0-RTT

TLS 1.3 の 0-RTT は、とても非同期的だ。クライアントが、0-RTTでearly dataを送る場合、専用のAPIを与えると実用に乏しいことが経験的に分かった。そこで、これまでのsendDataでearly dataを送ることを考える。

handshake: ClientHello
           + early_data
           + key_share*
           + psk_key_exchange_modes
           + pre_shared_key
sendData:  Application Data    -------->
                                                  ServerHello: handshake
                                             + pre_shared_key
                                                 + key_share*
                                          EncryptedExtensions
                                                + early_data*
                               <--------             Finished
sendData:  EndOfEarlyData
           Finished           
           Application Data    -------->                     : recvData
recvData:                      <--------    Application Data : sendData
                                            NewSessionTicket
bye:       Alert               -------->
  • クライアントのhandshakeは、ClientHelloを送ったら戻ってこないといけない。フルハンドシェイクのときは、戻らないことに注意。
  • クライアントのsendDataは、ハンドシェクが完了したら EndOfEarlyData と Finished を送らないといけない
  • サーバのrecvDataは、EndOfEarlyData と Finishedも受け取らないといけない

という訳で、TLS 1.3のAPIは大変なのだ。