sshkey 2.0.0 → 3.0.0
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 +5 -5
- data/.github/workflows/ci.yml +25 -0
- data/LICENSE +1 -1
- data/README.md +7 -9
- data/lib/sshkey/version.rb +1 -1
- data/lib/sshkey.rb +284 -39
- data/sshkey.gemspec +4 -1
- data/test/sshkey_test.rb +283 -2
- metadata +8 -9
- data/.travis.yml +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9e933102cbc9909bfcb4564475ef8ea53427869b58c424405a588c954f3f737c
|
4
|
+
data.tar.gz: 415a21ff41613ba75ce07dab568f8560c9b3b83b0b3e7cac015f4238f20420b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5867e989c0296a84807b9cf52e2d0512f3083c12f07a82fb8f9bca1bf475e38fd658132d5cbd91df03cee83114665c325f1a58e4c195b037f08f8feb648c188f
|
7
|
+
data.tar.gz: b314c5762fbe08f6e95ba8d18d01e6a51ef25656cd3797de8e3d2065d1066768656ac8c1ac030379672b2ed27d3672e5cbbcdc6d499fd90f7e35ba4b356f25b2
|
@@ -0,0 +1,25 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [main]
|
6
|
+
pull_request:
|
7
|
+
branches: [main]
|
8
|
+
|
9
|
+
permissions:
|
10
|
+
contents: read
|
11
|
+
|
12
|
+
jobs:
|
13
|
+
test:
|
14
|
+
runs-on: ubuntu-latest
|
15
|
+
strategy:
|
16
|
+
fail-fast: false
|
17
|
+
matrix:
|
18
|
+
ruby: ["2.5", "2.6", "2.7", "3.0", "3.1", "3.2", "3.3", jruby-9.3, jruby-9.4]
|
19
|
+
steps:
|
20
|
+
- uses: actions/checkout@v3
|
21
|
+
- uses: ruby/setup-ruby@v1
|
22
|
+
with:
|
23
|
+
bundler-cache: true
|
24
|
+
ruby-version: ${{ matrix.ruby }}
|
25
|
+
- run: bundle exec rake
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
# SSHKey
|
2
2
|
|
3
|
-
Generate private and public SSH keys (RSA and
|
4
|
-
|
5
|
-
[](http://travis-ci.org/bensie/sshkey)
|
3
|
+
Generate private and public SSH keys (RSA, DSA, and ECDSA supported) using pure Ruby.
|
6
4
|
|
7
5
|
## Requirements
|
8
6
|
|
9
|
-
Tested / supported on CRuby 2.
|
7
|
+
Tested / supported on CRuby 2.5+ and JRuby.
|
10
8
|
|
11
9
|
## Installation
|
12
10
|
|
@@ -16,7 +14,7 @@ Tested / supported on CRuby 2.0.0+ and JRuby.
|
|
16
14
|
|
17
15
|
### Generate a new key
|
18
16
|
|
19
|
-
When generating a new keypair the default key type is 2048-bit RSA, but you can supply the `type` (RSA or DSA) and `bits` in the options.
|
17
|
+
When generating a new keypair the default key type is 2048-bit RSA, but you can supply the `type` (RSA or DSA or ECDSA) and `bits` in the options.
|
20
18
|
You can also (optionally) supply a `comment` or `passphrase`.
|
21
19
|
|
22
20
|
```ruby
|
@@ -32,7 +30,7 @@ k = SSHKey.generate(
|
|
32
30
|
|
33
31
|
### Use your existing key
|
34
32
|
|
35
|
-
Return an SSHKey object from an existing RSA or DSA private key (provided as a string).
|
33
|
+
Return an SSHKey object from an existing RSA or DSA or ECDSA private key (provided as a string).
|
36
34
|
|
37
35
|
```ruby
|
38
36
|
f = File.read(File.expand_path("~/.ssh/id_rsa"))
|
@@ -43,7 +41,7 @@ k = SSHKey.new(f, comment: "foo@bar.com")
|
|
43
41
|
|
44
42
|
#### Private and public keys
|
45
43
|
|
46
|
-
Fetch the private and public keys as strings. Note that the `public_key` is the RSA or DSA public key, not an SSH public key.
|
44
|
+
Fetch the private and public keys as strings. Note that the `public_key` is the RSA or DSA or ECDSA public key, not an SSH public key.
|
47
45
|
|
48
46
|
```ruby
|
49
47
|
k.private_key
|
@@ -161,7 +159,7 @@ puts k.randomart
|
|
161
159
|
|
162
160
|
#### Original OpenSSL key object
|
163
161
|
|
164
|
-
Return the original [OpenSSL::PKey::RSA](
|
162
|
+
Return the original [OpenSSL::PKey::RSA](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/PKey/RSA.html) or [OpenSSL::PKey::DSA](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/PKey/DSA.html) or [OpenSSL::PKey::EC](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/PKey/EC.html)object.
|
165
163
|
|
166
164
|
```ruby
|
167
165
|
k.key_object
|
@@ -213,4 +211,4 @@ SSHKey.ssh_public_key_to_ssh2_public_key "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ
|
|
213
211
|
|
214
212
|
## Copyright
|
215
213
|
|
216
|
-
Copyright (c) 2011-
|
214
|
+
Copyright (c) 2011-2023 James Miller
|
data/lib/sshkey/version.rb
CHANGED
data/lib/sshkey.rb
CHANGED
@@ -4,6 +4,35 @@ require 'digest/md5'
|
|
4
4
|
require 'digest/sha1'
|
5
5
|
require 'digest/sha2'
|
6
6
|
|
7
|
+
def jruby_not_implemented(msg)
|
8
|
+
raise NotImplementedError.new "jruby-openssl #{JOpenSSL::VERSION}: #{msg}" if RUBY_PLATFORM == "java"
|
9
|
+
end
|
10
|
+
|
11
|
+
# Monkey patch OpenSSL::PKey::EC to provide convenience methods usable in this gem
|
12
|
+
class OpenSSL::PKey::EC
|
13
|
+
def identifier
|
14
|
+
# NOTE: Unable to find these constants within OpenSSL, so hardcode them here.
|
15
|
+
# Analogous to net-ssh OpenSSL::PKey::EC::CurveNameAliasInv
|
16
|
+
# https://github.com/net-ssh/net-ssh/blob/master/lib/net/ssh/transport/openssl.rb#L147-L151
|
17
|
+
case group.curve_name
|
18
|
+
when "prime256v1" then "nistp256" # https://stackoverflow.com/a/41953717
|
19
|
+
when "secp256r1" then "nistp256" # JRuby
|
20
|
+
when "secp384r1" then "nistp384"
|
21
|
+
when "secp521r1" then "nistp521"
|
22
|
+
else
|
23
|
+
raise "Unknown curve name: #{public_key.group.curve_name}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def q
|
28
|
+
# jruby-openssl does not currently support to_octet_string
|
29
|
+
# https://github.com/jruby/jruby-openssl/issues/226
|
30
|
+
jruby_not_implemented("to_octet_string is not implemented")
|
31
|
+
|
32
|
+
public_key.to_octet_string(group.point_conversion_form)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
7
36
|
class SSHKey
|
8
37
|
SSH_TYPES = {
|
9
38
|
"ssh-rsa" => "rsa",
|
@@ -20,7 +49,23 @@ class SSHKey
|
|
20
49
|
"ecdsa" => 3,
|
21
50
|
"ed25519" => 4,
|
22
51
|
}
|
23
|
-
|
52
|
+
|
53
|
+
ECDSA_CURVES = {
|
54
|
+
256 => "prime256v1", # https://stackoverflow.com/a/41953717
|
55
|
+
384 => "secp384r1",
|
56
|
+
521 => "secp521r1",
|
57
|
+
}
|
58
|
+
|
59
|
+
VALID_BITS = {
|
60
|
+
"ecdsa" => ECDSA_CURVES.keys,
|
61
|
+
}
|
62
|
+
|
63
|
+
# Accessor methods are defined in:
|
64
|
+
# - RSA: https://github.com/ruby/openssl/blob/master/ext/openssl/ossl_pkey_rsa.c
|
65
|
+
# - DSA: https://github.com/ruby/openssl/blob/master/ext/openssl/ossl_pkey_dsa.c
|
66
|
+
# - ECDSA: monkey patch OpenSSL::PKey::EC above
|
67
|
+
SSH_CONVERSION = {"rsa" => ["e", "n"], "dsa" => ["p", "q", "g", "pub_key"], "ecdsa" => ["identifier", "q"]}
|
68
|
+
|
24
69
|
SSH2_LINE_LENGTH = 70 # +1 (for line wrap '/' character) must be <= 72
|
25
70
|
|
26
71
|
class << self
|
@@ -40,17 +85,44 @@ class SSHKey
|
|
40
85
|
type = options[:type] || "rsa"
|
41
86
|
|
42
87
|
# JRuby modulus size must range from 512 to 1024
|
43
|
-
|
88
|
+
case type
|
89
|
+
when "rsa" then default_bits = 2048
|
90
|
+
when "ecdsa" then default_bits = 256
|
91
|
+
else
|
92
|
+
default_bits = 1024
|
93
|
+
end
|
44
94
|
|
45
95
|
bits = options[:bits] || default_bits
|
46
96
|
cipher = OpenSSL::Cipher.new("AES-128-CBC") if options[:passphrase]
|
47
97
|
|
98
|
+
raise "Bits must either: #{VALID_BITS[type.downcase].join(', ')}" unless VALID_BITS[type.downcase].nil? || VALID_BITS[type.downcase].include?(bits)
|
99
|
+
|
48
100
|
case type.downcase
|
49
|
-
when "rsa"
|
50
|
-
|
101
|
+
when "rsa"
|
102
|
+
key_object = OpenSSL::PKey::RSA.generate(bits)
|
103
|
+
|
104
|
+
when "dsa"
|
105
|
+
key_object = OpenSSL::PKey::DSA.generate(bits)
|
106
|
+
|
107
|
+
when "ecdsa"
|
108
|
+
# jruby-openssl OpenSSL::PKey::EC support isn't complete
|
109
|
+
# https://github.com/jruby/jruby-openssl/issues/189
|
110
|
+
jruby_not_implemented("OpenSSL::PKey::EC is not fully implemented")
|
111
|
+
|
112
|
+
if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000
|
113
|
+
# https://github.com/ruby/openssl/pull/480
|
114
|
+
key_object = OpenSSL::PKey::EC.generate(ECDSA_CURVES[bits])
|
115
|
+
else
|
116
|
+
key_pkey = OpenSSL::PKey::EC.new(ECDSA_CURVES[bits])
|
117
|
+
key_object = key_pkey.generate_key
|
118
|
+
end
|
119
|
+
|
51
120
|
else
|
52
121
|
raise "Unknown key type: #{type}"
|
53
122
|
end
|
123
|
+
|
124
|
+
key_pem = key_object.to_pem(cipher, options[:passphrase])
|
125
|
+
new(key_pem, options)
|
54
126
|
end
|
55
127
|
|
56
128
|
# Validate an existing SSH public key
|
@@ -83,9 +155,27 @@ class SSHKey
|
|
83
155
|
#
|
84
156
|
# ==== Parameters
|
85
157
|
# * ssh_public_key<~String> - "ssh-rsa AAAAB3NzaC1yc2EA...."
|
158
|
+
# * ssh_public_key<~String> - "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY...."
|
86
159
|
#
|
87
160
|
def ssh_public_key_bits(ssh_public_key)
|
88
|
-
|
161
|
+
ssh_type, encoded_key = parse_ssh_public_key(ssh_public_key)
|
162
|
+
sections = unpacked_byte_array(ssh_type, encoded_key)
|
163
|
+
|
164
|
+
case ssh_type
|
165
|
+
when "ssh-rsa", "ssh-dss", "ssh-ed25519"
|
166
|
+
sections.last.num_bytes * 8
|
167
|
+
|
168
|
+
when "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521"
|
169
|
+
raise PublicKeyError, "invalid ECDSA key" unless sections.count == 2
|
170
|
+
|
171
|
+
# https://tools.ietf.org/html/rfc5656#section-3.1
|
172
|
+
identifier = sections[0].to_s(2)
|
173
|
+
q = sections[1].to_s(2)
|
174
|
+
ecdsa_bits(ssh_type, identifier, q)
|
175
|
+
|
176
|
+
else
|
177
|
+
raise PublicKeyError, "unsupported key type #{ssh_type}"
|
178
|
+
end
|
89
179
|
end
|
90
180
|
|
91
181
|
# Fingerprints
|
@@ -194,6 +284,48 @@ class SSHKey
|
|
194
284
|
return data
|
195
285
|
end
|
196
286
|
|
287
|
+
def ecdsa_bits(ssh_type, identifier, q)
|
288
|
+
raise PublicKeyError, "invalid ssh type" unless ssh_type == "ecdsa-sha2-#{identifier}"
|
289
|
+
|
290
|
+
len_q = q.length
|
291
|
+
|
292
|
+
compression_octet = q.slice(0, 1)
|
293
|
+
if compression_octet == "\x04"
|
294
|
+
# Point compression is off
|
295
|
+
# Summary from https://www.secg.org/sec1-v2.pdf "2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion"
|
296
|
+
# - the leftmost octet indicates that point compression is off
|
297
|
+
# (first octet 0x04 as specified in "3.3. Output M = 04 base 16 ‖ X ‖ Y.")
|
298
|
+
# - the remainder of the octet string contains the x-coordinate followed by the y-coordinate.
|
299
|
+
len_x = (len_q - 1) / 2
|
300
|
+
|
301
|
+
else
|
302
|
+
# Point compression is on
|
303
|
+
# Summary from https://www.secg.org/sec1-v2.pdf "2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion"
|
304
|
+
# - the compressed y-coordinate is recovered from the leftmost octet
|
305
|
+
# - the x-coordinate is recovered from the remainder of the octet string
|
306
|
+
raise PublicKeyError, "invalid compression octet" unless compression_octet == "\x02" || compression_octet == "\x03"
|
307
|
+
len_x = len_q - 1
|
308
|
+
end
|
309
|
+
|
310
|
+
# https://www.secg.org/sec2-v2.pdf "2.1 Properties of Elliptic Curve Domain Parameters over Fp" defines
|
311
|
+
# five discrete bit lengths: 192, 224, 256, 384, 521
|
312
|
+
# These bit lengths can be ascertained from the length of the packed x-coordinate.
|
313
|
+
# Alternatively, these bit lengths can be derived from their associated prime constants using Math.log2(prime).ceil
|
314
|
+
# against the prime constants defined in https://www.secg.org/sec2-v2.pdf
|
315
|
+
case len_x
|
316
|
+
when 24 then bits = 192
|
317
|
+
when 28 then bits = 224
|
318
|
+
when 32 then bits = 256
|
319
|
+
when 48 then bits = 384
|
320
|
+
when 66 then bits = 521
|
321
|
+
else
|
322
|
+
raise PublicKeyError, "invalid x-coordinate length #{len_x}"
|
323
|
+
end
|
324
|
+
|
325
|
+
raise PublicKeyError, "invalid identifier #{identifier}" unless identifier =~ /#{bits}/
|
326
|
+
return bits
|
327
|
+
end
|
328
|
+
|
197
329
|
def decoded_key(key)
|
198
330
|
Base64.decode64(parse_ssh_public_key(key).last)
|
199
331
|
end
|
@@ -203,6 +335,10 @@ class SSHKey
|
|
203
335
|
end
|
204
336
|
|
205
337
|
def parse_ssh_public_key(public_key)
|
338
|
+
# lines starting with a '#' and empty lines are ignored as comments (as in ssh AuthorizedKeysFile)
|
339
|
+
public_key = public_key.gsub(/^#.*$/, '')
|
340
|
+
public_key = public_key.strip # leading and trailing whitespaces wiped out
|
341
|
+
|
206
342
|
raise PublicKeyError, "newlines are not permitted between key data" if public_key =~ /\n(?!$)/
|
207
343
|
|
208
344
|
parsed = public_key.split(" ")
|
@@ -227,13 +363,13 @@ class SSHKey
|
|
227
363
|
end
|
228
364
|
end
|
229
365
|
|
230
|
-
attr_reader :key_object, :type
|
366
|
+
attr_reader :key_object, :type, :typestr
|
231
367
|
attr_accessor :passphrase, :comment
|
232
368
|
|
233
369
|
# Create a new SSHKey object
|
234
370
|
#
|
235
371
|
# ==== Parameters
|
236
|
-
# * private_key - Existing RSA or DSA private key
|
372
|
+
# * private_key - Existing RSA or DSA or ECDSA private key
|
237
373
|
# * options<~Hash>
|
238
374
|
# * :comment<~String> - Comment to use for the public key, defaults to ""
|
239
375
|
# * :passphrase<~String> - If the key is encrypted, supply the passphrase
|
@@ -243,19 +379,41 @@ class SSHKey
|
|
243
379
|
@passphrase = options[:passphrase]
|
244
380
|
@comment = options[:comment] || ""
|
245
381
|
self.directives = options[:directives] || []
|
382
|
+
|
246
383
|
begin
|
247
384
|
@key_object = OpenSSL::PKey::RSA.new(private_key, passphrase)
|
248
385
|
@type = "rsa"
|
249
|
-
|
386
|
+
@typestr = "ssh-rsa"
|
387
|
+
rescue OpenSSL::PKey::RSAError
|
388
|
+
@type = nil
|
389
|
+
end
|
390
|
+
|
391
|
+
return if @type
|
392
|
+
|
393
|
+
begin
|
250
394
|
@key_object = OpenSSL::PKey::DSA.new(private_key, passphrase)
|
251
395
|
@type = "dsa"
|
396
|
+
@typestr = "ssh-dss"
|
397
|
+
rescue OpenSSL::PKey::DSAError
|
398
|
+
@type = nil
|
252
399
|
end
|
400
|
+
|
401
|
+
return if @type
|
402
|
+
|
403
|
+
@key_object = OpenSSL::PKey::EC.new(private_key, passphrase)
|
404
|
+
@type = "ecdsa"
|
405
|
+
bits = ECDSA_CURVES.invert[@key_object.group.curve_name]
|
406
|
+
@typestr = "ecdsa-sha2-nistp#{bits}"
|
253
407
|
end
|
254
408
|
|
255
|
-
# Fetch the
|
409
|
+
# Fetch the private key (PEM format)
|
256
410
|
#
|
257
411
|
# rsa_private_key and dsa_private_key are aliased for backward compatibility
|
258
412
|
def private_key
|
413
|
+
# jruby-openssl OpenSSL::PKey::EC support isn't complete
|
414
|
+
# https://github.com/jruby/jruby-openssl/issues/189
|
415
|
+
jruby_not_implemented("OpenSSL::PKey::EC is not fully implemented") if type == "ecdsa"
|
416
|
+
|
259
417
|
key_object.to_pem
|
260
418
|
end
|
261
419
|
alias_method :rsa_private_key, :private_key
|
@@ -269,18 +427,73 @@ class SSHKey
|
|
269
427
|
key_object.to_pem(OpenSSL::Cipher.new("AES-128-CBC"), passphrase)
|
270
428
|
end
|
271
429
|
|
272
|
-
# Fetch the
|
430
|
+
# Fetch the public key (PEM format)
|
273
431
|
#
|
274
432
|
# rsa_public_key and dsa_public_key are aliased for backward compatibility
|
275
433
|
def public_key
|
276
|
-
|
434
|
+
public_key_object.to_pem
|
277
435
|
end
|
278
436
|
alias_method :rsa_public_key, :public_key
|
279
437
|
alias_method :dsa_public_key, :public_key
|
280
438
|
|
439
|
+
def public_key_object
|
440
|
+
if type == "ecdsa"
|
441
|
+
return nil unless key_object
|
442
|
+
return nil unless key_object.group
|
443
|
+
|
444
|
+
if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000 && RUBY_PLATFORM != "java"
|
445
|
+
|
446
|
+
# jruby-openssl does not currently support point_conversion_form
|
447
|
+
# (futureproofing for if/when JRuby requires this technique to determine public key)
|
448
|
+
jruby_not_implemented("point_conversion_form is not implemented")
|
449
|
+
|
450
|
+
# Avoid "OpenSSL::PKey::PKeyError: pkeys are immutable on OpenSSL 3.0"
|
451
|
+
# https://github.com/ruby/openssl/blob/master/History.md#version-300
|
452
|
+
# https://github.com/ruby/openssl/issues/498
|
453
|
+
# https://github.com/net-ssh/net-ssh/commit/4de6831dea4e922bf3052192eec143af015a3486
|
454
|
+
# https://github.com/ClearlyClaire/cose-ruby/commit/28ee497fa7d9d49e72d5a5e97a567c0b58fdd822
|
455
|
+
|
456
|
+
curve_name = key_object.group.curve_name
|
457
|
+
return nil unless curve_name
|
458
|
+
|
459
|
+
# Map to different curve_name for JRuby
|
460
|
+
# (futureproofing for if/when JRuby requires this technique to determine public key)
|
461
|
+
# https://github.com/jwt/ruby-jwt/issues/362#issuecomment-722938409
|
462
|
+
curve_name = "prime256v1" if curve_name == "secp256r1" && RUBY_PLATFORM == "java"
|
463
|
+
|
464
|
+
# Construct public key OpenSSL::PKey::EC from OpenSSL::PKey::EC::Point
|
465
|
+
public_key_point = key_object.public_key # => OpenSSL::PKey::EC::Point
|
466
|
+
return nil unless public_key_point
|
467
|
+
|
468
|
+
asn1 = OpenSSL::ASN1::Sequence(
|
469
|
+
[
|
470
|
+
OpenSSL::ASN1::Sequence(
|
471
|
+
[
|
472
|
+
OpenSSL::ASN1::ObjectId("id-ecPublicKey"),
|
473
|
+
OpenSSL::ASN1::ObjectId(curve_name)
|
474
|
+
]
|
475
|
+
),
|
476
|
+
OpenSSL::ASN1::BitString(public_key_point.to_octet_string(key_object.group.point_conversion_form))
|
477
|
+
]
|
478
|
+
)
|
479
|
+
|
480
|
+
pub = OpenSSL::PKey::EC.new(asn1.to_der)
|
481
|
+
pub
|
482
|
+
|
483
|
+
else
|
484
|
+
pub = OpenSSL::PKey::EC.new(key_object.group)
|
485
|
+
pub.public_key = key_object.public_key
|
486
|
+
pub
|
487
|
+
end
|
488
|
+
|
489
|
+
else
|
490
|
+
key_object.public_key
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
281
494
|
# SSH public key
|
282
495
|
def ssh_public_key
|
283
|
-
[directives.join(",").strip,
|
496
|
+
[directives.join(",").strip, typestr, Base64.encode64(ssh_public_key_conversion).gsub("\n", ""), comment].join(" ").strip
|
284
497
|
end
|
285
498
|
|
286
499
|
# SSH2 public key (RFC4716)
|
@@ -323,6 +536,7 @@ class SSHKey
|
|
323
536
|
#
|
324
537
|
# Generate OpenSSH compatible ASCII art fingerprints
|
325
538
|
# See http://www.opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/key.c (key_fingerprint_randomart function)
|
539
|
+
# or https://mirrors.mit.edu/pub/OpenBSD/OpenSSH/ (sshkey.c fingerprint_randomart function)
|
326
540
|
#
|
327
541
|
# Example:
|
328
542
|
# +--[ RSA 2048]----+
|
@@ -336,13 +550,23 @@ class SSHKey
|
|
336
550
|
# | . . |
|
337
551
|
# | Eo. |
|
338
552
|
# +-----------------+
|
339
|
-
def randomart
|
553
|
+
def randomart(dgst_alg = "MD5")
|
340
554
|
fieldsize_x = 17
|
341
555
|
fieldsize_y = 9
|
342
556
|
x = fieldsize_x / 2
|
343
557
|
y = fieldsize_y / 2
|
344
|
-
|
345
|
-
|
558
|
+
|
559
|
+
case dgst_alg
|
560
|
+
when "MD5" then raw_digest = Digest::MD5.digest(ssh_public_key_conversion)
|
561
|
+
when "SHA256" then raw_digest = Digest::SHA2.new(256).digest(ssh_public_key_conversion)
|
562
|
+
when "SHA384" then raw_digest = Digest::SHA2.new(384).digest(ssh_public_key_conversion)
|
563
|
+
when "SHA512" then raw_digest = Digest::SHA2.new(512).digest(ssh_public_key_conversion)
|
564
|
+
else
|
565
|
+
raise "Unknown digest algorithm: #{digest}"
|
566
|
+
end
|
567
|
+
|
568
|
+
augmentation_string = " .o+=*BOX@%&#/^SE"
|
569
|
+
len = augmentation_string.length - 1
|
346
570
|
|
347
571
|
field = Array.new(fieldsize_x) { Array.new(fieldsize_y) {0} }
|
348
572
|
|
@@ -354,20 +578,27 @@ class SSHKey
|
|
354
578
|
x = [[x, 0].max, fieldsize_x - 1].min
|
355
579
|
y = [[y, 0].max, fieldsize_y - 1].min
|
356
580
|
|
357
|
-
field[x][y] += 1 if (field[x][y] <
|
581
|
+
field[x][y] += 1 if (field[x][y] < len - 2)
|
358
582
|
|
359
583
|
byte >>= 2
|
360
584
|
end
|
361
585
|
end
|
362
586
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
587
|
+
fieldsize_x_halved = fieldsize_x / 2
|
588
|
+
fieldsize_y_halved = fieldsize_y / 2
|
589
|
+
|
590
|
+
field[fieldsize_x_halved][fieldsize_y_halved] = len - 1
|
591
|
+
field[x][y] = len
|
592
|
+
|
593
|
+
type_name_length_max = 4 # Note: this will need to be extended to accomodate ed25519
|
594
|
+
bits_number_length_max = (bits < 1000 ? 3 : 4)
|
595
|
+
formatstr = "[%#{type_name_length_max}s %#{bits_number_length_max}u]"
|
596
|
+
output = "+--#{sprintf(formatstr, type.upcase, bits)}----+\n"
|
597
|
+
|
367
598
|
fieldsize_y.times do |y|
|
368
599
|
output << "|"
|
369
600
|
fieldsize_x.times do |x|
|
370
|
-
output << augmentation_string[[field[x][y],
|
601
|
+
output << augmentation_string[[field[x][y], len].min]
|
371
602
|
end
|
372
603
|
output << "|"
|
373
604
|
output << "\n"
|
@@ -387,6 +618,26 @@ class SSHKey
|
|
387
618
|
|
388
619
|
private
|
389
620
|
|
621
|
+
def self.ssh_public_key_data_dsarsa(val)
|
622
|
+
# Get byte-representation of absolute value of val
|
623
|
+
data = val.to_s(2)
|
624
|
+
|
625
|
+
first_byte = data[0,1].unpack("c").first
|
626
|
+
if val < 0
|
627
|
+
# For negative values, highest bit must be set
|
628
|
+
data[0] = [0x80 & first_byte].pack("c")
|
629
|
+
elsif first_byte < 0
|
630
|
+
# For positive values where highest bit would be set, prefix with \0
|
631
|
+
data = "\0" + data
|
632
|
+
end
|
633
|
+
|
634
|
+
data
|
635
|
+
end
|
636
|
+
|
637
|
+
def self.ssh_public_key_data_ecdsa(val)
|
638
|
+
val
|
639
|
+
end
|
640
|
+
|
390
641
|
# SSH Public Key Conversion
|
391
642
|
#
|
392
643
|
# All data type encoding is defined in the section #5 of RFC #4251.
|
@@ -397,26 +648,20 @@ class SSHKey
|
|
397
648
|
# For instance, the "ssh-rsa" string is encoded as the following byte array
|
398
649
|
# [0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a']
|
399
650
|
def ssh_public_key_conversion
|
400
|
-
typestr = SSH_TYPES.invert[type]
|
401
651
|
methods = SSH_CONVERSION[type]
|
402
|
-
|
403
|
-
|
404
|
-
#
|
405
|
-
#
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
data =
|
411
|
-
|
412
|
-
|
413
|
-
if val < 0
|
414
|
-
# For negative values, highest bit must be set
|
415
|
-
data[0] = [0x80 & first_byte].pack("c")
|
416
|
-
elsif first_byte < 0
|
417
|
-
# For positive values where highest bit would be set, prefix with \0
|
418
|
-
data = "\0" + data
|
652
|
+
methods.inject([typestr.length].pack("N") + typestr) do |pubkeystr, m|
|
653
|
+
# Given public_key_object.class == OpenSSL::BN, public_key_object.to_s(0)
|
654
|
+
# returns an MPI formatted string (length prefixed bytes). This is not
|
655
|
+
# supported by JRuby, so we still have to deal with length and data separately.
|
656
|
+
val = public_key_object.send(m)
|
657
|
+
|
658
|
+
case type
|
659
|
+
when "dsa","rsa" then data = self.class.ssh_public_key_data_dsarsa(val)
|
660
|
+
when "ecdsa" then data = self.class.ssh_public_key_data_ecdsa(val)
|
661
|
+
else
|
662
|
+
raise "Unknown key type: #{type}"
|
419
663
|
end
|
664
|
+
|
420
665
|
pubkeystr + [data.length].pack("N") + data
|
421
666
|
end
|
422
667
|
end
|
data/sshkey.gemspec
CHANGED
@@ -13,7 +13,10 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.description = %q{Generate private/public SSH keypairs using pure Ruby}
|
14
14
|
s.licenses = ["MIT"]
|
15
15
|
|
16
|
-
|
16
|
+
# ECDSA requires OpenSSL::PKey::EC::Point#to_octet_string
|
17
|
+
# to_octet string was added in Ruby/OpenSSL 2.1.0 https://github.com/ruby/openssl/blob/master/History.md#version-210
|
18
|
+
# Ruby 2.5 Updated Ruby/OpenSSL from to 2.1.0 https://github.com/ruby/ruby/blob/v2_5_0/NEWS
|
19
|
+
s.required_ruby_version = '>= 2.5'
|
17
20
|
|
18
21
|
s.files = `git ls-files`.split("\n")
|
19
22
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/test/sshkey_test.rb
CHANGED
@@ -2,6 +2,13 @@ require 'test/unit'
|
|
2
2
|
require 'sshkey'
|
3
3
|
|
4
4
|
class SSHKeyTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
# https://github.com/jruby/jruby-openssl/issues/189
|
7
|
+
# https://github.com/jruby/jruby-openssl/issues/226
|
8
|
+
def ecdsa_supported?
|
9
|
+
RUBY_PLATFORM != "java"
|
10
|
+
end
|
11
|
+
|
5
12
|
SSH_PRIVATE_KEY1 = <<-EOF
|
6
13
|
-----BEGIN RSA PRIVATE KEY-----
|
7
14
|
MIIEogIBAAKCAQEArfTA/lKVR84IMc9ZzXOCHr8DVtR8hzWuEVHF6KElavRHlk14
|
@@ -73,11 +80,66 @@ ItmZYXTvJDwLXgq2/iK1fRRcKk2PJEaSuJR7WeNGsJKfWmQ2UbOhqA3wWLDazIZt
|
|
73
80
|
cMKjFzD0hM4E8qgjHjMvKDE6WgT6SFP+tqx3nnh7pJWwsbGjSMQexpyRAhQLhz0l
|
74
81
|
GzM8qwTcXd06uIZAJdTHIQ==
|
75
82
|
-----END DSA PRIVATE KEY-----
|
83
|
+
EOF
|
84
|
+
|
85
|
+
SSH_PRIVATE_KEY4 = <<-EOF
|
86
|
+
-----BEGIN EC PRIVATE KEY-----
|
87
|
+
MHcCAQEEIByjVCRawGxEd/L/VblGjnJTJeOgk6vGFYnolYWHg+JkoAoGCCqGSM49
|
88
|
+
AwEHoUQDQgAEQOAmNzXT3XN5DQdHBYCgflosVlHd6MUB1n9n6CCijvVJCQGJAA0p
|
89
|
+
6+3o91ccyA0zHXuUno2eMzBUDghfNZYnHg==
|
90
|
+
-----END EC PRIVATE KEY-----
|
91
|
+
EOF
|
92
|
+
|
93
|
+
PUBLIC_KEY1 = <<-EOF
|
94
|
+
-----BEGIN PUBLIC KEY-----
|
95
|
+
MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEArfTA/lKVR84IMc9ZzXOC
|
96
|
+
Hr8DVtR8hzWuEVHF6KElavRHlk14g0SZu3m908Ejm/XF3EfNHjX9wN+62IMA0QBx
|
97
|
+
kBMFCuLF+U/oeUs0NoDdAEKxjj4n6lq6Ss8aLct+anMy7D1jwvOLbcwV54w1d5JD
|
98
|
+
dlZVdZ6AvHm9otwJq6rNpDgdmXY4HgC2nM9csFpuy0cDpL6fdJx9lcNL2RnkRC4+
|
99
|
+
RMsIB+PxDw0j3vDi04dYLBXMGYjyeGH+mIFpL3PTPXGXwL2XDYXZ2H4SQX6bOoKm
|
100
|
+
azTXq6QXuEB665njh1GxXldoIMcSshoJL0hrk3WrTOG22N2CQA+IfHgrXJ+A+QUz
|
101
|
+
KQIBIw==
|
102
|
+
-----END PUBLIC KEY-----
|
103
|
+
EOF
|
104
|
+
|
105
|
+
PUBLIC_KEY2 = <<-EOF
|
106
|
+
-----BEGIN PUBLIC KEY-----
|
107
|
+
MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAxl6TpN7uFiY/JZ8qDnD7
|
108
|
+
UrxDP+ABeh2PVg8Du1LEgXNk0+YWCeP5S6oHklqaWeDlbmAs1oHsBwCMAVpMa5tg
|
109
|
+
ONOLvz4JgwgkiqQEbKR8ofWJ+LADUElvqRVGmGiNEMLI6GJWeneL4sjmbb8d6U+M
|
110
|
+
53c6iWG0si9XE5m7teBQSsCl0Tk3qMIkQGw5zpJeCXjZ8KpJhIJRYgexFkGgPlYR
|
111
|
+
V+UYIhxpUW90t0Ra5i6JOFYwq98k5S/6SJIZQ/A9F4JNzwLw3eVxZj0yVHWxkGz1
|
112
|
+
+TyELNY1kOyMxnZaqSfGzSQJTrnIXpdweVHuYh1LtOgedRQhCyiELeSMGwio1vRP
|
113
|
+
KwIBIw==
|
114
|
+
-----END PUBLIC KEY-----
|
115
|
+
EOF
|
116
|
+
|
117
|
+
PUBLIC_KEY3 = <<-EOF
|
118
|
+
-----BEGIN PUBLIC KEY-----
|
119
|
+
MIIBuDCCASwGByqGSM44BAEwggEfAoGBALyVy5dwVwgL3CxXzsvo8DBh58qArQLB
|
120
|
+
NIPW/f9pptmy7jD5QXzOw+12w0/z4lZ86ncoVutRMf44OABcX9ovhRl+luxB7jjp
|
121
|
+
kVXy/p2ZaqPbeyTQUtdTmXa2y4n053Jd61VeMG+iLP7+viT+Ib96y9aVUYQfCrl5
|
122
|
+
heBDUZ9cAFjdAhUAxV5zuySaRSsJHqKK+Blhh7c9A9kCgYEAqel0RUBO0MY5b3DZ
|
123
|
+
69J/mRzUifN1O6twk4er2ph0JpryuUwZohLpcVZwqoGWmPQy/ZHmV1b3RtT9GWUa
|
124
|
+
+HUqKdMhFVOx/iq1khVfLi83whjMMvXj3ecqd0yzGxGHnSsjVKefa2ywCLHrh4nl
|
125
|
+
UVIaXI5gQpgMyVbMcromDe1WZzoDgYUAAoGBAIwTRPAEcroqOzaebiVspFcmsXxD
|
126
|
+
Q4wXQZQdho1ExW6FKS8s7/6pItmZYXTvJDwLXgq2/iK1fRRcKk2PJEaSuJR7WeNG
|
127
|
+
sJKfWmQ2UbOhqA3wWLDazIZtcMKjFzD0hM4E8qgjHjMvKDE6WgT6SFP+tqx3nnh7
|
128
|
+
pJWwsbGjSMQexpyR
|
129
|
+
-----END PUBLIC KEY-----
|
130
|
+
EOF
|
131
|
+
|
132
|
+
PUBLIC_KEY4 = <<-EOF
|
133
|
+
-----BEGIN PUBLIC KEY-----
|
134
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQOAmNzXT3XN5DQdHBYCgflosVlHd
|
135
|
+
6MUB1n9n6CCijvVJCQGJAA0p6+3o91ccyA0zHXuUno2eMzBUDghfNZYnHg==
|
136
|
+
-----END PUBLIC KEY-----
|
76
137
|
EOF
|
77
138
|
|
78
139
|
SSH_PUBLIC_KEY1 = 'AAAAB3NzaC1yc2EAAAABIwAAAQEArfTA/lKVR84IMc9ZzXOCHr8DVtR8hzWuEVHF6KElavRHlk14g0SZu3m908Ejm/XF3EfNHjX9wN+62IMA0QBxkBMFCuLF+U/oeUs0NoDdAEKxjj4n6lq6Ss8aLct+anMy7D1jwvOLbcwV54w1d5JDdlZVdZ6AvHm9otwJq6rNpDgdmXY4HgC2nM9csFpuy0cDpL6fdJx9lcNL2RnkRC4+RMsIB+PxDw0j3vDi04dYLBXMGYjyeGH+mIFpL3PTPXGXwL2XDYXZ2H4SQX6bOoKmazTXq6QXuEB665njh1GxXldoIMcSshoJL0hrk3WrTOG22N2CQA+IfHgrXJ+A+QUzKQ=='
|
79
140
|
SSH_PUBLIC_KEY2 = 'AAAAB3NzaC1yc2EAAAABIwAAAQEAxl6TpN7uFiY/JZ8qDnD7UrxDP+ABeh2PVg8Du1LEgXNk0+YWCeP5S6oHklqaWeDlbmAs1oHsBwCMAVpMa5tgONOLvz4JgwgkiqQEbKR8ofWJ+LADUElvqRVGmGiNEMLI6GJWeneL4sjmbb8d6U+M53c6iWG0si9XE5m7teBQSsCl0Tk3qMIkQGw5zpJeCXjZ8KpJhIJRYgexFkGgPlYRV+UYIhxpUW90t0Ra5i6JOFYwq98k5S/6SJIZQ/A9F4JNzwLw3eVxZj0yVHWxkGz1+TyELNY1kOyMxnZaqSfGzSQJTrnIXpdweVHuYh1LtOgedRQhCyiELeSMGwio1vRPKw=='
|
80
141
|
SSH_PUBLIC_KEY3 = 'AAAAB3NzaC1kc3MAAACBALyVy5dwVwgL3CxXzsvo8DBh58qArQLBNIPW/f9pptmy7jD5QXzOw+12w0/z4lZ86ncoVutRMf44OABcX9ovhRl+luxB7jjpkVXy/p2ZaqPbeyTQUtdTmXa2y4n053Jd61VeMG+iLP7+viT+Ib96y9aVUYQfCrl5heBDUZ9cAFjdAAAAFQDFXnO7JJpFKwkeoor4GWGHtz0D2QAAAIEAqel0RUBO0MY5b3DZ69J/mRzUifN1O6twk4er2ph0JpryuUwZohLpcVZwqoGWmPQy/ZHmV1b3RtT9GWUa+HUqKdMhFVOx/iq1khVfLi83whjMMvXj3ecqd0yzGxGHnSsjVKefa2ywCLHrh4nlUVIaXI5gQpgMyVbMcromDe1WZzoAAACBAIwTRPAEcroqOzaebiVspFcmsXxDQ4wXQZQdho1ExW6FKS8s7/6pItmZYXTvJDwLXgq2/iK1fRRcKk2PJEaSuJR7WeNGsJKfWmQ2UbOhqA3wWLDazIZtcMKjFzD0hM4E8qgjHjMvKDE6WgT6SFP+tqx3nnh7pJWwsbGjSMQexpyR'
|
142
|
+
SSH_PUBLIC_KEY4 = 'AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEDgJjc1091zeQ0HRwWAoH5aLFZR3ejFAdZ/Z+ggoo71SQkBiQANKevt6PdXHMgNMx17lJ6NnjMwVA4IXzWWJx4='
|
81
143
|
|
82
144
|
SSH_PUBLIC_KEY_ED25519 = 'AAAAC3NzaC1lZDI1NTE5AAAAIBrNsRCISAtKXV5OVxqV6unVcdis5Uh3oiC6B7CMB7HQ'
|
83
145
|
SSH_PUBLIC_KEY_ED25519_0_BYTE = 'AAAAC3NzaC1lZDI1NTE5AAAAIADK9x9t3yQQH7h4OEJpUa7l2j7mcmKf4LAsNXHxNbSm'
|
@@ -86,23 +148,36 @@ EOF
|
|
86
148
|
SSH_PUBLIC_KEY_ECDSA_384 = 'AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBP+GtUCOR8aW7xTtpkbJS0qqNZ98PgbUNtTFhE+Oe+khgoFMX+o0JG5bckVuvtkRl8dr+63kUK0QPTtzP9O5yixB9CYnB8CgCgYo1FCXZuJIImf12wW5nWKglrCH4kV1Qg=='
|
87
149
|
SSH_PUBLIC_KEY_ECDSA_521 = 'AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACsunidnIZ77AjCHSDp/xknLGDW3M0Ia7nxLdImmp0XGbxtbwYm2ga5XUzV9dMO9wF9ICC3OuH6g9DtGOBNPru1PwFDjaPISGgm0vniEzWazLsvjJVLThOA3VyYLxmtjm0WfS+/DfxgWVS6oeCTnDjjoVVpwU/fDbUbYPPRZI84/hOGNA=='
|
88
150
|
|
151
|
+
SSH_PUBLIC_KEY_ECDSA_256_COMPRESSED = 'AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAAAhA+YNpJJrrUsu5OLLvqGX5pAH3+x6/yEFU2AYdxb54Jk8'
|
152
|
+
SSH_PUBLIC_KEY_ECDSA_384_COMPRESSED = 'AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAAAxAgMhp0cNvtzncxXF0W5nrkBCTrxJIcYqUTX4RcKWIM74FfxizmWJqP/C+looEz6dLQ=='
|
153
|
+
SSH_PUBLIC_KEY_ECDSA_521_COMPRESSED = 'AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAABDAgDoeNR4bndT24BosNaTKCLOALjL6tXrpNHn0HJzHO5z30L4SvH0Gz9jvAiqehNHOgmK3/bFbwLVW1W4TJbNsp8BVA=='
|
154
|
+
|
89
155
|
KEY1_MD5_FINGERPRINT = "2a:89:84:c9:29:05:d1:f8:49:79:1c:ba:73:99:eb:af"
|
90
156
|
KEY2_MD5_FINGERPRINT = "3c:af:74:87:cc:cc:a1:12:05:1a:09:b7:7b:ce:ed:ce"
|
91
157
|
KEY3_MD5_FINGERPRINT = "14:f6:6a:12:96:be:44:32:e6:3c:77:43:94:52:f5:7a"
|
158
|
+
KEY4_MD5_FINGERPRINT = "38:0b:0f:63:36:64:b6:f0:43:94:de:32:75:eb:57:68"
|
92
159
|
ED25519_MD5_FINGERPRINT = "6f:1a:8a:c1:4f:13:5c:36:6e:3f:be:eb:49:3b:8e:3e"
|
93
160
|
ECDSA_256_MD5_FINGERPRINT = "d9:3a:7f:de:b2:65:04:ac:62:05:1a:1e:97:e9:2b:9d"
|
161
|
+
ECDSA_384_MD5_FINGERPRINT = "b5:bb:3e:f6:eb:3b:0f:1e:18:37:1f:36:ac:7c:87:0d"
|
162
|
+
ECDSA_521_MD5_FINGERPRINT = "98:8e:a9:4c:b9:aa:58:35:d1:42:65:c3:41:dd:04:e1"
|
94
163
|
|
95
164
|
KEY1_SHA1_FINGERPRINT = "e4:f9:79:f2:fe:d6:be:2d:ef:2e:c2:fa:aa:f8:b0:17:34:fe:0d:c0"
|
96
165
|
KEY2_SHA1_FINGERPRINT = "9a:52:78:2b:6b:cb:39:b7:85:ed:90:8a:28:62:aa:b3:98:88:e6:07"
|
97
166
|
KEY3_SHA1_FINGERPRINT = "15:68:c6:72:ac:18:d1:fc:ab:a2:b7:b5:8c:d1:fe:8f:b9:ae:a9:47"
|
167
|
+
KEY4_SHA1_FINGERPRINT = "aa:b5:e6:62:27:87:b8:05:f6:d6:8f:31:dc:83:81:d9:8f:f8:71:29"
|
98
168
|
ED25519_SHA1_FINGERPRINT = "57:41:7c:d0:e2:53:28:87:7e:87:53:d4:69:ef:ef:63:ec:c0:0e:5e"
|
99
169
|
ECDSA_256_SHA1_FINGERPRINT = "94:e8:92:2b:1b:ec:49:de:ff:85:ea:6e:10:d6:8d:87:7a:67:40:ee"
|
170
|
+
ECDSA_384_SHA1_FINGERPRINT = "cc:fb:4c:d6:e9:d0:03:ae:2d:82:e1:fc:70:d8:47:98:25:e1:83:2b"
|
171
|
+
ECDSA_521_SHA1_FINGERPRINT = "6b:2c:a2:6e:3a:82:6c:73:28:57:91:20:71:82:bc:8f:f8:9d:6c:41"
|
100
172
|
|
101
173
|
KEY1_SHA256_FINGERPRINT = "js3llFehloxCfsVuDw5xu3NtS9AOAxcXY8WL6vkDIts="
|
102
174
|
KEY2_SHA256_FINGERPRINT = "23f/6U/LdxIFx1CQFKHylw76n+LIHYoY4nRxKcFoos4="
|
103
175
|
KEY3_SHA256_FINGERPRINT = "mPqEPQlOPGORrTJrU17sPax1jOqeutZja6MOsFIca+8="
|
176
|
+
KEY4_SHA256_FINGERPRINT = "foUpf1ox3KfG3eKgJxGoSdZFRxHPsBYJgfD+CMYky6Y="
|
104
177
|
ED25519_SHA256_FINGERPRINT = "gyzHUKl1eO8Bk1Cvn4joRgxRlXo1+1HJ3Vho/hAtKEg="
|
105
178
|
ECDSA_256_SHA256_FINGERPRINT = "ncy2crhoL44R58GCZPQ5chPRrjlQKKgu07FDNelDmdk="
|
179
|
+
ECDSA_384_SHA256_FINGERPRINT = "mrr4QcP6qD05DUS6Rwefb9f0uuvjyMcO28LSiq2283U="
|
180
|
+
ECDSA_521_SHA256_FINGERPRINT = "QnaiGMIVDZyTG47hMWK6Y1z/yUzHIcTBGpNNuUwlhAk="
|
106
181
|
|
107
182
|
KEY1_RANDOMART = <<-EOF.rstrip
|
108
183
|
+--[ RSA 2048]----+
|
@@ -144,6 +219,66 @@ EOF
|
|
144
219
|
| |
|
145
220
|
| |
|
146
221
|
+-----------------+
|
222
|
+
EOF
|
223
|
+
|
224
|
+
# ssh-keygen -lv -E md5 -f ./id_ecdsa_ssh_public_key4.pub
|
225
|
+
KEY4_RANDOMART = <<-EOF.rstrip
|
226
|
+
+--[ECDSA 256]----+
|
227
|
+
| .. |
|
228
|
+
| .. . . |
|
229
|
+
| ..=o . . . |
|
230
|
+
| B+.... E . |
|
231
|
+
| @oo.S. . |
|
232
|
+
| o B o. . |
|
233
|
+
| o . |
|
234
|
+
| |
|
235
|
+
| |
|
236
|
+
+-----------------+
|
237
|
+
EOF
|
238
|
+
|
239
|
+
# ssh-keygen -lv -E sha256 -f ./id_ecdsa_ssh_public_key4.pub
|
240
|
+
KEY4_RANDOMART_USING_SHA256_DIGEST = <<-EOF.rstrip
|
241
|
+
+--[ECDSA 256]----+
|
242
|
+
| .. o++B+ |
|
243
|
+
| .. ...* |
|
244
|
+
| . ...o o o |
|
245
|
+
| . =o.o .= . |
|
246
|
+
| +o+oS o.= . .|
|
247
|
+
| o .oo =.. + +.|
|
248
|
+
| E o +.+ = o|
|
249
|
+
| ..=.+ . |
|
250
|
+
| oo . |
|
251
|
+
+-----------------+
|
252
|
+
EOF
|
253
|
+
|
254
|
+
# ssh-keygen -lv -E sha384 -f ./id_ecdsa_ssh_public_key4.pub
|
255
|
+
KEY4_RANDOMART_USING_SHA384_DIGEST = <<-EOF.rstrip
|
256
|
+
+--[ECDSA 256]----+
|
257
|
+
| o++. |
|
258
|
+
| . *oo. . |
|
259
|
+
|o .o+B.o.. |
|
260
|
+
|+o ooB+O *..|
|
261
|
+
|.=+ .SB== ^.+.|
|
262
|
+
|+ o +o .O Xo.|
|
263
|
+
| . ... .. + .o|
|
264
|
+
| . E. o + + +..|
|
265
|
+
| .... . o..Bo..|
|
266
|
+
+-----------------+
|
267
|
+
EOF
|
268
|
+
|
269
|
+
# ssh-keygen -lv -E sha512 -f ./id_ecdsa_ssh_public_key4.pub
|
270
|
+
KEY4_RANDOMART_USING_SHA512_DIGEST = <<-EOF.rstrip
|
271
|
+
+--[ECDSA 256]----+
|
272
|
+
| +*+o oo|
|
273
|
+
| . .o o . +|
|
274
|
+
| . o. oo oo|
|
275
|
+
|.. .+ . .*.o+ |
|
276
|
+
|..Bo.* S ..=o..|
|
277
|
+
| .+X+ Oo ...+ |
|
278
|
+
| +o.B*+=o .+ +|
|
279
|
+
|+=+O.+=+.+. +.o+.|
|
280
|
+
|@**EB*O++=o+ =o.+|
|
281
|
+
+-----------------+
|
147
282
|
EOF
|
148
283
|
|
149
284
|
KEY1_SSHFP = <<-EOF.rstrip
|
@@ -205,6 +340,7 @@ EOF
|
|
205
340
|
@key1 = SSHKey.new(SSH_PRIVATE_KEY1, :comment => "me@example.com")
|
206
341
|
@key2 = SSHKey.new(SSH_PRIVATE_KEY2, :comment => "me@example.com")
|
207
342
|
@key3 = SSHKey.new(SSH_PRIVATE_KEY3, :comment => "me@example.com")
|
343
|
+
@key4 = SSHKey.new(SSH_PRIVATE_KEY4, :comment => "me@example.com")
|
208
344
|
@key_without_comment = SSHKey.new(SSH_PRIVATE_KEY1)
|
209
345
|
end
|
210
346
|
|
@@ -217,7 +353,14 @@ EOF
|
|
217
353
|
end
|
218
354
|
|
219
355
|
def test_generator_with_type
|
356
|
+
assert_equal "rsa", SSHKey.generate(:type => "rsa").type
|
220
357
|
assert_equal "dsa", SSHKey.generate(:type => "dsa").type
|
358
|
+
|
359
|
+
if ecdsa_supported?
|
360
|
+
assert_equal "ecdsa", SSHKey.generate(:type => "ecdsa").type
|
361
|
+
else
|
362
|
+
assert_raises(NotImplementedError) { SSHKey.generate(:type => "ecdsa").type }
|
363
|
+
end
|
221
364
|
end
|
222
365
|
|
223
366
|
def test_generator_with_passphrase
|
@@ -239,6 +382,30 @@ EOF
|
|
239
382
|
assert_equal SSH_PRIVATE_KEY3, @key3.dsa_private_key
|
240
383
|
end
|
241
384
|
|
385
|
+
def test_private_key4
|
386
|
+
if ecdsa_supported?
|
387
|
+
assert_equal SSH_PRIVATE_KEY4, @key4.private_key
|
388
|
+
else
|
389
|
+
assert_raises(NotImplementedError) { @key4.private_key }
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
def test_public_key_1
|
394
|
+
assert_equal PUBLIC_KEY1, @key1.public_key
|
395
|
+
end
|
396
|
+
|
397
|
+
def test_public_key_2
|
398
|
+
assert_equal PUBLIC_KEY2, @key2.public_key
|
399
|
+
end
|
400
|
+
|
401
|
+
def test_public_key_3
|
402
|
+
assert_equal PUBLIC_KEY3, @key3.public_key
|
403
|
+
end
|
404
|
+
|
405
|
+
def test_public_key_4
|
406
|
+
assert_equal PUBLIC_KEY4, @key4.public_key
|
407
|
+
end
|
408
|
+
|
242
409
|
def test_ssh_public_key_decoded1
|
243
410
|
assert_equal Base64.decode64(SSH_PUBLIC_KEY1), @key1.send(:ssh_public_key_conversion)
|
244
411
|
end
|
@@ -251,6 +418,14 @@ EOF
|
|
251
418
|
assert_equal Base64.decode64(SSH_PUBLIC_KEY3), @key3.send(:ssh_public_key_conversion)
|
252
419
|
end
|
253
420
|
|
421
|
+
def test_ssh_public_key_decoded4
|
422
|
+
if ecdsa_supported?
|
423
|
+
assert_equal Base64.decode64(SSH_PUBLIC_KEY4), @key4.send(:ssh_public_key_conversion)
|
424
|
+
else
|
425
|
+
assert_raises(NotImplementedError) { @key4.send(:ssh_public_key_conversion) }
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
254
429
|
def test_ssh_public_key_encoded1
|
255
430
|
assert_equal SSH_PUBLIC_KEY1, Base64.encode64(@key1.send(:ssh_public_key_conversion)).gsub("\n", "")
|
256
431
|
end
|
@@ -263,15 +438,31 @@ EOF
|
|
263
438
|
assert_equal SSH_PUBLIC_KEY3, Base64.encode64(@key3.send(:ssh_public_key_conversion)).gsub("\n", "")
|
264
439
|
end
|
265
440
|
|
441
|
+
def test_ssh_public_key_encoded4
|
442
|
+
if ecdsa_supported?
|
443
|
+
assert_equal SSH_PUBLIC_KEY4, Base64.encode64(@key4.send(:ssh_public_key_conversion)).gsub("\n", "")
|
444
|
+
else
|
445
|
+
assert_raises(NotImplementedError) { Base64.encode64(@key4.send(:ssh_public_key_conversion)) }
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
266
449
|
def test_ssh_public_key_output
|
267
450
|
expected1 = "ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com"
|
268
451
|
expected2 = "ssh-rsa #{SSH_PUBLIC_KEY2} me@example.com"
|
269
452
|
expected3 = "ssh-dss #{SSH_PUBLIC_KEY3} me@example.com"
|
270
|
-
expected4 = "
|
453
|
+
expected4 = "ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY4} me@example.com"
|
454
|
+
expected1b = "ssh-rsa #{SSH_PUBLIC_KEY1}"
|
271
455
|
assert_equal expected1, @key1.ssh_public_key
|
272
456
|
assert_equal expected2, @key2.ssh_public_key
|
273
457
|
assert_equal expected3, @key3.ssh_public_key
|
274
|
-
|
458
|
+
|
459
|
+
if ecdsa_supported?
|
460
|
+
assert_equal expected4, @key4.ssh_public_key
|
461
|
+
else
|
462
|
+
assert_raises(NotImplementedError) { @key4.ssh_public_key }
|
463
|
+
end
|
464
|
+
|
465
|
+
assert_equal expected1b, @key_without_comment.ssh_public_key
|
275
466
|
end
|
276
467
|
|
277
468
|
def test_ssh2_public_key_output
|
@@ -285,6 +476,24 @@ EOF
|
|
285
476
|
'x-private-use-header' => 'some value that is long enough to go to wrap around to a new line.'})
|
286
477
|
end
|
287
478
|
|
479
|
+
def test_ssh_public_key_output_from_generated
|
480
|
+
generated_rsa = SSHKey.generate(:type => "rsa", :comment => "rsa key")
|
481
|
+
generated_dsa = SSHKey.generate(:type => "dsa", :comment => "dsa key")
|
482
|
+
generated_ecdsa = SSHKey.generate(:type => "ecdsa", :comment => "ecdsa key") if ecdsa_supported?
|
483
|
+
|
484
|
+
encoded_rsa = Base64.encode64(generated_rsa.send(:ssh_public_key_conversion)).gsub("\n", "")
|
485
|
+
encoded_dsa = Base64.encode64(generated_dsa.send(:ssh_public_key_conversion)).gsub("\n", "")
|
486
|
+
encoded_ecdsa = Base64.encode64(generated_ecdsa.send(:ssh_public_key_conversion)).gsub("\n", "") if ecdsa_supported?
|
487
|
+
|
488
|
+
expected_rsa = "ssh-rsa #{encoded_rsa} rsa key"
|
489
|
+
expected_dsa = "ssh-dss #{encoded_dsa} dsa key"
|
490
|
+
expected_ecdsa = "ecdsa-sha2-nistp256 #{encoded_ecdsa} ecdsa key"
|
491
|
+
|
492
|
+
assert_equal expected_rsa, generated_rsa.ssh_public_key
|
493
|
+
assert_equal expected_dsa, generated_dsa.ssh_public_key
|
494
|
+
assert_equal expected_ecdsa, generated_ecdsa.ssh_public_key if ecdsa_supported?
|
495
|
+
end
|
496
|
+
|
288
497
|
def test_public_key_directives
|
289
498
|
assert_equal [], SSHKey.generate.directives
|
290
499
|
|
@@ -367,6 +576,19 @@ EOF
|
|
367
576
|
assert !SSHKey.valid_ssh_public_key?(invalid4)
|
368
577
|
end
|
369
578
|
|
579
|
+
def test_ssh_public_key_validation_with_comments
|
580
|
+
expected1 = "# Comment\nssh-rsa #{SSH_PUBLIC_KEY1}"
|
581
|
+
expected2 = "# First comment\n\n# Second comment\n\nssh-ed25519 #{SSH_PUBLIC_KEY_ED25519} me@example.com"
|
582
|
+
invalid1 = "No starting hash # Valid comment\nssh-rsa #{SSH_PUBLIC_KEY1} me@example.com"
|
583
|
+
invalid2 = "# First comment\n\nSecond comment without hash\n\necdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256}\nme@example.com"
|
584
|
+
|
585
|
+
assert SSHKey.valid_ssh_public_key?(expected1)
|
586
|
+
assert SSHKey.valid_ssh_public_key?(expected2)
|
587
|
+
|
588
|
+
assert !SSHKey.valid_ssh_public_key?(invalid1)
|
589
|
+
assert !SSHKey.valid_ssh_public_key?(invalid2)
|
590
|
+
end
|
591
|
+
|
370
592
|
def test_ssh_public_key_sshfp
|
371
593
|
assert_equal KEY1_SSHFP, SSHKey.sshfp("localhost", "ssh-rsa #{SSH_PUBLIC_KEY1}\n")
|
372
594
|
assert_equal KEY2_SSHFP, SSHKey.sshfp("localhost", "ssh-rsa #{SSH_PUBLIC_KEY2}\n")
|
@@ -383,6 +605,12 @@ EOF
|
|
383
605
|
expected4 = "ssh-rsa #{SSH_PUBLIC_KEY1}"
|
384
606
|
expected5 = %Q{from="trusted.eng.cam.ac.uk",no-port-forwarding,no-pty ssh-rsa #{SSH_PUBLIC_KEY1}}
|
385
607
|
invalid1 = "#{SSH_PUBLIC_KEY1} me@example.com"
|
608
|
+
ecdsa256 = "ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256}"
|
609
|
+
ecdsa384 = "ecdsa-sha2-nistp384 #{SSH_PUBLIC_KEY_ECDSA_384}"
|
610
|
+
ecdsa521 = "ecdsa-sha2-nistp521 #{SSH_PUBLIC_KEY_ECDSA_521}"
|
611
|
+
ecdsa256_compressed = "ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256_COMPRESSED}"
|
612
|
+
ecdsa384_compressed = "ecdsa-sha2-nistp384 #{SSH_PUBLIC_KEY_ECDSA_384_COMPRESSED}"
|
613
|
+
ecdsa521_compressed = "ecdsa-sha2-nistp521 #{SSH_PUBLIC_KEY_ECDSA_521_COMPRESSED}"
|
386
614
|
|
387
615
|
assert_equal 2048, SSHKey.ssh_public_key_bits(expected1)
|
388
616
|
assert_equal 2048, SSHKey.ssh_public_key_bits(expected2)
|
@@ -390,6 +618,12 @@ EOF
|
|
390
618
|
assert_equal 2048, SSHKey.ssh_public_key_bits(expected4)
|
391
619
|
assert_equal 2048, SSHKey.ssh_public_key_bits(expected5)
|
392
620
|
assert_equal 512, SSHKey.ssh_public_key_bits(SSHKey.generate(:bits => 512).ssh_public_key)
|
621
|
+
assert_equal 256, SSHKey.ssh_public_key_bits(ecdsa256)
|
622
|
+
assert_equal 384, SSHKey.ssh_public_key_bits(ecdsa384)
|
623
|
+
assert_equal 521, SSHKey.ssh_public_key_bits(ecdsa521)
|
624
|
+
assert_equal 256, SSHKey.ssh_public_key_bits(ecdsa256_compressed)
|
625
|
+
assert_equal 384, SSHKey.ssh_public_key_bits(ecdsa384_compressed)
|
626
|
+
assert_equal 521, SSHKey.ssh_public_key_bits(ecdsa521_compressed)
|
393
627
|
|
394
628
|
exception1 = assert_raises(SSHKey::PublicKeyError) { SSHKey.ssh_public_key_bits( expected1.gsub('A','.') ) }
|
395
629
|
exception2 = assert_raises(SSHKey::PublicKeyError) { SSHKey.ssh_public_key_bits( expected1[0..-20] ) }
|
@@ -427,22 +661,43 @@ EOF
|
|
427
661
|
assert_equal KEY2_MD5_FINGERPRINT, @key2.md5_fingerprint
|
428
662
|
assert_equal KEY3_MD5_FINGERPRINT, @key3.md5_fingerprint
|
429
663
|
|
664
|
+
if ecdsa_supported?
|
665
|
+
assert_equal KEY4_MD5_FINGERPRINT, @key4.md5_fingerprint
|
666
|
+
else
|
667
|
+
assert_raises(NotImplementedError) { @key4.md5_fingerprint }
|
668
|
+
end
|
669
|
+
|
430
670
|
assert_equal KEY1_SHA1_FINGERPRINT, @key1.sha1_fingerprint
|
431
671
|
assert_equal KEY2_SHA1_FINGERPRINT, @key2.sha1_fingerprint
|
432
672
|
assert_equal KEY3_SHA1_FINGERPRINT, @key3.sha1_fingerprint
|
433
673
|
|
674
|
+
if ecdsa_supported?
|
675
|
+
assert_equal KEY4_SHA1_FINGERPRINT, @key4.sha1_fingerprint
|
676
|
+
else
|
677
|
+
assert_raises(NotImplementedError) { @key4.sha1_fingerprint }
|
678
|
+
end
|
679
|
+
|
434
680
|
assert_equal KEY1_SHA256_FINGERPRINT, @key1.sha256_fingerprint
|
435
681
|
assert_equal KEY2_SHA256_FINGERPRINT, @key2.sha256_fingerprint
|
436
682
|
assert_equal KEY3_SHA256_FINGERPRINT, @key3.sha256_fingerprint
|
437
683
|
|
684
|
+
if ecdsa_supported?
|
685
|
+
assert_equal KEY4_SHA256_FINGERPRINT, @key4.sha256_fingerprint
|
686
|
+
else
|
687
|
+
assert_raises(NotImplementedError) { @key4.sha256_fingerprint }
|
688
|
+
end
|
689
|
+
|
438
690
|
assert_equal KEY1_MD5_FINGERPRINT, SSHKey.md5_fingerprint(SSH_PRIVATE_KEY1)
|
439
691
|
assert_equal KEY1_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY1}")
|
440
692
|
assert_equal KEY2_MD5_FINGERPRINT, SSHKey.md5_fingerprint(SSH_PRIVATE_KEY2)
|
441
693
|
assert_equal KEY2_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY2} me@me.com")
|
442
694
|
assert_equal KEY3_MD5_FINGERPRINT, SSHKey.md5_fingerprint(SSH_PRIVATE_KEY3)
|
443
695
|
assert_equal KEY3_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-dss #{SSH_PUBLIC_KEY3}")
|
696
|
+
assert_equal KEY4_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY4}")
|
444
697
|
assert_equal ED25519_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}")
|
445
698
|
assert_equal ECDSA_256_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256} me@me.com")
|
699
|
+
assert_equal ECDSA_384_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ecdsa-sha2-nistp384 #{SSH_PUBLIC_KEY_ECDSA_384} me@me.com")
|
700
|
+
assert_equal ECDSA_521_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ecdsa-sha2-nistp521 #{SSH_PUBLIC_KEY_ECDSA_521} me@me.com")
|
446
701
|
|
447
702
|
assert_equal KEY1_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint(SSH_PRIVATE_KEY1)
|
448
703
|
assert_equal KEY1_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY1}")
|
@@ -450,8 +705,11 @@ EOF
|
|
450
705
|
assert_equal KEY2_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY2} me@me.com")
|
451
706
|
assert_equal KEY3_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint(SSH_PRIVATE_KEY3)
|
452
707
|
assert_equal KEY3_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-dss #{SSH_PUBLIC_KEY3}")
|
708
|
+
assert_equal KEY4_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY4}")
|
453
709
|
assert_equal ED25519_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}")
|
454
710
|
assert_equal ECDSA_256_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256} me@me.com")
|
711
|
+
assert_equal ECDSA_384_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ecdsa-sha2-nistp384 #{SSH_PUBLIC_KEY_ECDSA_384} me@me.com")
|
712
|
+
assert_equal ECDSA_521_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ecdsa-sha2-nistp521 #{SSH_PUBLIC_KEY_ECDSA_521} me@me.com")
|
455
713
|
|
456
714
|
assert_equal KEY1_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint(SSH_PRIVATE_KEY1)
|
457
715
|
assert_equal KEY1_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY1}")
|
@@ -459,14 +717,24 @@ EOF
|
|
459
717
|
assert_equal KEY2_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY2} me@me.com")
|
460
718
|
assert_equal KEY3_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint(SSH_PRIVATE_KEY3)
|
461
719
|
assert_equal KEY3_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-dss #{SSH_PUBLIC_KEY3}")
|
720
|
+
assert_equal KEY4_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY4}")
|
462
721
|
assert_equal ED25519_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}")
|
463
722
|
assert_equal ECDSA_256_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256} me@me.com")
|
723
|
+
assert_equal ECDSA_384_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ecdsa-sha2-nistp384 #{SSH_PUBLIC_KEY_ECDSA_384} me@me.com")
|
724
|
+
assert_equal ECDSA_521_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ecdsa-sha2-nistp521 #{SSH_PUBLIC_KEY_ECDSA_521} me@me.com")
|
464
725
|
end
|
465
726
|
|
466
727
|
def test_bits
|
467
728
|
assert_equal 2048, @key1.bits
|
468
729
|
assert_equal 2048, @key2.bits
|
469
730
|
assert_equal 1024, @key3.bits
|
731
|
+
|
732
|
+
if ecdsa_supported?
|
733
|
+
assert_equal 256, @key4.bits
|
734
|
+
else
|
735
|
+
assert_raises(NotImplementedError) { @key4.bits }
|
736
|
+
end
|
737
|
+
|
470
738
|
assert_equal 512, SSHKey.generate(:bits => 512).bits
|
471
739
|
end
|
472
740
|
|
@@ -474,6 +742,19 @@ EOF
|
|
474
742
|
assert_equal KEY1_RANDOMART, @key1.randomart
|
475
743
|
assert_equal KEY2_RANDOMART, @key2.randomart
|
476
744
|
assert_equal KEY3_RANDOMART, @key3.randomart
|
745
|
+
|
746
|
+
if ecdsa_supported?
|
747
|
+
assert_equal KEY4_RANDOMART, @key4.randomart
|
748
|
+
else
|
749
|
+
assert_raises(NotImplementedError) { @key4.randomart }
|
750
|
+
end
|
751
|
+
|
752
|
+
if ecdsa_supported?
|
753
|
+
assert_equal KEY4_RANDOMART_USING_SHA256_DIGEST, @key4.randomart("SHA256")
|
754
|
+
assert_equal KEY4_RANDOMART_USING_SHA384_DIGEST, @key4.randomart("SHA384")
|
755
|
+
assert_equal KEY4_RANDOMART_USING_SHA512_DIGEST, @key4.randomart("SHA512")
|
756
|
+
end
|
757
|
+
|
477
758
|
end
|
478
759
|
|
479
760
|
def test_sshfp
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sshkey
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Miller
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-08-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -45,8 +45,8 @@ executables: []
|
|
45
45
|
extensions: []
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
|
+
- ".github/workflows/ci.yml"
|
48
49
|
- ".gitignore"
|
49
|
-
- ".travis.yml"
|
50
50
|
- Gemfile
|
51
51
|
- LICENSE
|
52
52
|
- README.md
|
@@ -59,7 +59,7 @@ homepage: https://github.com/bensie/sshkey
|
|
59
59
|
licenses:
|
60
60
|
- MIT
|
61
61
|
metadata: {}
|
62
|
-
post_install_message:
|
62
|
+
post_install_message:
|
63
63
|
rdoc_options: []
|
64
64
|
require_paths:
|
65
65
|
- lib
|
@@ -67,16 +67,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
67
|
requirements:
|
68
68
|
- - ">="
|
69
69
|
- !ruby/object:Gem::Version
|
70
|
-
version: '
|
70
|
+
version: '2.5'
|
71
71
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
requirements: []
|
77
|
-
|
78
|
-
|
79
|
-
signing_key:
|
77
|
+
rubygems_version: 3.4.18
|
78
|
+
signing_key:
|
80
79
|
specification_version: 4
|
81
80
|
summary: SSH private/public key generator in Ruby
|
82
81
|
test_files:
|
data/.travis.yml
DELETED