sshkey 1.1.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,67 @@
1
+ sshkey
2
+ ======
3
+
4
+ Generate private/public SSH keys using Ruby without the `ssh-keygen` system command.
5
+
6
+ gem install sshkey
7
+
8
+ Tested on the following Rubies: MRI 1.8.7 and 1.9.2, Rubinius, JRuby. Ruby must be compiled with OpenSSL support.
9
+
10
+ Usage
11
+ -----
12
+
13
+ Generate an SSH RSA Keypair with foo@bar.com as the comment - providing a comment is optional
14
+
15
+ ``` ruby
16
+ k = SSHKey.generate(:comment => "foo@bar.com")
17
+ ```
18
+
19
+ Generate an SSH DSA Keypair with foo@bar.com as the comment - providing a comment is optional
20
+
21
+ ``` ruby
22
+ k = SSHKey.generate(:type => "dsa", :comment => "foo@bar.com")
23
+ ```
24
+
25
+ Return an SSHKey object from an existing RSA Private Key (provided as a string)
26
+
27
+ ``` ruby
28
+ k = SSHKey.new(File.read("~/.ssh/id_rsa"), :comment => "foo@bar.com")
29
+ ```
30
+
31
+ Both of these will return an SSHKey object with the following methods:
32
+
33
+ ``` ruby
34
+ # Returns an OpenSSL::PKey::RSA or OpenSSL::PKey::DSA key object
35
+ # See http://www.ruby-doc.org/stdlib/libdoc/openssl/rdoc/classes/OpenSSL/PKey/RSA.html
36
+ k.key_object
37
+ # => -----BEGIN RSA PRIVATE KEY-----\nMIIEowI...
38
+
39
+ # Returns the Private Key as a string
40
+ k.private_key
41
+ # => "-----BEGIN RSA PRIVATE KEY-----\nMIIEowI..."
42
+
43
+ # Returns the Public Key as a string
44
+ k.public_key
45
+ # => "-----BEGIN RSA PUBLIC KEY-----\nMIIBCg..."
46
+
47
+ # Returns the SSH Public Key as a string
48
+ k.ssh_public_key
49
+ # => "ssh-rsa AAAAB3NzaC1yc2EA...."
50
+
51
+ # Returns the comment as a string
52
+ k.comment
53
+ # => "foo@bar.com"
54
+
55
+ # Returns the fingerprint as a string
56
+ k.fingerprint
57
+ # => "2a:89:84:c9:29:05:d1:f8:49:79:1c:ba:73:99:eb:af"
58
+
59
+ # Validates SSH Public Key
60
+ SSHKey.valid? "ssh-rsa AAAAB3NzaC1yc2EA...."
61
+ # => true
62
+ ```
63
+
64
+ Copyright
65
+ ---------
66
+
67
+ Copyright (c) 2011 James Miller
@@ -3,28 +3,97 @@ require 'base64'
3
3
  require 'digest/md5'
4
4
 
5
5
  class SSHKey
6
+ SSH_TYPES = {"rsa" => "ssh-rsa", "dsa" => "ssh-dss"}
7
+ SSH_CONVERSION = {"rsa" => ["e", "n"], "dsa" => ["p", "q", "g", "pub_key"]}
8
+
9
+ attr_reader :key_object, :comment, :type
6
10
 
7
11
  def self.generate(options = {})
8
- SSHKey.new(OpenSSL::PKey::RSA.generate(2048).to_pem, options)
12
+ type = options[:type] || "rsa"
13
+ case type
14
+ when "rsa" then SSHKey.new(OpenSSL::PKey::RSA.generate(2048).to_pem, options)
15
+ when "dsa" then SSHKey.new(OpenSSL::PKey::DSA.generate(2048).to_pem, options)
16
+ else
17
+ raise "Unknown key type #{type}"
18
+ end
9
19
  end
10
20
 
11
- attr_reader :key_object, :comment
21
+ def self.valid?(ssh_key)
22
+ ssh_type, encoded_key = ssh_key.split(" ")
23
+ type = SSH_TYPES.invert[ssh_type]
24
+ prefix = [0,0,0,7].pack("C*")
25
+ decoded = Base64.decode64(encoded_key)
26
+
27
+ # Base64 decoding is too permissive, so we should validate if encoding is correct
28
+ return false unless Base64.encode64(decoded).gsub("\n", "") == encoded_key
29
+ return false unless decoded.sub!(/^#{prefix}#{ssh_type}/, "")
30
+
31
+ unpacked = decoded.unpack("C*")
32
+ data = []
33
+ index = 0
34
+ until unpacked[index].nil?
35
+ datum_size = from_byte_array unpacked[index..index+4-1], 4
36
+ index = index + 4
37
+ datum = from_byte_array unpacked[index..index+datum_size-1], datum_size
38
+ data << datum
39
+ index = index + datum_size
40
+ end
41
+
42
+ SSH_CONVERSION[type].size == data.size
43
+ rescue
44
+ false
45
+ end
46
+
47
+ def self.from_byte_array(byte_array, expected_size = nil)
48
+ num = 0
49
+ raise "Byte array too short" if !expected_size.nil? && expected_size != byte_array.size
50
+ byte_array.reverse.each_with_index do |item, index|
51
+ num += item * 256**(index)
52
+ end
53
+ num
54
+ end
12
55
 
13
56
  def initialize(private_key, options = {})
14
- @key_object = OpenSSL::PKey::RSA.new(private_key)
15
- @comment = options[:comment] || ""
57
+ begin
58
+ @key_object = OpenSSL::PKey::RSA.new(private_key)
59
+ @type = "rsa"
60
+ rescue
61
+ @key_object = OpenSSL::PKey::DSA.new(private_key)
62
+ @type = "dsa"
63
+ end
64
+
65
+ @comment = options[:comment] || ""
16
66
  end
17
67
 
18
- def rsa_private_key
68
+ def private_key
19
69
  key_object.to_pem
20
70
  end
21
71
 
22
- def rsa_public_key
72
+ def public_key
23
73
  key_object.public_key.to_pem
24
74
  end
25
75
 
76
+ ########################
77
+ # Backward compatibility
78
+ def rsa_private_key
79
+ private_key if type == "rsa"
80
+ end
81
+
82
+ def rsa_public_key
83
+ public_key if type == "rsa"
84
+ end
85
+
86
+ def dsa_private_key
87
+ private_key if type == "dsa"
88
+ end
89
+
90
+ def dsa_public_key
91
+ public_key if type == "dsa"
92
+ end
93
+ ########################
94
+
26
95
  def ssh_public_key
27
- ["ssh-rsa", Base64.encode64(ssh_public_key_conversion).gsub("\n", ""), @comment].join(" ").strip
96
+ [SSH_TYPES[type], Base64.encode64(ssh_public_key_conversion).gsub("\n", ""), comment].join(" ").strip
28
97
  end
29
98
 
30
99
  def fingerprint
@@ -41,15 +110,14 @@ class SSHKey
41
110
  # For instance, the "ssh-rsa" string is encoded as the following byte array
42
111
  # [0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a']
43
112
  def ssh_public_key_conversion
44
- e = @key_object.public_key.e.to_i
45
- n = @key_object.public_key.n.to_i
113
+ out = [0,0,0,7].pack("C*")
114
+ out += SSH_TYPES[type]
46
115
 
47
- out = [0,0,0,7].pack("c*")
48
- out += "ssh-rsa"
49
- out += encode_unsigned_int_32(to_byte_array(e).length).pack("c*")
50
- out += to_byte_array(e).pack("c*")
51
- out += encode_unsigned_int_32(to_byte_array(n).length).pack("c*")
52
- out += to_byte_array(n).pack("c*")
116
+ SSH_CONVERSION[type].each do |method|
117
+ byte_array = to_byte_array(key_object.public_key.send(method).to_i)
118
+ out += encode_unsigned_int_32(byte_array.length).pack("c*")
119
+ out += byte_array.pack("C*")
120
+ end
53
121
 
54
122
  return out
55
123
  end
@@ -71,4 +139,5 @@ class SSHKey
71
139
  end until (num == 0 || num == -1) && (result.last[7] == num[7])
72
140
  result.reverse
73
141
  end
142
+
74
143
  end
@@ -1,3 +1,3 @@
1
1
  class SSHKey
2
- VERSION = "1.1.3"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -59,28 +59,52 @@ NOAVAoGACIdfR5oZ4tvNIeqjtLF83HmUJARv86eMmjqgiQTFcZS3A8vk5y05STxX
59
59
  HU3kTCfT6sypRi9zDQafIIyqYFgaOezr2eRRFRojQZqzHjtuFUeKLrKf7R9bzwwx
60
60
  DPlNgYq8p4FOY5ZOL/ZOxUHW4vKRewURJttnxzw+LEy0T1FyAE0=
61
61
  -----END RSA PRIVATE KEY-----
62
+ EOF
63
+ SSH_PRIVATE_KEY3 = <<-EOF
64
+ -----BEGIN DSA PRIVATE KEY-----
65
+ MIIBvAIBAAKBgQC8lcuXcFcIC9wsV87L6PAwYefKgK0CwTSD1v3/aabZsu4w+UF8
66
+ zsPtdsNP8+JWfOp3KFbrUTH+ODgAXF/aL4UZfpbsQe446ZFV8v6dmWqj23sk0FLX
67
+ U5l2tsuJ9OdyXetVXjBvoiz+/r4k/iG/esvWlVGEHwq5eYXgQ1GfXABY3QIVAMVe
68
+ c7skmkUrCR6iivgZYYe3PQPZAoGBAKnpdEVATtDGOW9w2evSf5kc1InzdTurcJOH
69
+ q9qYdCaa8rlMGaIS6XFWcKqBlpj0Mv2R5ldW90bU/RllGvh1KinTIRVTsf4qtZIV
70
+ Xy4vN8IYzDL1493nKndMsxsRh50rI1Snn2tssAix64eJ5VFSGlyOYEKYDMlWzHK6
71
+ Jg3tVmc6AoGBAIwTRPAEcroqOzaebiVspFcmsXxDQ4wXQZQdho1ExW6FKS8s7/6p
72
+ ItmZYXTvJDwLXgq2/iK1fRRcKk2PJEaSuJR7WeNGsJKfWmQ2UbOhqA3wWLDazIZt
73
+ cMKjFzD0hM4E8qgjHjMvKDE6WgT6SFP+tqx3nnh7pJWwsbGjSMQexpyRAhQLhz0l
74
+ GzM8qwTcXd06uIZAJdTHIQ==
75
+ -----END DSA PRIVATE KEY-----
62
76
  EOF
63
77
 
64
78
  SSH_PUBLIC_KEY1 = 'AAAAB3NzaC1yc2EAAAABIwAAAQEArfTA/lKVR84IMc9ZzXOCHr8DVtR8hzWuEVHF6KElavRHlk14g0SZu3m908Ejm/XF3EfNHjX9wN+62IMA0QBxkBMFCuLF+U/oeUs0NoDdAEKxjj4n6lq6Ss8aLct+anMy7D1jwvOLbcwV54w1d5JDdlZVdZ6AvHm9otwJq6rNpDgdmXY4HgC2nM9csFpuy0cDpL6fdJx9lcNL2RnkRC4+RMsIB+PxDw0j3vDi04dYLBXMGYjyeGH+mIFpL3PTPXGXwL2XDYXZ2H4SQX6bOoKmazTXq6QXuEB665njh1GxXldoIMcSshoJL0hrk3WrTOG22N2CQA+IfHgrXJ+A+QUzKQ=='
65
79
  SSH_PUBLIC_KEY2 = 'AAAAB3NzaC1yc2EAAAABIwAAAQEAxl6TpN7uFiY/JZ8qDnD7UrxDP+ABeh2PVg8Du1LEgXNk0+YWCeP5S6oHklqaWeDlbmAs1oHsBwCMAVpMa5tgONOLvz4JgwgkiqQEbKR8ofWJ+LADUElvqRVGmGiNEMLI6GJWeneL4sjmbb8d6U+M53c6iWG0si9XE5m7teBQSsCl0Tk3qMIkQGw5zpJeCXjZ8KpJhIJRYgexFkGgPlYRV+UYIhxpUW90t0Ra5i6JOFYwq98k5S/6SJIZQ/A9F4JNzwLw3eVxZj0yVHWxkGz1+TyELNY1kOyMxnZaqSfGzSQJTrnIXpdweVHuYh1LtOgedRQhCyiELeSMGwio1vRPKw=='
80
+ SSH_PUBLIC_KEY3 = 'AAAAB3NzaC1kc3MAAACBALyVy5dwVwgL3CxXzsvo8DBh58qArQLBNIPW/f9pptmy7jD5QXzOw+12w0/z4lZ86ncoVutRMf44OABcX9ovhRl+luxB7jjpkVXy/p2ZaqPbeyTQUtdTmXa2y4n053Jd61VeMG+iLP7+viT+Ib96y9aVUYQfCrl5heBDUZ9cAFjdAAAAFQDFXnO7JJpFKwkeoor4GWGHtz0D2QAAAIEAqel0RUBO0MY5b3DZ69J/mRzUifN1O6twk4er2ph0JpryuUwZohLpcVZwqoGWmPQy/ZHmV1b3RtT9GWUa+HUqKdMhFVOx/iq1khVfLi83whjMMvXj3ecqd0yzGxGHnSsjVKefa2ywCLHrh4nlUVIaXI5gQpgMyVbMcromDe1WZzoAAACBAIwTRPAEcroqOzaebiVspFcmsXxDQ4wXQZQdho1ExW6FKS8s7/6pItmZYXTvJDwLXgq2/iK1fRRcKk2PJEaSuJR7WeNGsJKfWmQ2UbOhqA3wWLDazIZtcMKjFzD0hM4E8qgjHjMvKDE6WgT6SFP+tqx3nnh7pJWwsbGjSMQexpyR'
66
81
 
67
82
  KEY1_FINGERPRINT = "2a:89:84:c9:29:05:d1:f8:49:79:1c:ba:73:99:eb:af"
68
83
  KEY2_FINGERPRINT = "3c:af:74:87:cc:cc:a1:12:05:1a:09:b7:7b:ce:ed:ce"
84
+ KEY3_FINGERPRINT = "14:f6:6a:12:96:be:44:32:e6:3c:77:43:94:52:f5:7a"
69
85
 
70
86
  def setup
71
87
  @key1 = SSHKey.new(SSH_PRIVATE_KEY1, :comment => "me@example.com")
72
88
  @key2 = SSHKey.new(SSH_PRIVATE_KEY2, :comment => "me@example.com")
89
+ @key3 = SSHKey.new(SSH_PRIVATE_KEY3, :comment => "me@example.com")
73
90
  @key_without_comment = SSHKey.new(SSH_PRIVATE_KEY1)
74
91
  end
75
92
 
76
93
  def test_private_key1
94
+ assert_equal SSH_PRIVATE_KEY1, @key1.private_key
77
95
  assert_equal SSH_PRIVATE_KEY1, @key1.rsa_private_key
78
96
  end
79
97
 
80
98
  def test_private_key2
99
+ assert_equal SSH_PRIVATE_KEY2, @key2.private_key
81
100
  assert_equal SSH_PRIVATE_KEY2, @key2.rsa_private_key
82
101
  end
83
102
 
103
+ def test_private_key3
104
+ assert_equal SSH_PRIVATE_KEY3, @key3.private_key
105
+ assert_equal SSH_PRIVATE_KEY3, @key3.dsa_private_key
106
+ end
107
+
84
108
  def test_ssh_public_key_decoded1
85
109
  assert_equal Base64.decode64(SSH_PUBLIC_KEY1), @key1.send(:ssh_public_key_conversion)
86
110
  end
@@ -89,6 +113,10 @@ EOF
89
113
  assert_equal Base64.decode64(SSH_PUBLIC_KEY2), @key2.send(:ssh_public_key_conversion)
90
114
  end
91
115
 
116
+ def test_ssh_public_key_decoded3
117
+ assert_equal Base64.decode64(SSH_PUBLIC_KEY3), @key3.send(:ssh_public_key_conversion)
118
+ end
119
+
92
120
  def test_ssh_public_key_encoded1
93
121
  assert_equal SSH_PUBLIC_KEY1, Base64.encode64(@key1.send(:ssh_public_key_conversion)).gsub("\n", "")
94
122
  end
@@ -97,13 +125,42 @@ EOF
97
125
  assert_equal SSH_PUBLIC_KEY2, Base64.encode64(@key2.send(:ssh_public_key_conversion)).gsub("\n", "")
98
126
  end
99
127
 
128
+ def test_ssh_public_key_encoded3
129
+ assert_equal SSH_PUBLIC_KEY3, Base64.encode64(@key3.send(:ssh_public_key_conversion)).gsub("\n", "")
130
+ end
131
+
100
132
  def test_ssh_public_key_output
101
133
  expected1 = "ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com"
102
134
  expected2 = "ssh-rsa #{SSH_PUBLIC_KEY2} me@example.com"
103
- expected3 = "ssh-rsa #{SSH_PUBLIC_KEY1}"
135
+ expected3 = "ssh-dss #{SSH_PUBLIC_KEY3} me@example.com"
136
+ expected4 = "ssh-rsa #{SSH_PUBLIC_KEY1}"
104
137
  assert_equal expected1, @key1.ssh_public_key
105
138
  assert_equal expected2, @key2.ssh_public_key
106
- assert_equal expected3, @key_without_comment.ssh_public_key
139
+ assert_equal expected3, @key3.ssh_public_key
140
+ assert_equal expected4, @key_without_comment.ssh_public_key
141
+ end
142
+
143
+ def test_ssh_public_key_validation
144
+ expected1 = "ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com"
145
+ expected2 = "ssh-rsa #{SSH_PUBLIC_KEY2} me@example.com"
146
+ expected3 = "ssh-dss #{SSH_PUBLIC_KEY3} me@example.com"
147
+ expected4 = "ssh-rsa #{SSH_PUBLIC_KEY1}"
148
+ invalid1 = "ssh-rsa #{SSH_PUBLIC_KEY1}= me@example.com"
149
+ invalid2 = "ssh-rsa #{SSH_PUBLIC_KEY2}= me@example.com"
150
+ invalid3 = "ssh-dss #{SSH_PUBLIC_KEY3}= me@example.com"
151
+ invalid4 = "ssh-rsa A#{SSH_PUBLIC_KEY1}"
152
+ invalid5 = "ssh-rsa #{SSH_PUBLIC_KEY3} me@example.com"
153
+
154
+ assert SSHKey.valid?(expected1)
155
+ assert SSHKey.valid?(expected2)
156
+ assert SSHKey.valid?(expected3)
157
+ assert SSHKey.valid?(expected4)
158
+
159
+ assert !SSHKey.valid?(invalid1)
160
+ assert !SSHKey.valid?(invalid2)
161
+ assert !SSHKey.valid?(invalid3)
162
+ assert !SSHKey.valid?(invalid4)
163
+ assert !SSHKey.valid?(invalid5)
107
164
  end
108
165
 
109
166
  def test_exponent
@@ -119,6 +176,7 @@ EOF
119
176
  def test_fingerprint
120
177
  assert_equal KEY1_FINGERPRINT, @key1.fingerprint
121
178
  assert_equal KEY2_FINGERPRINT, @key2.fingerprint
179
+ assert_equal KEY3_FINGERPRINT, @key3.fingerprint
122
180
  end
123
181
 
124
182
  def test_to_byte_array
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: sshkey
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 1.1.3
5
+ version: 1.2.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - James Miller
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-05-12 00:00:00 -07:00
13
+ date: 2011-06-02 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
@@ -27,7 +27,7 @@ files:
27
27
  - .gitignore
28
28
  - Gemfile
29
29
  - LICENSE
30
- - README.rdoc
30
+ - README.md
31
31
  - Rakefile
32
32
  - lib/sshkey.rb
33
33
  - lib/sshkey/version.rb
@@ -1,48 +0,0 @@
1
- = sshkey
2
-
3
- Generate private/public SSH keys using Ruby without the `ssh-keygen` system command.
4
-
5
- gem install sshkey
6
-
7
- Tested on Ruby 1.8.7 and 1.9.2 (MRI). Ruby must be compiled with OpenSSL support.
8
-
9
- == Usage
10
-
11
- Generate an SSH Keypair with foo@bar.com as the comment - providing a comment is optional
12
-
13
- k = SSHKey.generate(:comment => "foo@bar.com")
14
-
15
- Return an SSHKey object from an existing RSA Private Key (provided as a string)
16
-
17
- k = SSHKey.new(File.read("~/.ssh/id_rsa"), :comment => "foo@bar.com")
18
-
19
- Both of these will return an SSHKey object with the following methods:
20
-
21
- # Returns an OpenSSL::PKey::RSA key object
22
- # See http://www.ruby-doc.org/stdlib/libdoc/openssl/rdoc/classes/OpenSSL/PKey/RSA.html
23
- k.key_object
24
- # => -----BEGIN RSA PRIVATE KEY-----\nMIIEowI...
25
-
26
- # Returns the RSA Private Key as a string
27
- k.rsa_private_key
28
- # => "-----BEGIN RSA PRIVATE KEY-----\nMIIEowI..."
29
-
30
- # Returns the RSA Public Key as a string
31
- k.rsa_public_key
32
- # => "-----BEGIN RSA PUBLIC KEY-----\nMIIBCg..."
33
-
34
- # Returns the SSH Public Key as a string
35
- k.ssh_public_key
36
- # => "ssh-rsa AAAAB3NzaC1yc2EA...."
37
-
38
- # Returns the comment as a string
39
- k.comment
40
- # => "foo@bar.com"
41
-
42
- # Returns the fingerprint as a string
43
- k.fingerprint
44
- # => "2a:89:84:c9:29:05:d1:f8:49:79:1c:ba:73:99:eb:af"
45
-
46
- == Copyright
47
-
48
- Copyright (c) 2011 James Miller