From 08b88026e884eac456b0d7dee1d5dbcbf30cebe3 Mon Sep 17 00:00:00 2001 From: AkaneAkaza Date: Wed, 30 May 2018 13:29:04 +0800 Subject: [PATCH] add auth_akarin --- shadowsocks/obfs.py | 7 +- shadowsocks/obfsplugin/auth_akarin.py | 801 ++++++++++++++++++++++++++ shadowsocks/tcprelay.py | 1 - shadowsocks/udprelay.py | 2 +- 4 files changed, 807 insertions(+), 4 deletions(-) create mode 100644 shadowsocks/obfsplugin/auth_akarin.py diff --git a/shadowsocks/obfs.py b/shadowsocks/obfs.py index 499ca1b..f8ee2d3 100644 --- a/shadowsocks/obfs.py +++ b/shadowsocks/obfs.py @@ -23,7 +23,7 @@ import hashlib import logging from shadowsocks import common -from shadowsocks.obfsplugin import plain, http_simple, obfs_tls, verify, auth, auth_chain +from shadowsocks.obfsplugin import plain, http_simple, obfs_tls, verify, auth, auth_chain, auth_akarin method_supported = {} @@ -33,9 +33,12 @@ method_supported.update(obfs_tls.obfs_map) method_supported.update(verify.obfs_map) method_supported.update(auth.obfs_map) method_supported.update(auth_chain.obfs_map) +method_supported.update(auth_akarin.obfs_map) def mu_protocol(): - return ["auth_aes128_md5", "auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c", "auth_chain_d", "auth_chain_e"] + return {"auth_aes128_md5", "auth_aes128_sha1", + "auth_chain_a", "auth_chain_b", "auth_chain_c", "auth_chain_d", "auth_chain_e", "auth_chain_f", + "auth_akarin_rand", "auth_akarin_spec_a"} class server_info(object): def __init__(self, data): diff --git a/shadowsocks/obfsplugin/auth_akarin.py b/shadowsocks/obfsplugin/auth_akarin.py new file mode 100644 index 0000000..58f84a3 --- /dev/null +++ b/shadowsocks/obfsplugin/auth_akarin.py @@ -0,0 +1,801 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2018-2018 Akkariin +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import hashlib +import logging +import binascii +import base64 +import time +import datetime +import random +import math +import struct +import hmac +import bisect + +import shadowsocks +from shadowsocks import common, lru_cache, encrypt +from shadowsocks.obfsplugin import plain +from shadowsocks.common import to_bytes, to_str, ord, chr +from shadowsocks.crypto import openssl + +rand_bytes = openssl.rand_bytes + +def create_auth_akarin_rand(method): + return auth_akarin_rand(method) + + +def create_auth_akarin_spec_a(method): + return auth_akarin_spec_a(method) + + +obfs_map = { + 'auth_akarin_rand': (create_auth_akarin_rand,), + 'auth_akarin_spec_a': (create_auth_akarin_spec_a,), +} + + +class xorshift128plus(object): + max_int = (1 << 64) - 1 + mov_mask = (1 << (64 - 23)) - 1 + + def __init__(self): + self.v0 = 0 + self.v1 = 0 + + def next(self): + x = self.v0 + y = self.v1 + self.v0 = y + x ^= ((x & xorshift128plus.mov_mask) << 23) + x ^= (y ^ (x >> 17) ^ (y >> 26)) + self.v1 = x + return (x + y) & xorshift128plus.max_int + + def init_from_bin(self, bin): + if len(bin) < 16: + bin += b'\0' * 16 + self.v0 = struct.unpack('= len(str2): + if str1[:len(str2)] == str2: + return True + return False + + +class auth_base(plain.plain): + def __init__(self, method): + super(auth_base, self).__init__(method) + self.method = method + self.no_compatible_method = '' + self.overhead = 4 + + def init_data(self): + return '' + + def get_overhead(self, direction): # direction: true for c->s false for s->c + return self.overhead + + def set_server_info(self, server_info): + self.server_info = server_info + + def client_encode(self, buf): + return buf + + def client_decode(self, buf): + return (buf, False) + + def server_encode(self, buf): + return buf + + def server_decode(self, buf): + return (buf, True, False) + + def not_match_return(self, buf): + self.raw_trans = True + self.overhead = 0 + if self.method == self.no_compatible_method: + return (b'E' * 2048, False) + return (buf, False) + + +class client_queue(object): + def __init__(self, begin_id): + self.front = begin_id - 64 + self.back = begin_id + 1 + self.alloc = {} + self.enable = True + self.last_update = time.time() + self.ref = 0 + + def update(self): + self.last_update = time.time() + + def addref(self): + self.ref += 1 + + def delref(self): + if self.ref > 0: + self.ref -= 1 + + def is_active(self): + return (self.ref > 0) and (time.time() - self.last_update < 60 * 10) + + def re_enable(self, connection_id): + self.enable = True + self.front = connection_id - 64 + self.back = connection_id + 1 + self.alloc = {} + + def insert(self, connection_id): + if not self.enable: + logging.warn('obfs auth: not enable') + return False + if not self.is_active(): + self.re_enable(connection_id) + self.update() + if connection_id < self.front: + logging.warn('obfs auth: deprecated id, someone replay attack') + return False + if connection_id > self.front + 0x4000: + logging.warn('obfs auth: wrong id') + return False + if connection_id in self.alloc: + logging.warn('obfs auth: duplicate id, someone replay attack') + return False + if self.back <= connection_id: + self.back = connection_id + 1 + self.alloc[connection_id] = 1 + while (self.front in self.alloc) or self.front + 0x1000 < self.back: + if self.front in self.alloc: + del self.alloc[self.front] + self.front += 1 + self.addref() + return True + + +class obfs_auth_akarin_data(object): + def __init__(self, name): + self.name = name + self.user_id = {} + self.local_client_id = b'' + self.connection_id = 0 + self.set_max_client(64) # max active client count + + def update(self, user_id, client_id, connection_id): + if user_id not in self.user_id: + self.user_id[user_id] = lru_cache.LRUCache() + local_client_id = self.user_id[user_id] + + if client_id in local_client_id: + local_client_id[client_id].update() + + def set_max_client(self, max_client): + self.max_client = max_client + self.max_buffer = max(self.max_client * 2, 1024) + + def insert(self, user_id, client_id, connection_id): + if user_id not in self.user_id: + self.user_id[user_id] = lru_cache.LRUCache() + local_client_id = self.user_id[user_id] + + if local_client_id.get(client_id, None) is None or not local_client_id[client_id].enable: + if local_client_id.first() is None or len(local_client_id) < self.max_client: + if client_id not in local_client_id: + # TODO: check + local_client_id[client_id] = client_queue(connection_id) + else: + local_client_id[client_id].re_enable(connection_id) + return local_client_id[client_id].insert(connection_id) + + if not local_client_id[local_client_id.first()].is_active(): + del local_client_id[local_client_id.first()] + if client_id not in local_client_id: + # TODO: check + local_client_id[client_id] = client_queue(connection_id) + else: + local_client_id[client_id].re_enable(connection_id) + return local_client_id[client_id].insert(connection_id) + + logging.warn(self.name + ': no inactive client') + return False + else: + return local_client_id[client_id].insert(connection_id) + + def remove(self, user_id, client_id): + if user_id in self.user_id: + local_client_id = self.user_id[user_id] + if client_id in local_client_id: + local_client_id[client_id].delref() + + +class auth_akarin_rand(auth_base): + def __init__(self, method): + super(auth_akarin_rand, self).__init__(method) + self.hashfunc = hashlib.md5 + self.recv_buf = b'' + self.unit_len = 2800 + self.raw_trans = False + self.has_sent_header = False + self.has_recv_header = False + self.client_id = 0 + self.connection_id = 0 + self.max_time_dif = 60 * 60 * 24 # time dif (second) setting + self.salt = b"auth_akarin_rand" + self.no_compatible_method = 'auth_akarin_rand' + self.pack_id = 1 + self.recv_id = 1 + self.user_id = None + self.user_id_num = 0 + self.user_key = None + self.overhead = 4 + self.client_over_head = self.overhead + self.last_client_hash = b'' + self.last_server_hash = b'' + self.random_client = xorshift128plus() + self.random_server = xorshift128plus() + self.encryptor = None + self.new_send_tcp_mss = 2000 + self.send_tcp_mss = 2000 + self.recv_tcp_mss = 2000 + self.send_back_cmd = [] + + def init_data(self): + return obfs_auth_akarin_data(self.method) + + def get_overhead(self, direction): # direction: true for c->s false for s->c + return self.overhead + + 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 + self.server_info.data.set_max_client(max_client) + + def trapezoid_random_float(self, d): + if d == 0: + return random.random() + s = random.random() + a = 1 - d + return (math.sqrt(a * a + 4 * d * s) - a) / (2 * d) + + def trapezoid_random_int(self, max_val, d): + v = self.trapezoid_random_float(d) + return int(v * max_val) + + def send_rnd_data_len(self, buf_size, last_hash, random): + if buf_size + self.server_info.overhead > self.send_tcp_mss: + random.init_from_bin_len(last_hash, buf_size) + return random.next() % 521 + if buf_size >= 1440 or buf_size + self.server_info.overhead == self.send_tcp_mss: + return 0 + random.init_from_bin_len(last_hash, buf_size) + if buf_size > 1300: + return random.next() % 31 + if buf_size > 900: + return random.next() % 127 + if buf_size > 400: + return random.next() % 521 + return random.next() % (self.send_tcp_mss - buf_size - self.server_info.overhead) + + def recv_rnd_data_len(self, buf_size, last_hash, random): + if buf_size + self.server_info.overhead > self.recv_tcp_mss: + random.init_from_bin_len(last_hash, buf_size) + return random.next() % 521 + if buf_size >= 1440 or buf_size + self.server_info.overhead == self.send_tcp_mss: + return 0 + random.init_from_bin_len(last_hash, buf_size) + if buf_size > 1300: + return random.next() % 31 + if buf_size > 900: + return random.next() % 127 + if buf_size > 400: + return random.next() % 521 + return random.next() % (self.recv_tcp_mss - buf_size - self.server_info.overhead) + + def udp_rnd_data_len(self, last_hash, random): + random.init_from_bin(last_hash) + return random.next() % 127 + + def rnd_data(self, buf_size, buf, last_hash, random): + rand_len = self.send_rnd_data_len(buf_size, last_hash, random) + + rnd_data_buf = rand_bytes(rand_len) + + if buf_size == 0: + return rnd_data_buf + else: + if rand_len > 0: + return buf + rnd_data_buf + else: + return buf + + def pack_client_data(self, buf): + buf = self.encryptor.encrypt(buf) + if self.send_back_cmd: + cmd_len = 2 + self.send_tcp_mss = self.recv_tcp_mss + data = self.rnd_data(len(buf) + cmd_len, buf, self.last_client_hash, self.random_client) + length = len(buf) ^ struct.unpack('H', rand_bytes(2))[0] % 1024 + 400 + data = data + (struct.pack(' 0xFF000000: + self.server_info.data.local_client_id = b'' + if not self.server_info.data.local_client_id: + self.server_info.data.local_client_id = rand_bytes(4) + logging.debug("local_client_id %s" % (binascii.hexlify(self.server_info.data.local_client_id),)) + self.server_info.data.connection_id = struct.unpack(' self.unit_len: + ret += self.pack_client_data(buf[:self.unit_len]) + buf = buf[self.unit_len:] + ret += self.pack_client_data(buf) + return ret + + def client_post_decrypt(self, buf): + if self.raw_trans: + return buf + self.recv_buf += buf + out_buf = b'' + while len(self.recv_buf) > 4: + mac_key = self.user_key + struct.pack('= 4096: + self.raw_trans = True + self.recv_buf = b'' + raise Exception('client_post_decrypt data error') + + if length + 4 > len(self.recv_buf): + break + + server_hash = hmac.new(mac_key, self.recv_buf[:length + 2], self.hashfunc).digest() + if server_hash[:2] != self.recv_buf[length + 2: length + 4]: + logging.info('%s: checksum error, data %s' + % (self.no_compatible_method, binascii.hexlify(self.recv_buf[:length]))) + self.raw_trans = True + self.recv_buf = b'' + raise Exception('client_post_decrypt data uncorrect checksum') + + pos = 2 + if data_len > 0 and rand_len > 0: + pos = 2 + out_buf += self.encryptor.decrypt(self.recv_buf[pos: data_len + pos]) + self.last_server_hash = server_hash + if self.recv_id == 1: + self.server_info.tcp_mss = struct.unpack(' self.unit_len: + ret += self.pack_server_data(buf[:self.unit_len]) + buf = buf[self.unit_len:] + ret += self.pack_server_data(buf) + return ret + + def server_post_decrypt(self, buf): + if self.raw_trans: + return (buf, False) + self.recv_buf += buf + out_buf = b'' + sendback = False + + if not self.has_recv_header: + if len(self.recv_buf) >= 12 or len(self.recv_buf) in [7, 8]: + recv_len = min(len(self.recv_buf), 12) + mac_key = self.server_info.recv_iv + self.server_info.key + md5data = hmac.new(mac_key, self.recv_buf[:4], self.hashfunc).digest() + if md5data[:recv_len - 4] != self.recv_buf[4:recv_len]: + return self.not_match_return(self.recv_buf) + + if len(self.recv_buf) < 12 + 24: + return (b'', False) + + self.last_client_hash = md5data + uid = struct.unpack(' self.max_time_dif: + logging.info('%s: wrong timestamp, time_dif %d, data %s' % ( + self.no_compatible_method, time_dif, binascii.hexlify(head) + )) + return self.not_match_return(self.recv_buf) + elif self.server_info.data.insert(self.user_id, client_id, connection_id): + self.has_recv_header = True + self.client_id = client_id + self.connection_id = connection_id + else: + logging.info('%s: auth fail, data %s' % (self.no_compatible_method, binascii.hexlify(out_buf))) + return self.not_match_return(self.recv_buf) + + self.on_recv_auth_data(utc_time) + self.encryptor = encrypt.Encryptor( + to_bytes(base64.b64encode(self.user_key)) + to_bytes(base64.b64encode(self.last_client_hash)), 'chacha20', self.last_server_hash[:8]) + self.encryptor.encrypt(b'') + self.encryptor.decrypt(self.last_client_hash[:8]) + self.recv_buf = self.recv_buf[36:] + self.has_recv_header = True + sendback = True + + while len(self.recv_buf) > 4: + mac_key = self.user_key + struct.pack('= 0xff00: + if data_len == 0xff00: + cmd_len += 2 + self.recv_tcp_mss = self.send_tcp_mss + recv_buf = recv_buf[2:] + data_len = struct.unpack('= 4096: + self.raw_trans = True + self.recv_buf = b'' + if self.recv_id == 1: + logging.info(self.no_compatible_method + ': over size') + return (b'E' * 2048, False) + else: + raise Exception('server_post_decrype data error') + + if length + 4 > len(recv_buf): + break + + client_hash = hmac.new(mac_key, self.recv_buf[:length + cmd_len + 2], self.hashfunc).digest() + if client_hash[:2] != self.recv_buf[length + cmd_len + 2: length + cmd_len + 4]: + logging.info('%s: checksum error, data %s' % ( + self.no_compatible_method, binascii.hexlify(self.recv_buf[:length + cmd_len]), + )) + self.raw_trans = True + self.recv_buf = b'' + if self.recv_id == 1: + return (b'E' * 2048, False) + else: + raise Exception('server_post_decrype data uncorrect checksum') + + self.recv_id = (self.recv_id + 1) & 0xFFFFFFFF + pos = 2 + if data_len > 0 and rand_len > 0: + pos = 2 + out_buf += self.encryptor.decrypt(recv_buf[pos: data_len + pos]) + self.last_client_hash = client_hash + self.recv_buf = recv_buf[length + 4:] + if data_len == 0: + sendback = True + + if out_buf: + self.server_info.data.update(self.user_id, self.client_id, self.connection_id) + return (out_buf, sendback) + + def client_udp_pre_encrypt(self, buf): + if self.user_key is None: + if b':' in to_bytes(self.server_info.protocol_param): + try: + items = to_bytes(self.server_info.protocol_param).split(':') + self.user_key = self.hashfunc(items[1]).digest() + self.user_id = struct.pack(' self.send_tcp_mss: + random.init_from_bin_len(last_hash, buf_size) + return random.next() % 521 + if buf_size >= 1440 or buf_size + self.server_info.overhead == self.send_tcp_mss: + return 0 + random.init_from_bin_len(last_hash, buf_size) + pos = bisect.bisect_left(self.data_size_list, buf_size + self.server_info.overhead) + final_pos = pos + random.next() % (len(self.data_size_list)) + if final_pos < len(self.data_size_list): + return self.data_size_list[final_pos] - buf_size - self.server_info.overhead + + pos = bisect.bisect_left(self.data_size_list2, buf_size + self.server_info.overhead) + final_pos = pos + random.next() % (len(self.data_size_list2)) + if final_pos < len(self.data_size_list2): + return self.data_size_list2[final_pos] - buf_size - self.server_info.overhead + if final_pos < pos + len(self.data_size_list2) - 1: + return 0 + + if buf_size > 1300: + return random.next() % 31 + if buf_size > 900: + return random.next() % 127 + if buf_size > 400: + return random.next() % 521 + return random.next() % 1021 + + + def recv_rnd_data_len(self, buf_size, last_hash, random): + if buf_size + self.server_info.overhead > self.recv_tcp_mss: + random.init_from_bin_len(last_hash, buf_size) + return random.next() % 521 + if buf_size >= 1440 or buf_size + self.server_info.overhead == self.send_tcp_mss: + return 0 + random.init_from_bin_len(last_hash, buf_size) + pos = bisect.bisect_left(self.data_size_list, buf_size + self.server_info.overhead) + final_pos = pos + random.next() % (len(self.data_size_list)) + if final_pos < len(self.data_size_list): + return self.data_size_list[final_pos] - buf_size - self.server_info.overhead + + pos = bisect.bisect_left(self.data_size_list2, buf_size + self.server_info.overhead) + final_pos = pos + random.next() % (len(self.data_size_list2)) + if final_pos < len(self.data_size_list2): + return self.data_size_list2[final_pos] - buf_size - self.server_info.overhead + if final_pos < pos + len(self.data_size_list2) - 1: + return 0 + + if buf_size > 1300: + return random.next() % 31 + if buf_size > 900: + return random.next() % 127 + if buf_size > 400: + return random.next() % 521 + return random.next() % 1021 + + diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 45a650e..1e11a6b 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -490,7 +490,6 @@ class TCPRelayHandler(object): return host_port[((hash_code & 0xffffffff) + addr) % len(host_port)] else: - host_port = [] for host in host_list: items_sum = common.to_str(host).rsplit('#', 1) items_match = common.to_str(items_sum[0]).rsplit(':', 1) diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index cebe974..0ab00b8 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -162,7 +162,7 @@ class UDPRelay(object): self.server_user_transfer_ul = {} self.server_user_transfer_dl = {} - if common.to_bytes(config['protocol']) in obfs.mu_protocol(): + if common.to_str(config['protocol']) in obfs.mu_protocol(): self._update_users(None, None) self.protocol_data = obfs.obfs(config['protocol']).init_data()