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
.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
- sudo tests/socksify/install.sh
- sudo tests/libsodium/install.sh
- sudo tests/libmbedtls/install.sh
- sudo tests/libopenssl/install.sh
- sudo tests/setup_tc.sh
script:
- tests/jenkins.sh

View file

@ -3,7 +3,6 @@ shadowsocks
[![PyPI version]][PyPI]
[![Build Status]][Travis CI]
[![Coverage Status]][Coverage]
A fast tunnel proxy that helps you bypass firewalls.
@ -22,16 +21,16 @@ Server
Debian / Ubuntu:
apt-get install python-pip
pip install shadowsocks
pip install git+https://github.com/shadowsocks/shadowsocks.git@master
CentOS:
yum install python-setuptools && easy_install pip
pip install shadowsocks
pip install git+https://github.com/shadowsocks/shadowsocks.git@master
Windows:
See [Install Server on Windows]
See [Install Shadowsocks Server on Windows](https://github.com/shadowsocks/shadowsocks/wiki/Install-Shadowsocks-Server-on-Windows).
### Usage
@ -52,10 +51,19 @@ To check the log:
Check all the options via `-h`. You can also use a [Configuration] file
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
-------------
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
-------
@ -69,8 +77,6 @@ Apache License
[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 version]: https://img.shields.io/pypi/v/shadowsocks.svg?style=flat
[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
* Initial release (Closes: #758900)

21
debian/control vendored
View file

@ -2,18 +2,23 @@ Source: shadowsocks
Section: python
Priority: extra
Maintainer: Shell.Xu <shell909090@gmail.com>
Build-Depends: debhelper (>= 8), python-all (>= 2.6.6-3~), python-setuptools
Standards-Version: 3.9.5
Homepage: https://github.com/clowwindy/shadowsocks
Vcs-Git: git://github.com/shell909090/shadowsocks.git
Vcs-Browser: http://github.com/shell909090/shadowsocks
Build-Depends: debhelper (>= 8),
python-all,
python-setuptools,
Standards-Version: 3.9.8
Homepage: https://github.com/shadowsocks/shadowsocks
Vcs-Git: https://github.com/shell909090/shadowsocks.git
Vcs-Browser: https://github.com/shell909090/shadowsocks
Package: shadowsocks
Architecture: all
Pre-Depends: dpkg (>= 1.15.6~)
Depends: ${misc:Depends}, ${python:Depends}, python-pkg-resources, python-m2crypto
Depends: lsb-base (>= 3.0-6),
python-m2crypto,
python-pkg-resources,
${misc:Depends},
${python:Depends},
Description: Fast tunnel proxy that helps you bypass firewalls
A secure socks5 proxy, designed to protect your Internet traffic.
.
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/
Upstream-Name: shadowsocks
Source: https://github.com/clowwindy/shadowsocks
Files: debian/*
Copyright: 2014 Shell.Xu <shell909090@gmail.com>
License: Expat
Source: https://github.com/shadowsocks/shadowsocks
Files: *
Copyright: 2014 clowwindy <clowwindy42@gmail.com>
License: Expat
License: Apache-2.0
License: Expat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
Files: debian/*
Copyright: 2016 Shell.Xu <shell909090@gmail.com>
License: Apache-2.0
License: Apache-2.0
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
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
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.
.
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/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
.IR "Shell Xu <shell909090@gmail.com>"
and
.IR "Clowwindy <clowwindy42@gmail.com>",
available via the Info system.
.IR "Clowwindy <clowwindy42@gmail.com>"
.

4
debian/ssserver.1 vendored
View file

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

View file

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

View file

@ -29,7 +29,7 @@ from shadowsocks import common, lru_cache, eventloop, shell
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()

View file

@ -146,6 +146,7 @@ ADDRTYPE_MASK = 0xF
def pack_addr(address):
address_str = to_str(address)
address = to_bytes(address)
for family in (socket.AF_INET, socket.AF_INET6):
try:
r = socket.inet_pton(family, address_str)
@ -160,6 +161,13 @@ def pack_addr(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):
addrtype = ord(data[0])
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.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']
libcrypto = None
loaded = False
libsodium = None
buf = None
buf_size = 2048
ctx_cleanup = None
def load_openssl():
global loaded, libcrypto, buf
CIPHER_ENC_UNCHANGED = -1
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'),
'EVP_get_cipherbyname',
'libcrypto')
'libcrypto', path)
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_CIPHER_CTX_new.restype = c_void_p
libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p,
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,
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,)
if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'):
libcrypto.OpenSSL_add_all_ciphers()
@ -59,7 +77,7 @@ def load_openssl():
def load_cipher(cipher_name):
func_name = 'EVP_' + cipher_name.replace('-', '_')
func_name = b'EVP_' + cipher_name.replace(b'-', b'_')
if bytes != str:
func_name = str(func_name, 'utf-8')
cipher = getattr(libcrypto, func_name, None)
@ -69,37 +87,45 @@ def load_cipher(cipher_name):
return None
class OpenSSLCrypto(object):
def __init__(self, cipher_name, key, iv, op):
class OpenSSLCryptoBase(object):
"""
OpenSSL crypto base class
"""
def __init__(self, cipher_name, crypto_path=None):
self._ctx = None
self._cipher = None
if not loaded:
load_openssl()
load_openssl(crypto_path)
cipher_name = common.to_bytes(cipher_name)
cipher = libcrypto.EVP_get_cipherbyname(cipher_name)
if not cipher:
cipher = load_cipher(cipher_name)
if not cipher:
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._cipher = cipher
if not self._ctx:
raise Exception('can not create cipher context')
r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None,
key_ptr, iv_ptr, c_int(op))
if not r:
self.clean()
raise Exception('can not initialize cipher context')
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_long(0)
l = len(data)
if buf_size < l:
buf_size = l * 2
buf = create_string_buffer(buf_size)
libcrypto.EVP_CipherUpdate(self._ctx, byref(buf),
byref(cipher_out_len), c_char_p(data), l)
libcrypto.EVP_CipherUpdate(
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
return buf.raw[:cipher_out_len.value]
@ -108,47 +134,256 @@ class OpenSSLCrypto(object):
def clean(self):
if self._ctx:
libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx)
ctx_cleanup(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 = {
'aes-128-cfb': (16, 16, OpenSSLCrypto),
'aes-192-cfb': (24, 16, OpenSSLCrypto),
'aes-256-cfb': (32, 16, OpenSSLCrypto),
'aes-128-ofb': (16, 16, OpenSSLCrypto),
'aes-192-ofb': (24, 16, OpenSSLCrypto),
'aes-256-ofb': (32, 16, OpenSSLCrypto),
'aes-128-ctr': (16, 16, OpenSSLCrypto),
'aes-192-ctr': (24, 16, OpenSSLCrypto),
'aes-256-ctr': (32, 16, OpenSSLCrypto),
'aes-128-cfb8': (16, 16, OpenSSLCrypto),
'aes-192-cfb8': (24, 16, OpenSSLCrypto),
'aes-256-cfb8': (32, 16, OpenSSLCrypto),
'aes-128-cfb1': (16, 16, OpenSSLCrypto),
'aes-192-cfb1': (24, 16, OpenSSLCrypto),
'aes-256-cfb1': (32, 16, OpenSSLCrypto),
'bf-cfb': (16, 8, OpenSSLCrypto),
'camellia-128-cfb': (16, 16, OpenSSLCrypto),
'camellia-192-cfb': (24, 16, OpenSSLCrypto),
'camellia-256-cfb': (32, 16, OpenSSLCrypto),
'cast5-cfb': (16, 8, OpenSSLCrypto),
'des-cfb': (8, 8, OpenSSLCrypto),
'idea-cfb': (16, 8, OpenSSLCrypto),
'rc2-cfb': (16, 8, OpenSSLCrypto),
'rc4': (16, 0, OpenSSLCrypto),
'seed-cfb': (16, 16, OpenSSLCrypto),
'aes-128-cfb': (16, 16, OpenSSLStreamCrypto),
'aes-192-cfb': (24, 16, OpenSSLStreamCrypto),
'aes-256-cfb': (32, 16, OpenSSLStreamCrypto),
'aes-128-ofb': (16, 16, OpenSSLStreamCrypto),
'aes-192-ofb': (24, 16, OpenSSLStreamCrypto),
'aes-256-ofb': (32, 16, OpenSSLStreamCrypto),
'aes-128-ctr': (16, 16, OpenSSLStreamCrypto),
'aes-192-ctr': (24, 16, OpenSSLStreamCrypto),
'aes-256-ctr': (32, 16, OpenSSLStreamCrypto),
'aes-128-cfb8': (16, 16, OpenSSLStreamCrypto),
'aes-192-cfb8': (24, 16, OpenSSLStreamCrypto),
'aes-256-cfb8': (32, 16, OpenSSLStreamCrypto),
'aes-128-cfb1': (16, 16, OpenSSLStreamCrypto),
'aes-192-cfb1': (24, 16, OpenSSLStreamCrypto),
'aes-256-cfb1': (32, 16, OpenSSLStreamCrypto),
'bf-cfb': (16, 8, OpenSSLStreamCrypto),
'camellia-128-cfb': (16, 16, OpenSSLStreamCrypto),
'camellia-192-cfb': (24, 16, OpenSSLStreamCrypto),
'camellia-256-cfb': (32, 16, OpenSSLStreamCrypto),
'cast5-cfb': (16, 8, OpenSSLStreamCrypto),
'des-cfb': (8, 8, OpenSSLStreamCrypto),
'idea-cfb': (16, 8, OpenSSLStreamCrypto),
'rc2-cfb': (16, 8, OpenSSLStreamCrypto),
'rc4': (16, 0, OpenSSLStreamCrypto),
'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):
cipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 1)
decipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 0)
print(method, ': [stream]', 32)
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)
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():
run_method('aes-128-cfb')
@ -179,3 +414,17 @@ def test_rc4():
if __name__ == '__main__':
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
import hashlib
from shadowsocks.crypto import openssl
__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):
md5 = hashlib.md5()
md5.update(key)
md5.update(iv)
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 = {

View file

@ -17,56 +17,162 @@
from __future__ import absolute_import, division, print_function, \
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
from shadowsocks.crypto import util
from shadowsocks.crypto import aead
from shadowsocks.crypto.aead import AeadCryptoBase
__all__ = ['ciphers']
libsodium = None
loaded = False
buf = None
buf_size = 2048
# for salsa20 and chacha20 and chacha20-ietf
BLOCK_SIZE = 64
def load_libsodium():
def load_libsodium(crypto_path=None):
global loaded, libsodium, buf
libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic',
'libsodium')
if libsodium is None:
raise Exception('libsodium not found')
crypto_path = dict(crypto_path) if crypto_path else dict()
path = crypto_path.get('sodium', None)
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.argtypes = (c_void_p, c_char_p,
c_ulonglong,
c_char_p, c_ulonglong,
c_char_p)
libsodium.crypto_stream_salsa20_xor_ic.argtypes = (
c_void_p, c_char_p, # cipher output, msg
c_ulonglong, # msg len
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.argtypes = (c_void_p, c_char_p,
c_ulonglong,
c_char_p, c_ulonglong,
c_char_p)
libsodium.crypto_stream_chacha20_xor_ic.argtypes = (
c_void_p, c_char_p,
c_ulonglong,
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.argtypes = (c_void_p,
c_char_p,
c_ulonglong,
c_char_p,
c_ulong,
c_char_p)
libsodium.crypto_stream_chacha20_ietf_xor_ic.argtypes = (
c_void_p, c_char_p,
c_ulonglong,
c_char_p,
c_uint, # uint32_t initial counter
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)
loaded = True
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:
load_libsodium()
load_libsodium(crypto_path)
self.key = key
self.iv = iv
self.key_ptr = c_char_p(key)
@ -75,12 +181,21 @@ class SodiumCrypto(object):
self.cipher = libsodium.crypto_stream_salsa20_xor_ic
elif cipher_name == 'chacha20':
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':
self.cipher = libsodium.crypto_stream_chacha20_ietf_xor_ic
else:
raise Exception('Unknown cipher')
# byte counter, not block counter
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):
global buf_size, buf
@ -102,38 +217,212 @@ class SodiumCrypto(object):
# strip off the padding
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 = {
'salsa20': (32, 8, SodiumCrypto),
'chacha20': (32, 8, SodiumCrypto),
'xchacha20': (32, 24, 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():
print("Test chacha20")
cipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 1)
decipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 0)
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)
decipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 0)
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__':
test_chacha20()
test_xchacha20()
test_salsa20()
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):
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._op = op
self.encrypt = self.update
self.decrypt = self.update
self.encrypt_once = self.update
self.decrypt_once = self.update
def update(self, data):
if self._op:

View file

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

View file

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

View file

@ -71,6 +71,7 @@ class Manager(object):
port_password = config['port_password']
del config['port_password']
config['crypto_path'] = config.get('crypto_path', dict())
for port, password in port_password.items():
a_config = config.copy()
a_config['server_port'] = int(port)
@ -202,7 +203,7 @@ def test():
import time
import threading
import struct
from shadowsocks import encrypt
from shadowsocks import cryptor
logging.basicConfig(level=5,
format='%(asctime)s %(levelname)-8s %(message)s',
@ -252,7 +253,7 @@ def test():
# test statistics for TCP
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')
tcp_cli = socket.socket()
tcp_cli.connect(('127.0.0.1', 7001))
@ -270,7 +271,7 @@ def test():
# test statistics for UDP
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')
udp_cli = socket.socket(type=socket.SOCK_DGRAM)
udp_cli.sendto(data, ('127.0.0.1', 8382))

View file

@ -28,7 +28,7 @@ import traceback
from functools import wraps
from shadowsocks.common import to_bytes, to_str, IPNetwork
from shadowsocks import encrypt
from shadowsocks import cryptor
VERBOSE_LEVEL = 5
@ -125,6 +125,29 @@ def check_config(config, is_local):
# no need to specify configuration for daemon stop
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):
logging.error('password not specified')
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:
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']:
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']:
@ -168,8 +196,19 @@ def check_config(config, is_local):
if os.name != 'posix':
logging.error('user can be used only on Unix')
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):
@ -180,12 +219,12 @@ def get_config(is_local):
if is_local:
shortopts = 'hd:s:b:p:k:l:m:c:t:vqa'
longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'user=',
'version']
'libopenssl=', 'libmbedtls=', 'libsodium=', 'version']
else:
shortopts = 'hd:s:p:k:m:c:t:vqa'
longopts = ['help', 'fast-open', 'pid-file=', 'log-file=', 'workers=',
'forbidden-ip=', 'user=', 'manager-address=', 'version',
'prefer-ipv6']
'libopenssl=', 'libmbedtls=', 'libsodium=', 'prefer-ipv6']
try:
config_path = find_config()
optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
@ -229,10 +268,16 @@ def get_config(is_local):
config['timeout'] = int(value)
elif key == '--fast-open':
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':
config['workers'] = int(value)
elif key == '--manager-address':
config['manager_address'] = value
config['manager_address'] = to_str(value)
elif key == '--user':
config['user'] = to_str(value)
elif key == '--forbidden-ip':
@ -280,22 +325,15 @@ def get_config(is_local):
config['local_port'] = config.get('local_port', 1080)
config['one_time_auth'] = config.get('one_time_auth', 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['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.addLevelName(VERBOSE_LEVEL, 'VERBOSE')
@ -340,16 +378,40 @@ Proxy options:
-l LOCAL_PORT local port, default: 1080
-k PASSWORD password
-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
-a ONE_TIME_AUTH one time auth
--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:
-h, --help show this help message and exit
-d start/stop/restart daemon mode
--pid-file PID_FILE pid file for daemon mode
--log-file LOG_FILE log file for daemon mode
--user USER username to run as
--pid-file=PID_FILE pid file for daemon mode
--log-file=LOG_FILE log file for daemon mode
--user=USER username to run as
-v, -vv verbose mode
-q, -qq quiet mode, only show warnings/errors
--version show version information
@ -370,13 +432,37 @@ Proxy options:
-p SERVER_PORT server port, default: 8388
-k PASSWORD password
-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
-a ONE_TIME_AUTH one time auth
--fast-open use TCP_FASTOPEN, requires Linux 3.7+
--workers WORKERS number of workers, available on Unix/Linux
--forbidden-ip IPLIST comma seperated IP list forbidden to connect
--manager-address ADDR optional server manager UDP address, see wiki
--workers=WORKERS number of workers, available on Unix/Linux
--forbidden-ip=IPLIST comma seperated IP list forbidden to connect
--manager-address=ADDR optional server manager UDP address, see wiki
--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:
-h, --help show this help message and exit

View file

@ -26,7 +26,7 @@ import logging
import traceback
import random
from shadowsocks import encrypt, eventloop, shell, common
from shadowsocks import cryptor, eventloop, shell, common
from shadowsocks.common import parse_header, onetimeauth_verify, \
onetimeauth_gen, ONETIMEAUTH_BYTES, ONETIMEAUTH_CHUNK_BYTES, \
ONETIMEAUTH_CHUNK_DATA_LEN, ADDRTYPE_AUTH
@ -93,10 +93,12 @@ WAIT_STATUS_WRITING = 2
WAIT_STATUS_READWRITING = WAIT_STATUS_READING | WAIT_STATUS_WRITING
BUF_SIZE = 32 * 1024
UP_STREAM_BUF_SIZE = 16 * 1024
DOWN_STREAM_BUF_SIZE = 32 * 1024
# helper exceptions for TCPRelayHandler
class BadSocksHeader(Exception):
pass
@ -106,6 +108,7 @@ class NoAcceptableMethods(Exception):
class TCPRelayHandler(object):
def __init__(self, server, fd_to_handlers, loop, local_sock, config,
dns_resolver, is_local):
self._server = server
@ -115,17 +118,19 @@ class TCPRelayHandler(object):
self._remote_sock = None
self._config = config
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
# if is_local, this is sslocal
self._is_local = is_local
self._stage = STAGE_INIT
self._encryptor = encrypt.Encryptor(config['password'],
config['method'])
if 'one_time_auth' in config and config['one_time_auth']:
self._ota_enable = True
else:
self._ota_enable = False
self._cryptor = cryptor.Cryptor(config['password'],
config['method'],
config['crypto_path'])
self._ota_enable = config.get('one_time_auth', False)
self._ota_enable_session = self._ota_enable
self._ota_buff_head = b''
self._ota_buff_data = b''
@ -138,10 +143,7 @@ class TCPRelayHandler(object):
self._downstream_status = WAIT_STATUS_INIT
self._client_address = local_sock.getpeername()[:2]
self._remote_address = None
if 'forbidden_ip' in config:
self._forbidden_iplist = config['forbidden_ip']
else:
self._forbidden_iplist = None
self._forbidden_iplist = config.get('forbidden_ip')
if is_local:
self._chosen_server = self._get_a_server()
fd_to_handlers[local_sock.fileno()] = self
@ -256,10 +258,9 @@ class TCPRelayHandler(object):
else:
self._data_to_write_to_remote.append(data)
return
if self._ota_enable_session:
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)
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)
def _handle_stage_addr(self, data):
if self._is_local:
cmd = common.ord(data[1])
if cmd == CMD_UDP_ASSOCIATE:
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:]
if self._is_tunnel:
# add ss header to data
tunnel_remote = self.tunnel_remote
tunnel_remote_port = self.tunnel_remote_port
data = common.add_header(tunnel_remote,
tunnel_remote_port, data)
else:
logging.error('unknown command %d', cmd)
self.destroy()
return
cmd = common.ord(data[1])
if cmd == CMD_UDP_ASSOCIATE:
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)
if header_result is None:
raise Exception('can not parse header')
@ -342,7 +350,7 @@ class TCPRelayHandler(object):
offset = header_length + ONETIMEAUTH_BYTES
_hash = data[header_length: offset]
_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:
logging.warn('one time auth fail')
self.destroy()
@ -353,17 +361,21 @@ class TCPRelayHandler(object):
self._update_stream(STREAM_UP, WAIT_STATUS_WRITING)
self._stage = STAGE_DNS
if self._is_local:
# forward address to remote
self._write_to_sock((b'\x05\x00\x00\x01'
b'\x00\x00\x00\x00\x10\x10'),
self._local_sock)
# jump over socks5 response
if not self._is_tunnel:
# forward address to remote
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
# ATYP & 0x10 == 0x10, then OTA is enabled.
if self._ota_enable_session:
data = common.chr(addrtype | ADDRTYPE_AUTH) + data[1:]
key = self._encryptor.cipher_iv + self._encryptor.key
data += onetimeauth_gen(data, key)
data_to_send = self._encryptor.encrypt(data)
key = self._cryptor.cipher_iv + self._cryptor.key
_header = data[:header_length]
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)
# notice here may go into _handle_dns_resolved directly
self._dns_resolver.resolve(self._chosen_server[0],
@ -466,7 +478,7 @@ class TCPRelayHandler(object):
_hash = self._ota_buff_head[ONETIMEAUTH_CHUNK_DATA_LEN:]
_data = self._ota_buff_data
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:
logging.warn('one time auth fail, drop chunk !')
else:
@ -481,7 +493,7 @@ class TCPRelayHandler(object):
def _ota_chunk_data_gen(self, data):
data_len = struct.pack(">H", len(data))
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)
self._ota_chunk_idx += 1
return data_len + sha110 + data
@ -490,7 +502,7 @@ class TCPRelayHandler(object):
if self._is_local:
if self._ota_enable_session:
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)
else:
if self._ota_enable_session:
@ -544,8 +556,12 @@ class TCPRelayHandler(object):
return
is_local = self._is_local
data = None
if is_local:
buf_size = UP_STREAM_BUF_SIZE
else:
buf_size = DOWN_STREAM_BUF_SIZE
try:
data = self._local_sock.recv(BUF_SIZE)
data = self._local_sock.recv(buf_size)
except (OSError, IOError) as e:
if eventloop.errno_from_exception(e) in \
(errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK):
@ -555,14 +571,19 @@ class TCPRelayHandler(object):
return
self._update_activity(len(data))
if not is_local:
data = self._encryptor.decrypt(data)
data = self._cryptor.decrypt(data)
if not data:
return
if self._stage == STAGE_STREAM:
self._handle_stage_stream(data)
return
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:
self._handle_stage_connecting(data)
elif (is_local and self._stage == STAGE_ADDR) or \
@ -572,8 +593,12 @@ class TCPRelayHandler(object):
def _on_remote_read(self):
# handle all remote read events
data = None
if self._is_local:
buf_size = UP_STREAM_BUF_SIZE
else:
buf_size = DOWN_STREAM_BUF_SIZE
try:
data = self._remote_sock.recv(BUF_SIZE)
data = self._remote_sock.recv(buf_size)
except (OSError, IOError) as e:
if eventloop.errno_from_exception(e) in \
@ -584,9 +609,9 @@ class TCPRelayHandler(object):
return
self._update_activity(len(data))
if self._is_local:
data = self._encryptor.decrypt(data)
data = self._cryptor.decrypt(data)
else:
data = self._encryptor.encrypt(data)
data = self._cryptor.encrypt(data)
try:
self._write_to_sock(data, self._local_sock)
except Exception as e:
@ -627,6 +652,7 @@ class TCPRelayHandler(object):
logging.error(eventloop.get_sock_error(self._remote_sock))
self.destroy()
@shell.exception_handle(self_=True, destroy=True)
def handle_event(self, sock, event):
# handle all events in this handler and dispatch them to methods
if self._stage == STAGE_DESTROYED:
@ -693,6 +719,7 @@ class TCPRelayHandler(object):
class TCPRelay(object):
def __init__(self, config, dns_resolver, is_local, stat_callback=None):
self._config = config
self._is_local = is_local
@ -700,6 +727,7 @@ class TCPRelay(object):
self._closed = False
self._eventloop = None
self._fd_to_handlers = {}
self._is_tunnel = False
self._timeout = config['timeout']
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 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, \
onetimeauth_gen, ONETIMEAUTH_BYTES, ADDRTYPE_AUTH
@ -82,6 +82,7 @@ def client_key(source_addr, server_af):
class UDPRelay(object):
def __init__(self, config, dns_resolver, is_local, stat_callback=None):
self._config = config
if is_local:
@ -94,14 +95,15 @@ class UDPRelay(object):
self._listen_port = config['server_port']
self._remote_addr = 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._password = common.to_bytes(config['password'])
self._method = config['method']
self._timeout = config['timeout']
if 'one_time_auth' in config and config['one_time_auth']:
self._ota_enable = True
else:
self._ota_enable = False
self._ota_enable = config.get('one_time_auth', False)
self._ota_enable_session = self._ota_enable
self._is_local = is_local
self._cache = lru_cache.LRUCache(timeout=config['timeout'],
@ -112,10 +114,8 @@ class UDPRelay(object):
self._eventloop = None
self._closed = False
self._sockets = set()
if 'forbidden_ip' in config:
self._forbidden_iplist = config['forbidden_ip']
else:
self._forbidden_iplist = None
self._forbidden_iplist = config.get('forbidden_ip')
self._crypto_path = config['crypto_path']
addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0,
socket.SOCK_DGRAM, socket.SOL_UDP)
@ -158,27 +158,37 @@ class UDPRelay(object):
if self._stat_callback:
self._stat_callback(self._listen_port, len(data))
if self._is_local:
frag = common.ord(data[2])
if frag != 0:
logging.warn('UDP drop a message since frag is not 0')
return
if self._is_tunnel:
# add ss header to data
tunnel_remote = self.tunnel_remote
tunnel_remote_port = self.tunnel_remote_port
data = common.add_header(tunnel_remote,
tunnel_remote_port, data)
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:
data, key, iv = encrypt.dencrypt_all(self._password,
self._method,
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:
logging.debug(
'UDP handle_server: data is empty after decrypt'
)
logging.debug('UDP handle_server: data is empty after decrypt')
return
header_result = parse_header(data)
if header_result is None:
return
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:
server_addr, server_port = self._get_a_server()
else:
@ -228,11 +238,16 @@ class UDPRelay(object):
self._eventloop.add(client, eventloop.POLL_IN, self)
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
if self._ota_enable_session:
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:
return
else:
@ -261,22 +276,38 @@ class UDPRelay(object):
# drop
return
data = pack_addr(r_addr[0]) + struct.pack('>H', r_addr[1]) + data
response = encrypt.encrypt_all(self._password, self._method, 1,
data)
try:
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:
return
else:
data = encrypt.encrypt_all(self._password, self._method, 0,
data)
try:
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:
return
header_result = parse_header(data)
if header_result is None:
return
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())
if client_addr:
logging.debug("send udp response to %s:%d"
% (client_addr[0], client_addr[1]))
self._server_socket.sendto(response, client_addr)
else:
# this packet is from somewhere else we know

View file

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

View file

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

View file

@ -1,10 +1,10 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"aes-256-ctr",
"local_address":"127.0.0.1",
"fast_open":false
}
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"aes-256-ctr",
"local_address":"127.0.0.1",
"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_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"aes-256-cfb",
"local_address":"127.0.0.1",
"fast_open":false
}
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"aes-256-cfb",
"local_address":"127.0.0.1",
"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_port":8388,
"local_port":1081,
"password":"salsa20_password",
"timeout":60,
"method":"chacha20-ietf",
"local_address":"127.0.0.1",
"fast_open":false
}
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"salsa20_password",
"timeout":60,
"method":"chacha20-ietf",
"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-poly1305",
"local_address":"127.0.0.1",
"fast_open":false
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,10 @@
{
"server":"::",
"server_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"aes-256-cfb",
"local_address":"127.0.0.1",
"fast_open":false
}
{
"server":"::",
"server_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"aes-256-cfb",
"local_address":"127.0.0.1",
"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 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/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/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-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/salsa20.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/server-multi-ports.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 --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
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
if [ ! -d libsodium-1.0.11 ]; then
wget https://github.com/jedisct1/libsodium/releases/download/1.0.11/libsodium-1.0.11.tar.gz || exit 1
tar xf libsodium-1.0.11.tar.gz || exit 1
if [ ! -d libsodium-1.0.12 ]; then
wget https://github.com/jedisct1/libsodium/releases/download/1.0.12/libsodium-1.0.12.tar.gz || exit 1
tar xf libsodium-1.0.12.tar.gz || exit 1
fi
pushd libsodium-1.0.11
pushd libsodium-1.0.12
./configure && make -j2 && make install || exit 1
sudo ldconfig
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_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"rc4-md5",
"local_address":"127.0.0.1",
"fast_open":false,
"one_time_auth":true
}
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"rc4-md5",
"local_address":"127.0.0.1",
"fast_open":false,
"one_time_auth":true
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,10 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"table_password",
"timeout":60,
"method":"table",
"local_address":"127.0.0.1",
"fast_open":false
}
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"table_password",
"timeout":60,
"method":"table",
"local_address":"127.0.0.1",
"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!"
$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
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"
$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
assert_end command

View file

@ -1,10 +1,10 @@
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"workers_password",
"timeout":60,
"method":"aes-256-cfb",
"local_address":"127.0.0.1",
"workers": 4
}
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"workers_password",
"timeout":60,
"method":"aes-256-cfb",
"local_address":"127.0.0.1",
"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()
for line in sys.stdin:
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:
ips[ip] = 1
print(ip)