[インデックス 13668] ファイルの概要
このコミットでは、Go言語の標準ライブラリである net/http
パッケージにおけるTLS (Transport Layer Security) クライアントの挙動が改善されています。具体的には、HTTPリクエストごとに TLSClientConfig.ServerName
が自動的に設定されるようになり、SNI (Server Name Indication) が http.Client
を利用するユーザーにとって「ただ動作する」ように変更されました。
変更されたファイルは以下の通りです。
src/pkg/net/http/client_test.go
:net/http
クライアントのテストファイル。SNIの自動設定を検証するための新しいテストケースが追加されました。src/pkg/net/http/httptest/server.go
: テスト用のHTTPサーバーを提供するhttptest
パッケージのファイル。テストサーバーが使用する証明書がCA (Certificate Authority) 証明書として機能するように変更されました。src/pkg/net/http/transport.go
:net/http
クライアントのトランスポート層の実装ファイル。TLS接続確立時にServerName
を自動設定するロジックが追加されました。
コミット
commit 2eb6a16e16411a394527447b4f6ec0ba838b18e8
Author: Dave Borowitz <dborowitz@google.com>
Date: Wed Aug 22 09:15:41 2012 -0700
net/http: Set TLSClientConfig.ServerName on every HTTP request.
This makes SNI "just work" for callers using the standard http.Client.
Since we now have a test that depends on the httptest.Server cert, change
the cert to be a CA (keeping all other fields the same).
R=bradfitz
CC=agl, dsymonds, gobot, golang-dev
https://golang.org/cl/6448154
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2eb6a16e16411a394527447b4f6ec0ba838b18e8
元コミット内容
net/http: Set TLSClientConfig.ServerName on every HTTP request.
This makes SNI "just work" for callers using the standard http.Client.
Since we now have a test that depends on the httptest.Server cert, change
the cert to be a CA (keeping all other fields the same).
R=bradfitz
CC=agl, dsymonds, gobot, golang-dev
https://golang.org/cl/6448154
変更の背景
この変更の主な背景は、TLS (Transport Layer Security) の拡張である SNI (Server Name Indication) を、Goの標準HTTPクライアント (http.Client
) でより透過的に、かつ自動的に機能させることにあります。
SNIは、単一のIPアドレスとポート番号で複数のTLS証明書をホストするサーバーにおいて、クライアントがどのホスト名に接続しようとしているかをサーバーに伝えるための仕組みです。これにより、サーバーは適切な証明書をクライアントに提示し、TLSハンドシェイクを正常に完了させることができます。
このコミット以前は、Goの http.Client
を使用してSNIが必要なサーバーに接続する場合、開発者が明示的に http.Transport
の TLSClientConfig
内の ServerName
フィールドを設定する必要がありました。これは、特に多くの異なるホストに接続するアプリケーションにとって、煩雑な作業であり、SNIの概念を理解していない開発者にとっては予期せぬTLS接続エラーの原因となる可能性がありました。
このコミットは、http.Client
がリクエスト先のホスト名から自動的に ServerName
を推測し、TLS設定に適用することで、この手動設定の必要性を排除し、SNIを「ただ動作する」ようにすることを目指しています。これにより、開発者はSNIの詳細を意識することなく、安全なHTTPS接続を確立できるようになります。
また、この変更に伴い、SNIの自動設定が正しく機能することを検証するためのテストが追加されました。これらのテストをサポートするために、httptest.Server
が使用するテスト用証明書がCA証明書として機能するように更新されています。これは、テスト環境でクライアントがサーバーの証明書を信頼できるようにするために必要でした。
前提知識の解説
1. TLS/SSL (Transport Layer Security / Secure Sockets Layer)
TLSは、インターネット上で安全な通信を行うための暗号化プロトコルです。ウェブサイトのHTTPS接続などで広く利用されており、通信の盗聴、改ざん、なりすましを防ぎます。TLSハンドシェイクと呼ばれるプロセスを通じて、クライアントとサーバー間で暗号化された通信チャネルを確立します。
2. SNI (Server Name Indication)
SNIは、TLSプロトコルの拡張機能の一つです。TLSハンドシェイクの初期段階で、クライアントが接続しようとしているサーバーのホスト名(ドメイン名)をサーバーに伝えます。
なぜSNIが必要か?
かつては、一つのIPアドレスとポート番号には一つのTLS証明書しか関連付けられませんでした。しかし、仮想ホスティングの普及により、一つのIPアドレスで複数のドメイン(例: example.com
と anothersite.org
)をホストするサーバーが増えました。これらのドメインがそれぞれ異なるTLS証明書を持つ場合、クライアントがIPアドレスとポート番号だけで接続すると、サーバーはどの証明書を提示すべきか判断できません。
SNIは、この問題を解決します。クライアントがホスト名を伝えることで、サーバーは適切な証明書を選択し、TLSハンドシェイクを正常に続行できます。SNIがない場合、サーバーはデフォルトの証明書を提示するか、ハンドシェイクに失敗する可能性があります。
3. Go言語の net/http
パッケージ
Go言語の net/http
パッケージは、HTTPクライアントとサーバーの実装を提供します。
http.Client
: HTTPリクエストを送信し、HTTPレスポンスを受信するクライアントを表します。通常、http.DefaultClient
を使用するか、カスタムのhttp.Client
インスタンスを作成します。http.Transport
:http.Client
が実際にネットワーク接続を確立し、リクエストを送信し、レスポンスを受信する際の低レベルな詳細を処理します。TLS設定(証明書、キー、検証設定など)は、http.Transport
のTLSClientConfig
フィールドを通じて行われます。
4. Go言語の crypto/tls
パッケージ
Go言語の crypto/tls
パッケージは、TLSプロトコルの実装を提供します。
tls.Config
: TLS接続の設定を保持する構造体です。この構造体には、証明書、秘密鍵、ルートCA証明書プール、そしてServerName
などのフィールドが含まれます。tls.Config.ServerName
: クライアントが接続しようとしているサーバーのホスト名を指定します。この値は、SNI拡張としてサーバーに送信され、サーバーが適切な証明書を選択するために使用されます。また、サーバーから受け取った証明書の検証時にも、このServerName
と証明書のSubject Alternative Name (SAN) またはCommon Name (CN) が比較されます。
5. Certificate Authority (CA) 証明書
CA (Certificate Authority) は、デジタル証明書を発行し、その正当性を保証する信頼された第三者機関です。CA証明書は、他の証明書(サーバー証明書など)の署名を検証するために使用される公開鍵を含んでいます。クライアントは、信頼できるCAの証明書(ルートCA証明書や中間CA証明書)を事前に持っており、それらを使ってサーバーから提示された証明書が本物であることを検証します。
このコミットでは、httptest.Server
が使用するテスト用証明書がCA証明書として変更されました。これは、テストクライアントがこのテストサーバーの証明書を信頼し、TLSハンドシェイクを正常に完了できるようにするためです。
6. httptest
パッケージ
Go言語の net/http/httptest
パッケージは、HTTPサーバーとクライアントのテストを容易にするためのユーティリティを提供します。httptest.NewTLSServer
は、TLSを有効にしたテスト用HTTPサーバーを簡単に起動できる関数です。
技術的詳細
このコミットの技術的な核心は、net/http/transport.go
内の Transport
型の getConn
メソッドにおける変更です。
-
ServerName
の自動設定:getConn
メソッドは、HTTPリクエストの接続を確立する際に呼び出されます。HTTPS接続の場合、TLSハンドシェイクを開始する前にtls.Client
関数を呼び出します。この変更では、tls.Client
に渡すtls.Config
オブジェクトのServerName
フィールドが、リクエスト先のホスト名に基づいて自動的に設定されるようになりました。 具体的には、以下のロジックが追加されました。- 既存の
t.TLSClientConfig
がnil
であるか、またはServerName
が空文字列である場合、接続先のアドレス (cm.addr()
) からホスト名 (host
) を抽出します。 - もし
t.TLSClientConfig
がnil
であれば、新しいtls.Config
インスタンスを作成し、そのServerName
を抽出したホスト名に設定します。 - もし
t.TLSClientConfig
が存在し、ServerName
が空であれば、既存のtls.Config
をシャローコピー(クローン)し、そのクローンに抽出したホスト名をServerName
として設定します。これにより、元のt.TLSClientConfig
が変更されることなく、リクエスト固有のServerName
が適用されます。 この自動設定により、http.Client
を利用する開発者は、SNIが必要な場合でもTLSClientConfig.ServerName
を手動で設定する必要がなくなりました。
- 既存の
-
httptest.Server
証明書のCA化:src/pkg/net/http/httptest/server.go
内のlocalhostCert
(テストサーバーが使用する自己署名証明書) のASN.1構造が変更され、CA証明書として機能するように更新されました。- 具体的には、証明書の拡張フィールドに
Basic Constraints: CA:TRUE
が含まれるように変更されました。これにより、この証明書が他の証明書に署名できるCAとして扱われるようになります。 - この変更は、
client_test.go
で追加された新しいテストケース (TestClientWithCorrectTLSServerName
など) が、httptest.Server
から提供される証明書を信頼して検証できるようにするために必要でした。テストクライアントは、このCA証明書をルートCAとして信頼することで、テストサーバーの証明書を検証できるようになります。
- 具体的には、証明書の拡張フィールドに
-
新しいテストケースの追加:
src/pkg/net/http/client_test.go
には、SNIの自動設定と検証を目的とした以下のテスト関数が追加されました。newTLSTransport
:httptest.Server
のTLS設定からルートCA証明書を抽出し、それを含むhttp.Transport
を作成するヘルパー関数。これにより、テストクライアントはテストサーバーの証明書を信頼できます。TestClientWithCorrectTLSServerName
:http.Client
が正しいServerName
(この場合は127.0.0.1
) を設定してTLS接続を確立できることを検証します。サーバー側で受信したリクエストのr.TLS.ServerName
をチェックしています。TestClientWithIncorrectTLSServerName
:http.Client
が意図的に間違ったServerName
を設定した場合に、TLSハンドシェイクが失敗し、適切なエラーが返されることを検証します。これは、ServerName
の検証が正しく機能していることを確認するためです。
これらの変更により、Goの net/http
クライアントは、SNIを透過的にサポートし、より堅牢なHTTPS通信を提供できるようになりました。
コアとなるコードの変更箇所
src/pkg/net/http/transport.go
--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -379,7 +379,18 @@ func (t *Transport) getConn(cm *connectMethod) (*persistConn, error) {
if cm.targetScheme == "https" {
// Initiate TLS and check remote host name against certificate.
- conn = tls.Client(conn, t.TLSClientConfig)
+ cfg := t.TLSClientConfig
+ if cfg == nil || cfg.ServerName == "" {
+ host, _, _ := net.SplitHostPort(cm.addr())
+ if cfg == nil {
+ cfg = &tls.Config{ServerName: host}
+ } else {
+ clone := *cfg // shallow clone
+ clone.ServerName = host
+ cfg = &clone
+ }
+ }
+ conn = tls.Client(conn, cfg)
if err = conn.(*tls.Conn).Handshake(); err != nil {
return nil, err
}
src/pkg/net/http/httptest/server.go
--- a/src/pkg/net/http/httptest/server.go
+++ b/src/pkg/net/http/httptest/server.go
@@ -184,15 +184,15 @@ func (h *waitGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
// of ASN.1 time).
var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
-MIIBOTCB5qADAgECAgEAMAsGCSqGSIb3DQEBBTAAMB4XDTcwMDEwMTAwMDAwMFoX
+MIIBTTCB+qADAgECAgEAMAsGCSqGSIb3DQEBBTAAMB4XDTcwMDEwMTAwMDAwMFoX
DTQ5MTIzMTIzNTk1OVowADBaMAsGCSqGSIb3DQEBAQNLADBIAkEAsuA5mAFMj6Q7
qoBzcvKzIq4kzuT5epSp2AkcQfyBHm7K13Ws7u+0b5Vb9gqTf5cAiIKcrtrXVqkL
-8i1UQF6AzwIDAQABo08wTTAOBgNVHQ8BAf8EBAMCACQwDQYDVR0OBAYEBAECAwQw
-DwYDVR0jBAgwBoAEAQIDBDAbBgNVHREEFDASggkxMjcuMC4wLjGCBVs6OjFdMAsG
-CSqGSIb3DQEBBQNBAJH30zjLWRztrWpOCgJL8RQWLaKzhK79pVhAx6q/3NrF16C7
-+l1BRZstTwIGdoGId8BRpErK1TXkniFb95ZMynM=
------END CERTIFICATE-----
-`)\n+8i1UQF6AzwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCACQwEgYDVR0TAQH/BAgwBgEB
++/wIBATANBgNVHQ4EBgQEAQIDBDAPBgNVHSMECDAGgAQBAgMEMBsGA1UdEQQUMBKC
++CTEyNy4wLjAuMYIFWzo6MV0wCwYJKoZIhvcNAQEFA0EAj1Jsn/h2KHy7dgqutZNB
++nCGlNN+8vw263Bax9MklR85Ti6a0VWSvp/fDQZUADvmFTDkcXeA24pqmdUxeQDWw
++Pg==
++-----END CERTIFICATE-----`)\n
// localhostKey is the private key for localhostCert.
var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
コアとなるコードの解説
src/pkg/net/http/transport.go
の変更
この変更は、Transport
の getConn
メソッド内、特にHTTPS接続を確立する部分にあります。
cfg := t.TLSClientConfig
if cfg == nil || cfg.ServerName == "" {
host, _, _ := net.SplitHostPort(cm.addr())
if cfg == nil {
cfg = &tls.Config{ServerName: host}
} else {
clone := *cfg // shallow clone
clone.ServerName = host
cfg = &clone
}
}
conn = tls.Client(conn, cfg)
cfg := t.TLSClientConfig
: まず、Transport
に設定されているTLSClientConfig
をcfg
変数に代入します。if cfg == nil || cfg.ServerName == ""
: ここが重要な条件分岐です。- もし
TLSClientConfig
が全く設定されていない (nil
) か、 - または
TLSClientConfig
は設定されているものの、その中のServerName
フィールドが空文字列である場合、自動設定のロジックが実行されます。
- もし
host, _, _ := net.SplitHostPort(cm.addr())
: 接続先のアドレス (cm.addr()
) からホスト名(例:example.com
や192.168.1.1
)を抽出します。net.SplitHostPort
は、host:port
形式の文字列をホストとポートに分割します。if cfg == nil { cfg = &tls.Config{ServerName: host} }
:- もし元の
TLSClientConfig
がnil
だった場合、新しいtls.Config
インスタンスを作成し、抽出したhost
をServerName
として設定します。
- もし元の
else { clone := *cfg; clone.ServerName = host; cfg = &clone }
:- もし元の
TLSClientConfig
が存在し、ServerName
が空だった場合、既存のcfg
をシャローコピー(*cfg
で値渡し)してclone
を作成します。 - その
clone
のServerName
を抽出したhost
に設定します。 - そして、
cfg
変数をこのclone
を指すように更新します。このシャローコピーの目的は、Transport
インスタンスに設定されている元のTLSClientConfig
を変更することなく、この特定の接続のために一時的にServerName
を設定することです。これにより、同じTransport
を使用する他の接続に影響を与えることなく、SNIが適切に機能します。
- もし元の
conn = tls.Client(conn, cfg)
: 最終的に、更新された(または新しく作成された)tls.Config
を使用してtls.Client
が呼び出され、TLSハンドシェイクが開始されます。このcfg
には、適切なServerName
が設定されているため、SNIが自動的に機能します。
この変更により、Goの http.Client
は、特別な設定なしにSNIをサポートするようになり、複数の仮想ホストを持つHTTPSサーバーへの接続がより簡単かつ堅牢になりました。
src/pkg/net/http/httptest/server.go
の変更
localhostCert
変数に格納されている自己署名証明書のASN.1エンコーディングが変更されました。具体的には、証明書の Basic Constraints
拡張に CA:TRUE
が含まれるように更新されています。
この変更は、新しいテストケースが httptest.Server
の証明書を信頼して検証できるようにするために不可欠です。テストクライアントは、この localhostCert
をルートCAとして認識し、それによって署名されたサーバー証明書を有効と判断できるようになります。これにより、テスト環境で実際のSNIの挙動を正確にシミュレートし、検証することが可能になります。
関連リンク
- Go Gerrit Code Review: https://golang.org/cl/6448154
参考にした情報源リンク
- TLS Server Name Indication (SNI): https://en.wikipedia.org/wiki/Server_Name_Indication
- Go
net/http
package documentation: https://pkg.go.dev/net/http - Go
crypto/tls
package documentation: https://pkg.go.dev/crypto/tls - Go
net/http/httptest
package documentation: https://pkg.go.dev/net/http/httptest - X.509 Basic Constraints Extension: https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.9 (RFC 5280, Section 4.2.1.9)
- Go
net
package documentation: https://pkg.go.dev/net (forSplitHostPort
)