[インデックス 10362] ファイルの概要
コミット
- コミットハッシュ: 3ee171d174e401950d2d508583d090ee1a79e884
- 作者: Dave Cheney dave@cheney.net
- コミット日時: 2011年11月13日 14:48:22 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3ee171d174e401950d2d508583d090ee1a79e884
元コミット内容
exp/ssh: add client side support for publickey auth
client.go/client_auth.go:
* add support for publickey key auth using the interface
outlined by rsc in the previous auth CL
client_auth_test.go:
* password and publickey tests against server.go
common.go/server.go:
* move some helper methods from server.go into common.go
* generalise serializeRSASignature
R=rsc, agl, huin
CC=cw, golang-dev, n13m3y3r
https://golang.org/cl/5373055
変更の背景
このコミットは、Go言語の実験的なSSHパッケージ (exp/ssh
) において、クライアント側での公開鍵認証のサポートを追加することを目的としています。SSHプロトコルにおける認証メカニズムは多岐にわたりますが、公開鍵認証はパスワード認証に比べてセキュリティが高く、自動化に適しているため、SSHの利用において非常に重要な機能です。
これまでのexp/ssh
パッケージでは、クライアント側での公開鍵認証がサポートされていなかったため、SSHサーバーへの接続時にパスワード認証以外の選択肢が限られていました。この変更により、クライアントは秘密鍵を用いて自身を認証できるようになり、よりセキュアで柔軟なSSH接続が可能になります。
また、関連するヘルパー関数の共通化やテストの追加も行われており、コードベースの品質向上と機能の堅牢化が図られています。
前提知識の解説
SSH (Secure Shell)
SSHは、ネットワークを介してコンピュータを安全に操作するためのプロトコルです。主にリモートログインやファイル転送(SCP, SFTP)に利用されます。SSHは、クライ.アントとサーバー間の通信を暗号化し、盗聴や改ざんを防ぎます。
公開鍵認証 (Public Key Authentication)
公開鍵認証は、SSHで最も一般的に使用される認証方法の一つです。ユーザーは公開鍵と秘密鍵のペアを生成します。
- 公開鍵: サーバーに登録され、誰にでも公開されます。
- 秘密鍵: ユーザーが安全に保管し、誰にも知られないようにします。
認証プロセスでは、クライアントは秘密鍵を使い、サーバーからのチャレンジに署名します。サーバーは、登録されている公開鍵を使ってその署名を検証し、クライアントが正当なユーザーであることを確認します。これにより、パスワードをネットワーク上で送信する必要がなくなり、セキュリティが向上します。
Diffie-Hellman鍵交換 (KEX: Key Exchange)
SSH接続の初期段階で行われる鍵交換プロトコルです。クライアントとサーバーが安全に共有秘密鍵を確立するために使用されます。この共有秘密鍵は、その後の通信の暗号化に使用されるセッション鍵の導出に利用されます。このコミットでは、鍵交換後に認証プロセスが開始されるように変更されています。
Go言語の exp/ssh
パッケージ
exp/ssh
は、Go言語でSSHプロトコルを実装するための実験的なパッケージです。Goの標準ライブラリの一部として提供される前に、新しい機能やプロトコルの側面を試すために使用されます。このパッケージは、SSHクライアントとサーバーの両方を構築するための基本的な構成要素を提供します。
RFC 4252: The Secure Shell (SSH) Authentication Protocol
SSH認証プロトコルを定義する標準ドキュメントです。公開鍵認証を含む様々な認証方法の詳細が記述されています。このコミットの公開鍵認証の実装は、このRFCに準拠しています。
技術的詳細
このコミットにおける公開鍵認証の実装は、RFC 4252のセクション7「Public Key Authentication Method」に厳密に従っています。公開鍵認証は、以下の2段階のプロセスで実行されます。
-
問い合わせフェーズ (Query Phase): クライアントは、自身の秘密鍵に対応する公開鍵をサーバーに提示し、その公開鍵が認証に利用可能であるか(サーバーがその公開鍵を知っているか、または受け入れる準備があるか)を問い合わせます。この段階では署名は行われません。サーバーは
SSH_MSG_USERAUTH_PK_OK
メッセージで応答し、その公開鍵が受け入れ可能であることを示します。 -
認証フェーズ (Authentication Phase): サーバーが公開鍵を受け入れることを確認した後、クライアントはセッションID、ユーザー名、サービス名、認証メソッド、公開鍵、そして特定のデータ(セッションID、ユーザー認証リクエストメッセージ、アルゴリズム名、公開鍵)に対する秘密鍵による署名を含む
SSH_MSG_USERAUTH_REQUEST
メッセージを送信します。サーバーはこの署名を提示された公開鍵で検証し、認証の成否を判断します。
このコミットでは、ClientKeyring
という新しいインターフェースが導入されており、これによりクライアントは複数の秘密鍵を管理し、認証時に適切な鍵を選択できるようになります。ClientKeyring
は、鍵の取得 (Key
) とデータの署名 (Sign
) の機能を提供します。
publickeyAuth
構造体は、このClientKeyring
インターフェースを実装し、公開鍵認証のロジックをカプセル化します。authenticate
メソッドは、まずClientKeyring
から利用可能な鍵を順に問い合わせフェーズでサーバーに提示し、サーバーが受け入れる鍵を特定します。その後、受け入れられた鍵に対して認証フェーズを実行します。
また、署名対象のデータ (buildDataSignedForAuth
) の構築方法や、公開鍵および署名のシリアライズ方法 (serializePublickey
, serializeSignature
) もRFCに準拠して実装されています。これらの関数は、SSHプロトコルで定められた特定のバイナリフォーマットでデータをエンコードするために使用されます。
コアとなるコードの変更箇所
このコミットでは、以下のファイルが変更されています。
-
src/pkg/exp/ssh/client.go
:- クライアント接続のハンドシェイク処理において、認証処理の呼び出し位置が変更されました。以前は
Client
関数内で直接authenticate()
が呼ばれていましたが、handshake()
関数内で鍵交換が完了した後にauthenticate(H)
(セッションIDを渡す)が呼ばれるように修正されました。これにより、認証が鍵交換の後に適切に行われるようになります。
- クライアント接続のハンドシェイク処理において、認証処理の呼び出し位置が変更されました。以前は
-
src/pkg/exp/ssh/client_auth.go
:- 主要な変更点: クライアント側での公開鍵認証をサポートするためのコードが大幅に追加されました。
ClientKeyring
インターフェースが定義され、秘密鍵の管理と署名操作を抽象化します。publickeyAuth
構造体が追加され、ClientKeyring
インターフェースを実装し、公開鍵認証のロジック(問い合わせフェーズと認証フェーズ)をカプセル化します。authenticate
メソッドのシグネチャが変更され、セッションID (session []byte
) を引数として受け取るようになりました。これは、署名対象のデータにセッションIDを含める必要があるためです。ClientAuth
インターフェースのauth
メソッドのシグネチャも変更され、同様にセッションIDを受け取るようになりました。ClientAuthPublickey
関数が追加され、ClientKeyring
の実装を受け取ってClientAuth
インターフェースを返すファクトリ関数として機能します。
-
src/pkg/exp/ssh/client_auth_test.go
:- 新規ファイル: クライアント側の公開鍵認証とパスワード認証のテストが追加されました。
keychain
構造体が定義され、ClientKeyring
インターフェースを実装し、テスト用のRSA秘密鍵を管理します。TestClientAuthPublickey
関数は、公開鍵認証が正しく機能するかを検証します。TestClientAuthPassword
関数は、パスワード認証が正しく機能するかを検証します。TestClientAuthPasswordAndPublickey
関数は、複数の認証方法が設定された場合に、正しい認証方法が選択されるかを検証します。
-
src/pkg/exp/ssh/common.go
:- SSHプロトコルで必要となる共通のヘルパー関数が追加されました。
serializeSignature
: 署名をSSHプロトコル形式でシリアライズします。serializePublickey
: 公開鍵(RSAまたはDSA)をSSHプロトコル形式でシリアライズします。algoName
: 鍵の型に基づいてアルゴリズム名(例: "ssh-rsa", "ssh-dss")を返します。buildDataSignedForAuth
: 認証のために署名されるべきデータをRFC 4252の規定に従って構築します。この関数は以前server.go
にありましたが、共通化のためにこちらに移動されました。
-
src/pkg/exp/ssh/server.go
:serializeRSASignature
関数が、より汎用的なserializeSignature
関数を使用するように変更されました。buildDataSignedForAuth
関数がcommon.go
に移動されたため、このファイルからは削除されました。
コアとなるコードの解説
src/pkg/exp/ssh/client_auth.go
における変更
// ClientKeyring implements access to a client key ring.
type ClientKeyring interface {
// Key returns the i'th rsa.Publickey or dsa.Publickey, or nil if
// no key exists at i.
Key(i int) (key interface{}, err error)
// Sign returns a signature of the given data using the i'th key
// and the supplied random source.
Sign(i int, rand io.Reader, data []byte) (sig []byte, err error)
}
// "publickey" authentication, RFC 4252 Section 7.
type publickeyAuth struct {
ClientKeyring
}
func (p *publickeyAuth) auth(session []byte, user string, t *transport) (bool, []string, error) {
// Authentication is performed in two stages. The first stage sends an
// enquiry to test if each key is acceptable to the remote. The second
// stage attempts to authenticate with the valid keys obtained in the
// first stage.
// ... (問い合わせフェーズのロジック) ...
// ... (認証フェーズのロジック) ...
}
func ClientAuthPublickey(impl ClientKeyring) ClientAuth {
return &publickeyAuth{impl}
}
ClientKeyring
インターフェースは、クライアントが持つ秘密鍵へのアクセスと、それらを使った署名操作を抽象化します。これにより、鍵の管理方法(ファイルシステム、エージェントなど)に依存しない公開鍵認証の実装が可能になります。publickeyAuth
構造体は、ClientKeyring
を埋め込むことでその機能を利用し、公開鍵認証の具体的なロジックを実装します。auth
メソッドは、前述の2段階認証プロセス(問い合わせと認証)を実行します。ClientAuthPublickey
は、ClientKeyring
の実装を受け取り、ClientAuth
インターフェースを返すヘルパー関数です。これにより、クライアント設定に公開鍵認証を追加する際のコードが簡潔になります。
src/pkg/exp/ssh/common.go
における追加
// serialize a signed slice according to RFC 4254 6.6.
func serializeSignature(algoname string, sig []byte) []byte {
// ...
}
// serialize an rsa.PublicKey or dsa.PublicKey according to RFC 4253 6.6.
func serializePublickey(key interface{}) []byte {
// ...
}
func algoName(key interface{}) string {
// ...
}
// buildDataSignedForAuth returns the data that is signed in order to prove
// posession of a private key. See RFC 4252, section 7.
func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
// ...
}
これらの関数は、SSHプロトコルにおける特定のデータ構造(署名、公開鍵、署名対象データ)をバイナリ形式に変換するために使用されます。SSHプロトコルは厳密なバイナリフォーマットを要求するため、これらのヘルパー関数は相互運用性を確保するために不可欠です。特にbuildDataSignedForAuth
は、クライアントが秘密鍵で署名する際に、サーバーとクライアントが同じデータに対して署名を行うことを保証するために重要です。
関連リンク
- Go CL 5373055: https://golang.org/cl/5373055
- RFC 4252: The Secure Shell (SSH) Authentication Protocol: https://tools.ietf.org/html/rfc4252
参考にした情報源リンク
- https://tools.ietf.org/html/rfc4252 (SSH認証プロトコルの詳細)
- https://pkg.go.dev/golang.org/x/crypto/ssh (Go言語のSSHパッケージのドキュメント -
exp/ssh
は現在golang.org/x/crypto/ssh
に統合されています) - https://www.ssh.com/academy/ssh/public-key-authentication (SSH公開鍵認証の概要)