shadowsocks_ruby 0.1.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.
- 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
|