module PuTTY::Key::OpenSSL::ClassMethods

The {ClassMethods} module is used to extend OpenSSL::PKey when using the PuTTY::Key refinement or calling {PuTTY::Key.global_install}. This adds a from_ppk class method to OpenSSL::PKey.

Public Instance Methods

from_ppk(ppk) click to toggle source

Creates a new OpenSSL::PKey from a PuTTY private key (instance of {PPK}).

This method is called using OpenSSL::PKey.from_ppk(ppk).

PuTTY keys using the algorithms ssh-dss, ssh-rsa, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384 and ecdsa-sha2-nistp521 are supported.

@return [Object] An instance of either OpenSSL::PKey::DSA, OpenSSL::PKey::RSA or OpenSSL::PKey::EC depending on the algorithm of ppk.

@raise [ArgumentError] If ppk is nil. @raise [ArgumentError] If the algorithm of ppk is not supported.

# File lib/putty/key/openssl.rb, line 48
def from_ppk(ppk)
  raise ArgumentError, 'ppk must not be nil' unless ppk

  case ppk.algorithm
  when 'ssh-dss'
    ::OpenSSL::PKey::DSA.new.tap do |pkey|
      _, p, q, g, pub_key = Util.ssh_unpack(ppk.public_blob, :string, :mpint, :mpint, :mpint, :mpint)
      priv_key = Util.ssh_unpack(ppk.private_blob, :mpint).first

      if pkey.respond_to?(:set_key)
        # :nocov_no_openssl_pkey_dsa_set_key:
        pkey.set_key(pub_key, priv_key)
        pkey.set_pqg(p, q, g)
        # :nocov_no_openssl_pkey_dsa_set_key:
      else
        # :nocov_openssl_pkey_dsa_set_key:
        pkey.p, pkey.q, pkey.g, pkey.pub_key, pkey.priv_key = p, q, g, pub_key, priv_key
        # :nocov_openssl_pkey_dsa_set_key:
      end
    end
  when 'ssh-rsa'
    ::OpenSSL::PKey::RSA.new.tap do |pkey|
      _, e, n = Util.ssh_unpack(ppk.public_blob, :string, :mpint, :mpint)
      d, p, q, iqmp = Util.ssh_unpack(ppk.private_blob, :mpint, :mpint, :mpint, :mpint)

      dmp1 = d % (p - 1)
      dmq1 = d % (q - 1)

      if pkey.respond_to?(:set_factors)
        # :nocov_no_openssl_pkey_rsa_set_factors:
        pkey.set_factors(p, q)
        pkey.set_key(n, e, d)
        pkey.set_crt_params(dmp1, dmq1, iqmp)
        # :nocov_no_openssl_pkey_rsa_set_factors:
      else
        # :nocov_openssl_pkey_rsa_set_factors:
        pkey.e, pkey.n, pkey.d, pkey.p, pkey.q, pkey.iqmp, pkey.dmp1, pkey.dmq1 = e, n, d, p, q, iqmp, dmp1, dmq1
        # :nocov_openssl_pkey_rsa_set_factors:
      end
    end
  when /\Aecdsa-sha2-(nistp(?:256|384|521))\z/
    curve = OPENSSL_CURVES[$1]

    # Old versions of jruby-openssl don't include an EC class (version 0.9.16).
    ec_class = (::OpenSSL::PKey::EC rescue raise ArgumentError, "Unsupported algorithm: #{ppk.algorithm}")

    ec_class.new(curve).tap do |pkey|
      _, _, point = Util.ssh_unpack(ppk.public_blob, :string, :string, :mpint)
      group = pkey.group || ::OpenSSL::PKey::EC::Group.new(curve)
      pkey.public_key = ::OpenSSL::PKey::EC::Point.new(group, point)
      pkey.private_key = Util.ssh_unpack(ppk.private_blob, :mpint).first
    end
  else
    raise ArgumentError, "Unsupported algorithm: #{ppk.algorithm}"
  end
end