openssl-additions 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e579a780c0c156cf7e9921d632043963b784ba0fdf5c01ebd877e6ab4f49d4f4
4
- data.tar.gz: b984f65aabc9d6715cb1e9a86b4f8cb18f670990b360bf1f80d7a2ec6b767a71
3
+ metadata.gz: c2919b9f7c9b0a1a2166bcb5bd1e7273f7ac23b9efc05473c2a6ef4647b4b24d
4
+ data.tar.gz: cf8c7640fde294f77a01643a68d4befb4511db17d6f6fa38c37100de04d59c25
5
5
  SHA512:
6
- metadata.gz: 8beaadc77e7a316a32d019c06653122f25c309eced92f2908e2c58a738bdc77d5b91d66659e2ff7e421bf50503cd7027afe35e10c0b48d9d002d5c20bc5c5d16
7
- data.tar.gz: d0b27c304bd94da7c91a124c2dce1815f11354e30753f895872db19a12f73d02c2c572f63b0e250752521db482b803ea545dee4252395b612834c260fbf1cff9
6
+ metadata.gz: 92dbce64ce5e27996a9cbe3208fba4a62e3a36da5a3cf4188942bfe7e77c4e9509f0692b226962341d7e15a7532e48072481115be3b9e502b36c41a7166eba6c
7
+ data.tar.gz: add7918b67e7e5e369a89a0231bb32d775434244d11e0daac325330e4adbdeca9606efec686f308c79a1d38b236775766ca7299a406a8b69a0847acd7a00c4f2
@@ -1,254 +1 @@
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
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.0
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