Merge pull request #1 from shadowsocks/master

Update from origin
This commit is contained in:
何清宝 2017-06-10 15:10:51 +08:00 committed by GitHub
commit 32ee84f138
64 changed files with 2471 additions and 512 deletions

14
.gitignore vendored
View file

@ -29,3 +29,17 @@ htmlcov
.DS_Store .DS_Store
.idea .idea
tags
#Emacs
.#*
venv/
# VS-code
.vscode/
# Pycharm
.idea
#ss
config.json

View file

@ -16,6 +16,8 @@ before_install:
- pip install pep8 pyflakes nose coverage PySocks - pip install pep8 pyflakes nose coverage PySocks
- sudo tests/socksify/install.sh - sudo tests/socksify/install.sh
- sudo tests/libsodium/install.sh - sudo tests/libsodium/install.sh
- sudo tests/libmbedtls/install.sh
- sudo tests/libopenssl/install.sh
- sudo tests/setup_tc.sh - sudo tests/setup_tc.sh
script: script:
- tests/jenkins.sh - tests/jenkins.sh

View file

@ -3,7 +3,6 @@ shadowsocks
[![PyPI version]][PyPI] [![PyPI version]][PyPI]
[![Build Status]][Travis CI] [![Build Status]][Travis CI]
[![Coverage Status]][Coverage]
A fast tunnel proxy that helps you bypass firewalls. A fast tunnel proxy that helps you bypass firewalls.
@ -22,16 +21,16 @@ Server
Debian / Ubuntu: Debian / Ubuntu:
apt-get install python-pip apt-get install python-pip
pip install shadowsocks pip install git+https://github.com/shadowsocks/shadowsocks.git@master
CentOS: CentOS:
yum install python-setuptools && easy_install pip yum install python-setuptools && easy_install pip
pip install shadowsocks pip install git+https://github.com/shadowsocks/shadowsocks.git@master
Windows: Windows:
See [Install Server on Windows] See [Install Shadowsocks Server on Windows](https://github.com/shadowsocks/shadowsocks/wiki/Install-Shadowsocks-Server-on-Windows).
### Usage ### Usage
@ -52,10 +51,19 @@ To check the log:
Check all the options via `-h`. You can also use a [Configuration] file Check all the options via `-h`. You can also use a [Configuration] file
instead. instead.
### Usage with Config File
[Create configeration file and run](https://github.com/shadowsocks/shadowsocks/wiki/Configuration-via-Config-File)
To start:
ssserver -c /etc/shadowsocks.json
Documentation Documentation
------------- -------------
You can find all the documentation in the [Wiki]. You can find all the documentation in the [Wiki](https://github.com/shadowsocks/shadowsocks/wiki).
License License
------- -------
@ -69,8 +77,6 @@ Apache License
[Build Status]: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat [Build Status]: https://img.shields.io/travis/shadowsocks/shadowsocks/master.svg?style=flat
[Coverage Status]: https://jenkins.shadowvpn.org/result/shadowsocks
[Coverage]: https://jenkins.shadowvpn.org/job/Shadowsocks/ws/PYENV/py34/label/linux/htmlcov/index.html
[PyPI]: https://pypi.python.org/pypi/shadowsocks [PyPI]: https://pypi.python.org/pypi/shadowsocks
[PyPI version]: https://img.shields.io/pypi/v/shadowsocks.svg?style=flat [PyPI version]: https://img.shields.io/pypi/v/shadowsocks.svg?style=flat
[Travis CI]: https://travis-ci.org/shadowsocks/shadowsocks [Travis CI]: https://travis-ci.org/shadowsocks/shadowsocks

17
config.json.example Normal file
View file

@ -0,0 +1,17 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1080,
"password":"password",
"timeout":600,
"method":"aes-256-cfb",
"local_address":"127.0.0.1",
"fast_open":false,
"tunnel_remote":"8.8.8.8",
"dns_server":["8.8.8.8", "8.8.4.4"],
"tunnel_remote_port":53,
"tunnel_port":53,
"libopenssl":"C:\\Program Files\\Git\\mingw64\\bin\\libeay32.dll",
"libsodium":"/usr/local/lib/libsodium.so",
"libmbedtls":"/usr/local/lib/libmbedcrypto.2.4.0.dylib"
}

23
debian/changelog vendored
View file

@ -1,3 +1,26 @@
shadowsocks (2.9.0-2) unstable; urgency=medium
[ Shell.Xu ]
* Fix compatible issue (Closes: #845016)
-- Shell.Xu <shell909090@gmail.com> Sun, 20 Nov 2016 14:33:31 +0800
shadowsocks (2.9.0-1) unstable; urgency=medium
[ Shell Xu ]
* Upstream update (Closes: #824640)
* Remove reference not exists (Closes: #810688)
[ Thomas Goirand ]
* Added lsb-base as Depends:.
* Removed Pre-Depends: dpkg (>= 1.15.6~).
* Ran wrap-and-sort -t -a.
* Fixed VCS URLs to use https.
* Removed useless obsolete version of python-all build-depends.
* Fixed debian/copyright ordering.
-- Shell.Xu <shell909090@gmail.com> Sat, 01 Oct 2016 16:14:47 +0800
shadowsocks (2.1.0-1) unstable; urgency=low shadowsocks (2.1.0-1) unstable; urgency=low
* Initial release (Closes: #758900) * Initial release (Closes: #758900)

21
debian/control vendored
View file

@ -2,18 +2,23 @@ Source: shadowsocks
Section: python Section: python
Priority: extra Priority: extra
Maintainer: Shell.Xu <shell909090@gmail.com> Maintainer: Shell.Xu <shell909090@gmail.com>
Build-Depends: debhelper (>= 8), python-all (>= 2.6.6-3~), python-setuptools Build-Depends: debhelper (>= 8),
Standards-Version: 3.9.5 python-all,
Homepage: https://github.com/clowwindy/shadowsocks python-setuptools,
Vcs-Git: git://github.com/shell909090/shadowsocks.git Standards-Version: 3.9.8
Vcs-Browser: http://github.com/shell909090/shadowsocks Homepage: https://github.com/shadowsocks/shadowsocks
Vcs-Git: https://github.com/shell909090/shadowsocks.git
Vcs-Browser: https://github.com/shell909090/shadowsocks
Package: shadowsocks Package: shadowsocks
Architecture: all Architecture: all
Pre-Depends: dpkg (>= 1.15.6~) Depends: lsb-base (>= 3.0-6),
Depends: ${misc:Depends}, ${python:Depends}, python-pkg-resources, python-m2crypto python-m2crypto,
python-pkg-resources,
${misc:Depends},
${python:Depends},
Description: Fast tunnel proxy that helps you bypass firewalls Description: Fast tunnel proxy that helps you bypass firewalls
A secure socks5 proxy, designed to protect your Internet traffic. A secure socks5 proxy, designed to protect your Internet traffic.
. .
This package contain local and server part of shadowsocks, a fast, This package contain local and server part of shadowsocks, a fast,
powerful tunnel proxy to bypass firewalls. powerful tunnel proxy to bypass firewalls.

43
debian/copyright vendored
View file

@ -1,30 +1,27 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: shadowsocks Upstream-Name: shadowsocks
Source: https://github.com/clowwindy/shadowsocks Source: https://github.com/shadowsocks/shadowsocks
Files: debian/*
Copyright: 2014 Shell.Xu <shell909090@gmail.com>
License: Expat
Files: * Files: *
Copyright: 2014 clowwindy <clowwindy42@gmail.com> Copyright: 2014 clowwindy <clowwindy42@gmail.com>
License: Expat License: Apache-2.0
License: Expat Files: debian/*
Permission is hereby granted, free of charge, to any person obtaining a copy Copyright: 2016 Shell.Xu <shell909090@gmail.com>
of this software and associated documentation files (the "Software"), to deal License: Apache-2.0
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell License: Apache-2.0
copies of the Software, and to permit persons to whom the Software is Licensed under the Apache License, Version 2.0 (the "License");
furnished to do so, subject to the following conditions: you may not use this file except in compliance with the License.
. You may obtain a copy of the License at
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
. .
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR http://www.apache.org/licenses/LICENSE-2.0
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, .
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE Unless required by applicable law or agreed to in writing, software
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER distributed under the License is distributed on an "AS IS" BASIS,
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE See the License for the specific language governing permissions and
SOFTWARE. limitations under the License.
.
On Debian systems, the complete text of the Apache License 2.0 can
be found in "/usr/share/common-licenses/Apache-2.0"

2
debian/install vendored
View file

@ -1 +1 @@
debian/config.json etc/shadowsocks/ debian/config.json etc/shadowsocks/

View file

@ -1,2 +1,2 @@
debian/sslocal.1 debian/sslocal.1
debian/ssserver.1 debian/ssserver.1

4
debian/sslocal.1 vendored
View file

@ -55,5 +55,5 @@ Quiet mode, only show warnings/errors.
The programs are documented fully by The programs are documented fully by
.IR "Shell Xu <shell909090@gmail.com>" .IR "Shell Xu <shell909090@gmail.com>"
and and
.IR "Clowwindy <clowwindy42@gmail.com>", .IR "Clowwindy <clowwindy42@gmail.com>"
available via the Info system. .

4
debian/ssserver.1 vendored
View file

@ -55,5 +55,5 @@ Quiet mode, only show warnings/errors.
The programs are documented fully by The programs are documented fully by
.IR "Shell Xu <shell909090@gmail.com>" .IR "Shell Xu <shell909090@gmail.com>"
and and
.IR "Clowwindy <clowwindy42@gmail.com>", .IR "Clowwindy <clowwindy42@gmail.com>"
available via the Info system. .

View file

@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f:
setup( setup(
name="shadowsocks", name="shadowsocks",
version="2.9.0", version="3.0.0",
license='http://www.apache.org/licenses/LICENSE-2.0', license='http://www.apache.org/licenses/LICENSE-2.0',
description="A fast tunnel proxy that help you get through firewalls", description="A fast tunnel proxy that help you get through firewalls",
author='clowwindy', author='clowwindy',

View file

@ -29,7 +29,7 @@ from shadowsocks import common, lru_cache, eventloop, shell
CACHE_SWEEP_INTERVAL = 30 CACHE_SWEEP_INTERVAL = 30
VALID_HOSTNAME = re.compile(br"(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE) VALID_HOSTNAME = re.compile(br"(?!-)[A-Z\d\-_]{1,63}(?<!-)$", re.IGNORECASE)
common.patch_socket() common.patch_socket()

View file

@ -146,6 +146,7 @@ ADDRTYPE_MASK = 0xF
def pack_addr(address): def pack_addr(address):
address_str = to_str(address) address_str = to_str(address)
address = to_bytes(address)
for family in (socket.AF_INET, socket.AF_INET6): for family in (socket.AF_INET, socket.AF_INET6):
try: try:
r = socket.inet_pton(family, address_str) r = socket.inet_pton(family, address_str)
@ -160,6 +161,13 @@ def pack_addr(address):
return b'\x03' + chr(len(address)) + address return b'\x03' + chr(len(address)) + address
# add ss header
def add_header(address, port, data=b''):
_data = b''
_data = pack_addr(address) + struct.pack('>H', port) + data
return _data
def parse_header(data): def parse_header(data):
addrtype = ord(data[0]) addrtype = ord(data[0])
dest_addr = None dest_addr = None

343
shadowsocks/crypto/aead.py Normal file
View file

@ -0,0 +1,343 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Void Copyright NO ONE
#
# Void License
#
# The code belongs to no one. Do whatever you want.
# Forget about boring open source license.
#
# AEAD cipher for shadowsocks
#
from __future__ import absolute_import, division, print_function, \
with_statement
from ctypes import c_int, create_string_buffer, byref, c_void_p
import hashlib
from struct import pack, unpack
from shadowsocks.crypto import util
from shadowsocks.crypto import hkdf
from shadowsocks.common import ord, chr
EVP_CTRL_GCM_SET_IVLEN = 0x9
EVP_CTRL_GCM_GET_TAG = 0x10
EVP_CTRL_GCM_SET_TAG = 0x11
EVP_CTRL_CCM_SET_IVLEN = EVP_CTRL_GCM_SET_IVLEN
EVP_CTRL_CCM_GET_TAG = EVP_CTRL_GCM_GET_TAG
EVP_CTRL_CCM_SET_TAG = EVP_CTRL_GCM_SET_TAG
EVP_CTRL_AEAD_SET_IVLEN = EVP_CTRL_GCM_SET_IVLEN
EVP_CTRL_AEAD_SET_TAG = EVP_CTRL_GCM_SET_TAG
EVP_CTRL_AEAD_GET_TAG = EVP_CTRL_GCM_GET_TAG
AEAD_MSG_LEN_UNKNOWN = 0
AEAD_CHUNK_SIZE_LEN = 2
AEAD_CHUNK_SIZE_MASK = 0x3FFF
CIPHER_NONCE_LEN = {
'aes-128-gcm': 12,
'aes-192-gcm': 12,
'aes-256-gcm': 12,
'aes-128-ocb': 12, # requires openssl 1.1
'aes-192-ocb': 12,
'aes-256-ocb': 12,
'chacha20-poly1305': 12,
'chacha20-ietf-poly1305': 12,
'xchacha20-ietf-poly1305': 24,
'sodium:aes-256-gcm': 12,
}
CIPHER_TAG_LEN = {
'aes-128-gcm': 16,
'aes-192-gcm': 16,
'aes-256-gcm': 16,
'aes-128-ocb': 16, # requires openssl 1.1
'aes-192-ocb': 16,
'aes-256-ocb': 16,
'chacha20-poly1305': 16,
'chacha20-ietf-poly1305': 16,
'xchacha20-ietf-poly1305': 16,
'sodium:aes-256-gcm': 16,
}
SUBKEY_INFO = b"ss-subkey"
libsodium = None
sodium_loaded = False
def load_sodium(path=None):
"""
Load libsodium helpers for nonce increment
:return: None
"""
global libsodium, sodium_loaded
libsodium = util.find_library('sodium', 'sodium_increment',
'libsodium', path)
if libsodium is None:
print('load libsodium failed with path %s' % path)
return
if libsodium.sodium_init() < 0:
libsodium = None
print('sodium init failed')
return
libsodium.sodium_increment.restype = c_void_p
libsodium.sodium_increment.argtypes = (
c_void_p, c_int
)
sodium_loaded = True
return
def nonce_increment(nonce, nlen):
"""
Increase nonce by 1 in little endian
From libsodium sodium_increment():
for (; i < nlen; i++) {
c += (uint_fast16_t) n[i];
n[i] = (unsigned char) c;
c >>= 8;
}
:param nonce: string_buffer nonce
:param nlen: nonce length
:return: nonce plus by 1
"""
c = 1
i = 0
# n = create_string_buffer(nlen)
while i < nlen:
c += ord(nonce[i])
nonce[i] = chr(c & 0xFF)
c >>= 8
i += 1
return # n.raw
class AeadCryptoBase(object):
"""
Handles basic aead process of shadowsocks protocol
TCP Chunk (after encryption, *ciphertext*)
+--------------+---------------+--------------+------------+
| *DataLen* | DataLen_TAG | *Data* | Data_TAG |
+--------------+---------------+--------------+------------+
| 2 | Fixed | Variable | Fixed |
+--------------+---------------+--------------+------------+
UDP (after encryption, *ciphertext*)
+--------+-----------+-----------+
| NONCE | *Data* | Data_TAG |
+-------+-----------+-----------+
| Fixed | Variable | Fixed |
+--------+-----------+-----------+
"""
def __init__(self, cipher_name, key, iv, op, crypto_path=None):
self._op = int(op)
self._salt = iv
self._nlen = CIPHER_NONCE_LEN[cipher_name]
self._nonce = create_string_buffer(self._nlen)
self._tlen = CIPHER_TAG_LEN[cipher_name]
crypto_hkdf = hkdf.Hkdf(iv, key, algorithm=hashlib.sha1)
self._skey = crypto_hkdf.expand(info=SUBKEY_INFO, length=len(key))
# _chunk['mlen']:
# -1, waiting data len header
# n, n > 0, waiting data
self._chunk = {'mlen': AEAD_MSG_LEN_UNKNOWN, 'data': b''}
self.encrypt_once = self.aead_encrypt
self.decrypt_once = self.aead_decrypt
# load libsodium for nonce increment
if not sodium_loaded:
crypto_path = dict(crypto_path) if crypto_path else dict()
path = crypto_path.get('sodium', None)
load_sodium(path)
def nonce_increment(self):
"""
AEAD ciphers need nonce to be unique per key
TODO: cache and check unique
:return: None
"""
global libsodium, sodium_loaded
if sodium_loaded:
libsodium.sodium_increment(byref(self._nonce), c_int(self._nlen))
else:
nonce_increment(self._nonce, self._nlen)
# print("".join("%02x" % ord(b) for b in self._nonce))
def cipher_ctx_init(self):
"""
Increase nonce to make it unique for the same key
:return: None
"""
self.nonce_increment()
def aead_encrypt(self, data):
"""
Encrypt data with authenticate tag
:param data: plain text
:return: str [payload][tag] cipher text with tag
"""
raise Exception("Must implement aead_encrypt method")
def encrypt_chunk(self, data):
"""
Encrypt a chunk for TCP chunks
:param data: str
:return: str [len][tag][payload][tag]
"""
plen = len(data)
# l = AEAD_CHUNK_SIZE_LEN + plen + self._tlen * 2
# network byte order
ctext = [self.aead_encrypt(pack("!H", plen & AEAD_CHUNK_SIZE_MASK))]
if len(ctext[0]) != AEAD_CHUNK_SIZE_LEN + self._tlen:
self.clean()
raise Exception("size length invalid")
ctext.append(self.aead_encrypt(data))
if len(ctext[1]) != plen + self._tlen:
self.clean()
raise Exception("data length invalid")
return b''.join(ctext)
def encrypt(self, data):
"""
Encrypt data, for TCP divided into chunks
For UDP data, call aead_encrypt instead
:param data: str data bytes
:return: str encrypted data
"""
plen = len(data)
if plen <= AEAD_CHUNK_SIZE_MASK:
ctext = self.encrypt_chunk(data)
return ctext
ctext = []
while plen > 0:
mlen = plen if plen < AEAD_CHUNK_SIZE_MASK \
else AEAD_CHUNK_SIZE_MASK
c = self.encrypt_chunk(data[:mlen])
ctext.append(c)
data = data[mlen:]
plen -= mlen
return b''.join(ctext)
def aead_decrypt(self, data):
"""
Decrypt data and authenticate tag
:param data: str [len][tag][payload][tag] cipher text with tag
:return: str plain text
"""
raise Exception("Must implement aead_decrypt method")
def decrypt_chunk_size(self, data):
"""
Decrypt chunk size
:param data: str [size][tag] encrypted chunk payload len
:return: (int, str) msg length and remaining encrypted data
"""
if self._chunk['mlen'] > 0:
return self._chunk['mlen'], data
data = self._chunk['data'] + data
self._chunk['data'] = b""
hlen = AEAD_CHUNK_SIZE_LEN + self._tlen
if hlen > len(data):
self._chunk['data'] = data
return 0, b""
plen = self.aead_decrypt(data[:hlen])
plen, = unpack("!H", plen)
if plen & AEAD_CHUNK_SIZE_MASK != plen or plen <= 0:
self.clean()
raise Exception('Invalid message length')
return plen, data[hlen:]
def decrypt_chunk_payload(self, plen, data):
"""
Decrypted encrypted msg payload
:param plen: int payload length
:param data: str [payload][tag][[len][tag]....] encrypted data
:return: (str, str) plain text and remaining encrypted data
"""
data = self._chunk['data'] + data
if len(data) < plen + self._tlen:
self._chunk['mlen'] = plen
self._chunk['data'] = data
return b"", b""
self._chunk['mlen'] = AEAD_MSG_LEN_UNKNOWN
self._chunk['data'] = b""
plaintext = self.aead_decrypt(data[:plen + self._tlen])
if len(plaintext) != plen:
self.clean()
raise Exception("plaintext length invalid")
return plaintext, data[plen + self._tlen:]
def decrypt_chunk(self, data):
"""
Decrypt a TCP chunk
:param data: str [len][tag][payload][tag][[len][tag]...] encrypted msg
:return: (str, str) decrypted msg and remaining encrypted data
"""
plen, data = self.decrypt_chunk_size(data)
if plen <= 0:
return b"", b""
return self.decrypt_chunk_payload(plen, data)
def decrypt(self, data):
"""
Decrypt data for TCP data divided into chunks
For UDP data, call aead_decrypt instead
:param data: str
:return: str
"""
ptext = []
pnext, left = self.decrypt_chunk(data)
ptext.append(pnext)
while len(left) > 0:
pnext, left = self.decrypt_chunk(left)
ptext.append(pnext)
return b''.join(ptext)
def test_nonce_increment():
buf = create_string_buffer(12)
print("".join("%02x" % ord(b) for b in buf))
nonce_increment(buf, 12)
nonce_increment(buf, 12)
nonce_increment(buf, 12)
nonce_increment(buf, 12)
print("".join("%02x" % ord(b) for b in buf))
for i in range(256):
nonce_increment(buf, 12)
print("".join("%02x" % ord(b) for b in buf))
if __name__ == '__main__':
load_sodium()
test_nonce_increment()

View file

@ -0,0 +1,98 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Void Copyright NO ONE
#
# Void License
#
# The code belongs to no one. Do whatever you want.
# Forget about boring open source license.
#
# HKDF for AEAD ciphers
#
from __future__ import division
import hmac
import hashlib
import sys
if sys.version_info[0] == 3:
def buffer(x):
return x
def hkdf_extract(salt, input_key_material, algorithm=hashlib.sha256):
"""
Extract a pseudorandom key suitable for use with hkdf_expand
from the input_key_material and a salt using HMAC with the
provided hash (default SHA-256).
salt should be a random, application-specific byte string. If
salt is None or the empty string, an all-zeros string of the same
length as the hash's block size will be used instead per the RFC.
See the HKDF draft RFC and paper for usage notes.
"""
hash_len = algorithm().digest_size
if salt is None or len(salt) == 0:
salt = bytearray((0,) * hash_len)
return hmac.new(bytes(salt), buffer(input_key_material), algorithm)\
.digest()
def hkdf_expand(pseudo_random_key, info=b"", length=32,
algorithm=hashlib.sha256):
"""
Expand `pseudo_random_key` and `info` into a key of length `bytes` using
HKDF's expand function based on HMAC with the provided hash (default
SHA-256). See the HKDF draft RFC and paper for usage notes.
"""
hash_len = algorithm().digest_size
length = int(length)
if length > 255 * hash_len:
raise Exception("Cannot expand to more than 255 * %d = %d "
"bytes using the specified hash function" %
(hash_len, 255 * hash_len))
blocks_needed = length // hash_len \
+ (0 if length % hash_len == 0 else 1) # ceil
okm = b""
output_block = b""
for counter in range(blocks_needed):
output_block = hmac.new(
pseudo_random_key,
buffer(output_block + info + bytearray((counter + 1,))),
algorithm
).digest()
okm += output_block
return okm[:length]
class Hkdf(object):
"""
Wrapper class for HKDF extract and expand functions
"""
def __init__(self, salt, input_key_material, algorithm=hashlib.sha256):
"""
Extract a pseudorandom key from `salt` and `input_key_material`
arguments.
See the HKDF draft RFC for guidance on setting these values.
The constructor optionally takes a `algorithm` argument defining
the hash function use, defaulting to hashlib.sha256.
"""
self._hash = algorithm
self._prk = hkdf_extract(salt, input_key_material, self._hash)
def expand(self, info, length=32):
"""
Generate output key material based on an `info` value
Arguments:
- info - context to generate the OKM
- length - length in bytes of the key to generate
See the HKDF draft RFC for guidance.
"""
return hkdf_expand(self._prk, info, length, self._hash)

View file

@ -0,0 +1,481 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Void Copyright NO ONE
#
# Void License
#
# The code belongs to no one. Do whatever you want.
# Forget about boring open source license.
#
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
from __future__ import absolute_import, division, print_function, \
with_statement
from ctypes import c_char_p, c_int, c_size_t, byref,\
create_string_buffer, c_void_p
from shadowsocks import common
from shadowsocks.crypto import util
from shadowsocks.crypto.aead import AeadCryptoBase
__all__ = ['ciphers']
libmbedtls = None
loaded = False
buf = None
buf_size = 2048
CIPHER_ENC_UNCHANGED = -1
# define MAX_KEY_LENGTH 64
# define MAX_NONCE_LENGTH 32
# typedef struct {
# uint32_t init;
# uint64_t counter;
# cipher_evp_t *evp;
# cipher_t *cipher;
# buffer_t *chunk;
# uint8_t salt[MAX_KEY_LENGTH];
# uint8_t skey[MAX_KEY_LENGTH];
# uint8_t nonce[MAX_NONCE_LENGTH];
# } cipher_ctx_t;
#
# sizeof(cipher_ctx_t) = 196
CIPHER_CTX_SIZE = 256
def load_mbedtls(crypto_path=None):
global loaded, libmbedtls, buf
crypto_path = dict(crypto_path) if crypto_path else dict()
path = crypto_path.get('mbedtls', None)
libmbedtls = util.find_library('mbedcrypto',
'mbedtls_cipher_init',
'libmbedcrypto', path)
if libmbedtls is None:
raise Exception('libmbedcrypto(mbedtls) not found with path %s'
% path)
libmbedtls.mbedtls_cipher_init.restype = None
libmbedtls.mbedtls_cipher_free.restype = None
libmbedtls.mbedtls_cipher_info_from_string.restype = c_void_p
libmbedtls.mbedtls_cipher_info_from_string.argtypes = (c_char_p,)
libmbedtls.mbedtls_cipher_setup.restype = c_int # 0 on success
libmbedtls.mbedtls_cipher_setup.argtypes = (c_void_p, c_void_p)
libmbedtls.mbedtls_cipher_setkey.restype = c_int # 0 on success
libmbedtls.mbedtls_cipher_setkey.argtypes = (
c_void_p, # ctx
c_char_p, # key
c_int, # key_bitlen, not bytes
c_int # op: 1 enc, 0 dec, -1 none
)
libmbedtls.mbedtls_cipher_set_iv.restype = c_int # 0 on success
libmbedtls.mbedtls_cipher_set_iv.argtypes = (
c_void_p, # ctx
c_char_p, # iv
c_size_t # iv_len
)
libmbedtls.mbedtls_cipher_reset.restype = c_int # 0 on success
libmbedtls.mbedtls_cipher_reset.argtypes = (c_void_p,) # ctx
if hasattr(libmbedtls, 'mbedtls_cipher_update_ad'):
libmbedtls.mbedtls_cipher_update_ad.restype = c_int # 0 on success
libmbedtls.mbedtls_cipher_update_ad.argtypes = (
c_void_p, # ctx
c_char_p, # ad
c_size_t # ad_len
)
libmbedtls.mbedtls_cipher_update.restype = c_int # 0 on success
libmbedtls.mbedtls_cipher_update.argtypes = (
c_void_p, # ctx
c_char_p, # input
c_size_t, # ilen, must be multiple of block size except last one
c_void_p, # *output
c_void_p # *olen
)
libmbedtls.mbedtls_cipher_finish.restype = c_int # 0 on success
libmbedtls.mbedtls_cipher_finish.argtypes = (
c_void_p, # ctx
c_void_p, # *output
c_void_p # *olen
)
if hasattr(libmbedtls, 'mbedtls_cipher_write_tag'):
libmbedtls.mbedtls_cipher_write_tag.restype = c_int # 0 on success
libmbedtls.mbedtls_cipher_write_tag.argtypes = (
c_void_p, # ctx
c_void_p, # *tag
c_size_t # tag_len
)
libmbedtls.mbedtls_cipher_check_tag.restype = c_int # 0 on success
libmbedtls.mbedtls_cipher_check_tag.argtypes = (
c_void_p, # ctx
c_char_p, # tag
c_size_t # tag_len
)
libmbedtls.mbedtls_cipher_crypt.restype = c_int # 0 on success
libmbedtls.mbedtls_cipher_crypt.argtypes = (
c_void_p, # ctx
c_char_p, # iv
c_size_t, # iv_len, = 0 if iv = NULL
c_char_p, # input
c_size_t, # ilen
c_void_p, # *output, no less than ilen + block_size
c_void_p # *olen
)
if hasattr(libmbedtls, 'mbedtls_cipher_auth_encrypt'):
libmbedtls.mbedtls_cipher_auth_encrypt.restype = c_int # 0 on success
libmbedtls.mbedtls_cipher_auth_encrypt.argtypes = (
c_void_p, # ctx
c_char_p, # iv
c_size_t, # iv_len
c_char_p, # ad
c_size_t, # ad_len
c_char_p, # input
c_size_t, # ilen
c_void_p, # *output, no less than ilen + block_size
c_void_p, # *olen
c_void_p, # *tag
c_size_t # tag_len
)
libmbedtls.mbedtls_cipher_auth_decrypt.restype = c_int # 0 on success
libmbedtls.mbedtls_cipher_auth_decrypt.argtypes = (
c_void_p, # ctx
c_char_p, # iv
c_size_t, # iv_len
c_char_p, # ad
c_size_t, # ad_len
c_char_p, # input
c_size_t, # ilen
c_void_p, # *output, no less than ilen + block_size
c_void_p, # *olen
c_char_p, # tag
c_size_t, # tag_len
)
buf = create_string_buffer(buf_size)
loaded = True
class MbedTLSCryptoBase(object):
"""
MbedTLS crypto base class
"""
def __init__(self, cipher_name, crypto_path=None):
global loaded
self._ctx = create_string_buffer(b'\0' * CIPHER_CTX_SIZE)
self._cipher = None
if not loaded:
load_mbedtls(crypto_path)
cipher_name = common.to_bytes(cipher_name.upper())
cipher = libmbedtls.mbedtls_cipher_info_from_string(cipher_name)
if not cipher:
raise Exception('cipher %s not found in libmbedtls' % cipher_name)
libmbedtls.mbedtls_cipher_init(byref(self._ctx))
if libmbedtls.mbedtls_cipher_setup(byref(self._ctx), cipher):
raise Exception('can not setup cipher')
self._cipher = cipher
self.encrypt_once = self.update
self.decrypt_once = self.update
def update(self, data):
"""
Encrypt/decrypt data
:param data: str
:return: str
"""
global buf_size, buf
cipher_out_len = c_size_t(0)
l = len(data)
if buf_size < l:
buf_size = l * 2
buf = create_string_buffer(buf_size)
libmbedtls.mbedtls_cipher_update(
byref(self._ctx),
c_char_p(data), c_size_t(l),
byref(buf), byref(cipher_out_len)
)
# buf is copied to a str object when we access buf.raw
return buf.raw[:cipher_out_len.value]
def __del__(self):
self.clean()
def clean(self):
if self._ctx:
libmbedtls.mbedtls_cipher_free(byref(self._ctx))
class MbedTLSAeadCrypto(MbedTLSCryptoBase, AeadCryptoBase):
"""
Implement mbedtls Aead mode: gcm
"""
def __init__(self, cipher_name, key, iv, op, crypto_path=None):
if cipher_name[:len('mbedtls:')] == 'mbedtls:':
cipher_name = cipher_name[len('mbedtls:'):]
MbedTLSCryptoBase.__init__(self, cipher_name, crypto_path)
AeadCryptoBase.__init__(self, cipher_name, key, iv, op, crypto_path)
key_ptr = c_char_p(self._skey)
r = libmbedtls.mbedtls_cipher_setkey(
byref(self._ctx),
key_ptr, c_int(len(key) * 8),
c_int(op)
)
if r:
self.clean()
raise Exception('can not initialize cipher context')
r = libmbedtls.mbedtls_cipher_reset(byref(self._ctx))
if r:
self.clean()
raise Exception('can not finish preparation of mbed TLS '
'cipher context')
def cipher_ctx_init(self):
"""
Nonce + 1
:return: None
"""
AeadCryptoBase.nonce_increment(self)
def set_tag(self, tag):
"""
Set tag before decrypt any data (update)
:param tag: authenticated tag
:return: None
"""
tag_len = self._tlen
r = libmbedtls.mbedtls_cipher_check_tag(
byref(self._ctx),
c_char_p(tag), c_size_t(tag_len)
)
if not r:
raise Exception('Set tag failed')
def get_tag(self):
"""
Get authenticated tag, called after EVP_CipherFinal_ex
:return: str
"""
tag_len = self._tlen
tag_buf = create_string_buffer(tag_len)
r = libmbedtls.mbedtls_cipher_write_tag(
byref(self._ctx),
byref(tag_buf), c_size_t(tag_len)
)
if not r:
raise Exception('Get tag failed')
return tag_buf.raw[:tag_len]
def final(self):
"""
Finish encrypt/decrypt a chunk (<= 0x3FFF)
:return: str
"""
global buf_size, buf
cipher_out_len = c_size_t(0)
r = libmbedtls.mbedtls_cipher_finish(
byref(self._ctx),
byref(buf), byref(cipher_out_len)
)
if not r:
# print(self._nonce.raw, r, cipher_out_len)
raise Exception('Finalize cipher failed')
return buf.raw[:cipher_out_len.value]
def aead_encrypt(self, data):
"""
Encrypt data with authenticate tag
:param data: plain text
:return: cipher text with tag
"""
global buf_size, buf
plen = len(data)
if buf_size < plen + self._tlen:
buf_size = (plen + self._tlen) * 2
buf = create_string_buffer(buf_size)
cipher_out_len = c_size_t(0)
tag_buf = create_string_buffer(self._tlen)
r = libmbedtls.mbedtls_cipher_auth_encrypt(
byref(self._ctx),
c_char_p(self._nonce.raw), c_size_t(self._nlen),
None, c_size_t(0),
c_char_p(data), c_size_t(plen),
byref(buf), byref(cipher_out_len),
byref(tag_buf), c_size_t(self._tlen)
)
assert cipher_out_len.value == plen
if r:
raise Exception('AEAD encrypt failed {0:#x}'.format(r))
self.cipher_ctx_init()
return buf.raw[:cipher_out_len.value] + tag_buf.raw[:self._tlen]
def aead_decrypt(self, data):
"""
Decrypt data and authenticate tag
:param data: cipher text with tag
:return: plain text
"""
global buf_size, buf
cipher_out_len = c_size_t(0)
plen = len(data) - self._tlen
if buf_size < plen:
buf_size = plen * 2
buf = create_string_buffer(buf_size)
tag = data[plen:]
r = libmbedtls.mbedtls_cipher_auth_decrypt(
byref(self._ctx),
c_char_p(self._nonce.raw), c_size_t(self._nlen),
None, c_size_t(0),
c_char_p(data), c_size_t(plen),
byref(buf), byref(cipher_out_len),
c_char_p(tag), c_size_t(self._tlen)
)
if r:
raise Exception('AEAD encrypt failed {0:#x}'.format(r))
self.cipher_ctx_init()
return buf.raw[:cipher_out_len.value]
class MbedTLSStreamCrypto(MbedTLSCryptoBase):
"""
Crypto for stream modes: cfb, ofb, ctr
"""
def __init__(self, cipher_name, key, iv, op, crypto_path=None):
if cipher_name[:len('mbedtls:')] == 'mbedtls:':
cipher_name = cipher_name[len('mbedtls:'):]
MbedTLSCryptoBase.__init__(self, cipher_name, crypto_path)
key_ptr = c_char_p(key)
iv_ptr = c_char_p(iv)
r = libmbedtls.mbedtls_cipher_setkey(
byref(self._ctx),
key_ptr, c_int(len(key) * 8),
c_int(op)
)
if r:
self.clean()
raise Exception('can not set cipher key')
r = libmbedtls.mbedtls_cipher_set_iv(
byref(self._ctx),
iv_ptr, c_size_t(len(iv))
)
if r:
self.clean()
raise Exception('can not set cipher iv')
r = libmbedtls.mbedtls_cipher_reset(byref(self._ctx))
if r:
self.clean()
raise Exception('can not reset cipher')
self.encrypt = self.update
self.decrypt = self.update
ciphers = {
'mbedtls:aes-128-cfb128': (16, 16, MbedTLSStreamCrypto),
'mbedtls:aes-192-cfb128': (24, 16, MbedTLSStreamCrypto),
'mbedtls:aes-256-cfb128': (32, 16, MbedTLSStreamCrypto),
'mbedtls:aes-128-ctr': (16, 16, MbedTLSStreamCrypto),
'mbedtls:aes-192-ctr': (24, 16, MbedTLSStreamCrypto),
'mbedtls:aes-256-ctr': (32, 16, MbedTLSStreamCrypto),
'mbedtls:camellia-128-cfb128': (16, 16, MbedTLSStreamCrypto),
'mbedtls:camellia-192-cfb128': (24, 16, MbedTLSStreamCrypto),
'mbedtls:camellia-256-cfb128': (32, 16, MbedTLSStreamCrypto),
# AEAD: iv_len = salt_len = key_len
'mbedtls:aes-128-gcm': (16, 16, MbedTLSAeadCrypto),
'mbedtls:aes-192-gcm': (24, 24, MbedTLSAeadCrypto),
'mbedtls:aes-256-gcm': (32, 32, MbedTLSAeadCrypto),
}
def run_method(method):
from shadowsocks.crypto import openssl
print(method, ': [stream]', 32)
cipher = MbedTLSStreamCrypto(method, b'k' * 32, b'i' * 16, 1)
decipher = openssl.OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 0)
util.run_cipher(cipher, decipher)
def run_aead_method(method, key_len=16):
from shadowsocks.crypto import openssl
print(method, ': [payload][tag]', key_len)
key_len = int(key_len)
cipher = MbedTLSAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1)
decipher = openssl.OpenSSLAeadCrypto(
method,
b'k' * key_len, b'i' * key_len, 0
)
util.run_cipher(cipher, decipher)
def run_aead_method_chunk(method, key_len=16):
from shadowsocks.crypto import openssl
print(method, ': chunk([size][tag][payload][tag]', key_len)
key_len = int(key_len)
cipher = MbedTLSAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1)
decipher = openssl.OpenSSLAeadCrypto(
method,
b'k' * key_len, b'i' * key_len, 0
)
cipher.encrypt_once = cipher.encrypt
decipher.decrypt_once = decipher.decrypt
util.run_cipher(cipher, decipher)
def test_camellia_256_cfb():
run_method('camellia-256-cfb128')
def test_aes_gcm(bits=128):
method = "aes-{0}-gcm".format(bits)
run_aead_method(method, bits / 8)
def test_aes_gcm_chunk(bits=128):
method = "aes-{0}-gcm".format(bits)
run_aead_method_chunk(method, bits / 8)
def test_aes_256_cfb():
run_method('aes-256-cfb128')
def test_aes_256_ctr():
run_method('aes-256-ctr')
if __name__ == '__main__':
test_aes_256_cfb()
test_camellia_256_cfb()
test_aes_256_ctr()
test_aes_gcm(128)
test_aes_gcm(192)
test_aes_gcm(256)
test_aes_gcm_chunk(128)
test_aes_gcm_chunk(192)
test_aes_gcm_chunk(256)

View file

@ -22,34 +22,52 @@ from ctypes import c_char_p, c_int, c_long, byref,\
from shadowsocks import common from shadowsocks import common
from shadowsocks.crypto import util from shadowsocks.crypto import util
from shadowsocks.crypto.aead import AeadCryptoBase, EVP_CTRL_AEAD_SET_IVLEN, \
EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_SET_TAG
__all__ = ['ciphers'] __all__ = ['ciphers']
libcrypto = None libcrypto = None
loaded = False loaded = False
libsodium = None
buf = None
buf_size = 2048 buf_size = 2048
ctx_cleanup = None
def load_openssl(): CIPHER_ENC_UNCHANGED = -1
global loaded, libcrypto, buf
def load_openssl(crypto_path=None):
global loaded, libcrypto, libsodium, buf, ctx_cleanup
crypto_path = dict(crypto_path) if crypto_path else dict()
path = crypto_path.get('openssl', None)
libcrypto = util.find_library(('crypto', 'eay32'), libcrypto = util.find_library(('crypto', 'eay32'),
'EVP_get_cipherbyname', 'EVP_get_cipherbyname',
'libcrypto') 'libcrypto', path)
if libcrypto is None: if libcrypto is None:
raise Exception('libcrypto(OpenSSL) not found') raise Exception('libcrypto(OpenSSL) not found with path %s' % path)
libcrypto.EVP_get_cipherbyname.restype = c_void_p libcrypto.EVP_get_cipherbyname.restype = c_void_p
libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p
libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p, libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p,
c_char_p, c_char_p, c_int) c_char_p, c_char_p, c_int)
libcrypto.EVP_CIPHER_CTX_ctrl.argtypes = (c_void_p, c_int, c_int, c_void_p)
libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p, libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p,
c_char_p, c_int) c_char_p, c_int)
libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,) libcrypto.EVP_CipherFinal_ex.argtypes = (c_void_p, c_void_p, c_void_p)
try:
libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,)
ctx_cleanup = libcrypto.EVP_CIPHER_CTX_cleanup
except AttributeError:
libcrypto.EVP_CIPHER_CTX_reset.argtypes = (c_void_p,)
ctx_cleanup = libcrypto.EVP_CIPHER_CTX_reset
libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,) libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,)
if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'): if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'):
libcrypto.OpenSSL_add_all_ciphers() libcrypto.OpenSSL_add_all_ciphers()
@ -59,7 +77,7 @@ def load_openssl():
def load_cipher(cipher_name): def load_cipher(cipher_name):
func_name = 'EVP_' + cipher_name.replace('-', '_') func_name = b'EVP_' + cipher_name.replace(b'-', b'_')
if bytes != str: if bytes != str:
func_name = str(func_name, 'utf-8') func_name = str(func_name, 'utf-8')
cipher = getattr(libcrypto, func_name, None) cipher = getattr(libcrypto, func_name, None)
@ -69,37 +87,45 @@ def load_cipher(cipher_name):
return None return None
class OpenSSLCrypto(object): class OpenSSLCryptoBase(object):
def __init__(self, cipher_name, key, iv, op): """
OpenSSL crypto base class
"""
def __init__(self, cipher_name, crypto_path=None):
self._ctx = None self._ctx = None
self._cipher = None
if not loaded: if not loaded:
load_openssl() load_openssl(crypto_path)
cipher_name = common.to_bytes(cipher_name) cipher_name = common.to_bytes(cipher_name)
cipher = libcrypto.EVP_get_cipherbyname(cipher_name) cipher = libcrypto.EVP_get_cipherbyname(cipher_name)
if not cipher: if not cipher:
cipher = load_cipher(cipher_name) cipher = load_cipher(cipher_name)
if not cipher: if not cipher:
raise Exception('cipher %s not found in libcrypto' % cipher_name) raise Exception('cipher %s not found in libcrypto' % cipher_name)
key_ptr = c_char_p(key)
iv_ptr = c_char_p(iv)
self._ctx = libcrypto.EVP_CIPHER_CTX_new() self._ctx = libcrypto.EVP_CIPHER_CTX_new()
self._cipher = cipher
if not self._ctx: if not self._ctx:
raise Exception('can not create cipher context') raise Exception('can not create cipher context')
r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None,
key_ptr, iv_ptr, c_int(op)) self.encrypt_once = self.update
if not r: self.decrypt_once = self.update
self.clean()
raise Exception('can not initialize cipher context')
def update(self, data): def update(self, data):
"""
Encrypt/decrypt data
:param data: str
:return: str
"""
global buf_size, buf global buf_size, buf
cipher_out_len = c_long(0) cipher_out_len = c_long(0)
l = len(data) l = len(data)
if buf_size < l: if buf_size < l:
buf_size = l * 2 buf_size = l * 2
buf = create_string_buffer(buf_size) buf = create_string_buffer(buf_size)
libcrypto.EVP_CipherUpdate(self._ctx, byref(buf), libcrypto.EVP_CipherUpdate(
byref(cipher_out_len), c_char_p(data), l) self._ctx, byref(buf),
byref(cipher_out_len), c_char_p(data), l
)
# buf is copied to a str object when we access buf.raw # buf is copied to a str object when we access buf.raw
return buf.raw[:cipher_out_len.value] return buf.raw[:cipher_out_len.value]
@ -108,47 +134,256 @@ class OpenSSLCrypto(object):
def clean(self): def clean(self):
if self._ctx: if self._ctx:
libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx) ctx_cleanup(self._ctx)
libcrypto.EVP_CIPHER_CTX_free(self._ctx) libcrypto.EVP_CIPHER_CTX_free(self._ctx)
class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase):
"""
Implement OpenSSL Aead mode: gcm, ocb
"""
def __init__(self, cipher_name, key, iv, op, crypto_path=None):
OpenSSLCryptoBase.__init__(self, cipher_name, crypto_path)
AeadCryptoBase.__init__(self, cipher_name, key, iv, op, crypto_path)
key_ptr = c_char_p(self._skey)
r = libcrypto.EVP_CipherInit_ex(
self._ctx,
self._cipher,
None,
key_ptr, None,
c_int(op)
)
if not r:
self.clean()
raise Exception('can not initialize cipher context')
r = libcrypto.EVP_CIPHER_CTX_ctrl(
self._ctx,
c_int(EVP_CTRL_AEAD_SET_IVLEN),
c_int(self._nlen),
None
)
if not r:
self.clean()
raise Exception('Set ivlen failed')
self.cipher_ctx_init()
def cipher_ctx_init(self):
"""
Need init cipher context after EVP_CipherFinal_ex to reuse context
:return: None
"""
iv_ptr = c_char_p(self._nonce.raw)
r = libcrypto.EVP_CipherInit_ex(
self._ctx,
None,
None,
None, iv_ptr,
c_int(CIPHER_ENC_UNCHANGED)
)
if not r:
self.clean()
raise Exception('can not initialize cipher context')
AeadCryptoBase.nonce_increment(self)
def set_tag(self, tag):
"""
Set tag before decrypt any data (update)
:param tag: authenticated tag
:return: None
"""
tag_len = self._tlen
r = libcrypto.EVP_CIPHER_CTX_ctrl(
self._ctx,
c_int(EVP_CTRL_AEAD_SET_TAG),
c_int(tag_len), c_char_p(tag)
)
if not r:
self.clean()
raise Exception('Set tag failed')
def get_tag(self):
"""
Get authenticated tag, called after EVP_CipherFinal_ex
:return: str
"""
tag_len = self._tlen
tag_buf = create_string_buffer(tag_len)
r = libcrypto.EVP_CIPHER_CTX_ctrl(
self._ctx,
c_int(EVP_CTRL_AEAD_GET_TAG),
c_int(tag_len), byref(tag_buf)
)
if not r:
self.clean()
raise Exception('Get tag failed')
return tag_buf.raw[:tag_len]
def final(self):
"""
Finish encrypt/decrypt a chunk (<= 0x3FFF)
:return: str
"""
global buf_size, buf
cipher_out_len = c_long(0)
r = libcrypto.EVP_CipherFinal_ex(
self._ctx,
byref(buf), byref(cipher_out_len)
)
if not r:
self.clean()
# print(self._nonce.raw, r, cipher_out_len)
raise Exception('Finalize cipher failed')
return buf.raw[:cipher_out_len.value]
def aead_encrypt(self, data):
"""
Encrypt data with authenticate tag
:param data: plain text
:return: cipher text with tag
"""
ctext = self.update(data) + self.final() + self.get_tag()
self.cipher_ctx_init()
return ctext
def aead_decrypt(self, data):
"""
Decrypt data and authenticate tag
:param data: cipher text with tag
:return: plain text
"""
clen = len(data)
if clen < self._tlen:
self.clean()
raise Exception('Data too short')
self.set_tag(data[clen - self._tlen:])
plaintext = self.update(data[:clen - self._tlen]) + self.final()
self.cipher_ctx_init()
return plaintext
class OpenSSLStreamCrypto(OpenSSLCryptoBase):
"""
Crypto for stream modes: cfb, ofb, ctr
"""
def __init__(self, cipher_name, key, iv, op, crypto_path=None):
OpenSSLCryptoBase.__init__(self, cipher_name, crypto_path)
key_ptr = c_char_p(key)
iv_ptr = c_char_p(iv)
r = libcrypto.EVP_CipherInit_ex(self._ctx, self._cipher, None,
key_ptr, iv_ptr, c_int(op))
if not r:
self.clean()
raise Exception('can not initialize cipher context')
self.encrypt = self.update
self.decrypt = self.update
ciphers = { ciphers = {
'aes-128-cfb': (16, 16, OpenSSLCrypto), 'aes-128-cfb': (16, 16, OpenSSLStreamCrypto),
'aes-192-cfb': (24, 16, OpenSSLCrypto), 'aes-192-cfb': (24, 16, OpenSSLStreamCrypto),
'aes-256-cfb': (32, 16, OpenSSLCrypto), 'aes-256-cfb': (32, 16, OpenSSLStreamCrypto),
'aes-128-ofb': (16, 16, OpenSSLCrypto), 'aes-128-ofb': (16, 16, OpenSSLStreamCrypto),
'aes-192-ofb': (24, 16, OpenSSLCrypto), 'aes-192-ofb': (24, 16, OpenSSLStreamCrypto),
'aes-256-ofb': (32, 16, OpenSSLCrypto), 'aes-256-ofb': (32, 16, OpenSSLStreamCrypto),
'aes-128-ctr': (16, 16, OpenSSLCrypto), 'aes-128-ctr': (16, 16, OpenSSLStreamCrypto),
'aes-192-ctr': (24, 16, OpenSSLCrypto), 'aes-192-ctr': (24, 16, OpenSSLStreamCrypto),
'aes-256-ctr': (32, 16, OpenSSLCrypto), 'aes-256-ctr': (32, 16, OpenSSLStreamCrypto),
'aes-128-cfb8': (16, 16, OpenSSLCrypto), 'aes-128-cfb8': (16, 16, OpenSSLStreamCrypto),
'aes-192-cfb8': (24, 16, OpenSSLCrypto), 'aes-192-cfb8': (24, 16, OpenSSLStreamCrypto),
'aes-256-cfb8': (32, 16, OpenSSLCrypto), 'aes-256-cfb8': (32, 16, OpenSSLStreamCrypto),
'aes-128-cfb1': (16, 16, OpenSSLCrypto), 'aes-128-cfb1': (16, 16, OpenSSLStreamCrypto),
'aes-192-cfb1': (24, 16, OpenSSLCrypto), 'aes-192-cfb1': (24, 16, OpenSSLStreamCrypto),
'aes-256-cfb1': (32, 16, OpenSSLCrypto), 'aes-256-cfb1': (32, 16, OpenSSLStreamCrypto),
'bf-cfb': (16, 8, OpenSSLCrypto), 'bf-cfb': (16, 8, OpenSSLStreamCrypto),
'camellia-128-cfb': (16, 16, OpenSSLCrypto), 'camellia-128-cfb': (16, 16, OpenSSLStreamCrypto),
'camellia-192-cfb': (24, 16, OpenSSLCrypto), 'camellia-192-cfb': (24, 16, OpenSSLStreamCrypto),
'camellia-256-cfb': (32, 16, OpenSSLCrypto), 'camellia-256-cfb': (32, 16, OpenSSLStreamCrypto),
'cast5-cfb': (16, 8, OpenSSLCrypto), 'cast5-cfb': (16, 8, OpenSSLStreamCrypto),
'des-cfb': (8, 8, OpenSSLCrypto), 'des-cfb': (8, 8, OpenSSLStreamCrypto),
'idea-cfb': (16, 8, OpenSSLCrypto), 'idea-cfb': (16, 8, OpenSSLStreamCrypto),
'rc2-cfb': (16, 8, OpenSSLCrypto), 'rc2-cfb': (16, 8, OpenSSLStreamCrypto),
'rc4': (16, 0, OpenSSLCrypto), 'rc4': (16, 0, OpenSSLStreamCrypto),
'seed-cfb': (16, 16, OpenSSLCrypto), 'seed-cfb': (16, 16, OpenSSLStreamCrypto),
# AEAD: iv_len = salt_len = key_len
'aes-128-gcm': (16, 16, OpenSSLAeadCrypto),
'aes-192-gcm': (24, 24, OpenSSLAeadCrypto),
'aes-256-gcm': (32, 32, OpenSSLAeadCrypto),
'aes-128-ocb': (16, 16, OpenSSLAeadCrypto),
'aes-192-ocb': (24, 24, OpenSSLAeadCrypto),
'aes-256-ocb': (32, 32, OpenSSLAeadCrypto),
} }
def run_method(method): def run_method(method):
cipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 1) print(method, ': [stream]', 32)
decipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 0) cipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 1)
decipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 0)
util.run_cipher(cipher, decipher) util.run_cipher(cipher, decipher)
def run_aead_method(method, key_len=16):
print(method, ': [payload][tag]', key_len)
cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(method))
if not cipher:
cipher = load_cipher(common.to_bytes(method))
if not cipher:
print('cipher not avaiable, please upgrade openssl')
return
key_len = int(key_len)
cipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1)
decipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 0)
util.run_cipher(cipher, decipher)
def run_aead_method_chunk(method, key_len=16):
print(method, ': chunk([size][tag][payload][tag]', key_len)
cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(method))
if not cipher:
cipher = load_cipher(common.to_bytes(method))
if not cipher:
print('cipher not avaiable, please upgrade openssl')
return
key_len = int(key_len)
cipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1)
decipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 0)
cipher.encrypt_once = cipher.encrypt
decipher.decrypt_once = decipher.decrypt
util.run_cipher(cipher, decipher)
def test_aes_gcm(bits=128):
method = "aes-{0}-gcm".format(bits)
run_aead_method(method, bits / 8)
def test_aes_ocb(bits=128):
method = "aes-{0}-ocb".format(bits)
run_aead_method(method, bits / 8)
def test_aes_gcm_chunk(bits=128):
method = "aes-{0}-gcm".format(bits)
run_aead_method_chunk(method, bits / 8)
def test_aes_ocb_chunk(bits=128):
method = "aes-{0}-ocb".format(bits)
run_aead_method_chunk(method, bits / 8)
def test_aes_128_cfb(): def test_aes_128_cfb():
run_method('aes-128-cfb') run_method('aes-128-cfb')
@ -179,3 +414,17 @@ def test_rc4():
if __name__ == '__main__': if __name__ == '__main__':
test_aes_128_cfb() test_aes_128_cfb()
test_aes_256_cfb()
test_aes_256_ofb()
test_aes_gcm(128)
test_aes_gcm(192)
test_aes_gcm(256)
test_aes_gcm_chunk(128)
test_aes_gcm_chunk(192)
test_aes_gcm_chunk(256)
test_aes_ocb(128)
test_aes_ocb(192)
test_aes_ocb(256)
test_aes_ocb_chunk(128)
test_aes_ocb_chunk(192)
test_aes_ocb_chunk(256)

View file

@ -18,19 +18,19 @@ from __future__ import absolute_import, division, print_function, \
with_statement with_statement
import hashlib import hashlib
from shadowsocks.crypto import openssl from shadowsocks.crypto import openssl
__all__ = ['ciphers'] __all__ = ['ciphers']
def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None, def create_cipher(alg, key, iv, op, crypto_path=None,
key_as_bytes=0, d=None, salt=None,
i=1, padding=1): i=1, padding=1):
md5 = hashlib.md5() md5 = hashlib.md5()
md5.update(key) md5.update(key)
md5.update(iv) md5.update(iv)
rc4_key = md5.digest() rc4_key = md5.digest()
return openssl.OpenSSLCrypto(b'rc4', rc4_key, b'', op) return openssl.OpenSSLStreamCrypto(b'rc4', rc4_key, b'', op, crypto_path)
ciphers = { ciphers = {

View file

@ -17,56 +17,162 @@
from __future__ import absolute_import, division, print_function, \ from __future__ import absolute_import, division, print_function, \
with_statement with_statement
from ctypes import c_char_p, c_int, c_ulonglong, byref, c_ulong, \ from ctypes import c_char_p, c_int, c_uint, c_ulonglong, byref, \
create_string_buffer, c_void_p create_string_buffer, c_void_p
from shadowsocks.crypto import util from shadowsocks.crypto import util
from shadowsocks.crypto import aead
from shadowsocks.crypto.aead import AeadCryptoBase
__all__ = ['ciphers'] __all__ = ['ciphers']
libsodium = None libsodium = None
loaded = False loaded = False
buf = None
buf_size = 2048 buf_size = 2048
# for salsa20 and chacha20 and chacha20-ietf # for salsa20 and chacha20 and chacha20-ietf
BLOCK_SIZE = 64 BLOCK_SIZE = 64
def load_libsodium(): def load_libsodium(crypto_path=None):
global loaded, libsodium, buf global loaded, libsodium, buf
libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic', crypto_path = dict(crypto_path) if crypto_path else dict()
'libsodium') path = crypto_path.get('sodium', None)
if libsodium is None:
raise Exception('libsodium not found') if not aead.sodium_loaded:
aead.load_sodium(path)
if aead.sodium_loaded:
libsodium = aead.libsodium
else:
print('load libsodium again with path %s' % path)
libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic',
'libsodium', path)
if libsodium is None:
raise Exception('libsodium not found')
if libsodium.sodium_init() < 0:
raise Exception('libsodium init failed')
libsodium.crypto_stream_salsa20_xor_ic.restype = c_int libsodium.crypto_stream_salsa20_xor_ic.restype = c_int
libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p, libsodium.crypto_stream_salsa20_xor_ic.argtypes = (
c_ulonglong, c_void_p, c_char_p, # cipher output, msg
c_char_p, c_ulonglong, c_ulonglong, # msg len
c_char_p) c_char_p, c_ulonglong, # nonce, uint64_t initial block counter
c_char_p # key
)
libsodium.crypto_stream_chacha20_xor_ic.restype = c_int libsodium.crypto_stream_chacha20_xor_ic.restype = c_int
libsodium.crypto_stream_chacha20_xor_ic.argtypes = (c_void_p, c_char_p, libsodium.crypto_stream_chacha20_xor_ic.argtypes = (
c_ulonglong, c_void_p, c_char_p,
c_char_p, c_ulonglong, c_ulonglong,
c_char_p) c_char_p, c_ulonglong,
c_char_p
)
if hasattr(libsodium, 'crypto_stream_xchacha20_xor_ic'):
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
)
libsodium.crypto_stream_chacha20_ietf_xor_ic.restype = c_int libsodium.crypto_stream_chacha20_ietf_xor_ic.restype = c_int
libsodium.crypto_stream_chacha20_ietf_xor_ic.argtypes = (c_void_p, libsodium.crypto_stream_chacha20_ietf_xor_ic.argtypes = (
c_char_p, c_void_p, c_char_p,
c_ulonglong, c_ulonglong,
c_char_p, c_char_p,
c_ulong, c_uint, # uint32_t initial counter
c_char_p) c_char_p
)
# chacha20-poly1305
libsodium.crypto_aead_chacha20poly1305_encrypt.restype = c_int
libsodium.crypto_aead_chacha20poly1305_encrypt.argtypes = (
c_void_p, c_void_p, # c, clen
c_char_p, c_ulonglong, # m, mlen
c_char_p, c_ulonglong, # ad, adlen
c_char_p, # nsec, not used
c_char_p, c_char_p # npub, k
)
libsodium.crypto_aead_chacha20poly1305_decrypt.restype = c_int
libsodium.crypto_aead_chacha20poly1305_decrypt.argtypes = (
c_void_p, c_void_p, # m, mlen
c_char_p, # nsec, not used
c_char_p, c_ulonglong, # c, clen
c_char_p, c_ulonglong, # ad, adlen
c_char_p, c_char_p # npub, k
)
# chacha20-ietf-poly1305, same api structure as above
libsodium.crypto_aead_chacha20poly1305_ietf_encrypt.restype = c_int
libsodium.crypto_aead_chacha20poly1305_ietf_encrypt.argtypes = (
c_void_p, c_void_p,
c_char_p, c_ulonglong,
c_char_p, c_ulonglong,
c_char_p,
c_char_p, c_char_p
)
libsodium.crypto_aead_chacha20poly1305_ietf_decrypt.restype = c_int
libsodium.crypto_aead_chacha20poly1305_ietf_decrypt.argtypes = (
c_void_p, c_void_p,
c_char_p,
c_char_p, c_ulonglong,
c_char_p, c_ulonglong,
c_char_p, c_char_p
)
# xchacha20-ietf-poly1305, same api structure as above
if hasattr(libsodium, 'crypto_aead_xchacha20poly1305_ietf_encrypt'):
libsodium.crypto_aead_xchacha20poly1305_ietf_encrypt.restype = c_int
libsodium.crypto_aead_xchacha20poly1305_ietf_encrypt.argtypes = (
c_void_p, c_void_p,
c_char_p, c_ulonglong,
c_char_p, c_ulonglong,
c_char_p,
c_char_p, c_char_p
)
libsodium.crypto_aead_xchacha20poly1305_ietf_decrypt.restype = c_int
libsodium.crypto_aead_xchacha20poly1305_ietf_decrypt.argtypes = (
c_void_p, c_void_p,
c_char_p,
c_char_p, c_ulonglong,
c_char_p, c_ulonglong,
c_char_p, c_char_p
)
# aes-256-gcm, same api structure as above
libsodium.crypto_aead_aes256gcm_is_available.restype = c_int
if libsodium.crypto_aead_aes256gcm_is_available():
libsodium.crypto_aead_aes256gcm_encrypt.restype = c_int
libsodium.crypto_aead_aes256gcm_encrypt.argtypes = (
c_void_p, c_void_p,
c_char_p, c_ulonglong,
c_char_p, c_ulonglong,
c_char_p,
c_char_p, c_char_p
)
libsodium.crypto_aead_aes256gcm_decrypt.restype = c_int
libsodium.crypto_aead_aes256gcm_decrypt.argtypes = (
c_void_p, c_void_p,
c_char_p,
c_char_p, c_ulonglong,
c_char_p, c_ulonglong,
c_char_p, c_char_p
)
buf = create_string_buffer(buf_size) buf = create_string_buffer(buf_size)
loaded = True loaded = True
class SodiumCrypto(object): class SodiumCrypto(object):
def __init__(self, cipher_name, key, iv, op): def __init__(self, cipher_name, key, iv, op, crypto_path=None):
if not loaded: if not loaded:
load_libsodium() load_libsodium(crypto_path)
self.key = key self.key = key
self.iv = iv self.iv = iv
self.key_ptr = c_char_p(key) self.key_ptr = c_char_p(key)
@ -75,12 +181,21 @@ class SodiumCrypto(object):
self.cipher = libsodium.crypto_stream_salsa20_xor_ic self.cipher = libsodium.crypto_stream_salsa20_xor_ic
elif cipher_name == 'chacha20': elif cipher_name == 'chacha20':
self.cipher = libsodium.crypto_stream_chacha20_xor_ic self.cipher = libsodium.crypto_stream_chacha20_xor_ic
elif cipher_name == 'xchacha20':
if hasattr(libsodium, 'crypto_stream_xchacha20_xor_ic'):
self.cipher = libsodium.crypto_stream_xchacha20_xor_ic
else:
raise Exception('Unsupported cipher')
elif cipher_name == 'chacha20-ietf': elif cipher_name == 'chacha20-ietf':
self.cipher = libsodium.crypto_stream_chacha20_ietf_xor_ic self.cipher = libsodium.crypto_stream_chacha20_ietf_xor_ic
else: else:
raise Exception('Unknown cipher') raise Exception('Unknown cipher')
# byte counter, not block counter # byte counter, not block counter
self.counter = 0 self.counter = 0
self.encrypt = self.update
self.decrypt = self.update
self.encrypt_once = self.update
self.decrypt_once = self.update
def update(self, data): def update(self, data):
global buf_size, buf global buf_size, buf
@ -102,38 +217,212 @@ class SodiumCrypto(object):
# strip off the padding # strip off the padding
return buf.raw[padding:padding + l] return buf.raw[padding:padding + l]
def clean(self):
pass
class SodiumAeadCrypto(AeadCryptoBase):
def __init__(self, cipher_name, key, iv, op, crypto_path=None):
if not loaded:
load_libsodium(crypto_path)
AeadCryptoBase.__init__(self, cipher_name, key, iv, op, crypto_path)
if cipher_name == 'chacha20-poly1305':
self.encryptor = libsodium.crypto_aead_chacha20poly1305_encrypt
self.decryptor = libsodium.crypto_aead_chacha20poly1305_decrypt
elif cipher_name == 'chacha20-ietf-poly1305':
self.encryptor = libsodium. \
crypto_aead_chacha20poly1305_ietf_encrypt
self.decryptor = libsodium. \
crypto_aead_chacha20poly1305_ietf_decrypt
elif cipher_name == 'xchacha20-ietf-poly1305':
if hasattr(libsodium,
'crypto_aead_xchacha20poly1305_ietf_encrypt'):
self.encryptor = libsodium. \
crypto_aead_xchacha20poly1305_ietf_encrypt
self.decryptor = libsodium. \
crypto_aead_xchacha20poly1305_ietf_decrypt
else:
raise Exception('Unsupported cipher')
elif cipher_name == 'sodium:aes-256-gcm':
if hasattr(libsodium, 'crypto_aead_aes256gcm_encrypt'):
self.encryptor = libsodium.crypto_aead_aes256gcm_encrypt
self.decryptor = libsodium.crypto_aead_aes256gcm_decrypt
else:
raise Exception('Unsupported cipher')
else:
raise Exception('Unknown cipher')
def cipher_ctx_init(self):
global libsodium
libsodium.sodium_increment(byref(self._nonce), c_int(self._nlen))
# print("".join("%02x" % ord(b) for b in self._nonce))
def aead_encrypt(self, data):
global buf, buf_size
plen = len(data)
if buf_size < plen + self._tlen:
buf_size = (plen + self._tlen) * 2
buf = create_string_buffer(buf_size)
cipher_out_len = c_ulonglong(0)
self.encryptor(
byref(buf), byref(cipher_out_len),
c_char_p(data), c_ulonglong(plen),
None, c_ulonglong(0), None,
c_char_p(self._nonce.raw), c_char_p(self._skey)
)
if cipher_out_len.value != plen + self._tlen:
raise Exception("Encrypt failed")
self.cipher_ctx_init()
return buf.raw[:cipher_out_len.value]
def aead_decrypt(self, data):
global buf, buf_size
clen = len(data)
if buf_size < clen:
buf_size = clen * 2
buf = create_string_buffer(buf_size)
cipher_out_len = c_ulonglong(0)
r = self.decryptor(
byref(buf), byref(cipher_out_len),
None,
c_char_p(data), c_ulonglong(clen),
None, c_ulonglong(0),
c_char_p(self._nonce.raw), c_char_p(self._skey)
)
if r != 0:
raise Exception("Decrypt failed")
if cipher_out_len.value != clen - self._tlen:
raise Exception("Decrypt failed")
self.cipher_ctx_init()
return buf.raw[:cipher_out_len.value]
ciphers = { ciphers = {
'salsa20': (32, 8, SodiumCrypto), 'salsa20': (32, 8, SodiumCrypto),
'chacha20': (32, 8, SodiumCrypto), 'chacha20': (32, 8, SodiumCrypto),
'xchacha20': (32, 24, SodiumCrypto),
'chacha20-ietf': (32, 12, SodiumCrypto), 'chacha20-ietf': (32, 12, SodiumCrypto),
# AEAD: iv_len = salt_len = key_len
'chacha20-poly1305': (32, 32, SodiumAeadCrypto),
'chacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto),
'xchacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto),
'sodium:aes-256-gcm': (32, 32, SodiumAeadCrypto),
} }
def test_salsa20():
cipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 1)
decipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 0)
util.run_cipher(cipher, decipher)
def test_chacha20(): def test_chacha20():
print("Test chacha20")
cipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 1) cipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 1)
decipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 0) decipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 0)
util.run_cipher(cipher, decipher) util.run_cipher(cipher, decipher)
def test_chacha20_ietf(): def test_xchacha20():
print("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_salsa20():
print("Test salsa20")
cipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 1)
decipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 0)
util.run_cipher(cipher, decipher)
def test_chacha20_ietf():
print("Test chacha20-ietf")
cipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 1) cipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 1)
decipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 0) decipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 0)
util.run_cipher(cipher, decipher) util.run_cipher(cipher, decipher)
def test_chacha20_poly1305():
print("Test chacha20-poly1305 [payload][tag]")
cipher = SodiumAeadCrypto('chacha20-poly1305',
b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('chacha20-poly1305',
b'k' * 32, b'i' * 32, 0)
util.run_cipher(cipher, decipher)
def test_chacha20_poly1305_chunk():
print("Test chacha20-poly1305 chunk [size][tag][payload][tag]")
cipher = SodiumAeadCrypto('chacha20-poly1305',
b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('chacha20-poly1305',
b'k' * 32, b'i' * 32, 0)
cipher.encrypt_once = cipher.encrypt
decipher.decrypt_once = decipher.decrypt
util.run_cipher(cipher, decipher)
def test_chacha20_ietf_poly1305():
print("Test chacha20-ietf-poly1305 [payload][tag]")
cipher = SodiumAeadCrypto('chacha20-ietf-poly1305',
b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('chacha20-ietf-poly1305',
b'k' * 32, b'i' * 32, 0)
util.run_cipher(cipher, decipher)
def test_chacha20_ietf_poly1305_chunk():
print("Test chacha20-ietf-poly1305 chunk [size][tag][payload][tag]")
cipher = SodiumAeadCrypto('chacha20-ietf-poly1305',
b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('chacha20-ietf-poly1305',
b'k' * 32, b'i' * 32, 0)
cipher.encrypt_once = cipher.encrypt
decipher.decrypt_once = decipher.decrypt
util.run_cipher(cipher, decipher)
def test_aes_256_gcm():
print("Test sodium:aes-256-gcm [payload][tag]")
cipher = SodiumAeadCrypto('sodium:aes-256-gcm',
b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('sodium:aes-256-gcm',
b'k' * 32, b'i' * 32, 0)
util.run_cipher(cipher, decipher)
def test_aes_256_gcm_chunk():
print("Test sodium:aes-256-gcm chunk [size][tag][payload][tag]")
cipher = SodiumAeadCrypto('sodium:aes-256-gcm',
b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('sodium:aes-256-gcm',
b'k' * 32, b'i' * 32, 0)
cipher.encrypt_once = cipher.encrypt
decipher.decrypt_once = decipher.decrypt
util.run_cipher(cipher, decipher)
if __name__ == '__main__': if __name__ == '__main__':
test_chacha20() test_chacha20()
test_xchacha20()
test_salsa20() test_salsa20()
test_chacha20_ietf() test_chacha20_ietf()
test_chacha20_poly1305()
test_chacha20_poly1305_chunk()
test_chacha20_ietf_poly1305()
test_chacha20_ietf_poly1305_chunk()
test_aes_256_gcm()
test_aes_256_gcm_chunk()

View file

@ -55,9 +55,13 @@ def init_table(key):
class TableCipher(object): class TableCipher(object):
def __init__(self, cipher_name, key, iv, op): def __init__(self, cipher_name, key, iv, op, crypto_path=None):
self._encrypt_table, self._decrypt_table = init_table(key) self._encrypt_table, self._decrypt_table = init_table(key)
self._op = op self._op = op
self.encrypt = self.update
self.decrypt = self.update
self.encrypt_once = self.update
self.decrypt_once = self.update
def update(self, data): def update(self, data):
if self._op: if self._op:

View file

@ -26,6 +26,7 @@ def find_library_nt(name):
# ctypes.util.find_library just returns first result he found # ctypes.util.find_library just returns first result he found
# but we want to try them all # but we want to try them all
# because on Windows, users may have both 32bit and 64bit version installed # because on Windows, users may have both 32bit and 64bit version installed
import glob
results = [] results = []
for directory in os.environ['PATH'].split(os.pathsep): for directory in os.environ['PATH'].split(os.pathsep):
fname = os.path.join(directory, name) fname = os.path.join(directory, name)
@ -33,15 +34,34 @@ def find_library_nt(name):
results.append(fname) results.append(fname)
if fname.lower().endswith(".dll"): if fname.lower().endswith(".dll"):
continue continue
fname = fname + ".dll" fname += "*.dll"
if os.path.isfile(fname): files = glob.glob(fname)
results.append(fname) if files:
results.extend(files)
return results return results
def find_library(possible_lib_names, search_symbol, library_name): def load_library(path, search_symbol, library_name):
import ctypes.util
from ctypes import CDLL from ctypes import CDLL
try:
lib = CDLL(path)
if hasattr(lib, search_symbol):
logging.info('loading %s from %s', library_name, path)
return lib
else:
logging.warn('can\'t find symbol %s in %s', search_symbol,
path)
except Exception:
pass
return None
def find_library(possible_lib_names, search_symbol, library_name,
custom_path=None):
import ctypes.util
if custom_path:
return load_library(custom_path, search_symbol, library_name)
paths = [] paths = []
@ -79,16 +99,22 @@ def find_library(possible_lib_names, search_symbol, library_name):
if files: if files:
paths.extend(files) paths.extend(files)
for path in paths: for path in paths:
try: lib = load_library(path, search_symbol, library_name)
lib = CDLL(path) if lib:
if hasattr(lib, search_symbol): return lib
logging.info('loading %s from %s', library_name, path) return None
return lib
else:
logging.warn('can\'t find symbol %s in %s', search_symbol, def parse_mode(cipher_nme):
path) """
except Exception: Parse the cipher mode from cipher name
pass e.g. aes-128-gcm, the mode is gcm
:param cipher_nme: str cipher name, aes-128-cfb, aes-128-gcm ...
:return: str/None The mode, cfb, gcm ...
"""
hyphen = cipher_nme.rfind('-')
if hyphen > 0:
return cipher_nme[hyphen:]
return None return None
@ -97,29 +123,31 @@ def run_cipher(cipher, decipher):
import random import random
import time import time
BLOCK_SIZE = 16384 block_size = 16384
rounds = 1 * 1024 rounds = 1 * 1024
plain = urandom(BLOCK_SIZE * rounds) plain = urandom(block_size * rounds)
results = [] cipher_results = []
pos = 0 pos = 0
print('test start') print('test start')
start = time.time() start = time.time()
while pos < len(plain): while pos < len(plain):
l = random.randint(100, 32768) l = random.randint(100, 32768)
c = cipher.update(plain[pos:pos + l]) # print(pos, l)
results.append(c) c = cipher.encrypt_once(plain[pos:pos + l])
cipher_results.append(c)
pos += l pos += l
pos = 0 pos = 0
c = b''.join(results) # c = b''.join(cipher_results)
results = [] plain_results = []
while pos < len(plain): for c in cipher_results:
l = random.randint(100, 32768) # l = random.randint(100, 32768)
results.append(decipher.update(c[pos:pos + l])) l = len(c)
plain_results.append(decipher.decrypt_once(c))
pos += l pos += l
end = time.time() end = time.time()
print('speed: %d bytes/s' % (BLOCK_SIZE * rounds / (end - start))) print('speed: %d bytes/s' % (block_size * rounds / (end - start)))
assert b''.join(results) == plain assert b''.join(plain_results) == plain
def test_find_library(): def test_find_library():

View file

@ -23,12 +23,20 @@ import hashlib
import logging import logging
from shadowsocks import common from shadowsocks import common
from shadowsocks.crypto import rc4_md5, openssl, sodium, table from shadowsocks.crypto import rc4_md5, openssl, mbedtls, sodium, table
CIPHER_ENC_ENCRYPTION = 1
CIPHER_ENC_DECRYPTION = 0
METHOD_INFO_KEY_LEN = 0
METHOD_INFO_IV_LEN = 1
METHOD_INFO_CRYPTO = 2
method_supported = {} method_supported = {}
method_supported.update(rc4_md5.ciphers) method_supported.update(rc4_md5.ciphers)
method_supported.update(openssl.ciphers) method_supported.update(openssl.ciphers)
method_supported.update(mbedtls.ciphers)
method_supported.update(sodium.ciphers) method_supported.update(sodium.ciphers)
method_supported.update(table.ciphers) method_supported.update(table.ciphers)
@ -36,12 +44,11 @@ method_supported.update(table.ciphers)
def random_string(length): def random_string(length):
return os.urandom(length) return os.urandom(length)
cached_keys = {} cached_keys = {}
def try_cipher(key, method=None): def try_cipher(key, method=None, crypto_path=None):
Encryptor(key, method) Cryptor(key, method, crypto_path)
def EVP_BytesToKey(password, key_len, iv_len): def EVP_BytesToKey(password, key_len, iv_len):
@ -68,8 +75,15 @@ def EVP_BytesToKey(password, key_len, iv_len):
return key, iv return key, iv
class Encryptor(object): class Cryptor(object):
def __init__(self, password, method): def __init__(self, password, method, crypto_path=None):
"""
Crypto wrapper
:param password: str cipher password
:param method: str cipher
:param crypto_path: dict or none
{'openssl': path, 'sodium': path, 'mbedtls': path}
"""
self.password = password self.password = password
self.key = None self.key = None
self.method = method self.method = method
@ -77,16 +91,20 @@ class Encryptor(object):
self.cipher_iv = b'' self.cipher_iv = b''
self.decipher = None self.decipher = None
self.decipher_iv = None self.decipher_iv = None
self.crypto_path = crypto_path
method = method.lower() method = method.lower()
self._method_info = self.get_method_info(method) self._method_info = Cryptor.get_method_info(method)
if self._method_info: if self._method_info:
self.cipher = self.get_cipher(password, method, 1, self.cipher = self.get_cipher(
random_string(self._method_info[1])) password, method, CIPHER_ENC_ENCRYPTION,
random_string(self._method_info[METHOD_INFO_IV_LEN])
)
else: else:
logging.error('method %s not supported' % method) logging.error('method %s not supported' % method)
sys.exit(1) sys.exit(1)
def get_method_info(self, method): @staticmethod
def get_method_info(method):
method = method.lower() method = method.lower()
m = method_supported.get(method) m = method_supported.get(method)
return m return m
@ -97,46 +115,50 @@ class Encryptor(object):
def get_cipher(self, password, method, op, iv): def get_cipher(self, password, method, op, iv):
password = common.to_bytes(password) password = common.to_bytes(password)
m = self._method_info m = self._method_info
if m[0] > 0: if m[METHOD_INFO_KEY_LEN] > 0:
key, iv_ = EVP_BytesToKey(password, m[0], m[1]) key, _ = EVP_BytesToKey(password,
m[METHOD_INFO_KEY_LEN],
m[METHOD_INFO_IV_LEN])
else: else:
# key_length == 0 indicates we should use the key directly # key_length == 0 indicates we should use the key directly
key, iv = password, b'' key, iv = password, b''
self.key = key self.key = key
iv = iv[:m[1]] iv = iv[:m[METHOD_INFO_IV_LEN]]
if op == 1: if op == CIPHER_ENC_ENCRYPTION:
# this iv is for cipher not decipher # this iv is for cipher not decipher
self.cipher_iv = iv[:m[1]] self.cipher_iv = iv
return m[2](method, key, iv, op) return m[METHOD_INFO_CRYPTO](method, key, iv, op, self.crypto_path)
def encrypt(self, buf): def encrypt(self, buf):
if len(buf) == 0: if len(buf) == 0:
return buf return buf
if self.iv_sent: if self.iv_sent:
return self.cipher.update(buf) return self.cipher.encrypt(buf)
else: else:
self.iv_sent = True self.iv_sent = True
return self.cipher_iv + self.cipher.update(buf) return self.cipher_iv + self.cipher.encrypt(buf)
def decrypt(self, buf): def decrypt(self, buf):
if len(buf) == 0: if len(buf) == 0:
return buf return buf
if self.decipher is None: if self.decipher is None:
decipher_iv_len = self._method_info[1] decipher_iv_len = self._method_info[METHOD_INFO_IV_LEN]
decipher_iv = buf[:decipher_iv_len] decipher_iv = buf[:decipher_iv_len]
self.decipher_iv = decipher_iv self.decipher_iv = decipher_iv
self.decipher = self.get_cipher(self.password, self.method, 0, self.decipher = self.get_cipher(
iv=decipher_iv) self.password, self.method,
CIPHER_ENC_DECRYPTION,
decipher_iv
)
buf = buf[decipher_iv_len:] buf = buf[decipher_iv_len:]
if len(buf) == 0: if len(buf) == 0:
return buf return buf
return self.decipher.update(buf) return self.decipher.decrypt(buf)
def gen_key_iv(password, method): def gen_key_iv(password, method):
method = method.lower() method = method.lower()
(key_len, iv_len, m) = method_supported[method] (key_len, iv_len, m) = method_supported[method]
key = None
if key_len > 0: if key_len > 0:
key, _ = EVP_BytesToKey(password, key_len, iv_len) key, _ = EVP_BytesToKey(password, key_len, iv_len)
else: else:
@ -145,53 +167,38 @@ def gen_key_iv(password, method):
return key, iv, m return key, iv, m
def encrypt_all_m(key, iv, m, method, data): def encrypt_all_m(key, iv, m, method, data, crypto_path=None):
result = [] result = [iv]
result.append(iv) cipher = m(method, key, iv, 1, crypto_path)
cipher = m(method, key, iv, 1) result.append(cipher.encrypt_once(data))
result.append(cipher.update(data))
return b''.join(result) return b''.join(result)
def dencrypt_all(password, method, data): def decrypt_all(password, method, data, crypto_path=None):
result = [] result = []
method = method.lower() method = method.lower()
(key_len, iv_len, m) = method_supported[method] (key, iv, m) = gen_key_iv(password, method)
key = None iv = data[:len(iv)]
if key_len > 0: data = data[len(iv):]
key, _ = EVP_BytesToKey(password, key_len, iv_len) cipher = m(method, key, iv, CIPHER_ENC_DECRYPTION, crypto_path)
else: result.append(cipher.decrypt_once(data))
key = password
iv = data[:iv_len]
data = data[iv_len:]
cipher = m(method, key, iv, 0)
result.append(cipher.update(data))
return b''.join(result), key, iv return b''.join(result), key, iv
def encrypt_all(password, method, op, data): def encrypt_all(password, method, data, crypto_path=None):
result = [] result = []
method = method.lower() method = method.lower()
(key_len, iv_len, m) = method_supported[method] (key, iv, m) = gen_key_iv(password, method)
key = None result.append(iv)
if key_len > 0: cipher = m(method, key, iv, CIPHER_ENC_ENCRYPTION, crypto_path)
key, _ = EVP_BytesToKey(password, key_len, iv_len) result.append(cipher.encrypt_once(data))
else:
key = password
if op:
iv = random_string(iv_len)
result.append(iv)
else:
iv = data[:iv_len]
data = data[iv_len:]
cipher = m(method, key, iv, op)
result.append(cipher.update(data))
return b''.join(result) return b''.join(result)
CIPHERS_TO_TEST = [ CIPHERS_TO_TEST = [
'aes-128-cfb', 'aes-128-cfb',
'aes-256-cfb', 'aes-256-cfb',
'aes-256-gcm',
'rc4-md5', 'rc4-md5',
'salsa20', 'salsa20',
'chacha20', 'chacha20',
@ -204,8 +211,8 @@ def test_encryptor():
plain = urandom(10240) plain = urandom(10240)
for method in CIPHERS_TO_TEST: for method in CIPHERS_TO_TEST:
logging.warn(method) logging.warn(method)
encryptor = Encryptor(b'key', method) encryptor = Cryptor(b'key', method)
decryptor = Encryptor(b'key', method) decryptor = Cryptor(b'key', method)
cipher = encryptor.encrypt(plain) cipher = encryptor.encrypt(plain)
plain2 = decryptor.decrypt(cipher) plain2 = decryptor.decrypt(cipher)
assert plain == plain2 assert plain == plain2
@ -216,8 +223,8 @@ def test_encrypt_all():
plain = urandom(10240) plain = urandom(10240)
for method in CIPHERS_TO_TEST: for method in CIPHERS_TO_TEST:
logging.warn(method) logging.warn(method)
cipher = encrypt_all(b'key', method, 1, plain) cipher = encrypt_all(b'key', method, plain)
plain2 = encrypt_all(b'key', method, 0, cipher) plain2, key, iv = decrypt_all(b'key', method, cipher)
assert plain == plain2 assert plain == plain2
@ -228,7 +235,7 @@ def test_encrypt_all_m():
logging.warn(method) logging.warn(method)
key, iv, m = gen_key_iv(b'key', method) key, iv, m = gen_key_iv(b'key', method)
cipher = encrypt_all_m(key, iv, m, method, plain) cipher = encrypt_all_m(key, iv, m, method, plain)
plain2, key, iv = dencrypt_all(b'key', method, cipher) plain2, key, iv = decrypt_all(b'key', method, cipher)
assert plain == plain2 assert plain == plain2

View file

@ -71,6 +71,7 @@ class Manager(object):
port_password = config['port_password'] port_password = config['port_password']
del config['port_password'] del config['port_password']
config['crypto_path'] = config.get('crypto_path', dict())
for port, password in port_password.items(): for port, password in port_password.items():
a_config = config.copy() a_config = config.copy()
a_config['server_port'] = int(port) a_config['server_port'] = int(port)
@ -202,7 +203,7 @@ def test():
import time import time
import threading import threading
import struct import struct
from shadowsocks import encrypt from shadowsocks import cryptor
logging.basicConfig(level=5, logging.basicConfig(level=5,
format='%(asctime)s %(levelname)-8s %(message)s', format='%(asctime)s %(levelname)-8s %(message)s',
@ -252,7 +253,7 @@ def test():
# test statistics for TCP # test statistics for TCP
header = common.pack_addr(b'google.com') + struct.pack('>H', 80) header = common.pack_addr(b'google.com') + struct.pack('>H', 80)
data = encrypt.encrypt_all(b'asdfadsfasdf', 'aes-256-cfb', 1, data = cryptor.encrypt_all(b'asdfadsfasdf', 'aes-256-cfb',
header + b'GET /\r\n\r\n') header + b'GET /\r\n\r\n')
tcp_cli = socket.socket() tcp_cli = socket.socket()
tcp_cli.connect(('127.0.0.1', 7001)) tcp_cli.connect(('127.0.0.1', 7001))
@ -270,7 +271,7 @@ def test():
# test statistics for UDP # test statistics for UDP
header = common.pack_addr(b'127.0.0.1') + struct.pack('>H', 80) header = common.pack_addr(b'127.0.0.1') + struct.pack('>H', 80)
data = encrypt.encrypt_all(b'foobar2', 'aes-256-cfb', 1, data = cryptor.encrypt_all(b'foobar2', 'aes-256-cfb',
header + b'test') header + b'test')
udp_cli = socket.socket(type=socket.SOCK_DGRAM) udp_cli = socket.socket(type=socket.SOCK_DGRAM)
udp_cli.sendto(data, ('127.0.0.1', 8382)) udp_cli.sendto(data, ('127.0.0.1', 8382))

View file

@ -28,7 +28,7 @@ import traceback
from functools import wraps from functools import wraps
from shadowsocks.common import to_bytes, to_str, IPNetwork from shadowsocks.common import to_bytes, to_str, IPNetwork
from shadowsocks import encrypt from shadowsocks import cryptor
VERBOSE_LEVEL = 5 VERBOSE_LEVEL = 5
@ -125,6 +125,29 @@ def check_config(config, is_local):
# no need to specify configuration for daemon stop # no need to specify configuration for daemon stop
return return
if is_local:
if config.get('server', None) is None:
logging.error('server addr not specified')
print_local_help()
sys.exit(2)
else:
config['server'] = to_str(config['server'])
if config.get('tunnel_remote', None) is None:
logging.error('tunnel_remote addr not specified')
print_local_help()
sys.exit(2)
else:
config['tunnel_remote'] = to_str(config['tunnel_remote'])
else:
config['server'] = to_str(config.get('server', '0.0.0.0'))
try:
config['forbidden_ip'] = \
IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128'))
except Exception as e:
logging.error(e)
sys.exit(2)
if is_local and not config.get('password', None): if is_local and not config.get('password', None):
logging.error('password not specified') logging.error('password not specified')
print_help(is_local) print_help(is_local)
@ -143,6 +166,11 @@ def check_config(config, is_local):
if 'server_port' in config and type(config['server_port']) != list: if 'server_port' in config and type(config['server_port']) != list:
config['server_port'] = int(config['server_port']) config['server_port'] = int(config['server_port'])
if 'tunnel_remote_port' in config:
config['tunnel_remote_port'] = int(config['tunnel_remote_port'])
if 'tunnel_port' in config:
config['tunnel_port'] = int(config['tunnel_port'])
if config.get('local_address', '') in [b'0.0.0.0']: if config.get('local_address', '') in [b'0.0.0.0']:
logging.warn('warning: local set to listen on 0.0.0.0, it\'s not safe') logging.warn('warning: local set to listen on 0.0.0.0, it\'s not safe')
if config.get('server', '') in ['127.0.0.1', 'localhost']: if config.get('server', '') in ['127.0.0.1', 'localhost']:
@ -168,8 +196,19 @@ def check_config(config, is_local):
if os.name != 'posix': if os.name != 'posix':
logging.error('user can be used only on Unix') logging.error('user can be used only on Unix')
sys.exit(1) sys.exit(1)
if config.get('dns_server', None) is not None:
if type(config['dns_server']) != list:
config['dns_server'] = to_str(config['dns_server'])
else:
config['dns_server'] = [to_str(ds) for ds in config['dns_server']]
logging.info('Specified DNS server: %s' % config['dns_server'])
encrypt.try_cipher(config['password'], config['method']) config['crypto_path'] = {'openssl': config['libopenssl'],
'mbedtls': config['libmbedtls'],
'sodium': config['libsodium']}
cryptor.try_cipher(config['password'], config['method'],
config['crypto_path'])
def get_config(is_local): def get_config(is_local):
@ -180,12 +219,12 @@ def get_config(is_local):
if is_local: if is_local:
shortopts = 'hd:s:b:p:k:l:m:c:t:vqa' shortopts = 'hd:s:b:p:k:l:m:c:t:vqa'
longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'user=', longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'user=',
'version'] 'libopenssl=', 'libmbedtls=', 'libsodium=', 'version']
else: else:
shortopts = 'hd:s:p:k:m:c:t:vqa' shortopts = 'hd:s:p:k:m:c:t:vqa'
longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=',
'forbidden-ip=', 'user=', 'manager-address=', 'version', 'forbidden-ip=', 'user=', 'manager-address=', 'version',
'prefer-ipv6'] 'libopenssl=', 'libmbedtls=', 'libsodium=', 'prefer-ipv6']
try: try:
config_path = find_config() config_path = find_config()
optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts) optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
@ -229,10 +268,16 @@ def get_config(is_local):
config['timeout'] = int(value) config['timeout'] = int(value)
elif key == '--fast-open': elif key == '--fast-open':
config['fast_open'] = True config['fast_open'] = True
elif key == '--libopenssl':
config['libopenssl'] = to_str(value)
elif key == '--libmbedtls':
config['libmbedtls'] = to_str(value)
elif key == '--libsodium':
config['libsodium'] = to_str(value)
elif key == '--workers': elif key == '--workers':
config['workers'] = int(value) config['workers'] = int(value)
elif key == '--manager-address': elif key == '--manager-address':
config['manager_address'] = value config['manager_address'] = to_str(value)
elif key == '--user': elif key == '--user':
config['user'] = to_str(value) config['user'] = to_str(value)
elif key == '--forbidden-ip': elif key == '--forbidden-ip':
@ -280,22 +325,15 @@ def get_config(is_local):
config['local_port'] = config.get('local_port', 1080) config['local_port'] = config.get('local_port', 1080)
config['one_time_auth'] = config.get('one_time_auth', False) config['one_time_auth'] = config.get('one_time_auth', False)
config['prefer_ipv6'] = config.get('prefer_ipv6', False) config['prefer_ipv6'] = config.get('prefer_ipv6', False)
if is_local:
if config.get('server', None) is None:
logging.error('server addr not specified')
print_local_help()
sys.exit(2)
else:
config['server'] = to_str(config['server'])
else:
config['server'] = to_str(config.get('server', '0.0.0.0'))
try:
config['forbidden_ip'] = \
IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128'))
except Exception as e:
logging.error(e)
sys.exit(2)
config['server_port'] = config.get('server_port', 8388) config['server_port'] = config.get('server_port', 8388)
config['dns_server'] = config.get('dns_server', None)
config['libopenssl'] = config.get('libopenssl', None)
config['libmbedtls'] = config.get('libmbedtls', None)
config['libsodium'] = config.get('libsodium', None)
config['tunnel_remote'] = to_str(config.get('tunnel_remote', '8.8.8.8'))
config['tunnel_remote_port'] = config.get('tunnel_remote_port', 53)
config['tunnel_port'] = config.get('tunnel_port', 53)
logging.getLogger('').handlers = [] logging.getLogger('').handlers = []
logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE') logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE')
@ -340,16 +378,40 @@ Proxy options:
-l LOCAL_PORT local port, default: 1080 -l LOCAL_PORT local port, default: 1080
-k PASSWORD password -k PASSWORD password
-m METHOD encryption method, default: aes-256-cfb -m METHOD encryption method, default: aes-256-cfb
Sodium:
chacha20-poly1305, chacha20-ietf-poly1305,
xchacha20-ietf-poly1305,
sodium:aes-256-gcm,
salsa20, chacha20, chacha20-ietf.
Sodium 1.0.12:
xchacha20
OpenSSL:
aes-{128|192|256}-gcm, aes-{128|192|256}-cfb,
aes-{128|192|256}-ofb, aes-{128|192|256}-ctr,
camellia-{128|192|256}-cfb,
bf-cfb, cast5-cfb, des-cfb, idea-cfb,
rc2-cfb, seed-cfb,
rc4, rc4-md5, table.
OpenSSL 1.1:
aes-{128|192|256}-ocb
mbedTLS:
mbedtls:aes-{128|192|256}-cfb128,
mbedtls:aes-{128|192|256}-ctr,
mbedtls:camellia-{128|192|256}-cfb128,
mbedtls:aes-{128|192|256}-gcm
-t TIMEOUT timeout in seconds, default: 300 -t TIMEOUT timeout in seconds, default: 300
-a ONE_TIME_AUTH one time auth -a ONE_TIME_AUTH one time auth
--fast-open use TCP_FASTOPEN, requires Linux 3.7+ --fast-open use TCP_FASTOPEN, requires Linux 3.7+
--libopenssl=PATH custom openssl crypto lib path
--libmbedtls=PATH custom mbedtls crypto lib path
--libsodium=PATH custom sodium crypto lib path
General options: General options:
-h, --help show this help message and exit -h, --help show this help message and exit
-d start/stop/restart daemon mode -d start/stop/restart daemon mode
--pid-file PID_FILE pid file for daemon mode --pid-file=PID_FILE pid file for daemon mode
--log-file LOG_FILE log file for daemon mode --log-file=LOG_FILE log file for daemon mode
--user USER username to run as --user=USER username to run as
-v, -vv verbose mode -v, -vv verbose mode
-q, -qq quiet mode, only show warnings/errors -q, -qq quiet mode, only show warnings/errors
--version show version information --version show version information
@ -370,13 +432,37 @@ Proxy options:
-p SERVER_PORT server port, default: 8388 -p SERVER_PORT server port, default: 8388
-k PASSWORD password -k PASSWORD password
-m METHOD encryption method, default: aes-256-cfb -m METHOD encryption method, default: aes-256-cfb
Sodium:
chacha20-poly1305, chacha20-ietf-poly1305,
xchacha20-ietf-poly1305,
sodium:aes-256-gcm,
salsa20, chacha20, chacha20-ietf.
Sodium 1.0.12:
xchacha20
OpenSSL:
aes-{128|192|256}-gcm, aes-{128|192|256}-cfb,
aes-{128|192|256}-ofb, aes-{128|192|256}-ctr,
camellia-{128|192|256}-cfb,
bf-cfb, cast5-cfb, des-cfb, idea-cfb,
rc2-cfb, seed-cfb,
rc4, rc4-md5, table.
OpenSSL 1.1:
aes-{128|192|256}-ocb
mbedTLS:
mbedtls:aes-{128|192|256}-cfb128,
mbedtls:aes-{128|192|256}-ctr,
mbedtls:camellia-{128|192|256}-cfb128,
mbedtls:aes-{128|192|256}-gcm
-t TIMEOUT timeout in seconds, default: 300 -t TIMEOUT timeout in seconds, default: 300
-a ONE_TIME_AUTH one time auth -a ONE_TIME_AUTH one time auth
--fast-open use TCP_FASTOPEN, requires Linux 3.7+ --fast-open use TCP_FASTOPEN, requires Linux 3.7+
--workers WORKERS number of workers, available on Unix/Linux --workers=WORKERS number of workers, available on Unix/Linux
--forbidden-ip IPLIST comma seperated IP list forbidden to connect --forbidden-ip=IPLIST comma seperated IP list forbidden to connect
--manager-address ADDR optional server manager UDP address, see wiki --manager-address=ADDR optional server manager UDP address, see wiki
--prefer-ipv6 resolve ipv6 address first --prefer-ipv6 resolve ipv6 address first
--libopenssl=PATH custom openssl crypto lib path
--libmbedtls=PATH custom mbedtls crypto lib path
--libsodium=PATH custom sodium crypto lib path
General options: General options:
-h, --help show this help message and exit -h, --help show this help message and exit

View file

@ -26,7 +26,7 @@ import logging
import traceback import traceback
import random import random
from shadowsocks import encrypt, eventloop, shell, common from shadowsocks import cryptor, eventloop, shell, common
from shadowsocks.common import parse_header, onetimeauth_verify, \ from shadowsocks.common import parse_header, onetimeauth_verify, \
onetimeauth_gen, ONETIMEAUTH_BYTES, ONETIMEAUTH_CHUNK_BYTES, \ onetimeauth_gen, ONETIMEAUTH_BYTES, ONETIMEAUTH_CHUNK_BYTES, \
ONETIMEAUTH_CHUNK_DATA_LEN, ADDRTYPE_AUTH ONETIMEAUTH_CHUNK_DATA_LEN, ADDRTYPE_AUTH
@ -93,10 +93,12 @@ WAIT_STATUS_WRITING = 2
WAIT_STATUS_READWRITING = WAIT_STATUS_READING | WAIT_STATUS_WRITING WAIT_STATUS_READWRITING = WAIT_STATUS_READING | WAIT_STATUS_WRITING
BUF_SIZE = 32 * 1024 BUF_SIZE = 32 * 1024
UP_STREAM_BUF_SIZE = 16 * 1024
DOWN_STREAM_BUF_SIZE = 32 * 1024
# helper exceptions for TCPRelayHandler # helper exceptions for TCPRelayHandler
class BadSocksHeader(Exception): class BadSocksHeader(Exception):
pass pass
@ -106,6 +108,7 @@ class NoAcceptableMethods(Exception):
class TCPRelayHandler(object): class TCPRelayHandler(object):
def __init__(self, server, fd_to_handlers, loop, local_sock, config, def __init__(self, server, fd_to_handlers, loop, local_sock, config,
dns_resolver, is_local): dns_resolver, is_local):
self._server = server self._server = server
@ -115,17 +118,19 @@ class TCPRelayHandler(object):
self._remote_sock = None self._remote_sock = None
self._config = config self._config = config
self._dns_resolver = dns_resolver self._dns_resolver = dns_resolver
self.tunnel_remote = config.get('tunnel_remote', "8.8.8.8")
self.tunnel_remote_port = config.get('tunnel_remote_port', 53)
self.tunnel_port = config.get('tunnel_port', 53)
self._is_tunnel = server._is_tunnel
# TCP Relay works as either sslocal or ssserver # TCP Relay works as either sslocal or ssserver
# if is_local, this is sslocal # if is_local, this is sslocal
self._is_local = is_local self._is_local = is_local
self._stage = STAGE_INIT self._stage = STAGE_INIT
self._encryptor = encrypt.Encryptor(config['password'], self._cryptor = cryptor.Cryptor(config['password'],
config['method']) config['method'],
if 'one_time_auth' in config and config['one_time_auth']: config['crypto_path'])
self._ota_enable = True self._ota_enable = config.get('one_time_auth', False)
else:
self._ota_enable = False
self._ota_enable_session = self._ota_enable self._ota_enable_session = self._ota_enable
self._ota_buff_head = b'' self._ota_buff_head = b''
self._ota_buff_data = b'' self._ota_buff_data = b''
@ -138,10 +143,7 @@ class TCPRelayHandler(object):
self._downstream_status = WAIT_STATUS_INIT self._downstream_status = WAIT_STATUS_INIT
self._client_address = local_sock.getpeername()[:2] self._client_address = local_sock.getpeername()[:2]
self._remote_address = None self._remote_address = None
if 'forbidden_ip' in config: self._forbidden_iplist = config.get('forbidden_ip')
self._forbidden_iplist = config['forbidden_ip']
else:
self._forbidden_iplist = None
if is_local: if is_local:
self._chosen_server = self._get_a_server() self._chosen_server = self._get_a_server()
fd_to_handlers[local_sock.fileno()] = self fd_to_handlers[local_sock.fileno()] = self
@ -256,10 +258,9 @@ class TCPRelayHandler(object):
else: else:
self._data_to_write_to_remote.append(data) self._data_to_write_to_remote.append(data)
return return
if self._ota_enable_session: if self._ota_enable_session:
data = self._ota_chunk_data_gen(data) data = self._ota_chunk_data_gen(data)
data = self._encryptor.encrypt(data) data = self._cryptor.encrypt(data)
self._data_to_write_to_remote.append(data) self._data_to_write_to_remote.append(data)
if self._config['fast_open'] and not self._fastopen_connected: if self._config['fast_open'] and not self._fastopen_connected:
@ -299,29 +300,36 @@ class TCPRelayHandler(object):
@shell.exception_handle(self_=True, destroy=True, conn_err=True) @shell.exception_handle(self_=True, destroy=True, conn_err=True)
def _handle_stage_addr(self, data): def _handle_stage_addr(self, data):
if self._is_local: if self._is_local:
cmd = common.ord(data[1]) if self._is_tunnel:
if cmd == CMD_UDP_ASSOCIATE: # add ss header to data
logging.debug('UDP associate') tunnel_remote = self.tunnel_remote
if self._local_sock.family == socket.AF_INET6: tunnel_remote_port = self.tunnel_remote_port
header = b'\x05\x00\x00\x04' data = common.add_header(tunnel_remote,
else: tunnel_remote_port, data)
header = b'\x05\x00\x00\x01'
addr, port = self._local_sock.getsockname()[:2]
addr_to_send = socket.inet_pton(self._local_sock.family,
addr)
port_to_send = struct.pack('>H', port)
self._write_to_sock(header + addr_to_send + port_to_send,
self._local_sock)
self._stage = STAGE_UDP_ASSOC
# just wait for the client to disconnect
return
elif cmd == CMD_CONNECT:
# just trim VER CMD RSV
data = data[3:]
else: else:
logging.error('unknown command %d', cmd) cmd = common.ord(data[1])
self.destroy() if cmd == CMD_UDP_ASSOCIATE:
return logging.debug('UDP associate')
if self._local_sock.family == socket.AF_INET6:
header = b'\x05\x00\x00\x04'
else:
header = b'\x05\x00\x00\x01'
addr, port = self._local_sock.getsockname()[:2]
addr_to_send = socket.inet_pton(self._local_sock.family,
addr)
port_to_send = struct.pack('>H', port)
self._write_to_sock(header + addr_to_send + port_to_send,
self._local_sock)
self._stage = STAGE_UDP_ASSOC
# just wait for the client to disconnect
return
elif cmd == CMD_CONNECT:
# just trim VER CMD RSV
data = data[3:]
else:
logging.error('unknown command %d', cmd)
self.destroy()
return
header_result = parse_header(data) header_result = parse_header(data)
if header_result is None: if header_result is None:
raise Exception('can not parse header') raise Exception('can not parse header')
@ -342,7 +350,7 @@ class TCPRelayHandler(object):
offset = header_length + ONETIMEAUTH_BYTES offset = header_length + ONETIMEAUTH_BYTES
_hash = data[header_length: offset] _hash = data[header_length: offset]
_data = data[:header_length] _data = data[:header_length]
key = self._encryptor.decipher_iv + self._encryptor.key key = self._cryptor.decipher_iv + self._cryptor.key
if onetimeauth_verify(_hash, _data, key) is False: if onetimeauth_verify(_hash, _data, key) is False:
logging.warn('one time auth fail') logging.warn('one time auth fail')
self.destroy() self.destroy()
@ -353,17 +361,21 @@ class TCPRelayHandler(object):
self._update_stream(STREAM_UP, WAIT_STATUS_WRITING) self._update_stream(STREAM_UP, WAIT_STATUS_WRITING)
self._stage = STAGE_DNS self._stage = STAGE_DNS
if self._is_local: if self._is_local:
# forward address to remote # jump over socks5 response
self._write_to_sock((b'\x05\x00\x00\x01' if not self._is_tunnel:
b'\x00\x00\x00\x00\x10\x10'), # forward address to remote
self._local_sock) self._write_to_sock((b'\x05\x00\x00\x01'
b'\x00\x00\x00\x00\x10\x10'),
self._local_sock)
# spec https://shadowsocks.org/en/spec/one-time-auth.html # spec https://shadowsocks.org/en/spec/one-time-auth.html
# ATYP & 0x10 == 0x10, then OTA is enabled. # ATYP & 0x10 == 0x10, then OTA is enabled.
if self._ota_enable_session: if self._ota_enable_session:
data = common.chr(addrtype | ADDRTYPE_AUTH) + data[1:] data = common.chr(addrtype | ADDRTYPE_AUTH) + data[1:]
key = self._encryptor.cipher_iv + self._encryptor.key key = self._cryptor.cipher_iv + self._cryptor.key
data += onetimeauth_gen(data, key) _header = data[:header_length]
data_to_send = self._encryptor.encrypt(data) sha110 = onetimeauth_gen(data, key)
data = _header + sha110 + data[header_length:]
data_to_send = self._cryptor.encrypt(data)
self._data_to_write_to_remote.append(data_to_send) self._data_to_write_to_remote.append(data_to_send)
# notice here may go into _handle_dns_resolved directly # notice here may go into _handle_dns_resolved directly
self._dns_resolver.resolve(self._chosen_server[0], self._dns_resolver.resolve(self._chosen_server[0],
@ -466,7 +478,7 @@ class TCPRelayHandler(object):
_hash = self._ota_buff_head[ONETIMEAUTH_CHUNK_DATA_LEN:] _hash = self._ota_buff_head[ONETIMEAUTH_CHUNK_DATA_LEN:]
_data = self._ota_buff_data _data = self._ota_buff_data
index = struct.pack('>I', self._ota_chunk_idx) index = struct.pack('>I', self._ota_chunk_idx)
key = self._encryptor.decipher_iv + index key = self._cryptor.decipher_iv + index
if onetimeauth_verify(_hash, _data, key) is False: if onetimeauth_verify(_hash, _data, key) is False:
logging.warn('one time auth fail, drop chunk !') logging.warn('one time auth fail, drop chunk !')
else: else:
@ -481,7 +493,7 @@ class TCPRelayHandler(object):
def _ota_chunk_data_gen(self, data): def _ota_chunk_data_gen(self, data):
data_len = struct.pack(">H", len(data)) data_len = struct.pack(">H", len(data))
index = struct.pack('>I', self._ota_chunk_idx) index = struct.pack('>I', self._ota_chunk_idx)
key = self._encryptor.cipher_iv + index key = self._cryptor.cipher_iv + index
sha110 = onetimeauth_gen(data, key) sha110 = onetimeauth_gen(data, key)
self._ota_chunk_idx += 1 self._ota_chunk_idx += 1
return data_len + sha110 + data return data_len + sha110 + data
@ -490,7 +502,7 @@ class TCPRelayHandler(object):
if self._is_local: if self._is_local:
if self._ota_enable_session: if self._ota_enable_session:
data = self._ota_chunk_data_gen(data) data = self._ota_chunk_data_gen(data)
data = self._encryptor.encrypt(data) data = self._cryptor.encrypt(data)
self._write_to_sock(data, self._remote_sock) self._write_to_sock(data, self._remote_sock)
else: else:
if self._ota_enable_session: if self._ota_enable_session:
@ -544,8 +556,12 @@ class TCPRelayHandler(object):
return return
is_local = self._is_local is_local = self._is_local
data = None data = None
if is_local:
buf_size = UP_STREAM_BUF_SIZE
else:
buf_size = DOWN_STREAM_BUF_SIZE
try: try:
data = self._local_sock.recv(BUF_SIZE) data = self._local_sock.recv(buf_size)
except (OSError, IOError) as e: except (OSError, IOError) as e:
if eventloop.errno_from_exception(e) in \ if eventloop.errno_from_exception(e) in \
(errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK): (errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK):
@ -555,14 +571,19 @@ class TCPRelayHandler(object):
return return
self._update_activity(len(data)) self._update_activity(len(data))
if not is_local: if not is_local:
data = self._encryptor.decrypt(data) data = self._cryptor.decrypt(data)
if not data: if not data:
return return
if self._stage == STAGE_STREAM: if self._stage == STAGE_STREAM:
self._handle_stage_stream(data) self._handle_stage_stream(data)
return return
elif is_local and self._stage == STAGE_INIT: elif is_local and self._stage == STAGE_INIT:
self._handle_stage_init(data) # jump over socks5 init
if self._is_tunnel:
self._handle_stage_addr(data)
return
else:
self._handle_stage_init(data)
elif self._stage == STAGE_CONNECTING: elif self._stage == STAGE_CONNECTING:
self._handle_stage_connecting(data) self._handle_stage_connecting(data)
elif (is_local and self._stage == STAGE_ADDR) or \ elif (is_local and self._stage == STAGE_ADDR) or \
@ -572,8 +593,12 @@ class TCPRelayHandler(object):
def _on_remote_read(self): def _on_remote_read(self):
# handle all remote read events # handle all remote read events
data = None data = None
if self._is_local:
buf_size = UP_STREAM_BUF_SIZE
else:
buf_size = DOWN_STREAM_BUF_SIZE
try: try:
data = self._remote_sock.recv(BUF_SIZE) data = self._remote_sock.recv(buf_size)
except (OSError, IOError) as e: except (OSError, IOError) as e:
if eventloop.errno_from_exception(e) in \ if eventloop.errno_from_exception(e) in \
@ -584,9 +609,9 @@ class TCPRelayHandler(object):
return return
self._update_activity(len(data)) self._update_activity(len(data))
if self._is_local: if self._is_local:
data = self._encryptor.decrypt(data) data = self._cryptor.decrypt(data)
else: else:
data = self._encryptor.encrypt(data) data = self._cryptor.encrypt(data)
try: try:
self._write_to_sock(data, self._local_sock) self._write_to_sock(data, self._local_sock)
except Exception as e: except Exception as e:
@ -627,6 +652,7 @@ class TCPRelayHandler(object):
logging.error(eventloop.get_sock_error(self._remote_sock)) logging.error(eventloop.get_sock_error(self._remote_sock))
self.destroy() self.destroy()
@shell.exception_handle(self_=True, destroy=True)
def handle_event(self, sock, event): def handle_event(self, sock, event):
# handle all events in this handler and dispatch them to methods # handle all events in this handler and dispatch them to methods
if self._stage == STAGE_DESTROYED: if self._stage == STAGE_DESTROYED:
@ -693,6 +719,7 @@ class TCPRelayHandler(object):
class TCPRelay(object): class TCPRelay(object):
def __init__(self, config, dns_resolver, is_local, stat_callback=None): def __init__(self, config, dns_resolver, is_local, stat_callback=None):
self._config = config self._config = config
self._is_local = is_local self._is_local = is_local
@ -700,6 +727,7 @@ class TCPRelay(object):
self._closed = False self._closed = False
self._eventloop = None self._eventloop = None
self._fd_to_handlers = {} self._fd_to_handlers = {}
self._is_tunnel = False
self._timeout = config['timeout'] self._timeout = config['timeout']
self._timeouts = [] # a list for all the handlers self._timeouts = [] # a list for all the handlers

74
shadowsocks/tunnel.py Executable file
View file

@ -0,0 +1,74 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2012-2015 clowwindy
#
# 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 sys
import os
import logging
import signal
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../'))
from shadowsocks import shell, daemon, eventloop, tcprelay, udprelay, asyncdns
@shell.exception_handle(self_=False, exit_code=1)
def main():
shell.check_python()
# fix py2exe
if hasattr(sys, "frozen") and sys.frozen in \
("windows_exe", "console_exe"):
p = os.path.dirname(os.path.abspath(sys.executable))
os.chdir(p)
config = shell.get_config(True)
daemon.daemon_exec(config)
dns_resolver = asyncdns.DNSResolver()
loop = eventloop.EventLoop()
dns_resolver.add_to_loop(loop)
_config = config.copy()
_config["local_port"] = _config["tunnel_port"]
logging.info("starting tcp tunnel at %s:%d forward to %s:%d" %
(_config['local_address'], _config['local_port'],
_config['tunnel_remote'], _config['tunnel_remote_port']))
tunnel_tcp_server = tcprelay.TCPRelay(_config, dns_resolver, True)
tunnel_tcp_server._is_tunnel = True
tunnel_tcp_server.add_to_loop(loop)
logging.info("starting udp tunnel at %s:%d forward to %s:%d" %
(_config['local_address'], _config['local_port'],
_config['tunnel_remote'], _config['tunnel_remote_port']))
tunnel_udp_server = udprelay.UDPRelay(_config, dns_resolver, True)
tunnel_udp_server._is_tunnel = True
tunnel_udp_server.add_to_loop(loop)
def handler(signum, _):
logging.warn('received SIGQUIT, doing graceful shutting down..')
tunnel_tcp_server.close(next_tick=True)
tunnel_udp_server.close(next_tick=True)
signal.signal(getattr(signal, 'SIGQUIT', signal.SIGTERM), handler)
def int_handler(signum, _):
sys.exit(1)
signal.signal(signal.SIGINT, int_handler)
daemon.set_user(config.get('user', None))
loop.run()
if __name__ == '__main__':
main()

View file

@ -68,7 +68,7 @@ import struct
import errno import errno
import random import random
from shadowsocks import encrypt, eventloop, lru_cache, common, shell from shadowsocks import cryptor, eventloop, lru_cache, common, shell
from shadowsocks.common import parse_header, pack_addr, onetimeauth_verify, \ from shadowsocks.common import parse_header, pack_addr, onetimeauth_verify, \
onetimeauth_gen, ONETIMEAUTH_BYTES, ADDRTYPE_AUTH onetimeauth_gen, ONETIMEAUTH_BYTES, ADDRTYPE_AUTH
@ -82,6 +82,7 @@ def client_key(source_addr, server_af):
class UDPRelay(object): class UDPRelay(object):
def __init__(self, config, dns_resolver, is_local, stat_callback=None): def __init__(self, config, dns_resolver, is_local, stat_callback=None):
self._config = config self._config = config
if is_local: if is_local:
@ -94,14 +95,15 @@ class UDPRelay(object):
self._listen_port = config['server_port'] self._listen_port = config['server_port']
self._remote_addr = None self._remote_addr = None
self._remote_port = None self._remote_port = None
self.tunnel_remote = config.get('tunnel_remote', "8.8.8.8")
self.tunnel_remote_port = config.get('tunnel_remote_port', 53)
self.tunnel_port = config.get('tunnel_port', 53)
self._is_tunnel = False
self._dns_resolver = dns_resolver self._dns_resolver = dns_resolver
self._password = common.to_bytes(config['password']) self._password = common.to_bytes(config['password'])
self._method = config['method'] self._method = config['method']
self._timeout = config['timeout'] self._timeout = config['timeout']
if 'one_time_auth' in config and config['one_time_auth']: self._ota_enable = config.get('one_time_auth', False)
self._ota_enable = True
else:
self._ota_enable = False
self._ota_enable_session = self._ota_enable self._ota_enable_session = self._ota_enable
self._is_local = is_local self._is_local = is_local
self._cache = lru_cache.LRUCache(timeout=config['timeout'], self._cache = lru_cache.LRUCache(timeout=config['timeout'],
@ -112,10 +114,8 @@ class UDPRelay(object):
self._eventloop = None self._eventloop = None
self._closed = False self._closed = False
self._sockets = set() self._sockets = set()
if 'forbidden_ip' in config: self._forbidden_iplist = config.get('forbidden_ip')
self._forbidden_iplist = config['forbidden_ip'] self._crypto_path = config['crypto_path']
else:
self._forbidden_iplist = None
addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0, addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0,
socket.SOCK_DGRAM, socket.SOL_UDP) socket.SOCK_DGRAM, socket.SOL_UDP)
@ -158,27 +158,37 @@ class UDPRelay(object):
if self._stat_callback: if self._stat_callback:
self._stat_callback(self._listen_port, len(data)) self._stat_callback(self._listen_port, len(data))
if self._is_local: if self._is_local:
frag = common.ord(data[2]) if self._is_tunnel:
if frag != 0: # add ss header to data
logging.warn('UDP drop a message since frag is not 0') tunnel_remote = self.tunnel_remote
return tunnel_remote_port = self.tunnel_remote_port
data = common.add_header(tunnel_remote,
tunnel_remote_port, data)
else: else:
data = data[3:] frag = common.ord(data[2])
if frag != 0:
logging.warn('UDP drop a message since frag is not 0')
return
else:
data = data[3:]
else: else:
data, key, iv = encrypt.dencrypt_all(self._password,
self._method,
data)
# decrypt data # decrypt data
try:
data, key, iv = cryptor.decrypt_all(self._password,
self._method,
data, self._crypto_path)
except Exception:
logging.debug('UDP handle_server: decrypt data failed')
return
if not data: if not data:
logging.debug( logging.debug('UDP handle_server: data is empty after decrypt')
'UDP handle_server: data is empty after decrypt'
)
return return
header_result = parse_header(data) header_result = parse_header(data)
if header_result is None: if header_result is None:
return return
addrtype, dest_addr, dest_port, header_length = header_result addrtype, dest_addr, dest_port, header_length = header_result
logging.info("udp data to %s:%d from %s:%d"
% (dest_addr, dest_port, r_addr[0], r_addr[1]))
if self._is_local: if self._is_local:
server_addr, server_port = self._get_a_server() server_addr, server_port = self._get_a_server()
else: else:
@ -228,11 +238,16 @@ class UDPRelay(object):
self._eventloop.add(client, eventloop.POLL_IN, self) self._eventloop.add(client, eventloop.POLL_IN, self)
if self._is_local: if self._is_local:
key, iv, m = encrypt.gen_key_iv(self._password, self._method) key, iv, m = cryptor.gen_key_iv(self._password, self._method)
# spec https://shadowsocks.org/en/spec/one-time-auth.html # spec https://shadowsocks.org/en/spec/one-time-auth.html
if self._ota_enable_session: if self._ota_enable_session:
data = self._ota_chunk_data_gen(key, iv, data) data = self._ota_chunk_data_gen(key, iv, data)
data = encrypt.encrypt_all_m(key, iv, m, self._method, data) try:
data = cryptor.encrypt_all_m(key, iv, m, self._method, data,
self._crypto_path)
except Exception:
logging.debug("UDP handle_server: encrypt data failed")
return
if not data: if not data:
return return
else: else:
@ -261,22 +276,38 @@ class UDPRelay(object):
# drop # drop
return return
data = pack_addr(r_addr[0]) + struct.pack('>H', r_addr[1]) + data data = pack_addr(r_addr[0]) + struct.pack('>H', r_addr[1]) + data
response = encrypt.encrypt_all(self._password, self._method, 1, try:
data) response = cryptor.encrypt_all(self._password,
self._method, data,
self._crypto_path)
except Exception:
logging.debug("UDP handle_client: encrypt data failed")
return
if not response: if not response:
return return
else: else:
data = encrypt.encrypt_all(self._password, self._method, 0, try:
data) data, key, iv = cryptor.decrypt_all(self._password,
self._method, data,
self._crypto_path)
except Exception:
logging.debug('UDP handle_client: decrypt data failed')
return
if not data: if not data:
return return
header_result = parse_header(data) header_result = parse_header(data)
if header_result is None: if header_result is None:
return return
addrtype, dest_addr, dest_port, header_length = header_result addrtype, dest_addr, dest_port, header_length = header_result
response = b'\x00\x00\x00' + data if self._is_tunnel:
# remove ss header
response = data[header_length:]
else:
response = b'\x00\x00\x00' + data
client_addr = self._client_fd_to_server_addr.get(sock.fileno()) client_addr = self._client_fd_to_server_addr.get(sock.fileno())
if client_addr: if client_addr:
logging.debug("send udp response to %s:%d"
% (client_addr[0], client_addr[1]))
self._server_socket.sendto(response, client_addr) self._server_socket.sendto(response, client_addr)
else: else:
# this packet is from somewhere else we know # this packet is from somewhere else we know

View file

@ -1,10 +1,10 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"aes_password", "password":"aes_password",
"timeout":60, "timeout":60,
"method":"aes-256-cfb1", "method":"aes-256-cfb1",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false "fast_open":false
} }

View file

@ -1,10 +1,10 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"aes_password", "password":"aes_password",
"timeout":60, "timeout":60,
"method":"aes-256-cfb8", "method":"aes-256-cfb8",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false "fast_open":false
} }

View file

@ -1,10 +1,10 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"aes_password", "password":"aes_password",
"timeout":60, "timeout":60,
"method":"aes-256-ctr", "method":"aes-256-ctr",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false "fast_open":false
} }

10
tests/aes-gcm.json Normal file
View file

@ -0,0 +1,10 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"aes-256-gcm",
"local_address":"127.0.0.1",
"fast_open":false
}

11
tests/aes-ocb.json Normal file
View file

@ -0,0 +1,11 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"aes-256-ocb",
"local_address":"127.0.0.1",
"fast_open":false,
"libopenssl":"/usr/local/lib/libcrypto.so.1.1"
}

10
tests/aes-ofb.json Normal file
View file

@ -0,0 +1,10 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"aes-256-ofb",
"local_address":"127.0.0.1",
"fast_open":false
}

View file

@ -1,10 +1,10 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"aes_password", "password":"aes_password",
"timeout":60, "timeout":60,
"method":"aes-256-cfb", "method":"aes-256-cfb",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false "fast_open":false
} }

10
tests/camellia.json Normal file
View file

@ -0,0 +1,10 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"camellia_password",
"timeout":60,
"method":"camellia-256-cfb",
"local_address":"127.0.0.1",
"fast_open":false
}

View file

@ -0,0 +1,10 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"salsa20_password",
"timeout":60,
"method":"chacha20-ietf-poly1305",
"local_address":"127.0.0.1",
"fast_open":false
}

View file

@ -1,10 +1,10 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"salsa20_password", "password":"salsa20_password",
"timeout":60, "timeout":60,
"method":"chacha20-ietf", "method":"chacha20-ietf",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false "fast_open":false
} }

View file

@ -0,0 +1,10 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"salsa20_password",
"timeout":60,
"method":"chacha20-poly1305",
"local_address":"127.0.0.1",
"fast_open":false
}

View file

@ -1,10 +1,10 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"salsa20_password", "password":"chacha20_password",
"timeout":60, "timeout":60,
"method":"chacha20", "method":"chacha20",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false "fast_open":false
} }

View file

@ -1,10 +1,10 @@
{ {
"server":["127.0.0.1", "127.0.0.1"], "server":["127.0.0.1", "127.0.0.1"],
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"aes_password", "password":"aes_password",
"timeout":60, "timeout":60,
"method":"aes-256-cfb", "method":"aes-256-cfb",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false "fast_open":false
} }

View file

@ -1,10 +1,10 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"fastopen_password", "password":"fastopen_password",
"timeout":60, "timeout":60,
"method":"aes-256-cfb", "method":"aes-256-cfb",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":true "fast_open":true
} }

View file

@ -1,10 +1,10 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"aes_password", "password":"aes_password",
"timeout":15, "timeout":15,
"method":"aes-256-cfb", "method":"aes-256-cfb",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false "fast_open":false
} }

View file

@ -1,10 +1,10 @@
{ {
"server":"::1", "server":"::1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"aes_password", "password":"aes_password",
"timeout":60, "timeout":60,
"method":"aes-256-cfb", "method":"aes-256-cfb",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false "fast_open":false
} }

View file

@ -1,10 +1,10 @@
{ {
"server":"::", "server":"::",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"aes_password", "password":"aes_password",
"timeout":60, "timeout":60,
"method":"aes-256-cfb", "method":"aes-256-cfb",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false "fast_open":false
} }

View file

@ -33,12 +33,25 @@ run_test coverage run tests/nose_plugin.py -v
run_test python setup.py sdist run_test python setup.py sdist
run_test tests/test_daemon.sh run_test tests/test_daemon.sh
run_test python tests/test.py --with-coverage -c tests/aes.json run_test python tests/test.py --with-coverage -c tests/aes.json
run_test python tests/test.py --with-coverage -c tests/mbedtls-aes.json
run_test python tests/test.py --with-coverage -c tests/aes-gcm.json
run_test python tests/test.py --with-coverage -c tests/aes-ocb.json
run_test python tests/test.py --with-coverage -c tests/mbedtls-aes-gcm.json
run_test python tests/test.py --with-coverage -c tests/aes-ctr.json run_test python tests/test.py --with-coverage -c tests/aes-ctr.json
run_test python tests/test.py --with-coverage -c tests/mbedtls-aes-ctr.json
run_test python tests/test.py --with-coverage -c tests/aes-cfb1.json run_test python tests/test.py --with-coverage -c tests/aes-cfb1.json
run_test python tests/test.py --with-coverage -c tests/aes-cfb8.json run_test python tests/test.py --with-coverage -c tests/aes-cfb8.json
run_test python tests/test.py --with-coverage -c tests/aes-ofb.json
run_test python tests/test.py --with-coverage -c tests/camellia.json
run_test python tests/test.py --with-coverage -c tests/mbedtls-camellia.json
run_test python tests/test.py --with-coverage -c tests/rc4-md5.json run_test python tests/test.py --with-coverage -c tests/rc4-md5.json
run_test python tests/test.py --with-coverage -c tests/salsa20.json run_test python tests/test.py --with-coverage -c tests/salsa20.json
run_test python tests/test.py --with-coverage -c tests/chacha20.json run_test python tests/test.py --with-coverage -c tests/chacha20.json
run_test python tests/test.py --with-coverage -c tests/xchacha20.json
run_test python tests/test.py --with-coverage -c tests/chacha20-ietf.json
run_test python tests/test.py --with-coverage -c tests/chacha20-poly1305.json
run_test python tests/test.py --with-coverage -c tests/xchacha20-ietf-poly1305.json
run_test python tests/test.py --with-coverage -c tests/chacha20-ietf-poly1305.json
run_test python tests/test.py --with-coverage -c tests/table.json run_test python tests/test.py --with-coverage -c tests/table.json
run_test python tests/test.py --with-coverage -c tests/server-multi-ports.json run_test python tests/test.py --with-coverage -c tests/server-multi-ports.json
run_test python tests/test.py --with-coverage -s tests/aes.json -c tests/client-multi-server-ip.json run_test python tests/test.py --with-coverage -s tests/aes.json -c tests/client-multi-server-ip.json
@ -52,6 +65,15 @@ run_test python tests/test.py --with-coverage -b "-m rc4-md5 -k testrc4 -s 127.0
run_test python tests/test.py --with-coverage -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --workers 1" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -qq -b 127.0.0.1" run_test python tests/test.py --with-coverage -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --workers 1" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -qq -b 127.0.0.1"
run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=127.0.0.1,::1,8.8.8.8" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1" run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip=127.0.0.1,::1,8.8.8.8" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1"
# test custom lib path
run_test python tests/test.py --with-coverage --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libopenssl=/usr/local/lib/libcrypto.so" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libopenssl=/usr/local/lib/libcrypto.so"
run_test python tests/test.py --with-coverage --url="http://127.0.0.1/" -b "-m mbedtls:aes-256-cfb128 -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libmbedtls=/usr/local/lib/libmbedcrypto.so" -a "-m mbedtls:aes-256-cfb128 -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libmbedtls=/usr/local/lib/libmbedcrypto.so"
run_test python tests/test.py --with-coverage --url="http://127.0.0.1/" -b "-m chacha20-ietf -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libsodium=/usr/local/lib/libsodium.so" -a "-m chacha20-ietf -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libsodium=/usr/local/lib/libsodium.so"
run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libopenssl=invalid_path" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libopenssl=invalid_path"
run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m chacha20-ietf -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libsodium=invalid_path" -a "-m chacha20-ietf -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libsodium=invalid_path"
run_test python tests/test.py --with-coverage --should-fail --url="http://127.0.0.1/" -b "-m mbedtls:aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --forbidden-ip= --libmbedtls=invalid_path" -a "-m mbedtls:aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -b 127.0.0.1 --libmbedtls=invalid_path"
# test if DNS works # test if DNS works
run_test python tests/test.py --with-coverage -c tests/aes.json --url="https://clients1.google.com/generate_204" run_test python tests/test.py --with-coverage -c tests/aes.json --url="https://clients1.google.com/generate_204"

12
tests/libmbedtls/install.sh Executable file
View file

@ -0,0 +1,12 @@
#!/bin/bash
MBEDTLS_VER=2.4.2
if [ ! -d mbedtls-$MBEDTLS_VER ]; then
wget https://tls.mbed.org/download/mbedtls-$MBEDTLS_VER-gpl.tgz || exit 1
tar xf mbedtls-$MBEDTLS_VER-gpl.tgz || exit 1
fi
pushd mbedtls-$MBEDTLS_VER
make SHARED=1 CFLAGS=-fPIC && sudo make install || exit 1
sudo ldconfig
popd
rm -rf mbedtls-$MBEDTLS_VER || exit 1

12
tests/libopenssl/install.sh Executable file
View file

@ -0,0 +1,12 @@
#!/bin/bash
OPENSSL_VER=1.1.0e
if [ ! -d openssl-$OPENSSL_VER ]; then
wget https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz || exit 1
tar xf openssl-$OPENSSL_VER.tar.gz || exit 1
fi
pushd openssl-$OPENSSL_VER
./config && make && sudo make install || exit 1
# sudo ldconfig # test multiple libcrypto
popd
rm -rf openssl-$OPENSSL_VER || exit 1

View file

@ -1,10 +1,11 @@
#!/bin/bash #!/bin/bash
if [ ! -d libsodium-1.0.11 ]; then if [ ! -d libsodium-1.0.12 ]; then
wget https://github.com/jedisct1/libsodium/releases/download/1.0.11/libsodium-1.0.11.tar.gz || exit 1 wget https://github.com/jedisct1/libsodium/releases/download/1.0.12/libsodium-1.0.12.tar.gz || exit 1
tar xf libsodium-1.0.11.tar.gz || exit 1 tar xf libsodium-1.0.12.tar.gz || exit 1
fi fi
pushd libsodium-1.0.11 pushd libsodium-1.0.12
./configure && make -j2 && make install || exit 1 ./configure && make -j2 && make install || exit 1
sudo ldconfig sudo ldconfig
popd popd
rm -rf libsodium-1.0.12 || exit 1

View file

@ -0,0 +1,10 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"mbedtls:aes-256-ctr",
"local_address":"127.0.0.1",
"fast_open":false
}

View file

@ -0,0 +1,10 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"mbedtls:aes-256-gcm",
"local_address":"127.0.0.1",
"fast_open":false
}

10
tests/mbedtls-aes.json Normal file
View file

@ -0,0 +1,10 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"mbedtls:aes-256-cfb128",
"local_address":"127.0.0.1",
"fast_open":false
}

View file

@ -0,0 +1,10 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"camellia_password",
"timeout":60,
"method":"mbedtls:camellia-256-cfb128",
"local_address":"127.0.0.1",
"fast_open":false
}

View file

@ -1,11 +1,11 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"aes_password", "password":"aes_password",
"timeout":60, "timeout":60,
"method":"rc4-md5", "method":"rc4-md5",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false, "fast_open":false,
"one_time_auth":true "one_time_auth":true
} }

View file

@ -1,10 +1,10 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"aes_password", "password":"aes_password",
"timeout":60, "timeout":60,
"method":"rc4-md5", "method":"rc4-md5",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false "fast_open":false
} }

View file

@ -1,10 +1,10 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"salsa20_password", "password":"salsa20_password",
"timeout":60, "timeout":60,
"method":"salsa20-ctr", "method":"salsa20-ctr",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false "fast_open":false
} }

View file

@ -1,10 +1,10 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"salsa20_password", "password":"salsa20_password",
"timeout":60, "timeout":60,
"method":"salsa20", "method":"salsa20",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false "fast_open":false
} }

View file

@ -1,11 +1,11 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"aes_password", "password":"aes_password",
"timeout":60, "timeout":60,
"method":"aes-256-cfb", "method":"aes-256-cfb",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false, "fast_open":false,
"dns_server": ["8.8.8.8","8.8.4.4"] "dns_server": ["8.8.8.8","8.8.4.4"]
} }

View file

@ -1,10 +1,10 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"table_password", "password":"table_password",
"timeout":60, "timeout":60,
"method":"table", "method":"table",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"fast_open":false "fast_open":false
} }

View file

@ -30,7 +30,7 @@ $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d sto
assert "$LOCAL 2>&1 -m rc4-md5 -k mypassword -s 0.0.0.0 -p 8388 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " DON'T USE DEFAULT PASSWORD! Please change it in your config.json!" assert "$LOCAL 2>&1 -m rc4-md5 -k mypassword -s 0.0.0.0 -p 8388 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " DON'T USE DEFAULT PASSWORD! Please change it in your config.json!"
$LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -k testrc4 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": server addr not specified" assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -k testrc4 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " server addr not specified"
$LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " password not specified" assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " password not specified"
@ -39,7 +39,7 @@ $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d sto
assert "$SERVER 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " password or port_password not specified" assert "$SERVER 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " password or port_password not specified"
$LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
assert "$SERVER 2>&1 --forbidden-ip 127.0.0.1/4a -m rc4-md5 -k 12345 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": Not a valid CIDR notation: 127.0.0.1/4a" assert "$SERVER 2>&1 --forbidden-ip 127.0.0.1/4a -m rc4-md5 -k 12345 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " Not a valid CIDR notation: 127.0.0.1/4a"
$LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop $LOCAL 2>/dev/null 1>/dev/null -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
assert_end command assert_end command

View file

@ -1,10 +1,10 @@
{ {
"server":"127.0.0.1", "server":"127.0.0.1",
"server_port":8388, "server_port":8388,
"local_port":1081, "local_port":1081,
"password":"workers_password", "password":"workers_password",
"timeout":60, "timeout":60,
"method":"aes-256-cfb", "method":"aes-256-cfb",
"local_address":"127.0.0.1", "local_address":"127.0.0.1",
"workers": 4 "workers": 4
} }

View file

@ -0,0 +1,10 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"salsa20_password",
"timeout":60,
"method":"xchacha20-ietf-poly1305",
"local_address":"127.0.0.1",
"fast_open":false
}

10
tests/xchacha20.json Normal file
View file

@ -0,0 +1,10 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"xchacha20_password",
"timeout":60,
"method":"xchacha20",
"local_address":"127.0.0.1",
"fast_open":false
}

View file

@ -38,7 +38,7 @@ if __name__ == '__main__':
banned = set() banned = set()
for line in sys.stdin: for line in sys.stdin:
if 'can not parse header when' in line: if 'can not parse header when' in line:
ip = line.split()[-1].split(':')[0] ip = line.split()[-1].split(':')[-2]
if ip not in ips: if ip not in ips:
ips[ip] = 1 ips[ip] = 1
print(ip) print(ip)