net-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/doc/LICENSE-BSD +27 -0
- data/doc/LICENSE-GPL +280 -0
- data/doc/LICENSE-RUBY +56 -0
- data/doc/README +13 -0
- data/doc/manual-html/chapter-1.html +333 -0
- data/doc/manual-html/chapter-2.html +455 -0
- data/doc/manual-html/chapter-3.html +413 -0
- data/doc/manual-html/chapter-4.html +353 -0
- data/doc/manual-html/chapter-5.html +393 -0
- data/doc/manual-html/chapter-6.html +296 -0
- data/doc/manual-html/index.html +217 -0
- data/doc/manual-html/manual.css +192 -0
- data/doc/manual/chapter.erb +18 -0
- data/doc/manual/example.erb +18 -0
- data/doc/manual/index.erb +29 -0
- data/doc/manual/manual.css +192 -0
- data/doc/manual/manual.rb +240 -0
- data/doc/manual/manual.yml +67 -0
- data/doc/manual/page.erb +87 -0
- data/doc/manual/parts/channels_callbacks.txt +32 -0
- data/doc/manual/parts/channels_loop.txt +14 -0
- data/doc/manual/parts/channels_open.txt +20 -0
- data/doc/manual/parts/channels_operations.txt +15 -0
- data/doc/manual/parts/channels_types.txt +3 -0
- data/doc/manual/parts/channels_what_are.txt +7 -0
- data/doc/manual/parts/exec_channels.txt +28 -0
- data/doc/manual/parts/exec_open.txt +51 -0
- data/doc/manual/parts/exec_popen3.txt +35 -0
- data/doc/manual/parts/forward_direct.txt +37 -0
- data/doc/manual/parts/forward_handlers.txt +16 -0
- data/doc/manual/parts/forward_intro.txt +18 -0
- data/doc/manual/parts/forward_local.txt +18 -0
- data/doc/manual/parts/forward_remote.txt +14 -0
- data/doc/manual/parts/intro_author.txt +1 -0
- data/doc/manual/parts/intro_getting.txt +39 -0
- data/doc/manual/parts/intro_license.txt +6 -0
- data/doc/manual/parts/intro_support.txt +7 -0
- data/doc/manual/parts/intro_what_is.txt +7 -0
- data/doc/manual/parts/intro_what_is_not.txt +3 -0
- data/doc/manual/parts/proxy_http.txt +52 -0
- data/doc/manual/parts/proxy_intro.txt +1 -0
- data/doc/manual/parts/proxy_socks.txt +23 -0
- data/doc/manual/parts/session_key.txt +66 -0
- data/doc/manual/parts/session_options.txt +42 -0
- data/doc/manual/parts/session_session.txt +14 -0
- data/doc/manual/parts/session_start.txt +49 -0
- data/doc/manual/tutorial.erb +30 -0
- data/examples/channel-demo.rb +81 -0
- data/examples/port-forward.rb +51 -0
- data/examples/process-demo.rb +91 -0
- data/examples/remote-net-port-forward.rb +45 -0
- data/examples/remote-port-forward.rb +80 -0
- data/examples/tail-demo.rb +49 -0
- data/lib/net/ssh.rb +52 -0
- data/lib/net/ssh/connection/channel.rb +411 -0
- data/lib/net/ssh/connection/constants.rb +47 -0
- data/lib/net/ssh/connection/driver.rb +343 -0
- data/lib/net/ssh/connection/services.rb +72 -0
- data/lib/net/ssh/connection/term.rb +90 -0
- data/lib/net/ssh/errors.rb +27 -0
- data/lib/net/ssh/proxy/errors.rb +34 -0
- data/lib/net/ssh/proxy/http.rb +126 -0
- data/lib/net/ssh/proxy/socks4.rb +83 -0
- data/lib/net/ssh/proxy/socks5.rb +160 -0
- data/lib/net/ssh/service/forward/driver.rb +319 -0
- data/lib/net/ssh/service/forward/local-network-handler.rb +74 -0
- data/lib/net/ssh/service/forward/remote-network-handler.rb +81 -0
- data/lib/net/ssh/service/forward/services.rb +76 -0
- data/lib/net/ssh/service/process/driver.rb +153 -0
- data/lib/net/ssh/service/process/open.rb +193 -0
- data/lib/net/ssh/service/process/popen3.rb +160 -0
- data/lib/net/ssh/service/process/services.rb +66 -0
- data/lib/net/ssh/service/services.rb +44 -0
- data/lib/net/ssh/session.rb +242 -0
- data/lib/net/ssh/transport/algorithm-negotiator.rb +267 -0
- data/lib/net/ssh/transport/compress/compressor.rb +53 -0
- data/lib/net/ssh/transport/compress/decompressor.rb +53 -0
- data/lib/net/ssh/transport/compress/none-compressor.rb +39 -0
- data/lib/net/ssh/transport/compress/none-decompressor.rb +39 -0
- data/lib/net/ssh/transport/compress/services.rb +68 -0
- data/lib/net/ssh/transport/compress/zlib-compressor.rb +60 -0
- data/lib/net/ssh/transport/compress/zlib-decompressor.rb +52 -0
- data/lib/net/ssh/transport/constants.rb +66 -0
- data/lib/net/ssh/transport/errors.rb +47 -0
- data/lib/net/ssh/transport/identity-cipher.rb +61 -0
- data/lib/net/ssh/transport/kex/dh-gex.rb +106 -0
- data/lib/net/ssh/transport/kex/dh.rb +231 -0
- data/lib/net/ssh/transport/kex/services.rb +60 -0
- data/lib/net/ssh/transport/ossl/buffer-factory.rb +52 -0
- data/lib/net/ssh/transport/ossl/buffer.rb +87 -0
- data/lib/net/ssh/transport/ossl/cipher-factory.rb +98 -0
- data/lib/net/ssh/transport/ossl/digest-factory.rb +51 -0
- data/lib/net/ssh/transport/ossl/hmac-factory.rb +71 -0
- data/lib/net/ssh/transport/ossl/hmac/hmac.rb +62 -0
- data/lib/net/ssh/transport/ossl/hmac/md5-96.rb +44 -0
- data/lib/net/ssh/transport/ossl/hmac/md5.rb +46 -0
- data/lib/net/ssh/transport/ossl/hmac/none.rb +46 -0
- data/lib/net/ssh/transport/ossl/hmac/services.rb +68 -0
- data/lib/net/ssh/transport/ossl/hmac/sha1-96.rb +44 -0
- data/lib/net/ssh/transport/ossl/hmac/sha1.rb +45 -0
- data/lib/net/ssh/transport/ossl/key-factory.rb +113 -0
- data/lib/net/ssh/transport/ossl/services.rb +149 -0
- data/lib/net/ssh/transport/packet-stream.rb +210 -0
- data/lib/net/ssh/transport/services.rb +146 -0
- data/lib/net/ssh/transport/session.rb +296 -0
- data/lib/net/ssh/transport/version-negotiator.rb +73 -0
- data/lib/net/ssh/userauth/agent.rb +218 -0
- data/lib/net/ssh/userauth/constants.rb +35 -0
- data/lib/net/ssh/userauth/driver.rb +176 -0
- data/lib/net/ssh/userauth/methods/hostbased.rb +119 -0
- data/lib/net/ssh/userauth/methods/password.rb +70 -0
- data/lib/net/ssh/userauth/methods/publickey.rb +137 -0
- data/lib/net/ssh/userauth/methods/services.rb +63 -0
- data/lib/net/ssh/userauth/services.rb +126 -0
- data/lib/net/ssh/userauth/userkeys.rb +258 -0
- data/lib/net/ssh/util/buffer.rb +274 -0
- data/lib/net/ssh/util/openssl.rb +146 -0
- data/lib/net/ssh/util/prompter.rb +73 -0
- data/lib/net/ssh/version.rb +29 -0
- data/test/ALL-TESTS.rb +21 -0
- data/test/connection/tc_channel.rb +136 -0
- data/test/connection/tc_driver.rb +287 -0
- data/test/connection/tc_integration.rb +85 -0
- data/test/proxy/tc_http.rb +209 -0
- data/test/proxy/tc_socks4.rb +148 -0
- data/test/proxy/tc_socks5.rb +214 -0
- data/test/service/forward/tc_driver.rb +289 -0
- data/test/service/forward/tc_local_network_handler.rb +123 -0
- data/test/service/forward/tc_remote_network_handler.rb +108 -0
- data/test/service/process/tc_driver.rb +79 -0
- data/test/service/process/tc_integration.rb +117 -0
- data/test/service/process/tc_open.rb +179 -0
- data/test/service/process/tc_popen3.rb +164 -0
- data/test/tc_integration.rb +79 -0
- data/test/transport/compress/tc_none_compress.rb +41 -0
- data/test/transport/compress/tc_none_decompress.rb +45 -0
- data/test/transport/compress/tc_zlib_compress.rb +61 -0
- data/test/transport/compress/tc_zlib_decompress.rb +48 -0
- data/test/transport/kex/tc_dh.rb +304 -0
- data/test/transport/kex/tc_dh_gex.rb +70 -0
- data/test/transport/ossl/fixtures/dsa-encrypted +15 -0
- data/test/transport/ossl/fixtures/dsa-encrypted-bad +15 -0
- data/test/transport/ossl/fixtures/dsa-unencrypted +12 -0
- data/test/transport/ossl/fixtures/dsa-unencrypted-bad +12 -0
- data/test/transport/ossl/fixtures/dsa-unencrypted.pub +1 -0
- data/test/transport/ossl/fixtures/not-a-private-key +4 -0
- data/test/transport/ossl/fixtures/not-supported +2 -0
- data/test/transport/ossl/fixtures/rsa-encrypted +18 -0
- data/test/transport/ossl/fixtures/rsa-encrypted-bad +18 -0
- data/test/transport/ossl/fixtures/rsa-unencrypted +15 -0
- data/test/transport/ossl/fixtures/rsa-unencrypted-bad +15 -0
- data/test/transport/ossl/fixtures/rsa-unencrypted.pub +1 -0
- data/test/transport/ossl/hmac/tc_hmac.rb +58 -0
- data/test/transport/ossl/hmac/tc_md5.rb +50 -0
- data/test/transport/ossl/hmac/tc_md5_96.rb +50 -0
- data/test/transport/ossl/hmac/tc_none.rb +50 -0
- data/test/transport/ossl/hmac/tc_sha1.rb +50 -0
- data/test/transport/ossl/hmac/tc_sha1_96.rb +50 -0
- data/test/transport/ossl/tc_buffer.rb +97 -0
- data/test/transport/ossl/tc_buffer_factory.rb +67 -0
- data/test/transport/ossl/tc_cipher_factory.rb +84 -0
- data/test/transport/ossl/tc_digest_factory.rb +39 -0
- data/test/transport/ossl/tc_hmac_factory.rb +72 -0
- data/test/transport/ossl/tc_key_factory.rb +199 -0
- data/test/transport/tc_algorithm_negotiator.rb +169 -0
- data/test/transport/tc_identity_cipher.rb +52 -0
- data/test/transport/tc_integration.rb +110 -0
- data/test/transport/tc_packet_stream.rb +183 -0
- data/test/transport/tc_session.rb +283 -0
- data/test/transport/tc_version_negotiator.rb +86 -0
- data/test/userauth/methods/tc_hostbased.rb +136 -0
- data/test/userauth/methods/tc_password.rb +89 -0
- data/test/userauth/methods/tc_publickey.rb +167 -0
- data/test/userauth/tc_agent.rb +223 -0
- data/test/userauth/tc_driver.rb +190 -0
- data/test/userauth/tc_integration.rb +81 -0
- data/test/userauth/tc_userkeys.rb +265 -0
- data/test/util/tc_buffer.rb +217 -0
- metadata +256 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# This source file is distributed as part of the Net::SSH Secure Shell Client
|
|
7
|
+
# library for Ruby. This file (and the library as a whole) may be used only as
|
|
8
|
+
# allowed by either the BSD license, or the Ruby license (or, by association
|
|
9
|
+
# with the Ruby license, the GPL). See the "doc" subdirectory of the Net::SSH
|
|
10
|
+
# distribution for the texts of these licenses.
|
|
11
|
+
# -----------------------------------------------------------------------------
|
|
12
|
+
# net-ssh website : http://net-ssh.rubyforge.org
|
|
13
|
+
# project website: http://rubyforge.org/projects/net-ssh
|
|
14
|
+
# =============================================================================
|
|
15
|
+
#++
|
|
16
|
+
|
|
17
|
+
require 'thread'
|
|
18
|
+
|
|
19
|
+
require 'net/ssh/errors'
|
|
20
|
+
require 'net/ssh/transport/errors'
|
|
21
|
+
|
|
22
|
+
module Net
|
|
23
|
+
module SSH
|
|
24
|
+
module Transport
|
|
25
|
+
|
|
26
|
+
# The abstract parent of IncomingPacketStream and OutgoingPacketStream. It
|
|
27
|
+
# represents the common interface of its subclasses.
|
|
28
|
+
class PacketStream
|
|
29
|
+
|
|
30
|
+
# the sequence number of the next packet to be processed.
|
|
31
|
+
attr_reader :sequence_number
|
|
32
|
+
|
|
33
|
+
# the setter for setting the socket to use for IO communication
|
|
34
|
+
attr_writer :socket
|
|
35
|
+
|
|
36
|
+
# Create a new packet stream. The given ciphers and hmacs are factories
|
|
37
|
+
# that are used to initialize the cipher and mac attributes.
|
|
38
|
+
def initialize( ciphers, hmacs )
|
|
39
|
+
@sequence_number = 0
|
|
40
|
+
|
|
41
|
+
@cipher = ciphers.get( "none" )
|
|
42
|
+
@hmac = hmacs.get( "none" )
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Set the cipher and mac algorithms to the given arguments.
|
|
46
|
+
def set_algorithms( cipher, mac )
|
|
47
|
+
@cipher, @hmac = cipher, mac
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Compute the mac for the given payload.
|
|
51
|
+
def compute_hmac( payload )
|
|
52
|
+
@hmac.digest( [ @sequence_number, payload ].pack( "NA*" ) )
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Increment the sequence number. This handles the (rare) case of a
|
|
56
|
+
# sequence number overflowing a long integer, and resets it safely to 0
|
|
57
|
+
# (as required by the SSH2 protocol).
|
|
58
|
+
def increment_sequence_number
|
|
59
|
+
@sequence_number += 1
|
|
60
|
+
@sequence_number = 0 if @sequence_number > 0xFFFFFFFF
|
|
61
|
+
end
|
|
62
|
+
private :increment_sequence_number
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Handles the compression and encryption of outgoing packets.
|
|
67
|
+
class OutgoingPacketStream < PacketStream
|
|
68
|
+
|
|
69
|
+
# Create a new OutgoingPacketStream.
|
|
70
|
+
def initialize( ciphers, hmacs, compressors )
|
|
71
|
+
super( ciphers, hmacs )
|
|
72
|
+
@compressor = compressors.fetch( "none" )
|
|
73
|
+
@mutex = Mutex.new
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Set the cipher, mac, and compressor to the given values.
|
|
77
|
+
def set_algorithms( cipher, hmac, compressor )
|
|
78
|
+
super( cipher, hmac )
|
|
79
|
+
@compressor = compressor
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Send the given payload over the socket, after (possibly) compressing
|
|
83
|
+
# and encrypting it. The payload is converted to a string (using #to_s)
|
|
84
|
+
# before being manipulated.
|
|
85
|
+
def send( payload )
|
|
86
|
+
@mutex.synchronize do
|
|
87
|
+
# force the payload into a string
|
|
88
|
+
payload = @compressor.compress( payload.to_s )
|
|
89
|
+
|
|
90
|
+
# the length of the packet, minus the padding
|
|
91
|
+
actual_length = 4 + payload.length + 1
|
|
92
|
+
|
|
93
|
+
# compute the padding length
|
|
94
|
+
padding_length = @cipher.block_size -
|
|
95
|
+
( actual_length % @cipher.block_size )
|
|
96
|
+
padding_length += @cipher.block_size if padding_length < 4
|
|
97
|
+
|
|
98
|
+
# compute the packet length (sans the length field itself)
|
|
99
|
+
packet_length = payload.length + padding_length + 1
|
|
100
|
+
|
|
101
|
+
if packet_length < 16
|
|
102
|
+
padding_length += @cipher.block_size
|
|
103
|
+
packet_length = payload.length + padding_length + 1
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
padding = Array.new( padding_length ) { rand(256) }.pack("C*")
|
|
107
|
+
|
|
108
|
+
unencrypted_data = [ packet_length, padding_length, payload,
|
|
109
|
+
padding ].pack( "NCA*A*" )
|
|
110
|
+
mac = compute_hmac( unencrypted_data )
|
|
111
|
+
|
|
112
|
+
encrypted_data = @cipher.update( unencrypted_data ) << @cipher.final
|
|
113
|
+
message = encrypted_data + mac
|
|
114
|
+
@socket.send message, 0
|
|
115
|
+
|
|
116
|
+
increment_sequence_number
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Handles the decompression and dencryption of incoming packets.
|
|
123
|
+
class IncomingPacketStream < PacketStream
|
|
124
|
+
|
|
125
|
+
# A handle to the buffer factory to use when creating buffers
|
|
126
|
+
attr_writer :buffers
|
|
127
|
+
|
|
128
|
+
# A handle to the logger instance to use for writing log messages
|
|
129
|
+
attr_writer :log
|
|
130
|
+
|
|
131
|
+
# Create a new IncomingPacketStream.
|
|
132
|
+
def initialize( ciphers, hmacs, decompressors )
|
|
133
|
+
super( ciphers, hmacs )
|
|
134
|
+
@decompressor = decompressors.fetch( "none" )
|
|
135
|
+
@mutex = Mutex.new
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Set the cipher, mac, and decompressor algorithms to the given values.
|
|
139
|
+
def set_algorithms( cipher, mac, decompressor )
|
|
140
|
+
super( cipher, mac )
|
|
141
|
+
@decompressor = decompressor
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Retrieve the next packet from the string, after (possibly) decrypting
|
|
145
|
+
# and decompressing it. The packet is returned as a reader buffer.
|
|
146
|
+
def get
|
|
147
|
+
@mutex.synchronize do
|
|
148
|
+
# get the first block of data
|
|
149
|
+
if @log.debug?
|
|
150
|
+
@log.debug "reading #{@cipher.block_size} bytes from socket..."
|
|
151
|
+
end
|
|
152
|
+
data = @socket.recv( @cipher.block_size )
|
|
153
|
+
|
|
154
|
+
# if the data is empty, then the socket was closed
|
|
155
|
+
if data.length < 1
|
|
156
|
+
raise Net::SSH::Transport::Disconnect,
|
|
157
|
+
"connection closed by remote host"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# decipher it
|
|
161
|
+
reader = @buffers.reader( @cipher.update( data ) )
|
|
162
|
+
|
|
163
|
+
# determine the packet length and how many bytes remain to be read
|
|
164
|
+
packet_length = reader.read_long
|
|
165
|
+
remaining_to_read = packet_length + 4 - @cipher.block_size
|
|
166
|
+
if @log.debug?
|
|
167
|
+
@log.debug "packet length(#{packet_length}) " +
|
|
168
|
+
"remaining(#{remaining_to_read})"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# read the remainder of the packet and decrypt it.
|
|
172
|
+
data = ""
|
|
173
|
+
loop do
|
|
174
|
+
data << @socket.recv( remaining_to_read - data.length )
|
|
175
|
+
break if data.length >= remaining_to_read
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
reader.append @cipher.update( data )
|
|
179
|
+
reader.append @cipher.final
|
|
180
|
+
|
|
181
|
+
padding_length = reader.read_byte
|
|
182
|
+
|
|
183
|
+
payload = reader.read( packet_length - padding_length - 1 )
|
|
184
|
+
padding = reader.read( padding_length ) if padding_length > 0
|
|
185
|
+
|
|
186
|
+
# get the hmac from the tail of the packet (if one exists), and
|
|
187
|
+
# then validate it.
|
|
188
|
+
hmac = @hmac.mac_length > 0 ? @socket.recv( @hmac.mac_length ) : ""
|
|
189
|
+
|
|
190
|
+
my_computed_hmac = compute_hmac( reader.content )
|
|
191
|
+
raise Net::SSH::Exception,
|
|
192
|
+
"corrupted mac detected" if hmac != my_computed_hmac
|
|
193
|
+
|
|
194
|
+
# decompress the payload
|
|
195
|
+
payload = @decompressor.decompress( payload )
|
|
196
|
+
|
|
197
|
+
increment_sequence_number
|
|
198
|
+
|
|
199
|
+
buffer = @buffers.reader( payload )
|
|
200
|
+
@log.debug "received: #{buffer.content.inspect}" if @log.debug?
|
|
201
|
+
|
|
202
|
+
return buffer
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# This source file is distributed as part of the Net::SSH Secure Shell Client
|
|
7
|
+
# library for Ruby. This file (and the library as a whole) may be used only as
|
|
8
|
+
# allowed by either the BSD license, or the Ruby license (or, by association
|
|
9
|
+
# with the Ruby license, the GPL). See the "doc" subdirectory of the Net::SSH
|
|
10
|
+
# distribution for the texts of these licenses.
|
|
11
|
+
# -----------------------------------------------------------------------------
|
|
12
|
+
# net-ssh website : http://net-ssh.rubyforge.org
|
|
13
|
+
# project website: http://rubyforge.org/projects/net-ssh
|
|
14
|
+
# =============================================================================
|
|
15
|
+
#++
|
|
16
|
+
|
|
17
|
+
module Net
|
|
18
|
+
module SSH
|
|
19
|
+
module Transport
|
|
20
|
+
|
|
21
|
+
# Register the services that together implement the SSH transport layer.
|
|
22
|
+
def register_services( container )
|
|
23
|
+
container.namespace_define :transport do |b|
|
|
24
|
+
b.kex_names { Hash.new }
|
|
25
|
+
b.compression_algorithms { Hash.new }
|
|
26
|
+
b.decompression_algorithms { Hash.new }
|
|
27
|
+
|
|
28
|
+
b.cipher_factories { Hash.new }
|
|
29
|
+
b.hmac_factories { Hash.new }
|
|
30
|
+
b.key_factories { Hash.new }
|
|
31
|
+
b.buffer_factories { Hash.new }
|
|
32
|
+
b.bn_factories { Hash.new }
|
|
33
|
+
b.digest_factories { Hash.new }
|
|
34
|
+
|
|
35
|
+
b.ciphers( :model => :prototype ) { |c,|
|
|
36
|
+
c.cipher_factories.fetch( c.crypto_backend ) }
|
|
37
|
+
|
|
38
|
+
b.hmacs( :model => :prototype ) { |c,|
|
|
39
|
+
c.hmac_factories.fetch( c.crypto_backend ) }
|
|
40
|
+
|
|
41
|
+
b.keys( :model => :prototype ) { |c,|
|
|
42
|
+
c.key_factories.fetch( c.crypto_backend ) }
|
|
43
|
+
|
|
44
|
+
b.buffers( :model => :prototype ) { |c,|
|
|
45
|
+
c.buffer_factories.fetch( c.crypto_backend ) }
|
|
46
|
+
|
|
47
|
+
b.bns( :model => :prototype ) { |c,|
|
|
48
|
+
c.bn_factories.fetch( c.crypto_backend ) }
|
|
49
|
+
|
|
50
|
+
b.digesters( :model => :prototype ) { |c,|
|
|
51
|
+
c.digest_factories.fetch( c.crypto_backend ) }
|
|
52
|
+
|
|
53
|
+
b.identity_cipher do
|
|
54
|
+
require 'net/ssh/transport/identity-cipher'
|
|
55
|
+
IdentityCipher.new
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
b.outgoing_packet_stream :model => :prototype_deferred do |c,|
|
|
59
|
+
require 'net/ssh/transport/packet-stream'
|
|
60
|
+
OutgoingPacketStream.new(
|
|
61
|
+
c.ciphers, c.hmacs, c.compression_algorithms )
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
b.incoming_packet_stream :model => :prototype_deferred do |c,point|
|
|
65
|
+
require 'net/ssh/transport/packet-stream'
|
|
66
|
+
stream = IncomingPacketStream.new(
|
|
67
|
+
c.ciphers, c.hmacs, c.decompression_algorithms )
|
|
68
|
+
stream.buffers = c.buffers
|
|
69
|
+
stream.log = c.log_for( point )
|
|
70
|
+
stream
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
b.algorithms do
|
|
74
|
+
Hash[
|
|
75
|
+
:host_key => [ "ssh-dss", "ssh-rsa" ],
|
|
76
|
+
:kex => [ "diffie-hellman-group-exchange-sha1",
|
|
77
|
+
"diffie-hellman-group1-sha1" ],
|
|
78
|
+
:encryption => [ "3des-cbc",
|
|
79
|
+
"aes128-cbc",
|
|
80
|
+
"blowfish-cbc",
|
|
81
|
+
"aes256-cbc",
|
|
82
|
+
"aes192-cbc",
|
|
83
|
+
"idea-cbc",
|
|
84
|
+
"none" ],
|
|
85
|
+
:hmac => [ "hmac-md5",
|
|
86
|
+
"hmac-sha1",
|
|
87
|
+
"hmac-md5-96",
|
|
88
|
+
"hmac-sha1-96",
|
|
89
|
+
"none" ],
|
|
90
|
+
:compression => [ "none", "zlib" ],
|
|
91
|
+
:languages => []
|
|
92
|
+
]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
b.default_ssh_port { 22 }
|
|
96
|
+
|
|
97
|
+
b.socket_factory do
|
|
98
|
+
require 'socket'
|
|
99
|
+
TCPSocket
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
b.version_negotiator do |c,point|
|
|
103
|
+
require 'net/ssh/transport/version-negotiator'
|
|
104
|
+
VersionNegotiator.new( c.log_for( point ) )
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
b.algorithm_negotiator do |c,point|
|
|
108
|
+
require 'net/ssh/transport/algorithm-negotiator'
|
|
109
|
+
AlgorithmNegotiator.new(
|
|
110
|
+
c.log_for( point ),
|
|
111
|
+
c.algorithms,
|
|
112
|
+
c.buffers )
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
b.session do |c,point|
|
|
116
|
+
require 'net/ssh/transport/session'
|
|
117
|
+
|
|
118
|
+
args = [ c[:transport_host] ]
|
|
119
|
+
args << c[:transport_options] if c.knows_key?(:transport_options)
|
|
120
|
+
|
|
121
|
+
Session.new( *args ) do |s|
|
|
122
|
+
s.logger = c[:log_for, point]
|
|
123
|
+
s.default_port = c[:default_ssh_port]
|
|
124
|
+
s.version_negotiator = c[:version_negotiator]
|
|
125
|
+
s.algorithm_negotiator = c[:algorithm_negotiator]
|
|
126
|
+
s.socket_factory = c[:socket_factory]
|
|
127
|
+
s.packet_sender = c[:outgoing_packet_stream]
|
|
128
|
+
s.packet_receiver = c[:incoming_packet_stream]
|
|
129
|
+
s.ciphers = c[:ciphers]
|
|
130
|
+
s.hmacs = c[:hmacs]
|
|
131
|
+
s.kexs = c[:kex_names]
|
|
132
|
+
s.compressors = c[:compression_algorithms]
|
|
133
|
+
s.decompressors = c[:decompression_algorithms]
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
b.require 'net/ssh/transport/ossl/services', "#{self}::OSSL"
|
|
138
|
+
b.require 'net/ssh/transport/compress/services', "#{self}::Compress"
|
|
139
|
+
b.require 'net/ssh/transport/kex/services', "#{self}::Kex"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
module_function :register_services
|
|
143
|
+
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# This source file is distributed as part of the Net::SSH Secure Shell Client
|
|
7
|
+
# library for Ruby. This file (and the library as a whole) may be used only as
|
|
8
|
+
# allowed by either the BSD license, or the Ruby license (or, by association
|
|
9
|
+
# with the Ruby license, the GPL). See the "doc" subdirectory of the Net::SSH
|
|
10
|
+
# distribution for the texts of these licenses.
|
|
11
|
+
# -----------------------------------------------------------------------------
|
|
12
|
+
# net-ssh website : http://net-ssh.rubyforge.org
|
|
13
|
+
# project website: http://rubyforge.org/projects/net-ssh
|
|
14
|
+
# =============================================================================
|
|
15
|
+
#++
|
|
16
|
+
|
|
17
|
+
require 'socket'
|
|
18
|
+
require 'net/ssh/transport/constants'
|
|
19
|
+
require 'net/ssh/transport/errors'
|
|
20
|
+
require 'net/ssh/version'
|
|
21
|
+
|
|
22
|
+
module Net
|
|
23
|
+
module SSH
|
|
24
|
+
module Transport
|
|
25
|
+
|
|
26
|
+
# Represents a low-level SSH session, at the transport protocol level.
|
|
27
|
+
# This handles the algorithm negotiation and key exchange for any SSH
|
|
28
|
+
# connection.
|
|
29
|
+
class Session
|
|
30
|
+
include Constants
|
|
31
|
+
|
|
32
|
+
# the unique session identifier
|
|
33
|
+
attr_reader :session_id
|
|
34
|
+
|
|
35
|
+
# the collection of algorithms currently being used
|
|
36
|
+
attr_reader :algorithms
|
|
37
|
+
|
|
38
|
+
attr_writer :logger
|
|
39
|
+
attr_writer :default_port
|
|
40
|
+
attr_writer :version_negotiator
|
|
41
|
+
attr_writer :algorithm_negotiator
|
|
42
|
+
attr_writer :socket_factory
|
|
43
|
+
attr_writer :packet_sender
|
|
44
|
+
attr_writer :packet_receiver
|
|
45
|
+
attr_writer :ciphers
|
|
46
|
+
attr_writer :hmacs
|
|
47
|
+
attr_writer :kexs
|
|
48
|
+
attr_writer :compressors
|
|
49
|
+
attr_writer :decompressors
|
|
50
|
+
|
|
51
|
+
# The name that Net::SSH reports for itself
|
|
52
|
+
NAME = "Ruby/Net::SSH"
|
|
53
|
+
|
|
54
|
+
# The SSH protocol supported by Net::SSH.
|
|
55
|
+
PROTOCOL = "SSH-2.0"
|
|
56
|
+
|
|
57
|
+
# Returns the version string of this client.
|
|
58
|
+
def self.version
|
|
59
|
+
"#{PROTOCOL}-#{NAME}_#{Net::SSH::Version::STRING}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
VALID_OPTIONS = [ :port, :host_key, :kex, :encryption, :hmac,
|
|
63
|
+
:compression, :languages, :compression_level, :proxy ]
|
|
64
|
+
|
|
65
|
+
# Create a new connection to the given host. This will negotiate the
|
|
66
|
+
# algorithms to use and exchange the keys. A block must be given. The
|
|
67
|
+
# uninitialized +self+ will be passed to the block, so that dependencies
|
|
68
|
+
# may be injected.
|
|
69
|
+
def initialize( host, options={} )
|
|
70
|
+
@saved_message = nil
|
|
71
|
+
@session_id = nil
|
|
72
|
+
|
|
73
|
+
yield self
|
|
74
|
+
|
|
75
|
+
invalid_options = options.keys - VALID_OPTIONS
|
|
76
|
+
|
|
77
|
+
unless invalid_options.empty?
|
|
78
|
+
raise ArgumentError,
|
|
79
|
+
"invalid option(s) to #{self.class}: #{invalid_options.inspect}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
@port = options[ :port ] || @default_port
|
|
83
|
+
@socket = ( options[:proxy] || @socket_factory ).open( host, @port )
|
|
84
|
+
|
|
85
|
+
@packet_sender.socket = @socket
|
|
86
|
+
@packet_receiver.socket = @socket
|
|
87
|
+
|
|
88
|
+
@kex_info = {
|
|
89
|
+
:client_version_string => self.class.version,
|
|
90
|
+
:server_version_string =>
|
|
91
|
+
@version_negotiator.negotiate( @socket, self.class.version ) }
|
|
92
|
+
|
|
93
|
+
@options = options
|
|
94
|
+
kexinit
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Returns the name of the client's host, as reported by the socket.
|
|
98
|
+
def client_name
|
|
99
|
+
return @hostname if defined? @hostname
|
|
100
|
+
|
|
101
|
+
sockaddr = @socket.getsockname
|
|
102
|
+
begin
|
|
103
|
+
@hostname =
|
|
104
|
+
Socket.getnameinfo( sockaddr, Socket::NI_NAMEREQD ).first
|
|
105
|
+
rescue
|
|
106
|
+
@hostname = Socket.getnameinfo( sockaddr ).first
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
return @hostname
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def kexinit
|
|
113
|
+
@doing_kexinit = true
|
|
114
|
+
@algorithms = @algorithm_negotiator.negotiate( self, @options )
|
|
115
|
+
|
|
116
|
+
@kex_info[ :server_algorithm_packet ] = @algorithms.server_packet
|
|
117
|
+
@kex_info[ :client_algorithm_packet ] = @algorithms.client_packet
|
|
118
|
+
|
|
119
|
+
exchange_keys
|
|
120
|
+
@doing_kexinit = false
|
|
121
|
+
end
|
|
122
|
+
private :kexinit
|
|
123
|
+
|
|
124
|
+
# Closes the connection.
|
|
125
|
+
def close
|
|
126
|
+
# TODO: send a DISCONNECT message to the server to close gracefully
|
|
127
|
+
@socket.shutdown
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def get_kex_byte_requirement
|
|
131
|
+
need = 0
|
|
132
|
+
|
|
133
|
+
[ @algorithms.encryption_s2c,
|
|
134
|
+
@algorithms.encryption_c2s
|
|
135
|
+
].each do |alg|
|
|
136
|
+
key_len, block_size = @ciphers.get_lengths( alg )
|
|
137
|
+
need = key_len if need < key_len
|
|
138
|
+
need = block_size if need < block_size
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
[ @algorithms.mac_c2s, @algorithms.mac_s2c ].each do |alg|
|
|
142
|
+
key_len = @hmacs.get_key_length( alg )
|
|
143
|
+
need = key_len if need < key_len
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
return need
|
|
147
|
+
end
|
|
148
|
+
private :get_kex_byte_requirement
|
|
149
|
+
|
|
150
|
+
# Exchanges keys with the server, using the kex algorithm negotiated
|
|
151
|
+
# during the algorithm negotiation phase. After finishing this phase,
|
|
152
|
+
# further packets sent to or from the server will be encrypted and
|
|
153
|
+
# (possibly) compressed.
|
|
154
|
+
def exchange_keys
|
|
155
|
+
@kex_info[ :need_bytes ] = get_kex_byte_requirement
|
|
156
|
+
|
|
157
|
+
kex = @kexs.fetch( @algorithms.kex )
|
|
158
|
+
result = kex.exchange_keys( self, @kex_info )
|
|
159
|
+
|
|
160
|
+
@shared_secret = result[ :shared_secret ]
|
|
161
|
+
hash = result[ :session_id ]
|
|
162
|
+
@session_id = hash unless @session_id
|
|
163
|
+
@server_key = result[ :server_key ]
|
|
164
|
+
@hashing_algorithm = result[ :hashing_algorithm ]
|
|
165
|
+
|
|
166
|
+
# prepare the ciphers, et. al.
|
|
167
|
+
secret_bin = @shared_secret.to_ssh
|
|
168
|
+
|
|
169
|
+
iv_c2s = @hashing_algorithm.digest( secret_bin +
|
|
170
|
+
hash +
|
|
171
|
+
"A" +
|
|
172
|
+
@session_id )
|
|
173
|
+
iv_s2c = @hashing_algorithm.digest( secret_bin +
|
|
174
|
+
hash +
|
|
175
|
+
"B" +
|
|
176
|
+
@session_id )
|
|
177
|
+
key_c2s = @hashing_algorithm.digest( secret_bin +
|
|
178
|
+
hash +
|
|
179
|
+
"C" +
|
|
180
|
+
@session_id )
|
|
181
|
+
key_s2c = @hashing_algorithm.digest( secret_bin +
|
|
182
|
+
hash +
|
|
183
|
+
"D" +
|
|
184
|
+
@session_id )
|
|
185
|
+
mac_key_c2s = @hashing_algorithm.digest( secret_bin +
|
|
186
|
+
hash +
|
|
187
|
+
"E" +
|
|
188
|
+
@session_id )
|
|
189
|
+
mac_key_s2c = @hashing_algorithm.digest( secret_bin +
|
|
190
|
+
hash +
|
|
191
|
+
"F" +
|
|
192
|
+
@session_id )
|
|
193
|
+
|
|
194
|
+
cipher_c2s = @ciphers.get(
|
|
195
|
+
@algorithms.encryption_c2s, iv_c2s, key_c2s,
|
|
196
|
+
secret_bin, hash, @hashing_algorithm,
|
|
197
|
+
true )
|
|
198
|
+
|
|
199
|
+
cipher_s2c = @ciphers.get(
|
|
200
|
+
@algorithms.encryption_s2c, iv_s2c, key_s2c,
|
|
201
|
+
secret_bin, hash, @hashing_algorithm,
|
|
202
|
+
false )
|
|
203
|
+
|
|
204
|
+
mac_c2s = @hmacs.get( @algorithms.mac_c2s, mac_key_c2s );
|
|
205
|
+
mac_s2c = @hmacs.get( @algorithms.mac_s2c, mac_key_s2c );
|
|
206
|
+
|
|
207
|
+
compression_c2s = @compressors[ @algorithms.compression_c2s ].new(
|
|
208
|
+
:level => @algorithms.compression_level )
|
|
209
|
+
compression_s2c = @decompressors[ @algorithms.compression_s2c ].new
|
|
210
|
+
|
|
211
|
+
@packet_sender.set_algorithms cipher_c2s, mac_c2s, compression_c2s
|
|
212
|
+
@packet_receiver.set_algorithms cipher_s2c, mac_s2c, compression_s2c
|
|
213
|
+
end
|
|
214
|
+
private :exchange_keys
|
|
215
|
+
|
|
216
|
+
# Waits for the next message from the server, handling common requests
|
|
217
|
+
# like DISCONNECT, IGNORE, DEBUG, and KEXINIT in the background. The
|
|
218
|
+
# next message is returned as a [ type, buffer ] tuple, where the buffer
|
|
219
|
+
# is a Net::SSH::Util::ReaderBuffer.
|
|
220
|
+
def wait_for_message
|
|
221
|
+
buffer = type = nil
|
|
222
|
+
|
|
223
|
+
if @saved_message
|
|
224
|
+
type, buffer = @saved_message
|
|
225
|
+
@logger.debug "returning saved message: #{type}" if @logger.debug?
|
|
226
|
+
@saved_message = nil
|
|
227
|
+
else
|
|
228
|
+
loop do
|
|
229
|
+
if @logger.debug?
|
|
230
|
+
@logger.debug "waiting for packet from server..."
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
buffer = @packet_receiver.get
|
|
234
|
+
type = buffer.read_byte
|
|
235
|
+
@logger.debug "got packet of type #{type}" if @logger.debug?
|
|
236
|
+
|
|
237
|
+
case type
|
|
238
|
+
when DISCONNECT
|
|
239
|
+
reason_code = buffer.read_long
|
|
240
|
+
description = buffer.read_string
|
|
241
|
+
language = buffer.read_string
|
|
242
|
+
raise Net::SSH::Transport::Disconnect,
|
|
243
|
+
"disconnected: #{description} (#{reason_code})"
|
|
244
|
+
|
|
245
|
+
when IGNORE
|
|
246
|
+
# do nothing
|
|
247
|
+
@logger.info "received IGNORE message " +
|
|
248
|
+
"(#{buffer.read_string.inspect})" if @logger.debug?
|
|
249
|
+
|
|
250
|
+
when DEBUG
|
|
251
|
+
# do nothing
|
|
252
|
+
@logger.info "received DEBUG message" if @logger.debug?
|
|
253
|
+
always_display = buffer.read_bool
|
|
254
|
+
message = buffer.read_string
|
|
255
|
+
language = buffer.read_string
|
|
256
|
+
if always_display
|
|
257
|
+
@logger.warn "#{message} (#{language})" if @logger.warn?
|
|
258
|
+
else
|
|
259
|
+
@logger.debug "#{message} (#{language})" if @logger.debug?
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
when KEXINIT
|
|
263
|
+
# unless we're already doing a key-exchange, do key
|
|
264
|
+
# re-exchange
|
|
265
|
+
if !@doing_kexinit
|
|
266
|
+
@logger.info "re-key requested" if @logger.info?
|
|
267
|
+
@saved_message = [ type, buffer ]
|
|
268
|
+
kexinit
|
|
269
|
+
else
|
|
270
|
+
break
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
else
|
|
274
|
+
break
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
return type, buffer
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Sends the given payload, using the currently configured
|
|
283
|
+
# OutgoingPacketStream.
|
|
284
|
+
def send_message( message )
|
|
285
|
+
if @logger.debug?
|
|
286
|
+
@logger.debug "sending message >>#{message.to_s.inspect}<<"
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
@packet_sender.send message
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|