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

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

このコミットは、Go言語のcrypto/tlsパッケージにおけるクライアント接続のセキュリティ強化に関するものです。具体的には、tls.Client関数を使用する際に、サーバー名の指定(ServerName)または証明書検証の無効化(InsecureSkipVerify)のいずれかを必須とすることで、意図しない証明書検証スキップによるセキュリティリスクを低減することを目的としています。

コミット

commit fca335e91a915b6aae536936a7694c4a2a007a60
Author: Adam Langley <agl@golang.org>
Date:   Fri Feb 21 15:56:41 2014 -0500

    crypto/tls: enforce that either ServerName or InsecureSkipVerify be given.
    
    crypto/tls has two functions for creating a client connection: Dial,
    which most users are expected to use, and Client, which is the
    lower-level API.
    
    Dial does what you expect: it gives you a secure connection to the host
    that you specify and the majority of users of crypto/tls appear to work
    fine with it.
    
    Client gives more control but needs more care. Specifically, if it
    wasn't given a server name in the tls.Config then it didn't check that
    the server's certificates match any hostname - because it doesn't have
    one to check against. It was assumed that users of the low-level API
    call VerifyHostname on the certificate themselves if they didn't supply
    a hostname.
    
    A review of the uses of Client both within Google and in a couple of
    external libraries has shown that nearly all of them got this wrong.
    
    Thus, this change enforces that either a ServerName or
    InsecureSkipVerify is given. This does not affect tls.Dial.
    
    See discussion at https://groups.google.com/d/msg/golang-nuts/4vnt7NdLvVU/b1SJ4u0ikb0J.
    
    Fixes #7342.
    
    LGTM=bradfitz
    R=golang-codereviews, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/67010043

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

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

元コミット内容

このコミットは、crypto/tlsパッケージにおいて、ServerNameまたはInsecureSkipVerifyのいずれかが指定されていることを強制するものです。

crypto/tlsには、クライアント接続を作成するための2つの主要な関数があります。

  1. Dial: ほとんどのユーザーが使用することを想定されている高レベルAPI。指定されたホストへの安全な接続を提供し、ほとんどのユーザーはこれで問題なく動作します。
  2. Client: より低レベルなAPIで、より詳細な制御が可能ですが、より注意が必要です。特に、tls.Configにサーバー名が指定されていない場合、サーバーの証明書がどのホスト名とも一致するかどうかをチェックしませんでした。これは、チェック対象のホスト名がないためです。低レベルAPIのユーザーは、ホスト名を指定しない場合、自身で証明書に対してVerifyHostnameを呼び出すことが想定されていました。

しかし、Google内部およびいくつかの外部ライブラリにおけるClientの使用状況をレビューした結果、ほとんどのケースでこの点が誤って使用されていることが判明しました。

そのため、この変更は、ServerNameまたはInsecureSkipVerifyのいずれかが指定されていることを強制します。この変更はtls.Dialには影響しません。

この変更に関する議論は、https://groups.google.com/d/msg/golang-nuts/4vnt7NdLvVU/b1SJ4u0ikb0J で参照できます。

このコミットはIssue #7342を修正します。

変更の背景

Go言語のcrypto/tlsパッケージは、TLS(Transport Layer Security)プロトコルを実装し、セキュアな通信を可能にします。クライアントがTLS接続を確立する際、サーバーの身元を確認するためにサーバー証明書の検証が行われます。この検証プロセスには、証明書が信頼できる認証局によって発行されたものであるかの確認(証明書チェーンの検証)と、証明書に記載されたホスト名が接続しようとしているサーバーのホスト名と一致するかの確認(ホスト名検証)が含まれます。

従来のtls.Client関数は、既に確立されたネットワーク接続(net.Conn)に対してTLSレイヤーを追加する低レベルAPIでした。この関数を使用する際、tls.Config構造体にServerNameフィールドが設定されていない場合、GoのTLSクライアントはサーバーのホスト名検証を行いませんでした。これは、ServerNameが指定されていない場合、どのホスト名に対して検証を行うべきか不明であるためです。開発者は、このような状況では自身でVerifyHostnameのような追加の検証を行うか、InsecureSkipVerifyを明示的に設定して検証をスキップする責任がありました。

しかし、コミットメッセージに記載されているように、多くの開発者がこの低レベルAPIの挙動を誤解し、ServerNameを指定せずにtls.Clientを使用した場合に、意図せずホスト名検証がスキップされてしまうというセキュリティ上の脆弱性を生み出していました。これにより、中間者攻撃(Man-in-the-Middle, MITM)に対して脆弱な接続が確立される可能性がありました。MITM攻撃では、攻撃者が正当なサーバーになりすまし、クライアントとサーバー間の通信を傍受・改ざんすることができます。

この問題に対処するため、Goチームはtls.Clientを使用する際に、開発者が明示的にServerNameを指定してホスト名検証を有効にするか、あるいはInsecureSkipVerifytrueに設定して意図的に検証をスキップするかのいずれかを強制する変更を導入しました。これにより、開発者がセキュリティ上の考慮事項を意識し、適切な設定を行うことを促し、デフォルトで安全な挙動を保証するよう設計されました。

前提知識の解説

このコミットを理解するためには、以下のTLSおよびGo言語のcrypto/tlsパッケージに関する知識が必要です。

TLS (Transport Layer Security)

TLSは、インターネット上での通信のセキュリティを確保するための暗号化プロトコルです。主に以下の機能を提供します。

  • 認証: 通信相手が主張する通りの相手であることを確認します。サーバー認証が一般的ですが、クライアント認証も可能です。
  • 機密性: 通信内容が第三者に傍受されないように暗号化します。
  • 完全性: 通信内容が途中で改ざんされていないことを保証します。

TLS接続の確立は「TLSハンドシェイク」と呼ばれる一連のステップで行われます。このハンドシェイク中に、サーバーは自身の身元を証明するためにデジタル証明書をクライアントに提示します。

デジタル証明書とホスト名検証

サーバー証明書には、サーバーの公開鍵、証明書の発行者、有効期間、そして証明書が有効なホスト名(Common NameやSubject Alternative Name)が含まれています。クライアントは、この証明書を検証することで、接続先のサーバーが信頼できる正規のサーバーであることを確認します。

  • 証明書チェーンの検証: クライアントは、サーバー証明書が信頼できる認証局(CA)によって署名されているかを確認します。これは、証明書チェーンをたどり、最終的にクライアントが信頼するルートCA証明書に到達するかどうかを検証するプロセスです。
  • ホスト名検証: クライアントは、サーバー証明書に記載されているホスト名が、実際に接続しようとしているホスト名と一致するかどうかを確認します。例えば、example.comに接続しようとしているのに、証明書がattacker.comのものであれば、ホスト名検証は失敗します。この検証が適切に行われないと、中間者攻撃のリスクが高まります。

SNI (Server Name Indication)

SNIはTLSプロトコルの拡張機能で、クライアントがTLSハンドシェイクの初期段階で接続しようとしているホスト名をサーバーに伝える仕組みです。これにより、一つのIPアドレスで複数のドメインのTLS証明書をホストしているサーバーが、クライアントに対して適切な証明書を提示できるようになります。tls.ConfigServerNameフィールドは、このSNIの目的でも使用されます。

Go言語 crypto/tls パッケージ

Goのcrypto/tlsパッケージは、TLSクライアントおよびサーバーの実装を提供します。

  • tls.Config: TLS接続の設定を保持する構造体です。この構造体には、証明書、鍵、ルートCA、プロトコルバージョン、そして本コミットで重要なServerNameInsecureSkipVerifyなどのフィールドが含まれます。

    • ServerName (string): クライアントが接続しようとしているサーバーのホスト名を指定します。この値はSNIとしてサーバーに送信され、またサーバー証明書のホスト名検証にも使用されます。
    • InsecureSkipVerify (bool): trueに設定すると、サーバー証明書の検証(証明書チェーンの検証とホスト名検証の両方)を完全にスキップします。本番環境での使用は強く非推奨であり、中間者攻撃に対して脆弱になります。デバッグや特定のテストシナリオでのみ使用されるべきです。
  • tls.Dial: net.Dialと同様に、ネットワーク接続の確立とTLSハンドシェイクの両方を一度に行う高レベルのコンビニエンス関数です。通常、この関数を使用する場合、tls.ConfigServerNameが指定されていれば、自動的にホスト名検証が行われます。

  • tls.Client: 既に確立されたnet.Connインターフェース(例えば、net.Dialで作成されたTCP接続)を受け取り、その上にTLSレイヤーを構築する低レベルの関数です。この関数は、より複雑なネットワーク構成やプロキシ、トンネルなどのシナリオで、開発者がネットワーク接続の確立とTLSハンドシェイクのタイミングを分離したい場合に利用されます。

VerifyHostname (Go 1.3以前の挙動)

Go 1.3以前のcrypto/tlsパッケージでは、tls.Clientを使用し、かつtls.ConfigServerNameが指定されていない場合、ホスト名検証は行われませんでした。この場合、開発者は自身でx509.VerifyHostnameのような関数を呼び出して、証明書のホスト名を手動で検証することが期待されていました。しかし、この手動検証の必要性がしばしば見落とされ、セキュリティ上の問題を引き起こしていました。

技術的詳細

このコミットの技術的な核心は、crypto/tlsパッケージのクライアントサイドハンドシェイクロジックに、tls.ConfigServerNameフィールドとInsecureSkipVerifyフィールドのいずれかが設定されていることを強制するチェックを追加した点にあります。

変更が加えられたのは、src/pkg/crypto/tls/handshake_client.goファイル内のclientHandshake()関数です。この関数は、TLSクライアントがサーバーとのハンドシェイクを開始する際に呼び出される主要なロジックを含んでいます。

コミットによって追加されたコードは以下の通りです。

if len(c.config.ServerName) == 0 && !c.config.InsecureSkipVerify {
	return errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config")
}

このコードスニペットは、TLSハンドシェイクが開始される直前に実行されます。そのロジックは以下のようになります。

  1. c.config.ServerNameの長さが0であるか(つまり、ServerNameが設定されていないか)をチェックします。
  2. 同時に、c.config.InsecureSkipVerifyfalseであるか(つまり、証明書検証をスキップしない設定であるか)をチェックします。
  3. 上記の両方の条件がtrueである場合、つまり「ServerNameが指定されておらず、かつ証明書検証をスキップしない設定である」という状況であれば、errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config")というエラーを返してハンドシェイクを中断します。

このチェックにより、開発者がtls.Clientを使用する際に、以下のいずれかの選択を強制されることになります。

  • ServerNameを指定する: これが推奨される方法です。ServerNameを指定することで、GoのTLSクライアントは自動的にサーバー証明書のホスト名検証を行います。これにより、中間者攻撃から保護されます。
  • InsecureSkipVerifytrueに設定する: このオプションは、証明書検証を意図的に無効にする場合に使用します。これはセキュリティリスクを伴うため、デバッグ、テスト、または非常に特殊な環境でのみ使用されるべきです。この設定を行うことで、開発者は検証をスキップすることのセキュリティ上の影響を明示的に認識し、受け入れたことになります。

この変更は、tls.Dial関数には影響しません。tls.Dialは内部でtls.Clientを使用しますが、tls.Dialの引数としてホスト名が渡されるため、そのホスト名が自動的にtls.ConfigServerNameフィールドに設定されます。したがって、tls.Dialを使用する限り、この新しいチェックによってエラーが発生することはありません。

この修正は、Go 1.3リリースの一部として導入されました。

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

変更は以下の2つのファイルにわたります。

  1. doc/go1.3.txt: Go 1.3のリリースノートにこの変更が追加されました。

    --- a/doc/go1.3.txt
    +++ b/doc/go1.3.txt
    @@ -9,3 +9,4 @@ misc/benchcmp has been replaced by go tool benchcmp (CL 47980043)
     cmd/go, go/build: support .m files (CL 60590044)
     unicode: upgrade from Unicode 6.2.0 to 6.3.0 (CL 65400044)
     runtime/debug: add SetPanicOnFault (CL 66590044)
    +crypto/tls: ServerName or InsecureSkipVerify (CL 67010043)
    

    この行は、Go 1.3の変更点として「crypto/tls: ServerNameまたはInsecureSkipVerify」が追加されたことを示しています。

  2. src/pkg/crypto/tls/handshake_client.go: 実際のロジック変更が行われたファイルです。

    --- a/src/pkg/crypto/tls/handshake_client.go
    +++ b/src/pkg/crypto/tls/handshake_client.go
    @@ -33,6 +33,10 @@ func (c *Conn) clientHandshake() error {
     		c.config = defaultConfig()
     	}
     
    +	if len(c.config.ServerName) == 0 && !c.config.InsecureSkipVerify {
    +		return errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config")
    +	}
    +
     	hello := &clientHelloMsg{
     		vers:                c.config.maxVersion(),
     		compressionMethods:  []uint8{compressionNone},
    

    この差分は、clientHandshake()関数の冒頭に、ServerNameが空でかつInsecureSkipVerifyfalseの場合にエラーを返す新しいチェックが追加されたことを示しています。

コアとなるコードの解説

src/pkg/crypto/tls/handshake_client.go内の変更は、clientHandshake()関数にセキュリティチェックを追加するものです。

func (c *Conn) clientHandshake() error {
	// ... 既存のコード ...

	// TLS設定がデフォルトの場合、デフォルト設定を適用
	if c.config == nil {
		c.config = defaultConfig()
	}

	// ここから追加されたコード
	// ServerNameが指定されておらず、かつInsecureSkipVerifyがfalseの場合、エラーを返す
	if len(c.config.ServerName) == 0 && !c.config.InsecureSkipVerify {
		return errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config")
	}
	// ここまで追加されたコード

	// ... 既存のハンドシェイク処理の続き ...
	hello := &clientHelloMsg{
		vers:                c.config.maxVersion(),
		compressionMethods:  []uint8{compressionNone},
		// ...
	}
	// ...
}

このコードは、tls.Client関数が呼び出され、その内部でTLSハンドシェイクが開始される際に実行されます。

  1. c.config == nilのチェック: これは、tls.Configが明示的に提供されていない場合に、デフォルトのTLS設定が適用されることを保証します。このコミットの変更は、デフォルト設定が適用された後、またはユーザーがカスタム設定を提供した場合のいずれにも適用されます。

  2. if len(c.config.ServerName) == 0 && !c.config.InsecureSkipVerify:

    • len(c.config.ServerName) == 0: これは、tls.Config構造体のServerNameフィールドが空文字列であるかどうかをチェックします。ServerNameが空であるということは、クライアントが接続しようとしているサーバーのホスト名が明示的に指定されていないことを意味します。通常、このホスト名はサーバー証明書の検証に使用されます。
    • !c.config.InsecureSkipVerify: これは、tls.Config構造体のInsecureSkipVerifyフィールドがfalseであるかどうかをチェックします。InsecureSkipVerifyfalseであるということは、クライアントがサーバー証明書の検証をスキップしない、つまり検証を行うことを意図していることを意味します。
  3. return errors.New(...): 上記の2つの条件が両方とも真である場合、つまり「ホスト名が指定されておらず、かつ証明書検証をスキップしない」という状況であれば、"tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config"というエラーメッセージを含む新しいエラーを返します。このエラーは、開発者に対して、セキュリティ上のリスクを避けるためにServerNameを指定するか、または意図的にInsecureSkipVerifytrueに設定する必要があることを明確に伝えます。

この変更により、tls.Clientのユーザーは、ホスト名検証の重要性を認識し、適切なセキュリティ対策を講じることを強制されます。これにより、Goのcrypto/tlsパッケージを使用するアプリケーションのデフォルトのセキュリティレベルが向上します。

関連リンク

  • Go Issue #7342 (このコミットが修正したとされるIssue): コミットメッセージに記載されていますが、Goの公式Issueトラッカーで直接この番号のIssueを見つけることはできませんでした。しかし、コミットメッセージが明示的に言及しているため、内部的な追跡番号であるか、または関連する議論の文脈で参照されている可能性があります。
  • Go言語のTLSパッケージに関する議論 (golang-nutsメーリングリスト): https://groups.google.com/d/msg/golang-nuts/4vnt7NdLvVU/b1SJ4u0ikb0J
  • Go CL 67010043 (Gerrit Code Review): https://golang.org/cl/67010043

参考にした情報源リンク