commit
32ee84f138
64 changed files with 2471 additions and 512 deletions
14
.gitignore
vendored
14
.gitignore
vendored
|
@ -29,3 +29,17 @@ htmlcov
|
|||
|
||||
.DS_Store
|
||||
.idea
|
||||
tags
|
||||
|
||||
#Emacs
|
||||
.#*
|
||||
venv/
|
||||
|
||||
# VS-code
|
||||
.vscode/
|
||||
|
||||
# Pycharm
|
||||
.idea
|
||||
|
||||
#ss
|
||||
config.json
|
||||
|
|
|
@ -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
|
||||
|
|
20
README.md
20
README.md
|
@ -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
17
config.json.example
Normal 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
23
debian/changelog
vendored
|
@ -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
21
debian/control
vendored
|
@ -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
43
debian/copyright
vendored
|
@ -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
2
debian/install
vendored
|
@ -1 +1 @@
|
|||
debian/config.json etc/shadowsocks/
|
||||
debian/config.json etc/shadowsocks/
|
||||
|
|
2
debian/shadowsocks.manpages
vendored
2
debian/shadowsocks.manpages
vendored
|
@ -1,2 +1,2 @@
|
|||
debian/sslocal.1
|
||||
debian/ssserver.1
|
||||
debian/ssserver.1
|
||||
|
|
4
debian/sslocal.1
vendored
4
debian/sslocal.1
vendored
|
@ -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
4
debian/ssserver.1
vendored
|
@ -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>"
|
||||
.
|
||||
|
|
2
setup.py
2
setup.py
|
@ -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',
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
343
shadowsocks/crypto/aead.py
Normal 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()
|
98
shadowsocks/crypto/hkdf.py
Normal file
98
shadowsocks/crypto/hkdf.py
Normal 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)
|
481
shadowsocks/crypto/mbedtls.py
Normal file
481
shadowsocks/crypto/mbedtls.py
Normal 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)
|
|
@ -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)
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
74
shadowsocks/tunnel.py
Executable 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()
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
10
tests/aes-gcm.json
Normal 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
11
tests/aes-ocb.json
Normal 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
10
tests/aes-ofb.json
Normal 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
|
||||
}
|
|
@ -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
10
tests/camellia.json
Normal 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
|
||||
}
|
10
tests/chacha20-ietf-poly1305.json
Normal file
10
tests/chacha20-ietf-poly1305.json
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
10
tests/chacha20-poly1305.json
Normal file
10
tests/chacha20-poly1305.json
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
12
tests/libmbedtls/install.sh
Executable 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
12
tests/libopenssl/install.sh
Executable 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
|
|
@ -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
|
||||
|
|
10
tests/mbedtls-aes-ctr.json
Normal file
10
tests/mbedtls-aes-ctr.json
Normal 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
|
||||
}
|
10
tests/mbedtls-aes-gcm.json
Normal file
10
tests/mbedtls-aes-gcm.json
Normal 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
10
tests/mbedtls-aes.json
Normal 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
|
||||
}
|
10
tests/mbedtls-camellia.json
Normal file
10
tests/mbedtls-camellia.json
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
10
tests/xchacha20-ietf-poly1305.json
Normal file
10
tests/xchacha20-ietf-poly1305.json
Normal 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
10
tests/xchacha20.json
Normal 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
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue