Module: OpenSSL::PKey

Defined in:
lib/openssl/ssh_pkey.rb

Overview

Enhancements to the core asymmetric key handling.

Constant Summary collapse

SSH_CURVE_NAME_MAP =

A mapping of the “SSH” names for various curves, to their OpenSSL equivalents.

{
  "nistp256" => "prime256v1",
  "nistp384" => "secp384r1",
  "nistp521" => "secp521r1",
}

Class Method Summary collapse

Class Method Details

.from_putty_key(s) { ... } ⇒ OpenSSL::PKey::PKey

Create a new ‘OpenSSL::PKey` from a PuTTY private key.

Given a PuTTY version 2 key file (“PPK”), an equivalent instance of an ‘OpenSSL::PKey::PKey` subclass will be derived representing the same key parameters.

Parameters:

  • s (String)

    the PuTTY PPK file contents to convert.

Yields:

  • if the key data passed is an encrypted private key, the block (if provided) will be called.

Returns:

  • (OpenSSL::PKey::PKey)

    the OpenSSL-compatible key object.



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/openssl/ssh_pkey.rb', line 63

def self.from_putty_key(s, &blk)
  lines = s.gsub("\r\n", "\n").gsub("\r", "\n").split("\n")

  unless lines.shift =~ /\APuTTY-User-Key-File-2: ([a-z0-9-]+)\z/
    raise OpenSSL::PKey::PKeyError,
          "No PuTTY key file header found"
  end

  keytype = $1

  key = case keytype
        when 'ssh-rsa'
          OpenSSL::PKey::RSA.new
        when 'ssh-dss'
          OpenSSL::PKey::DSA.new
        when /ecdsa-sha2-/
          OpenSSL::PKey::EC.new
        else
          raise OpenSSL::PKey::PKeyError,
                "Unknown key type #{keytype}"
        end

  unless lines.shift =~ /\AEncryption: (none|aes256-cbc)\z/
    raise OpenSSL::PKey::PKeyError,
          "Missing or invalid PuTTY Encryption line"
  end

  cipher = $1

  if cipher != "none"
    yield if block_given?
    raise OpenSSL::PKey::PKeyError,
          "Encrypted PuTTY keys are not (yet) supported"
  end

  unless lines.shift =~ /\AComment: /
    raise OpenSSL::PKey::PKeyError,
          "Missing or invalid PuTTY Comment line"
  end

  unless lines.shift =~ /\APublic-Lines: (\d+)\z/
    raise OpenSSL::PKey::PKeyError,
          "Missing or invalid PuTTY Public-Lines line"
  end

  line_count = $1.to_i

  if lines.length < line_count
    raise OpenSSL::PKey::PKeyError,
          "Invalid Public-Lines value, only #{lines.length} lines remaining in file"
  end

  pubkey = lines[0, line_count].join.unpack("m").first

  lines = lines[line_count..-1]

  unless lines.shift =~ /Private-Lines: (\d+)\z/
    raise OpenSSL::PKey::PKeyError,
          "Missing or invalid PuTTY Private-Lines line"
  end

  line_count = $1.to_i

  if lines.length < line_count
    raise OpenSSL::PKey::PKeyError,
          "Invalid Private-Lines value, only #{lines.length} lines remaining in file"
  end

  privkey = lines[0, line_count].join.unpack("m").first

  case key
  when OpenSSL::PKey::RSA
    _kt, e, n        = ssh_key_lv_decode(pubkey,  3).map { |c| ssh_key_mpi_decode(c) }
    d, p, q, iqmp, _ = ssh_key_lv_decode(privkey, 4).map { |c| ssh_key_mpi_decode(c) }

    key.set_key(n, e, d)
    key.set_factors(p, q)
    key.set_crt_params(d % (p - 1), d % (q - 1), iqmp)
  when OpenSSL::PKey::DSA
    _kt, p, q, g, y = ssh_key_lv_decode(pubkey,  5).map { |c| ssh_key_mpi_decode(c) }
    x, _            = ssh_key_lv_decode(privkey, 1).map { |c| ssh_key_mpi_decode(c) }

    key.set_key(y, x)
    key.set_pqg(p, q, g)
  when OpenSSL::PKey::EC
    _kt, curve, w = ssh_key_lv_decode(pubkey, 3)
    p, _          = ssh_key_lv_decode(privkey, 1).map { |c| ssh_key_mpi_decode(c) }

    begin
      key = OpenSSL::PKey::EC.new(SSH_CURVE_NAME_MAP[curve])
    rescue TypeError
      raise OpenSSL::PKey::PKeyError,
            "Unknown curve identifier #{curve}"
    end

    key.public_key  = OpenSSL::PKey::EC::Point.new(key.group, w)
    key.private_key = p
  end

  key
end

.from_ssh_key(s) { ... } ⇒ OpenSSL::PKey::PKey

Create a new ‘OpenSSL::PKey` from an SSH public or private key.

Given an OpenSSH 2 public key (with or without the ‘ssh-rsa` / `ecdsa-etc` prefix), or an encrypted or unencrypted OpenSSH private key, create an equivalent instance of an `OpenSSL::PKey::PKey` subclass which represents the same key parameters.

Parameters:

  • s (String)

    the SSH public or private key to convert. Public keys should be in their usual all-on-one-line bas64-encoded form, with or without the key type prefix. Private keys must have the ‘—–BEGIN/END OPENSSH PRIVATE KEY—–` delimiters.

  • passphrase (String)

    if an encrypted private key is provided, this passphrase will be used to try and decrypt the key. If the passphrase is incorrect, an exception will be raised.

Yields:

  • if the key data passed is an encrypted private key and no passphrase was given, the block (if provided) will be called, and whatever the value of that block call is, it will be used to try and decrypt the private key.

Returns:

  • (OpenSSL::PKey::PKey)

    the OpenSSL-compatible key object.

Raises:

  • (OpenSSL::PKey::PKeyError)

    if anything went wrong with the decoding process.



39
40
41
42
43
44
45
# File 'lib/openssl/ssh_pkey.rb', line 39

def self.from_ssh_key(s, &blk)
  if s =~ /\A-----BEGIN OPENSSH PRIVATE KEY-----/
    decode_private_ssh_key(s, &blk)
  else
    decode_public_ssh_key(s)
  end
end