crypto/tls: allow QUIC to configure net.Conn used on ClientHelloInfo
要約
概要
crypto/tls パッケージにおいて、QUICスタックがハンドシェイク時のコールバック(GetCertificate や GetConfigForClient 等)に渡される tls.ClientHelloInfo.Conn フィールドへ独自の net.Conn を注入できるよう、tls.QUICConfig に ClientHelloInfoConn フィールドを追加するProposalです。
ステータス変更
likely_accept → accepted
likely_accept となった後、反対意見や設計上の懸念が追加で寄せられなかったため、コンセンサスに変化なしと判断され、2026年4月8日に正式にacceptedとなりました。
技術的背景
現状の問題点
crypto/tls の GetCertificate や GetConfigForClient コールバックは tls.ClientHelloInfo を受け取り、その Conn フィールド(型は net.Conn)にアクセスできます。しかしQUICはUDPを使用しており、TCPベースの net.Conn が持つRead/Write/Closeといった操作は意味をなしません。実質的に有用なのは LocalAddr と RemoteAddr のみです。
この問題を回避するために、quic-goなどのQUICスタックは tls.Config.Clone() を呼び出してコンフィグを複製し、コールバックをラップしてアドレス情報だけを返すフェイクの net.Conn を注入するワークアラウンドを採用していました。
ところがGo 1.25.6のセキュリティ修正(CVE-2025-68121、Issue #77113)により、Config.Clone() が自動生成されたセッションチケットキーをコピーしなくなりました。これによってQUICスタックがConfigをCloneすると、セッション再開(Session Resumption)が機能しなくなるという問題が発生しました。
// Before(quic-goのワークアラウンド)
clonedConfig := tlsConfig.Clone()
origGetCertificate := clonedConfig.GetCertificate
clonedConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
info.Conn = &fakeConn{localAddr: localAddr, remoteAddr: remoteAddr}
return origGetCertificate(info)
}
// Config.Clone()がセッションチケットキーをコピーしなくなったため、
// セッション再開が壊れる
提案された解決策
tls.QUICConfig に ClientHelloInfoConn net.Conn フィールドを追加します。QUICスタックはここにフェイクの net.Conn をセットするだけでよく、tls.Config のCloneは不要になります。
type QUICConfig struct {
TLSConfig *Config
EnableSessionEvents bool
ClientHelloInfoConn net.Conn // ハンドシェイク中にClientHelloInfo.Connへ渡されるConn
}
これによって何ができるようになるか
QUICスタック(quic-goや x/net/quic 等)は tls.Config をCloneすることなく、ハンドシェイク時のコールバックにローカル・リモートアドレスを安全に渡せるようになります。
// After: QUICConfig.ClientHelloInfoConnを使った書き方
fakeConn := &net.UDPConn{} // LocalAddr/RemoteAddrのみを実装したフェイクConn
quicConfig := &tls.QUICConfig{
TLSConfig: tlsConfig, // Cloneは不要
ClientHelloInfoConn: fakeConn,
}
// GetCertificateコールバック内でinfo.Conn.LocalAddr()/RemoteAddr()が正しく返る
具体的なメリット:
- セッション再開の正常化:
tls.Config.Clone()が不要になり、自動生成セッションチケットキーの共有問題を回避できる - デュアルスタックサーバーの簡略化: HTTP/2(TLS 1.2+)とHTTP/3(TLS 1.3のみ)で同一の
tls.Configを共有しやすくなる(関連Issue #77631とあわせて) - 拡張性:
net.Connを渡す設計のため、将来的にQUICスタックが追加のメタデータを提供する必要が生じても対応可能
議論のハイライト
neild(Go標準ライブラリのコントリビュータ)が、フィールド名としてHandshakeConnではなくClientHelloInfoConnを提案し、用途が明確になるとして採用された- アドレス情報を
LocalAddr net.Addr/RemoteAddr net.Addrの2フィールドに分割する案(Option 2)も検討されたが、net.Connを直接渡すOption 1が採用された。理由は「将来QUICスタックがフェイクConnで提供する情報が増えても、APIを再変更する必要がなくなる」という拡張性への配慮 - 提案者(marten-seemann、quic-goメンテナ)は実際にCL #745720を実装し、quic-goのテストスイートで動作確認を行った上でProposalを提出した
- 本Proposalと関連Issue #77631(QUICでのTLS最小バージョン要件の撤廃)の2つを合わせることで、quic-goが
tls.Config.Clone()を一切必要としない構成が実現できることが確認された - RFC 9000で定義されるコネクションマイグレーションはハンドシェイク後にのみ発生するため、ハンドシェイク中に5タプルが固定されていることが前提のこの変更には矛盾がないことが確認された