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

[インデックス 17076] ファイルの概要

このコミットは、Go言語の標準ライブラリnet/httpパッケージにおけるHTTP Basic認証の実装に関する複数の修正を目的としています。具体的には、Basic認証の資格情報(ユーザー名とパスワード)をBase64エンコードする際に、base64.StdEncodingbase64.URLEncodingが混在して使用されていた問題を解決し、常にRFCに準拠した標準的なBase64エンコーディングが適用されるようにコードを統一しています。これにより、Basic認証の信頼性と互換性が向上しました。

コミット

commit a08b1d13eaff45b0506369269ee9c597f3355646
Author: Pieter Droogendijk <pieter@binky.org.uk>
Date:   Wed Aug 7 11:58:59 2013 -0700

    net/http: Various fixes to Basic authentication
    
    There were some issues with the code sometimes using base64.StdEncoding,
    and sometimes base64.URLEncoding.
    Encoding basic authentication is now always done by the same code.
    
    Fixes #5970.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/12397043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a08b1d13eaff45b0506369269ee9c597f3355646

元コミット内容

net/http: Various fixes to Basic authentication

There were some issues with the code sometimes using base64.StdEncoding,
and sometimes base64.URLEncoding.
Encoding basic authentication is now always done by the same code.

Fixes #5970.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/12397043

変更の背景

このコミットの主な背景は、Go言語のnet/httpパッケージ内でHTTP Basic認証の資格情報をBase64エンコードする際に、一貫性のないエンコーディング方式が使用されていたことです。具体的には、base64.StdEncoding(標準Base64エンコーディング)とbase64.URLEncoding(URLセーフBase64エンコーディング)が混在していました。

HTTP Basic認証は、RFC 2617で定義されており、ユーザー名とパスワードをコロンで結合した文字列を標準Base64でエンコードし、Authorizationヘッダーに含めることを規定しています。base64.URLEncodingは、URLのパスやクエリパラメータで使用するために、標準Base64の+/をそれぞれ-_に置き換え、パディング文字=を省略する可能性があるエンコーディングです。Basic認証においては、このURLセーフなエンコーディングは不適切であり、RFCの仕様に準拠していません。

このような不一致は、以下のような問題を引き起こす可能性がありました。

  1. 互換性の問題: RFCに厳密に準拠していないエンコード方式を使用すると、一部のHTTPサーバーやプロキシが正しく認証情報を解釈できない可能性があります。
  2. コードの一貫性の欠如: 同じ目的(Basic認証のエンコード)に対して異なるエンコーディング方式が使われていると、コードの可読性や保守性が低下します。
  3. 潜在的なセキュリティリスク: 誤ったエンコーディングは、意図しない情報漏洩や認証バイパスの脆弱性につながる可能性は低いものの、標準からの逸脱は予期せぬ挙動を引き起こすリスクをはらんでいます。

このコミットは、Go issue #5970で報告された問題を修正するものであり、net/httpパッケージがRFC 2617に完全に準拠し、より堅牢で予測可能なBasic認証の挙動を提供することを目的としています。

前提知識の解説

1. HTTP Basic認証 (RFC 2617)

HTTP Basic認証は、HTTPプロトコルにおける最もシンプルな認証スキームの一つです。クライアントが保護されたリソースにアクセスしようとすると、サーバーはWWW-Authenticateヘッダー(例: WWW-Authenticate: Basic realm="Restricted Area")を含む401 Unauthorizedレスポンスを返します。これを受け取ったクライアントは、ユーザー名とパスワードを要求し、以下の形式でAuthorizationヘッダーを構築して再度リクエストを送信します。

Authorization: Basic <credentials>

ここで<credentials>は、username:passwordという形式の文字列をBase64エンコードしたものです。例えば、ユーザー名がAladdin、パスワードがopen sesameの場合、Aladdin:open sesameという文字列をBase64エンコードします。

2. Base64エンコーディング

Base64は、バイナリデータをASCII文字列に変換するエンコーディング方式です。主に、テキストベースのプロトコル(HTTPヘッダー、MIMEメールなど)でバイナリデータを安全に転送するために使用されます。

Go言語のencoding/base64パッケージには、主に以下の2つのエンコーディング方式が提供されています。

  • base64.StdEncoding: 標準的なBase64エンコーディングです。エンコードされた文字列には、英数字、+/、そしてパディング文字として=が使用されます。HTTP Basic認証では、この標準エンコーディングが要求されます。
  • base64.URLEncoding: URLセーフなBase64エンコーディングです。URLのパスやクエリパラメータで特殊な意味を持つ+/を、それぞれ-_に置き換えます。また、パディング文字の=は通常省略されます。これは、URLエンコードを別途行う必要がないように設計されています。

このコミットの核心は、Basic認証においてbase64.StdEncodingを使用すべきであるにもかかわらず、一部でbase64.URLEncodingが誤って使用されていた点にあります。

3. Go言語のnet/httpパッケージ

net/httpパッケージは、Go言語でHTTPクライアントおよびサーバーを実装するための主要なパッケージです。

  • http.Client: HTTPリクエストを送信するためのクライアントです。
  • http.Request: HTTPリクエストを表す構造体です。URL、メソッド、ヘッダー、ボディなどの情報を含みます。URL.Userフィールドを通じて、URLに埋め込まれたユーザー名とパスワードにアクセスできます。また、SetBasicAuthメソッドは、プログラム的にBasic認証ヘッダーを設定するために使用されます。
  • http.Transport: http.Clientが実際にネットワーク経由でリクエストを送信し、レスポンスを受信する際の低レベルな詳細を処理します。プロキシ設定などもここで行われます。

これらのコンポーネントが、Basic認証のエンコード処理に関与していました。

技術的詳細

このコミットは、net/httpパッケージ内のBasic認証ロジックを統一し、RFC 2617に準拠させるために、以下の主要な変更を導入しました。

  1. basicAuthヘルパー関数の導入: 以前は、Basic認証のエンコードロジックがclient.gorequest.gotransport.goの複数の箇所に分散しており、それぞれで異なるbase64エンコーディングが使用される可能性がありました。このコミットでは、src/pkg/net/http/client.gobasicAuth(username, password string) stringという新しいヘルパー関数を導入しました。この関数は、ユーザー名とパスワードをコロンで結合し、その結果を常にbase64.StdEncodingでエンコードして返します。

    // See 2 (end of page 4) http://www.ietf.org/rfc/rfc2617.txt
    // "To receive authorization, the client sends the userid and password,
    // separated by a single colon (":") character, within a base64
    // encoded string in the credentials."
    // It is not meant to be urlencoded.
    func basicAuth(username, password string) string {
        auth := username + ":" + password
        return base64.StdEncoding.EncodeToString([]byte(auth))
    }
    

    この関数は、RFC 2617の要件をコメントで明記し、「URLエンコードされるべきではない」ことを強調しています。

  2. 既存のBasic認証ロジックの統一:

    • src/pkg/net/http/client.go: http.ClientがURLのUserInfo(ユーザー名とパスワード)から自動的にBasic認証ヘッダーを生成する部分が修正されました。以前はbase64.URLEncodingを使用していた箇所が、新しく導入されたbasicAuth関数を使用するように変更されました。これにより、URLにユーザー名とパスワードが含まれる場合でも、正しいBase64エンコーディングが適用されます。
    • src/pkg/net/http/request.go: Request.SetBasicAuthメソッドが修正されました。このメソッドは、ユーザーが明示的にBasic認証を設定する際に使用されます。以前はbase64.StdEncodingを使用していたものの、basicAuth関数に処理を委譲することで、コードの重複を排除し、一貫性を確保しました。
    • src/pkg/net/http/transport.go: プロキシ認証のロジックが修正されました。プロキシ認証もBasic認証スキームを使用することがあり、ここでもbase64.URLEncodingが誤って使用されていた箇所が、basicAuth関数を使用するように変更されました。
  3. テストケースの追加: src/pkg/net/http/client_test.goTestBasicAuthという新しいテストケースが追加されました。このテストは、ユーザー名とパスワードに特殊文字(スペースなど)を含むURLを使用してhttp.Client.Getを呼び出し、生成されたAuthorizationヘッダーが正しくBase64エンコードされているか(特にbase64.StdEncodingでデコードできるか)を検証します。これにより、修正が正しく機能していることが保証されます。

  4. 不要なインポートの削除: src/pkg/net/http/request.gosrc/pkg/net/http/transport.goから、もはや直接base64パッケージをインポートする必要がなくなったため、関連するインポート文が削除されました。これは、basicAuth関数がbase64パッケージのインポートをカプセル化しているためです。

  5. src/pkg/net/url/url.goの微修正: このファイルでは、URL.String()メソッド内の変数名がuからuiに変更されました。これは機能的な変更ではなく、コードの可読性と一貫性を向上させるためのリファクタリングです。

これらの変更により、net/httpパッケージ全体でBasic認証のエンコード処理が一元化され、RFC 2617に完全に準拠するようになりました。

コアとなるコードの変更箇所

新規追加されたヘルパー関数 basicAuth (src/pkg/net/http/client.go)

// See 2 (end of page 4) http://www.ietf.org/rfc/rfc2617.txt
// "To receive authorization, the client sends the userid and password,
// separated by a single colon (":") character, within a base64
// encoded string in the credentials."
// It is not meant to be urlencoded.
func basicAuth(username, password string) string {
    auth := username + ":" + password
    return base64.StdEncoding.EncodeToString([]byte(auth))
}

http.ClientでのBasic認証生成箇所の修正 (src/pkg/net/http/client.go)

--- a/src/pkg/net/http/client.go
+++ b/src/pkg/net/http/client.go
@@ -161,18 +161,9 @@ func send(req *Request, t RoundTripper) (resp *Response, err error) {
 	}
 
 	if u := req.URL.User; u != nil {
-		auth := u.String()
-		// UserInfo.String() only returns the colon when the
-		// password is set, so we must add it here.
-		//
-		// See 2 (end of page 4) http://www.ietf.org/rfc/rfc2617.txt
-		// "To receive authorization, the client sends the userid and password,
-		// separated by a single colon (":") character, within a base64
-		// encoded string in the credentials."
-		if _, hasPassword := u.Password(); !hasPassword {
-			auth += ":"
-		}
-		req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(auth)))
+		username := u.Username()
+		password, _ := u.Password()
+		req.Header.Set("Authorization", "Basic "+basicAuth(username, password))
 	}
 	resp, err = t.RoundTrip(req)
 	if err != nil {

Request.SetBasicAuthメソッドの修正 (src/pkg/net/http/request.go)

--- a/src/pkg/net/http/request.go
+++ b/src/pkg/net/http/request.go
@@ -467,8 +466,7 @@ func NewRequest(method, urlStr string, body io.Reader) (*Request, error) {
 // With HTTP Basic Authentication the provided username and password
 // are not encrypted.
 func (r *Request) SetBasicAuth(username, password string) {
-	s := username + ":" + password
-	r.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(s)))
+	r.Header.Set("Authorization", "Basic "+basicAuth(username, password))
 }

プロキシ認証ロジックの修正 (src/pkg/net/http/transport.go)

--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -273,7 +272,9 @@ func (cm *connectMethod) proxyAuth() string {
 		return ""
 	}
 	if u := cm.proxyURL.User; u != nil {
-		return "Basic " + base64.URLEncoding.EncodeToString([]byte(u.String()))
+		username := u.Username()
+		password, _ := u.Password()
+		return "Basic " + basicAuth(username, password)
 	}
 	return ""
 }

新規追加されたテストケース (src/pkg/net/http/client_test.go)

func TestBasicAuth(t *testing.T) {
	defer afterTest(t)
	tr := &recordingTransport{}
	client := &Client{Transport: tr}

	url := "http://My%20User:My%20Pass@dummy.faketld/"
	expected := "My User:My Pass"
	client.Get(url)

	if tr.req.Method != "GET" {
		t.Errorf("got method %q, want %q", tr.req.Method, "GET")
	}
	if tr.req.URL.String() != url {
		t.Errorf("got URL %q, want %q", tr.req.URL.String(), url)
	}
	if tr.req.Header == nil {
		t.Fatalf("expected non-nil request Header")
	}
	auth := tr.req.Header.Get("Authorization")
	if strings.HasPrefix(auth, "Basic ") {
		encoded := auth[6:]
		decoded, err := base64.StdEncoding.DecodeString(encoded)
		if err != nil {
			t.Fatal(err)
		}
		s := string(decoded)
		if expected != s {
			t.Errorf("Invalid Authorization header. Got %q, wanted %q", s, expected)
		}
	} else {
		t.Errorf("Invalid auth %q", auth)
	}
}

コアとなるコードの解説

このコミットの最も重要な変更は、basicAuthヘルパー関数の導入と、それを利用した既存コードの統一です。

basicAuth関数の役割

basicAuth関数は、HTTP Basic認証の資格情報文字列を生成し、Base64エンコードする処理をカプセル化します。

  1. auth := username + ":" + password: RFC 2617の規定通り、ユーザー名とパスワードをコロン(:)で結合します。
  2. return base64.StdEncoding.EncodeToString([]byte(auth)): 結合された文字列をバイトスライスに変換し、**base64.StdEncoding**を使用してBase64エンコードします。このStdEncodingの使用が、RFC準拠の鍵となります。コメントで「URLエンコードされるべきではない」と明記されているのは、過去の誤用(URLEncoding)を意識したものです。

変更がもたらす効果

  • 一貫性: net/httpパッケージ内のBasic認証に関連するすべての箇所(クライアントの自動認証、SetBasicAuthメソッド、プロキシ認証)が、この単一のbasicAuth関数を使用するようになりました。これにより、エンコーディング方式の不一致が解消され、コード全体で一貫した挙動が保証されます。
  • RFC準拠: base64.StdEncodingの強制により、生成されるBasic認証ヘッダーがRFC 2617の仕様に完全に準拠するようになります。これにより、様々なHTTPサーバーやプロキシとの互換性が向上します。
  • 保守性の向上: Basic認証のエンコードロジックが一箇所に集約されたため、将来的な変更や修正が必要になった場合でも、影響範囲が限定され、保守が容易になります。
  • 堅牢性の向上: 新しいテストケースが追加されたことで、Basic認証の機能が正しく動作すること、特に特殊文字を含むユーザー名やパスワードが適切に処理されることが保証されます。

この修正は、Go言語の標準ライブラリの品質と信頼性を高める上で重要な改善と言えます。

関連リンク

参考にした情報源リンク

  • RFC 2617: HTTP Authentication: Basic and Digest Access Authentication
  • Go encoding/base64 package documentation
  • Go net/http package documentation
  • Go issue tracker (当時の#5970の議論内容)
  • GitHubのコミット履歴と差分表示