[インデックス 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つの主要な関数があります。
Dial
: ほとんどのユーザーが使用することを想定されている高レベルAPI。指定されたホストへの安全な接続を提供し、ほとんどのユーザーはこれで問題なく動作します。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
を指定してホスト名検証を有効にするか、あるいはInsecureSkipVerify
をtrue
に設定して意図的に検証をスキップするかのいずれかを強制する変更を導入しました。これにより、開発者がセキュリティ上の考慮事項を意識し、適切な設定を行うことを促し、デフォルトで安全な挙動を保証するよう設計されました。
前提知識の解説
このコミットを理解するためには、以下の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.Config
のServerName
フィールドは、このSNIの目的でも使用されます。
Go言語 crypto/tls
パッケージ
Goのcrypto/tls
パッケージは、TLSクライアントおよびサーバーの実装を提供します。
-
tls.Config
: TLS接続の設定を保持する構造体です。この構造体には、証明書、鍵、ルートCA、プロトコルバージョン、そして本コミットで重要なServerName
やInsecureSkipVerify
などのフィールドが含まれます。ServerName
(string): クライアントが接続しようとしているサーバーのホスト名を指定します。この値はSNIとしてサーバーに送信され、またサーバー証明書のホスト名検証にも使用されます。InsecureSkipVerify
(bool):true
に設定すると、サーバー証明書の検証(証明書チェーンの検証とホスト名検証の両方)を完全にスキップします。本番環境での使用は強く非推奨であり、中間者攻撃に対して脆弱になります。デバッグや特定のテストシナリオでのみ使用されるべきです。
-
tls.Dial
:net.Dial
と同様に、ネットワーク接続の確立とTLSハンドシェイクの両方を一度に行う高レベルのコンビニエンス関数です。通常、この関数を使用する場合、tls.Config
にServerName
が指定されていれば、自動的にホスト名検証が行われます。 -
tls.Client
: 既に確立されたnet.Conn
インターフェース(例えば、net.Dial
で作成されたTCP接続)を受け取り、その上にTLSレイヤーを構築する低レベルの関数です。この関数は、より複雑なネットワーク構成やプロキシ、トンネルなどのシナリオで、開発者がネットワーク接続の確立とTLSハンドシェイクのタイミングを分離したい場合に利用されます。
VerifyHostname
(Go 1.3以前の挙動)
Go 1.3以前のcrypto/tls
パッケージでは、tls.Client
を使用し、かつtls.Config
にServerName
が指定されていない場合、ホスト名検証は行われませんでした。この場合、開発者は自身でx509.VerifyHostname
のような関数を呼び出して、証明書のホスト名を手動で検証することが期待されていました。しかし、この手動検証の必要性がしばしば見落とされ、セキュリティ上の問題を引き起こしていました。
技術的詳細
このコミットの技術的な核心は、crypto/tls
パッケージのクライアントサイドハンドシェイクロジックに、tls.Config
のServerName
フィールドと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ハンドシェイクが開始される直前に実行されます。そのロジックは以下のようになります。
c.config.ServerName
の長さが0であるか(つまり、ServerName
が設定されていないか)をチェックします。- 同時に、
c.config.InsecureSkipVerify
がfalse
であるか(つまり、証明書検証をスキップしない設定であるか)をチェックします。 - 上記の両方の条件が
true
である場合、つまり「ServerName
が指定されておらず、かつ証明書検証をスキップしない設定である」という状況であれば、errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config")
というエラーを返してハンドシェイクを中断します。
このチェックにより、開発者がtls.Client
を使用する際に、以下のいずれかの選択を強制されることになります。
ServerName
を指定する: これが推奨される方法です。ServerName
を指定することで、GoのTLSクライアントは自動的にサーバー証明書のホスト名検証を行います。これにより、中間者攻撃から保護されます。InsecureSkipVerify
をtrue
に設定する: このオプションは、証明書検証を意図的に無効にする場合に使用します。これはセキュリティリスクを伴うため、デバッグ、テスト、または非常に特殊な環境でのみ使用されるべきです。この設定を行うことで、開発者は検証をスキップすることのセキュリティ上の影響を明示的に認識し、受け入れたことになります。
この変更は、tls.Dial
関数には影響しません。tls.Dial
は内部でtls.Client
を使用しますが、tls.Dial
の引数としてホスト名が渡されるため、そのホスト名が自動的にtls.Config
のServerName
フィールドに設定されます。したがって、tls.Dial
を使用する限り、この新しいチェックによってエラーが発生することはありません。
この修正は、Go 1.3リリースの一部として導入されました。
コアとなるコードの変更箇所
変更は以下の2つのファイルにわたります。
-
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
」が追加されたことを示しています。 -
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
が空でかつInsecureSkipVerify
がfalse
の場合にエラーを返す新しいチェックが追加されたことを示しています。
コアとなるコードの解説
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ハンドシェイクが開始される際に実行されます。
-
c.config == nil
のチェック: これは、tls.Config
が明示的に提供されていない場合に、デフォルトのTLS設定が適用されることを保証します。このコミットの変更は、デフォルト設定が適用された後、またはユーザーがカスタム設定を提供した場合のいずれにも適用されます。 -
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
であるかどうかをチェックします。InsecureSkipVerify
がfalse
であるということは、クライアントがサーバー証明書の検証をスキップしない、つまり検証を行うことを意図していることを意味します。
-
return errors.New(...)
: 上記の2つの条件が両方とも真である場合、つまり「ホスト名が指定されておらず、かつ証明書検証をスキップしない」という状況であれば、"tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config"
というエラーメッセージを含む新しいエラーを返します。このエラーは、開発者に対して、セキュリティ上のリスクを避けるためにServerName
を指定するか、または意図的にInsecureSkipVerify
をtrue
に設定する必要があることを明確に伝えます。
この変更により、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
参考にした情報源リンク
- Go
crypto/tls
パッケージのドキュメント: https://pkg.go.dev/crypto/tls - Go
tls.Config
のServerName
とInsecureSkipVerify
に関する解説: tls.Dial
とtls.Client
の違いに関する解説:- Go言語におけるホスト名検証の仕組み: