orthrus-ssh 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ # Autotest.add_hook :initialize do |at|
6
+ # at.extra_files << "../some/external/dependency.rb"
7
+ #
8
+ # at.libs << ":../some/external"
9
+ #
10
+ # at.add_exception 'vendor'
11
+ #
12
+ # at.add_mapping(/dependency.rb/) do |f, _|
13
+ # at.files_matching(/test_.*rb$/)
14
+ # end
15
+ #
16
+ # %w(TestA TestB).each do |klass|
17
+ # at.extra_class_map[klass] = "test/test_misc.rb"
18
+ # end
19
+ # end
20
+
21
+ # Autotest.add_hook :run_command do |at|
22
+ # system "rake build"
23
+ # end
data/.gemtest ADDED
File without changes
data/History.txt ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2012-03-18
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
data/Manifest.txt ADDED
@@ -0,0 +1,31 @@
1
+ .autotest
2
+ History.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ bin/orthrus
7
+ lib/orthrus.rb
8
+ lib/orthrus/key.rb
9
+ lib/orthrus/key_holder.rb
10
+ lib/orthrus/ssh.rb
11
+ lib/orthrus/ssh/agent.rb
12
+ lib/orthrus/ssh/buffer.rb
13
+ lib/orthrus/ssh/dsa.rb
14
+ lib/orthrus/ssh/http_agent.rb
15
+ lib/orthrus/ssh/key.rb
16
+ lib/orthrus/ssh/public_key_set.rb
17
+ lib/orthrus/ssh/rack_app.rb
18
+ lib/orthrus/ssh/rsa.rb
19
+ lib/orthrus/ssh/utils.rb
20
+ test/data/authorized_keys
21
+ test/data/id_dsa
22
+ test/data/id_dsa.pub
23
+ test/data/id_rsa
24
+ test/data/id_rsa.pub
25
+ test/sessions.rb
26
+ test/test_orthrus_ssh_agent.rb
27
+ test/test_orthrus_ssh_dsa.rb
28
+ test/test_orthrus_ssh_http_agent.rb
29
+ test/test_orthrus_ssh_public_key_set.rb
30
+ test/test_orthrus_ssh_rackapp.rb
31
+ test/test_orthrus_ssh_rsa.rb
data/README.txt ADDED
@@ -0,0 +1,61 @@
1
+ = orthrus-ssh
2
+
3
+ * http://github.com/evanphx/orthrus
4
+
5
+ == DESCRIPTION:
6
+
7
+ A user authentication system built on SSH's key
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Uses ssh keys to authenticate users
12
+ * Can use the ssh-agent for added security
13
+ * Includes HTTPAgent and sample RackApp
14
+
15
+ == SYNOPSIS:
16
+
17
+ agent = Orthrus::SSH::HTTPAgent.new url
18
+ h.start user
19
+ p h.access_token
20
+
21
+ == REQUIREMENTS:
22
+
23
+ * OpenSSL
24
+
25
+ == INSTALL:
26
+
27
+ * gem install orthrus
28
+
29
+ == DEVELOPERS:
30
+
31
+ After checking out the source, run:
32
+
33
+ $ rake newb
34
+
35
+ This task will install any missing dependencies, run the tests/specs,
36
+ and generate the RDoc.
37
+
38
+ == LICENSE:
39
+
40
+ (The MIT License)
41
+
42
+ Copyright (c) 2012 FIX
43
+
44
+ Permission is hereby granted, free of charge, to any person obtaining
45
+ a copy of this software and associated documentation files (the
46
+ 'Software'), to deal in the Software without restriction, including
47
+ without limitation the rights to use, copy, modify, merge, publish,
48
+ distribute, sublicense, and/or sell copies of the Software, and to
49
+ permit persons to whom the Software is furnished to do so, subject to
50
+ the following conditions:
51
+
52
+ The above copyright notice and this permission notice shall be
53
+ included in all copies or substantial portions of the Software.
54
+
55
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
56
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
57
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
58
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
59
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
60
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
61
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.plugin :minitest
7
+
8
+ Hoe.spec 'orthrus-ssh' do
9
+ developer('Evan Phoenix', 'evan@phx.io')
10
+ end
11
+
12
+ # vim: syntax=ruby
data/bin/orthrus ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ case cmd = ARGV.shift
4
+ when "ids"
5
+ require 'orthrus/ssh/agent'
6
+
7
+ agent = Orthrus::SSH::Agent.connect
8
+
9
+ puts "Agent identities:"
10
+ agent.identities.each do |i|
11
+ puts "#{i.type}: #{i.fingerprint}"
12
+ end
13
+
14
+ else
15
+ abort "Unsupported option - #{cmd}"
16
+ end
data/lib/orthrus.rb ADDED
@@ -0,0 +1,2 @@
1
+ class Orthrus
2
+ end
@@ -0,0 +1,12 @@
1
+ require 'openssl'
2
+
3
+ module Orthrus
4
+
5
+ CURVE = "secp224k1"
6
+
7
+ class Key
8
+ def self.new_pair
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module Orthrus
2
+ class KeyHolder
3
+ def initialize
4
+ @keys = {}
5
+ end
6
+
7
+ def add_key(name, key)
8
+ @keys[name] = key
9
+ end
10
+
11
+ def key(name)
12
+ @keys[name]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,50 @@
1
+ require 'openssl'
2
+
3
+ module Orthrus; end
4
+
5
+ require 'orthrus/ssh/rsa'
6
+ require 'orthrus/ssh/dsa'
7
+ require 'orthrus/ssh/utils'
8
+
9
+ module Orthrus::SSH
10
+ VERSION = '0.5.0'
11
+
12
+ def self.load_private(path)
13
+ data = File.read(path)
14
+ if data.index("-----BEGIN RSA PRIVATE KEY-----") == 0
15
+ k = OpenSSL::PKey::RSA.new data
16
+ return RSAPrivateKey.new k
17
+ elsif data.index("-----BEGIN DSA PRIVATE KEY-----") == 0
18
+ k = OpenSSL::PKey::DSA.new data
19
+ return DSAPrivateKey.new k
20
+ else
21
+ raise "Unknown key type in '#{path}'"
22
+ end
23
+ end
24
+
25
+ def self.parse_public(data)
26
+ type, key, comment = data.split " ", 3
27
+
28
+ case type
29
+ when "ssh-rsa"
30
+ RSAPublicKey.parse key
31
+ when "ssh-dss"
32
+ DSAPublicKey.parse key
33
+ else
34
+ raise "Unknown key type - #{type}"
35
+ end
36
+ end
37
+
38
+ def self.load_public(path)
39
+ parse_public File.read(path)
40
+ end
41
+
42
+ end
43
+
44
+ # For 1.8/1.9 compat
45
+ class String
46
+ unless method_defined? :getbytpe
47
+ alias_method :getbyte, :[]
48
+ end
49
+ end
50
+
@@ -0,0 +1,176 @@
1
+ require 'orthrus/ssh'
2
+ require 'orthrus/ssh/buffer'
3
+
4
+ require 'socket'
5
+
6
+ # Adapted from agent.rb in net-ssh
7
+
8
+ module Orthrus::SSH
9
+ # A trivial exception class for representing agent-specific errors.
10
+ class AgentError < StandardError; end
11
+
12
+ # An exception for indicating that the SSH agent is not available.
13
+ class AgentNotAvailable < AgentError; end
14
+
15
+ # This class implements a simple client for the ssh-agent protocol. It
16
+ # does not implement any specific protocol, but instead copies the
17
+ # behavior of the ssh-agent functions in the OpenSSH library (3.8).
18
+ #
19
+ # This means that although it behaves like a SSH1 client, it also has
20
+ # some SSH2 functionality (like signing data).
21
+ class Agent
22
+ SSH2_AGENT_REQUEST_VERSION = 1
23
+ SSH2_AGENT_REQUEST_IDENTITIES = 11
24
+ SSH2_AGENT_IDENTITIES_ANSWER = 12
25
+ SSH2_AGENT_SIGN_REQUEST = 13
26
+ SSH2_AGENT_SIGN_RESPONSE = 14
27
+ SSH2_AGENT_FAILURE = 30
28
+ SSH2_AGENT_VERSION_RESPONSE = 103
29
+
30
+ SSH_COM_AGENT2_FAILURE = 102
31
+
32
+ SSH_AGENT_REQUEST_RSA_IDENTITIES = 1
33
+ SSH_AGENT_RSA_IDENTITIES_ANSWER1 = 2
34
+ SSH_AGENT_RSA_IDENTITIES_ANSWER2 = 5
35
+ SSH_AGENT_FAILURE = 5
36
+
37
+ # The underlying socket being used to communicate with the SSH agent.
38
+ attr_reader :socket
39
+
40
+ # Instantiates a new agent object, connects to a running SSH agent,
41
+ # negotiates the agent protocol version, and returns the agent object.
42
+ def self.connect
43
+ agent = new
44
+ agent.connect!
45
+ agent.negotiate!
46
+ agent
47
+ end
48
+
49
+ def self.available?
50
+ ENV.key?("SSH_AUTH_SOCK") && File.exists?(ENV['SSH_AUTH_SOCK'])
51
+ end
52
+
53
+ # Creates a new Agent object.
54
+ def initialize
55
+ @socket = nil
56
+ end
57
+
58
+ # Connect to the agent process using the socket factory and socket name
59
+ # given by the attribute writers. If the agent on the other end of the
60
+ # socket reports that it is an SSH2-compatible agent, this will fail
61
+ # (it only supports the ssh-agent distributed by OpenSSH).
62
+ def connect!
63
+ begin
64
+ @socket = UNIXSocket.open(ENV['SSH_AUTH_SOCK'])
65
+ rescue
66
+ raise AgentNotAvailable, $!.message
67
+ end
68
+ end
69
+
70
+ ID = "SSH-2.0-Ruby/Orthrus #{RUBY_PLATFORM}"
71
+
72
+ # Attempts to negotiate the SSH agent protocol version. Raises an error
73
+ # if the version could not be negotiated successfully.
74
+ def negotiate!
75
+ # determine what type of agent we're communicating with
76
+ type, body = send_and_wait(SSH2_AGENT_REQUEST_VERSION, :string, ID)
77
+
78
+ if type == SSH2_AGENT_VERSION_RESPONSE
79
+ raise NotImplementedError, "SSH2 agents are not yet supported"
80
+ elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
81
+ raise AgentError, "unknown response from agent: #{type}, #{body.to_s.inspect}"
82
+ end
83
+ end
84
+
85
+ # Return an array of all identities (public keys) known to the agent.
86
+ # Each key returned is augmented with a +comment+ property which is set
87
+ # to the comment returned by the agent for that key.
88
+ def identities
89
+ type, body = send_and_wait(SSH2_AGENT_REQUEST_IDENTITIES)
90
+ raise AgentError, "could not get identity count" if agent_failed(type)
91
+ raise AgentError, "bad authentication reply: #{type}" if type != SSH2_AGENT_IDENTITIES_ANSWER
92
+
93
+ identities = []
94
+ body.read_long.times do
95
+ key = Buffer.new(body.read_string).read_key
96
+ case key
97
+ when OpenSSL::PKey::RSA
98
+ key = RSAPublicKey.new key
99
+ when OpenSSL::PKey::DSA
100
+ key = DSAPublicKey.new key
101
+ else
102
+ raise AgentError, "Unknown key type - #{key.class}"
103
+ end
104
+
105
+ key.comment = body.read_string
106
+ identities.push key
107
+ end
108
+
109
+ return identities
110
+ end
111
+
112
+ # Closes this socket. This agent reference is no longer able to
113
+ # query the agent.
114
+ def close
115
+ @socket.close
116
+ end
117
+
118
+ # Using the agent and the given public key, sign the given data. The
119
+ # signature is returned in SSH2 format.
120
+ def sign(key, data)
121
+ type, reply = send_and_wait(SSH2_AGENT_SIGN_REQUEST,
122
+ :string, Buffer.from(:key, key),
123
+ :string, data,
124
+ :long, 0)
125
+
126
+ if agent_failed(type)
127
+ raise AgentError, "agent could not sign data with requested identity"
128
+ elsif type != SSH2_AGENT_SIGN_RESPONSE
129
+ raise AgentError, "bad authentication response #{type}"
130
+ end
131
+
132
+ b = Buffer.new reply.read_string
133
+ [b.read_string, b.read_string]
134
+ end
135
+
136
+ def hexsign(key, data)
137
+ type, sig = sign key, data
138
+
139
+ [type, [sig].pack("m").gsub("\n","")]
140
+ end
141
+
142
+ private
143
+
144
+ # Send a new packet of the given type, with the associated data.
145
+ def send_packet(type, *args)
146
+ buffer = Buffer.from(*args)
147
+ data = [buffer.length + 1, type.to_i, buffer.to_s].pack("NCA*")
148
+ @socket.send data, 0
149
+ end
150
+
151
+ # Read the next packet from the agent. This will return a two-part
152
+ # tuple consisting of the packet type, and the packet's body (which
153
+ # is returned as a Net::SSH::Buffer).
154
+ def read_packet
155
+ buffer = Buffer.new @socket.read(4)
156
+ buffer.append @socket.read(buffer.read_long)
157
+ type = buffer.read_byte
158
+ return type, buffer
159
+ end
160
+
161
+ # Send the given packet and return the subsequent reply from the agent.
162
+ # (See #send_packet and #read_packet).
163
+ def send_and_wait(type, *args)
164
+ send_packet(type, *args)
165
+ read_packet
166
+ end
167
+
168
+ # Returns +true+ if the parameter indicates a "failure" response from
169
+ # the agent, and +false+ otherwise.
170
+ def agent_failed(type)
171
+ type == SSH_AGENT_FAILURE ||
172
+ type == SSH2_AGENT_FAILURE ||
173
+ type == SSH_COM_AGENT2_FAILURE
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,343 @@
1
+ require 'openssl'
2
+
3
+ require 'orthrus/ssh/utils'
4
+
5
+ # Adapted from buffer.rb in net-ssh
6
+
7
+ module Orthrus::SSH
8
+
9
+ # Net::SSH::Buffer is a flexible class for building and parsing binary
10
+ # data packets. It provides a stream-like interface for sequentially
11
+ # reading data items from the buffer, as well as a useful helper method
12
+ # for building binary packets given a signature.
13
+ #
14
+ # Writing to a buffer always appends to the end, regardless of where the
15
+ # read cursor is. Reading, on the other hand, always begins at the first
16
+ # byte of the buffer and increments the read cursor, with subsequent reads
17
+ # taking up where the last left off.
18
+ #
19
+ # As a consumer of the Net::SSH library, you will rarely come into contact
20
+ # with these buffer objects directly, but it could happen. Also, if you
21
+ # are ever implementing a protocol on top of SSH (e.g. SFTP), this buffer
22
+ # class can be quite handy.
23
+ class Buffer
24
+ # This is a convenience method for creating and populating a new buffer
25
+ # from a single command. The arguments must be even in length, with the
26
+ # first of each pair of arguments being a symbol naming the type of the
27
+ # data that follows. If the type is :raw, the value is written directly
28
+ # to the hash.
29
+ #
30
+ # b = Buffer.from(:byte, 1, :string, "hello", :raw, "\1\2\3\4")
31
+ # #-> "\1\0\0\0\5hello\1\2\3\4"
32
+ #
33
+ # The supported data types are:
34
+ #
35
+ # * :raw => write the next value verbatim (#write)
36
+ # * :int64 => write an 8-byte integer (#write_int64)
37
+ # * :long => write a 4-byte integer (#write_long)
38
+ # * :byte => write a single byte (#write_byte)
39
+ # * :string => write a 4-byte length followed by character data (#write_string)
40
+ # * :bool => write a single byte, interpreted as a boolean (#write_bool)
41
+ # * :bignum => write an SSH-encoded bignum (#write_bignum)
42
+ # * :key => write an SSH-encoded key value (#write_key)
43
+ #
44
+ # Any of these, except for :raw, accepts an Array argument, to make it
45
+ # easier to write multiple values of the same type in a briefer manner.
46
+ def self.from(*args)
47
+ raise ArgumentError, "odd number of arguments given" unless args.length % 2 == 0
48
+
49
+ buffer = new
50
+ 0.step(args.length-1, 2) do |index|
51
+ type = args[index]
52
+ value = args[index+1]
53
+ if type == :raw
54
+ buffer.append(value.to_s)
55
+ elsif Array === value
56
+ buffer.send("write_#{type}", *value)
57
+ else
58
+ buffer.send("write_#{type}", value)
59
+ end
60
+ end
61
+
62
+ buffer
63
+ end
64
+
65
+ # exposes the raw content of the buffer
66
+ attr_reader :content
67
+
68
+ # the current position of the pointer in the buffer
69
+ attr_accessor :position
70
+
71
+ # Creates a new buffer, initialized to the given content. The position
72
+ # is initialized to the beginning of the buffer.
73
+ def initialize(content="")
74
+ @content = content.to_s
75
+ @position = 0
76
+ end
77
+
78
+ # Returns the length of the buffer's content.
79
+ def length
80
+ @content.length
81
+ end
82
+
83
+ # Returns the number of bytes available to be read (e.g., how many bytes
84
+ # remain between the current position and the end of the buffer).
85
+ def available
86
+ length - position
87
+ end
88
+
89
+ # Returns a copy of the buffer's content.
90
+ def to_s
91
+ (@content || "").dup
92
+ end
93
+
94
+ # Compares the contents of the two buffers, returning +true+ only if they
95
+ # are identical in size and content.
96
+ def ==(buffer)
97
+ to_s == buffer.to_s
98
+ end
99
+
100
+ # Returns +true+ if the buffer contains no data (e.g., it is of zero length).
101
+ def empty?
102
+ @content.empty?
103
+ end
104
+
105
+ # Resets the pointer to the start of the buffer. Subsequent reads will
106
+ # begin at position 0.
107
+ def reset!
108
+ @position = 0
109
+ end
110
+
111
+ # Returns true if the pointer is at the end of the buffer. Subsequent
112
+ # reads will return nil, in this case.
113
+ def eof?
114
+ @position >= length
115
+ end
116
+
117
+ # Resets the buffer, making it empty. Also, resets the read position to
118
+ # 0.
119
+ def clear!
120
+ @content = ""
121
+ @position = 0
122
+ end
123
+
124
+ # Consumes n bytes from the buffer, where n is the current position
125
+ # unless otherwise specified. This is useful for removing data from the
126
+ # buffer that has previously been read, when you are expecting more data
127
+ # to be appended. It helps to keep the size of buffers down when they
128
+ # would otherwise tend to grow without bound.
129
+ #
130
+ # Returns the buffer object itself.
131
+ def consume!(n=position)
132
+ if n >= length
133
+ # optimize for a fairly common case
134
+ clear!
135
+ elsif n > 0
136
+ @content = @content[n..-1] || ""
137
+ @position -= n
138
+ @position = 0 if @position < 0
139
+ end
140
+ self
141
+ end
142
+
143
+ # Appends the given text to the end of the buffer. Does not alter the
144
+ # read position. Returns the buffer object itself.
145
+ def append(text)
146
+ @content << text
147
+ self
148
+ end
149
+
150
+ # Returns all text from the current pointer to the end of the buffer as
151
+ # a new Net::SSH::Buffer object.
152
+ def remainder_as_buffer
153
+ Buffer.new(@content[@position..-1])
154
+ end
155
+
156
+ # Reads all data up to and including the given pattern, which may be a
157
+ # String, Fixnum, or Regexp and is interpreted exactly as String#index
158
+ # does. Returns nil if nothing matches. Increments the position to point
159
+ # immediately after the pattern, if it does match. Returns all data up to
160
+ # and including the text that matched the pattern.
161
+ def read_to(pattern)
162
+ index = @content.index(pattern, @position) or return nil
163
+ length = case pattern
164
+ when String then pattern.length
165
+ when Fixnum then 1
166
+ when Regexp then $&.length
167
+ end
168
+ index && read(index+length)
169
+ end
170
+
171
+ # Reads and returns the next +count+ bytes from the buffer, starting from
172
+ # the read position. If +count+ is +nil+, this will return all remaining
173
+ # text in the buffer. This method will increment the pointer.
174
+ def read(count=nil)
175
+ count ||= length
176
+ count = length - @position if @position + count > length
177
+ @position += count
178
+ @content[@position-count, count]
179
+ end
180
+
181
+ # Reads (as #read) and returns the given number of bytes from the buffer,
182
+ # and then consumes (as #consume!) all data up to the new read position.
183
+ def read!(count=nil)
184
+ data = read(count)
185
+ consume!
186
+ data
187
+ end
188
+
189
+ # Return the next 8 bytes as a 64-bit integer (in network byte order).
190
+ # Returns nil if there are less than 8 bytes remaining to be read in the
191
+ # buffer.
192
+ def read_int64
193
+ hi = read_long or return nil
194
+ lo = read_long or return nil
195
+ return (hi << 32) + lo
196
+ end
197
+
198
+ # Return the next four bytes as a long integer (in network byte order).
199
+ # Returns nil if there are less than 4 bytes remaining to be read in the
200
+ # buffer.
201
+ def read_long
202
+ b = read(4) or return nil
203
+ b.unpack("N").first
204
+ end
205
+
206
+ # Read and return the next byte in the buffer. Returns nil if called at
207
+ # the end of the buffer.
208
+ def read_byte
209
+ b = read(1) or return nil
210
+ b.getbyte(0)
211
+ end
212
+
213
+ # Read and return an SSH2-encoded string. The string starts with a long
214
+ # integer that describes the number of bytes remaining in the string.
215
+ # Returns nil if there are not enough bytes to satisfy the request.
216
+ def read_string
217
+ length = read_long or return nil
218
+ read(length)
219
+ end
220
+
221
+ # Read a single byte and convert it into a boolean, using 'C' rules
222
+ # (i.e., zero is false, non-zero is true).
223
+ def read_bool
224
+ b = read_byte or return nil
225
+ b != 0
226
+ end
227
+
228
+ # Read a bignum (OpenSSL::BN) from the buffer, in SSH2 format. It is
229
+ # essentially just a string, which is reinterpreted to be a bignum in
230
+ # binary format.
231
+ def read_bignum
232
+ data = read_string
233
+ return unless data
234
+ OpenSSL::BN.new(data, 2)
235
+ end
236
+
237
+ # Read a key from the buffer. The key will start with a string
238
+ # describing its type. The remainder of the key is defined by the
239
+ # type that was read.
240
+ def read_key
241
+ type = read_string
242
+ return (type ? read_keyblob(type) : nil)
243
+ end
244
+
245
+ # Read a keyblob of the given type from the buffer, and return it as
246
+ # a key. Only RSA and DSA keys are supported.
247
+ def read_keyblob(type)
248
+ case type
249
+ when "ssh-dss"
250
+ key = OpenSSL::PKey::DSA.new
251
+ key.p = read_bignum
252
+ key.q = read_bignum
253
+ key.g = read_bignum
254
+ key.pub_key = read_bignum
255
+
256
+ when "ssh-rsa"
257
+ key = OpenSSL::PKey::RSA.new
258
+ key.e = read_bignum
259
+ key.n = read_bignum
260
+
261
+ else
262
+ raise NotImplementedError, "unsupported key type `#{type}'"
263
+ end
264
+
265
+ return key
266
+ end
267
+
268
+ # Reads the next string from the buffer, and returns a new Buffer
269
+ # object that wraps it.
270
+ def read_buffer
271
+ Buffer.new(read_string)
272
+ end
273
+
274
+ # Writes the given data literally into the string. Does not alter the
275
+ # read position. Returns the buffer object.
276
+ def write(*data)
277
+ data.each { |datum| @content << datum }
278
+ self
279
+ end
280
+
281
+ # Writes each argument to the buffer as a network-byte-order-encoded
282
+ # 64-bit integer (8 bytes). Does not alter the read position. Returns the
283
+ # buffer object.
284
+ def write_int64(*n)
285
+ n.each do |i|
286
+ hi = (i >> 32) & 0xFFFFFFFF
287
+ lo = i & 0xFFFFFFFF
288
+ @content << [hi, lo].pack("N2")
289
+ end
290
+ self
291
+ end
292
+
293
+ # Writes each argument to the buffer as a network-byte-order-encoded
294
+ # long (4-byte) integer. Does not alter the read position. Returns the
295
+ # buffer object.
296
+ def write_long(*n)
297
+ @content << n.pack("N*")
298
+ self
299
+ end
300
+
301
+ # Writes each argument to the buffer as a byte. Does not alter the read
302
+ # position. Returns the buffer object.
303
+ def write_byte(*n)
304
+ n.each { |b| @content << b.chr }
305
+ self
306
+ end
307
+
308
+ # Writes each argument to the buffer as an SSH2-encoded string. Each
309
+ # string is prefixed by its length, encoded as a 4-byte long integer.
310
+ # Does not alter the read position. Returns the buffer object.
311
+ def write_string(*text)
312
+ text.each do |string|
313
+ s = string.to_s
314
+ write_long(s.length)
315
+ write(s)
316
+ end
317
+ self
318
+ end
319
+
320
+ # Writes each argument to the buffer as a (C-style) boolean, with 1
321
+ # meaning true, and 0 meaning false. Does not alter the read position.
322
+ # Returns the buffer object.
323
+ def write_bool(*b)
324
+ b.each { |v| @content << (v ? "\1" : "\0") }
325
+ self
326
+ end
327
+
328
+ # Writes each argument to the buffer as a bignum (SSH2-style). No
329
+ # checking is done to ensure that the arguments are, in fact, bignums.
330
+ # Does not alter the read position. Returns the buffer object.
331
+ def write_bignum(*n)
332
+ @content << n.map { |b| Utils.write_bignum(b) }.join
333
+ self
334
+ end
335
+
336
+ # Writes the given arguments to the buffer as SSH2-encoded keys. Does not
337
+ # alter the read position. Returns the buffer object.
338
+ def write_key(*key)
339
+ key.each { |k| append(k.public_identity(false)) }
340
+ self
341
+ end
342
+ end
343
+ end