coderrr-rtunnel 0.3.9 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +13 -0
- data/LICENSE +21 -0
- data/Manifest +48 -0
- data/README.markdown +40 -15
- data/Rakefile +31 -4
- data/bin/rtunnel_client +2 -1
- data/bin/rtunnel_server +2 -1
- data/lib/rtunnel.rb +20 -0
- 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/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 +67 -27
- data/ab_test.rb +0 -23
- data/lib/client.rb +0 -150
- data/lib/cmds.rb +0 -166
- data/lib/core.rb +0 -58
- data/lib/rtunnel_client_cmd.rb +0 -23
- data/lib/rtunnel_server_cmd.rb +0 -18
- data/lib/server.rb +0 -197
- data/rtunnel.gemspec +0 -18
- data/rtunnel_client.rb +0 -3
- data/rtunnel_server.rb +0 -3
- data/stress_test.rb +0 -68
@@ -0,0 +1,62 @@
|
|
1
|
+
# The plumbing for processing RTunnel commands.
|
2
|
+
module RTunnel::CommandProcessor
|
3
|
+
# Called by CommandProtocol to process a command.
|
4
|
+
def receive_command(command)
|
5
|
+
case @last_command = command
|
6
|
+
when RTunnel::CloseConnectionCommand
|
7
|
+
process_close_connection command.connection_id
|
8
|
+
when RTunnel::CreateConnectionCommand
|
9
|
+
process_create_connection command.connection_id
|
10
|
+
when RTunnel::GenerateSessionKeyCommand
|
11
|
+
process_generate_session_key command.public_key_fp
|
12
|
+
when RTunnel::KeepAliveCommand
|
13
|
+
process_keep_alive
|
14
|
+
when RTunnel::RemoteListenCommand
|
15
|
+
process_remote_listen command.address
|
16
|
+
when RTunnel::SendDataCommand
|
17
|
+
process_send_data command.connection_id, command.data
|
18
|
+
when RTunnel::SetSessionKeyCommand
|
19
|
+
process_set_session_key command.encrypted_keys
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Override to process CloseConnectionCommand. Do NOT call super.
|
24
|
+
def process_close_connection(connection_id)
|
25
|
+
unexpected_command @last_command
|
26
|
+
end
|
27
|
+
|
28
|
+
# Override to process CreateConnectionCommand. Do NOT call super.
|
29
|
+
def process_create_connection(connection_id)
|
30
|
+
unexpected_command @last_command
|
31
|
+
end
|
32
|
+
|
33
|
+
# Override to process GenerateSessionKeyCommand. Do NOT call super.
|
34
|
+
def process_generate_session_key(public_key_fp)
|
35
|
+
unexpected_command @last_command
|
36
|
+
end
|
37
|
+
|
38
|
+
# Override to process KeepAliveCommand. Do NOT call super.
|
39
|
+
def process_keep_alive
|
40
|
+
unexpected_command @last_command
|
41
|
+
end
|
42
|
+
|
43
|
+
# Override to process RemoteListenCommand. Do NOT call super.
|
44
|
+
def process_remote_listen(address)
|
45
|
+
unexpected_command @last_command
|
46
|
+
end
|
47
|
+
|
48
|
+
# Override to process SendDataCommand. Do NOT call super.
|
49
|
+
def process_send_data(connection_id, data)
|
50
|
+
unexpected_command @last_command
|
51
|
+
end
|
52
|
+
|
53
|
+
# Override to process SetSessionKeyCommand. Do NOT call super.
|
54
|
+
def process_set_session_key(encrypted_keys)
|
55
|
+
unexpected_command @last_command
|
56
|
+
end
|
57
|
+
|
58
|
+
# Override to handle commands that haven't been overridden.
|
59
|
+
def unexpected_command(command)
|
60
|
+
W "Unexpected command: #{command.inspect}"
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module RTunnel::CommandProtocol
|
2
|
+
include RTunnel::FrameProtocol
|
3
|
+
|
4
|
+
# Sends an encoded RTunnel command as a frame.
|
5
|
+
def send_command(command)
|
6
|
+
command_str = command.to_encoded_str
|
7
|
+
if @out_command_hasher
|
8
|
+
send_frame command_str + @out_command_hasher.hash(command_str)
|
9
|
+
else
|
10
|
+
send_frame command_str
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Decodes a frame into an RTunnel command.
|
15
|
+
def receive_frame(frame)
|
16
|
+
ioframe = StringIO.new frame
|
17
|
+
begin
|
18
|
+
command = RTunnel::Command.decode ioframe
|
19
|
+
rescue Exception => e
|
20
|
+
receive_bad_frame frame, e
|
21
|
+
return
|
22
|
+
end
|
23
|
+
if @in_command_hasher
|
24
|
+
signature = ioframe.read
|
25
|
+
if signature != @in_command_hasher.hash(frame[0...(-signature.length)])
|
26
|
+
receive_bad_frame frame, :bad_signature
|
27
|
+
return
|
28
|
+
end
|
29
|
+
end
|
30
|
+
receive_command command
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sets a cryptographic hasher that will be used to sign outgoing commands.
|
34
|
+
# Once a hasher is set, all outgoing frames will be signed.
|
35
|
+
def outgoing_command_hasher=(hasher)
|
36
|
+
@out_command_hasher = hasher
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sets a cryptographic hasher that will be used to verify incoming commands.
|
40
|
+
# Once a hasher is set, all incoming frames without a matching signature will
|
41
|
+
# be ignored.
|
42
|
+
def incoming_command_hasher=(hasher)
|
43
|
+
@in_command_hasher = hasher
|
44
|
+
end
|
45
|
+
|
46
|
+
# Override to handle frames with corrupted or absent signatures.
|
47
|
+
def receive_bad_frame(frame, exception)
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
class RTunnel::Command
|
4
|
+
# Associates command codes with the classes implementing them.
|
5
|
+
class Registry
|
6
|
+
def initialize
|
7
|
+
@classes = {}
|
8
|
+
@codes = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def register(klass, command_code)
|
12
|
+
if @codes.has_key? command_code
|
13
|
+
raise "Command code #{command_code} already used for #{@codes[command_code].name}"
|
14
|
+
end
|
15
|
+
|
16
|
+
@codes[klass] = command_code
|
17
|
+
@classes[command_code] = klass
|
18
|
+
end
|
19
|
+
|
20
|
+
def class_for(command_code)
|
21
|
+
@classes[command_code]
|
22
|
+
end
|
23
|
+
|
24
|
+
def code_for(klass)
|
25
|
+
@codes[klass]
|
26
|
+
end
|
27
|
+
|
28
|
+
def codes_and_classes
|
29
|
+
ret_val = []
|
30
|
+
@codes.each { |klass, code| ret_val << [code, klass.name] }
|
31
|
+
ret_val.sort!
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
@@registry = Registry.new
|
36
|
+
def self.registry
|
37
|
+
@@registry
|
38
|
+
end
|
39
|
+
|
40
|
+
# subclasses must call this to register and declare their command code
|
41
|
+
def self.command_code(code)
|
42
|
+
registry.register self, code
|
43
|
+
end
|
44
|
+
|
45
|
+
# subclasses should override this (and add to the result)
|
46
|
+
# to provide a debug string
|
47
|
+
def to_s
|
48
|
+
self.class.name
|
49
|
+
end
|
50
|
+
|
51
|
+
# subclasses should override this and call super
|
52
|
+
# before performing their own initialization
|
53
|
+
def initialize_from_io(io)
|
54
|
+
return self
|
55
|
+
end
|
56
|
+
|
57
|
+
# Encode this command to a IO / IOString.
|
58
|
+
def encode(io)
|
59
|
+
io.write RTunnel::Command.registry.code_for(self.class)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Produce a string with an encoding of this command.
|
63
|
+
def to_encoded_str
|
64
|
+
string_io = StringIO.new
|
65
|
+
self.encode string_io
|
66
|
+
string_io.string
|
67
|
+
end
|
68
|
+
|
69
|
+
# Decode a Command instance from a IO / IOString.
|
70
|
+
def self.decode(io)
|
71
|
+
return nil unless code = io.getc
|
72
|
+
klass = registry.class_for code.chr
|
73
|
+
return nil unless klass
|
74
|
+
|
75
|
+
command = klass.new
|
76
|
+
command.initialize_from_io io
|
77
|
+
end
|
78
|
+
|
79
|
+
# Printable string containing all the codes and their classes.
|
80
|
+
def self.printable_codes
|
81
|
+
printable = ''
|
82
|
+
registry.codes_and_classes.each do |code_and_class|
|
83
|
+
printable << "#{code_and_class.first}: #{code_and_class.last}\n"
|
84
|
+
end
|
85
|
+
return printable
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class RTunnel::ConnectionCommand < RTunnel::Command
|
90
|
+
attr_reader :connection_id
|
91
|
+
|
92
|
+
def initialize(connection_id = nil)
|
93
|
+
super()
|
94
|
+
@connection_id = connection_id
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_s
|
98
|
+
super + "/id=#{connection_id}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize_from_io(io)
|
102
|
+
super
|
103
|
+
@connection_id = io.read_varstring
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
def encode(io)
|
108
|
+
super
|
109
|
+
io.write_varstring @connection_id
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class RTunnel::CreateConnectionCommand < RTunnel::ConnectionCommand
|
114
|
+
command_code 'C'
|
115
|
+
end
|
116
|
+
|
117
|
+
class RTunnel::CloseConnectionCommand < RTunnel::ConnectionCommand
|
118
|
+
command_code 'X'
|
119
|
+
end
|
120
|
+
|
121
|
+
class RTunnel::SendDataCommand < RTunnel::ConnectionCommand
|
122
|
+
command_code 'D'
|
123
|
+
|
124
|
+
attr_reader :data
|
125
|
+
|
126
|
+
def initialize(connection_id = nil, data = nil)
|
127
|
+
super(connection_id)
|
128
|
+
@data = data
|
129
|
+
end
|
130
|
+
|
131
|
+
def initialize_from_io(io)
|
132
|
+
super
|
133
|
+
@data = io.read_varstring
|
134
|
+
self
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_s
|
138
|
+
super + "/data=#{data}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def encode(io)
|
142
|
+
super
|
143
|
+
io.write_varstring @data
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class RTunnel::RemoteListenCommand < RTunnel::Command
|
148
|
+
command_code 'L'
|
149
|
+
|
150
|
+
attr_reader :address
|
151
|
+
|
152
|
+
def initialize(address = nil)
|
153
|
+
super()
|
154
|
+
@address = address
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_s
|
158
|
+
super + "/address=#{address}"
|
159
|
+
end
|
160
|
+
|
161
|
+
def initialize_from_io(io)
|
162
|
+
super
|
163
|
+
@address = io.read_varstring
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
def encode(io)
|
168
|
+
super
|
169
|
+
io.write_varstring address
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
class RTunnel::KeepAliveCommand < RTunnel::Command
|
174
|
+
command_code 'A'
|
175
|
+
|
176
|
+
def initialize_from_io(io)
|
177
|
+
super
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class RTunnel::GenerateSessionKeyCommand < RTunnel::Command
|
182
|
+
command_code 'S'
|
183
|
+
|
184
|
+
attr_reader :public_key_fp
|
185
|
+
|
186
|
+
def initialize(public_key_fp = nil)
|
187
|
+
super()
|
188
|
+
@public_key_fp = public_key_fp
|
189
|
+
end
|
190
|
+
|
191
|
+
def to_s
|
192
|
+
super + "/pubkey_fp=#{@public_key_fp.inspect}"
|
193
|
+
end
|
194
|
+
|
195
|
+
def initialize_from_io(io)
|
196
|
+
super
|
197
|
+
@public_key_fp = io.read_varstring
|
198
|
+
self
|
199
|
+
end
|
200
|
+
|
201
|
+
def encode(io)
|
202
|
+
super
|
203
|
+
io.write_varstring @public_key_fp
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
class RTunnel::SetSessionKeyCommand < RTunnel::Command
|
208
|
+
command_code 'K'
|
209
|
+
|
210
|
+
attr_reader :encrypted_keys
|
211
|
+
|
212
|
+
def initialize(encrypted_keys = nil)
|
213
|
+
super()
|
214
|
+
@encrypted_keys = encrypted_keys
|
215
|
+
end
|
216
|
+
|
217
|
+
def to_s
|
218
|
+
super + "/enc_keys=#{@encrypted_key.inspect}"
|
219
|
+
end
|
220
|
+
|
221
|
+
def initialize_from_io(io)
|
222
|
+
super
|
223
|
+
@encrypted_keys = io.read_varstring
|
224
|
+
self
|
225
|
+
end
|
226
|
+
|
227
|
+
def encode(io)
|
228
|
+
super
|
229
|
+
io.write_varstring @encrypted_keys
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# TODO(not_me): this file (and its tests) cry for a DSL
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
# Unique ID generation functionality.
|
5
|
+
module RTunnel::ConnectionId
|
6
|
+
def self.new_cipher
|
7
|
+
cipher = OpenSSL::Cipher::Cipher.new 'aes-128-ecb'
|
8
|
+
cipher.encrypt
|
9
|
+
cipher.key, cipher.iv = cipher.random_key, cipher.random_iv
|
10
|
+
cipher
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.new_counter
|
14
|
+
'0' * 16
|
15
|
+
end
|
16
|
+
|
17
|
+
def new_connection_id
|
18
|
+
@session_id_cipher ||= RTunnel::ConnectionId.new_cipher
|
19
|
+
@session_id_counter ||= RTunnel::ConnectionId.new_counter
|
20
|
+
connection_id = @session_id_cipher.update @session_id_counter
|
21
|
+
@session_id_counter.succ!
|
22
|
+
Base64.encode64(connection_id).strip
|
23
|
+
end
|
24
|
+
end
|
data/lib/rtunnel/core.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module RTunnel
|
4
|
+
DEFAULT_CONTROL_PORT = 19050
|
5
|
+
TUNNEL_TIMEOUT = 10
|
6
|
+
KEEP_ALIVE_INTERVAL = 2
|
7
|
+
|
8
|
+
class AbortProgramException < Exception
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module RTunnel::Logging
|
14
|
+
def init_log(options = {})
|
15
|
+
# TODO(costan): parse logging options
|
16
|
+
if options[:to]
|
17
|
+
@log = options[:to].instance_variable_get(:@log).dup
|
18
|
+
else
|
19
|
+
@log = Logger.new(STDERR)
|
20
|
+
@log.level = Logger::ERROR
|
21
|
+
end
|
22
|
+
if options[:level]
|
23
|
+
@log.level = Logger::const_get(options[:level].upcase.to_sym)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def D(message)
|
28
|
+
@log.debug message
|
29
|
+
end
|
30
|
+
|
31
|
+
def W(message)
|
32
|
+
@log.warn message
|
33
|
+
end
|
34
|
+
|
35
|
+
def I(message)
|
36
|
+
@log.info message
|
37
|
+
end
|
38
|
+
|
39
|
+
def E(message)
|
40
|
+
@log.error message
|
41
|
+
end
|
42
|
+
|
43
|
+
def F(message)
|
44
|
+
@log.fatal message
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module RTunnel
|
49
|
+
# Resolve the given address to an IP.
|
50
|
+
# The address can have the following formats: host; host:port; ip; ip:port;
|
51
|
+
def self.resolve_address(address, timeout_sec = 5)
|
52
|
+
host, rest = address.split(':', 2)
|
53
|
+
ip = timeout(timeout_sec) { Resolv.getaddress(host) }
|
54
|
+
rest ? "#{ip}:#{rest}" : ip
|
55
|
+
rescue Exception
|
56
|
+
raise AbortProgramException, "Error resolving #{host}"
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
require 'openssl'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'net/ssh'
|
7
|
+
|
8
|
+
module RTunnel::Crypto
|
9
|
+
# Reads all the keys from an openssh known_hosts or authorized_keys2 file.
|
10
|
+
def self.read_authorized_keys(file_name)
|
11
|
+
keys = []
|
12
|
+
File.read(file_name).each_line do |line|
|
13
|
+
pubkey_match = /ssh-\w*\s*(\S*)/.match line
|
14
|
+
next unless pubkey_match
|
15
|
+
pubkey_blob = pubkey_match[1].unpack('m*').first
|
16
|
+
keys << Net::SSH::Buffer.new(pubkey_blob).read_key
|
17
|
+
end
|
18
|
+
keys
|
19
|
+
end
|
20
|
+
|
21
|
+
# Loads a private key from an openssh key file.
|
22
|
+
def self.read_private_key(file_name)
|
23
|
+
Net::SSH::KeyFactory.load_private_key file_name
|
24
|
+
end
|
25
|
+
|
26
|
+
# Computes a string that represents the key. Different keys should
|
27
|
+
# map out to different fingerprints.
|
28
|
+
def self.key_fingerprint(key)
|
29
|
+
key.public_key.to_der
|
30
|
+
end
|
31
|
+
|
32
|
+
# Encrypts some data with a public key. The matching private key will be
|
33
|
+
# required to decrypt the data.
|
34
|
+
def self.encrypt_with_key(key, data)
|
35
|
+
if key.kind_of? OpenSSL::PKey::RSA
|
36
|
+
key.public_encrypt data, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
37
|
+
elsif key.kind_of? OpenSSL::PKey::DSA
|
38
|
+
key.public_encrypt encrypted_data
|
39
|
+
else
|
40
|
+
raise 'Unsupported key type'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Decrypts data that was previously encrypted with encrypt_with_key.
|
45
|
+
def self.decrypt_with_key(key, encrypted_data)
|
46
|
+
if key.kind_of? OpenSSL::PKey::RSA
|
47
|
+
key.private_decrypt encrypted_data, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
48
|
+
elsif key.kind_of? OpenSSL::PKey::DSA
|
49
|
+
key.private_decrypt encrypted_data
|
50
|
+
else
|
51
|
+
raise 'Unsupported key type'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Loads public keys to be used by a server.
|
56
|
+
def self.load_public_keys(file_name)
|
57
|
+
key_list = read_authorized_keys file_name
|
58
|
+
RTunnel::Crypto::KeySet.new key_list
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# A set of keys used by a server to authenticate clients.
|
63
|
+
class RTunnel::Crypto::KeySet
|
64
|
+
def initialize(key_list)
|
65
|
+
@keys_by_fp = {}
|
66
|
+
key_list.each { |k| @keys_by_fp[RTunnel::Crypto.key_fingerprint(k)] = k }
|
67
|
+
end
|
68
|
+
|
69
|
+
def [](key_fp)
|
70
|
+
@keys_by_fp[key_fp]
|
71
|
+
end
|
72
|
+
|
73
|
+
def length
|
74
|
+
@keys_by_fp.length
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# A cryptographically secure hasher. Instances will hash the data
|
79
|
+
class RTunnel::Crypto::Hasher
|
80
|
+
attr_reader :key
|
81
|
+
|
82
|
+
def initialize(key = nil)
|
83
|
+
@key = key || RTunnel::Crypto::Hasher.random_key
|
84
|
+
@cipher = OpenSSL::Cipher::Cipher.new 'aes-128-cbc'
|
85
|
+
@cipher.encrypt
|
86
|
+
iokey = StringIO.new @key
|
87
|
+
@cipher.key = iokey.read_varstring
|
88
|
+
@cipher.iv = iokey.read_varstring
|
89
|
+
end
|
90
|
+
|
91
|
+
# Creates a hash for the given data. Warning: this method is not idempotent.
|
92
|
+
# The intent is that the same hash can be produced by another hasher that is
|
93
|
+
# initialized with the same key and has been fed the same data.
|
94
|
+
def hash(data)
|
95
|
+
@cipher.update Digest::SHA2.digest(data)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Produces a random key for the hasher.
|
99
|
+
def self.random_key
|
100
|
+
cipher = OpenSSL::Cipher::Cipher.new 'aes-128-cbc'
|
101
|
+
iokey = StringIO.new
|
102
|
+
iokey.write_varstring cipher.random_key
|
103
|
+
iokey.write_varstring cipher.random_iv
|
104
|
+
iokey.string
|
105
|
+
end
|
106
|
+
end
|