shadowsocks_ruby 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/.yardopts +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +160 -0
- data/Rakefile +14 -0
- data/bin/aruba +17 -0
- data/bin/bundler +17 -0
- data/bin/console +14 -0
- data/bin/cucumber +17 -0
- data/bin/einhorn +17 -0
- data/bin/einhornsh +17 -0
- data/bin/htmldiff +17 -0
- data/bin/ldiff +17 -0
- data/bin/lrucache_server +16 -0
- data/bin/rake +17 -0
- data/bin/rspec +17 -0
- data/bin/setup +8 -0
- data/bin/yard +17 -0
- data/bin/yardoc +17 -0
- data/bin/yri +17 -0
- data/config.example.json +6 -0
- data/exe/sslocal-ruby +4 -0
- data/exe/ssserver-ruby +4 -0
- data/lib/shadowsocks_ruby/app.rb +236 -0
- data/lib/shadowsocks_ruby/cipher/cipher.rb +112 -0
- data/lib/shadowsocks_ruby/cipher/openssl.rb +81 -0
- data/lib/shadowsocks_ruby/cipher/rbnacl.rb +64 -0
- data/lib/shadowsocks_ruby/cipher/rc4_md5.rb +54 -0
- data/lib/shadowsocks_ruby/cipher/table.rb +65 -0
- data/lib/shadowsocks_ruby/cli/sslocal_runner.rb +125 -0
- data/lib/shadowsocks_ruby/cli/ssserver_runner.rb +115 -0
- data/lib/shadowsocks_ruby/connections/backend_connection.rb +50 -0
- data/lib/shadowsocks_ruby/connections/connection.rb +195 -0
- data/lib/shadowsocks_ruby/connections/server_connection.rb +63 -0
- data/lib/shadowsocks_ruby/connections/tcp/client_connection.rb +43 -0
- data/lib/shadowsocks_ruby/connections/tcp/destination_connection.rb +19 -0
- data/lib/shadowsocks_ruby/connections/tcp/localbackend_connection.rb +29 -0
- data/lib/shadowsocks_ruby/connections/tcp/remoteserver_connection.rb +18 -0
- data/lib/shadowsocks_ruby/connections/udp/client_connection.rb +43 -0
- data/lib/shadowsocks_ruby/connections/udp/destination_connection.rb +18 -0
- data/lib/shadowsocks_ruby/connections/udp/localbackend_connection.rb +29 -0
- data/lib/shadowsocks_ruby/connections/udp/remoteserver_connection.rb +18 -0
- data/lib/shadowsocks_ruby/protocols/cipher/iv_cipher.rb +99 -0
- data/lib/shadowsocks_ruby/protocols/cipher/no_iv_cipher.rb +53 -0
- data/lib/shadowsocks_ruby/protocols/cipher/verify_sha1.rb +138 -0
- data/lib/shadowsocks_ruby/protocols/obfs/http_simple.rb +189 -0
- data/lib/shadowsocks_ruby/protocols/obfs/tls_ticket.rb +342 -0
- data/lib/shadowsocks_ruby/protocols/packet/plain.rb +51 -0
- data/lib/shadowsocks_ruby/protocols/packet/shadowsocks.rb +88 -0
- data/lib/shadowsocks_ruby/protocols/packet/socks5.rb +162 -0
- data/lib/shadowsocks_ruby/protocols/protocol.rb +164 -0
- data/lib/shadowsocks_ruby/protocols/protocol_stack.rb +117 -0
- data/lib/shadowsocks_ruby/util.rb +52 -0
- data/lib/shadowsocks_ruby/version.rb +3 -0
- data/lib/shadowsocks_ruby.rb +86 -0
- data/shadowsocks_ruby.gemspec +48 -0
- metadata +290 -0
@@ -0,0 +1,236 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'singleton'
|
3
|
+
require 'socket'
|
4
|
+
require 'json'
|
5
|
+
require 'einhorn'
|
6
|
+
|
7
|
+
module ShadowsocksRuby
|
8
|
+
# App is a singleton object which provide either Shadowsocks Client functionality
|
9
|
+
# or Shadowsocks Server functionality. One App startup one EventMachine event loop
|
10
|
+
# on one Native Thread / CPU core.
|
11
|
+
#
|
12
|
+
# Because Ruby MRI has a GIL, it is unable to utilize execution parallelism on
|
13
|
+
# multi-core CPU.
|
14
|
+
# However, one can use a shared socket manager to spin off a few Apps that can be
|
15
|
+
# executed parallelly and let the Kernel to do the load balance things.
|
16
|
+
#
|
17
|
+
# A few things noticeable when using a shared socket manager:
|
18
|
+
# * At present, only {https://github.com/stripe/einhorn Einhorn socket manager} are supported.
|
19
|
+
# systemd shared socket manager support is in the plan.
|
20
|
+
#
|
21
|
+
# * At present, using socket manager could cause TLS1.2 obsfucation protocol's
|
22
|
+
# replay attact detection malfunctions, because every process have it's own
|
23
|
+
# copy of LRUCache.
|
24
|
+
#
|
25
|
+
# * Shared socket manager does not work on Windows
|
26
|
+
#
|
27
|
+
class App
|
28
|
+
include Singleton
|
29
|
+
|
30
|
+
MAX_FAST_SHUTDOWN_SECONDS = 10
|
31
|
+
|
32
|
+
attr_reader :options
|
33
|
+
attr_reader :logger
|
34
|
+
|
35
|
+
@@options = {}
|
36
|
+
|
37
|
+
def self.options= options
|
38
|
+
@@options = options
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
@options = @@options
|
43
|
+
|
44
|
+
@logger = Logger.new(STDOUT).tap do |log|
|
45
|
+
|
46
|
+
if options[:__server]
|
47
|
+
log.progname = "ssserver-ruby"
|
48
|
+
elsif options[:__client]
|
49
|
+
log.progname = "sslocal-ruby"
|
50
|
+
end
|
51
|
+
|
52
|
+
if options[:verbose]
|
53
|
+
log.level = Logger::DEBUG
|
54
|
+
elsif options[:quiet]
|
55
|
+
log.level = Logger::WARN
|
56
|
+
else
|
57
|
+
log.level = Logger::INFO
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def run!
|
64
|
+
if options[:__server]
|
65
|
+
start_server
|
66
|
+
elsif options[:__client]
|
67
|
+
start_client
|
68
|
+
end
|
69
|
+
#rescue Exception => e
|
70
|
+
# logger.fatal { e.message + "\n" + e.backtrace.join("\n")}
|
71
|
+
end
|
72
|
+
|
73
|
+
def trap_signals
|
74
|
+
STDOUT.sync = true
|
75
|
+
STDERR.sync = true
|
76
|
+
trap('QUIT') do
|
77
|
+
self.fast_shutdown('QUIT')
|
78
|
+
end
|
79
|
+
trap('TERM') do
|
80
|
+
self.fast_shutdown('TERM')
|
81
|
+
end
|
82
|
+
trap('INT') do
|
83
|
+
self.fast_shutdown('INT')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# TODO: next_tick can't be called from trap context
|
88
|
+
def graceful_shutdown(signal)
|
89
|
+
EventMachine.stop_server(@server) if @server
|
90
|
+
Thread.new{ logger.info "Received #{signal} signal. No longer accepting new connections." }
|
91
|
+
Thread.new{ logger.info "Waiting for #{EventMachine.connection_count} connections to finish." }
|
92
|
+
@server = nil
|
93
|
+
graceful_shutdown_check
|
94
|
+
end
|
95
|
+
|
96
|
+
# TODO: next_tick can't be called from trap context
|
97
|
+
def graceful_shutdown_check
|
98
|
+
EventMachine.next_tick do
|
99
|
+
count = EventMachine.connection_count
|
100
|
+
if count == 0
|
101
|
+
EventMachine.stop_event_loop
|
102
|
+
else
|
103
|
+
@wait_count ||= count
|
104
|
+
Thread.new{ logger.info "Waiting for #{EventMachine.connection_count} connections to finish." if @wait_count != count }
|
105
|
+
EventMachine.next_tick self.method(:graceful_shutdown_check)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# TODO: where does EventMachine.connection_count come from?
|
111
|
+
def fast_shutdown(signal)
|
112
|
+
EventMachine.stop_server(@server) if @server
|
113
|
+
Thread.new{ logger.info "Received #{signal} signal. No longer accepting new connections." }
|
114
|
+
Thread.new{ logger.info "Maximum time to wait for connections is #{MAX_FAST_SHUTDOWN_SECONDS} seconds." }
|
115
|
+
Thread.new{ logger.info "Waiting for #{EventMachine.connection_count} connections to finish." }
|
116
|
+
@server = nil
|
117
|
+
EventMachine.stop_event_loop
|
118
|
+
#EventMachine.stop_event_loop if EventMachine.connection_count == 0
|
119
|
+
#Thread.new do
|
120
|
+
# sleep MAX_FAST_SHUTDOWN_SECONDS
|
121
|
+
# $kernel.exit!
|
122
|
+
#end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
def start_server
|
127
|
+
stack3 = Protocols::ProtocolStack.new([
|
128
|
+
get_packet_protocol,
|
129
|
+
get_cipher_protocol,
|
130
|
+
get_obfs_protocol
|
131
|
+
].compact, options[:cipher_name], options[:password])
|
132
|
+
|
133
|
+
stack4 = Protocols::ProtocolStack.new([
|
134
|
+
["plain", {}]
|
135
|
+
], options[:cipher_name], options[:password])
|
136
|
+
|
137
|
+
server_args = [
|
138
|
+
stack3,
|
139
|
+
{},
|
140
|
+
stack4,
|
141
|
+
{}
|
142
|
+
]
|
143
|
+
|
144
|
+
start_em options[:server], options[:port], Connections::TCP::LocalBackendConnection, server_args
|
145
|
+
end
|
146
|
+
|
147
|
+
def start_client
|
148
|
+
stack1 = Protocols::ProtocolStack.new([
|
149
|
+
["socks5", {}]
|
150
|
+
], options[:cipher_name], options[:password])
|
151
|
+
|
152
|
+
stack2 = Protocols::ProtocolStack.new([
|
153
|
+
get_packet_protocol,
|
154
|
+
get_cipher_protocol,
|
155
|
+
get_obfs_protocol
|
156
|
+
].compact, options[:cipher_name], options[:password])
|
157
|
+
|
158
|
+
local_args = [
|
159
|
+
stack1,
|
160
|
+
{:host => options[:server], :port => options[:port]},
|
161
|
+
stack2,
|
162
|
+
{}
|
163
|
+
]
|
164
|
+
|
165
|
+
start_em options[:local_addr], options[:local_port], Connections::TCP::ClientConnection, local_args
|
166
|
+
end
|
167
|
+
|
168
|
+
def start_em host, port, klass_server, server_args
|
169
|
+
EventMachine.epoll
|
170
|
+
|
171
|
+
EventMachine.run do
|
172
|
+
if options[:einhorn] != true
|
173
|
+
@server = EventMachine.start_server(host, port, klass_server, *server_args)
|
174
|
+
else
|
175
|
+
fd_num = Einhorn::Worker.socket!
|
176
|
+
socket = Socket.for_fd(fd_num)
|
177
|
+
|
178
|
+
@server = EventMachine.attach_server(socket, klass_server, *server_args)
|
179
|
+
end
|
180
|
+
logger.info "server started"
|
181
|
+
logger.info "Listening on #{host}:#{port}"
|
182
|
+
logger.info "Send QUIT to quit after waiting for all connections to finish."
|
183
|
+
logger.info "Send TERM or INT to quit after waiting for up to #{MAX_FAST_SHUTDOWN_SECONDS} seconds for connections to finish."
|
184
|
+
|
185
|
+
trap_signals
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def get_packet_protocol
|
190
|
+
case options[:packet_name]
|
191
|
+
when "origin", "verify_sha1", "verify_sha1_strict"
|
192
|
+
["shadowsocks", {}]
|
193
|
+
else
|
194
|
+
raise AppError, "no such protocol: #{options[:packet_name]}"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def get_cipher_protocol
|
199
|
+
if options[:cipher_name] == nil || options[:cipher_name] == "none"
|
200
|
+
return nil
|
201
|
+
end
|
202
|
+
case options[:cipher_name]
|
203
|
+
when "table"
|
204
|
+
["no_iv_cipher", {}]
|
205
|
+
else
|
206
|
+
case options[:packet_name]
|
207
|
+
when "origin"
|
208
|
+
["iv_cipher", {}]
|
209
|
+
when "verify_sha1"
|
210
|
+
["verify_sha1", {}]
|
211
|
+
when "verify_sha1_strict"
|
212
|
+
["verify_sha1", {:compatible => false}]
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def get_obfs_protocol
|
218
|
+
if options[:obfs_name] == nil
|
219
|
+
return nil
|
220
|
+
end
|
221
|
+
case options[:obfs_name]
|
222
|
+
when "http_simple"
|
223
|
+
["http_simple", {:host => options[:server], :port => options[:port], :obfs_param => options[:obfs_param]}]
|
224
|
+
when "http_simple_strict"
|
225
|
+
["http_simple", {:host => options[:server], :port => options[:port], :obfs_param => options[:obfs_param], :compatible => false}]
|
226
|
+
when "tls_ticket"
|
227
|
+
["tls_ticket", {:host => options[:server], :obfs_param => options[:obfs_param]}]
|
228
|
+
when "tls_ticket_strict"
|
229
|
+
["tls_ticket", {:host => options[:server], :obfs_param => options[:obfs_param], :compatible => false}]
|
230
|
+
else
|
231
|
+
raise AppError, "no such protocol: #{options[:obfs_name]}"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
module ShadowsocksRuby
|
3
|
+
|
4
|
+
# This module provide classes to encapsulate different underlying crypto library,
|
5
|
+
# to utilize them with an unique interface.
|
6
|
+
#
|
7
|
+
# It also provide some useful utility functions like
|
8
|
+
# {.hmac_sha1_digest} and {.bytes_to_key}.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # Demonstrate how to build a cipher object and it's typical use case.
|
12
|
+
# cipher = ShadowsocksRuby::Cipher.build('aes-256-cfb', 'secret123')
|
13
|
+
# iv = cipher.random_id
|
14
|
+
# encrypted_text = cipher.encrypt("hello world!", iv)
|
15
|
+
# puts cipher.decrypt(encrypted_text, iv) # hello world!
|
16
|
+
# puts cipher.key # ...... # in case key need to be used in some Digest algorithm.
|
17
|
+
|
18
|
+
module Cipher
|
19
|
+
extend self
|
20
|
+
|
21
|
+
# Builder for cipher object
|
22
|
+
#
|
23
|
+
# Supported methods are:
|
24
|
+
# * table
|
25
|
+
# * rc4-md5
|
26
|
+
# * chacha20, chacha2-ietf, salsa20 which are provided by RbNaCl
|
27
|
+
# * all cipher methods supported by ruby gems OpenSSL, use
|
28
|
+
#
|
29
|
+
# ruby -e "require 'openssl'; puts OpenSSL::Cipher.ciphers"
|
30
|
+
#
|
31
|
+
# to get a full list.
|
32
|
+
# @param [String] method Cipher methods
|
33
|
+
# @param [String] password Password
|
34
|
+
# @return [OpenSSL, Table, RC4_MD5, RbNaCl] A duck type cipher object
|
35
|
+
#
|
36
|
+
def build method, password
|
37
|
+
case method
|
38
|
+
when 'table'
|
39
|
+
ShadowsocksRuby::Cipher::Table.new password
|
40
|
+
when 'rc4-md5'
|
41
|
+
ShadowsocksRuby::Cipher::RC4_MD5.new password
|
42
|
+
when 'chacha20','chacha20-ietf','salsa20'
|
43
|
+
ShadowsocksRuby::Cipher::RbNaCl.new method, password
|
44
|
+
else
|
45
|
+
ShadowsocksRuby::Cipher::OpenSSL.new method, password
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Generate <b>first 10 bytes</b> of HMAC using sha1 Digest
|
50
|
+
#
|
51
|
+
# @param [String] key Key, use {#bytes_to_key} to convert a password to key if you need
|
52
|
+
# @param [String] message Message to digest
|
53
|
+
# @return [String] Digest, <b> only first 10 bytes</b>
|
54
|
+
def hmac_sha1_digest(key, message)
|
55
|
+
@digest ||= ::OpenSSL::Digest.new('sha1')
|
56
|
+
::OpenSSL::HMAC.digest(@digest, key, message)[0,10]
|
57
|
+
end
|
58
|
+
|
59
|
+
# Equivalent to OpenSSL's EVP_BytesToKey() with count = 1
|
60
|
+
#
|
61
|
+
# @param [String] Password Password bytes
|
62
|
+
# @param [Integer] key_len Key length, the length of key bytes to generate
|
63
|
+
# @param [Integer] iv_len IV length, needed by internal algorithm
|
64
|
+
# @return [String] Key bytes, of *key_len* length
|
65
|
+
def bytes_to_key(password, key_len, iv_len)
|
66
|
+
bytes_to_key1(nil, password, 1, key_len, iv_len)[0]
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def bytes_to_key0(md_buf, salt, data, count)
|
72
|
+
src = md_buf.empty? ? '' : md_buf
|
73
|
+
src << data
|
74
|
+
src << salt if salt
|
75
|
+
|
76
|
+
dst = ::OpenSSL::Digest::MD5.digest(src)
|
77
|
+
|
78
|
+
(count - 1).times do
|
79
|
+
dst = ::OpenSSL::Digest::MD5.digest(dst)
|
80
|
+
end
|
81
|
+
|
82
|
+
return dst
|
83
|
+
end
|
84
|
+
|
85
|
+
# Equivalent to OpenSSL's EVP_BytesToKey()
|
86
|
+
# Taken from http://d.hatena.ne.jp/winebarrel/20081208/p1
|
87
|
+
# Fixed by Zhenkyle
|
88
|
+
def bytes_to_key1(salt, data, count, nkey, niv)
|
89
|
+
key = ''
|
90
|
+
iv = ''
|
91
|
+
md_buf = ''
|
92
|
+
|
93
|
+
loop do
|
94
|
+
md_buf = bytes_to_key0(md_buf, salt, data, count)
|
95
|
+
|
96
|
+
if nkey.nonzero?
|
97
|
+
key << (nkey > md_buf.length ? md_buf : md_buf[0, nkey])
|
98
|
+
nkey -= nkey > md_buf.length ? md_buf.length : nkey
|
99
|
+
elsif niv.nonzero?
|
100
|
+
iv << (niv > md_buf.length ? md_buf : md_buf[0, niv])
|
101
|
+
niv -= niv > md_buf.length ? md_buf.length : niv
|
102
|
+
end
|
103
|
+
|
104
|
+
if nkey.zero? and niv.zero?
|
105
|
+
break
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
return [key, iv]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module ShadowsocksRuby
|
4
|
+
module Cipher
|
5
|
+
|
6
|
+
# Encapsulate RubyGems version of OpenSSL, the gems version is newer than
|
7
|
+
# the version in Ruby Standand Library.
|
8
|
+
#
|
9
|
+
# Cipher methods provided by Ruby OpenSSL library is dicided by
|
10
|
+
# the OpenSSL library comes with ruby on your system.
|
11
|
+
# To work with specific version of OpenSSL library other than the version
|
12
|
+
# comes with ruby, you may need to specify the path where OpenSSL is installed.
|
13
|
+
#
|
14
|
+
# gem install openssl -- --with-openssl-dir=/opt/openssl
|
15
|
+
#
|
16
|
+
# Use this command to get a full list of cipher methods supported on your system.
|
17
|
+
# ruby -e "require 'openssl'; puts OpenSSL::Cipher.ciphers"
|
18
|
+
#
|
19
|
+
#
|
20
|
+
# See https://github.com/ruby/openssl for more detail.
|
21
|
+
#
|
22
|
+
# Normally you should use {ShadowsocksRuby::Cipher#build} to get an
|
23
|
+
# instance of this class.
|
24
|
+
class OpenSSL
|
25
|
+
|
26
|
+
# Return the key, which length is decided by the cipher method.
|
27
|
+
# @return [String] key
|
28
|
+
attr_reader :key
|
29
|
+
|
30
|
+
# @param [String] method Cipher methods
|
31
|
+
# @param [String] password Password
|
32
|
+
def initialize method, password
|
33
|
+
@cipher_encrypt = ::OpenSSL::Cipher.new(method).encrypt
|
34
|
+
@cipher_decrypt = ::OpenSSL::Cipher.new(method).decrypt
|
35
|
+
key_len = @cipher_encrypt.key_len
|
36
|
+
iv_len = @cipher_encrypt.iv_len
|
37
|
+
@key = Cipher.bytes_to_key(password, key_len, iv_len)
|
38
|
+
@cipher_encrypt.key = @key
|
39
|
+
@cipher_decrypt.key = @key
|
40
|
+
@encrypt_iv = nil
|
41
|
+
@decrypt_iv = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Generate a random IV for the cipher method
|
45
|
+
# @return [String] random IV of the length of the cipher method
|
46
|
+
def random_iv
|
47
|
+
@encrypt_iv = @cipher_encrypt.random_iv
|
48
|
+
end
|
49
|
+
|
50
|
+
# Encrypt message by provided IV
|
51
|
+
# @param [String] message
|
52
|
+
# @param [String] iv
|
53
|
+
# @return [String] Encrypted Message
|
54
|
+
def encrypt(message, iv)
|
55
|
+
if @encrypt_iv != iv
|
56
|
+
@encrypt_iv = iv
|
57
|
+
@cipher_encrypt.iv = iv
|
58
|
+
end
|
59
|
+
@cipher_encrypt.update(message) << @cipher_encrypt.final
|
60
|
+
end
|
61
|
+
|
62
|
+
# Decrypt message by provided IV
|
63
|
+
# @param [String] message
|
64
|
+
# @param [String] iv
|
65
|
+
# @return [String] Decrypted Message
|
66
|
+
def decrypt(message, iv)
|
67
|
+
if @decrypt_iv != iv
|
68
|
+
@decrypt_iv = iv
|
69
|
+
@cipher_decrypt.iv = iv
|
70
|
+
end
|
71
|
+
@cipher_decrypt.update(message) << @cipher_decrypt.final
|
72
|
+
end
|
73
|
+
|
74
|
+
# Get the cipher object's IV length
|
75
|
+
# @return [Integer]
|
76
|
+
def iv_len
|
77
|
+
@cipher_encrypt.iv_len
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rbnacl'
|
2
|
+
|
3
|
+
module ShadowsocksRuby
|
4
|
+
module Cipher
|
5
|
+
|
6
|
+
# Encapsulate RbNaCl ruby library, cipher methods provided by this Class are:
|
7
|
+
# * chacha20 -- ChaCha20Poly1305Legacy without ad
|
8
|
+
# * chacha2-ietf -- ChaCha20Poly1305IETF without ad
|
9
|
+
# * salsa20 -- XSalsa20Poly1305 without ad
|
10
|
+
#
|
11
|
+
# Normally you should use {ShadowsocksRuby::Cipher#build} to get an
|
12
|
+
# instance of this class.
|
13
|
+
|
14
|
+
class RbNaCl
|
15
|
+
|
16
|
+
attr_reader :key
|
17
|
+
# (see OpenSSL#initialize)
|
18
|
+
def initialize method, password
|
19
|
+
klass = case method
|
20
|
+
when 'chacha20'
|
21
|
+
::RbNaCl::AEAD::ChaCha20Poly1305Legacy
|
22
|
+
when 'chacha20-ietf'
|
23
|
+
::RbNaCl::AEAD::ChaCha20Poly1305IETF
|
24
|
+
when 'salsa20'
|
25
|
+
::RbNaCl::SecretBoxes::XSalsa20Poly1305
|
26
|
+
else
|
27
|
+
raise CipherError, "unsupported method: " + method
|
28
|
+
end
|
29
|
+
key_len = klass.key_bytes
|
30
|
+
iv_len = klass.nonce_bytes
|
31
|
+
@key = ShadowsocksRuby::Cipher.bytes_to_key(password, key_len, iv_len)
|
32
|
+
@cipher = klass.new(@key)
|
33
|
+
end
|
34
|
+
|
35
|
+
# (see OpenSSL#random_iv)
|
36
|
+
def random_iv
|
37
|
+
::RbNaCl::Random.random_bytes(@cipher.nonce_bytes)
|
38
|
+
end
|
39
|
+
|
40
|
+
# (see OpenSSL#encrypt)
|
41
|
+
def encrypt(message, iv)
|
42
|
+
if @cipher.class == ::RbNaCl::SecretBoxes::XSalsa20Poly1305
|
43
|
+
@cipher.encrypt(iv, message)
|
44
|
+
else
|
45
|
+
@cipher.encrypt(iv, message, nil)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# (see OpenSSL#decrypt)
|
50
|
+
def decrypt(message, iv)
|
51
|
+
if @cipher.class == ::RbNaCl::SecretBoxes::XSalsa20Poly1305
|
52
|
+
@cipher.decrypt(iv, message)
|
53
|
+
else
|
54
|
+
@cipher.decrypt(iv, message, nil)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# (see OpenSSL#iv_len)
|
59
|
+
def iv_len
|
60
|
+
@cipher.iv_bytes
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module ShadowsocksRuby
|
4
|
+
module Cipher
|
5
|
+
|
6
|
+
# Implementation of the RC4_MD5 cipher method.
|
7
|
+
#
|
8
|
+
# Normally you should use {ShadowsocksRuby::Cipher#build} to get an
|
9
|
+
# instance of this class.
|
10
|
+
|
11
|
+
class RC4_MD5
|
12
|
+
attr_reader :key
|
13
|
+
|
14
|
+
# (see OpenSSL#initialize)
|
15
|
+
def initialize password
|
16
|
+
@key = ShadowsocksRuby::Cipher.bytes_to_key(password, 16, 16)
|
17
|
+
@cipher_encrypt = ::OpenSSL::Cipher.new('rc4').encrypt
|
18
|
+
@cipher_decrypt = ::OpenSSL::Cipher.new('rc4').decrypt
|
19
|
+
@encrypt_iv = nil
|
20
|
+
@decrypt_iv = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
# (see OpenSSL#random_iv)
|
24
|
+
def random_iv
|
25
|
+
Random.new.bytes(16)
|
26
|
+
end
|
27
|
+
|
28
|
+
# (see OpenSSL#encrypt)
|
29
|
+
def encrypt(message, iv)
|
30
|
+
if @encrypt_iv != iv
|
31
|
+
@encrypt_iv = iv
|
32
|
+
key = ::OpenSSL::Digest::MD5.digest(@key + iv)
|
33
|
+
@cipher_encrypt.key = key
|
34
|
+
end
|
35
|
+
@cipher_encrypt.update(message) << @cipher_encrypt.final
|
36
|
+
end
|
37
|
+
|
38
|
+
# (see OpenSSL#decrypt)
|
39
|
+
def decrypt(message, iv)
|
40
|
+
if @decrypt_iv != iv
|
41
|
+
@decrypt_iv = iv
|
42
|
+
key = ::OpenSSL::Digest::MD5.digest(@key + iv)
|
43
|
+
@cipher_decrypt.key = key
|
44
|
+
end
|
45
|
+
@cipher_decrypt.update(message) << @cipher_decrypt.final
|
46
|
+
end
|
47
|
+
|
48
|
+
# (see OpenSSL#iv_len)
|
49
|
+
def iv_len
|
50
|
+
16
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module ShadowsocksRuby
|
4
|
+
module Cipher
|
5
|
+
|
6
|
+
# Implementation of the Table cipher method.
|
7
|
+
#
|
8
|
+
# Note: this cipher method have neither IV or key, so may be
|
9
|
+
# incompatible with protocols which needs IV or key.
|
10
|
+
#
|
11
|
+
# Normally you should use {ShadowsocksRuby::Cipher#build} to get an
|
12
|
+
# instance of this class.
|
13
|
+
class Table
|
14
|
+
# (see OpenSSL#initialize)
|
15
|
+
def initialize password
|
16
|
+
@encrypt_table, @decrypt_table = get_table(password)
|
17
|
+
end
|
18
|
+
|
19
|
+
# (see OpenSSL#encrypt)
|
20
|
+
def encrypt(message)
|
21
|
+
translate @encrypt_table, message
|
22
|
+
end
|
23
|
+
|
24
|
+
# (see OpenSSL#decrypt)
|
25
|
+
def decrypt(message)
|
26
|
+
translate @decrypt_table, message
|
27
|
+
end
|
28
|
+
|
29
|
+
# (see OpenSSL#iv_len)
|
30
|
+
#
|
31
|
+
# returns 0 for Table
|
32
|
+
def iv_len
|
33
|
+
0
|
34
|
+
end
|
35
|
+
|
36
|
+
# (see OpenSSL#key)
|
37
|
+
#
|
38
|
+
# returns nil for Table
|
39
|
+
def key
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def get_table(key)
|
46
|
+
table = [*0..255]
|
47
|
+
a = ::OpenSSL::Digest::MD5.digest(key).unpack('Q<')[0]
|
48
|
+
|
49
|
+
(1...1024).each do |i|
|
50
|
+
table.sort! { |x, y| a % (x + i) - a % (y + i) }
|
51
|
+
end
|
52
|
+
|
53
|
+
decrypt_table = Array.new(256)
|
54
|
+
table.each_with_index {|x, i| decrypt_table[x] = i}
|
55
|
+
|
56
|
+
[table, decrypt_table]
|
57
|
+
end
|
58
|
+
|
59
|
+
def translate(table, buf)
|
60
|
+
buf.bytes.map!{|x| table[x]}.pack("C*")
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|