diff --git a/shadowsocks/crypto/openssl.py b/shadowsocks/crypto/openssl.py index abefc33..e4980a4 100644 --- a/shadowsocks/crypto/openssl.py +++ b/shadowsocks/crypto/openssl.py @@ -17,7 +17,7 @@ from __future__ import absolute_import, division, print_function, \ with_statement -from ctypes import c_char_p, c_int, c_long, byref,\ +from ctypes import c_char_p, c_int, c_long, byref, \ create_string_buffer, c_void_p from shadowsocks import common @@ -77,6 +77,7 @@ def load_cipher(cipher_name): return cipher() return None + def rand_bytes(length): if not loaded: load_openssl() @@ -86,6 +87,7 @@ def rand_bytes(length): raise Exception('RAND_bytes return error') return buf.raw + class OpenSSLCrypto(object): def __init__(self, cipher_name, key, iv, op): self._ctx = None @@ -130,9 +132,14 @@ class OpenSSLCrypto(object): ciphers = { + # CBC mode need a special use way that different from other. + # CBC mode encrypt message with 16n length, and need 16n+1 length space to decrypt it , otherwise don't decrypt it 'aes-128-cbc': (16, 16, OpenSSLCrypto), 'aes-192-cbc': (24, 16, OpenSSLCrypto), 'aes-256-cbc': (32, 16, OpenSSLCrypto), + 'aes-128-gcm': (16, 16, OpenSSLCrypto), + 'aes-192-gcm': (24, 16, OpenSSLCrypto), + 'aes-256-gcm': (32, 16, OpenSSLCrypto), 'aes-128-cfb': (16, 16, OpenSSLCrypto), 'aes-192-cfb': (24, 16, OpenSSLCrypto), 'aes-256-cfb': (32, 16, OpenSSLCrypto), @@ -162,7 +169,6 @@ ciphers = { def run_method(method): - cipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 1) decipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 0) @@ -197,5 +203,20 @@ def test_rc4(): run_method('rc4') +def test_all(): + for k, v in ciphers.items(): + print(k) + try: + run_method(k) + except AssertionError as e: + eprint("AssertionError===========" + k) + eprint(e) + + +def eprint(*args, **kwargs): + import sys + print(*args, file=sys.stderr, **kwargs) + + if __name__ == '__main__': - test_aes_128_cfb() + test_all() diff --git a/shadowsocks/crypto/sodium.py b/shadowsocks/crypto/sodium.py index 25705f7..390941c 100644 --- a/shadowsocks/crypto/sodium.py +++ b/shadowsocks/crypto/sodium.py @@ -20,6 +20,8 @@ from __future__ import absolute_import, division, print_function, \ from ctypes import c_char_p, c_int, c_ulong, c_ulonglong, byref, \ create_string_buffer, c_void_p +import logging + from shadowsocks.crypto import util __all__ = ['ciphers'] @@ -55,10 +57,31 @@ def load_libsodium(): try: libsodium.crypto_stream_chacha20_ietf_xor_ic.restype = c_int libsodium.crypto_stream_chacha20_ietf_xor_ic.argtypes = (c_void_p, c_char_p, - c_ulonglong, - c_char_p, c_ulong, - c_char_p) + c_ulonglong, + c_char_p, c_ulong, + c_char_p) + except: + logging.info("ChaCha20 IETF not support.") + pass + + try: + libsodium.crypto_stream_xsalsa20_xor_ic.restype = c_int + libsodium.crypto_stream_xsalsa20_xor_ic.argtypes = (c_void_p, c_char_p, + c_ulonglong, + c_char_p, c_ulonglong, + c_char_p) + except: + logging.info("XSalsa20 not support.") + pass + + try: + libsodium.crypto_stream_xchacha20_xor_ic.restype = c_int + libsodium.crypto_stream_xchacha20_xor_ic.argtypes = (c_void_p, c_char_p, + c_ulonglong, + c_char_p, c_ulonglong, + c_char_p) except: + logging.info("XChaCha20 not support. XChaCha20 only support since libsodium v1.0.12") pass buf = create_string_buffer(buf_size) @@ -79,6 +102,10 @@ class SodiumCrypto(object): self.cipher = libsodium.crypto_stream_chacha20_xor_ic elif cipher_name == 'chacha20-ietf': self.cipher = libsodium.crypto_stream_chacha20_ietf_xor_ic + elif cipher_name == 'xchacha20': + self.cipher = libsodium.crypto_stream_xchacha20_xor_ic + elif cipher_name == 'xsalsa20': + self.cipher = libsodium.crypto_stream_xsalsa20_xor_ic else: raise Exception('Unknown cipher') # byte counter, not block counter @@ -107,10 +134,13 @@ class SodiumCrypto(object): def clean(self): pass + ciphers = { 'salsa20': (32, 8, SodiumCrypto), 'chacha20': (32, 8, SodiumCrypto), 'chacha20-ietf': (32, 12, SodiumCrypto), + 'xchacha20': (32, 24, SodiumCrypto), + 'xsalsa20': (32, 24, SodiumCrypto), } @@ -122,7 +152,6 @@ def test_salsa20(): def test_chacha20(): - cipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 1) decipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 0) @@ -130,13 +159,29 @@ def test_chacha20(): def test_chacha20_ietf(): - cipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 1) decipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 0) util.run_cipher(cipher, decipher) + +def test_xchacha20(): + cipher = SodiumCrypto('xchacha20', b'k' * 32, b'i' * 24, 1) + decipher = SodiumCrypto('xchacha20', b'k' * 32, b'i' * 24, 0) + + util.run_cipher(cipher, decipher) + + +def test_xsalsa20(): + cipher = SodiumCrypto('xsalsa20', b'k' * 32, b'i' * 24, 1) + decipher = SodiumCrypto('xsalsa20', b'k' * 32, b'i' * 24, 0) + + util.run_cipher(cipher, decipher) + + if __name__ == '__main__': test_chacha20_ietf() test_chacha20() test_salsa20() + test_xchacha20() + test_xsalsa20() diff --git a/shadowsocks/crypto/util.py b/shadowsocks/crypto/util.py index 212df86..679540d 100644 --- a/shadowsocks/crypto/util.py +++ b/shadowsocks/crypto/util.py @@ -22,6 +22,13 @@ import logging def find_library_nt(name): + # type: (str) -> list + """ + find lib in windows in all the directory in path env + + :param name: can end with `.dll` or not + :return: lib results list + """ # modified from ctypes.util # ctypes.util.find_library just returns first result he found # but we want to try them all diff --git a/shadowsocks/obfs.py b/shadowsocks/obfs.py index 3dfdb14..499ca1b 100644 --- a/shadowsocks/obfs.py +++ b/shadowsocks/obfs.py @@ -35,7 +35,7 @@ method_supported.update(auth.obfs_map) method_supported.update(auth_chain.obfs_map) def mu_protocol(): - return ["auth_aes128_md5", "auth_aes128_sha1", "auth_chain_a"] + return ["auth_aes128_md5", "auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c", "auth_chain_d", "auth_chain_e"] class server_info(object): def __init__(self, data): diff --git a/shadowsocks/obfsplugin/auth_chain.py b/shadowsocks/obfsplugin/auth_chain.py index d9d3326..6619f4f 100644 --- a/shadowsocks/obfsplugin/auth_chain.py +++ b/shadowsocks/obfsplugin/auth_chain.py @@ -60,12 +60,17 @@ def create_auth_chain_e(method): return auth_chain_e(method) +def create_auth_chain_f(method): + return auth_chain_f(method) + + obfs_map = { 'auth_chain_a': (create_auth_chain_a,), 'auth_chain_b': (create_auth_chain_b,), 'auth_chain_c': (create_auth_chain_c,), 'auth_chain_d': (create_auth_chain_d,), 'auth_chain_e': (create_auth_chain_e,), + 'auth_chain_f': (create_auth_chain_f,), } @@ -845,7 +850,7 @@ class auth_chain_d(auth_chain_b): class auth_chain_e(auth_chain_d): def __init__(self, method): - super(auth_chain_d, self).__init__(method) + super(auth_chain_e, self).__init__(method) self.salt = b"auth_chain_e" self.no_compatible_method = 'auth_chain_e' @@ -858,3 +863,49 @@ class auth_chain_e(auth_chain_d): # use the mini size in the data_size_list0 pos = bisect.bisect_left(self.data_size_list0, other_data_size) return self.data_size_list0[pos] - other_data_size + + +# auth_chain_f +# when every connect create, generate size_list will different when every day or every custom time interval which set in the config +class auth_chain_f(auth_chain_e): + def __init__(self, method): + super(auth_chain_f, self).__init__(method) + self.salt = b"auth_chain_f" + self.no_compatible_method = 'auth_chain_f' + + def set_server_info(self, server_info): + self.server_info = server_info + try: + max_client = int(server_info.protocol_param.split('#')[0]) + except: + max_client = 64 + try: + self.key_change_interval = int(server_info.protocol_param.split('#')[1]) # config are in second + except: + self.key_change_interval = 60 * 60 * 24 # a day by second + self.key_change_datetime_key = int(int(time.time()) / self.key_change_interval) + self.key_change_datetime_key_bytes = [] # big bit first list + for i in range(7, -1, -1): # big-ending compare to c + self.key_change_datetime_key_bytes.append((self.key_change_datetime_key >> (8 * i)) & 0xFF) + self.server_info.data.set_max_client(max_client) + self.init_data_size(self.server_info.key) + + def init_data_size(self, key): + if self.data_size_list0: + self.data_size_list0 = [] + random = xorshift128plus() + # key xor with key_change_datetime_key + new_key = bytearray(key) + for i in range(0, 8): + new_key[i] ^= self.key_change_datetime_key_bytes[i] + random.init_from_bin(new_key) + # 补全数组长为12~24-1 + list_len = random.next() % (8 + 16) + (4 + 8) + for i in range(0, list_len): + self.data_size_list0.append(int(random.next() % 2340 % 2040 % 1440)) + self.data_size_list0.sort() + old_len = len(self.data_size_list0) + self.check_and_patch_data_size(random) + # if check_and_patch_data_size are work, re-sort again. + if old_len != len(self.data_size_list0): + self.data_size_list0.sort()