sshkey 2.0.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://secure.travis-ci.org/bensie/sshkey.svg?branch=master)](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