orthrus-ssh 0.5.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.
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