rtunnel 0.3.8 → 0.4.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/CHANGELOG +13 -0
- data/LICENSE +21 -0
- data/Manifest +48 -0
- data/README.markdown +84 -0
- data/Rakefile +45 -0
- data/bin/rtunnel_client +2 -1
- data/bin/rtunnel_server +2 -1
- data/lib/rtunnel/client.rb +308 -0
- data/lib/rtunnel/command_processor.rb +62 -0
- data/lib/rtunnel/command_protocol.rb +50 -0
- data/lib/rtunnel/commands.rb +233 -0
- data/lib/rtunnel/connection_id.rb +24 -0
- data/lib/rtunnel/core.rb +58 -0
- data/lib/rtunnel/crypto.rb +106 -0
- data/lib/rtunnel/frame_protocol.rb +34 -0
- data/lib/rtunnel/io_extensions.rb +54 -0
- data/lib/rtunnel/leak.rb +35 -0
- data/lib/rtunnel/rtunnel_client_cmd.rb +41 -0
- data/lib/rtunnel/rtunnel_server_cmd.rb +32 -0
- data/lib/rtunnel/server.rb +351 -0
- data/lib/rtunnel/socket_factory.rb +119 -0
- data/lib/rtunnel.rb +20 -0
- data/rtunnel.gemspec +51 -0
- data/spec/client_spec.rb +47 -0
- data/spec/cmds_spec.rb +127 -0
- data/spec/integration_spec.rb +105 -0
- data/spec/server_spec.rb +21 -0
- data/spec/spec_helper.rb +3 -0
- data/test/command_stubs.rb +77 -0
- data/test/protocol_mocks.rb +43 -0
- data/test/scenario_connection.rb +109 -0
- data/test/test_client.rb +48 -0
- data/test/test_command_protocol.rb +82 -0
- data/test/test_commands.rb +49 -0
- data/test/test_connection_id.rb +30 -0
- data/test/test_crypto.rb +127 -0
- data/test/test_frame_protocol.rb +109 -0
- data/test/test_io_extensions.rb +70 -0
- data/test/test_server.rb +70 -0
- data/test/test_socket_factory.rb +42 -0
- data/test/test_tunnel.rb +186 -0
- data/test_data/authorized_keys2 +4 -0
- data/test_data/known_hosts +4 -0
- data/test_data/random_rsa_key +27 -0
- data/test_data/ssh_host_dsa_key +12 -0
- data/test_data/ssh_host_rsa_key +27 -0
- data/tests/_ab_test.rb +16 -0
- data/tests/_stress_test.rb +96 -0
- data/tests/lo_http_server.rb +55 -0
- metadata +127 -31
- data/History.txt +0 -3
- data/Manifest.txt +0 -13
- data/README.txt +0 -362
- data/lib/client.rb +0 -185
- data/lib/cmds.rb +0 -166
- data/lib/core.rb +0 -53
- data/lib/rtunnel_client_cmd.rb +0 -25
- data/lib/rtunnel_server_cmd.rb +0 -20
- data/lib/server.rb +0 -181
- data/rtunnel_client.rb +0 -3
- data/rtunnel_server.rb +0 -3
data/test/test_crypto.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'rtunnel'
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
class CryptoTest < Test::Unit::TestCase
|
7
|
+
C = RTunnel::Crypto
|
8
|
+
|
9
|
+
@@rsa_key_path = 'test_data/ssh_host_rsa_key'
|
10
|
+
@@dsa_key_path = 'test_data/ssh_host_dsa_key'
|
11
|
+
@@known_hosts_path = 'test_data/known_hosts'
|
12
|
+
@@authorized_keys_path = 'test_data/authorized_keys2'
|
13
|
+
|
14
|
+
def test_read_private_key
|
15
|
+
key = C.read_private_key @@rsa_key_path
|
16
|
+
assert_equal File.read(@@rsa_key_path), key.to_pem
|
17
|
+
assert_equal OpenSSL::PKey::RSA, key.class
|
18
|
+
|
19
|
+
key = C.read_private_key @@dsa_key_path
|
20
|
+
assert_equal File.read(@@dsa_key_path), key.to_pem
|
21
|
+
assert_equal OpenSSL::PKey::DSA, key.class
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_read_known_hosts
|
25
|
+
keys = C.read_authorized_keys @@known_hosts_path
|
26
|
+
verify_authorized_keys keys
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_read_authorized_keys
|
30
|
+
keys = C.read_authorized_keys @@authorized_keys_path
|
31
|
+
verify_authorized_keys keys
|
32
|
+
end
|
33
|
+
|
34
|
+
def verify_authorized_keys(keys)
|
35
|
+
assert_equal 4, keys.length
|
36
|
+
assert_equal [OpenSSL::PKey::RSA] * 3 + [OpenSSL::PKey::DSA],
|
37
|
+
keys.map { |k| k.class }
|
38
|
+
assert_equal C.read_private_key(@@rsa_key_path).public_key.to_pem,
|
39
|
+
keys[1].to_pem
|
40
|
+
assert_equal C.read_private_key(@@dsa_key_path).public_key.to_pem,
|
41
|
+
keys[3].to_pem
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_key_fingerprint
|
45
|
+
keys = C.read_authorized_keys @@known_hosts_path
|
46
|
+
|
47
|
+
assert_equal 4, keys.map { |k| C.key_fingerprint k }.uniq.length
|
48
|
+
keys.each { |k| assert_equal C.key_fingerprint(k), C.key_fingerprint(k) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_load_public_keys
|
52
|
+
keyset = C.load_public_keys @@known_hosts_path
|
53
|
+
rsa_key = C.read_private_key @@rsa_key_path
|
54
|
+
dsa_key = C.read_private_key @@dsa_key_path
|
55
|
+
|
56
|
+
assert_equal rsa_key.public_key.to_pem,
|
57
|
+
keyset[C.key_fingerprint(rsa_key)].to_pem
|
58
|
+
assert_equal dsa_key.public_key.to_pem,
|
59
|
+
keyset[C.key_fingerprint(dsa_key)].to_pem
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_key_encryption
|
63
|
+
test_data = 'qwertyuiopasdfghjklzxcvbnm' * 2
|
64
|
+
rsa_key = C.read_private_key @@rsa_key_path
|
65
|
+
dsa_key = C.read_private_key @@rsa_key_path
|
66
|
+
|
67
|
+
[rsa_key, dsa_key].each do |key|
|
68
|
+
encrypted_data = C.encrypt_with_key key.public_key, test_data
|
69
|
+
decrypted_data = C.decrypt_with_key key, encrypted_data
|
70
|
+
|
71
|
+
assert_equal test_data, decrypted_data
|
72
|
+
0.upto(test_data.length - 4) do |i|
|
73
|
+
assert !encrypted_data.index(test_data[i, 4]),
|
74
|
+
'Encryption did not wipe the original pattern'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_encryption_depends_on_key
|
80
|
+
num_keys = 16
|
81
|
+
test_data = 'qwertyuiopasdfghjklzxcvbnm' * 2
|
82
|
+
keys = (0...num_keys).map { OpenSSL::PKey::RSA.generate 1024, 35 }
|
83
|
+
assert_equal num_keys, keys.map { |k| C.encrypt_with_key k, test_data }.
|
84
|
+
uniq.length
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_hasher_random_keys_are_random
|
88
|
+
num_keys = 1024
|
89
|
+
assert_equal num_keys, (0...num_keys).map { C::Hasher.random_key }.
|
90
|
+
uniq.length
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_hasher_random_keys_yield_random_results
|
94
|
+
num_keys = 1024
|
95
|
+
test_data = 'qwertyuiopasdfghjklzxcvbnm' * 2
|
96
|
+
assert_equal num_keys, (0...num_keys).map { C::Hasher.new.hash test_data }.
|
97
|
+
uniq.length
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_hasher_hashes_are_finite
|
101
|
+
num_blocks = 64
|
102
|
+
block = 'qwertyuiopasdfghjklzxcvbnm'
|
103
|
+
|
104
|
+
hasher = C::Hasher.new
|
105
|
+
hash_length = hasher.hash('').length
|
106
|
+
1.upto(num_blocks) { |n| assert_equal hash_length, hasher.hash(block * n).
|
107
|
+
length }
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_hasher_has_state
|
111
|
+
num_blocks = 1024
|
112
|
+
block = 'qwertyuiopasdfghjklzxcvbnm'
|
113
|
+
hasher = C::Hasher.new
|
114
|
+
|
115
|
+
assert_equal num_blocks, (0...num_blocks).map { hasher.hash block}.uniq.
|
116
|
+
length
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_hasher_is_reproducible
|
120
|
+
hasher = C::Hasher.new
|
121
|
+
hasher2 = C::Hasher.new hasher.key
|
122
|
+
|
123
|
+
num_blocks = 128
|
124
|
+
block = 'qwertyuiopasdfghjklzxcvbnm'
|
125
|
+
1.upto(num_blocks) { assert_equal hasher.hash(block), hasher2.hash(block) }
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'rtunnel'
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
require 'test/protocol_mocks.rb'
|
6
|
+
|
7
|
+
# Send mock for frames.
|
8
|
+
class EmSendFramesMock < EmSendMock
|
9
|
+
include RTunnel::FrameProtocol
|
10
|
+
end
|
11
|
+
|
12
|
+
# Receive mock for frames.
|
13
|
+
class EmReceiveFramesMock < EmReceiveMock
|
14
|
+
include RTunnel::FrameProtocol
|
15
|
+
object_name :frame
|
16
|
+
end
|
17
|
+
|
18
|
+
class FrameProtocolTest < Test::Unit::TestCase
|
19
|
+
def setup
|
20
|
+
super
|
21
|
+
@send_mock = EmSendFramesMock.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def teardown
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def continuous_data_test(frames)
|
29
|
+
truncated_data_test frames, []
|
30
|
+
end
|
31
|
+
|
32
|
+
def truncated_data_test(frames, sub_lengths)
|
33
|
+
frames.each { |frame| @send_mock.send_frame frame }
|
34
|
+
in_string = @send_mock.string
|
35
|
+
in_strings, i = [], 0
|
36
|
+
sub_lengths.each do |sublen|
|
37
|
+
in_strings << in_string[i, sublen]
|
38
|
+
i += sublen
|
39
|
+
end
|
40
|
+
in_strings << in_string[i..-1] if i < in_string.length
|
41
|
+
out_frames = EmReceiveFramesMock.new(@send_mock.string).replay.frames
|
42
|
+
assert_equal frames, out_frames
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_empty_frame
|
46
|
+
continuous_data_test ['']
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_byte_frame
|
50
|
+
continuous_data_test ['F']
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_string_frame
|
54
|
+
continuous_data_test [(32...128).to_a.pack('C*')]
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_multiple_frames
|
58
|
+
continuous_data_test [(32...128).to_a.pack('C*'), '', 'F', '', '1234567890']
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_truncated_border
|
62
|
+
truncated_data_test ['A', 'A'], [1, 0, 2, 0]
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_truncated_border_and_joined_data_size
|
66
|
+
truncated_data_test ['A', 'A'], [1, 1, 1, 1]
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_truncated_size
|
70
|
+
long_frame = (32...128).to_a.pack('C*') * 5
|
71
|
+
truncated_data_test [long_frame], [1]
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_truncated_size_and_data
|
75
|
+
long_frame = (32...128).to_a.pack('C*') * 5
|
76
|
+
truncated_data_test [long_frame], [1, 16]
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_badass
|
80
|
+
# TODO(not_me): this test takes 4 seconds; replace with more targeted tests
|
81
|
+
|
82
|
+
# build the badass string
|
83
|
+
s2_frame = 'qwertyuiopasdfgh' * 8 * 128 # 16384 characters, size is 3 bytes
|
84
|
+
@send_mock.send_frame s2_frame
|
85
|
+
s2_string = @send_mock.string
|
86
|
+
s2_count = 3
|
87
|
+
send_string = s2_string * s2_count
|
88
|
+
ex_frames = [s2_frame] * s2_count
|
89
|
+
|
90
|
+
# build cut points in a string
|
91
|
+
s2_points = [0, 1, 2, 3, 4, 5, 127, 128, 8190, 16381, 16382, 16383]
|
92
|
+
cut_points = []
|
93
|
+
0.upto(s2_count - 1) do |i|
|
94
|
+
cut_points += s2_points.map { |p| p + i * s2_string.length }
|
95
|
+
end
|
96
|
+
|
97
|
+
# try all combinations of cutting up the string in 4 pieces
|
98
|
+
0.upto(cut_points.length - 1) do |i|
|
99
|
+
(i + 1).upto(cut_points.length - 1) do |j|
|
100
|
+
(j + 1).upto(cut_points.length - 1) do |k|
|
101
|
+
packets = [0...cut_points[i], cut_points[i]...cut_points[j],
|
102
|
+
cut_points[j]...cut_points[k], cut_points[k]..-1].
|
103
|
+
map { |r| send_string[r] }
|
104
|
+
assert_equal ex_frames, EmReceiveFramesMock.new(packets).replay.frames
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'rtunnel'
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
class IOExtensionsTest < Test::Unit::TestCase
|
7
|
+
def setup
|
8
|
+
@test_sizes = [0, 1, 10, 127, 128, (1 << 20), (1 << 45) / 3, (1 << 62) / 5]
|
9
|
+
@test_strings = ['', 'a',
|
10
|
+
(32..127).to_a.map { |c| c.chr}.join,
|
11
|
+
(32..127).to_a.map { |c| c.chr}.join * 511]
|
12
|
+
@str = StringIO.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_varsizes_independent
|
16
|
+
@test_sizes.each do |size|
|
17
|
+
@str = StringIO.new
|
18
|
+
@str.write_varsize size
|
19
|
+
@str.rewind
|
20
|
+
assert_equal size, @str.read_varsize
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_varsizes_appended
|
25
|
+
@test_sizes.each { |size| @str.write_varsize size }
|
26
|
+
@str.rewind
|
27
|
+
@test_sizes.each do |size|
|
28
|
+
assert_equal size, @str.read_varsize
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_varsize_error
|
33
|
+
@str.write_varsize((1 << 62) / 3)
|
34
|
+
vs = @str.string
|
35
|
+
0.upto(vs.length - 1) do |truncated_len|
|
36
|
+
@str = StringIO.new
|
37
|
+
@str.write vs[0, truncated_len]
|
38
|
+
@str.rewind
|
39
|
+
assert_raise(RTunnel::TruncatedDataError) { @str.read_varsize }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_varstring_independent
|
44
|
+
@test_strings.each do |str|
|
45
|
+
@str = StringIO.new
|
46
|
+
@str.write_varstring str
|
47
|
+
@str.rewind
|
48
|
+
assert_equal str, @str.read_varstring
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_varstring_appended
|
53
|
+
@test_strings.each { |str| @str.write_varstring str }
|
54
|
+
@str.rewind
|
55
|
+
@test_strings.each do |str|
|
56
|
+
assert_equal str, @str.read_varstring
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_varstring_error
|
61
|
+
@str.write_varstring 'This will be truncated'
|
62
|
+
vs = @str.string
|
63
|
+
0.upto(vs.length - 1) do |truncated_len|
|
64
|
+
@str = StringIO.new
|
65
|
+
@str.write vs[0, truncated_len]
|
66
|
+
@str.rewind
|
67
|
+
assert_raise(RTunnel::TruncatedDataError) { @str.read_varstring }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/test/test_server.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'rtunnel'
|
2
|
+
|
3
|
+
require 'resolv'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
|
7
|
+
class ServerTest < Test::Unit::TestCase
|
8
|
+
def setup
|
9
|
+
super
|
10
|
+
@server = RTunnel::Server.new(:control_address => 'localhost')
|
11
|
+
@localhost_addr = Resolv.getaddress 'localhost'
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_options
|
15
|
+
server = RTunnel::Server
|
16
|
+
assert_equal "18.241.3.100:#{RTunnel::DEFAULT_CONTROL_PORT}",
|
17
|
+
server.extract_control_address('18.241.3.100')
|
18
|
+
assert_equal "18.241.3.100:9199",
|
19
|
+
server.extract_control_address('18.241.3.100:9199')
|
20
|
+
assert_equal "#{@localhost_addr}:#{RTunnel::DEFAULT_CONTROL_PORT}",
|
21
|
+
server.extract_control_address('localhost')
|
22
|
+
assert_equal "#{@localhost_addr}:9199",
|
23
|
+
server.extract_control_address('localhost:9199')
|
24
|
+
assert_equal "0.0.0.0:9199",
|
25
|
+
server.extract_control_address('9199')
|
26
|
+
|
27
|
+
assert_equal RTunnel::KEEP_ALIVE_INTERVAL,
|
28
|
+
server.extract_keep_alive_interval(nil)
|
29
|
+
assert_equal 29, server.extract_keep_alive_interval(29)
|
30
|
+
|
31
|
+
assert_equal 0, server.extract_lowest_listen_port(nil)
|
32
|
+
assert_equal 29, server.extract_lowest_listen_port(29)
|
33
|
+
assert_equal 65535, server.extract_highest_listen_port(nil)
|
34
|
+
assert_equal 29, server.extract_highest_listen_port(29)
|
35
|
+
|
36
|
+
assert_equal nil, server.extract_authorized_keys(nil)
|
37
|
+
keyset = server.extract_authorized_keys 'test_data/known_hosts'
|
38
|
+
assert_equal RTunnel::Crypto::KeySet, keyset.class
|
39
|
+
assert keyset.length != 0, 'No key read from the known_hosts file'
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_validate_remote_listen
|
43
|
+
# remove Connection's new override, don't want event_machine here
|
44
|
+
EventMachine::Connection.class_eval do
|
45
|
+
class << self
|
46
|
+
alias_method :backup_new, :new
|
47
|
+
remove_method :new
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
server = RTunnel::Server.new(:control_address => 'localhost',
|
52
|
+
:lowest_listen_port => 91,
|
53
|
+
:highest_listen_port => 105)
|
54
|
+
connection = RTunnel::Server::ControlConnection.new server
|
55
|
+
|
56
|
+
assert !connection.validate_remote_listen('localhost', 80)
|
57
|
+
assert connection.validate_remote_listen('localhost', 91)
|
58
|
+
assert connection.validate_remote_listen('localhost', 105)
|
59
|
+
assert !connection.validate_remote_listen('localhost', 90)
|
60
|
+
assert !connection.validate_remote_listen('localhost', 106)
|
61
|
+
|
62
|
+
# re-instate Connection's new override
|
63
|
+
EventMachine::Connection.class_eval do
|
64
|
+
class << self
|
65
|
+
alias_method :new, :backup_new
|
66
|
+
remove_method :backup_new
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rtunnel'
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
class SocketFactoryTest < Test::Unit::TestCase
|
6
|
+
SF = RTunnel::SocketFactory
|
7
|
+
|
8
|
+
def setup
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
def teardown
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_host_from_address
|
17
|
+
assert_equal nil, SF.host_from_address(nil)
|
18
|
+
assert_equal '127.0.0.1', SF.host_from_address('127.0.0.1')
|
19
|
+
assert_equal '127.0.0.1', SF.host_from_address('127.0.0.1:1234')
|
20
|
+
assert_equal 'fe80::1%lo0', SF.host_from_address('fe80::1%lo0')
|
21
|
+
assert_equal 'fe80::1%lo0', SF.host_from_address('fe80::1%lo0:19020')
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_port_from_address
|
25
|
+
assert_equal nil, SF.port_from_address(nil)
|
26
|
+
assert_equal nil, SF.port_from_address('127.0.0.1')
|
27
|
+
assert_equal 1234, SF.port_from_address('127.0.0.1:1234')
|
28
|
+
assert_equal nil, SF.port_from_address('fe80::1%lo0')
|
29
|
+
assert_equal 19020, SF.port_from_address('fe80::1%lo0:19020')
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_inbound
|
33
|
+
assert SF.inbound?(:in_port => 1)
|
34
|
+
assert SF.inbound?(:in_host => '1')
|
35
|
+
assert SF.inbound?(:in_addr => '1')
|
36
|
+
assert SF.inbound?(:inbound => true)
|
37
|
+
assert !SF.inbound?(:out_port => 1)
|
38
|
+
assert !SF.inbound?(:out_host => '1')
|
39
|
+
assert !SF.inbound?(:out_addr => '1')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
data/test/test_tunnel.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'rtunnel'
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'eventmachine'
|
7
|
+
|
8
|
+
require 'test/scenario_connection.rb'
|
9
|
+
|
10
|
+
# Integration tests ensuring that we can start a tunnel.
|
11
|
+
class TunnelTest < Test::Unit::TestCase
|
12
|
+
def setup
|
13
|
+
super
|
14
|
+
|
15
|
+
@connection_time = 0.001
|
16
|
+
@secure_connection_time = 0.5
|
17
|
+
@log_level = 'debug'
|
18
|
+
@local_host = '127.0.0.1'
|
19
|
+
@listen_port = 21335
|
20
|
+
@tunnel_port = 21336
|
21
|
+
@control_port = 21337
|
22
|
+
@key_file = 'test_data/ssh_host_rsa_key'
|
23
|
+
@hosts_file = 'test_data/known_hosts'
|
24
|
+
|
25
|
+
@tunnel_server = new_server
|
26
|
+
@tunnel_client = new_client
|
27
|
+
end
|
28
|
+
|
29
|
+
def new_server(extra_options = {})
|
30
|
+
RTunnel::Server.new({
|
31
|
+
:control_address => "#{@local_host}:#{@control_port}",
|
32
|
+
:log_level => @log_level
|
33
|
+
}.merge(extra_options))
|
34
|
+
end
|
35
|
+
|
36
|
+
def new_client(extra_options = {})
|
37
|
+
RTunnel::Client.new({
|
38
|
+
:control_address => "#{@local_host}:#{@control_port}",
|
39
|
+
:remote_listen_address => "#{@local_host}:#{@listen_port}",
|
40
|
+
:tunnel_to_address => "#{@local_host}:#{@tunnel_port}",
|
41
|
+
:log_level => @log_level
|
42
|
+
}.merge(extra_options))
|
43
|
+
end
|
44
|
+
|
45
|
+
def tunnel_test(connection_time = nil)
|
46
|
+
@stop_proc = proc do
|
47
|
+
@tunnel_client.stop
|
48
|
+
@tunnel_server.stop
|
49
|
+
@stop_proc = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
EventMachine::run do
|
53
|
+
@tunnel_server.start
|
54
|
+
@tunnel_client.start
|
55
|
+
|
56
|
+
if connection_time
|
57
|
+
EventMachine.add_timer(connection_time) { yield }
|
58
|
+
else
|
59
|
+
EventMachine.next_tick { yield }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_client_driven_tunnel
|
65
|
+
tunnel_test do
|
66
|
+
@tunnel_server.on_remote_listen do
|
67
|
+
EventMachine::start_server @local_host, @tunnel_port,
|
68
|
+
ScenarioConnection, self, [[:recv, 'Hello'], [:send, 'World'],
|
69
|
+
[:unbind], [:stop, @stop_proc]]
|
70
|
+
|
71
|
+
EventMachine::connect @local_host, @listen_port,
|
72
|
+
ScenarioConnection, self, [[:send, 'Hello'], [:recv, 'World'],
|
73
|
+
[:close]]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_server_driven_tunnel
|
79
|
+
tunnel_test do
|
80
|
+
@tunnel_server.on_remote_listen do
|
81
|
+
EventMachine::start_server @local_host, @tunnel_port,
|
82
|
+
ScenarioConnection, self, [[:send, 'Hello'], [:recv, 'World'],
|
83
|
+
[:close]]
|
84
|
+
|
85
|
+
EventMachine::connect @local_host, @listen_port,
|
86
|
+
ScenarioConnection, self, [[:recv, 'Hello'], [:send, 'World'],
|
87
|
+
[:unbind], [:stop]]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_two_tunnels
|
93
|
+
start_second = lambda do
|
94
|
+
@tunnel_client.stop
|
95
|
+
@tunnel_client.start
|
96
|
+
@tunnel_server.on_remote_listen do
|
97
|
+
EventMachine::connect @local_host, @listen_port,
|
98
|
+
ScenarioConnection, self, [[:send, 'Hello'], [:recv, 'World'],
|
99
|
+
[:unbind], [:stop, @stop_proc]]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
tunnel_test do
|
104
|
+
@tunnel_server.on_remote_listen do
|
105
|
+
EventMachine::start_server @local_host, @tunnel_port,
|
106
|
+
ScenarioConnection, self, [[:recv, 'Hello'], [:send, 'World'],
|
107
|
+
[:close], [:recv, 'Hello'], [:send, 'World'], [:close]]
|
108
|
+
|
109
|
+
EventMachine::connect @local_host, @listen_port,
|
110
|
+
ScenarioConnection, self, [[:send, 'Hello'], [:recv, 'World'],
|
111
|
+
[:proc, start_second], [:unbind]]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_secure_tunnel
|
117
|
+
@tunnel_server = new_server :authorized_keys => @hosts_file
|
118
|
+
@tunnel_client = new_client :private_key => @key_file
|
119
|
+
tunnel_test do
|
120
|
+
@tunnel_server.on_remote_listen do
|
121
|
+
EventMachine::start_server @local_host, @tunnel_port,
|
122
|
+
ScenarioConnection, self, [[:recv, 'Hello'], [:send, 'World'],
|
123
|
+
[:unbind], [:stop, @stop_proc]]
|
124
|
+
|
125
|
+
EventMachine::connect @local_host, @listen_port,
|
126
|
+
ScenarioConnection, self, [[:send, 'Hello'], [:recv, 'World'],
|
127
|
+
[:close]]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_secure_async_tunnel
|
133
|
+
@tunnel_server = new_server :authorized_keys => @hosts_file
|
134
|
+
@tunnel_client = new_client :private_key => @key_file
|
135
|
+
tunnel_test do
|
136
|
+
@tunnel_server.on_remote_listen do
|
137
|
+
EventMachine::start_server @local_host, @tunnel_port,
|
138
|
+
ScenarioConnection, self, [[:send, 'World'], [:recv, 'Hello'],
|
139
|
+
[:unbind], [:stop, @stop_proc]]
|
140
|
+
|
141
|
+
EventMachine::connect @local_host, @listen_port,
|
142
|
+
ScenarioConnection, self, [[:send, 'Hello'], [:recv, 'World'],
|
143
|
+
[:close]]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_bad_listen_address
|
149
|
+
@tunnel_server = new_server
|
150
|
+
@tunnel_client = new_client :remote_listen_address =>
|
151
|
+
"18.70.0.160:#{@listen_port}"
|
152
|
+
|
153
|
+
tunnel_test do
|
154
|
+
EventMachine::start_server @local_host, @tunnel_port,
|
155
|
+
ScenarioConnection, self, []
|
156
|
+
|
157
|
+
EventMachine::connect @local_host, @listen_port,
|
158
|
+
ScenarioConnection, self, [[:unbind], [:stop, @stop_proc]]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# TODO: fix this
|
163
|
+
def test_secure_server_rejects_unsecure_client
|
164
|
+
@tunnel_server = new_server :authorized_keys => @hosts_file
|
165
|
+
tunnel_test(@secure_connection_time) do
|
166
|
+
EventMachine::start_server @local_host, @tunnel_port,
|
167
|
+
ScenarioConnection, self, []
|
168
|
+
|
169
|
+
EventMachine::connect @local_host, @listen_port,
|
170
|
+
ScenarioConnection, self, [[:unbind], [:stop, @stop_proc]]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# hrmf... this is testing security and its not 100% reliable... but I can't think of a better way
|
175
|
+
def test_secure_server_rejects_unauthorized_key
|
176
|
+
@tunnel_server = new_server :authorized_keys => @hosts_file
|
177
|
+
@tunnel_client = new_client :private_key => 'test_data/random_rsa_key'
|
178
|
+
tunnel_test(@secure_connection_time) do
|
179
|
+
EventMachine::start_server @local_host, @tunnel_port,
|
180
|
+
ScenarioConnection, self, []
|
181
|
+
|
182
|
+
EventMachine::connect @local_host, @listen_port,
|
183
|
+
ScenarioConnection, self, [[:unbind], [:stop, @stop_proc]]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,4 @@
|
|
1
|
+
no-port-forwarding,no-agent-forwarding,command="/usr/NX/bin/nxnode" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== stub@github.org
|
2
|
+
no-port-forwarding,no-agent-forwarding,command="/usr/NX/bin/nxnode" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq7VciLo2wB+/2ThHXejC/kgkN205zlyS5cvN40Mqi3qvRVS75X1RawDoLot8eJ9KCYqZFr2Dr73d/EQNltN7dKJoPIj1IKxoraWkyNFbhhzpYuOltg9oO5UBSTNLupqla7zcdj4IwCCkBYk4+TS0dwmi20buOJ0FPY5PgbzmMnUiV9ipBqeJSdZB+TePH1gqlt7AP/6ti/0gxb2K7F69dZl/BSxMEzRCfBlTFC3f/4n8IdCuSJvNxxY+TtRnLL5CKUhj9QaIBan6JCkdRvVOBY7wmsNT8nGDzfDFSDD3KKn93g4LRkyMeaYlSDLxKy8PnNhjWgBNH1YNYyicsGfBKQ== costan@obsidian.local
|
3
|
+
no-port-forwarding,no-agent-forwarding,command="/usr/NX/bin/nxnode" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3tPhdPUFGAYyrT1quRSOevLbAdKAJ6Ovwqw0m99R0QkqUwMUh09pgedWZeij7HtAHtoWPrNFev8FrwcwnL14NgA/gwNnXxbqd4twC1HyFShUf7POry8bz3Qk+84STHeMY8++hhn8LgNyfuVQswHoW661aqieLM6pF8q8xIUtkXA7daNpJAL4nTN1TxgUoCDpCa0EbUPkGpwFPNtGPuokRXNOCR9g8T6LmPQbzGUTc4CzFfQ9rrHaimqkEmRWJbBOaik1bdQNqOh6MUDDuUSpkJV7fwu3bl4fF5/1kw2HCREEjJmESOYnZhOS+MCp1qAUcuXqpMqXD8ATNsuXqrIhTQ== stub@rubyforge.org
|
4
|
+
no-port-forwarding,no-agent-forwarding,command="/usr/NX/bin/nxnode" ssh-dss AAAAB3NzaC1kc3MAAACBAPdWp4HhWUoNawosyEBvrhPSbjhCJiKnVWiUS6bo0BCGzTHhugrkv2HgtlKhWo8nqTw5E4YxzFVyZ0YQt4m7NYDLTZVjrqbIpL/3F5qNXco127O/im0cG27AKC8Jf7knmUTjd8EBhtK65tNDmxPtzKQtemlNTVPX1VccOn6eLtn1AAAAFQDtQC21TJrf/p5WNsU9UIJzO9/hIwAAAIEAgwTrIfleQMEAK9N3xeMVZpAGfSAoX6owLtk3z+iQ3rM9FRvM/CgezOgezLowJghkw/bcQDmMuudBUuijrM3zZWdr6eqoNbFTR/KKiUx3cYf0LAHNPbXfVz+P7BXqjcEj75qnwuHQMp7vNMg+dmV40UA2TiC5/8QlaZVwkOSPN6gAAACAPMGJEFkoR0ayfkd0S/tnY9ilO17T6rdoDuF25ATtNUd6Zji6tslxBkQFWtTeinO3rGkqJRPndq0wp3E33AOHhJE/FOIlWl4Tf6aeU95Y4enYujKQDiImSTXmdiw5wq/LFdc3a2waOUvuI+647wxgHhqTmD7xI2biGZLYN9Oasy0= costan@obsidian.local
|
@@ -0,0 +1,4 @@
|
|
1
|
+
github.com,65.74.177.129 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
|
2
|
+
obsidian.local ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq7VciLo2wB+/2ThHXejC/kgkN205zlyS5cvN40Mqi3qvRVS75X1RawDoLot8eJ9KCYqZFr2Dr73d/EQNltN7dKJoPIj1IKxoraWkyNFbhhzpYuOltg9oO5UBSTNLupqla7zcdj4IwCCkBYk4+TS0dwmi20buOJ0FPY5PgbzmMnUiV9ipBqeJSdZB+TePH1gqlt7AP/6ti/0gxb2K7F69dZl/BSxMEzRCfBlTFC3f/4n8IdCuSJvNxxY+TtRnLL5CKUhj9QaIBan6JCkdRvVOBY7wmsNT8nGDzfDFSDD3KKn93g4LRkyMeaYlSDLxKy8PnNhjWgBNH1YNYyicsGfBKQ==
|
3
|
+
rubyforge.org,205.234.109.19 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3tPhdPUFGAYyrT1quRSOevLbAdKAJ6Ovwqw0m99R0QkqUwMUh09pgedWZeij7HtAHtoWPrNFev8FrwcwnL14NgA/gwNnXxbqd4twC1HyFShUf7POry8bz3Qk+84STHeMY8++hhn8LgNyfuVQswHoW661aqieLM6pF8q8xIUtkXA7daNpJAL4nTN1TxgUoCDpCa0EbUPkGpwFPNtGPuokRXNOCR9g8T6LmPQbzGUTc4CzFfQ9rrHaimqkEmRWJbBOaik1bdQNqOh6MUDDuUSpkJV7fwu3bl4fF5/1kw2HCREEjJmESOYnZhOS+MCp1qAUcuXqpMqXD8ATNsuXqrIhTQ==
|
4
|
+
obsidian.local ssh-dss AAAAB3NzaC1kc3MAAACBAPdWp4HhWUoNawosyEBvrhPSbjhCJiKnVWiUS6bo0BCGzTHhugrkv2HgtlKhWo8nqTw5E4YxzFVyZ0YQt4m7NYDLTZVjrqbIpL/3F5qNXco127O/im0cG27AKC8Jf7knmUTjd8EBhtK65tNDmxPtzKQtemlNTVPX1VccOn6eLtn1AAAAFQDtQC21TJrf/p5WNsU9UIJzO9/hIwAAAIEAgwTrIfleQMEAK9N3xeMVZpAGfSAoX6owLtk3z+iQ3rM9FRvM/CgezOgezLowJghkw/bcQDmMuudBUuijrM3zZWdr6eqoNbFTR/KKiUx3cYf0LAHNPbXfVz+P7BXqjcEj75qnwuHQMp7vNMg+dmV40UA2TiC5/8QlaZVwkOSPN6gAAACAPMGJEFkoR0ayfkd0S/tnY9ilO17T6rdoDuF25ATtNUd6Zji6tslxBkQFWtTeinO3rGkqJRPndq0wp3E33AOHhJE/FOIlWl4Tf6aeU95Y4enYujKQDiImSTXmdiw5wq/LFdc3a2waOUvuI+647wxgHhqTmD7xI2biGZLYN9Oasy0=
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEowIBAAKCAQEAoSWzcqViB/JUz6Zd80c3km114/OWfxbGo/xeZqJm4s9+kR8o
|
3
|
+
xtFjfbj9oDiuckSkAPNQW5VUWCeeNdpNjLmBe4Jm40R+FfibLOR5lw5I/7jVozBZ
|
4
|
+
gQKuF395ODgbOwGXMsmRc1xViDYsOzo4A/f+5WhbpG20AtBz6+mg3yDLWXDffhat
|
5
|
+
ecRf7PvWvlkj0QtL6538a7OE4MY75pCiTLWWbHJnBoQgNmYFZZdBaarHsenlKSQ9
|
6
|
+
P6xL6ENyDOom3/Xk9bvqHQNNvgHyN4e4PRAtZUqsjyoRMm8sfDIJhK+cvpvqqWWm
|
7
|
+
QK1ef1Fb1yXnmjkmUfx15UXxGgB79v3/vYWmJwIBIwKCAQBugECJIPLDntsSC64j
|
8
|
+
KYUxNR2jn7enmpbW2PBGYLuUNoKsp6buu3dsJxRQm+VVq2klOSEo220JP7zxukPO
|
9
|
+
Ng+lJjfsTDkzoyiTz95Y89pI88WxusEIAdZ2g0vOx2MhJamB6U3LcoPLHdUv7Wg9
|
10
|
+
PFcDtUYnm66i2BT5ikJtHc1EpBTte71pZVRQsHYEsgcnNfsIc9hpXZacQ8CxF3Cc
|
11
|
+
gM35GRSC7z/UVouyMbhQCjeKrlR/aZIE1whpnWpdOa+AeYtdndwxn6gNeplV8lA1
|
12
|
+
8J0mpQlsy9i8cl2Nx/euN+dAW87zI3kwm1znj4pHF5e12v+SziZqnjuI4PhxNJO1
|
13
|
+
CSRLAoGBAM9WfBRkVuWv+RdVm5VBpSwvBDytJpqcSbTxkypmV7sN6I4A2rBcNws9
|
14
|
+
IaJ7Bf+ZxG4R8THXg5jCt1yfjIwMYrQ1Jg7aVALwF+JzeZgGTWFYJwCcC5VXCvy7
|
15
|
+
gsJhnboBPVuM3Vxhz98nHKUngbLDbV8Sl+DVp1NDJ+tDJqY6tVZNAoGBAMb38M+G
|
16
|
+
ZF9HVr33Oe7LjLxQRiQk5G6gmbidZfOszK797/BxYd1vMMPEl8d56zcYkeaIDmvL
|
17
|
+
xHGLhoT6vtA4N0WCuk96USBk7JrRYhXoGTw0yFOJyopzPWdR66iaVbF/g1aknzki
|
18
|
+
pZfb2LeyocztlxKJTUe0IocEkHu5aqzFeFBDAoGBAI4srNrIdhnXwL+LKNtgNr82
|
19
|
+
LsqFXEzC8LaXBdPuaAs8vLklKD5rHm4bSkOHjxWrRN3DKQw8Andg8sMrk5M7sWWg
|
20
|
+
yaPI6SaWAb/aJ34wNQ+M54tjsCvj6kbm+pPrKlOpFCGFKhN2RWXRrT4MdjFwEHu0
|
21
|
+
+m5JXMthP/HHpXlM1B3rAoGBAMFIoMmYfsL01+u89nLxnqhN+v6KPP3AlVRBITXb
|
22
|
+
D/pzBldJkkTSaeK++de4Q5SbhmrqkaqLbl+sHEaqcDf8GG9pDMI8Ts8C9HkjLBVH
|
23
|
+
3f/4wppLVwomze9W5OztslSnwWocQucLts4Iw+WmNscSsANgzrqu/PgwUddGZ6CF
|
24
|
+
UE33AoGBAJ3VCQx0hwDSIQuuakjUb5ouPup+8B1A7wspGirkQNlTZ1F2q4Zug/Fp
|
25
|
+
dovyzPZuV3/0dVDNbQzz21gRuiE2I/l9uF4Bz2LqY1Jg1R9WuQH5sdRG0ncf6Ikn
|
26
|
+
1LRLgE2tCGGfhLUi2vDMwORx1o46p4hboF8ZaOBK1+WIwNnfRGNl
|
27
|
+
-----END RSA PRIVATE KEY-----
|
@@ -0,0 +1,12 @@
|
|
1
|
+
-----BEGIN DSA PRIVATE KEY-----
|
2
|
+
MIIBvAIBAAKBgQD3VqeB4VlKDWsKLMhAb64T0m44QiYip1VolEum6NAQhs0x4boK
|
3
|
+
5L9h4LZSoVqPJ6k8OROGMcxVcmdGELeJuzWAy02VY66myKS/9xeajV3KNduzv4pt
|
4
|
+
HBtuwCgvCX+5J5lE43fBAYbSuubTQ5sT7cykLXppTU1T19VXHDp+ni7Z9QIVAO1A
|
5
|
+
LbVMmt/+nlY2xT1QgnM73+EjAoGBAIME6yH5XkDBACvTd8XjFWaQBn0gKF+qMC7Z
|
6
|
+
N8/okN6zPRUbzPwoHszoHsy6MCYIZMP23EA5jLrnQVLoo6zN82Vna+nqqDWxU0fy
|
7
|
+
iolMd3GH9CwBzT2131c/j+wV6o3BI++ap8Lh0DKe7zTIPnZleNFANk4guf/EJWmV
|
8
|
+
cJDkjzeoAoGAPMGJEFkoR0ayfkd0S/tnY9ilO17T6rdoDuF25ATtNUd6Zji6tslx
|
9
|
+
BkQFWtTeinO3rGkqJRPndq0wp3E33AOHhJE/FOIlWl4Tf6aeU95Y4enYujKQDiIm
|
10
|
+
STXmdiw5wq/LFdc3a2waOUvuI+647wxgHhqTmD7xI2biGZLYN9Oasy0CFQDL/9Z1
|
11
|
+
fSLn88m1sUeWAZ4Ys2IIxw==
|
12
|
+
-----END DSA PRIVATE KEY-----
|