Hatena::Grouprubyist

Going My Ruby Way このページをアンテナに追加 RSSフィード

Ruby ロゴ (C) Ruby Association LLC

2011年08月24日(水)

SSH サーバを作る(その10) トランスポート層プロトコルのおさらい(1)

| 22:17 | SSH サーバを作る(その10) トランスポート層プロトコルのおさらい(1) - Going My Ruby Way を含むブックマーク はてなブックマーク - SSH サーバを作る(その10) トランスポート層プロトコルのおさらい(1) - Going My Ruby Way SSH サーバを作る(その10) トランスポート層プロトコルのおさらい(1) - Going My Ruby Way のブックマークコメント

実装 phase1 もほぼ完了なので、SSH プロトコルの最下層・トランスポート層プロトコルについて、忘れないうちに、まとめていきます。

トランスポート層プロトコルは、主に RFC4253 の範囲になります。

位置プロトコル提供する機能
最下層トランスポート層プロトコル*1気密性(暗号化)、完全性、サーバ認証、圧縮<==ここ
中間層認証プロトコルクライアント認証(ユーザ認証)
最上層コネクションプロトコル多重化チャネル

トランスポート層(SSHプロトコル)の下

TCP です。サーバは通常 22 番ポートを listen します。*2

クライアント-サーバモデルです。*3

プロトコルバージョンの交換

最初はプロトコルバージョンの交換です。

サーバとクライアント双方がバージョン文字列(識別子)を送信し合います。(※送信順序不定)

  <client>                         <server>
    |       <バージョン識別子>       |
    |<==============================>| (※送信順序不定)
    |                                |

バージョン文字列(識別子)は以下のフォーマットです。

SSH-2.0-xxxxxxxxx [SP] yyyyyyyyy [CR][LF]
  • SP は 空白(ASCII 20h)、CR は復帰(ASCII 0Dh)、LF は改行(ASCII 0Ah) です
  • SSH-2.0 は固定です
  • xxxxxxxxx はソフトウェアバージョンです
  • yyyyyyyyy はコメントです。これ(とその前の空白(SP))はオプションです

バージョンがマッチしない場合はコネクションを切断します。

実装では、SSH-2.0 を扱うので(SSH-1.0 には非対応です)、それ以外のバージョンの場合はコネクションを切断して処理を終了させます。*4

プロトコルバージョンの交換以後は、バイナリパケットプロトコルに移行します。

シーケンスナンバー

バイナリパケットプロトコルでは陽に表れないパラメータとしてシーケンスナンバーがあります。

これはパケット送信毎に加算されていく送信方向毎に独立のカウンタです。精度は符号無し32ビット整数で、初期値は 0 です。最大値を超えるとまた 0 に戻ります。

通常、次に紹介する KEXINT がカウント値 0 になります。

アルゴリズムネゴシエーション

鍵交換では最初に KEXINIT(番号=20) をクライアントとサーバ双方が送信し合います(送信順序不定)。

  <client>                         <server>
    |         <KEXINIT(20)>          |
    |<==============================>| (※送信順序不定)
    |                                |

KEXINIT には各項目毎のアルゴリズムのリストが載っています。

フィールド名説明
kex_algorithms KEX(鍵交換)アルゴリズム
server_host_key_algorithms ホスト鍵アルゴリズム
encryption_algorithms_client_to_server 対称暗号アルゴリズム(クライアントtoサーバ)
encryption_algorithms_server_to_client 対称暗号アルゴリズム(サーバtoクライアント)
mac_algorithms_client_to_server MACアルゴリズム(クライアントtoサーバ)
mac_algorithms_server_to_client MACアルゴリズム(サーバtoクライアント)
compression_algorithms_client_to_server圧縮アルゴリズム(クライアントtoサーバ)
compression_algorithms_server_to_client圧縮アルゴリズム(サーバtoクライアント)

暗号、MAC、圧縮のアルゴリズムは送信方向ごとに独立に選択されます。

アルゴリズムは、

  • クライアントKEXINIT のリストの先頭から順に見ていき
  • サーバ KEXINIT のリストに存在するもの

を選択します。

例)
  クライアントKEXINIT   [A, B, C]
  サーバKEXINIT         [X, B, A]

  ... この場合 A が選択される

アルゴリズム名については、RFC4253, RFC4250 などに載っています。

gmrw-ssh2-server で現在サポートするアルゴリズム*5は以下です*6

種類アルゴリズムSSH仕様上の要求OpenSSH の選択*7
KEXdiffie-hellman-group-exchange-sha256
diffie-hellman-group-exchange-sha1
diffie-hellman-group14-sha1REQUIRED
diffie-hellman-group1-sha1REQUIRED
ホスト鍵ssh-dssREQUIRED
ssh-rsaRECOMMENDED
対称暗号aes128-cbcRECOMMENDED
aes256-cbc
aes192-cbc
blowfish-cbc
cast128-cbc
3des-cbcREQUIRED
MAC hmac-sha1REQUIRED
hmac-sha1-96RECOMMENDED
hmac-md5
hmac-md5-96
圧縮noneREQUIRED
zlib

----

KEXINIT には他の項目に、cookie があります。これはランダムバイト列です。

gmrw-ssh2-server では KEXINIT メッセージインスタンスを作る毎にランダムな値を生成しています。

また、languages_client_to_server、languages_server_to_client (言語に関するタグ)、first_kex_packet_follows(後続KEXパケットの有無フラッグ) がありますが、gmrw-ssh2-server では Don't care です。

KEXINIT 以降

KEXINIT の次は、鍵交換アルゴリズムになります。

鍵交換アルゴリズムでのプロトコルはネゴシエートしたアルゴリズムにより決まります。また、そこで使われるメッセージの番号も 30..49 (鍵交換方式ごとに特有(再割り当て可))になります。

鍵交換アルゴリズムについては次回記述します。

鍵交換が終わると NEWKEYS(番号=21) をクライアントとサーバ双方が送信し合います(送信順序不定)。そこでトランスポートプロトコルが終了します。

  <client>                         <server>
    |         <KEXINIT(20)>          |
    |<==============================>| (※送信順序不定)
    |                                |
    |................................|
    |                                |
    |      鍵交換アルゴリズム        |         
    |      (鍵交換法毎に特有)        |
    |................................|
    |                                |
    |         <NEYKEYS(21)>          |
    |<==============================>| (※送信順序不定)
    |                                |
         (暗号、MAC、圧縮の開始)

      (これ以降、ユーザ認証プロトコル)

---

余談。

KEX は Key Exchange のことです。(多分)

SSHサーバを作る(その9) Ruby1.8 で動作させる

| 19:13 | SSHサーバを作る(その9) Ruby1.8 で動作させる - Going My Ruby Way を含むブックマーク はてなブックマーク - SSHサーバを作る(その9) Ruby1.8 で動作させる - Going My Ruby Way SSHサーバを作る(その9) Ruby1.8 で動作させる - Going My Ruby Way のブックマークコメント

トランスポート層の実装もほぼ終わり、そろそろ phase1 ブランチを master にマージしようかと思ってるところです。(まだ先は長いです)

マージ前の総括として、Ruby1.8 で動くか試したところ、define_singleton_method は 1.8 にない、とエラーになりました。

その修正方法のメモです。

(修正前) # Ruby1.9 では OK
  xxx.define_singleton_method(:foo) { ...... }

(修正後) # Ruby1.8 でも OK
  c = class << xxx ; self ; end
  c.send(:define_method, :foo) { ......}

 または

  m = Module.new { define_method(:foo) { ...... } }
  c.extend m

以下の方法でも特異メソッドは定義できます。

ただし、classdef の外のスコープのオブジェクトを参照したい場合は上のようにします。

 a = 10
 class << xxx
   def foo
     a        # <= a はスコープの外なので...
   end
 end

 xxx.foo      # <= NameError: undefined local variable or method `a' for ...
  a = 10
  c = class << xxx ; self ; end       # xxx の特異クラスを c に取得
  c.send(:define_method, :foo) { a }  # define_method は private なので send を使う 
  
  xxx.foo  # => 10
  b = 20
  m = Module.new { define_method(:bar) { b } }
  xxx.extend m

  xxx.bar  # => 20

-----

Ruby1.8 と Ruby1.9 では、Hash を "#{hash}" した時の表示が異なります。

# Ruby1.9
>> h = {:a=>10,:b=>20}
=> {:a=>10, :b=>20}
>> "#{h}"
=> "{:a=>10, :b=>20}"

# Ruby1.8
>> h = {:a=>10,:b=>20}
=> {:a=>10, :b=>20}
>> "#{h}"
=> "a10b20"

この辺りを修正したら、Ruby1.8 でも動作しました。

現在、ユーザ認証プロトコルが開始する辺りまで実装済みです。

----

あと、Active Support はあまり使わないので基本的に使用しないことにしました。

一部だけ extension(標準クラスの拡張)に残し、代替ライブラリも削除しました。(スッキリ)


*1OSI 7階層モデルでいうトランスポート層ではありません

*2:gmrw-ssh2-server ではデフォルトで 50022 番ポートを listen します

*3:gmrw-ssh2-server は、標準ライブラリの gserver のサービスとして実装されてます。サービスのインスタンスは 1 つの確立コネクションに対して 1 つです

*4RFC には、古いバージョン(SSH-1.0)との互換性や、バージョン文字列以外の情報の送信方法などが記載されていますが、実装には関係ないので反映させてません

*5:環境にインストールされる OpenSSL、Zlib、および関連ライブラリ依存になります

*6SSH仕様上のREQUIRED,RECOMMENDED はすべて含みます

*7:OpenSSH_5.8p1 Debian-1ubuntu3 クライアントがデフォルトで選択するもの