Hatena::Grouprubyist

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

Ruby ロゴ (C) Ruby Association LLC

2011年08月31日(水)

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

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

SSH 鍵ファイルのフォーマット

gmrw-ssh2-server ではサーバホスト鍵のフォーマットは OpenSSL コマンドのデフォルトの出力形式にしています。(変換なしで読み込めるため)

しかし、通常使われる OpenSSH の ssh-keygen で作成した公開鍵ファイルのフォーマット*1は以下のようになっています。(~/.ssh/{id_dsa,id_rsa}.pub など)

(DSA の場合)
ssh-dss AACBq3NNHQZWavYnLTpJsZFvwQnWJCKm+RRAAF... foobar@example.org

(RSA の場合)
ssh-rsa AACBq3NNHQZWavYnLTpJsZFvwQnWJCKm+RRAAF... foobar@example.org

1行で1個の鍵なので、鍵が複数個ある場合は複数行になっていると思います。

空白で区切られた 2 番目の文字列が BASE64 でエンコードされた公開鍵です。

(この例では適当な文字列が書かれています。正しくありません)

これらの公開鍵は SSH プロトコルで使用されている以下の形式でエンコーディングされています。

(DSA 鍵の場合)

       [:string, 'ssh-dss' ],
       [ :mpint, p         ],
       [ :mpint, q         ],
       [ :mpint, g         ],
       [ :mpint, pub_key   ]

(RSA 鍵の場合)

       [:string, 'ssh-rsa'],
       [:mpint,  e        ],
       [:mpint,  n        ]

OpenSSL の SSH2 形式の公開鍵の内容を表示するスクリプトを書いてみました。

以下のようにして使用します。(詳しいことはスクリプトの内容を見てください)

  $ print_ssh_foromat_pubkey.rb < ~/.ssh/id_dsa.pub
または
  $ print_ssh_foromat_pubkey.rb < ~/.ssh/id_rsa.pub
#!/usr/bin/env ruby

require 'openssl'

def sep(s, result=[])
  n, s = s.unpack("Na*")
  v, s = s.unpack("a#{n} a*")

  result << v

  s.empty? ? result : sep(s, result)
end

def dec(id, *nums)
  [id] + nums.map {|n| OpenSSL::BN.new(n, 2) }
end

ARGF.each do |line|
  form_id, s, mail = line.split(/\s+/)
  parsed = dec *(sep s.unpack("m")[0])

  case form_id
    when 'ssh-rsa'
      id, e, n = parsed
      puts <<-RSA
id = #{form_id}, mail = #{mail}
  id: #{id}
  e : #{e}
  n : #{n}

RSA

    when 'ssh-dss'
      id, p_, q, g, key = parsed
      puts <<-DSA
id = #{form_id}, mail = #{mail}
  id : #{id}
  p  : #{p_}
  q  : #{q}
  g  : #{g}
  key: #{key}

DSA

    else
      raise "unexpected format"
  end
end

# vim:set ts=2 sw=2 et ai:

DSA 署名フォーマット

DSA 署名は r, s を出力します。SSH プロトコルの署名フォーマットの BLOB には、これらを 各 160 bit (20オクテット)の整数にエンコーディングします。(合計 40オクテット)

dsa_key = OpenSSL::PKey::DSA.new open 'dsa_key.pem'
sig = dsa_key.sign('dss1', data)  # sig は DER 形式の文字列

# SSH プロトコルの署名フォーマット(BLOB)に合うようにエンコーディング
s = OpenSSL::ASN1.decode(sig).value.map {|v| v.value.to_s(2).rjust(20, "\0") }.join

# 通常、ここで署名フォーマットに格納して相手に送信

# エンコードしたものを戻す
sig2 = OpenSSL::ASN1::Sequence.new(s.unpack("a20 a20").map {|v| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(v,2)) }).to_der

sig == sig2 # => true (一致)

# 署名検証
dsa_key.verify('dss1', sig, data)  # => true
dsa_key.verify('dss1', sig2, data) # => true

RSA では sign で出力した s をそのまま署名フォーマットの BLOB として通信します。

s = rsa_key.sign('sha1', data)  # 署名

rsa_key.verify('sha1', s, data) # 検証

SSH プロトコルの署名フォーマット

     [:string, 'ssh-dss'],   # RSA の場合は 'ssh-rsa'
     [:string, s        ]

DSA 署名を ASN.1 記法で表す。(あってるかどうか自信なし)

DSAsignature :: SEQUENCE {
  r INTEGER,
  s INTEGER
}

*1:OpenSSL の SSH2 形式