ツナワタリマイライフ

日常ネタから技術ネタ、音楽ネタまで何でも書きます。

Ruby sshkey gem で OpenSSH 6.8 以降のデフォルトで生成される形式の private key の initialize に失敗する

背景

OpenSSH 6.8 以降と以前では ssh-keygen で生成される private key の形式が変わっている。

haruyama.blog.jp

  • Add FingerprintHash option to ssh(1) and sshd(8), and equivalent command-line flags to the other tools to control algorithm used for key fingerprints. The default changes from MD5 to SHA256 and format from hex to base64.

    FingerprintHash オプションを ssh(1) と sshd(8) に追加する. また, 鍵の指紋のために使われるアルゴリズムを制御する他のツールに 同様の指定をするコマンドラインフラグも追加する. デフォルトが MD5 から SHA256 に, 形式が hex から base64 になる.

問題

社内の内製ツールで、CircleCI の sshkey や環境変数をコード管理して適用するツールがある。そのツールが sshkey gem を使っているが、上記新形式の private key を読み込むときに以下のエラーで失敗する。

irb(main):004:0> f = File.read(File.expand_path("./openssh"))
=> "-----BEGIN OPENSSH PRIVATE KEY-----\nbbbbbbbbb (snip) ssssssssssecret\n-----END OPENSSH PRIVATE KEY-----\n"

irb(main):005:0> k = SSHKey.new(f, comment: "foo@bar.com")
Traceback (most recent call last):
        9: from /Users/chaspy/.rbenv/versions/2.6.5/bin/irb:23:in `<main>'
        8: from /Users/chaspy/.rbenv/versions/2.6.5/bin/irb:23:in `load'
        7: from /Users/chaspy/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
        6: from (irb):5
        5: from (irb):5:in `new'
        4: from /Users/chaspy/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sshkey-1.9.0/lib/sshkey.rb:214:in `initialize'
        3: from /Users/chaspy/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sshkey-1.9.0/lib/sshkey.rb:218:in `rescue in initialize'
        2: from /Users/chaspy/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sshkey-1.9.0/lib/sshkey.rb:218:in `new'
        1: from /Users/chaspy/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/sshkey-1.9.0/lib/sshkey.rb:218:in `initialize'
OpenSSL::PKey::DSAError (Neither PUB key nor PRIV key)

上記 key は以下のコマンドで生成している。

ssh-keygen -t rsa -b 4096

なお、旧形式では成功する。

$ ssh-keygen -t rsa -b 4096 -m pem
irb(main):002:0> f = File.read(File.expand_path("./pem"))
=> "-----BEGIN RSA PRIVATE KEY-----\nbbbbbbb (snip) aaaaaaa\n-----END RSA PRIVATE KEY-----\n"
irb(main):003:0> k = SSHKey.new(f, comment: "foo@bar.com")
=> #<SSHKey:0x00007fa9ef87e6a0 @passphrase=nil, @comment="foo@bar.com", @directives=[], @key_object=#<OpenSSL::PKey::RSA:0x00007fa9ef87e5b0>, @type="rsa">

バージョン

  • Ruby 2.6.5
  • sshkey 1.9.0

調査

sshkey gem

エラーが発生している箇所は以下。

github.com

  # Create a new SSHKey object
  #
  # ==== Parameters
  # * private_key - Existing RSA or DSA private key
  # * options<~Hash>
  #   * :comment<~String> - Comment to use for the public key, defaults to ""
  #   * :passphrase<~String> - If the key is encrypted, supply the passphrase
  #   * :directives<~Array> - Options prefixed to the public key
  #
  def initialize(private_key, options = {})
    @passphrase = options[:passphrase]
    @comment    = options[:comment] || ""
    self.directives = options[:directives] || []
    begin
      @key_object = OpenSSL::PKey::RSA.new(private_key, passphrase)
      @type = "rsa"
    rescue
      @key_object = OpenSSL::PKey::DSA.new(private_key, passphrase)
      @type = "dsa"
    end
  end

最初の begin block でエラーとなり、resque でキャッチして DSA のところでこけてスタックトレースに出ている箇所が表示されているのだと思う。

OpenSSL::PKey::RSA.newRuby 標準ライブラリの openssl である。

github.com

が、issue を見ても似たような現象を見つけられず、そもそも Ruby のどのバージョンが openssl のどのバージョンを使っているかわかっていない。

Ruby の Version を変えてみる

irb(main):001:0> require 'sshkey'
=> true
irb(main):002:0> f = File.read(File.expand_path("./openssh"))
=> "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAgE...
irb(main):003:0> k = SSHKey.new(f, comment: "foo@bar.com")
Traceback (most recent call last):
        9: from /Users/chaspy/.rbenv/versions/2.7.2/bin/irb:23:in `<main>'
        8: from /Users/chaspy/.rbenv/versions/2.7.2/bin/irb:23:in `load'
        7: from /Users/chaspy/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
        6: from (irb):3
        5: from (irb):3:in `new'
        4: from /Users/chaspy/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sshkey-1.9.0/lib/sshkey.rb:214:in `initialize'
        3: from /Users/chaspy/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sshkey-1.9.0/lib/sshkey.rb:218:in `rescue in initialize'
        2: from /Users/chaspy/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sshkey-1.9.0/lib/sshkey.rb:218:in `new'
        1: from /Users/chaspy/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sshkey-1.9.0/lib/sshkey.rb:218:in `initialize'
OpenSSL::PKey::DSAError (Neither PUB key nor PRIV key)

再現する。

  • Ruby 2.7.2 / sshkey 2.0.0
irb(main):001:0> require 'sshkey'
=> true
irb(main):002:0> f = File.read(File.expand_path("./openssh"))
=> "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAgE...
irb(main):003:0> k = SSHKey.new(f, comment: "foo@bar.com")
Traceback (most recent call last):
        9: from /Users/chaspy/.rbenv/versions/2.7.2/bin/irb:23:in `<main>'
        8: from /Users/chaspy/.rbenv/versions/2.7.2/bin/irb:23:in `load'
        7: from /Users/chaspy/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
        6: from (irb):3
        5: from (irb):3:in `new'
        4: from /Users/chaspy/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sshkey-2.0.0/lib/sshkey.rb:246:in `initialize'
        3: from /Users/chaspy/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sshkey-2.0.0/lib/sshkey.rb:250:in `rescue in initialize'
        2: from /Users/chaspy/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sshkey-2.0.0/lib/sshkey.rb:250:in `new'
        1: from /Users/chaspy/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/sshkey-2.0.0/lib/sshkey.rb:250:in `initialize'
OpenSSL::PKey::DSAError (Neither PUB key nor PRIV key)

再現する。

  • Ruby 3.0.0 / sshkey 2.0.0
irb(main):001:0> require 'sshkey'
=> true
irb(main):002:0> f = File.read(File.expand_path("./openssh"))
=> "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAgE...
irb(main):003:0> k = SSHKey.new(f, comment: "foo@bar.com")
Traceback (most recent call last):
        9: from /Users/chaspy/.rbenv/versions/3.0.0/bin/irb:23:in `<main>'
        8: from /Users/chaspy/.rbenv/versions/3.0.0/bin/irb:23:in `load'
        7: from /Users/chaspy/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.0/exe/irb:11:in `<top (required)>'
        6: from (irb):3:in `<main>'
        5: from (irb):3:in `new'
        4: from /Users/chaspy/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/sshkey-2.0.0/lib/sshkey.rb:246:in `initialize'
        3: from /Users/chaspy/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/sshkey-2.0.0/lib/sshkey.rb:250:in `rescue in initialize'
        2: from /Users/chaspy/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/sshkey-2.0.0/lib/sshkey.rb:250:in `new'
        1: from /Users/chaspy/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/sshkey-2.0.0/lib/sshkey.rb:250:in `initialize'
OpenSSL::PKey::DSAError (Neither PUB key nor PRIV key)

再現する。

sshkey に類似 issue があったのでコメントしておいた。

github.com

openssl library

さて最新の Ruby でも再現するので、openssl library の問題の可能性が高い。こっちでも再現させる。

使っているのはこれである。

docs.ruby-lang.org

irb(main):001:0' require 'openssl'
=> true
irb(main):002:0> f = File.read(File.expand_path("./openssh"))
=> "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAgE...
irb(main):003:0> OpenSSL::PKey::RSA.new(f)
Traceback (most recent call last):
        6: from /Users/chaspy/.rbenv/versions/3.0.0/bin/irb:23:in `<main>'
        5: from /Users/chaspy/.rbenv/versions/3.0.0/bin/irb:23:in `load'
        4: from /Users/chaspy/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.0/exe/irb:11:in `<top (required)>'
        3: from (irb):3:in `<main>'
        2: from (irb):3:in `new'
        1: from (irb):3:in `initialize'
OpenSSL::PKey::RSAError (Neither PUB key nor PRIV key: nested asn1 error)

再現する。

さてここからは C の世界である。

トレースが出てないので、推測だが、初期化はここではないか。

github.com

raise されてるところはここではないか。

github.com

ただ、 nested asn1 error というのがどこで出ているのかわかっていない。

repository を検索すると別の部分で同じエラーが出ているようだ。

github.com

ここでお手上げ。

まとめ

  • OpenSSL::PKey::RSA.new に失敗する
    • OpenSSL::PKey::RSAError (Neither PUB key nor PRIV key: nested asn1 error)

  • Ruby Version は 3.0.0
  • OpenSSH 7.8 以降でデフォルトで生成される Key 形式は OpenSSL で読み取ることはできなさそう
    • 試したバージョン: OpenSSH_8.1p1, LibreSSL 2.7.3