sshkey 1.6.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  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