sshkey 1.6.1 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dcab21406c740089ec26317a5499a2b757f03e7c
4
- data.tar.gz: 212a9a19f541aea1aeaf577cb2e938f1eae9c4fd
3
+ metadata.gz: b6e9c3f40bf3a5b7a9f80cadd7d98e8c4e9b41d4
4
+ data.tar.gz: 362495eb1d46d0befe4c932c94318ce1e40c8ec2
5
5
  SHA512:
6
- metadata.gz: ba8bf9fcef2981609256d9765e855e49771992caa20d1b87ff52a20d627a8d2571318486687b2cf51c52d6f8a510281ad351069e94cb092e44a520f069af5ab6
7
- data.tar.gz: f1f47cb1a2861aa2a8a741d8b511529c62701e943658c1dead0f6621ad639158874b4c13fc6a00f9bc6375e00ea46aab32612d5337c8e55002503f0aaac6dab9
6
+ metadata.gz: 7f99fe44852eb551a34ff7fc8ca2dcab7e51a222e9e2bb7c9708115419e1e1e08444ca03973692547b2ce5d83d4dda593b95baa3bca19b1def9df3037bae127c
7
+ data.tar.gz: 846c9b1966e56c5f405897254619367867d34855c9ebaa65f7d863ad271763427d574589c22b6570c790642a352e8fb39fb055a56ce446c18d717f17cf943f42
data/.travis.yml CHANGED
@@ -1,41 +1,21 @@
1
+ language: ruby
2
+
1
3
  rvm:
2
- - 1.8.7
3
- - 1.9.2
4
- - 1.9.3
5
- - 2.0.0
6
- - ree
4
+ - 2.0
5
+ - 2.1
6
+ - 2.2
7
+ - 2.3
8
+ - 2.4
9
+ - 2.5
10
+ - 2.6
7
11
  - ruby-head
8
- - jruby-18mode
9
- - jruby-19mode
10
- - jruby-20mode
12
+ - jruby
11
13
  - jruby-head
12
- - rbx-2.1.1
13
14
 
14
15
  matrix:
15
16
  allow_failures:
16
17
  - rvm: ruby-head
17
- include:
18
- - rvm: jruby-18mode
19
- jdk: openjdk6
20
- - rvm: jruby-18mode
21
- jdk: openjdk7
22
- - rvm: jruby-18mode
23
- jdk: oraclejdk7
24
- - rvm: jruby-19mode
25
- jdk: openjdk6
26
- - rvm: jruby-19mode
27
- jdk: openjdk7
28
- - rvm: jruby-19mode
29
- jdk: oraclejdk7
30
- - rvm: jruby-20mode
31
- jdk: openjdk6
32
- - rvm: jruby-20mode
33
- jdk: openjdk7
34
- - rvm: jruby-20mode
35
- jdk: oraclejdk7
36
- - rvm: jruby-head
37
- jdk: openjdk6
38
18
  - rvm: jruby-head
39
- jdk: openjdk7
40
- - rvm: jruby-head
41
- jdk: oraclejdk7
19
+
20
+ sudo: required
21
+ dist: xenial
data/Gemfile CHANGED
@@ -1,6 +1,5 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gem "jruby-openssl", ">= 0.8.2", :platforms => :jruby
4
- gem "rubysl", "~> 2.0.14", :platforms => :rbx
3
+ gem "jruby-openssl", ">= 0.8.2", platform: :jruby
5
4
 
6
5
  gemspec
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2013 James Miller
1
+ Copyright (c) 2011-2016 James Miller
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -2,11 +2,15 @@
2
2
 
3
3
  Generate private and public SSH keys (RSA and DSA supported) using pure Ruby.
4
4
 
5
- gem install sshkey
5
+ [![Build Status](https://secure.travis-ci.org/bensie/sshkey.svg?branch=master)](http://travis-ci.org/bensie/sshkey)
6
6
 
7
- Tested on the following Rubies: MRI 1.8.7, 1.9.2, 1.9.3, 2.0.0, REE, JRuby (1.7.2 and newer), Rubinius (2.1.1 and newer). Ruby must be compiled with OpenSSL support.
7
+ ## Requirements
8
8
 
9
- [![Build Status](https://secure.travis-ci.org/bensie/sshkey.png)](http://travis-ci.org/bensie/sshkey)
9
+ Tested / supported on CRuby 2.0.0+ and JRuby.
10
+
11
+ ## Installation
12
+
13
+ gem install sshkey
10
14
 
11
15
  ## Usage
12
16
 
@@ -18,7 +22,12 @@ You can also (optionally) supply a `comment` or `passphrase`.
18
22
  ```ruby
19
23
  k = SSHKey.generate
20
24
 
21
- k = SSHKey.generate(:type => "DSA", :bits => 1024, :comment => "foo@bar.com", :passphrase => "foobar")
25
+ k = SSHKey.generate(
26
+ type: "DSA",
27
+ bits: 1024,
28
+ comment: "foo@bar.com",
29
+ passphrase: "foobar"
30
+ )
22
31
  ```
23
32
 
24
33
  ### Use your existing key
@@ -26,7 +35,8 @@ k = SSHKey.generate(:type => "DSA", :bits => 1024, :comment => "foo@bar.com", :p
26
35
  Return an SSHKey object from an existing RSA or DSA private key (provided as a string).
27
36
 
28
37
  ```ruby
29
- k = SSHKey.new(File.read("~/.ssh/id_rsa"), :comment => "foo@bar.com")
38
+ f = File.read(File.expand_path("~/.ssh/id_rsa"))
39
+ k = SSHKey.new(f, comment: "foo@bar.com")
30
40
  ```
31
41
 
32
42
  ### The SSHKey object
@@ -52,11 +62,11 @@ k.ssh_public_key
52
62
 
53
63
  #### Encryption
54
64
 
55
- If a passcode is set when a key is generated or by setting the `passcode` accessor, you can
65
+ If a passphrase is set when a key is generated or by setting the `passphrase` accessor, you can
56
66
  fetch the encrypted version of the private key.
57
67
 
58
68
  ```ruby
59
- k.passcode = "foo"
69
+ k.passphrase = "foo"
60
70
  # => "foo"
61
71
 
62
72
  k.encrypted_private_key
@@ -77,6 +87,9 @@ k.comment = "me@me.com"
77
87
 
78
88
  k.ssh_public_key
79
89
  # => "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7 me@me.com"
90
+
91
+ k.ssh2_public_key
92
+ # => "---- BEGIN SSH2 PUBLIC KEY ----\nComment: me@me.com\nAAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+n\nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5\nXsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoA\nv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I\n9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVIC\nWtKbqW263HT5LvSxwKorR7\n---- END SSH2 PUBLIC KEY ----"
80
93
  ```
81
94
 
82
95
  #### Bit length
@@ -91,14 +104,17 @@ k.bits
91
104
  #### Fingerprints
92
105
 
93
106
  It is often helpful to use a fingerprint to visually or programmatically check if one key
94
- matches another. Fetch either an MD5 (OpenSSH default) or SHA1 fingerprint of the SSH public key.
107
+ matches another. Fetch an MD5, SHA1, or SHA256 fingerprint of the SSH public key.
95
108
 
96
109
  ```ruby
97
110
  k.md5_fingerprint
98
- # => "2a:89:84:c9:29:05:d1:f8:49:79:1c:ba:73:99:eb:af"
111
+ # => "04:1b:d4:18:df:87:60:94:8c:83:8a:7b:5a:35:59:3d"
99
112
 
100
113
  k.sha1_fingerprint
101
- # => "e4:f9:79:f2:fe:d6:be:2d:ef:2e:c2:fa:aa:f8:b0:17:34:fe:0d:c0"
114
+ # => "e5:c2:43:9e:e4:0c:0c:47:82:7a:3b:e9:61:13:bd:9c:43:eb:4c:b7"
115
+
116
+ k.sha256_fingerprint
117
+ # => "x1GEnx1SRY/QwxjMAoyO6mhQlaBedDHtYLEmfeUXy3o="
102
118
  ```
103
119
 
104
120
  #### Public Key Directives
@@ -126,7 +142,7 @@ k.ssh_public_key
126
142
 
127
143
  #### Randomart
128
144
 
129
- Generate [OpenSSH compatible](http://www.opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/key.c) ASCII art fingerprints
145
+ Generate [OpenSSH compatible](http://www.opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/key.c) ASCII art fingerprints.
130
146
 
131
147
  ```ruby
132
148
  puts k.randomart
@@ -165,7 +181,7 @@ SSHKey.valid_ssh_public_key? "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE
165
181
 
166
182
  #### Bit length
167
183
 
168
- Determine the strenth of the key in bits as an integer. Returns `SSHKey::PublicKeyError` if bits cannot be determined.
184
+ Determine the strength of the key in bits as an integer. Returns `SSHKey::PublicKeyError` if bits cannot be determined.
169
185
 
170
186
  ```ruby
171
187
  SSHKey.ssh_public_key_bits "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7"
@@ -174,16 +190,27 @@ SSHKey.ssh_public_key_bits "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o
174
190
 
175
191
  #### Fingerprints
176
192
 
177
- Fetch either an MD5 (OpenSSH default) or SHA1 fingerprint of the SSH
178
- public key. Returns `SSHKey::PublicKeyError` if a fingerprint cannot be determined.
193
+ Fetch an MD5, SHA1, or SHA256 fingerprint of the SSH public key.
194
+ Returns `SSHKey::PublicKeyError` if a fingerprint cannot be determined.
179
195
 
180
196
  ```ruby
181
197
  SSHKey.fingerprint "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7"
182
198
  # => "04:1b:d4:18:df:87:60:94:8c:83:8a:7b:5a:35:59:3d"
183
199
  SSHKey.sha1_fingerprint "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7"
184
200
  # => "e5:c2:43:9e:e4:0c:0c:47:82:7a:3b:e9:61:13:bd:9c:43:eb:4c:b7"
201
+ SSHKey.sha256_fingerprint "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7"
202
+ # => "x1GEnx1SRY/QwxjMAoyO6mhQlaBedDHtYLEmfeUXy3o="
203
+ ```
204
+
205
+ #### Convert to SSH2 Public Key
206
+
207
+ Convert an existing SSH Public Key into an SSH2 Public key. Returns `SSHKey::PublicKeyError` if a valid key cannot be generated.
208
+
209
+ ```ruby
210
+ SSHKey.ssh_public_key_to_ssh2_public_key "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7 me@me.com"
211
+ # => "---- BEGIN SSH2 PUBLIC KEY ----\nComment: me@me.com\nAAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+n\nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5\nXsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoA\nv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I\n9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVIC\nWtKbqW263HT5LvSxwKorR7\n---- END SSH2 PUBLIC KEY ----"
185
212
  ```
186
213
 
187
214
  ## Copyright
188
215
 
189
- Copyright (c) 2011-2013 James Miller
216
+ Copyright (c) 2011-2019 James Miller
data/lib/sshkey.rb CHANGED
@@ -1,14 +1,27 @@
1
- $:.unshift File.dirname(__FILE__)
2
-
3
1
  require 'openssl'
4
2
  require 'base64'
5
3
  require 'digest/md5'
6
4
  require 'digest/sha1'
7
- require 'sshkey/exception'
5
+ require 'digest/sha2'
8
6
 
9
7
  class SSHKey
10
- SSH_TYPES = {"rsa" => "ssh-rsa", "dsa" => "ssh-dss"}
8
+ SSH_TYPES = {
9
+ "ssh-rsa" => "rsa",
10
+ "ssh-dss" => "dsa",
11
+ "ssh-ed25519" => "ed25519",
12
+ "ecdsa-sha2-nistp256" => "ecdsa",
13
+ "ecdsa-sha2-nistp384" => "ecdsa",
14
+ "ecdsa-sha2-nistp521" => "ecdsa",
15
+ }
16
+
17
+ SSHFP_TYPES = {
18
+ "rsa" => 1,
19
+ "dsa" => 2,
20
+ "ecdsa" => 3,
21
+ "ed25519" => 4,
22
+ }
11
23
  SSH_CONVERSION = {"rsa" => ["e", "n"], "dsa" => ["p", "q", "g", "pub_key"]}
24
+ SSH2_LINE_LENGTH = 70 # +1 (for line wrap '/' character) must be <= 72
12
25
 
13
26
  class << self
14
27
  # Generate a new keypair and return an SSHKey object
@@ -30,7 +43,7 @@ class SSHKey
30
43
  default_bits = type == "rsa" ? 2048 : 1024
31
44
 
32
45
  bits = options[:bits] || default_bits
33
- cipher = OpenSSL::Cipher::Cipher.new("AES-128-CBC") if options[:passphrase]
46
+ cipher = OpenSSL::Cipher.new("AES-128-CBC") if options[:passphrase]
34
47
 
35
48
  case type.downcase
36
49
  when "rsa" then new(OpenSSL::PKey::RSA.generate(bits).to_pem(cipher, options[:passphrase]), options)
@@ -49,7 +62,17 @@ class SSHKey
49
62
  #
50
63
  def valid_ssh_public_key?(ssh_public_key)
51
64
  ssh_type, encoded_key = parse_ssh_public_key(ssh_public_key)
52
- SSH_CONVERSION[SSH_TYPES.invert[ssh_type]].size == unpacked_byte_array(ssh_type, encoded_key).size
65
+ sections = unpacked_byte_array(ssh_type, encoded_key)
66
+ case ssh_type
67
+ when "ssh-rsa", "ssh-dss"
68
+ sections.size == SSH_CONVERSION[SSH_TYPES[ssh_type]].size
69
+ when "ssh-ed25519"
70
+ sections.size == 1 # https://tools.ietf.org/id/draft-bjh21-ssh-ed25519-00.html#rfc.section.4
71
+ when "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521"
72
+ sections.size == 2 # https://tools.ietf.org/html/rfc5656#section-3.1
73
+ else
74
+ false
75
+ end
53
76
  rescue
54
77
  false
55
78
  end
@@ -88,10 +111,59 @@ class SSHKey
88
111
  end
89
112
  end
90
113
 
114
+ # SHA256 fingerprint for the given SSH key
115
+ def sha256_fingerprint(key)
116
+ if key.match(/PRIVATE/)
117
+ new(key).sha256_fingerprint
118
+ else
119
+ Base64.encode64(Digest::SHA256.digest(decoded_key(key))).gsub("\n", "")
120
+ end
121
+ end
122
+
123
+ # SSHFP records for the given SSH key
124
+ def sshfp(hostname, key)
125
+ if key.match(/PRIVATE/)
126
+ new(key).sshfp hostname
127
+ else
128
+ type, encoded_key = parse_ssh_public_key(key)
129
+ format_sshfp_record(hostname, SSH_TYPES[type], Base64.decode64(encoded_key))
130
+ end
131
+ end
132
+
133
+ # Convert an existing SSH public key to SSH2 (RFC4716) public key
134
+ #
135
+ # ==== Parameters
136
+ # * ssh_public_key<~String> - "ssh-rsa AAAAB3NzaC1yc2EA...."
137
+ # * headers<~Hash> - The Key will be used as the header-tag and the value as the header-value
138
+ #
139
+ def ssh_public_key_to_ssh2_public_key(ssh_public_key, headers = nil)
140
+ raise PublicKeyError, "invalid ssh public key" unless SSHKey.valid_ssh_public_key?(ssh_public_key)
141
+
142
+ _source_format, source_key = parse_ssh_public_key(ssh_public_key)
143
+
144
+ # Add a 'Comment' Header Field unless others are explicitly passed in
145
+ if source_comment = ssh_public_key.split(source_key)[1]
146
+ headers = {'Comment' => source_comment.strip} if headers.nil? && !source_comment.empty?
147
+ end
148
+ header_fields = build_ssh2_headers(headers)
149
+
150
+ ssh2_key = "---- BEGIN SSH2 PUBLIC KEY ----\n"
151
+ ssh2_key << header_fields unless header_fields.nil?
152
+ ssh2_key << source_key.scan(/.{1,#{SSH2_LINE_LENGTH}}/).join("\n")
153
+ ssh2_key << "\n---- END SSH2 PUBLIC KEY ----"
154
+ end
155
+
156
+ def format_sshfp_record(hostname, type, key)
157
+ [[Digest::SHA1, 1], [Digest::SHA256, 2]].map { |f, num|
158
+ fpr = f.hexdigest(key)
159
+ "#{hostname} IN SSHFP #{SSHFP_TYPES[type]} #{num} #{fpr}"
160
+ }.join("\n")
161
+ end
162
+
91
163
  private
92
164
 
93
165
  def unpacked_byte_array(ssh_type, encoded_key)
94
- prefix = [7].pack("N") + ssh_type
166
+ prefix = [ssh_type.length].pack("N") + ssh_type
95
167
  decoded = Base64.decode64(encoded_key)
96
168
 
97
169
  # Base64 decoding is too permissive, so we should validate if encoding is correct
@@ -99,16 +171,26 @@ class SSHKey
99
171
  raise PublicKeyError, "validation error"
100
172
  end
101
173
 
174
+ byte_count = 0
102
175
  data = []
103
176
  until decoded.empty?
104
177
  front = decoded.slice!(0,4)
105
178
  size = front.unpack("N").first
106
179
  segment = decoded.slice!(0, size)
180
+ byte_count += segment.length
107
181
  unless front.length == 4 && segment.length == size
108
182
  raise PublicKeyError, "byte array too short"
109
183
  end
110
184
  data << OpenSSL::BN.new(segment, 2)
111
185
  end
186
+
187
+
188
+ if ssh_type == "ssh-ed25519"
189
+ unless byte_count == 32
190
+ raise PublicKeyError, "validation error, ed25519 key length not OK"
191
+ end
192
+ end
193
+
112
194
  return data
113
195
  end
114
196
 
@@ -121,12 +203,28 @@ class SSHKey
121
203
  end
122
204
 
123
205
  def parse_ssh_public_key(public_key)
206
+ raise PublicKeyError, "newlines are not permitted between key data" if public_key =~ /\n(?!$)/
207
+
124
208
  parsed = public_key.split(" ")
125
209
  parsed.each_with_index do |el, index|
126
- return parsed[index..(index+1)] if SSH_TYPES.invert[el]
210
+ return parsed[index..(index+1)] if SSH_TYPES[el]
127
211
  end
128
212
  raise PublicKeyError, "cannot determine key type"
129
213
  end
214
+
215
+ def build_ssh2_headers(headers = {})
216
+ return nil if headers.nil? || headers.empty?
217
+
218
+ headers.keys.sort.collect do |header_tag|
219
+ # header-tag must be us-ascii & <= 64 bytes and header-data must be UTF-8 & <= 1024 bytes
220
+ raise PublicKeyError, "SSH2 header-tag '#{header_tag}' must be US-ASCII" unless header_tag.each_byte.all? {|b| b < 128 }
221
+ raise PublicKeyError, "SSH2 header-tag '#{header_tag}' must be <= 64 bytes" unless header_tag.size <= 64
222
+ raise PublicKeyError, "SSH2 header-value for '#{header_tag}' must be <= 1024 bytes" unless headers[header_tag].size <= 1024
223
+
224
+ header_field = "#{header_tag}: #{headers[header_tag]}"
225
+ header_field.scan(/.{1,#{SSH2_LINE_LENGTH}}/).join("\\\n")
226
+ end.join("\n") << "\n"
227
+ end
130
228
  end
131
229
 
132
230
  attr_reader :key_object, :type
@@ -168,7 +266,7 @@ class SSHKey
168
266
  # If no passphrase is set, returns the unencrypted private key
169
267
  def encrypted_private_key
170
268
  return private_key unless passphrase
171
- key_object.to_pem(OpenSSL::Cipher::Cipher.new("AES-128-CBC"), passphrase)
269
+ key_object.to_pem(OpenSSL::Cipher.new("AES-128-CBC"), passphrase)
172
270
  end
173
271
 
174
272
  # Fetch the RSA/DSA public key
@@ -182,7 +280,20 @@ class SSHKey
182
280
 
183
281
  # SSH public key
184
282
  def ssh_public_key
185
- [directives.join(",").strip, SSH_TYPES[type], Base64.encode64(ssh_public_key_conversion).gsub("\n", ""), comment].join(" ").strip
283
+ [directives.join(",").strip, SSH_TYPES.invert[type], Base64.encode64(ssh_public_key_conversion).gsub("\n", ""), comment].join(" ").strip
284
+ end
285
+
286
+ # SSH2 public key (RFC4716)
287
+ #
288
+ # ==== Parameters
289
+ # * headers<~Hash> - Keys will be used as header-tags and values as header-values.
290
+ #
291
+ # ==== Examples
292
+ # {'Comment' => '2048-bit RSA created by user@example'}
293
+ # {'x-private-use-tag' => 'Private Use Value'}
294
+ #
295
+ def ssh2_public_key(headers = nil)
296
+ self.class.ssh_public_key_to_ssh2_public_key(ssh_public_key, headers)
186
297
  end
187
298
 
188
299
  # Fingerprints
@@ -198,6 +309,11 @@ class SSHKey
198
309
  Digest::SHA1.hexdigest(ssh_public_key_conversion).gsub(/(.{2})(?=.)/, '\1:\2')
199
310
  end
200
311
 
312
+ # SHA256 fingerprint for the given SSH public key
313
+ def sha256_fingerprint
314
+ Base64.encode64(Digest::SHA256.digest(ssh_public_key_conversion)).gsub("\n", "")
315
+ end
316
+
201
317
  # Determine the length (bits) of the key as an integer
202
318
  def bits
203
319
  self.class.ssh_public_key_bits(ssh_public_key)
@@ -260,6 +376,10 @@ class SSHKey
260
376
  output
261
377
  end
262
378
 
379
+ def sshfp(hostname)
380
+ self.class.format_sshfp_record(hostname, @type, ssh_public_key_conversion)
381
+ end
382
+
263
383
  def directives=(directives)
264
384
  @directives = Array[directives].flatten.compact
265
385
  end
@@ -277,7 +397,7 @@ class SSHKey
277
397
  # For instance, the "ssh-rsa" string is encoded as the following byte array
278
398
  # [0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a']
279
399
  def ssh_public_key_conversion
280
- typestr = SSH_TYPES[type]
400
+ typestr = SSH_TYPES.invert[type]
281
401
  methods = SSH_CONVERSION[type]
282
402
  pubkey = key_object.public_key
283
403
  methods.inject([7].pack("N") + typestr) do |pubkeystr, m|
@@ -300,4 +420,6 @@ class SSHKey
300
420
  pubkeystr + [data.length].pack("N") + data
301
421
  end
302
422
  end
423
+
424
+ class PublicKeyError < StandardError; end
303
425
  end
@@ -1,3 +1,3 @@
1
1
  class SSHKey
2
- VERSION = "1.6.1"
2
+ VERSION = "2.0.0"
3
3
  end
data/sshkey.gemspec CHANGED
@@ -21,4 +21,5 @@ Gem::Specification.new do |s|
21
21
  s.require_paths = ["lib"]
22
22
 
23
23
  s.add_development_dependency("rake")
24
+ s.add_development_dependency("test-unit")
24
25
  end
data/test/sshkey_test.rb CHANGED
@@ -79,13 +79,30 @@ EOF
79
79
  SSH_PUBLIC_KEY2 = 'AAAAB3NzaC1yc2EAAAABIwAAAQEAxl6TpN7uFiY/JZ8qDnD7UrxDP+ABeh2PVg8Du1LEgXNk0+YWCeP5S6oHklqaWeDlbmAs1oHsBwCMAVpMa5tgONOLvz4JgwgkiqQEbKR8ofWJ+LADUElvqRVGmGiNEMLI6GJWeneL4sjmbb8d6U+M53c6iWG0si9XE5m7teBQSsCl0Tk3qMIkQGw5zpJeCXjZ8KpJhIJRYgexFkGgPlYRV+UYIhxpUW90t0Ra5i6JOFYwq98k5S/6SJIZQ/A9F4JNzwLw3eVxZj0yVHWxkGz1+TyELNY1kOyMxnZaqSfGzSQJTrnIXpdweVHuYh1LtOgedRQhCyiELeSMGwio1vRPKw=='
80
80
  SSH_PUBLIC_KEY3 = 'AAAAB3NzaC1kc3MAAACBALyVy5dwVwgL3CxXzsvo8DBh58qArQLBNIPW/f9pptmy7jD5QXzOw+12w0/z4lZ86ncoVutRMf44OABcX9ovhRl+luxB7jjpkVXy/p2ZaqPbeyTQUtdTmXa2y4n053Jd61VeMG+iLP7+viT+Ib96y9aVUYQfCrl5heBDUZ9cAFjdAAAAFQDFXnO7JJpFKwkeoor4GWGHtz0D2QAAAIEAqel0RUBO0MY5b3DZ69J/mRzUifN1O6twk4er2ph0JpryuUwZohLpcVZwqoGWmPQy/ZHmV1b3RtT9GWUa+HUqKdMhFVOx/iq1khVfLi83whjMMvXj3ecqd0yzGxGHnSsjVKefa2ywCLHrh4nlUVIaXI5gQpgMyVbMcromDe1WZzoAAACBAIwTRPAEcroqOzaebiVspFcmsXxDQ4wXQZQdho1ExW6FKS8s7/6pItmZYXTvJDwLXgq2/iK1fRRcKk2PJEaSuJR7WeNGsJKfWmQ2UbOhqA3wWLDazIZtcMKjFzD0hM4E8qgjHjMvKDE6WgT6SFP+tqx3nnh7pJWwsbGjSMQexpyR'
81
81
 
82
+ SSH_PUBLIC_KEY_ED25519 = 'AAAAC3NzaC1lZDI1NTE5AAAAIBrNsRCISAtKXV5OVxqV6unVcdis5Uh3oiC6B7CMB7HQ'
83
+ SSH_PUBLIC_KEY_ED25519_0_BYTE = 'AAAAC3NzaC1lZDI1NTE5AAAAIADK9x9t3yQQH7h4OEJpUa7l2j7mcmKf4LAsNXHxNbSm'
84
+
85
+ SSH_PUBLIC_KEY_ECDSA_256 = 'AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHJFDZ5qymZfIzoJcxYeu3C9HjJ08QAbqR28C2zSMLwcb3ZzWdRApnj6wEgRvizsBmr9zyPKb2u5Rp0vjJtQcZo='
86
+ SSH_PUBLIC_KEY_ECDSA_384 = 'AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBP+GtUCOR8aW7xTtpkbJS0qqNZ98PgbUNtTFhE+Oe+khgoFMX+o0JG5bckVuvtkRl8dr+63kUK0QPTtzP9O5yixB9CYnB8CgCgYo1FCXZuJIImf12wW5nWKglrCH4kV1Qg=='
87
+ SSH_PUBLIC_KEY_ECDSA_521 = 'AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACsunidnIZ77AjCHSDp/xknLGDW3M0Ia7nxLdImmp0XGbxtbwYm2ga5XUzV9dMO9wF9ICC3OuH6g9DtGOBNPru1PwFDjaPISGgm0vniEzWazLsvjJVLThOA3VyYLxmtjm0WfS+/DfxgWVS6oeCTnDjjoVVpwU/fDbUbYPPRZI84/hOGNA=='
88
+
82
89
  KEY1_MD5_FINGERPRINT = "2a:89:84:c9:29:05:d1:f8:49:79:1c:ba:73:99:eb:af"
83
90
  KEY2_MD5_FINGERPRINT = "3c:af:74:87:cc:cc:a1:12:05:1a:09:b7:7b:ce:ed:ce"
84
91
  KEY3_MD5_FINGERPRINT = "14:f6:6a:12:96:be:44:32:e6:3c:77:43:94:52:f5:7a"
92
+ ED25519_MD5_FINGERPRINT = "6f:1a:8a:c1:4f:13:5c:36:6e:3f:be:eb:49:3b:8e:3e"
93
+ ECDSA_256_MD5_FINGERPRINT = "d9:3a:7f:de:b2:65:04:ac:62:05:1a:1e:97:e9:2b:9d"
85
94
 
86
95
  KEY1_SHA1_FINGERPRINT = "e4:f9:79:f2:fe:d6:be:2d:ef:2e:c2:fa:aa:f8:b0:17:34:fe:0d:c0"
87
96
  KEY2_SHA1_FINGERPRINT = "9a:52:78:2b:6b:cb:39:b7:85:ed:90:8a:28:62:aa:b3:98:88:e6:07"
88
97
  KEY3_SHA1_FINGERPRINT = "15:68:c6:72:ac:18:d1:fc:ab:a2:b7:b5:8c:d1:fe:8f:b9:ae:a9:47"
98
+ ED25519_SHA1_FINGERPRINT = "57:41:7c:d0:e2:53:28:87:7e:87:53:d4:69:ef:ef:63:ec:c0:0e:5e"
99
+ ECDSA_256_SHA1_FINGERPRINT = "94:e8:92:2b:1b:ec:49:de:ff:85:ea:6e:10:d6:8d:87:7a:67:40:ee"
100
+
101
+ KEY1_SHA256_FINGERPRINT = "js3llFehloxCfsVuDw5xu3NtS9AOAxcXY8WL6vkDIts="
102
+ KEY2_SHA256_FINGERPRINT = "23f/6U/LdxIFx1CQFKHylw76n+LIHYoY4nRxKcFoos4="
103
+ KEY3_SHA256_FINGERPRINT = "mPqEPQlOPGORrTJrU17sPax1jOqeutZja6MOsFIca+8="
104
+ ED25519_SHA256_FINGERPRINT = "gyzHUKl1eO8Bk1Cvn4joRgxRlXo1+1HJ3Vho/hAtKEg="
105
+ ECDSA_256_SHA256_FINGERPRINT = "ncy2crhoL44R58GCZPQ5chPRrjlQKKgu07FDNelDmdk="
89
106
 
90
107
  KEY1_RANDOMART = <<-EOF.rstrip
91
108
  +--[ RSA 2048]----+
@@ -127,6 +144,61 @@ EOF
127
144
  | |
128
145
  | |
129
146
  +-----------------+
147
+ EOF
148
+
149
+ KEY1_SSHFP = <<-EOF.rstrip
150
+ localhost IN SSHFP 1 1 e4f979f2fed6be2def2ec2faaaf8b01734fe0dc0
151
+ localhost IN SSHFP 1 2 8ecde59457a1968c427ec56e0f0e71bb736d4bd00e03171763c58beaf90322db
152
+ EOF
153
+
154
+ KEY2_SSHFP = <<-EOF.rstrip
155
+ localhost IN SSHFP 1 1 9a52782b6bcb39b785ed908a2862aab39888e607
156
+ localhost IN SSHFP 1 2 db77ffe94fcb771205c7509014a1f2970efa9fe2c81d8a18e2747129c168a2ce
157
+ EOF
158
+
159
+ KEY3_SSHFP = <<-EOF.rstrip
160
+ localhost IN SSHFP 2 1 1568c672ac18d1fcaba2b7b58cd1fe8fb9aea947
161
+ localhost IN SSHFP 2 2 98fa843d094e3c6391ad326b535eec3dac758cea9ebad6636ba30eb0521c6bef
162
+ EOF
163
+
164
+ SSH2_PUBLIC_KEY1 = <<-EOF.rstrip
165
+ ---- BEGIN SSH2 PUBLIC KEY ----
166
+ Comment: me@example.com
167
+ AAAAB3NzaC1yc2EAAAABIwAAAQEArfTA/lKVR84IMc9ZzXOCHr8DVtR8hzWuEVHF6KElav
168
+ RHlk14g0SZu3m908Ejm/XF3EfNHjX9wN+62IMA0QBxkBMFCuLF+U/oeUs0NoDdAEKxjj4n
169
+ 6lq6Ss8aLct+anMy7D1jwvOLbcwV54w1d5JDdlZVdZ6AvHm9otwJq6rNpDgdmXY4HgC2nM
170
+ 9csFpuy0cDpL6fdJx9lcNL2RnkRC4+RMsIB+PxDw0j3vDi04dYLBXMGYjyeGH+mIFpL3PT
171
+ PXGXwL2XDYXZ2H4SQX6bOoKmazTXq6QXuEB665njh1GxXldoIMcSshoJL0hrk3WrTOG22N
172
+ 2CQA+IfHgrXJ+A+QUzKQ==
173
+ ---- END SSH2 PUBLIC KEY ----
174
+ EOF
175
+
176
+ SSH2_PUBLIC_KEY2 = <<-EOF.rstrip
177
+ ---- BEGIN SSH2 PUBLIC KEY ----
178
+ AAAAB3NzaC1yc2EAAAABIwAAAQEAxl6TpN7uFiY/JZ8qDnD7UrxDP+ABeh2PVg8Du1LEgX
179
+ Nk0+YWCeP5S6oHklqaWeDlbmAs1oHsBwCMAVpMa5tgONOLvz4JgwgkiqQEbKR8ofWJ+LAD
180
+ UElvqRVGmGiNEMLI6GJWeneL4sjmbb8d6U+M53c6iWG0si9XE5m7teBQSsCl0Tk3qMIkQG
181
+ w5zpJeCXjZ8KpJhIJRYgexFkGgPlYRV+UYIhxpUW90t0Ra5i6JOFYwq98k5S/6SJIZQ/A9
182
+ F4JNzwLw3eVxZj0yVHWxkGz1+TyELNY1kOyMxnZaqSfGzSQJTrnIXpdweVHuYh1LtOgedR
183
+ QhCyiELeSMGwio1vRPKw==
184
+ ---- END SSH2 PUBLIC KEY ----
185
+ EOF
186
+
187
+ SSH2_PUBLIC_KEY3 = <<-EOF.rstrip
188
+ ---- BEGIN SSH2 PUBLIC KEY ----
189
+ Comment: 1024-bit DSA with provided comment
190
+ x-private-use-header: some value that is long enough to go to wrap aro\\
191
+ und to a new line.
192
+ AAAAB3NzaC1kc3MAAACBALyVy5dwVwgL3CxXzsvo8DBh58qArQLBNIPW/f9pptmy7jD5QX
193
+ zOw+12w0/z4lZ86ncoVutRMf44OABcX9ovhRl+luxB7jjpkVXy/p2ZaqPbeyTQUtdTmXa2
194
+ y4n053Jd61VeMG+iLP7+viT+Ib96y9aVUYQfCrl5heBDUZ9cAFjdAAAAFQDFXnO7JJpFKw
195
+ keoor4GWGHtz0D2QAAAIEAqel0RUBO0MY5b3DZ69J/mRzUifN1O6twk4er2ph0JpryuUwZ
196
+ ohLpcVZwqoGWmPQy/ZHmV1b3RtT9GWUa+HUqKdMhFVOx/iq1khVfLi83whjMMvXj3ecqd0
197
+ yzGxGHnSsjVKefa2ywCLHrh4nlUVIaXI5gQpgMyVbMcromDe1WZzoAAACBAIwTRPAEcroq
198
+ OzaebiVspFcmsXxDQ4wXQZQdho1ExW6FKS8s7/6pItmZYXTvJDwLXgq2/iK1fRRcKk2PJE
199
+ aSuJR7WeNGsJKfWmQ2UbOhqA3wWLDazIZtcMKjFzD0hM4E8qgjHjMvKDE6WgT6SFP+tqx3
200
+ nnh7pJWwsbGjSMQexpyR
201
+ ---- END SSH2 PUBLIC KEY ----
130
202
  EOF
131
203
 
132
204
  def setup
@@ -202,6 +274,17 @@ EOF
202
274
  assert_equal expected4, @key_without_comment.ssh_public_key
203
275
  end
204
276
 
277
+ def test_ssh2_public_key_output
278
+ expected1 = SSH2_PUBLIC_KEY1
279
+ expected2 = SSH2_PUBLIC_KEY2
280
+ expected3 = SSH2_PUBLIC_KEY3
281
+
282
+ assert_equal expected1, @key1.ssh2_public_key
283
+ assert_equal expected2, @key2.ssh2_public_key({})
284
+ assert_equal expected3, @key3.ssh2_public_key({'Comment' => '1024-bit DSA with provided comment',
285
+ 'x-private-use-header' => 'some value that is long enough to go to wrap around to a new line.'})
286
+ end
287
+
205
288
  def test_public_key_directives
206
289
  assert_equal [], SSHKey.generate.directives
207
290
 
@@ -253,6 +336,46 @@ EOF
253
336
  assert !SSHKey.valid_ssh_public_key?(invalid5)
254
337
  end
255
338
 
339
+ def test_ssh_public_key_validation_elliptic
340
+ assert SSHKey.valid_ssh_public_key?("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519} me@example.com")
341
+ assert SSHKey.valid_ssh_public_key?("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519_0_BYTE} me@example.com")
342
+ assert SSHKey.valid_ssh_public_key?("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256}")
343
+ assert SSHKey.valid_ssh_public_key?("ecdsa-sha2-nistp384 #{SSH_PUBLIC_KEY_ECDSA_384} me@example.com")
344
+ assert SSHKey.valid_ssh_public_key?(%Q{from="trusted.eng.cam.ac.uk",no-port-forwarding,no-pty ecdsa-sha2-nistp521 #{SSH_PUBLIC_KEY_ECDSA_521} me@example.com})
345
+
346
+ assert !SSHKey.valid_ssh_public_key?("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}= me@example.com") # bad base64
347
+ assert !SSHKey.valid_ssh_public_key?("ssh-ed25519 #{SSH_PUBLIC_KEY_ECDSA_384} me@example.com") # mismatched key format
348
+ assert !SSHKey.valid_ssh_public_key?("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_384} me@example.com") # mismatched key format
349
+ assert !SSHKey.valid_ssh_public_key?("ssh-ed25519 asdf me@example.com") # gibberish key data
350
+ assert !SSHKey.valid_ssh_public_key?("ecdsa-sha2-nistp256 asdf me@example.com") # gibberish key data
351
+ end
352
+
353
+ def test_ssh_public_key_validation_with_newlines
354
+ expected1 = "ssh-rsa #{SSH_PUBLIC_KEY1}\n"
355
+ expected2 = "ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519} me@example.com\n"
356
+ invalid1 = "ssh-rsa #{SSH_PUBLIC_KEY1}\nme@example.com"
357
+ invalid2 = "ssh-rsa #{SSH_PUBLIC_KEY1}\n me@example.com"
358
+ invalid3 = "ssh-rsa #{SSH_PUBLIC_KEY1} \nme@example.com"
359
+ invalid4 = "ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256}\nme@example.com"
360
+
361
+ assert SSHKey.valid_ssh_public_key?(expected1)
362
+ assert SSHKey.valid_ssh_public_key?(expected2)
363
+
364
+ assert !SSHKey.valid_ssh_public_key?(invalid1)
365
+ assert !SSHKey.valid_ssh_public_key?(invalid2)
366
+ assert !SSHKey.valid_ssh_public_key?(invalid3)
367
+ assert !SSHKey.valid_ssh_public_key?(invalid4)
368
+ end
369
+
370
+ def test_ssh_public_key_sshfp
371
+ assert_equal KEY1_SSHFP, SSHKey.sshfp("localhost", "ssh-rsa #{SSH_PUBLIC_KEY1}\n")
372
+ assert_equal KEY2_SSHFP, SSHKey.sshfp("localhost", "ssh-rsa #{SSH_PUBLIC_KEY2}\n")
373
+ assert_equal KEY3_SSHFP, SSHKey.sshfp("localhost", "ssh-dss #{SSH_PUBLIC_KEY3}\n")
374
+ assert_equal KEY1_SSHFP, SSHKey.sshfp("localhost", SSH_PRIVATE_KEY1)
375
+ assert_equal KEY2_SSHFP, SSHKey.sshfp("localhost", SSH_PRIVATE_KEY2)
376
+ assert_equal KEY3_SSHFP, SSHKey.sshfp("localhost", SSH_PRIVATE_KEY3)
377
+ end
378
+
256
379
  def test_ssh_public_key_bits
257
380
  expected1 = "ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com"
258
381
  expected2 = "ssh-rsa #{SSH_PUBLIC_KEY2} me@example.com"
@@ -277,6 +400,17 @@ EOF
277
400
  assert_equal( "cannot determine key type", exception3.message )
278
401
  end
279
402
 
403
+ def test_ssh2_public_key_bits
404
+ public_key1 = "ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com"
405
+ public_key2 = "ssh-rsa #{SSH_PUBLIC_KEY2}"
406
+ public_key3 = "ssh-dss #{SSH_PUBLIC_KEY3} 1024-bit DSA with provided comment"
407
+
408
+ assert_equal(SSH2_PUBLIC_KEY1, SSHKey.ssh_public_key_to_ssh2_public_key(public_key1))
409
+ assert_equal(SSH2_PUBLIC_KEY2, SSHKey.ssh_public_key_to_ssh2_public_key(public_key2))
410
+ assert_equal(SSH2_PUBLIC_KEY2, SSHKey.ssh_public_key_to_ssh2_public_key(public_key2, {}))
411
+ assert_equal(SSH2_PUBLIC_KEY3, SSHKey.ssh_public_key_to_ssh2_public_key(public_key3, {'Comment' => '1024-bit DSA with provided comment', 'x-private-use-header' => 'some value that is long enough to go to wrap around to a new line.'}))
412
+ end
413
+
280
414
  def test_exponent
281
415
  assert_equal 35, @key1.key_object.e.to_i
282
416
  assert_equal 35, @key2.key_object.e.to_i
@@ -297,12 +431,18 @@ EOF
297
431
  assert_equal KEY2_SHA1_FINGERPRINT, @key2.sha1_fingerprint
298
432
  assert_equal KEY3_SHA1_FINGERPRINT, @key3.sha1_fingerprint
299
433
 
434
+ assert_equal KEY1_SHA256_FINGERPRINT, @key1.sha256_fingerprint
435
+ assert_equal KEY2_SHA256_FINGERPRINT, @key2.sha256_fingerprint
436
+ assert_equal KEY3_SHA256_FINGERPRINT, @key3.sha256_fingerprint
437
+
300
438
  assert_equal KEY1_MD5_FINGERPRINT, SSHKey.md5_fingerprint(SSH_PRIVATE_KEY1)
301
439
  assert_equal KEY1_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY1}")
302
440
  assert_equal KEY2_MD5_FINGERPRINT, SSHKey.md5_fingerprint(SSH_PRIVATE_KEY2)
303
441
  assert_equal KEY2_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY2} me@me.com")
304
442
  assert_equal KEY3_MD5_FINGERPRINT, SSHKey.md5_fingerprint(SSH_PRIVATE_KEY3)
305
443
  assert_equal KEY3_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-dss #{SSH_PUBLIC_KEY3}")
444
+ assert_equal ED25519_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}")
445
+ assert_equal ECDSA_256_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256} me@me.com")
306
446
 
307
447
  assert_equal KEY1_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint(SSH_PRIVATE_KEY1)
308
448
  assert_equal KEY1_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY1}")
@@ -310,6 +450,17 @@ EOF
310
450
  assert_equal KEY2_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY2} me@me.com")
311
451
  assert_equal KEY3_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint(SSH_PRIVATE_KEY3)
312
452
  assert_equal KEY3_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-dss #{SSH_PUBLIC_KEY3}")
453
+ assert_equal ED25519_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}")
454
+ assert_equal ECDSA_256_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256} me@me.com")
455
+
456
+ assert_equal KEY1_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint(SSH_PRIVATE_KEY1)
457
+ assert_equal KEY1_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY1}")
458
+ assert_equal KEY2_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint(SSH_PRIVATE_KEY2)
459
+ assert_equal KEY2_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY2} me@me.com")
460
+ assert_equal KEY3_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint(SSH_PRIVATE_KEY3)
461
+ assert_equal KEY3_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-dss #{SSH_PUBLIC_KEY3}")
462
+ assert_equal ED25519_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}")
463
+ assert_equal ECDSA_256_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256} me@me.com")
313
464
  end
314
465
 
315
466
  def test_bits
@@ -324,6 +475,13 @@ EOF
324
475
  assert_equal KEY2_RANDOMART, @key2.randomart
325
476
  assert_equal KEY3_RANDOMART, @key3.randomart
326
477
  end
478
+
479
+ def test_sshfp
480
+ assert_equal KEY1_SSHFP, @key1.sshfp("localhost")
481
+ assert_equal KEY2_SSHFP, @key2.sshfp("localhost")
482
+ assert_equal KEY3_SSHFP, @key3.sshfp("localhost")
483
+ end
484
+
327
485
  end
328
486
 
329
487
  class SSHKeyEncryptedTest < Test::Unit::TestCase
metadata CHANGED
@@ -1,27 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sshkey
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Miller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-14 00:00:00.000000000 Z
11
+ date: 2019-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: test-unit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
25
39
  - !ruby/object:Gem::Version
26
40
  version: '0'
27
41
  description: Generate private/public SSH keypairs using pure Ruby
@@ -31,14 +45,13 @@ executables: []
31
45
  extensions: []
32
46
  extra_rdoc_files: []
33
47
  files:
34
- - .gitignore
35
- - .travis.yml
48
+ - ".gitignore"
49
+ - ".travis.yml"
36
50
  - Gemfile
37
51
  - LICENSE
38
52
  - README.md
39
53
  - Rakefile
40
54
  - lib/sshkey.rb
41
- - lib/sshkey/exception.rb
42
55
  - lib/sshkey/version.rb
43
56
  - sshkey.gemspec
44
57
  - test/sshkey_test.rb
@@ -52,17 +65,17 @@ require_paths:
52
65
  - lib
53
66
  required_ruby_version: !ruby/object:Gem::Requirement
54
67
  requirements:
55
- - - '>='
68
+ - - ">="
56
69
  - !ruby/object:Gem::Version
57
70
  version: '0'
58
71
  required_rubygems_version: !ruby/object:Gem::Requirement
59
72
  requirements:
60
- - - '>='
73
+ - - ">="
61
74
  - !ruby/object:Gem::Version
62
75
  version: '0'
63
76
  requirements: []
64
77
  rubyforge_project: sshkey
65
- rubygems_version: 2.0.3
78
+ rubygems_version: 2.5.2.3
66
79
  signing_key:
67
80
  specification_version: 4
68
81
  summary: SSH private/public key generator in Ruby
@@ -1,3 +0,0 @@
1
- class SSHKey
2
- class PublicKeyError < StandardError; end
3
- end