これまで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では、各セッションで独自のメインシークレットが使用される。