Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af5455c820 | ||
|
|
ccd1c0b45c |
@@ -1,6 +1,3 @@
|
||||
2.6.2 2015-01-03
|
||||
- Log client IP
|
||||
|
||||
2.6.1 2014-12-26
|
||||
- Fix a problem with TCP Fast Open on local side
|
||||
- Fix sometimes daemon_start returns wrong exit status
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Shadowsocks
|
||||
|
||||
Copyright (c) 2012-2015 clowwindy
|
||||
Copyright (c) 2014 clowwindy
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f:
|
||||
|
||||
setup(
|
||||
name="shadowsocks",
|
||||
version="2.6.2",
|
||||
version="2.6.1",
|
||||
license='MIT',
|
||||
description="A fast tunnel proxy that help you get through firewalls",
|
||||
author='clowwindy',
|
||||
|
||||
@@ -27,7 +27,7 @@ import logging
|
||||
from ctypes import CDLL, c_char_p, c_int, c_ulonglong, byref, \
|
||||
create_string_buffer, c_void_p
|
||||
|
||||
__all__ = ['ciphers']
|
||||
__all__ = ['ciphers', 'auths']
|
||||
|
||||
libsodium = None
|
||||
loaded = False
|
||||
@@ -62,6 +62,14 @@ def load_libsodium():
|
||||
c_char_p, c_ulonglong,
|
||||
c_char_p)
|
||||
|
||||
libsodium.crypto_onetimeauth.restype = c_int
|
||||
libsodium.crypto_onetimeauth.argtypes = (c_void_p, c_char_p,
|
||||
c_ulonglong, c_char_p)
|
||||
|
||||
libsodium.crypto_onetimeauth_verify.restype = c_int
|
||||
libsodium.crypto_onetimeauth_verify.argtypes = (c_char_p, c_char_p,
|
||||
c_ulonglong, c_char_p)
|
||||
|
||||
libsodium.sodium_init()
|
||||
|
||||
buf = create_string_buffer(buf_size)
|
||||
@@ -106,11 +114,32 @@ class Salsa20Crypto(object):
|
||||
return buf.raw[padding:padding + l]
|
||||
|
||||
|
||||
class Poly1305(object):
|
||||
@staticmethod
|
||||
def auth(method, key, data):
|
||||
if not loaded:
|
||||
load_libsodium()
|
||||
tag_buf = create_string_buffer(16)
|
||||
libsodium.crypto_onetimeauth(byref(tag_buf), data, len(data), key)
|
||||
return tag_buf.raw
|
||||
|
||||
@staticmethod
|
||||
def verify(method, key, data, tag):
|
||||
if not loaded:
|
||||
load_libsodium()
|
||||
r = libsodium.crypto_onetimeauth_verify(tag, data, len(data), key)
|
||||
return r == 0
|
||||
|
||||
|
||||
ciphers = {
|
||||
b'salsa20': (32, 8, Salsa20Crypto),
|
||||
b'chacha20': (32, 8, Salsa20Crypto),
|
||||
}
|
||||
|
||||
auths = {
|
||||
b'poly1305': (32, 16, Poly1305)
|
||||
}
|
||||
|
||||
|
||||
def test_salsa20():
|
||||
from shadowsocks.crypto import util
|
||||
|
||||
Executable → Regular
+43
-27
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2015 clowwindy
|
||||
# Copyright (c) 2014 clowwindy
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -24,28 +23,45 @@
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
with_statement
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import hmac
|
||||
import hashlib
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='See README')
|
||||
parser.add_argument('-c', '--count', default=3, type=int,
|
||||
help='with how many failure times it should be '
|
||||
'considered as an attack')
|
||||
config = parser.parse_args()
|
||||
ips = {}
|
||||
banned = set()
|
||||
for line in sys.stdin:
|
||||
if 'can not parse header when' in line:
|
||||
ip = line.split()[-1].split(':')[0]
|
||||
if ip not in ips:
|
||||
ips[ip] = 1
|
||||
print(ip)
|
||||
else:
|
||||
ips[ip] += 1
|
||||
if ip not in banned and ips[ip] >= config.count:
|
||||
banned.add(ip)
|
||||
cmd = 'iptables -A INPUT -s %s -j DROP' % ip
|
||||
print(cmd, file=sys.stderr)
|
||||
os.system(cmd)
|
||||
from shadowsocks import common
|
||||
|
||||
__all__ = ['auths']
|
||||
|
||||
|
||||
class HMAC(object):
|
||||
@staticmethod
|
||||
def auth(method, key, data):
|
||||
digest = common.to_str(method.replace(b'hmac-', b''))
|
||||
return hmac.new(key, data, getattr(hashlib, digest)).digest()
|
||||
|
||||
@staticmethod
|
||||
def verify(method, key, data, tag):
|
||||
digest = common.to_str(method.replace(b'hmac-', b''))
|
||||
t = hmac.new(key, data, getattr(hashlib, digest)).digest()
|
||||
if hasattr(hmac, 'compare_digest'):
|
||||
return hmac.compare_digest(t, tag)
|
||||
else:
|
||||
return _time_independent_equals(t, tag)
|
||||
|
||||
|
||||
# from tornado
|
||||
def _time_independent_equals(a, b):
|
||||
if len(a) != len(b):
|
||||
return False
|
||||
result = 0
|
||||
if type(a[0]) is int: # python3 byte strings
|
||||
for x, y in zip(a, b):
|
||||
result |= x ^ y
|
||||
else: # python2
|
||||
for x, y in zip(a, b):
|
||||
result |= ord(x) ^ ord(y)
|
||||
return result == 0
|
||||
|
||||
|
||||
auths = {
|
||||
b'hmac-md5': (32, 16, HMAC),
|
||||
b'hmac-sha256': (32, 32, HMAC),
|
||||
}
|
||||
+79
-23
@@ -29,17 +29,23 @@ import hashlib
|
||||
import logging
|
||||
|
||||
from shadowsocks.crypto import m2, rc4_md5, salsa20_ctr,\
|
||||
ctypes_openssl, ctypes_libsodium, table
|
||||
ctypes_openssl, ctypes_libsodium, table, hmac
|
||||
from shadowsocks import common
|
||||
|
||||
|
||||
method_supported = {}
|
||||
method_supported.update(rc4_md5.ciphers)
|
||||
method_supported.update(salsa20_ctr.ciphers)
|
||||
method_supported.update(ctypes_openssl.ciphers)
|
||||
method_supported.update(ctypes_libsodium.ciphers)
|
||||
ciphers_supported = {}
|
||||
ciphers_supported.update(rc4_md5.ciphers)
|
||||
ciphers_supported.update(salsa20_ctr.ciphers)
|
||||
ciphers_supported.update(ctypes_openssl.ciphers)
|
||||
ciphers_supported.update(ctypes_libsodium.ciphers)
|
||||
# let M2Crypto override ctypes_openssl
|
||||
method_supported.update(m2.ciphers)
|
||||
method_supported.update(table.ciphers)
|
||||
ciphers_supported.update(m2.ciphers)
|
||||
ciphers_supported.update(table.ciphers)
|
||||
|
||||
|
||||
auths_supported = {}
|
||||
auths_supported.update(hmac.auths)
|
||||
auths_supported.update(ctypes_libsodium.auths)
|
||||
|
||||
|
||||
def random_string(length):
|
||||
@@ -50,22 +56,14 @@ def random_string(length):
|
||||
return os.urandom(length)
|
||||
|
||||
|
||||
cached_keys = {}
|
||||
|
||||
|
||||
def try_cipher(key, method=None):
|
||||
def try_cipher(key, method=None, auth=None):
|
||||
Encryptor(key, method)
|
||||
auth_create(b'test', key, b'test', auth)
|
||||
|
||||
|
||||
def EVP_BytesToKey(password, key_len, iv_len):
|
||||
# equivalent to OpenSSL's EVP_BytesToKey() with count 1
|
||||
# so that we make the same key and iv as nodejs version
|
||||
if hasattr(password, 'encode'):
|
||||
password = password.encode('utf-8')
|
||||
cached_key = '%s-%d-%d' % (password, key_len, iv_len)
|
||||
r = cached_keys.get(cached_key, None)
|
||||
if r:
|
||||
return r
|
||||
m = []
|
||||
i = 0
|
||||
while len(b''.join(m)) < (key_len + iv_len):
|
||||
@@ -79,7 +77,6 @@ def EVP_BytesToKey(password, key_len, iv_len):
|
||||
ms = b''.join(m)
|
||||
key = ms[:key_len]
|
||||
iv = ms[key_len:key_len + iv_len]
|
||||
cached_keys[cached_key] = (key, iv)
|
||||
return key, iv
|
||||
|
||||
|
||||
@@ -102,15 +99,14 @@ class Encryptor(object):
|
||||
|
||||
def get_method_info(self, method):
|
||||
method = method.lower()
|
||||
m = method_supported.get(method)
|
||||
m = ciphers_supported.get(method)
|
||||
return m
|
||||
|
||||
def iv_len(self):
|
||||
return len(self.cipher_iv)
|
||||
|
||||
def get_cipher(self, password, method, op, iv):
|
||||
if hasattr(password, 'encode'):
|
||||
password = password.encode('utf-8')
|
||||
password = common.to_bytes(password)
|
||||
m = self._method_info
|
||||
if m[0] > 0:
|
||||
key, iv_ = EVP_BytesToKey(password, m[0], m[1])
|
||||
@@ -150,7 +146,8 @@ class Encryptor(object):
|
||||
def encrypt_all(password, method, op, data):
|
||||
result = []
|
||||
method = method.lower()
|
||||
(key_len, iv_len, m) = method_supported[method]
|
||||
password = common.to_bytes(password)
|
||||
(key_len, iv_len, m) = ciphers_supported[method]
|
||||
if key_len > 0:
|
||||
key, _ = EVP_BytesToKey(password, key_len, iv_len)
|
||||
else:
|
||||
@@ -166,6 +163,42 @@ def encrypt_all(password, method, op, data):
|
||||
return b''.join(result)
|
||||
|
||||
|
||||
def auth_create(data, password, iv, method):
|
||||
if method is None:
|
||||
return data
|
||||
# prepend hmac to data
|
||||
password = common.to_bytes(password)
|
||||
method = method.lower()
|
||||
method_info = auths_supported.get(method)
|
||||
if not method_info:
|
||||
logging.error('method %s not supported' % method)
|
||||
sys.exit(1)
|
||||
key_len, tag_len, m = method_info
|
||||
key, _ = EVP_BytesToKey(password + iv, key_len, 0)
|
||||
tag = m.auth(method, key, data)
|
||||
return tag + data
|
||||
|
||||
|
||||
def auth_open(data, password, iv, method):
|
||||
if not method:
|
||||
return data
|
||||
# verify hmac and remove the hmac or return None
|
||||
password = common.to_bytes(password)
|
||||
method = method.lower()
|
||||
method_info = auths_supported.get(method)
|
||||
if not method_info:
|
||||
logging.error('method %s not supported' % method)
|
||||
sys.exit(1)
|
||||
key_len, tag_len, m = method_info
|
||||
key, _ = EVP_BytesToKey(password + iv, key_len, 0)
|
||||
if len(data) <= tag_len:
|
||||
return None
|
||||
result = data[tag_len:]
|
||||
if not m.verify(method, key, result, data[:tag_len]):
|
||||
return None
|
||||
return result
|
||||
|
||||
|
||||
CIPHERS_TO_TEST = [
|
||||
b'aes-128-cfb',
|
||||
b'aes-256-cfb',
|
||||
@@ -175,6 +208,13 @@ CIPHERS_TO_TEST = [
|
||||
b'table',
|
||||
]
|
||||
|
||||
AUTHS_TO_TEST = [
|
||||
None,
|
||||
b'hmac-md5',
|
||||
b'hmac-sha256',
|
||||
b'poly1305',
|
||||
]
|
||||
|
||||
|
||||
def test_encryptor():
|
||||
from os import urandom
|
||||
@@ -198,6 +238,22 @@ def test_encrypt_all():
|
||||
assert plain == plain2
|
||||
|
||||
|
||||
def test_auth():
|
||||
from os import urandom
|
||||
plain = urandom(10240)
|
||||
for method in AUTHS_TO_TEST:
|
||||
logging.warn(method)
|
||||
boxed = auth_create(plain, b'key', b'iv', method)
|
||||
unboxed = auth_open(boxed, b'key', b'iv', method)
|
||||
assert plain == unboxed
|
||||
if method is not None:
|
||||
b = common.ord(boxed[0])
|
||||
b ^= 1
|
||||
attack = common.chr(b) + boxed[1:]
|
||||
assert auth_open(attack, b'key', b'iv', method) is None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_encrypt_all()
|
||||
test_encryptor()
|
||||
test_auth()
|
||||
|
||||
@@ -49,7 +49,7 @@ def main():
|
||||
|
||||
utils.print_shadowsocks()
|
||||
|
||||
encrypt.try_cipher(config['password'], config['method'])
|
||||
encrypt.try_cipher(config['password'], config['method'], config['auth'])
|
||||
|
||||
try:
|
||||
logging.info("starting local at %s:%d" %
|
||||
|
||||
@@ -57,7 +57,7 @@ def main():
|
||||
else:
|
||||
config['port_password'][str(server_port)] = config['password']
|
||||
|
||||
encrypt.try_cipher(config['password'], config['method'])
|
||||
encrypt.try_cipher(config['password'], config['method'], config['auth'])
|
||||
tcp_servers = []
|
||||
udp_servers = []
|
||||
dns_resolver = asyncdns.DNSResolver()
|
||||
|
||||
+5
-11
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2015 clowwindy
|
||||
# Copyright (c) 2014 clowwindy
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -121,7 +121,6 @@ class TCPRelayHandler(object):
|
||||
self._data_to_write_to_remote = []
|
||||
self._upstream_status = WAIT_STATUS_READING
|
||||
self._downstream_status = WAIT_STATUS_INIT
|
||||
self._client_address = local_sock.getpeername()[:2]
|
||||
self._remote_address = None
|
||||
if is_local:
|
||||
self._chosen_server = self._get_a_server()
|
||||
@@ -295,9 +294,8 @@ class TCPRelayHandler(object):
|
||||
if header_result is None:
|
||||
raise Exception('can not parse header')
|
||||
addrtype, remote_addr, remote_port, header_length = header_result
|
||||
logging.info('connecting %s:%d from %s:%d' %
|
||||
(common.to_str(remote_addr), remote_port,
|
||||
self._client_address[0], self._client_address[1]))
|
||||
logging.info('connecting %s:%d' % (common.to_str(remote_addr),
|
||||
remote_port))
|
||||
self._remote_address = (remote_addr, remote_port)
|
||||
# pause reading
|
||||
self._update_stream(STREAM_UP, WAIT_STATUS_WRITING)
|
||||
@@ -319,7 +317,7 @@ class TCPRelayHandler(object):
|
||||
self._dns_resolver.resolve(remote_addr,
|
||||
self._handle_dns_resolved)
|
||||
except Exception as e:
|
||||
self._log_error(e)
|
||||
logging.error(e)
|
||||
if self._config['verbose']:
|
||||
traceback.print_exc()
|
||||
# TODO use logging when debug completed
|
||||
@@ -340,7 +338,7 @@ class TCPRelayHandler(object):
|
||||
|
||||
def _handle_dns_resolved(self, result, error):
|
||||
if error:
|
||||
self._log_error(error)
|
||||
logging.error(error)
|
||||
self.destroy()
|
||||
return
|
||||
if result:
|
||||
@@ -509,10 +507,6 @@ class TCPRelayHandler(object):
|
||||
else:
|
||||
logging.warn('unknown socket')
|
||||
|
||||
def _log_error(self, e):
|
||||
logging.error('%s when handling connection from %s:%d' %
|
||||
(e, self._client_address[0], self._client_address[1]))
|
||||
|
||||
def destroy(self):
|
||||
# destroy the handler and release any resources
|
||||
# promises:
|
||||
|
||||
@@ -173,6 +173,7 @@ def get_config(is_local):
|
||||
|
||||
config['password'] = config.get('password', '')
|
||||
config['method'] = config.get('method', 'aes-256-cfb')
|
||||
config['auth'] = config.get('auth', None)
|
||||
config['port_password'] = config.get('port_password', None)
|
||||
config['timeout'] = int(config.get('timeout', 300))
|
||||
config['fast_open'] = config.get('fast_open', False)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
Useful Tools
|
||||
===========
|
||||
|
||||
autoban.py
|
||||
----------
|
||||
|
||||
Automatically ban IPs that try to brute force crack the server.
|
||||
|
||||
See https://github.com/shadowsocks/shadowsocks/wiki/Ban-Brute-Force-Crackers
|
||||
Reference in New Issue
Block a user