sshkey 1.1.3 → 1.2.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.
@@ -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