これまでTLS 1.3とセッションID方式は実装したことがあったが、この経験だけではTLS 1.2に対するTLS 1.3の利点に気づいていなかった。この二ヶ月の間に、セッションチケット方式を実装し、また引き継いだTLS 1.2のコードを大幅にリファクタリングした過程で、セッションの再開は、TLS 1.2よりもTLS 1.3の方が安全であると気づくことができた。備忘録として安全である理由を書いておく。
以下は、セッションID方式にもセッションチケット方式にも、共通している性質である。説明の都合で、セッションチケット方式を取り上げる。
TLS 1.2
TLS 1.2用のセッションチケット方式は、RFC 5077で定義されている。以下にRFC 5077からTLS 1.2のフルハンドシェイクの図を抜粋する。
Client Server
ClientHello
(empty SessionTicket extension)-------->
ServerHello
(empty SessionTicket extension)
Certificate*
ServerKeyExchange*
CertificateRequest*
<-------- ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished -------->
NewSessionTicket
[ChangeCipherSpec]
<-------- Finished
Application Data <-------> Application Data
NewSesionTicketはChangeCipherSpecの前に送られる。つまり、(セッションチケット自体はサーバしか知らない秘密鍵で暗号化されているが)通信自体は平文である。(ChangeCipherSpecが角括弧で囲まれているのは、ハンドシェイクメッセージではないという意味。)
サーバ認証を省略し、TLS 1.2ではメインシークレットを復元するための仕組みがセッションの再開である。再開の図も引用する:
Client Server
ClientHello
(SessionTicket extension) -------->
ServerHello
(empty SessionTicket extension)
NewSessionTicket
[ChangeCipherSpec]
<-------- Finished
[ChangeCipherSpec]
Finished -------->
Application Data <-------> Application Data
フルハンドシェイクではクライントが先に(ハンドシェイクメッセージ全体のチェクサムである)Finishedを送るが、再開ではサーバが先に送る。
サーバは、再開時に再びNewSessionTicketを送ってもよい(MAY)。しかし、メインシークレットを変える方法はないので、同じメインシークレットを格納し、単に使用期間が伸びたチケットを発行することになる。OpenSSLのs_serverで試してみると、再開時にはNewSessionTicketを送ってこない。
このようにTLS 1.2では、セッションの再開を使うと、サーバ認証を省略できるものの、メインシークレットを更新する方法がなく、メインシークレットを複数のセッションで使い回すことになる。
TLS 1.3
TLS 1.3のフルハンドシェイクをRFC 8446から引用する:
Client Server
Initial Handshake:
ClientHello
+ key_share -------->
ServerHello
+ key_share
{EncryptedExtensions}
{CertificateRequest*}
{Certificate*}
{CertificateVerify*}
{Finished}
<-------- [Application Data*]
{Certificate*}
{CertificateVerify*}
{Finished} -------->
<-------- [NewSessionTicket]
[Application Data] <-------> [Application Data]
角括弧で囲まれたメッセージは、暗号化されていることを表しているので、NewSessionTicketは暗号化されて送信されことが分かる。
次にセッションの再開の図も引用するが、説明の都合上NewSessionTicketを書き加える:
Subsequent Handshake:
ClientHello
+ key_share*
+ pre_shared_key -------->
ServerHello
+ pre_shared_key
+ key_share*
{EncryptedExtensions}
{Finished}
<-------- [Application Data*]
{Finished} -------->
<-------- [NewSessionTicket]
[Application Data] <-------> [Application Data]
このセッションでの(複数の)メインシークレットは、pre_shared_keyに含まれる(前のセッションで受け取った)セッションチケットの情報と、key_shareを利用して生成された一時的な鍵から生成される。端的に言えば、前回のセッションとは異なるメインシークレットが生成される。よって、新たに発行するNewSessionTicketにも、新しいメインシークレットが格納される。このように、TLS 1.3では、各セッションで独自のメインシークレットが使用される。