[インデックス 16075] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/smtp
パッケージにおけるSMTP認証の挙動を修正するものです。具体的には、src/pkg/net/smtp/auth.go
で PLAIN
認証の条件が緩和され、src/pkg/net/smtp/smtp_test.go
にその変更を検証するためのテストが追加されています。
コミット
commit ca24f9ec00c65f8e75e38ad33d1b0b3bb287e2a3
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Apr 3 10:52:20 2013 -0700
net/smtp: allow PLAIN auth when advertised
The smtp package originally allowed PLAIN whenever, but then
the TLS check was added for paranoia, but it's too paranoid:
it prevents using PLAIN auth even from localhost to localhost
when the server advertises PLAIN support.
This CL also permits the client to send PLAIN if the server
advertises it.
Fixes #5184
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/8279043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ca24f9ec00c65f8e75e38ad33d1b0b3bb287e2a3
元コミット内容
net/smtp
: サーバーが PLAIN
認証をアドバタイズしている場合に PLAIN
認証を許可する。
smtp
パッケージは元々常に PLAIN
認証を許可していましたが、その後、過度な用心のためにTLSチェックが追加されました。しかし、これは過剰であり、サーバーが PLAIN
サポートをアドバタイズしている場合でも、localhostからlocalhostへの接続でさえ PLAIN
認証の使用を妨げていました。
この変更は、サーバーが PLAIN
をアドバタイズしている場合、クライアントが PLAIN
を送信することを許可します。
Fixes #5184
変更の背景
このコミットの背景には、Go言語の net/smtp
パッケージにおける PLAIN
認証の扱いに関する問題がありました。元々、このパッケージは PLAIN
認証を常に許可していましたが、セキュリティ上の懸念から、TLS (Transport Layer Security) が確立されていない接続では PLAIN
認証を禁止するチェックが追加されました。
しかし、このTLSチェックは「過剰な用心 (too paranoid)」であることが判明しました。具体的には、SMTPサーバーが PLAIN
認証メカニズムを明示的にサポートしているとアドバタイズしている場合であっても、TLS接続が確立されていないという理由だけで PLAIN
認証が拒否されていました。これは、特にlocalhost間の通信など、TLSによる暗号化が必ずしも必要ではない、あるいはサーバー側で PLAIN
認証が意図的に許可されているシナリオにおいて、正当な認証を妨げる問題を引き起こしていました。
このコミットは、このような過剰な制限を緩和し、サーバーが PLAIN
認証をサポートしていると明示的に通知(アドバタイズ)している場合には、TLS接続がなくても PLAIN
認証を許可するように変更することで、実用性とセキュリティのバランスを取ることを目的としています。コミットメッセージにある Fixes #5184
は、Goの内部バグトラッカーにおける関連する課題番号を示していると考えられます。
前提知識の解説
SMTP (Simple Mail Transfer Protocol)
SMTPは、電子メールを送信するためのインターネット標準プロトコルです。メールクライアントからメールサーバーへ、またはメールサーバー間でメールを転送するために使用されます。
SMTP認証
SMTPサーバーは、メール送信を許可する前にユーザーの身元を確認するために認証を要求することがあります。これにより、スパム送信や不正利用を防ぎます。様々な認証メカニズムが存在します。
PLAIN認証
PLAIN
認証は、SMTP認証メカニズムの一つです。ユーザー名とパスワードを平文(クリアテキスト)で送信します。このため、盗聴されるリスクがあるため、通常はTLS/SSLなどの暗号化された接続と組み合わせて使用することが強く推奨されます。しかし、特定の環境(例: 信頼できるローカルネットワーク内、localhost間の通信)では、暗号化なしで PLAIN
認証が許可される場合があります。
TLS (Transport Layer Security)
TLSは、インターネット上で通信のセキュリティを提供する暗号化プロトコルです。以前はSSL (Secure Sockets Layer) と呼ばれていました。TLSを使用することで、通信内容が暗号化され、盗聴や改ざんから保護されます。SMTPにおいては、STARTTLS
コマンドを使用して平文接続からTLS暗号化接続にアップグレードするのが一般的です。
SMTPサーバーのアドバタイズ (EHLO/HELO)
SMTPクライアントがサーバーに接続すると、まず HELO
または EHLO
コマンドを送信して自身を識別します。サーバーはこれに応答し、自身がサポートする機能や認証メカニズムのリストをクライアントに通知(アドバタイズ)します。例えば、サーバーが PLAIN
認証をサポートしている場合、その応答の中に AUTH PLAIN
のような情報が含まれます。クライアントはこのアドバタイズされた情報に基づいて、どの認証メカニズムを使用できるかを判断します。
技術的詳細
このコミットの技術的な核心は、net/smtp
パッケージの PlainAuth
構造体の Start
メソッドにおける PLAIN
認証の許可条件の変更です。
変更前は、Start
メソッド内で server.TLS
が false
(つまり、TLS接続が確立されていない)であれば、無条件に「unencrypted connection」というエラーを返していました。これは、PLAIN
認証が平文で資格情報を送信するため、盗聴のリスクを避けるためのセキュリティ対策でした。
しかし、この厳格なチェックは、サーバーが PLAIN
認証を明示的にサポートしているとアドバタイズしている場合でも、TLS接続がないという理由だけで認証を拒否するという問題を引き起こしていました。これは、例えば開発環境でのlocalhost間の通信や、内部ネットワークでセキュリティが確保されていると見なされる環境など、TLSが必須ではない、あるいはサーバー側で意図的に PLAIN
認証が許可されているシナリオで不便でした。
このコミットでは、この挙動を修正するために、server.TLS
が false
の場合でも、サーバーがアドバタイズしている認証メカニズムのリスト (server.Auth
) に PLAIN
が含まれているかどうかを確認するロジックが追加されました。
具体的には、以下の条件が導入されました。
- 接続がTLSで暗号化されていない (
!server.TLS
)。 - かつ、サーバーが
PLAIN
認証をアドバタイズしていない (!advertised
)。
この両方の条件が満たされた場合にのみ、「unencrypted connection」エラーが返されるようになりました。これにより、TLS接続がない場合でも、サーバーが PLAIN
認証を明示的に許可している場合には、クライアントが PLAIN
認証を使用できるようになります。
この変更は、セキュリティを完全に無視するものではなく、サーバーが PLAIN
認証を意図的に許可しているというシグナルを尊重することで、柔軟性を高めるものです。同時に、サーバーが PLAIN
をアドバタイズしていないにもかかわらず、TLSなしで PLAIN
認証を試みるような、潜在的に危険なシナリオは引き続きブロックされます。
また、この変更を検証するために、smtp_test.go
に新しいテストケース TestAuthPlain
が追加されました。このテストは、様々な ServerInfo
の設定(TLSの有無、アドバタイズされる認証メカニズム)に対して PlainAuth
の Start
メソッドが期待通りのエラーを返すか、またはエラーを返さないかを検証します。特に、TLSなしで PLAIN
がアドバタイズされている場合にエラーが発生しないことを確認するテストケースが含まれています。
コアとなるコードの変更箇所
src/pkg/net/smtp/auth.go
--- a/src/pkg/net/smtp/auth.go
+++ b/src/pkg/net/smtp/auth.go
@@ -54,7 +54,16 @@ func PlainAuth(identity, username, password, host string) Auth {
func (a *plainAuth) Start(server *ServerInfo) (string, []byte, error) {
if !server.TLS {
- return "", nil, errors.New("unencrypted connection")
+ advertised := false
+ for _, mechanism := range server.Auth {
+ if mechanism == "PLAIN" {
+ advertised = true
+ break
+ }
+ }
+ if !advertised {
+ return "", nil, errors.New("unencrypted connection")
+ }
}
if server.Name != a.host {
return "", nil, errors.New("wrong host name")
src/pkg/net/smtp/smtp_test.go
--- a/src/pkg/net/smtp/smtp_test.go
+++ b/src/pkg/net/smtp/smtp_test.go
@@ -57,6 +57,41 @@ testLoop:\n }\n }\n \n+func TestAuthPlain(t *testing.T) {
+\tauth := PlainAuth("foo", "bar", "baz", "servername")
+\n+\ttests := []struct {
+\t\tserver *ServerInfo
+\t\terr string
+\t}{\n+\t\t{
+\t\t\tserver: &ServerInfo{Name: "servername", TLS: true},
+\t\t},\n+\t\t{
+\t\t\t// Okay; explicitly advertised by server.
+\t\t\tserver: &ServerInfo{Name: "servername", Auth: []string{"PLAIN"}},
+\t\t},\n+\t\t{
+\t\t\tserver: &ServerInfo{Name: "servername", Auth: []string{"CRAM-MD5"}},
+\t\t\terr: "unencrypted connection",
+\t\t},\n+\t\t{
+\t\t\tserver: &ServerInfo{Name: "attacker", TLS: true},
+\t\t\terr: "wrong host name",
+\t\t},\n+\t}\n+\tfor i, tt := range tests {
+\t\t_, _, err := auth.Start(tt.server)
+\t\tgot := ""
+\t\tif err != nil {
+\t\t\tgot = err.Error()
+\t\t}\n+\t\tif got != tt.err {
+\t\t\tt.Errorf("%d. got error = %q; want %q", i, got, tt.err)
+\t\t}\n+\t}\n+}\n+\n type faker struct {\n \tio.ReadWriter\n }\n```
## コアとなるコードの解説
### `src/pkg/net/smtp/auth.go` の変更
`plainAuth` 型の `Start` メソッドは、SMTPサーバーとの認証プロセスを開始する際に呼び出されます。このメソッドは `ServerInfo` 構造体を受け取り、サーバーの現在の状態(TLS接続の有無、サポートする認証メカニズムなど)を判断します。
変更前は、以下のシンプルな条件がありました。
```go
if !server.TLS {
return "", nil, errors.New("unencrypted connection")
}
これは、TLS接続が確立されていない場合 (server.TLS
が false
)、即座に「unencrypted connection」エラーを返していました。
変更後、このブロックはより複雑になりました。
if !server.TLS { // TLS接続が確立されていない場合
advertised := false
for _, mechanism := range server.Auth { // サーバーがアドバタイズする認証メカニズムをループ
if mechanism == "PLAIN" { // "PLAIN" がリストに含まれているかチェック
advertised = true
break // 見つかったらループを抜ける
}
}
if !advertised { // "PLAIN" がアドバタイズされていない場合
return "", nil, errors.New("unencrypted connection") // エラーを返す
}
}
この新しいロジックは、TLS接続がない場合でも、サーバーが PLAIN
認証をサポートしていると明示的にアドバタイズしているかどうかを確認します。server.Auth
は、サーバーが EHLO
コマンドへの応答で通知した認証メカニズムの文字列スライスです。このスライスの中に "PLAIN"
が含まれていれば advertised
フラグが true
に設定されます。
最終的に、TLS接続がなく、かつ PLAIN
がアドバタイズされていない場合にのみ、「unencrypted connection」エラーが返されるようになりました。これにより、TLSがない場合でもサーバーが PLAIN
を許可しているシナリオで認証が可能になります。
src/pkg/net/smtp/smtp_test.go
の変更
TestAuthPlain
という新しいテスト関数が追加されました。このテストは、PlainAuth
の Start
メソッドが様々なサーバー情報 (ServerInfo
) に対して正しく動作するかを検証します。
テストケースは以下のシナリオをカバーしています。
-
TLS接続があり、サーバー名が一致する場合:
{ server: &ServerInfo{Name: "servername", TLS: true}, },
これはエラーを返すべきではありません。TLSがあるため
PLAIN
認証は安全と見なされます。 -
TLS接続はないが、サーバーが
PLAIN
を明示的にアドバタイズしている場合:{ // Okay; explicitly advertised by server. server: &ServerInfo{Name: "servername", Auth: []string{"PLAIN"}}, },
これがこのコミットの主要なテストケースです。TLSがないにもかかわらず、サーバーが
PLAIN
をアドバタイズしているため、エラーを返すべきではありません。 -
TLS接続はなく、サーバーが
PLAIN
をアドバタイズしていない場合(他の認証メカニズムをアドバタイズしている場合):{ server: &ServerInfo{Name: "servername", Auth: []string{"CRAM-MD5"}}, err: "unencrypted connection", },
この場合、TLSがなく、
PLAIN
もアドバタイズされていないため、「unencrypted connection」エラーを返すべきです。 -
サーバー名が一致しない場合:
{ server: &ServerInfo{Name: "attacker", TLS: true}, err: "wrong host name", },
これはホスト名の不一致によるエラーをテストします。
これらのテストケースを通じて、plainAuth.Start
メソッドが新しいロジックに従って期待通りの挙動を示すことが保証されます。
関連リンク
- Go CL 8279043: https://golang.org/cl/8279043
参考にした情報源リンク
- GitHubコミットページ: https://github.com/golang/go/commit/ca24f9ec00c65f8e75e38ad33d1b0b3bb287e2a3
- SMTP (Simple Mail Transfer Protocol) - Wikipedia: https://ja.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol
- Transport Layer Security (TLS) - Wikipedia: https://ja.wikipedia.org/wiki/Transport_Layer_Security
- SMTP認証 - Wikipedia: https://ja.wikipedia.org/wiki/SMTP%E8%AA%8D%E8%A8%BC
- RFC 4954 - SMTP Service Extension for Authentication: https://datatracker.ietf.org/doc/html/rfc4954 (特に
PLAIN
メカニズムとAUTH
拡張について) - RFC 3207 - SMTP Service Extension for Secure SMTP over TLS: https://datatracker.ietf.org/doc/html/rfc3207 (SMTPにおける
STARTTLS
について)