Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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.ResponseTLS *tls.ConnectionState フィールドを追加し、TLS接続の状態をレスポンスから直接参照できるようにすることで、この情報ギャップを埋めることを目的としています。

Fixes #7289 という記述がありますが、Goの公開されているIssueトラッカーではこの番号のIssueは見つかりませんでした。これは、内部的なトラッキング番号であるか、非常に古い、あるいは既にアーカイブされたIssueである可能性が考えられます。しかし、このコミットの目的は、TLS接続状態をレスポンスに含めるという明確な機能追加であり、その必要性がコミュニティや開発者の間で認識されていたことを示唆しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の標準ライブラリとネットワークプロトコルに関する知識が必要です。

  1. net/http パッケージ:

    • Go言語でHTTPクライアントおよびサーバーを実装するための主要なパッケージです。
    • http.Client: HTTPリクエストを送信し、HTTPレスポンスを受信するクライアントを表します。
    • http.Request: HTTPリクエストを表す構造体です。
    • http.Response: HTTPレスポンスを表す構造体です。このコミットの主要な変更対象です。
    • http.Transport: http.Client が実際にネットワーク接続を確立し、リクエストを送信し、レスポンスを受信するメカニズムを定義します。TLS接続の確立も Transport の役割の一部です。
  2. crypto/tls パッケージ:

    • Go言語でTLS (Transport Layer Security) プロトコルを実装するためのパッケージです。TLSは、インターネット上での安全な通信を可能にする暗号化プロトコルです。
    • tls.Config: TLS接続の設定(証明書、キー、暗号スイート、TLSバージョンなど)を定義する構造体です。
    • tls.Conn: TLSハンドシェイクが完了し、暗号化された通信が可能になったTLS接続を表します。
    • tls.ConnectionState: 確立されたTLS接続の現在の状態に関する詳細情報(使用されたTLSバージョン、暗号スイート、サーバー証明書、クライアント証明書など)を保持する構造体です。このコミットで http.Response に追加されるフィールドの型です。
  3. TLSハンドシェイク:

    • クライアントとサーバーが安全な通信を開始する前に実行する一連のプロトコルです。このプロセス中に、両者は互いを認証し、暗号化アルゴリズムと共有キーをネゴシエートします。
    • ハンドシェイクが成功すると、tls.ConnectionState オブジェクトにその接続に関するすべての詳細情報が格納されます。
  4. HTTP over TLS (HTTPS):

    • HTTPプロトコルをTLS上で動作させることで、通信の機密性、完全性、認証を保証します。net/http パッケージは、内部的に crypto/tls を利用してHTTPS通信を処理します。

このコミットは、http.Transport がTLS接続を確立した後、その接続から得られる tls.ConnectionState 情報を http.Response オブジェクトにコピーするというメカニズムを導入しています。これにより、アプリケーション開発者は、HTTPレスポンスを受け取った際に、そのレスポンスがどのTLS設定で送られてきたかを容易に検査できるようになります。

技術的詳細

このコミットの技術的な詳細は、主に以下の3つのファイルへの変更に集約されます。

  1. 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 // 新しく追加されたフィールド
    }
    
  2. 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 オブジェクトに設定されるようになります。

  3. src/pkg/net/http/client_test.go:

    • TestResponseSetsTLSConnectionState という新しいテストケースが追加されました。
    • このテストは、httptest.NewTLSServer を使用してTLSサーバーをセットアップし、特定の暗号スイート (tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA) を使用するようにクライアントの TLSClientConfig を設定します。
    • クライアントがサーバーにリクエストを送信した後、受信した http.Responseres.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状態の取得と設定: persistConnhttp.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()
    }
    
    1. tlsConn, ok := pc.conn.(*tls.Conn): pc.connnet.Conn インターフェース型ですが、これが実際に *tls.Conn 型(TLS接続を表す具体的な型)であるかを型アサーションによって確認します。oktrue であれば、現在の接続はTLS接続です。
    2. resp != nil && ok: レスポンスが正常に読み取られており (resp != nil)、かつ接続がTLS接続である (ok) 場合にのみ、以下の処理を実行します。
    3. resp.TLS = new(tls.ConnectionState): http.Response オブジェクトの TLS フィールドに、新しい tls.ConnectionState 構造体へのポインタを割り当てます。
    4. *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 テスト関数の追加: この新しいテストは、上記の変更が正しく機能することを確認するために追加されました。
    1. httptest.NewTLSServer: テスト用のTLSサーバーを起動します。これにより、クライアントはHTTPSリクエストを送信できます。
    2. tr.TLSClientConfig.CipherSuites = []uint16{tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA}: クライアントが特定の暗号スイートを使用するように設定します。これは、テストで期待される CipherSuite の値を明確にするためです。
    3. c.Get("https://example.com/"): クライアントがHTTPSリクエストを送信します。
    4. if res.TLS == nil: 受信したレスポンス resTLS フィールドが nil でないことを確認します。nil であれば、TLS接続状態が設定されていないためテストは失敗します。
    5. if res.TLS.CipherSuite != tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA: res.TLS に含まれる CipherSuite が、クライアントが設定した期待される暗号スイートと一致することを確認します。これにより、TLS接続状態が正しく取得され、レスポンスに反映されていることが検証されます。

これらのコード変更は、Goの net/http クライアントがTLS通信の透明性を高め、開発者がセキュリティ関連の情報をより簡単に利用できるようにするための重要な改善です。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (net/http, crypto/tls パッケージ)
  • GitHubのGoリポジトリのコミット履歴
  • Fixes #7289 についてのWeb検索: 公式のGoリポジトリやドキュメントでは、この特定のIssue番号に関する情報は見つかりませんでした。これは、内部的なトラッキング番号であるか、非常に古い、あるいは既にアーカイブされたIssueである可能性が考えられます。