openssl-additions 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/openssl/pkey.rb +1 -254
- data/lib/openssl/ssh_pkey.rb +254 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c2919b9f7c9b0a1a2166bcb5bd1e7273f7ac23b9efc05473c2a6ef4647b4b24d
|
4
|
+
data.tar.gz: cf8c7640fde294f77a01643a68d4befb4511db17d6f6fa38c37100de04d59c25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 92dbce64ce5e27996a9cbe3208fba4a62e3a36da5a3cf4188942bfe7e77c4e9509f0692b226962341d7e15a7532e48072481115be3b9e502b36c41a7166eba6c
|
7
|
+
data.tar.gz: add7918b67e7e5e369a89a0231bb32d775434244d11e0daac325330e4adbdeca9606efec686f308c79a1d38b236775766ca7299a406a8b69a0847acd7a00c4f2
|
data/lib/openssl/pkey.rb
CHANGED
@@ -1,254 +1 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# Enhancements to the core asymmetric key handling.
|
4
|
-
module OpenSSL::PKey
|
5
|
-
# A mapping of the "SSH" names for various curves, to their OpenSSL
|
6
|
-
# equivalents.
|
7
|
-
SSH_CURVE_NAME_MAP = {
|
8
|
-
"nistp256" => "prime256v1",
|
9
|
-
"nistp384" => "secp384r1",
|
10
|
-
"nistp521" => "secp521r1",
|
11
|
-
}
|
12
|
-
|
13
|
-
# Create a new `OpenSSL::PKey` from an SSH public or private key.
|
14
|
-
#
|
15
|
-
# Given an OpenSSH 2 public key (with or without the `ssh-rsa` / `ecdsa-etc`
|
16
|
-
# prefix), or an encrypted or unencrypted OpenSSH private key, create an
|
17
|
-
# equivalent instance of an `OpenSSL::PKey::PKey` subclass which represents
|
18
|
-
# the same key parameters.
|
19
|
-
#
|
20
|
-
# @param s [String] the SSH public or private key to convert. Public keys
|
21
|
-
# should be in their usual all-on-one-line bas64-encoded form, with or
|
22
|
-
# without the key type prefix. Private keys must have the `-----BEGIN/END
|
23
|
-
# OPENSSH PRIVATE KEY-----` delimiters.
|
24
|
-
#
|
25
|
-
# @param passphrase [String] if an encrypted private key is provided, this
|
26
|
-
# passphrase will be used to try and decrypt the key. If the passphrase
|
27
|
-
# is incorrect, an exception will be raised.
|
28
|
-
#
|
29
|
-
# @yield if the key data passed is an encrypted private key and no passphrase
|
30
|
-
# was given, the block (if provided) will be called, and whatever the value
|
31
|
-
# of that block call is, it will be used to try and decrypt the private
|
32
|
-
# key.
|
33
|
-
#
|
34
|
-
# @return [OpenSSL::PKey::PKey] the OpenSSL-compatible key object.
|
35
|
-
#
|
36
|
-
# @raise [OpenSSL::PKey::PKeyError] if anything went wrong with the decoding
|
37
|
-
# process.
|
38
|
-
#
|
39
|
-
def self.from_ssh_key(s, &blk)
|
40
|
-
if s =~ /\A-----BEGIN OPENSSH PRIVATE KEY-----/
|
41
|
-
decode_private_ssh_key(s, &blk)
|
42
|
-
else
|
43
|
-
decode_public_ssh_key(s)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def self.decode_private_ssh_key(s, &blk)
|
50
|
-
unless s =~ /-----BEGIN OPENSSH PRIVATE KEY-----\n([A-Za-z0-9\/+\n]*={0,2})\n-----END OPENSSH PRIVATE KEY-----/m
|
51
|
-
raise OpenSSL::PKey::PKeyError,
|
52
|
-
"invalid OpenSSH private key format"
|
53
|
-
end
|
54
|
-
|
55
|
-
keyblob = unpack_private_ssh_key($1, &blk)
|
56
|
-
keytype, rest = ssh_key_lv_decode(keyblob, 1)
|
57
|
-
|
58
|
-
case keytype
|
59
|
-
when "ssh-rsa"
|
60
|
-
parts = ssh_key_lv_decode(rest, 6)
|
61
|
-
OpenSSL::PKey::RSA.new.tap do |k|
|
62
|
-
k.n = ssh_key_mpi_decode(parts[0])
|
63
|
-
k.e = ssh_key_mpi_decode(parts[1])
|
64
|
-
k.d = ssh_key_mpi_decode(parts[2])
|
65
|
-
k.iqmp = ssh_key_mpi_decode(parts[3])
|
66
|
-
k.p = ssh_key_mpi_decode(parts[4])
|
67
|
-
k.q = ssh_key_mpi_decode(parts[5])
|
68
|
-
end
|
69
|
-
when "ssh-dss"
|
70
|
-
parts = ssh_key_lv_decode(rest, 5)
|
71
|
-
OpenSSL::PKey::DSA.new.tap do |k|
|
72
|
-
k.p = ssh_key_mpi_decode(parts[0])
|
73
|
-
k.q = ssh_key_mpi_decode(parts[1])
|
74
|
-
k.g = ssh_key_mpi_decode(parts[2])
|
75
|
-
k.pub_key = ssh_key_mpi_decode(parts[3])
|
76
|
-
k.priv_key = ssh_key_mpi_decode(parts[4])
|
77
|
-
end
|
78
|
-
when /ecdsa-sha2-/
|
79
|
-
parts = ssh_key_lv_decode(rest, 3)
|
80
|
-
|
81
|
-
begin
|
82
|
-
OpenSSL::PKey::EC.new(SSH_CURVE_NAME_MAP[parts[0]]).tap do |k|
|
83
|
-
k.public_key = OpenSSL::PKey::EC::Point.new(k.group, parts[1])
|
84
|
-
k.private_key = ssh_key_mpi_decode(parts[2])
|
85
|
-
end
|
86
|
-
rescue TypeError
|
87
|
-
raise OpenSSL::PKey::PKeyError.new,
|
88
|
-
"Unknown curve identifier #{parts[0]}"
|
89
|
-
end
|
90
|
-
else
|
91
|
-
raise OpenSSL::PKey::PKeyError,
|
92
|
-
"Unknown key type #{keytype}"
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def self.unpack_private_ssh_key(s)
|
97
|
-
rest = s.unpack("m").first
|
98
|
-
|
99
|
-
# From https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
|
100
|
-
|
101
|
-
# 8: #define AUTH_MAGIC "openssh-key-v1"
|
102
|
-
# 9:
|
103
|
-
# 10: byte[] AUTH_MAGIC
|
104
|
-
unless rest[0, 15] == "openssh-key-v1\0"
|
105
|
-
raise OpenSSL::PKey::PKeyError,
|
106
|
-
"Invalid OpenSSH private key: incorrect magic found (#{rest[0, 15].inspect})"
|
107
|
-
end
|
108
|
-
|
109
|
-
# 11: string ciphername
|
110
|
-
# 12: string kdfname
|
111
|
-
# 13: string kdfoptions
|
112
|
-
cipher, kdf, kdfopts, rest = ssh_key_lv_decode(rest[15..], 3)
|
113
|
-
|
114
|
-
# 14: int number of keys N
|
115
|
-
key_count, rest = rest.unpack("Na*")
|
116
|
-
if key_count == 0
|
117
|
-
raise OpenSSL::PKey::PKeyError,
|
118
|
-
"invalid OpenSSH private key: no keys!"
|
119
|
-
elsif key_count > 1
|
120
|
-
raise OpenSSL::PKey::PKeyError,
|
121
|
-
"unsupported OpenSSH private key: multiple keys"
|
122
|
-
end
|
123
|
-
|
124
|
-
# 15: string publickey1
|
125
|
-
# We care not for your stinky public key
|
126
|
-
_, rest = ssh_key_lv_decode(rest, 1)
|
127
|
-
|
128
|
-
# 19: string encrypted, padded list of private keys
|
129
|
-
rest, x = ssh_key_lv_decode(rest, 1)
|
130
|
-
unless x.nil?
|
131
|
-
#:nocov:
|
132
|
-
raise OpenSSL::PKey::PKeyError,
|
133
|
-
"invalid OpenSSH private key: trailing garbage after private key blob: #{x.inspect}"
|
134
|
-
#:nocov:
|
135
|
-
end
|
136
|
-
|
137
|
-
if kdf == "none"
|
138
|
-
# This one is easy
|
139
|
-
# 36: uint32 checkint
|
140
|
-
# 37: uint32 checkint
|
141
|
-
# 38: string privatekey1
|
142
|
-
check1, check2, rest = rest.unpack("NNa*")
|
143
|
-
unless check1 == check2
|
144
|
-
raise OpenSSL::PKey::PKeyError,
|
145
|
-
"invalid OpenSSH private key: check values don't match"
|
146
|
-
end
|
147
|
-
|
148
|
-
# The format spec says that the keyblob is, itself, a string, but that's
|
149
|
-
# not what I'm seeing in real-world keys generated by OpenSSH -- the
|
150
|
-
# first element of the keyblob (the key type string) is just straight up
|
151
|
-
# there after the check digits. That must make parsing out multiple
|
152
|
-
# private keys an absolute nightmare -- except, oh wait (from
|
153
|
-
# openssh-portable.git/sshkey.c):
|
154
|
-
#
|
155
|
-
# if (nkeys != 1) {
|
156
|
-
# /* XXX only one key supported */
|
157
|
-
# r = SSH_ERR_INVALID_FORMAT;
|
158
|
-
# goto out;
|
159
|
-
# }
|
160
|
-
#
|
161
|
-
# Cheaters.
|
162
|
-
#
|
163
|
-
# At any rate, the fact that the spec isn't what's implemented means that
|
164
|
-
# the keyblob I do send back needs to be carefully parsed itself, rather
|
165
|
-
# than just being able to blat it through ssh_key_lv_decode to get all
|
166
|
-
# the bits. Sigh.
|
167
|
-
return rest
|
168
|
-
elsif kdf == "bcrypt"
|
169
|
-
# This is cheating a little bit, but I'm not up for implementing
|
170
|
-
# decryption support today, and this at least allows us to reliably
|
171
|
-
# detect that the key *is* in fact encrypted rather than corrupted
|
172
|
-
yield if block_given?
|
173
|
-
raise OpenSSL::PKey::PKeyError,
|
174
|
-
"unsupported OpenSSH private key: decryption is not (yet) supported"
|
175
|
-
else
|
176
|
-
raise OpenSSL::PKey::PKeyError,
|
177
|
-
"unsupport OpenSSH private key KDF #{kdf.inspect}"
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
def self.decode_public_ssh_key(s)
|
182
|
-
if s =~ /\Assh-[a-z0-9-]+ /
|
183
|
-
# WHOOP WHOOP prefixed key detected.
|
184
|
-
s = s.split(" ")[1]
|
185
|
-
else
|
186
|
-
# Discard any comment, etc that might be lurking around
|
187
|
-
s = s.split(" ")[0]
|
188
|
-
end
|
189
|
-
|
190
|
-
unless s =~ /\A[A-Za-z0-9\/+]+={0,2}\z/
|
191
|
-
raise OpenSSL::PKey::PKeyError,
|
192
|
-
"Invalid key encoding (not valid base64)"
|
193
|
-
end
|
194
|
-
|
195
|
-
parts = ssh_key_lv_decode(s.unpack("m").first)
|
196
|
-
|
197
|
-
case parts.first
|
198
|
-
when "ssh-rsa"
|
199
|
-
OpenSSL::PKey::RSA.new.tap do |k|
|
200
|
-
k.e = ssh_key_mpi_decode(parts[1])
|
201
|
-
k.n = ssh_key_mpi_decode(parts[2])
|
202
|
-
end
|
203
|
-
when "ssh-dss"
|
204
|
-
OpenSSL::PKey::DSA.new.tap do |k|
|
205
|
-
k.p = ssh_key_mpi_decode(parts[1])
|
206
|
-
k.q = ssh_key_mpi_decode(parts[2])
|
207
|
-
k.g = ssh_key_mpi_decode(parts[3])
|
208
|
-
end
|
209
|
-
when /ecdsa-sha2-/
|
210
|
-
begin
|
211
|
-
OpenSSL::PKey::EC.new(SSH_CURVE_NAME_MAP[parts[1]]).tap do |k|
|
212
|
-
k.public_key = OpenSSL::PKey::EC::Point.new(k.group, parts[2])
|
213
|
-
end
|
214
|
-
rescue TypeError
|
215
|
-
raise OpenSSL::PKey::PKeyError.new,
|
216
|
-
"Unknown curve identifier #{parts[1]}"
|
217
|
-
end
|
218
|
-
else
|
219
|
-
raise OpenSSL::PKey::PKeyError,
|
220
|
-
"Unknown key type #{parts.first.inspect}"
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
# Take the base64 string and split it into its component parts.
|
225
|
-
#
|
226
|
-
def self.ssh_key_lv_decode(s, n = nil)
|
227
|
-
rest = s
|
228
|
-
|
229
|
-
[].tap do |parts|
|
230
|
-
until rest == "" || (n && n <= 0)
|
231
|
-
len, rest = rest.unpack("Na*")
|
232
|
-
if len > rest.length
|
233
|
-
raise OpenSSL::PKey::PKeyError,
|
234
|
-
"Invalid LV-encoded string; wanted #{len} octets, but there's only #{rest.length} octets left"
|
235
|
-
end
|
236
|
-
|
237
|
-
elem, rest = rest.unpack("a#{len}a*")
|
238
|
-
parts << elem
|
239
|
-
n -= 1 if n
|
240
|
-
end
|
241
|
-
|
242
|
-
if rest != ""
|
243
|
-
parts << rest
|
244
|
-
end
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
# Turn an SSH "MPI" (encoded arbitrary-length integer) string into a real
|
249
|
-
# Ruby integer.
|
250
|
-
#
|
251
|
-
def self.ssh_key_mpi_decode(s)
|
252
|
-
s.each_char.inject(0) { |i, c| i * 256 + c.ord }
|
253
|
-
end
|
254
|
-
end
|
1
|
+
require_relative './ssh_pkey'
|
@@ -0,0 +1,254 @@
|
|
1
|
+
require "openssl"
|
2
|
+
|
3
|
+
# Enhancements to the core asymmetric key handling.
|
4
|
+
module OpenSSL::PKey
|
5
|
+
# A mapping of the "SSH" names for various curves, to their OpenSSL
|
6
|
+
# equivalents.
|
7
|
+
SSH_CURVE_NAME_MAP = {
|
8
|
+
"nistp256" => "prime256v1",
|
9
|
+
"nistp384" => "secp384r1",
|
10
|
+
"nistp521" => "secp521r1",
|
11
|
+
}
|
12
|
+
|
13
|
+
# Create a new `OpenSSL::PKey` from an SSH public or private key.
|
14
|
+
#
|
15
|
+
# Given an OpenSSH 2 public key (with or without the `ssh-rsa` / `ecdsa-etc`
|
16
|
+
# prefix), or an encrypted or unencrypted OpenSSH private key, create an
|
17
|
+
# equivalent instance of an `OpenSSL::PKey::PKey` subclass which represents
|
18
|
+
# the same key parameters.
|
19
|
+
#
|
20
|
+
# @param s [String] the SSH public or private key to convert. Public keys
|
21
|
+
# should be in their usual all-on-one-line bas64-encoded form, with or
|
22
|
+
# without the key type prefix. Private keys must have the `-----BEGIN/END
|
23
|
+
# OPENSSH PRIVATE KEY-----` delimiters.
|
24
|
+
#
|
25
|
+
# @param passphrase [String] if an encrypted private key is provided, this
|
26
|
+
# passphrase will be used to try and decrypt the key. If the passphrase
|
27
|
+
# is incorrect, an exception will be raised.
|
28
|
+
#
|
29
|
+
# @yield if the key data passed is an encrypted private key and no passphrase
|
30
|
+
# was given, the block (if provided) will be called, and whatever the value
|
31
|
+
# of that block call is, it will be used to try and decrypt the private
|
32
|
+
# key.
|
33
|
+
#
|
34
|
+
# @return [OpenSSL::PKey::PKey] the OpenSSL-compatible key object.
|
35
|
+
#
|
36
|
+
# @raise [OpenSSL::PKey::PKeyError] if anything went wrong with the decoding
|
37
|
+
# process.
|
38
|
+
#
|
39
|
+
def self.from_ssh_key(s, &blk)
|
40
|
+
if s =~ /\A-----BEGIN OPENSSH PRIVATE KEY-----/
|
41
|
+
decode_private_ssh_key(s, &blk)
|
42
|
+
else
|
43
|
+
decode_public_ssh_key(s)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def self.decode_private_ssh_key(s, &blk)
|
50
|
+
unless s =~ /-----BEGIN OPENSSH PRIVATE KEY-----\n([A-Za-z0-9\/+\n]*={0,2})\n-----END OPENSSH PRIVATE KEY-----/m
|
51
|
+
raise OpenSSL::PKey::PKeyError,
|
52
|
+
"invalid OpenSSH private key format"
|
53
|
+
end
|
54
|
+
|
55
|
+
keyblob = unpack_private_ssh_key($1, &blk)
|
56
|
+
keytype, rest = ssh_key_lv_decode(keyblob, 1)
|
57
|
+
|
58
|
+
case keytype
|
59
|
+
when "ssh-rsa"
|
60
|
+
parts = ssh_key_lv_decode(rest, 6)
|
61
|
+
OpenSSL::PKey::RSA.new.tap do |k|
|
62
|
+
k.n = ssh_key_mpi_decode(parts[0])
|
63
|
+
k.e = ssh_key_mpi_decode(parts[1])
|
64
|
+
k.d = ssh_key_mpi_decode(parts[2])
|
65
|
+
k.iqmp = ssh_key_mpi_decode(parts[3])
|
66
|
+
k.p = ssh_key_mpi_decode(parts[4])
|
67
|
+
k.q = ssh_key_mpi_decode(parts[5])
|
68
|
+
end
|
69
|
+
when "ssh-dss"
|
70
|
+
parts = ssh_key_lv_decode(rest, 5)
|
71
|
+
OpenSSL::PKey::DSA.new.tap do |k|
|
72
|
+
k.p = ssh_key_mpi_decode(parts[0])
|
73
|
+
k.q = ssh_key_mpi_decode(parts[1])
|
74
|
+
k.g = ssh_key_mpi_decode(parts[2])
|
75
|
+
k.pub_key = ssh_key_mpi_decode(parts[3])
|
76
|
+
k.priv_key = ssh_key_mpi_decode(parts[4])
|
77
|
+
end
|
78
|
+
when /ecdsa-sha2-/
|
79
|
+
parts = ssh_key_lv_decode(rest, 3)
|
80
|
+
|
81
|
+
begin
|
82
|
+
OpenSSL::PKey::EC.new(SSH_CURVE_NAME_MAP[parts[0]]).tap do |k|
|
83
|
+
k.public_key = OpenSSL::PKey::EC::Point.new(k.group, parts[1])
|
84
|
+
k.private_key = ssh_key_mpi_decode(parts[2])
|
85
|
+
end
|
86
|
+
rescue TypeError
|
87
|
+
raise OpenSSL::PKey::PKeyError.new,
|
88
|
+
"Unknown curve identifier #{parts[0]}"
|
89
|
+
end
|
90
|
+
else
|
91
|
+
raise OpenSSL::PKey::PKeyError,
|
92
|
+
"Unknown key type #{keytype}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.unpack_private_ssh_key(s)
|
97
|
+
rest = s.unpack("m").first
|
98
|
+
|
99
|
+
# From https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
|
100
|
+
|
101
|
+
# 8: #define AUTH_MAGIC "openssh-key-v1"
|
102
|
+
# 9:
|
103
|
+
# 10: byte[] AUTH_MAGIC
|
104
|
+
unless rest[0, 15] == "openssh-key-v1\0"
|
105
|
+
raise OpenSSL::PKey::PKeyError,
|
106
|
+
"Invalid OpenSSH private key: incorrect magic found (#{rest[0, 15].inspect})"
|
107
|
+
end
|
108
|
+
|
109
|
+
# 11: string ciphername
|
110
|
+
# 12: string kdfname
|
111
|
+
# 13: string kdfoptions
|
112
|
+
cipher, kdf, kdfopts, rest = ssh_key_lv_decode(rest[15..], 3)
|
113
|
+
|
114
|
+
# 14: int number of keys N
|
115
|
+
key_count, rest = rest.unpack("Na*")
|
116
|
+
if key_count == 0
|
117
|
+
raise OpenSSL::PKey::PKeyError,
|
118
|
+
"invalid OpenSSH private key: no keys!"
|
119
|
+
elsif key_count > 1
|
120
|
+
raise OpenSSL::PKey::PKeyError,
|
121
|
+
"unsupported OpenSSH private key: multiple keys"
|
122
|
+
end
|
123
|
+
|
124
|
+
# 15: string publickey1
|
125
|
+
# We care not for your stinky public key
|
126
|
+
_, rest = ssh_key_lv_decode(rest, 1)
|
127
|
+
|
128
|
+
# 19: string encrypted, padded list of private keys
|
129
|
+
rest, x = ssh_key_lv_decode(rest, 1)
|
130
|
+
unless x.nil?
|
131
|
+
#:nocov:
|
132
|
+
raise OpenSSL::PKey::PKeyError,
|
133
|
+
"invalid OpenSSH private key: trailing garbage after private key blob: #{x.inspect}"
|
134
|
+
#:nocov:
|
135
|
+
end
|
136
|
+
|
137
|
+
if kdf == "none"
|
138
|
+
# This one is easy
|
139
|
+
# 36: uint32 checkint
|
140
|
+
# 37: uint32 checkint
|
141
|
+
# 38: string privatekey1
|
142
|
+
check1, check2, rest = rest.unpack("NNa*")
|
143
|
+
unless check1 == check2
|
144
|
+
raise OpenSSL::PKey::PKeyError,
|
145
|
+
"invalid OpenSSH private key: check values don't match"
|
146
|
+
end
|
147
|
+
|
148
|
+
# The format spec says that the keyblob is, itself, a string, but that's
|
149
|
+
# not what I'm seeing in real-world keys generated by OpenSSH -- the
|
150
|
+
# first element of the keyblob (the key type string) is just straight up
|
151
|
+
# there after the check digits. That must make parsing out multiple
|
152
|
+
# private keys an absolute nightmare -- except, oh wait (from
|
153
|
+
# openssh-portable.git/sshkey.c):
|
154
|
+
#
|
155
|
+
# if (nkeys != 1) {
|
156
|
+
# /* XXX only one key supported */
|
157
|
+
# r = SSH_ERR_INVALID_FORMAT;
|
158
|
+
# goto out;
|
159
|
+
# }
|
160
|
+
#
|
161
|
+
# Cheaters.
|
162
|
+
#
|
163
|
+
# At any rate, the fact that the spec isn't what's implemented means that
|
164
|
+
# the keyblob I do send back needs to be carefully parsed itself, rather
|
165
|
+
# than just being able to blat it through ssh_key_lv_decode to get all
|
166
|
+
# the bits. Sigh.
|
167
|
+
return rest
|
168
|
+
elsif kdf == "bcrypt"
|
169
|
+
# This is cheating a little bit, but I'm not up for implementing
|
170
|
+
# decryption support today, and this at least allows us to reliably
|
171
|
+
# detect that the key *is* in fact encrypted rather than corrupted
|
172
|
+
yield if block_given?
|
173
|
+
raise OpenSSL::PKey::PKeyError,
|
174
|
+
"unsupported OpenSSH private key: decryption is not (yet) supported"
|
175
|
+
else
|
176
|
+
raise OpenSSL::PKey::PKeyError,
|
177
|
+
"unsupport OpenSSH private key KDF #{kdf.inspect}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.decode_public_ssh_key(s)
|
182
|
+
if s =~ /\Assh-[a-z0-9-]+ /
|
183
|
+
# WHOOP WHOOP prefixed key detected.
|
184
|
+
s = s.split(" ")[1]
|
185
|
+
else
|
186
|
+
# Discard any comment, etc that might be lurking around
|
187
|
+
s = s.split(" ")[0]
|
188
|
+
end
|
189
|
+
|
190
|
+
unless s =~ /\A[A-Za-z0-9\/+]+={0,2}\z/
|
191
|
+
raise OpenSSL::PKey::PKeyError,
|
192
|
+
"Invalid key encoding (not valid base64)"
|
193
|
+
end
|
194
|
+
|
195
|
+
parts = ssh_key_lv_decode(s.unpack("m").first)
|
196
|
+
|
197
|
+
case parts.first
|
198
|
+
when "ssh-rsa"
|
199
|
+
OpenSSL::PKey::RSA.new.tap do |k|
|
200
|
+
k.e = ssh_key_mpi_decode(parts[1])
|
201
|
+
k.n = ssh_key_mpi_decode(parts[2])
|
202
|
+
end
|
203
|
+
when "ssh-dss"
|
204
|
+
OpenSSL::PKey::DSA.new.tap do |k|
|
205
|
+
k.p = ssh_key_mpi_decode(parts[1])
|
206
|
+
k.q = ssh_key_mpi_decode(parts[2])
|
207
|
+
k.g = ssh_key_mpi_decode(parts[3])
|
208
|
+
end
|
209
|
+
when /ecdsa-sha2-/
|
210
|
+
begin
|
211
|
+
OpenSSL::PKey::EC.new(SSH_CURVE_NAME_MAP[parts[1]]).tap do |k|
|
212
|
+
k.public_key = OpenSSL::PKey::EC::Point.new(k.group, parts[2])
|
213
|
+
end
|
214
|
+
rescue TypeError
|
215
|
+
raise OpenSSL::PKey::PKeyError.new,
|
216
|
+
"Unknown curve identifier #{parts[1]}"
|
217
|
+
end
|
218
|
+
else
|
219
|
+
raise OpenSSL::PKey::PKeyError,
|
220
|
+
"Unknown key type #{parts.first.inspect}"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Take the base64 string and split it into its component parts.
|
225
|
+
#
|
226
|
+
def self.ssh_key_lv_decode(s, n = nil)
|
227
|
+
rest = s
|
228
|
+
|
229
|
+
[].tap do |parts|
|
230
|
+
until rest == "" || (n && n <= 0)
|
231
|
+
len, rest = rest.unpack("Na*")
|
232
|
+
if len > rest.length
|
233
|
+
raise OpenSSL::PKey::PKeyError,
|
234
|
+
"Invalid LV-encoded string; wanted #{len} octets, but there's only #{rest.length} octets left"
|
235
|
+
end
|
236
|
+
|
237
|
+
elem, rest = rest.unpack("a#{len}a*")
|
238
|
+
parts << elem
|
239
|
+
n -= 1 if n
|
240
|
+
end
|
241
|
+
|
242
|
+
if rest != ""
|
243
|
+
parts << rest
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Turn an SSH "MPI" (encoded arbitrary-length integer) string into a real
|
249
|
+
# Ruby integer.
|
250
|
+
#
|
251
|
+
def self.ssh_key_mpi_decode(s)
|
252
|
+
s.each_char.inject(0) { |i, c| i * 256 + c.ord }
|
253
|
+
end
|
254
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openssl-additions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Palmer
|
@@ -167,6 +167,7 @@ files:
|
|
167
167
|
- lib/openssl/pkey.rb
|
168
168
|
- lib/openssl/pkey/ec.rb
|
169
169
|
- lib/openssl/pkey/rsa.rb
|
170
|
+
- lib/openssl/ssh_pkey.rb
|
170
171
|
- lib/openssl/x509/certificate.rb
|
171
172
|
- lib/openssl/x509/request.rb
|
172
173
|
- lib/openssl/x509/spki.rb
|