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 +4 -4
- data/.travis.yml +13 -33
- data/Gemfile +1 -2
- data/LICENSE +1 -1
- data/README.md +42 -15
- data/lib/sshkey.rb +133 -11
- data/lib/sshkey/version.rb +1 -1
- data/sshkey.gemspec +1 -0
- data/test/sshkey_test.rb +158 -0
- metadata +23 -10
- data/lib/sshkey/exception.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6e9c3f40bf3a5b7a9f80cadd7d98e8c4e9b41d4
|
4
|
+
data.tar.gz: 362495eb1d46d0befe4c932c94318ce1e40c8ec2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
-
|
3
|
-
- 1
|
4
|
-
-
|
5
|
-
- 2.
|
6
|
-
-
|
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
|
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
|
-
|
40
|
-
|
41
|
-
|
19
|
+
|
20
|
+
sudo: required
|
21
|
+
dist: xenial
|
data/Gemfile
CHANGED
data/LICENSE
CHANGED
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
|
-
|
5
|
+
[![Build Status](https://secure.travis-ci.org/bensie/sshkey.svg?branch=master)](http://travis-ci.org/bensie/sshkey)
|
6
6
|
|
7
|
-
|
7
|
+
## Requirements
|
8
8
|
|
9
|
-
|
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(
|
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
|
-
|
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
|
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.
|
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
|
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
|
-
# => "
|
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
|
-
# => "
|
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
|
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
|
178
|
-
|
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-
|
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 '
|
5
|
+
require 'digest/sha2'
|
8
6
|
|
9
7
|
class SSHKey
|
10
|
-
SSH_TYPES
|
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
|
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
|
-
|
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 = [
|
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
|
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
|
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
|
data/lib/sshkey/version.rb
CHANGED
data/sshkey.gemspec
CHANGED
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:
|
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:
|
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.
|
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
|
data/lib/sshkey/exception.rb
DELETED