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 +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
|
+
[](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