[インデックス 18764] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージにおいて、HTTPレスポンスにTLS接続の状態情報を含める機能を追加するものです。具体的には、http.Response
構造体に TLS *tls.ConnectionState
フィールドが追加され、TLSで確立された接続を介して受信したレスポンスに対して、そのTLS接続の詳細情報(使用された暗号スイート、TLSバージョン、証明書など)が提供されるようになります。これにより、クライアントアプリケーションは、受信したレスポンスがどのようなTLS設定で保護されていたかをプログラム的に確認できるようになります。
コミット
- コミットハッシュ:
4816986ff5cedc9adfb495fba2eca4ded4c90507
- 作者: Paul A Querna paul.querna@gmail.com
- コミット日時: 2014年3月5日 12:25:55 -0800
- コミットメッセージ:
net/http: Add TLS Connection State to Responses. Fixes #7289. LGTM=bradfitz R=golang-codereviews, r, bradfitz, rsc CC=golang-codereviews https://golang.org/cl/52660047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4816986ff5cedc9adfb495fba2eca4ded4c90507
元コミット内容
net/http: Add TLS Connection State to Responses.
Fixes #7289.
LGTM=bradfitz
R=golang-codereviews, r, bradfitz, rsc
CC=golang-codereviews
https://golang.org/cl/52660047
変更の背景
この変更の背景には、Goの net/http
クライアントがTLS接続を介してHTTPリクエストを送信し、レスポンスを受信する際に、そのTLS接続に関する詳細情報が http.Response
オブジェクトから直接利用できないという課題がありました。
従来の http.Response
構造体には、HTTPヘッダー、ボディ、ステータスコードなどの情報が含まれていましたが、基盤となるTLS接続の暗号スイート、TLSバージョン、サーバー証明書チェーンといったセキュリティ関連のメタデータは含まれていませんでした。これは、特にセキュリティを重視するアプリケーションや、TLS接続のプロパティに基づいて動作を調整する必要があるアプリケーションにとって、重要な情報の欠落を意味していました。
例えば、クライアントが特定の暗号スイートが使用されたことを確認したい場合や、サーバー証明書を検証したい場合、またはTLSハンドシェイクの詳細をログに記録したい場合など、これらの情報はレスポンスオブジェクトに直接紐付けられていることが望ましいです。このコミットは、このようなニーズに応えるために、http.Response
に TLS *tls.ConnectionState
フィールドを追加し、TLS接続の状態をレスポンスから直接参照できるようにすることで、この情報ギャップを埋めることを目的としています。
Fixes #7289
という記述がありますが、Goの公開されているIssueトラッカーではこの番号のIssueは見つかりませんでした。これは、内部的なトラッキング番号であるか、非常に古い、あるいは既にアーカイブされたIssueである可能性が考えられます。しかし、このコミットの目的は、TLS接続状態をレスポンスに含めるという明確な機能追加であり、その必要性がコミュニティや開発者の間で認識されていたことを示唆しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の標準ライブラリとネットワークプロトコルに関する知識が必要です。
-
net/http
パッケージ:- Go言語でHTTPクライアントおよびサーバーを実装するための主要なパッケージです。
http.Client
: HTTPリクエストを送信し、HTTPレスポンスを受信するクライアントを表します。http.Request
: HTTPリクエストを表す構造体です。http.Response
: HTTPレスポンスを表す構造体です。このコミットの主要な変更対象です。http.Transport
:http.Client
が実際にネットワーク接続を確立し、リクエストを送信し、レスポンスを受信するメカニズムを定義します。TLS接続の確立もTransport
の役割の一部です。
-
crypto/tls
パッケージ:- Go言語でTLS (Transport Layer Security) プロトコルを実装するためのパッケージです。TLSは、インターネット上での安全な通信を可能にする暗号化プロトコルです。
tls.Config
: TLS接続の設定(証明書、キー、暗号スイート、TLSバージョンなど)を定義する構造体です。tls.Conn
: TLSハンドシェイクが完了し、暗号化された通信が可能になったTLS接続を表します。tls.ConnectionState
: 確立されたTLS接続の現在の状態に関する詳細情報(使用されたTLSバージョン、暗号スイート、サーバー証明書、クライアント証明書など)を保持する構造体です。このコミットでhttp.Response
に追加されるフィールドの型です。
-
TLSハンドシェイク:
- クライアントとサーバーが安全な通信を開始する前に実行する一連のプロトコルです。このプロセス中に、両者は互いを認証し、暗号化アルゴリズムと共有キーをネゴシエートします。
- ハンドシェイクが成功すると、
tls.ConnectionState
オブジェクトにその接続に関するすべての詳細情報が格納されます。
-
HTTP over TLS (HTTPS):
- HTTPプロトコルをTLS上で動作させることで、通信の機密性、完全性、認証を保証します。
net/http
パッケージは、内部的にcrypto/tls
を利用してHTTPS通信を処理します。
- HTTPプロトコルをTLS上で動作させることで、通信の機密性、完全性、認証を保証します。
このコミットは、http.Transport
がTLS接続を確立した後、その接続から得られる tls.ConnectionState
情報を http.Response
オブジェクトにコピーするというメカニズムを導入しています。これにより、アプリケーション開発者は、HTTPレスポンスを受け取った際に、そのレスポンスがどのTLS設定で送られてきたかを容易に検査できるようになります。
技術的詳細
このコミットの技術的な詳細は、主に以下の3つのファイルへの変更に集約されます。
-
src/pkg/net/http/response.go
:http.Response
構造体に新しいフィールドTLS *tls.ConnectionState
が追加されました。- このフィールドは、レスポンスがTLS接続を介して受信された場合に、そのTLS接続の状態を指すポインタとなります。TLS接続でない場合は
nil
のままです。 crypto/tls
パッケージがインポートされるようになりました。
// response.go の変更点 import ( "bufio" "crypto/tls" // 新しく追加されたインポート "errors" "io" "net/textproto" "net/url" "strconv" "strings" ) type Response struct { // ... 既存のフィールド ... // TLS allows information about the TLS connection on which the // response was received. The Transport in this package sets the field // for TLS-enabled connections before returning the Response otherwise // it leaves the field nil. TLS *tls.ConnectionState // 新しく追加されたフィールド }
-
src/pkg/net/http/transport.go
:Transport
の内部で、実際にネットワーク接続を管理するpersistConn
構造体のreadLoop
メソッドが変更されました。readLoop
は、HTTPレスポンスを読み取る主要なループです。ここで、基盤となる接続が*tls.Conn
型であるかどうかをチェックします。- もし
*tls.Conn
であれば、そのConnectionState()
メソッドを呼び出してtls.ConnectionState
を取得し、新しく追加されたresp.TLS
フィールドにその情報をコピーします。
// transport.go の変更点 (persistConn.readLoop メソッド内) func (pc *persistConn) readLoop() { // ... 既存のレスポンス読み取りロジック ... // レスポンスが正常に読み取られ、かつ接続がTLS接続である場合 if tlsConn, ok := pc.conn.(*tls.Conn); resp != nil && ok { resp.TLS = new(tls.ConnectionState) // 新しいConnectionStateを割り当て *resp.TLS = tlsConn.ConnectionState() // TLS接続の状態をコピー } // ... 既存のロジック ... }
この変更により、
http.Transport
がTLS接続を確立し、その接続上でレスポンスを受信した際に、自動的にTLS接続の状態がhttp.Response
オブジェクトに設定されるようになります。 -
src/pkg/net/http/client_test.go
:TestResponseSetsTLSConnectionState
という新しいテストケースが追加されました。- このテストは、
httptest.NewTLSServer
を使用してTLSサーバーをセットアップし、特定の暗号スイート (tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA
) を使用するようにクライアントのTLSClientConfig
を設定します。 - クライアントがサーバーにリクエストを送信した後、受信した
http.Response
のres.TLS
フィールドがnil
でないこと、およびres.TLS.CipherSuite
が期待される値と一致することを検証します。 - これにより、
http.Response
にTLS接続状態が正しく設定されていることが保証されます。
// client_test.go の変更点 (新しいテストケース) func TestResponseSetsTLSConnectionState(t *testing.T) { defer afterTest(t) ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) { w.Write([]byte("Hello")) })) defer ts.Close() tr := newTLSTransport(t, ts) tr.TLSClientConfig.CipherSuites = []uint16{tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA} // 特定の暗号スイートを設定 tr.Dial = func(netw, addr string) (net.Conn, error) { return net.Dial(netw, ts.Listener.Addr().String()) } defer tr.CloseIdleConnections() c := &Client{Transport: tr} res, err := c.Get("https://example.com/") if err != nil { t.Fatal(err) } if res.TLS == nil { t.Fatal("Response didn't set TLS Connection State.") // TLS状態が設定されていることを確認 } if res.TLS.CipherSuite != tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA { t.Errorf("Unexpected TLS Cipher Suite: %d != %d", res.TLS.CipherSuite, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA) // 暗号スイートが正しいことを確認 } res.Body.Close() }
これらの変更により、Goの net/http
クライアントは、TLSで保護された通信を行った際に、その通信のセキュリティに関する詳細な情報をレスポンスオブジェクトを通じて提供できるようになり、より堅牢で情報豊富なアプリケーションの開発が可能になりました。
コアとなるコードの変更箇所
src/pkg/net/http/response.go
--- a/src/pkg/net/http/response.go
+++ b/src/pkg/net/http/response.go
@@ -8,6 +8,7 @@ package http
import (
"bufio"
+ "crypto/tls"
"errors"
"io"
"net/textproto"
@@ -74,6 +75,12 @@ type Response struct {
// Request's Body is nil (having already been consumed).
// This is only populated for Client requests.
Request *Request
+
+ // TLS allows information about the TLS connection on which the
+ // response was received. The Transport in this package sets the field
+ // for TLS-enabled connections before returning the Response otherwise
+ // it leaves the field nil.
+ TLS *tls.ConnectionState
}
// Cookies parses and returns the cookies set in the Set-Cookie headers.
src/pkg/net/http/transport.go
--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -791,6 +791,12 @@ func (pc *persistConn) readLoop() {
// resp, err = ReadResponse(pc.br, rc.req)
// }
}
+
+ if tlsConn, ok := pc.conn.(*tls.Conn); resp != nil && ok {
+ resp.TLS = new(tls.ConnectionState)
+ *resp.TLS = tlsConn.ConnectionState()
+ }
+
hasBody := resp != nil && rc.req.Method != "HEAD" && resp.ContentLength != 0
if err != nil {
src/pkg/net/http/client_test.go
--- a/src/pkg/net/http/client_test.go
+++ b/src/pkg/net/http/client_test.go
@@ -709,6 +709,34 @@ func TestTransportUsesTLSConfigServerName(t *testing.T) {
res.Body.Close()
}
+func TestResponseSetsTLSConnectionState(t *testing.T) {
+ defer afterTest(t)
+ ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ w.Write([]byte("Hello"))
+ }))
+ defer ts.Close()
+
+ tr := newTLSTransport(t, ts)
+ tr.TLSClientConfig.CipherSuites = []uint16{tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA}
+ tr.Dial = func(netw, addr string) (net.Conn, error) {
+ return net.Dial(netw, ts.Listener.Addr().String())
+ }
+ defer tr.CloseIdleConnections()
+ c := &Client{Transport: tr}
+ res, err := c.Get("https://example.com/")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if res.TLS == nil {
+ t.Fatal("Response didn't set TLS Connection State.")
+ }
+ if res.TLS.CipherSuite != tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA {
+ t.Errorf("Unexpected TLS Cipher Suite: %d != %d",
+ res.TLS.CipherSuite, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA)
+ }
+ res.Body.Close()
+}
+
// Verify Response.ContentLength is populated. http://golang.org/issue/4126
func TestClientHeadContentLength(t *testing.T) {
defer afterTest(t)
コアとなるコードの解説
src/pkg/net/http/response.go
の変更
import "crypto/tls"
の追加:http.Response
構造体内でtls.ConnectionState
型を使用するために、crypto/tls
パッケージがインポートされました。TLS *tls.ConnectionState
フィールドの追加:http.Response
構造体にTLS
という新しいフィールドが追加されました。このフィールドは*tls.ConnectionState
型であり、TLS接続に関する詳細な情報(TLSバージョン、暗号スイート、ピア証明書など)を保持します。このフィールドがnil
でない場合、そのレスポンスはTLS接続を介して受信されたことを意味します。
src/pkg/net/http/transport.go
の変更
persistConn.readLoop()
内でのTLS状態の取得と設定:persistConn
はhttp.Transport
の内部で、単一の永続的なネットワーク接続(HTTP/1.1のKeep-Aliveなど)を管理する役割を担います。readLoop()
メソッドは、この接続からレスポンスを継続的に読み取るゴルーチンです。 追加されたコードブロックは以下の処理を行います。if tlsConn, ok := pc.conn.(*tls.Conn); resp != nil && ok { resp.TLS = new(tls.ConnectionState) *resp.TLS = tlsConn.ConnectionState() }
tlsConn, ok := pc.conn.(*tls.Conn)
:pc.conn
はnet.Conn
インターフェース型ですが、これが実際に*tls.Conn
型(TLS接続を表す具体的な型)であるかを型アサーションによって確認します。ok
がtrue
であれば、現在の接続はTLS接続です。resp != nil && ok
: レスポンスが正常に読み取られており (resp != nil
)、かつ接続がTLS接続である (ok
) 場合にのみ、以下の処理を実行します。resp.TLS = new(tls.ConnectionState)
:http.Response
オブジェクトのTLS
フィールドに、新しいtls.ConnectionState
構造体へのポインタを割り当てます。*resp.TLS = tlsConn.ConnectionState()
:*tls.Conn
オブジェクトのConnectionState()
メソッドを呼び出して、現在のTLS接続の状態(暗号スイート、TLSバージョン、証明書など)を取得し、それをresp.TLS
が指すtls.ConnectionState
構造体にコピーします。
この変更により、http.Transport
はTLS接続を介して受信したすべてのHTTPレスポンスに、そのTLS接続のメタデータを自動的に付与するようになります。
src/pkg/net/http/client_test.go
の変更
TestResponseSetsTLSConnectionState
テスト関数の追加: この新しいテストは、上記の変更が正しく機能することを確認するために追加されました。httptest.NewTLSServer
: テスト用のTLSサーバーを起動します。これにより、クライアントはHTTPSリクエストを送信できます。tr.TLSClientConfig.CipherSuites = []uint16{tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA}
: クライアントが特定の暗号スイートを使用するように設定します。これは、テストで期待されるCipherSuite
の値を明確にするためです。c.Get("https://example.com/")
: クライアントがHTTPSリクエストを送信します。if res.TLS == nil
: 受信したレスポンスres
のTLS
フィールドがnil
でないことを確認します。nil
であれば、TLS接続状態が設定されていないためテストは失敗します。if res.TLS.CipherSuite != tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA
:res.TLS
に含まれるCipherSuite
が、クライアントが設定した期待される暗号スイートと一致することを確認します。これにより、TLS接続状態が正しく取得され、レスポンスに反映されていることが検証されます。
これらのコード変更は、Goの net/http
クライアントがTLS通信の透明性を高め、開発者がセキュリティ関連の情報をより簡単に利用できるようにするための重要な改善です。
関連リンク
- Go言語
net/http
パッケージのドキュメント: https://pkg.go.dev/net/http - Go言語
crypto/tls
パッケージのドキュメント: https://pkg.go.dev/crypto/tls - Go言語のIssueトラッカー (一般的なIssue検索): https://github.com/golang/go/issues
参考にした情報源リンク
- Go言語の公式ドキュメント (
net/http
,crypto/tls
パッケージ) - GitHubのGoリポジトリのコミット履歴
Fixes #7289
についてのWeb検索: 公式のGoリポジトリやドキュメントでは、この特定のIssue番号に関する情報は見つかりませんでした。これは、内部的なトラッキング番号であるか、非常に古い、あるいは既にアーカイブされたIssueである可能性が考えられます。