mirror of
https://github.com/shadowsocks/shadowsocks-libev.git
synced 2026-10-28 10:44:43 +08:00
C++20 port 15/15: remove the stream ciphers (#3079)
* Remove the stream ciphers The unauthenticated stream ciphers (aes-*-cfb, aes-*-ctr, camellia-*-cfb, chacha20, chacha20-ietf, salsa20) are gone from every build, together with the SS_ENABLE_LEGACY option, the C++ stream codecs, their C test oracle and the stream-only tests and configurations. A configuration naming one of them, or RC4/table, now fails at startup with a message pointing to an AEAD method, and test_cli checks that. Shell completions list only the supported methods (and now include the 2022 ones), and the docs and the compatibility contract say the stream ciphers were removed. * Drop the Mbed TLS CFB requirement and stale stream-cipher examples CFB was only needed by the removed stream ciphers, so a system Mbed TLS built without it no longer fails to configure. The public header example now uses aes-256-gcm.
This commit is contained in:
1 parent
002b4093fd
commit
f1f31c3c05
47 files changed
+71
-1786
No files matched your search
@@ -32,7 +32,7 @@ Binaries land in `<build>/bin/`, libraries in `<build>/lib/`.
|
||||
|
||||
### CMake Options
|
||||
|
||||
- `-DSS_MINIMAL=ON`: no regex, plugins, manager or legacy stream ciphers
|
||||
- `-DSS_MINIMAL=ON`: no regex, plugins or manager
|
||||
- `-DWITH_STATIC=OFF`: link third-party dependencies dynamically
|
||||
- `-DSS_BUILD_EXECUTABLES / SS_BUILD_STATIC_LIBRARY / SS_BUILD_SHARED_LIBRARY`: choose outputs
|
||||
- `-DWITH_DOC_MAN=ON` / `-DWITH_DOC_HTML=ON`: build documentation
|
||||
@@ -117,7 +117,7 @@ handshake), `udp.cpp` (UDP relay), `codec.cpp` (per-connection encoder/decoder v
|
||||
runs them.
|
||||
|
||||
**Crypto** (`src/crypto/`): value-type codecs `aead.cpp` (AES-GCM, ChaCha20-Poly1305), `aead2022.cpp`
|
||||
(Shadowsocks 2022, BLAKE3 from `src/blake3`), `stream.cpp` (legacy, `SS_ENABLE_LEGACY`), `keys.cpp`
|
||||
(Shadowsocks 2022, BLAKE3 from `src/blake3`), `keys.cpp`
|
||||
(key derivation, method tables). The old C implementation lives in `tests/reference/` as a test oracle.
|
||||
|
||||
**ACL:** `acl.cpp` / `rule.cpp` / `ipset.cpp` - IP prefix sets and PCRE2/literal domain rules.
|
||||
|
||||
+1
-3
@@ -61,14 +61,12 @@ option(SS_INSTALL_TOOLS "Install platform shell tools" OFF)
|
||||
option(ENABLE_CONNMARKTOS "Enable saved connmark to IP TOS QoS feature" OFF)
|
||||
option(ENABLE_NFTABLES "Report malicious IP to nftables" OFF)
|
||||
|
||||
option(SS_MINIMAL "Disable regex, plugins, manager and legacy stream ciphers" OFF)
|
||||
option(SS_MINIMAL "Disable regex, plugins and manager" OFF)
|
||||
option(SS_ENABLE_REGEX "Enable PCRE2 ACL patterns" ON)
|
||||
option(SS_ENABLE_PLUGINS "Enable SIP003 and obfsproxy subprocesses" ON)
|
||||
option(SS_ENABLE_LEGACY "Enable legacy stream ciphers" ON)
|
||||
if(SS_MINIMAL)
|
||||
set(SS_ENABLE_REGEX OFF)
|
||||
set(SS_ENABLE_PLUGINS OFF)
|
||||
set(SS_ENABLE_LEGACY OFF)
|
||||
set(WITH_SS_MANAGER OFF)
|
||||
endif()
|
||||
include(cmake/Dependencies.cmake)
|
||||
|
||||
@@ -172,7 +172,7 @@ Doxygen renders man pages and HTML documentation from CLI source comments. See [
|
||||
for validation and rendering commands.
|
||||
|
||||
For a smaller build, use `-DSS_MINIMAL=ON`. It excludes PCRE2 regex, plugin
|
||||
subprocesses, the manager, and legacy stream ciphers. Minimal ACLs support
|
||||
subprocesses and the manager. Minimal ACLs support
|
||||
IPv4/IPv6 CIDRs, `full:example.com` for exact domains, and
|
||||
`suffix:example.com` for the apex and subdomains. Literal matches ignore ASCII
|
||||
case and respect label boundaries. Unsupported regex rules are rejected.
|
||||
@@ -188,8 +188,8 @@ Use `-DCMAKE_PREFIX_PATH=/opt/homebrew/opt/mbedtls@3` when needed on macOS.
|
||||
| `SS_BUILD_EXECUTABLES` | `ON` | Command-line tools |
|
||||
| `SS_BUILD_STATIC_LIBRARY` | `ON` | Static embedding library with installed dependency archives |
|
||||
| `SS_BUILD_SHARED_LIBRARY` | `ON` | Shared embedding library |
|
||||
| `SS_MINIMAL` | `OFF` | Disable regex, plugins, manager, and legacy stream ciphers |
|
||||
| `SS_ENABLE_REGEX` / `SS_ENABLE_PLUGINS` / `SS_ENABLE_LEGACY` | `ON` | Individual compatibility features |
|
||||
| `SS_MINIMAL` | `OFF` | Disable regex, plugins and manager |
|
||||
| `SS_ENABLE_REGEX` / `SS_ENABLE_PLUGINS` | `ON` | Individual compatibility features |
|
||||
| `WITH_DOC_MAN` / `WITH_DOC_HTML` | `OFF` | Generate documentation (requires Doxygen 1.9.4+) |
|
||||
| `SS_INSTALL_TOOLS` | `OFF` | Install platform shell helpers |
|
||||
| `ENABLE_SANITIZERS` | `OFF` | AddressSanitizer and UndefinedBehaviorSanitizer |
|
||||
@@ -395,15 +395,9 @@ you may refer to the man pages of the applications, respectively.
|
||||
2022-blake3-aes-128-gcm,
|
||||
2022-blake3-aes-256-gcm,
|
||||
2022-blake3-chacha20-poly1305,
|
||||
rc4-md5,
|
||||
aes-128-gcm, aes-192-gcm, aes-256-gcm,
|
||||
aes-128-cfb, aes-192-cfb, aes-256-cfb,
|
||||
aes-128-ctr, aes-192-ctr, aes-256-ctr,
|
||||
camellia-128-cfb, camellia-192-cfb,
|
||||
camellia-256-cfb, bf-cfb,
|
||||
chacha20-ietf-poly1305,
|
||||
xchacha20-ietf-poly1305,
|
||||
salsa20, chacha20 and chacha20-ietf.
|
||||
chacha20-ietf-poly1305 and
|
||||
xchacha20-ietf-poly1305.
|
||||
The default cipher is chacha20-ietf-poly1305.
|
||||
|
||||
The 2022-blake3-* ciphers implement
|
||||
|
||||
+3
-8
@@ -363,15 +363,10 @@ você pode consultar as páginas de manual dos aplicativos, respectivamente.
|
||||
|
||||
-k <password> Senha do seu servidor remoto.
|
||||
|
||||
-m <encrypt_method> Método de criptografia: rc4-md5,
|
||||
-m <encrypt_method> Método de criptografia:
|
||||
aes-128-gcm, aes-192-gcm, aes-256-gcm,
|
||||
aes-128-cfb, aes-192-cfb, aes-256-cfb,
|
||||
aes-128-ctr, aes-192-ctr, aes-256-ctr,
|
||||
camellia-128-cfb, camellia-192-cfb,
|
||||
camellia-256-cfb, bf-cfb,
|
||||
chacha20-ietf-poly1305,
|
||||
xchacha20-ietf-poly1305,
|
||||
salsa20, chacha20 e chacha20-ietf.
|
||||
chacha20-ietf-poly1305 e
|
||||
xchacha20-ietf-poly1305.
|
||||
A cifra padrão é chacha20-ietf-poly1305.
|
||||
|
||||
[-a <user>] Executar como outro usuário.
|
||||
|
||||
@@ -92,32 +92,6 @@ if(MBEDTLS_INCLUDE_DIR AND MBEDTLS_CRYPTO_LIBRARY)
|
||||
set(CMAKE_REQUIRED_INCLUDES ${MBEDTLS_INCLUDE_DIR})
|
||||
set(CMAKE_REQUIRED_LIBRARIES ${MBEDTLS_CRYPTO_LIBRARY})
|
||||
|
||||
# Check for required CFB mode support
|
||||
check_c_source_compiles("
|
||||
#include <mbedtls/cipher.h>
|
||||
#if !defined(MBEDTLS_CIPHER_MODE_CFB)
|
||||
#error CFB mode not supported
|
||||
#endif
|
||||
int main(void) { return 0; }
|
||||
" MBEDTLS_HAS_CFB)
|
||||
|
||||
if(NOT MBEDTLS_HAS_CFB)
|
||||
# Try mbedtls 3.x config path
|
||||
check_c_source_compiles("
|
||||
#include <mbedtls/build_info.h>
|
||||
#include <mbedtls/cipher.h>
|
||||
#if !defined(MBEDTLS_CIPHER_MODE_CFB)
|
||||
#error CFB mode not supported
|
||||
#endif
|
||||
int main(void) { return 0; }
|
||||
" MBEDTLS_HAS_CFB_V3)
|
||||
|
||||
if(NOT MBEDTLS_HAS_CFB_V3)
|
||||
message(FATAL_ERROR "mbedTLS found but MBEDTLS_CIPHER_MODE_CFB is not enabled. "
|
||||
"Please enable CFB mode in your mbedTLS configuration.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Verify the headers and the runtime library agree on the version. A
|
||||
# mismatch silently corrupts the heap, so refuse to configure instead.
|
||||
if(NOT CMAKE_CROSSCOMPILING AND NOT DEFINED MBEDTLS_VERSION_CONSISTENT)
|
||||
|
||||
@@ -307,6 +307,5 @@
|
||||
|
||||
#cmakedefine01 SS_ENABLE_REGEX
|
||||
#cmakedefine01 SS_ENABLE_PLUGINS
|
||||
#cmakedefine01 SS_ENABLE_LEGACY
|
||||
|
||||
#endif
|
||||
@@ -2,7 +2,7 @@ _ss_local()
|
||||
{
|
||||
local cur prev opts ciphers
|
||||
opts='-s -p -l -k -m -a -f -t -c -n -i -b -u -U -v -h --reuse-port --fast-open --acl --mtu --mptcp --no-delay --printable-salt --key --plugin --plugin-opts --help'
|
||||
ciphers='rc4-md5 aes-128-gcm aes-192-gcm aes-256-gcm aes-128-cfb aes-192-cfb aes-256-cfb aes-128-ctr aes-192-ctr aes-256-ctr camellia-128-cfb camellia-192-cfb camellia-256-cfb bf-cfb chacha20-ietf-poly1305 xchacha20-ietf-poly1305 salsa20 chacha20 chacha20-ietf'
|
||||
ciphers='aes-128-gcm aes-192-gcm aes-256-gcm chacha20-ietf-poly1305 xchacha20-ietf-poly1305 2022-blake3-aes-128-gcm 2022-blake3-aes-256-gcm 2022-blake3-chacha20-poly1305'
|
||||
cur=${COMP_WORDS[COMP_CWORD]}
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
case "$prev" in
|
||||
|
||||
@@ -2,7 +2,7 @@ _ss_manager()
|
||||
{
|
||||
local cur prev opts ciphers
|
||||
opts='-s -p -l -k -m -a -f -t -c -n -i -b -u -U -v -h --reuse-port --manager-address --executable --mtu --mptcp --plugin --plugin-opts --help'
|
||||
ciphers='rc4-md5 aes-128-gcm aes-192-gcm aes-256-gcm aes-128-cfb aes-192-cfb aes-256-cfb aes-128-ctr aes-192-ctr aes-256-ctr camellia-128-cfb camellia-192-cfb camellia-256-cfb bf-cfb chacha20-ietf-poly1305 xchacha20-ietf-poly1305 salsa20 chacha20 chacha20-ietf'
|
||||
ciphers='aes-128-gcm aes-192-gcm aes-256-gcm chacha20-ietf-poly1305 xchacha20-ietf-poly1305 2022-blake3-aes-128-gcm 2022-blake3-aes-256-gcm 2022-blake3-chacha20-poly1305'
|
||||
cur=${COMP_WORDS[COMP_CWORD]}
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
case "$prev" in
|
||||
|
||||
@@ -2,7 +2,7 @@ _ss_redir()
|
||||
{
|
||||
local cur prev opts ciphers
|
||||
opts='-s -p -l -k -m -a -f -t -c -n -b -u -U -T -v -h --reuse-port --mtu --mptcp --key --plugin --plugin-opts --help'
|
||||
ciphers='rc4-md5 aes-128-gcm aes-192-gcm aes-256-gcm aes-128-cfb aes-192-cfb aes-256-cfb aes-128-ctr aes-192-ctr aes-256-ctr camellia-128-cfb camellia-192-cfb camellia-256-cfb bf-cfb chacha20-ietf-poly1305 xchacha20-ietf-poly1305 salsa20 chacha20 chacha20-ietf'
|
||||
ciphers='aes-128-gcm aes-192-gcm aes-256-gcm chacha20-ietf-poly1305 xchacha20-ietf-poly1305 2022-blake3-aes-128-gcm 2022-blake3-aes-256-gcm 2022-blake3-chacha20-poly1305'
|
||||
cur=${COMP_WORDS[COMP_CWORD]}
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
case "$prev" in
|
||||
|
||||
@@ -2,7 +2,7 @@ _ss_server()
|
||||
{
|
||||
local cur prev opts ciphers
|
||||
opts='-s -p -l -k -m -a -f -t -c -n -i -b -u -U -6 -d -v -h --reuse-port --fast-open --acl --manager-address --mtu --mptcp --no-delay --key --plugin --plugin-opts --help'
|
||||
ciphers='rc4-md5 aes-128-gcm aes-192-gcm aes-256-gcm aes-128-cfb aes-192-cfb aes-256-cfb aes-128-ctr aes-192-ctr aes-256-ctr camellia-128-cfb camellia-192-cfb camellia-256-cfb bf-cfb chacha20-ietf-poly1305 xchacha20-ietf-poly1305 salsa20 chacha20 chacha20-ietf'
|
||||
ciphers='aes-128-gcm aes-192-gcm aes-256-gcm chacha20-ietf-poly1305 xchacha20-ietf-poly1305 2022-blake3-aes-128-gcm 2022-blake3-aes-256-gcm 2022-blake3-chacha20-poly1305'
|
||||
COMPREPLY=()
|
||||
cur=${COMP_WORDS[COMP_CWORD]}
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
|
||||
@@ -2,7 +2,7 @@ _ss_tunnel()
|
||||
{
|
||||
local cur prev opts ciphers
|
||||
opts='-s -p -l -k -m -a -f -t -c -n -i -b -u -U -L -v -h --reuse-port --mtu --mptcp --key --plugin --plugin-opts --help'
|
||||
ciphers='rc4-md5 aes-128-gcm aes-192-gcm aes-256-gcm aes-128-cfb aes-192-cfb aes-256-cfb aes-128-ctr aes-192-ctr aes-256-ctr camellia-128-cfb camellia-192-cfb camellia-256-cfb bf-cfb chacha20-ietf-poly1305 xchacha20-ietf-poly1305 salsa20 chacha20 chacha20-ietf'
|
||||
ciphers='aes-128-gcm aes-192-gcm aes-256-gcm chacha20-ietf-poly1305 xchacha20-ietf-poly1305 2022-blake3-aes-128-gcm 2022-blake3-aes-256-gcm 2022-blake3-chacha20-poly1305'
|
||||
cur=${COMP_WORDS[COMP_CWORD]}
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
compopt +o nospace
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#compdef ss-local
|
||||
|
||||
local ciphers
|
||||
ciphers='(rc4-md5 aes-128-gcm aes-192-gcm aes-256-gcm aes-128-cfb aes-192-cfb aes-256-cfb aes-128-ctr aes-192-ctr aes-256-ctr camellia-128-cfb camellia-192-cfb camellia-256-cfb bf-cfb chacha20-ietf-poly1305 xchacha20-ietf-poly1305 salsa20 chacha20 chacha20-ietf)'
|
||||
ciphers='(aes-128-gcm aes-192-gcm aes-256-gcm chacha20-ietf-poly1305 xchacha20-ietf-poly1305 2022-blake3-aes-128-gcm 2022-blake3-aes-256-gcm 2022-blake3-chacha20-poly1305)'
|
||||
|
||||
_arguments "-h::" \
|
||||
"-s:server host:_hosts" \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#compdef ss-manager
|
||||
|
||||
local ciphers
|
||||
ciphers='(rc4-md5 aes-128-gcm aes-192-gcm aes-256-gcm aes-128-cfb aes-192-cfb aes-256-cfb aes-128-ctr aes-192-ctr aes-256-ctr camellia-128-cfb camellia-192-cfb camellia-256-cfb bf-cfb chacha20-ietf-poly1305 xchacha20-ietf-poly1305 salsa20 chacha20 chacha20-ietf)'
|
||||
ciphers='(aes-128-gcm aes-192-gcm aes-256-gcm chacha20-ietf-poly1305 xchacha20-ietf-poly1305 2022-blake3-aes-128-gcm 2022-blake3-aes-256-gcm 2022-blake3-chacha20-poly1305)'
|
||||
|
||||
_arguments "-h::" \
|
||||
"-s:server host:_hosts" \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#compdef ss-redir
|
||||
|
||||
local ciphers
|
||||
ciphers='(rc4-md5 aes-128-gcm aes-192-gcm aes-256-gcm aes-128-cfb aes-192-cfb aes-256-cfb aes-128-ctr aes-192-ctr aes-256-ctr camellia-128-cfb camellia-192-cfb camellia-256-cfb bf-cfb chacha20-ietf-poly1305 xchacha20-ietf-poly1305 salsa20 chacha20 chacha20-ietf)'
|
||||
ciphers='(aes-128-gcm aes-192-gcm aes-256-gcm chacha20-ietf-poly1305 xchacha20-ietf-poly1305 2022-blake3-aes-128-gcm 2022-blake3-aes-256-gcm 2022-blake3-chacha20-poly1305)'
|
||||
|
||||
_arguments "-h::" \
|
||||
"-s:server host:_hosts" \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#compdef ss-server
|
||||
|
||||
local ciphers
|
||||
ciphers='(rc4-md5 aes-128-gcm aes-192-gcm aes-256-gcm aes-128-cfb aes-192-cfb aes-256-cfb aes-128-ctr aes-192-ctr aes-256-ctr camellia-128-cfb camellia-192-cfb camellia-256-cfb bf-cfb chacha20-ietf-poly1305 xchacha20-ietf-poly1305 salsa20 chacha20 chacha20-ietf)'
|
||||
ciphers='(aes-128-gcm aes-192-gcm aes-256-gcm chacha20-ietf-poly1305 xchacha20-ietf-poly1305 2022-blake3-aes-128-gcm 2022-blake3-aes-256-gcm 2022-blake3-chacha20-poly1305)'
|
||||
|
||||
_arguments "-h::" \
|
||||
"-s:server host:_hosts" \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#compdef ss-tunnel
|
||||
|
||||
local ciphers
|
||||
ciphers='(rc4-md5 aes-128-gcm aes-192-gcm aes-256-gcm aes-128-cfb aes-192-cfb aes-256-cfb aes-128-ctr aes-192-ctr aes-256-ctr camellia-128-cfb camellia-192-cfb camellia-256-cfb bf-cfb chacha20-ietf-poly1305 xchacha20-ietf-poly1305 salsa20 chacha20 chacha20-ietf)'
|
||||
ciphers='(aes-128-gcm aes-192-gcm aes-256-gcm chacha20-ietf-poly1305 xchacha20-ietf-poly1305 2022-blake3-aes-128-gcm 2022-blake3-aes-256-gcm 2022-blake3-chacha20-poly1305)'
|
||||
|
||||
_arguments "-h::" \
|
||||
"-s:server host:_hosts" \
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ foreach(name IN LISTS DOC_NAMES)
|
||||
list(APPEND DOC_INPUTS "${CMAKE_CURRENT_SOURCE_DIR}/${name}.md")
|
||||
endforeach()
|
||||
set(DOC_SOURCES)
|
||||
foreach(name local.cpp server.cpp tunnel.cpp redir.cpp manager.cpp utils.cpp crypto/aead.cpp crypto/stream.cpp)
|
||||
foreach(name local.cpp server.cpp tunnel.cpp redir.cpp manager.cpp utils.cpp crypto/aead.cpp)
|
||||
list(APPEND DOC_SOURCES "${PROJECT_SOURCE_DIR}/src/${name}")
|
||||
endforeach()
|
||||
list(APPEND DOC_SOURCES "${PROJECT_SOURCE_DIR}/src/ss-nat")
|
||||
|
||||
@@ -72,7 +72,7 @@ The config file equivalent of command line options is listed as examples below.
|
||||
- `-b 2620:129:35::33`: `"local_ipv6_address": "2620:129:35::33"`
|
||||
- `-l 4321`: `"local_port": "4321"`
|
||||
- `-k "PasSworD"`: `"password": "PasSworD"`
|
||||
- `-m "aes-256-cfb"`: `"method": "aes-256-cfb"`
|
||||
- `-m "aes-256-gcm"`: `"method": "aes-256-gcm"`
|
||||
- `-t 60`: `"timeout": 60`
|
||||
- `-a nobody`: `"user": "nobody"`
|
||||
- `--acl "/path/to/acl"`: `"acl": "/path/to/acl"`
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ protocol:
|
||||
```
|
||||
# Start the manager. Arguments for ss-server will be passed to generated
|
||||
# ss-server process(es) respectively.
|
||||
ss-manager --manager-address /tmp/manager.sock --executable $(which ss-server) -s example.com -m aes-256-cfb -c /path/to/config.json
|
||||
ss-manager --manager-address /tmp/manager.sock --executable $(which ss-server) -s example.com -m aes-256-gcm -c /path/to/config.json
|
||||
|
||||
# Connect to the socket. Using netcat-openbsd as an example.
|
||||
# You should use scripts or other programs for further management.
|
||||
|
||||
+3
-3
@@ -83,8 +83,8 @@ engines do not link it.
|
||||
|
||||
## Crypto (`src/crypto/`)
|
||||
|
||||
`aead.hpp` (SIP004/SIP007 AEAD), `aead2022.hpp` (SIP022) and `stream.hpp`
|
||||
(legacy stream ciphers) provide value-type codecs: the cipher state is held
|
||||
`aead.hpp` (SIP004/SIP007 AEAD) and `aead2022.hpp` (SIP022) provide
|
||||
value-type codecs (the unauthenticated stream ciphers were removed): the cipher state is held
|
||||
inside the codec, TCP decoders reassemble into a fixed buffer sized for one
|
||||
chunk and can hand plaintext out in windows of any size, and HKDF-SHA1 runs
|
||||
on stack SHA-1 state. `keys.hpp` derives keys and lists the supported
|
||||
@@ -151,7 +151,7 @@ includes a DNS lookup.
|
||||
| aes-256-gcm | 89.1 | 39.2 |
|
||||
| chacha20-ietf-poly1305 | 83.1 | 33.0 |
|
||||
| 2022-blake3-aes-256-gcm | 84.0 | 33.0 |
|
||||
| aes-256-cfb (stream) | 77.1 | 26.5 |
|
||||
| aes-256-cfb (stream, since removed) | 77.1 | 26.5 |
|
||||
| aes-256-gcm, 1-byte payload | 85.0 | 36.0 |
|
||||
|
||||
The C++ programs are at 0 for every row, including the name-resolving
|
||||
|
||||
+12
-7
@@ -7,10 +7,15 @@ review: https://github.com/shadowsocks/shadowsocks-libev/pull/3050 (merged).
|
||||
## Compatibility contract
|
||||
|
||||
Keep the existing CLI, JSON configuration, ss:// parsing, SIP003 plugins,
|
||||
ACL blacklist/whitelist/outbound precedence, public shadowsocks.h API, legacy
|
||||
stream ciphers, AEAD and SIP022 TCP/UDP behavior in the compatibility profile.
|
||||
Removing regex or legacy ciphers is explicit opt-in, with configuration errors
|
||||
for unavailable functionality. Linux redirection/netfilter remains Linux-only.
|
||||
ACL blacklist/whitelist/outbound precedence, public shadowsocks.h API, AEAD
|
||||
and SIP022 TCP/UDP behavior in the compatibility profile. Removing regex is
|
||||
explicit opt-in, with configuration errors for unavailable functionality.
|
||||
|
||||
Amended in the C++20 rewrite: the unauthenticated stream ciphers
|
||||
(`aes-*-cfb`, `aes-*-ctr`, `camellia-*-cfb`, `chacha20`, `chacha20-ietf`,
|
||||
`salsa20`, and before them RC4 and `table`) are removed in every build. A
|
||||
configuration naming one fails at startup with a message pointing to an AEAD
|
||||
method. Linux redirection/netfilter remains Linux-only.
|
||||
|
||||
The sources are moving from C11 to C++20 module by module, with an explicit
|
||||
ownership model and no heap allocation after startup. The public
|
||||
@@ -34,7 +39,8 @@ ownership model and no heap allocation after startup. The public
|
||||
- [x] Portability layer for sockets/errors, I/O, clocks, randomness and processes.
|
||||
- [x] Runtime CI: Linux glibc/musl, macOS, FreeBSD, Windows MinGW.
|
||||
- [x] Document MSVC/event-loop compatibility assessment as a later milestone.
|
||||
- [x] Minimal profile: no regex/plugins/manager/legacy stream ciphers.
|
||||
- [x] Minimal profile: no regex/plugins/manager.
|
||||
- [x] Stream ciphers removed from all builds (C++20 rewrite).
|
||||
- [x] Crypto-only Mbed TLS configuration and evidence-backed provider assessment.
|
||||
- [x] Complete sanitizers/static analysis, compatibility and stress validation,
|
||||
release archive offline build, installed consumer, before/after report.
|
||||
@@ -162,8 +168,7 @@ or inventing replacement crypto implementations is outside this modernization.
|
||||
Retain libsodium plus Mbed TLS crypto primitives. The bundled Mbed TLS config
|
||||
omits TLS, X.509, certificate parsing and handshake machinery; it retains only
|
||||
cipher/hash primitives needed by protocol compatibility. Libsodium alone cannot
|
||||
replace AES-128-GCM, software AES fallback, MD5/SHA1 compatibility derivation and
|
||||
legacy stream modes. PCRE2 is absent in the minimal profile. This removes three
|
||||
replace AES-128-GCM, software AES fallback and MD5/SHA1 compatibility derivation. PCRE2 is absent in the minimal profile. This removes three
|
||||
submodule dependencies and reduces what is built without replacing audited
|
||||
cryptographic algorithms with project-specific implementations.
|
||||
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ connection as ss-server / ss-local.
|
||||
| aes-256-gcm | 39.0 / 39.0 | 0 / 0 |
|
||||
| chacha20-ietf-poly1305 | 33.1 / 33.0 | 0 / 0 |
|
||||
| 2022-blake3-aes-256-gcm | 34.0 / 32.9 | 0 / 0 |
|
||||
| aes-256-cfb | 27.1 / 26.4 | 0 / 0 |
|
||||
| aes-256-cfb (stream, since removed) | 27.1 / 26.4 | 0 / 0 |
|
||||
|
||||
With a host-name target (`localhost`, resolved by ss-server) the C++ programs
|
||||
are still at 0, because c-ares allocates from its fixed heap. The C ss-server
|
||||
|
||||
@@ -53,9 +53,6 @@ set(SS_RELAY_SOURCE
|
||||
crypto/aead2022.cpp
|
||||
crypto/keys.cpp
|
||||
)
|
||||
if(SS_ENABLE_LEGACY)
|
||||
list(APPEND SS_RELAY_SOURCE crypto/stream.cpp)
|
||||
endif()
|
||||
|
||||
set(SS_SHARED_SOURCES
|
||||
ppbloom.cpp
|
||||
|
||||
@@ -1,287 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "crypto/stream.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include <sodium.h>
|
||||
|
||||
#include "crypto/keys.hpp"
|
||||
|
||||
namespace ss::crypto {
|
||||
|
||||
namespace {
|
||||
|
||||
// [cli-stream-ciphers]
|
||||
constexpr StreamSpec kStreams[] = {
|
||||
{ "aes-128-cfb", StreamKind::aes_cfb, 16, 16 },
|
||||
{ "aes-192-cfb", StreamKind::aes_cfb, 24, 16 },
|
||||
{ "aes-256-cfb", StreamKind::aes_cfb, 32, 16 },
|
||||
{ "aes-128-ctr", StreamKind::aes_ctr, 16, 16 },
|
||||
{ "aes-192-ctr", StreamKind::aes_ctr, 24, 16 },
|
||||
{ "aes-256-ctr", StreamKind::aes_ctr, 32, 16 },
|
||||
{ "camellia-128-cfb", StreamKind::camellia_cfb, 16, 16 },
|
||||
{ "camellia-192-cfb", StreamKind::camellia_cfb, 24, 16 },
|
||||
{ "camellia-256-cfb", StreamKind::camellia_cfb, 32, 16 },
|
||||
{ "salsa20", StreamKind::salsa20, 32, 8 },
|
||||
{ "chacha20", StreamKind::chacha20, 32, 8 },
|
||||
{ "chacha20-ietf", StreamKind::chacha20_ietf, 32, 12 },
|
||||
};
|
||||
// [cli-stream-ciphers]
|
||||
|
||||
constexpr std::size_t kSodiumBlock = 64;
|
||||
|
||||
int sodium_xor(StreamKind kind, std::uint8_t *out, const std::uint8_t *in, std::size_t n, const std::uint8_t *nonce,
|
||||
std::uint64_t block, const std::uint8_t *key) noexcept
|
||||
{
|
||||
switch (kind) {
|
||||
case StreamKind::salsa20:
|
||||
return crypto_stream_salsa20_xor_ic(out, in, n, nonce, block, key);
|
||||
case StreamKind::chacha20:
|
||||
return crypto_stream_chacha20_xor_ic(out, in, n, nonce, block, key);
|
||||
case StreamKind::chacha20_ietf:
|
||||
return crypto_stream_chacha20_ietf_xor_ic(out, in, n, nonce, static_cast<std::uint32_t>(block), key);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<StreamSpec> find_stream(std::string_view name) noexcept
|
||||
{
|
||||
for (const StreamSpec &spec : kStreams) {
|
||||
if (spec.name == name) {
|
||||
return spec;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Result<StreamKey> StreamKey::from_password(const StreamSpec &spec, const char *password) noexcept
|
||||
{
|
||||
if (password == nullptr) {
|
||||
return fail(Errc::config);
|
||||
}
|
||||
StreamKey key{ spec, {} };
|
||||
if (!derive_key(password, { key.bytes.data(), spec.key_len })) {
|
||||
return fail(Errc::config);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
StreamCipher::Sodium::~Sodium()
|
||||
{
|
||||
sodium_memzero(key.data(), key.size());
|
||||
}
|
||||
|
||||
Status StreamCipher::init(const StreamKey &key, std::span<const std::uint8_t> iv, bool encrypt) noexcept
|
||||
{
|
||||
if (iv.size() != key.spec.iv_len) {
|
||||
return fail(Errc::invalid);
|
||||
}
|
||||
const unsigned bits = key.spec.key_len * 8U;
|
||||
switch (key.spec.kind) {
|
||||
case StreamKind::aes_cfb:
|
||||
case StreamKind::aes_ctr: {
|
||||
auto &aes = impl_.emplace<Aes>();
|
||||
// CFB and CTR only ever run the block cipher forwards.
|
||||
if (mbedtls_aes_setkey_enc(&aes.ctx, key.bytes.data(), bits) != 0) {
|
||||
return fail(Errc::crypto);
|
||||
}
|
||||
std::memcpy(aes.iv.data(), iv.data(), 16);
|
||||
aes.ctr = key.spec.kind == StreamKind::aes_ctr;
|
||||
aes.mode = encrypt ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT;
|
||||
return {};
|
||||
}
|
||||
case StreamKind::camellia_cfb: {
|
||||
auto &cam = impl_.emplace<Camellia>();
|
||||
if (mbedtls_camellia_setkey_enc(&cam.ctx, key.bytes.data(), bits) != 0) {
|
||||
return fail(Errc::crypto);
|
||||
}
|
||||
std::memcpy(cam.iv.data(), iv.data(), 16);
|
||||
cam.mode = encrypt ? MBEDTLS_CAMELLIA_ENCRYPT : MBEDTLS_CAMELLIA_DECRYPT;
|
||||
return {};
|
||||
}
|
||||
case StreamKind::salsa20:
|
||||
case StreamKind::chacha20:
|
||||
case StreamKind::chacha20_ietf: {
|
||||
auto &s = impl_.emplace<Sodium>();
|
||||
s.kind = key.spec.kind;
|
||||
std::memcpy(s.key.data(), key.bytes.data(), key.spec.key_len);
|
||||
std::memcpy(s.nonce.data(), iv.data(), iv.size());
|
||||
s.position = 0;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return fail(Errc::invalid);
|
||||
}
|
||||
|
||||
void StreamCipher::reset() noexcept
|
||||
{
|
||||
impl_.emplace<std::monostate>();
|
||||
}
|
||||
|
||||
Status StreamCipher::apply(const std::uint8_t *in, std::uint8_t *out, std::size_t n) noexcept
|
||||
{
|
||||
if (n == 0) {
|
||||
return {};
|
||||
}
|
||||
int rc = -1;
|
||||
if (auto *aes = std::get_if<Aes>(&impl_)) {
|
||||
if (aes->ctr) {
|
||||
rc = mbedtls_aes_crypt_ctr(&aes->ctx, n, &aes->offset, aes->iv.data(), aes->block.data(), in, out);
|
||||
} else {
|
||||
rc = mbedtls_aes_crypt_cfb128(&aes->ctx, aes->mode, n, &aes->offset, aes->iv.data(), in, out);
|
||||
}
|
||||
} else if (auto *cam = std::get_if<Camellia>(&impl_)) {
|
||||
rc = mbedtls_camellia_crypt_cfb128(&cam->ctx, cam->mode, n, &cam->offset, cam->iv.data(), in, out);
|
||||
} else if (auto *s = std::get_if<Sodium>(&impl_)) {
|
||||
rc = 0;
|
||||
std::size_t inner = static_cast<std::size_t>(s->position % kSodiumBlock);
|
||||
if (inner != 0) {
|
||||
// Finish the current keystream block from a scratch copy.
|
||||
std::uint8_t block[kSodiumBlock] = {};
|
||||
rc = sodium_xor(s->kind, block, block, kSodiumBlock, s->nonce.data(), s->position / kSodiumBlock,
|
||||
s->key.data());
|
||||
std::size_t take = std::min(kSodiumBlock - inner, n);
|
||||
for (std::size_t i = 0; i < take; ++i) {
|
||||
out[i] = in[i] ^ block[inner + i];
|
||||
}
|
||||
sodium_memzero(block, sizeof(block));
|
||||
in += take;
|
||||
out += take;
|
||||
n -= take;
|
||||
s->position += take;
|
||||
}
|
||||
if (rc == 0 && n != 0) {
|
||||
rc = sodium_xor(s->kind, out, in, n, s->nonce.data(), s->position / kSodiumBlock, s->key.data());
|
||||
s->position += n;
|
||||
}
|
||||
}
|
||||
if (rc != 0) {
|
||||
return fail(Errc::crypto);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Status StreamTcpEncryptor::init(const StreamKey &key, ReplayFilter filter) noexcept
|
||||
{
|
||||
key_ = key;
|
||||
filter_ = filter;
|
||||
iv_sent_ = false;
|
||||
randombytes_buf(iv_.data(), key.spec.iv_len);
|
||||
return cipher_.init(key_, iv(), true);
|
||||
}
|
||||
|
||||
Result<std::size_t> StreamTcpEncryptor::seal(std::span<const std::uint8_t> plaintext,
|
||||
std::span<std::uint8_t> out) noexcept
|
||||
{
|
||||
if (plaintext.empty()) {
|
||||
return std::size_t{ 0 };
|
||||
}
|
||||
if (out.size() < sealed_size(plaintext.size())) {
|
||||
return fail(Errc::invalid);
|
||||
}
|
||||
std::size_t at = 0;
|
||||
if (!iv_sent_) {
|
||||
std::memcpy(out.data(), iv_.data(), key_.spec.iv_len);
|
||||
at += key_.spec.iv_len;
|
||||
filter_.add(iv());
|
||||
iv_sent_ = true;
|
||||
}
|
||||
SS_TRY(cipher_.apply(plaintext.data(), out.data() + at, plaintext.size()));
|
||||
return at + plaintext.size();
|
||||
}
|
||||
|
||||
Status StreamTcpDecryptor::init(const StreamKey &key, ReplayFilter filter) noexcept
|
||||
{
|
||||
key_ = key;
|
||||
filter_ = filter;
|
||||
iv_have_ = 0;
|
||||
ready_ = false;
|
||||
failed_ = false;
|
||||
cipher_.reset();
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<StreamTcpDecryptor::Step> StreamTcpDecryptor::decrypt(std::span<const std::uint8_t> in,
|
||||
std::span<std::uint8_t> out) noexcept
|
||||
{
|
||||
if (failed_) {
|
||||
return fail(Errc::closed);
|
||||
}
|
||||
Step step{ 0, 0 };
|
||||
if (!ready_) {
|
||||
std::size_t want = key_.spec.iv_len - iv_have_;
|
||||
std::size_t take = std::min(want, in.size());
|
||||
if (take != 0) { // an empty span may have a null data()
|
||||
std::memcpy(iv_.data() + iv_have_, in.data(), take);
|
||||
}
|
||||
iv_have_ += take;
|
||||
step.consumed += take;
|
||||
in = in.subspan(take);
|
||||
if (iv_have_ < key_.spec.iv_len) {
|
||||
return step;
|
||||
}
|
||||
std::span<const std::uint8_t> iv(iv_.data(), key_.spec.iv_len);
|
||||
// Register the IV as soon as it is known, as the C decoder does, so
|
||||
// a concurrent connection cannot replay it in the meantime.
|
||||
if (filter_.check(iv)) {
|
||||
failed_ = true;
|
||||
return fail(Errc::crypto);
|
||||
}
|
||||
filter_.add(iv);
|
||||
Status st = cipher_.init(key_, iv, false);
|
||||
if (!st) {
|
||||
failed_ = true;
|
||||
return fail(st.error());
|
||||
}
|
||||
ready_ = true;
|
||||
}
|
||||
std::size_t n = std::min(in.size(), out.size());
|
||||
SS_TRY(cipher_.apply(in.data(), out.data(), n));
|
||||
step.consumed += n;
|
||||
step.produced = n;
|
||||
return step;
|
||||
}
|
||||
|
||||
Result<std::size_t> stream_udp_seal(const StreamKey &key, std::span<const std::uint8_t> plaintext,
|
||||
std::span<std::uint8_t> out, ReplayFilter filter) noexcept
|
||||
{
|
||||
const std::size_t iv_len = key.spec.iv_len;
|
||||
if (out.size() < iv_len + plaintext.size()) {
|
||||
return fail(Errc::invalid);
|
||||
}
|
||||
randombytes_buf(out.data(), iv_len);
|
||||
std::span<const std::uint8_t> iv(out.data(), iv_len);
|
||||
filter.add(iv);
|
||||
StreamCipher cipher;
|
||||
SS_TRY(cipher.init(key, iv, true));
|
||||
SS_TRY(cipher.apply(plaintext.data(), out.data() + iv_len, plaintext.size()));
|
||||
return iv_len + plaintext.size();
|
||||
}
|
||||
|
||||
Result<std::size_t> stream_udp_open(const StreamKey &key, std::span<const std::uint8_t> packet,
|
||||
std::span<std::uint8_t> out, ReplayFilter filter) noexcept
|
||||
{
|
||||
const std::size_t iv_len = key.spec.iv_len;
|
||||
if (packet.size() <= iv_len) {
|
||||
return fail(Errc::protocol);
|
||||
}
|
||||
std::size_t n = packet.size() - iv_len;
|
||||
if (out.size() < n) {
|
||||
return fail(Errc::invalid);
|
||||
}
|
||||
std::span<const std::uint8_t> iv = packet.first(iv_len);
|
||||
if (filter.check(iv)) {
|
||||
return fail(Errc::crypto);
|
||||
}
|
||||
StreamCipher cipher;
|
||||
SS_TRY(cipher.init(key, iv, false));
|
||||
SS_TRY(cipher.apply(packet.data() + iv_len, out.data(), n));
|
||||
filter.add(iv);
|
||||
return n;
|
||||
}
|
||||
|
||||
} // namespace ss::crypto
|
||||
@@ -1,159 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef SS_CRYPTO_STREAM_HPP
|
||||
#define SS_CRYPTO_STREAM_HPP
|
||||
|
||||
// Legacy (unauthenticated) stream ciphers as value types.
|
||||
//
|
||||
// Replaces stream.cpp's per-connection mbedtls_cipher_context_t (allocated by
|
||||
// mbedtls_cipher_setup), its static scratch buffers and the padding copies
|
||||
// it makes for the libsodium ciphers. The block-cipher contexts are held by
|
||||
// value through the AES/Camellia APIs directly; the libsodium ciphers track
|
||||
// their keystream position and handle partial blocks with a 64-byte stack
|
||||
// block. Only built with SS_ENABLE_LEGACY.
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
#include <mbedtls/aes.h>
|
||||
#include <mbedtls/camellia.h>
|
||||
|
||||
#include "core/result.hpp"
|
||||
#include "crypto/aead.hpp" // ReplayFilter
|
||||
|
||||
namespace ss::crypto {
|
||||
|
||||
inline constexpr std::size_t kMaxStreamIv = 16;
|
||||
inline constexpr std::size_t kMaxStreamKey = 32;
|
||||
|
||||
enum class StreamKind : std::uint8_t {
|
||||
aes_cfb,
|
||||
aes_ctr,
|
||||
camellia_cfb,
|
||||
salsa20,
|
||||
chacha20,
|
||||
chacha20_ietf,
|
||||
};
|
||||
|
||||
struct StreamSpec {
|
||||
std::string_view name;
|
||||
StreamKind kind;
|
||||
std::uint8_t key_len;
|
||||
std::uint8_t iv_len;
|
||||
};
|
||||
|
||||
// The stream method called `name`, if this build supports it (RC4, table and
|
||||
// the ciphers Mbed TLS 3 dropped are not).
|
||||
std::optional<StreamSpec> find_stream(std::string_view name) noexcept;
|
||||
|
||||
struct StreamKey {
|
||||
StreamSpec spec;
|
||||
std::array<std::uint8_t, kMaxStreamKey> bytes{};
|
||||
|
||||
static Result<StreamKey> from_password(const StreamSpec &spec, const char *password) noexcept;
|
||||
};
|
||||
|
||||
// Keystream for one direction of one connection.
|
||||
class StreamCipher {
|
||||
public:
|
||||
StreamCipher() noexcept = default;
|
||||
StreamCipher(const StreamCipher &) = delete;
|
||||
StreamCipher &operator=(const StreamCipher &) = delete;
|
||||
~StreamCipher() { reset(); }
|
||||
|
||||
Status init(const StreamKey &key, std::span<const std::uint8_t> iv, bool encrypt) noexcept;
|
||||
void reset() noexcept;
|
||||
|
||||
// XOR the next n keystream bytes; `in` and `out` may be the same buffer.
|
||||
Status apply(const std::uint8_t *in, std::uint8_t *out, std::size_t n) noexcept;
|
||||
|
||||
private:
|
||||
struct Aes {
|
||||
Aes() noexcept { mbedtls_aes_init(&ctx); }
|
||||
~Aes() { mbedtls_aes_free(&ctx); }
|
||||
Aes(const Aes &) = delete;
|
||||
Aes &operator=(const Aes &) = delete;
|
||||
mbedtls_aes_context ctx;
|
||||
std::array<unsigned char, 16> iv{};
|
||||
std::array<unsigned char, 16> block{};
|
||||
std::size_t offset = 0;
|
||||
bool ctr = false;
|
||||
int mode = MBEDTLS_AES_ENCRYPT;
|
||||
};
|
||||
struct Camellia {
|
||||
Camellia() noexcept { mbedtls_camellia_init(&ctx); }
|
||||
~Camellia() { mbedtls_camellia_free(&ctx); }
|
||||
Camellia(const Camellia &) = delete;
|
||||
Camellia &operator=(const Camellia &) = delete;
|
||||
mbedtls_camellia_context ctx;
|
||||
std::array<unsigned char, 16> iv{};
|
||||
std::size_t offset = 0;
|
||||
int mode = MBEDTLS_CAMELLIA_ENCRYPT;
|
||||
};
|
||||
struct Sodium {
|
||||
~Sodium();
|
||||
StreamKind kind = StreamKind::salsa20;
|
||||
std::array<std::uint8_t, kMaxStreamKey> key{};
|
||||
std::array<std::uint8_t, 12> nonce{};
|
||||
std::uint64_t position = 0; // keystream bytes used so far
|
||||
};
|
||||
|
||||
std::variant<std::monostate, Aes, Camellia, Sodium> impl_;
|
||||
};
|
||||
|
||||
class StreamTcpEncryptor {
|
||||
public:
|
||||
// A fresh random IV.
|
||||
Status init(const StreamKey &key, ReplayFilter filter) noexcept;
|
||||
std::size_t sealed_size(std::size_t plaintext_len) const noexcept
|
||||
{
|
||||
return plaintext_len + (iv_sent_ ? 0 : key_.spec.iv_len);
|
||||
}
|
||||
// The first call also emits the IV; returns the bytes written.
|
||||
Result<std::size_t> seal(std::span<const std::uint8_t> plaintext, std::span<std::uint8_t> out) noexcept;
|
||||
std::span<const std::uint8_t> iv() const noexcept { return { iv_.data(), key_.spec.iv_len }; }
|
||||
|
||||
private:
|
||||
StreamKey key_{};
|
||||
ReplayFilter filter_{};
|
||||
StreamCipher cipher_;
|
||||
std::array<std::uint8_t, kMaxStreamIv> iv_{};
|
||||
bool iv_sent_ = false;
|
||||
};
|
||||
|
||||
class StreamTcpDecryptor {
|
||||
public:
|
||||
Status init(const StreamKey &key, ReplayFilter filter) noexcept;
|
||||
|
||||
struct Step {
|
||||
std::size_t consumed;
|
||||
std::size_t produced;
|
||||
};
|
||||
// Collect the IV, then decrypt min(remaining input, out.size()) bytes.
|
||||
// Fails with Errc::crypto when the IV was seen before.
|
||||
Result<Step> decrypt(std::span<const std::uint8_t> in, std::span<std::uint8_t> out) noexcept;
|
||||
bool iv_received() const noexcept { return ready_; }
|
||||
|
||||
private:
|
||||
StreamKey key_{};
|
||||
ReplayFilter filter_{};
|
||||
StreamCipher cipher_;
|
||||
std::array<std::uint8_t, kMaxStreamIv> iv_{};
|
||||
std::size_t iv_have_ = 0;
|
||||
bool ready_ = false;
|
||||
bool failed_ = false;
|
||||
};
|
||||
|
||||
// One-shot UDP packet: [iv][ciphertext].
|
||||
Result<std::size_t> stream_udp_seal(const StreamKey &key, std::span<const std::uint8_t> plaintext,
|
||||
std::span<std::uint8_t> out, ReplayFilter filter) noexcept;
|
||||
Result<std::size_t> stream_udp_open(const StreamKey &key, std::span<const std::uint8_t> packet,
|
||||
std::span<std::uint8_t> out, ReplayFilter filter) noexcept;
|
||||
|
||||
} // namespace ss::crypto
|
||||
|
||||
#endif
|
||||
+15
-39
@@ -62,23 +62,22 @@ Status Method::init(const char *method, const char *password, const char *key, R
|
||||
}
|
||||
return {};
|
||||
}
|
||||
#if SS_ENABLE_LEGACY
|
||||
if (auto spec = crypto::find_stream(method)) {
|
||||
LOGI("Stream ciphers are insecure, therefore deprecated, and should be almost always avoided.");
|
||||
if (key != nullptr) {
|
||||
crypto::StreamKey stream{ *spec, {} };
|
||||
if (!crypto::parse_key(key, { stream.bytes.data(), spec->key_len }, false)) {
|
||||
crypto::report_bad_key("key", spec->key_len);
|
||||
return fail(Errc::config);
|
||||
}
|
||||
key_ = stream;
|
||||
} else {
|
||||
SS_TRY_ASSIGN(stream, crypto::StreamKey::from_password(*spec, password));
|
||||
key_ = stream;
|
||||
// The unauthenticated stream ciphers (and RC4/table before them) were
|
||||
// removed; say so instead of calling the name unknown.
|
||||
static constexpr const char *kRemoved[] = {
|
||||
"table", "rc4", "rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr",
|
||||
"aes-256-ctr", "bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", "cast5-cfb",
|
||||
"des-cfb", "idea-cfb", "rc2-cfb", "seed-cfb", "salsa20", "chacha20", "chacha20-ietf",
|
||||
};
|
||||
for (const char *name : kRemoved) {
|
||||
if (std::strcmp(name, method) == 0) {
|
||||
LOGE("stream cipher %s is no longer supported; use an AEAD method such as chacha20-ietf-poly1305 "
|
||||
"or 2022-blake3-aes-256-gcm",
|
||||
method);
|
||||
return fail(Errc::config);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
LOGE("unsupported method: %s", method);
|
||||
return fail(Errc::config);
|
||||
}
|
||||
|
||||
@@ -103,25 +102,11 @@ Status Encoder::init(Method &method, bool plain, const Decoder *peer, bool print
|
||||
const crypto::Aead2022TcpDecryptor *request = peer != nullptr ? peer->as_2022() : nullptr;
|
||||
return impl_.emplace<crypto::Aead2022TcpEncryptor>().init(*psk, method.role_, request);
|
||||
}
|
||||
#if SS_ENABLE_LEGACY
|
||||
if (auto *stream = std::get_if<crypto::StreamKey>(&method.key_)) {
|
||||
return impl_.emplace<crypto::StreamTcpEncryptor>().init(*stream, filter);
|
||||
}
|
||||
#endif
|
||||
return fail(Errc::config);
|
||||
}
|
||||
|
||||
// Explicit dispatch: std::visit can report bad_variant_access through the
|
||||
// C++ runtime, which the programs do not link.
|
||||
#if SS_ENABLE_LEGACY
|
||||
#define SS_STREAM_CASE(E, expr) \
|
||||
if (auto *e = std::get_if<crypto::Stream##E>(&impl_)) { \
|
||||
return expr; \
|
||||
}
|
||||
#else
|
||||
#define SS_STREAM_CASE(E, expr)
|
||||
#endif
|
||||
|
||||
std::size_t Encoder::sealed_size(std::size_t plaintext_len) const noexcept
|
||||
{
|
||||
if (auto *e = std::get_if<crypto::AeadTcpEncryptor>(&impl_)) {
|
||||
@@ -130,7 +115,6 @@ std::size_t Encoder::sealed_size(std::size_t plaintext_len) const noexcept
|
||||
if (auto *e = std::get_if<crypto::Aead2022TcpEncryptor>(&impl_)) {
|
||||
return e->sealed_size(plaintext_len);
|
||||
}
|
||||
SS_STREAM_CASE(TcpEncryptor, e->sealed_size(plaintext_len))
|
||||
return plaintext_len;
|
||||
}
|
||||
|
||||
@@ -142,7 +126,6 @@ Result<std::size_t> Encoder::seal(std::span<const std::uint8_t> plaintext, std::
|
||||
if (auto *e = std::get_if<crypto::Aead2022TcpEncryptor>(&impl_)) {
|
||||
return e->seal(plaintext, out);
|
||||
}
|
||||
SS_STREAM_CASE(TcpEncryptor, e->seal(plaintext, out))
|
||||
if (out.size() < plaintext.size()) {
|
||||
return fail(Errc::invalid);
|
||||
}
|
||||
@@ -179,11 +162,6 @@ Status Decoder::init(Method &method, bool plain, const Encoder *peer) noexcept
|
||||
}
|
||||
return impl_.emplace<crypto::Aead2022TcpDecryptor>().init(*psk, Role::server, {}, &method.salt_pool_);
|
||||
}
|
||||
#if SS_ENABLE_LEGACY
|
||||
if (auto *stream = std::get_if<crypto::StreamKey>(&method.key_)) {
|
||||
return impl_.emplace<crypto::StreamTcpDecryptor>().init(*stream, filter);
|
||||
}
|
||||
#endif
|
||||
return fail(Errc::config);
|
||||
}
|
||||
|
||||
@@ -195,7 +173,7 @@ std::size_t Decoder::accept_limit() const noexcept
|
||||
if (auto *d = std::get_if<crypto::Aead2022TcpDecryptor>(&impl_)) {
|
||||
return d->accept_limit();
|
||||
}
|
||||
return SIZE_MAX; // plain and stream decode as much as there is room for
|
||||
return SIZE_MAX; // plain decodes as much as there is room for
|
||||
}
|
||||
|
||||
Result<Decoder::Step> Decoder::decrypt(std::span<const std::uint8_t> in, std::span<std::uint8_t> out) noexcept
|
||||
@@ -212,7 +190,6 @@ Result<Decoder::Step> Decoder::decrypt(std::span<const std::uint8_t> in, std::sp
|
||||
if (auto *d = std::get_if<crypto::Aead2022TcpDecryptor>(&impl_)) {
|
||||
return convert(d->decrypt(in, out));
|
||||
}
|
||||
SS_STREAM_CASE(TcpDecryptor, convert(e->decrypt(in, out)))
|
||||
std::size_t n = std::min(in.size(), out.size());
|
||||
if (n != 0) {
|
||||
std::memcpy(out.data(), in.data(), n);
|
||||
@@ -220,6 +197,5 @@ Result<Decoder::Step> Decoder::decrypt(std::span<const std::uint8_t> in, std::sp
|
||||
return Step{ n, n };
|
||||
}
|
||||
|
||||
#undef SS_STREAM_CASE
|
||||
|
||||
} // namespace ss::relay
|
||||
@@ -19,9 +19,6 @@
|
||||
#include "core/result.hpp"
|
||||
#include "crypto/aead.hpp"
|
||||
#include "crypto/aead2022.hpp"
|
||||
#if SS_ENABLE_LEGACY
|
||||
#include "crypto/stream.hpp"
|
||||
#endif
|
||||
|
||||
namespace ss::relay {
|
||||
|
||||
@@ -39,9 +36,6 @@ public:
|
||||
// The key, by family (exactly one is set).
|
||||
const crypto::MasterKey *aead() const noexcept { return std::get_if<crypto::MasterKey>(&key_); }
|
||||
const crypto::Psk *psk() const noexcept { return std::get_if<crypto::Psk>(&key_); }
|
||||
#if SS_ENABLE_LEGACY
|
||||
const crypto::StreamKey *stream() const noexcept { return std::get_if<crypto::StreamKey>(&key_); }
|
||||
#endif
|
||||
bool is_2022() const noexcept { return std::holds_alternative<crypto::Psk>(key_); }
|
||||
// Room a first seal() needs on top of the plaintext.
|
||||
static constexpr std::size_t kMaxOverhead = 2048;
|
||||
@@ -56,11 +50,7 @@ private:
|
||||
friend class Encoder;
|
||||
friend class Decoder;
|
||||
|
||||
#if SS_ENABLE_LEGACY
|
||||
using Key = std::variant<crypto::MasterKey, crypto::Psk, crypto::StreamKey>;
|
||||
#else
|
||||
using Key = std::variant<crypto::MasterKey, crypto::Psk>;
|
||||
#endif
|
||||
Key key_{};
|
||||
Role role_ = Role::client;
|
||||
bool bloom_ready_ = false;
|
||||
@@ -81,11 +71,7 @@ public:
|
||||
|
||||
private:
|
||||
struct Plain {};
|
||||
#if SS_ENABLE_LEGACY
|
||||
std::variant<Plain, crypto::AeadTcpEncryptor, crypto::Aead2022TcpEncryptor, crypto::StreamTcpEncryptor> impl_;
|
||||
#else
|
||||
std::variant<Plain, crypto::AeadTcpEncryptor, crypto::Aead2022TcpEncryptor> impl_;
|
||||
#endif
|
||||
};
|
||||
|
||||
class Decoder {
|
||||
@@ -114,11 +100,7 @@ public:
|
||||
|
||||
private:
|
||||
struct Plain {};
|
||||
#if SS_ENABLE_LEGACY
|
||||
std::variant<Plain, crypto::AeadTcpDecryptor, crypto::Aead2022TcpDecryptor, crypto::StreamTcpDecryptor> impl_;
|
||||
#else
|
||||
std::variant<Plain, crypto::AeadTcpDecryptor, crypto::Aead2022TcpDecryptor> impl_;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace ss::relay
|
||||
|
||||
@@ -557,11 +557,6 @@ void UdpRelay::client_packet(Listener &listener, const sockaddr_storage &from, s
|
||||
sealed = crypto::udp2022_seal(*psk, Role::client, session->client2022_, plain, in_);
|
||||
}
|
||||
}
|
||||
#if SS_ENABLE_LEGACY
|
||||
else if (const crypto::StreamKey *stream = method_->stream()) {
|
||||
sealed = crypto::stream_udp_seal(*stream, plain, in_, crypto::ReplayFilter::ppbloom());
|
||||
}
|
||||
#endif
|
||||
if (!sealed) {
|
||||
return; // drop the packet silently
|
||||
}
|
||||
@@ -626,11 +621,6 @@ void UdpRelay::server_packet(Listener &listener, const sockaddr_storage &from, s
|
||||
}
|
||||
}
|
||||
}
|
||||
#if SS_ENABLE_LEGACY
|
||||
else if (const crypto::StreamKey *stream = method_->stream()) {
|
||||
opened = crypto::stream_udp_open(*stream, packet, out_, crypto::ReplayFilter::ppbloom());
|
||||
}
|
||||
#endif
|
||||
if (!opened) {
|
||||
char peer[INET6_ADDRSTRLEN] = {};
|
||||
const void *raw = from.ss_family == AF_INET
|
||||
@@ -766,11 +756,6 @@ void UdpRelay::reply(UdpSession &session) noexcept
|
||||
sealed = crypto::udp2022_seal(*psk, Role::server, session.server2022_->state, plain, in_);
|
||||
}
|
||||
}
|
||||
#if SS_ENABLE_LEGACY
|
||||
else if (const crypto::StreamKey *stream = method_->stream()) {
|
||||
sealed = crypto::stream_udp_seal(*stream, plain, in_, crypto::ReplayFilter::ppbloom());
|
||||
}
|
||||
#endif
|
||||
if (!sealed) {
|
||||
continue;
|
||||
}
|
||||
@@ -786,11 +771,6 @@ void UdpRelay::reply(UdpSession &session) noexcept
|
||||
static_cast<std::uint64_t>(std::time(nullptr)));
|
||||
}
|
||||
}
|
||||
#if SS_ENABLE_LEGACY
|
||||
else if (const crypto::StreamKey *stream = method_->stream()) {
|
||||
opened = crypto::stream_udp_open(*stream, packet, out_, crypto::ReplayFilter::ppbloom());
|
||||
}
|
||||
#endif
|
||||
if (!opened) {
|
||||
LOGE("failed to handshake with the server: %s", "suspicious UDP packet");
|
||||
continue;
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ typedef struct {
|
||||
* const profile_t EXAMPLE_PROFILE = {
|
||||
* .remote_host = "example.com",
|
||||
* .local_addr = "127.0.0.1",
|
||||
* .method = "bf-cfb",
|
||||
* .method = "aes-256-gcm",
|
||||
* .password = "barfoo!",
|
||||
* .remote_port = 8338,
|
||||
* .local_port = 1080,
|
||||
|
||||
+2
-3
@@ -379,9 +379,8 @@ Set the cipher. The default is `chacha20-ietf-poly1305`.
|
||||
AEAD cipher names from the source (availability depends on the build):
|
||||
\snippet aead.cpp cli-aead-ciphers
|
||||
|
||||
Legacy stream cipher names recognized by the source (disabled in minimal builds;
|
||||
some require backend support):
|
||||
\snippet stream.cpp cli-stream-ciphers
|
||||
The unauthenticated stream ciphers (`aes-256-cfb`, `chacha20-ietf`, `rc4-md5`
|
||||
and the like) are no longer supported.
|
||||
|
||||
The `2022-blake3-*` ciphers implement Shadowsocks 2022 (SIP022). They require
|
||||
a base64-encoded pre-shared key supplied with `--key` or `--password` (*-k*): 16 bytes for
|
||||
|
||||
@@ -210,9 +210,6 @@ set(SS_CRYPTO_TEST_SOURCES
|
||||
${PROJECT_SOURCE_DIR}/src/utils.cpp
|
||||
)
|
||||
|
||||
if(SS_ENABLE_LEGACY)
|
||||
list(APPEND SS_CRYPTO_TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/reference/stream.cpp)
|
||||
endif()
|
||||
|
||||
# test_buffer - needs sodium and mbedtls for crypto.cpp buffer functions
|
||||
ss_add_test(test_buffer
|
||||
@@ -250,13 +247,6 @@ ss_add_test(test_crypto_aead2022_cpp
|
||||
"${TEST_CRYPTO_LIBS};${SS_MATH_LIBRARY}")
|
||||
ss_strict_warnings(test_crypto_aead2022_cpp.cpp ${PROJECT_SOURCE_DIR}/src/crypto/aead2022.cpp)
|
||||
|
||||
if(SS_ENABLE_LEGACY)
|
||||
# test_crypto_stream_cpp - C++ stream codecs, differentially against stream.cpp
|
||||
ss_add_test(test_crypto_stream_cpp
|
||||
"test_crypto_stream_cpp.cpp;${PROJECT_SOURCE_DIR}/src/crypto/stream.cpp;${PROJECT_SOURCE_DIR}/src/crypto/aead.cpp;${PROJECT_SOURCE_DIR}/src/crypto/keys.cpp;${SS_CRYPTO_TEST_SOURCES}"
|
||||
"${TEST_CRYPTO_LIBS};${SS_MATH_LIBRARY}")
|
||||
ss_strict_warnings(test_crypto_stream_cpp.cpp ${PROJECT_SOURCE_DIR}/src/crypto/stream.cpp)
|
||||
endif()
|
||||
|
||||
# test_interop_rust - interoperability against shadowsocks-rust.
|
||||
# Skips itself when shadowsocks-rust or the built binaries are absent.
|
||||
@@ -359,12 +349,6 @@ if(SS_BUILD_EXECUTABLES AND NOT CMAKE_CROSSCOMPILING)
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/alloc_audit/check_steady.py
|
||||
--shim $<TARGET_FILE:ss_alloc_audit> -- $<TARGET_FILE:test_crypto_aead2022_cpp>)
|
||||
set_tests_properties(alloc_steady_crypto_aead2022 PROPERTIES LABELS "unit" TIMEOUT 60)
|
||||
if(TARGET test_crypto_stream_cpp)
|
||||
add_test(NAME alloc_steady_crypto_stream
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/alloc_audit/check_steady.py
|
||||
--shim $<TARGET_FILE:ss_alloc_audit> -- $<TARGET_FILE:test_crypto_stream_cpp>)
|
||||
set_tests_properties(alloc_steady_crypto_stream PROPERTIES LABELS "unit" TIMEOUT 60)
|
||||
endif()
|
||||
add_test(NAME alloc_steady_cache
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/alloc_audit/check_steady.py
|
||||
--shim $<TARGET_FILE:ss_alloc_audit> -- $<TARGET_FILE:test_cache_cpp>)
|
||||
@@ -374,9 +358,6 @@ if(SS_BUILD_EXECUTABLES AND NOT CMAKE_CROSSCOMPILING)
|
||||
# address, for each cipher family.
|
||||
set(SS_ALLOC_CASES "aes-256-gcm@localhost" "2022-blake3-aes-256-gcm@127.0.0.1"
|
||||
"chacha20-ietf-poly1305@127.0.0.1")
|
||||
if(SS_ENABLE_LEGACY)
|
||||
list(APPEND SS_ALLOC_CASES "aes-256-cfb@127.0.0.1")
|
||||
endif()
|
||||
foreach(case ${SS_ALLOC_CASES})
|
||||
string(REPLACE "@" ";" parts "${case}")
|
||||
list(GET parts 0 method)
|
||||
@@ -405,9 +386,6 @@ if(SS_BUILD_EXECUTABLES AND NOT CMAKE_CROSSCOMPILING)
|
||||
# ss-tunnel through the C relay engine against ss-server (and
|
||||
# shadowsocks-rust when installed), every method.
|
||||
set(SS_TUNNEL_INTEROP_ARGS --bin ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
if(SS_ENABLE_LEGACY)
|
||||
list(APPEND SS_TUNNEL_INTEROP_ARGS --legacy)
|
||||
endif()
|
||||
add_test(NAME test_limits
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_limits.py
|
||||
--bin ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"server":"127.0.0.1",
|
||||
"server_port":8389,
|
||||
"local_port":1081,
|
||||
"password":"aes_password",
|
||||
"timeout":60,
|
||||
"method":"aes-256-ctr",
|
||||
"local":"127.0.0.1",
|
||||
"fast_open":false
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"server":"127.0.0.1",
|
||||
"server_port":8389,
|
||||
"local_port":1081,
|
||||
"password":"aes_password",
|
||||
"timeout":60,
|
||||
"method":"aes-256-cfb",
|
||||
"local":"127.0.0.1",
|
||||
"fast_open":false
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"server":"127.0.0.1",
|
||||
"server_port":8389,
|
||||
"local_port":1081,
|
||||
"password":"salsa20_password",
|
||||
"timeout":60,
|
||||
"method":"chacha20-ietf",
|
||||
"local":"127.0.0.1",
|
||||
"fast_open":false
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"server":"127.0.0.1",
|
||||
"server_port":8389,
|
||||
"local_port":1081,
|
||||
"password":"chacha20_password",
|
||||
"timeout":60,
|
||||
"method":"chacha20",
|
||||
"local":"127.0.0.1",
|
||||
"fast_open":false
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"server":"127.0.0.1",
|
||||
"server_port":8389,
|
||||
"local_port":1081,
|
||||
"password":"aes_password",
|
||||
"timeout":60,
|
||||
"method":"rc4-md5",
|
||||
"local":"127.0.0.1",
|
||||
"fast_open":false
|
||||
}
|
||||
@@ -38,7 +38,6 @@
|
||||
|
||||
#include "base64.h"
|
||||
#include "crypto.h"
|
||||
#include "stream.h"
|
||||
#include "aead.h"
|
||||
#include "utils.h"
|
||||
#include "ppbloom.h"
|
||||
@@ -150,32 +149,6 @@ crypto_init(const char *password, const char *key, const char *method)
|
||||
#endif
|
||||
|
||||
if (method != NULL) {
|
||||
#if SS_ENABLE_LEGACY
|
||||
for (i = 0; i < STREAM_CIPHER_NUM; i++)
|
||||
if (strcmp(method, supported_stream_ciphers[i]) == 0) {
|
||||
m = i;
|
||||
break;
|
||||
}
|
||||
if (m != -1) {
|
||||
LOGI("Stream ciphers are insecure, therefore deprecated, and should be almost always avoided.");
|
||||
cipher_t *cipher = stream_init(password, key, method);
|
||||
if (cipher == NULL)
|
||||
return NULL;
|
||||
crypto_t *crypto = (crypto_t *)ss_malloc(sizeof(crypto_t));
|
||||
crypto_t tmp = {
|
||||
.cipher = cipher,
|
||||
.encrypt_all = &stream_encrypt_all,
|
||||
.decrypt_all = &stream_decrypt_all,
|
||||
.encrypt = &stream_encrypt,
|
||||
.decrypt = &stream_decrypt,
|
||||
.ctx_init = &stream_ctx_init,
|
||||
.ctx_release = &stream_ctx_release,
|
||||
};
|
||||
memcpy((void *)crypto, &tmp, sizeof(crypto_t)); /* const members: copy the bytes */
|
||||
return crypto;
|
||||
}
|
||||
|
||||
#endif
|
||||
for (i = 0; i < AEAD_CIPHER_NUM; i++)
|
||||
if (strcmp(method, supported_aead_ciphers[i]) == 0) {
|
||||
m = i;
|
||||
|
||||
@@ -52,10 +52,6 @@ typedef mbedtls_md_info_t digest_type_t;
|
||||
#define MAX_KEY_LENGTH 64
|
||||
#define MAX_NONCE_LENGTH 32
|
||||
#define MAX_MD_SIZE MBEDTLS_MD_MAX_SIZE
|
||||
/* we must have MBEDTLS_CIPHER_MODE_CFB defined */
|
||||
#if SS_ENABLE_LEGACY && !defined(MBEDTLS_CIPHER_MODE_CFB)
|
||||
#error Cipher Feedback mode a.k.a CFB not supported by your mbed TLS.
|
||||
#endif
|
||||
#ifndef MBEDTLS_GCM_C
|
||||
#error No GCM support detected
|
||||
#endif
|
||||
@@ -210,7 +206,6 @@ void dump(char *tag, char *text, int len);
|
||||
#endif
|
||||
|
||||
extern struct cache *nonce_cache;
|
||||
extern const char *supported_stream_ciphers[];
|
||||
extern const char *supported_aead_ciphers[];
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,654 +0,0 @@
|
||||
/*
|
||||
* stream.c - Manage stream ciphers
|
||||
*
|
||||
* Copyright (C) 2013 - 2019, Max Lv <max.c.lv@gmail.com>
|
||||
*
|
||||
* This file is part of the shadowsocks-libev.
|
||||
*
|
||||
* shadowsocks-libev is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* shadowsocks-libev is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with shadowsocks-libev; see the file COPYING. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/version.h>
|
||||
#define CIPHER_UNSUPPORTED "unsupported"
|
||||
|
||||
#include <sodium.h>
|
||||
|
||||
#include "ppbloom.h"
|
||||
#include "stream.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define SODIUM_BLOCK_SIZE 64
|
||||
|
||||
/*
|
||||
* Spec: http://shadowsocks.org/en/spec/Stream-Ciphers.html
|
||||
*
|
||||
* Stream ciphers provide only confidentiality. Data integrity and authenticity is not guaranteed. Users should use AEAD
|
||||
* ciphers whenever possible.
|
||||
*
|
||||
* Stream Encryption/Decryption
|
||||
*
|
||||
* Stream_encrypt is a function that takes a secret key, an initialization vector, a message, and produces a ciphertext
|
||||
* with the same length as the message.
|
||||
*
|
||||
* Stream_encrypt(key, IV, message) => ciphertext
|
||||
*
|
||||
* Stream_decrypt is a function that takes a secret key, an initializaiton vector, a ciphertext, and produces the
|
||||
* original message.
|
||||
*
|
||||
* Stream_decrypt(key, IV, ciphertext) => message
|
||||
*
|
||||
* TCP
|
||||
*
|
||||
* A stream cipher encrypted TCP stream starts with a randomly generated initializaiton vector, followed by encrypted
|
||||
* payload data.
|
||||
*
|
||||
* [IV][encrypted payload]
|
||||
*
|
||||
* UDP
|
||||
*
|
||||
* A stream cipher encrypted UDP packet has the following structure:
|
||||
*
|
||||
* [IV][encrypted payload]
|
||||
*
|
||||
* Each UDP packet is encrypted/decrypted independently with a randomly generated initialization vector.
|
||||
*
|
||||
*/
|
||||
|
||||
#define NONE -1
|
||||
#define TABLE 0
|
||||
#define RC4 1
|
||||
#define RC4_MD5 2
|
||||
#define AES_128_CFB 3
|
||||
#define AES_192_CFB 4
|
||||
#define AES_256_CFB 5
|
||||
#define AES_128_CTR 6
|
||||
#define AES_192_CTR 7
|
||||
#define AES_256_CTR 8
|
||||
#define BF_CFB 9
|
||||
#define CAMELLIA_128_CFB 10
|
||||
#define CAMELLIA_192_CFB 11
|
||||
#define CAMELLIA_256_CFB 12
|
||||
#define CAST5_CFB 13
|
||||
#define DES_CFB 14
|
||||
#define IDEA_CFB 15
|
||||
#define RC2_CFB 16
|
||||
#define SEED_CFB 17
|
||||
#define SALSA20 18
|
||||
#define CHACHA20 19
|
||||
#define CHACHA20IETF 20
|
||||
|
||||
const char *supported_stream_ciphers[STREAM_CIPHER_NUM] = {
|
||||
// [cli-stream-ciphers]
|
||||
"table",
|
||||
"rc4",
|
||||
"rc4-md5",
|
||||
"aes-128-cfb",
|
||||
"aes-192-cfb",
|
||||
"aes-256-cfb",
|
||||
"aes-128-ctr",
|
||||
"aes-192-ctr",
|
||||
"aes-256-ctr",
|
||||
"bf-cfb",
|
||||
"camellia-128-cfb",
|
||||
"camellia-192-cfb",
|
||||
"camellia-256-cfb",
|
||||
"cast5-cfb",
|
||||
"des-cfb",
|
||||
"idea-cfb",
|
||||
"rc2-cfb",
|
||||
"seed-cfb",
|
||||
"salsa20",
|
||||
"chacha20",
|
||||
"chacha20-ietf"
|
||||
// [cli-stream-ciphers]
|
||||
};
|
||||
|
||||
static const char *supported_stream_ciphers_mbedtls[STREAM_CIPHER_NUM] = {
|
||||
"table",
|
||||
"ARC4-128",
|
||||
"ARC4-128",
|
||||
"AES-128-CFB128",
|
||||
"AES-192-CFB128",
|
||||
"AES-256-CFB128",
|
||||
"AES-128-CTR",
|
||||
"AES-192-CTR",
|
||||
"AES-256-CTR",
|
||||
"BLOWFISH-CFB64",
|
||||
"CAMELLIA-128-CFB128",
|
||||
"CAMELLIA-192-CFB128",
|
||||
"CAMELLIA-256-CFB128",
|
||||
CIPHER_UNSUPPORTED,
|
||||
CIPHER_UNSUPPORTED,
|
||||
CIPHER_UNSUPPORTED,
|
||||
CIPHER_UNSUPPORTED,
|
||||
CIPHER_UNSUPPORTED,
|
||||
"salsa20",
|
||||
"chacha20",
|
||||
"chacha20-ietf"
|
||||
};
|
||||
|
||||
static const int supported_stream_ciphers_nonce_size[STREAM_CIPHER_NUM] = {
|
||||
0, 0, 16, 16, 16, 16, 16, 16, 16, 8, 16, 16, 16, 8, 8, 8, 8, 16, 8, 8, 12
|
||||
};
|
||||
|
||||
static const int supported_stream_ciphers_key_size[STREAM_CIPHER_NUM] = {
|
||||
0, 16, 16, 16, 24, 32, 16, 24, 32, 16, 16, 24, 32, 16, 8, 16, 16, 16, 32, 32, 32
|
||||
};
|
||||
|
||||
static int
|
||||
crypto_stream_xor_ic(uint8_t *c, const uint8_t *m, uint64_t mlen,
|
||||
const uint8_t *n, uint64_t ic, const uint8_t *k,
|
||||
int method)
|
||||
{
|
||||
switch (method) {
|
||||
case SALSA20:
|
||||
return crypto_stream_salsa20_xor_ic(c, m, mlen, n, ic, k);
|
||||
case CHACHA20:
|
||||
return crypto_stream_chacha20_xor_ic(c, m, mlen, n, ic, k);
|
||||
case CHACHA20IETF:
|
||||
return crypto_stream_chacha20_ietf_xor_ic(c, m, mlen, n, (uint32_t)ic, k);
|
||||
}
|
||||
// always return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
const cipher_kt_t *
|
||||
stream_get_cipher_type(int method)
|
||||
{
|
||||
if (method <= TABLE || method >= STREAM_CIPHER_NUM) {
|
||||
LOGE("stream_get_cipher_type(): Illegal method");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (method == RC4_MD5) {
|
||||
method = RC4;
|
||||
}
|
||||
|
||||
if (method >= SALSA20) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *ciphername = supported_stream_ciphers[method];
|
||||
const char *mbedtlsname = supported_stream_ciphers_mbedtls[method];
|
||||
if (strcmp(mbedtlsname, CIPHER_UNSUPPORTED) == 0) {
|
||||
LOGE("Cipher %s currently is not supported by mbed TLS library",
|
||||
ciphername);
|
||||
return NULL;
|
||||
}
|
||||
return mbedtls_cipher_info_from_string(mbedtlsname);
|
||||
}
|
||||
|
||||
void
|
||||
stream_cipher_ctx_init(cipher_ctx_t *ctx, int method, int enc)
|
||||
{
|
||||
if (method <= TABLE || method >= STREAM_CIPHER_NUM) {
|
||||
LOGE("stream_ctx_init(): Illegal method");
|
||||
return;
|
||||
}
|
||||
|
||||
if (method >= SALSA20) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char *ciphername = supported_stream_ciphers[method];
|
||||
const cipher_kt_t *cipher = stream_get_cipher_type(method);
|
||||
|
||||
ctx->evp = (cipher_evp_t *)ss_malloc(sizeof(cipher_evp_t));
|
||||
memset(ctx->evp, 0, sizeof(cipher_evp_t));
|
||||
cipher_evp_t *evp = ctx->evp;
|
||||
|
||||
if (cipher == NULL) {
|
||||
LOGE("Cipher %s not found in mbed TLS library", ciphername);
|
||||
FATAL("Cannot initialize mbed TLS cipher");
|
||||
}
|
||||
mbedtls_cipher_init(evp);
|
||||
if (mbedtls_cipher_setup(evp, cipher) != 0) {
|
||||
FATAL("Cannot initialize mbed TLS cipher context");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
stream_ctx_release(cipher_ctx_t *cipher_ctx)
|
||||
{
|
||||
if (cipher_ctx->chunk != NULL) {
|
||||
bfree(cipher_ctx->chunk);
|
||||
ss_free(cipher_ctx->chunk);
|
||||
cipher_ctx->chunk = NULL;
|
||||
}
|
||||
|
||||
if (cipher_ctx->cipher->method >= SALSA20) {
|
||||
return;
|
||||
}
|
||||
|
||||
mbedtls_cipher_free(cipher_ctx->evp);
|
||||
ss_free(cipher_ctx->evp);
|
||||
}
|
||||
|
||||
void
|
||||
cipher_ctx_set_nonce(cipher_ctx_t *cipher_ctx, uint8_t *nonce, size_t nonce_len,
|
||||
int enc)
|
||||
{
|
||||
const unsigned char *true_key;
|
||||
|
||||
cipher_t *cipher = cipher_ctx->cipher;
|
||||
|
||||
if (nonce == NULL) {
|
||||
LOGE("cipher_ctx_set_nonce(): NONCE is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cipher->method >= SALSA20) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cipher->method == RC4_MD5) {
|
||||
unsigned char key_nonce[32];
|
||||
memcpy(key_nonce, cipher->key, 16);
|
||||
memcpy(key_nonce + 16, nonce, 16);
|
||||
true_key = crypto_md5(key_nonce, 32, NULL);
|
||||
nonce_len = 0;
|
||||
} else {
|
||||
true_key = cipher->key;
|
||||
}
|
||||
|
||||
cipher_evp_t *evp = cipher_ctx->evp;
|
||||
if (evp == NULL) {
|
||||
LOGE("cipher_ctx_set_nonce(): Cipher context is null");
|
||||
return;
|
||||
}
|
||||
if (mbedtls_cipher_setkey(evp, true_key, cipher->key_len * 8, (mbedtls_operation_t)enc) != 0) {
|
||||
mbedtls_cipher_free(evp);
|
||||
FATAL("Cannot set mbed TLS cipher key");
|
||||
}
|
||||
if (mbedtls_cipher_set_iv(evp, nonce, nonce_len) != 0) {
|
||||
mbedtls_cipher_free(evp);
|
||||
FATAL("Cannot set mbed TLS cipher NONCE");
|
||||
}
|
||||
if (mbedtls_cipher_reset(evp) != 0) {
|
||||
mbedtls_cipher_free(evp);
|
||||
FATAL("Cannot finalize mbed TLS cipher context");
|
||||
}
|
||||
|
||||
#ifdef SS_DEBUG
|
||||
dump("NONCE", (char *)nonce, nonce_len);
|
||||
dump("KEY", (char *)true_key, 32);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
cipher_ctx_update(cipher_ctx_t *ctx, uint8_t *output, size_t *olen,
|
||||
const uint8_t *input, size_t ilen)
|
||||
{
|
||||
cipher_evp_t *evp = ctx->evp;
|
||||
return mbedtls_cipher_update(evp, (const uint8_t *)input, ilen,
|
||||
(uint8_t *)output, olen);
|
||||
}
|
||||
|
||||
int
|
||||
stream_encrypt_all(buffer_t *plaintext, cipher_t *cipher, size_t capacity)
|
||||
{
|
||||
cipher_ctx_t cipher_ctx;
|
||||
stream_ctx_init(cipher, &cipher_ctx, 1);
|
||||
|
||||
size_t nonce_len = cipher->nonce_len;
|
||||
int err = CRYPTO_OK;
|
||||
|
||||
static buffer_t tmp = { 0, 0, 0, NULL };
|
||||
brealloc(&tmp, nonce_len + plaintext->len, capacity);
|
||||
buffer_t *ciphertext = &tmp;
|
||||
ciphertext->len = plaintext->len;
|
||||
|
||||
uint8_t *nonce = cipher_ctx.nonce;
|
||||
cipher_ctx_set_nonce(&cipher_ctx, nonce, nonce_len, 1);
|
||||
memcpy(ciphertext->data, nonce, nonce_len);
|
||||
|
||||
#ifdef MODULE_REMOTE
|
||||
ppbloom_add((void *)nonce, nonce_len);
|
||||
#endif
|
||||
|
||||
if (cipher->method >= SALSA20) {
|
||||
crypto_stream_xor_ic((uint8_t *)(ciphertext->data + nonce_len),
|
||||
(const uint8_t *)plaintext->data, (uint64_t)(plaintext->len),
|
||||
(const uint8_t *)nonce,
|
||||
0, cipher->key, cipher->method);
|
||||
} else {
|
||||
err = cipher_ctx_update(&cipher_ctx, (uint8_t *)(ciphertext->data + nonce_len),
|
||||
&ciphertext->len, (const uint8_t *)plaintext->data,
|
||||
plaintext->len);
|
||||
}
|
||||
|
||||
stream_ctx_release(&cipher_ctx);
|
||||
|
||||
if (err)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
#ifdef SS_DEBUG
|
||||
dump("PLAIN", plaintext->data, plaintext->len);
|
||||
dump("CIPHER", ciphertext->data + nonce_len, ciphertext->len);
|
||||
dump("NONCE", ciphertext->data, nonce_len);
|
||||
#endif
|
||||
|
||||
bswap_data(plaintext, ciphertext);
|
||||
plaintext->len = nonce_len + ciphertext->len;
|
||||
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
|
||||
int
|
||||
stream_encrypt(buffer_t *plaintext, cipher_ctx_t *cipher_ctx, size_t capacity)
|
||||
{
|
||||
if (cipher_ctx == NULL)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
cipher_t *cipher = cipher_ctx->cipher;
|
||||
|
||||
static buffer_t tmp = { 0, 0, 0, NULL };
|
||||
|
||||
int err = CRYPTO_OK;
|
||||
size_t nonce_len = 0;
|
||||
if (!cipher_ctx->init) {
|
||||
nonce_len = cipher_ctx->cipher->nonce_len;
|
||||
}
|
||||
|
||||
brealloc(&tmp, nonce_len + plaintext->len, capacity);
|
||||
buffer_t *ciphertext = &tmp;
|
||||
ciphertext->len = plaintext->len;
|
||||
|
||||
if (!cipher_ctx->init) {
|
||||
cipher_ctx_set_nonce(cipher_ctx, cipher_ctx->nonce, nonce_len, 1);
|
||||
memcpy(ciphertext->data, cipher_ctx->nonce, nonce_len);
|
||||
cipher_ctx->counter = 0;
|
||||
cipher_ctx->init = 1;
|
||||
|
||||
#ifdef MODULE_REMOTE
|
||||
ppbloom_add((void *)cipher_ctx->nonce, nonce_len);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (cipher->method >= SALSA20) {
|
||||
int padding = cipher_ctx->counter % SODIUM_BLOCK_SIZE;
|
||||
brealloc(ciphertext, nonce_len + (padding + ciphertext->len) * 2, capacity);
|
||||
if (padding) {
|
||||
brealloc(plaintext, plaintext->len + padding, capacity);
|
||||
memmove(plaintext->data + padding, plaintext->data, plaintext->len);
|
||||
sodium_memzero(plaintext->data, padding);
|
||||
}
|
||||
crypto_stream_xor_ic((uint8_t *)(ciphertext->data + nonce_len),
|
||||
(const uint8_t *)plaintext->data,
|
||||
(uint64_t)plaintext->len + padding,
|
||||
(const uint8_t *)cipher_ctx->nonce,
|
||||
cipher_ctx->counter / SODIUM_BLOCK_SIZE, cipher->key,
|
||||
cipher->method);
|
||||
cipher_ctx->counter += plaintext->len;
|
||||
if (padding) {
|
||||
memmove(ciphertext->data + nonce_len,
|
||||
ciphertext->data + nonce_len + padding, ciphertext->len);
|
||||
}
|
||||
} else {
|
||||
err = cipher_ctx_update(cipher_ctx,
|
||||
(uint8_t *)(ciphertext->data + nonce_len),
|
||||
&ciphertext->len, (const uint8_t *)plaintext->data,
|
||||
plaintext->len);
|
||||
if (err) {
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SS_DEBUG
|
||||
dump("PLAIN", plaintext->data, plaintext->len);
|
||||
dump("CIPHER", ciphertext->data + nonce_len, ciphertext->len);
|
||||
#endif
|
||||
|
||||
bswap_data(plaintext, ciphertext);
|
||||
plaintext->len = nonce_len + ciphertext->len;
|
||||
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
|
||||
int
|
||||
stream_decrypt_all(buffer_t *ciphertext, cipher_t *cipher, size_t capacity)
|
||||
{
|
||||
size_t nonce_len = cipher->nonce_len;
|
||||
int err = CRYPTO_OK;
|
||||
|
||||
if (ciphertext->len <= nonce_len) {
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
cipher_ctx_t cipher_ctx;
|
||||
stream_ctx_init(cipher, &cipher_ctx, 0);
|
||||
|
||||
static buffer_t tmp = { 0, 0, 0, NULL };
|
||||
brealloc(&tmp, ciphertext->len, capacity);
|
||||
buffer_t *plaintext = &tmp;
|
||||
plaintext->len = ciphertext->len - nonce_len;
|
||||
|
||||
uint8_t *nonce = cipher_ctx.nonce;
|
||||
memcpy(nonce, ciphertext->data, nonce_len);
|
||||
|
||||
if (ppbloom_check((void *)nonce, nonce_len) == 1) {
|
||||
LOGE("crypto: stream: repeat IV detected");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
|
||||
cipher_ctx_set_nonce(&cipher_ctx, nonce, nonce_len, 0);
|
||||
|
||||
if (cipher->method >= SALSA20) {
|
||||
crypto_stream_xor_ic((uint8_t *)plaintext->data,
|
||||
(const uint8_t *)(ciphertext->data + nonce_len),
|
||||
(uint64_t)(ciphertext->len - nonce_len),
|
||||
(const uint8_t *)nonce, 0, cipher->key, cipher->method);
|
||||
} else {
|
||||
err = cipher_ctx_update(&cipher_ctx, (uint8_t *)plaintext->data, &plaintext->len,
|
||||
(const uint8_t *)(ciphertext->data + nonce_len),
|
||||
ciphertext->len - nonce_len);
|
||||
}
|
||||
|
||||
stream_ctx_release(&cipher_ctx);
|
||||
|
||||
if (err)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
#ifdef SS_DEBUG
|
||||
dump("PLAIN", plaintext->data, plaintext->len);
|
||||
dump("CIPHER", ciphertext->data + nonce_len, ciphertext->len - nonce_len);
|
||||
dump("NONCE", ciphertext->data, nonce_len);
|
||||
#endif
|
||||
|
||||
ppbloom_add((void *)nonce, nonce_len);
|
||||
|
||||
bswap_data(ciphertext, plaintext);
|
||||
ciphertext->len = plaintext->len;
|
||||
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
|
||||
int
|
||||
stream_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
|
||||
{
|
||||
if (cipher_ctx == NULL)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
cipher_t *cipher = cipher_ctx->cipher;
|
||||
|
||||
static buffer_t tmp = { 0, 0, 0, NULL };
|
||||
|
||||
int err = CRYPTO_OK;
|
||||
|
||||
brealloc(&tmp, ciphertext->len, capacity);
|
||||
buffer_t *plaintext = &tmp;
|
||||
plaintext->len = ciphertext->len;
|
||||
|
||||
if (!cipher_ctx->init) {
|
||||
if (cipher_ctx->chunk == NULL) {
|
||||
cipher_ctx->chunk = (buffer_t *)ss_malloc(sizeof(buffer_t));
|
||||
memset(cipher_ctx->chunk, 0, sizeof(buffer_t));
|
||||
balloc(cipher_ctx->chunk, cipher->nonce_len);
|
||||
}
|
||||
|
||||
size_t left_len = cipher->nonce_len - cipher_ctx->chunk->len;
|
||||
if (left_len > ciphertext->len)
|
||||
left_len = ciphertext->len;
|
||||
|
||||
if (left_len > 0) {
|
||||
memcpy(cipher_ctx->chunk->data + cipher_ctx->chunk->len, ciphertext->data, left_len);
|
||||
memmove(ciphertext->data, ciphertext->data + left_len,
|
||||
ciphertext->len - left_len);
|
||||
cipher_ctx->chunk->len += left_len;
|
||||
ciphertext->len -= left_len;
|
||||
}
|
||||
|
||||
if (cipher_ctx->chunk->len < cipher->nonce_len)
|
||||
return CRYPTO_NEED_MORE;
|
||||
|
||||
uint8_t *nonce = cipher_ctx->nonce;
|
||||
size_t nonce_len = cipher->nonce_len;
|
||||
plaintext->len -= left_len;
|
||||
|
||||
memcpy(nonce, cipher_ctx->chunk->data, nonce_len);
|
||||
cipher_ctx_set_nonce(cipher_ctx, nonce, nonce_len, 0);
|
||||
cipher_ctx->counter = 0;
|
||||
cipher_ctx->init = 1;
|
||||
|
||||
/*
|
||||
* Register the IV as soon as it is known to be fresh. Deferring the
|
||||
* add until after the payload is decrypted leaves a window in which a
|
||||
* peer that sends exactly nonce_len bytes returns CRYPTO_NEED_MORE
|
||||
* below with the IV checked but not yet recorded, so a concurrent
|
||||
* connection replaying the same IV would pass ppbloom_check().
|
||||
*/
|
||||
if (cipher->method >= RC4_MD5) {
|
||||
if (ppbloom_check((void *)nonce, nonce_len) == 1) {
|
||||
LOGE("crypto: stream: repeat IV detected");
|
||||
return CRYPTO_ERROR;
|
||||
}
|
||||
ppbloom_add((void *)nonce, nonce_len);
|
||||
cipher_ctx->init = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (ciphertext->len <= 0)
|
||||
return CRYPTO_NEED_MORE;
|
||||
|
||||
if (cipher->method >= SALSA20) {
|
||||
int padding = cipher_ctx->counter % SODIUM_BLOCK_SIZE;
|
||||
brealloc(plaintext, (plaintext->len + padding) * 2, capacity);
|
||||
|
||||
if (padding) {
|
||||
brealloc(ciphertext, ciphertext->len + padding, capacity);
|
||||
memmove(ciphertext->data + padding, ciphertext->data,
|
||||
ciphertext->len);
|
||||
sodium_memzero(ciphertext->data, padding);
|
||||
}
|
||||
crypto_stream_xor_ic((uint8_t *)plaintext->data,
|
||||
(const uint8_t *)(ciphertext->data),
|
||||
(uint64_t)ciphertext->len + padding,
|
||||
(const uint8_t *)cipher_ctx->nonce,
|
||||
cipher_ctx->counter / SODIUM_BLOCK_SIZE, cipher->key,
|
||||
cipher->method);
|
||||
cipher_ctx->counter += ciphertext->len;
|
||||
if (padding) {
|
||||
memmove(plaintext->data, plaintext->data + padding, plaintext->len);
|
||||
}
|
||||
} else {
|
||||
err = cipher_ctx_update(cipher_ctx, (uint8_t *)plaintext->data, &plaintext->len,
|
||||
(const uint8_t *)(ciphertext->data),
|
||||
ciphertext->len);
|
||||
}
|
||||
|
||||
if (err)
|
||||
return CRYPTO_ERROR;
|
||||
|
||||
#ifdef SS_DEBUG
|
||||
dump("PLAIN", plaintext->data, plaintext->len);
|
||||
dump("CIPHER", ciphertext->data, ciphertext->len);
|
||||
#endif
|
||||
|
||||
bswap_data(ciphertext, plaintext);
|
||||
ciphertext->len = plaintext->len;
|
||||
|
||||
return CRYPTO_OK;
|
||||
}
|
||||
|
||||
void
|
||||
stream_ctx_init(cipher_t *cipher, cipher_ctx_t *cipher_ctx, int enc)
|
||||
{
|
||||
sodium_memzero(cipher_ctx, sizeof(cipher_ctx_t));
|
||||
stream_cipher_ctx_init(cipher_ctx, cipher->method, enc);
|
||||
cipher_ctx->cipher = cipher;
|
||||
|
||||
if (enc) {
|
||||
rand_bytes(cipher_ctx->nonce, cipher->nonce_len);
|
||||
}
|
||||
}
|
||||
|
||||
cipher_t *
|
||||
stream_key_init(int method, const char *pass, const char *key)
|
||||
{
|
||||
if (method <= TABLE || method >= STREAM_CIPHER_NUM) {
|
||||
LOGE("cipher->key_init(): Illegal method");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cipher_t *cipher = (cipher_t *)ss_malloc(sizeof(cipher_t));
|
||||
memset(cipher, 0, sizeof(cipher_t));
|
||||
|
||||
if (method < SALSA20 && stream_get_cipher_type(method) == NULL) {
|
||||
LOGE("Cipher %s not found in crypto library", supported_stream_ciphers[method]);
|
||||
FATAL("Cannot initialize cipher");
|
||||
}
|
||||
|
||||
if (key != NULL)
|
||||
cipher->key_len = crypto_parse_key(key, cipher->key,
|
||||
supported_stream_ciphers_key_size[method]);
|
||||
else
|
||||
cipher->key_len = crypto_derive_key(pass, cipher->key,
|
||||
supported_stream_ciphers_key_size[method]);
|
||||
|
||||
if (cipher->key_len == 0) {
|
||||
FATAL("Cannot generate key and NONCE");
|
||||
}
|
||||
cipher->nonce_len = supported_stream_ciphers_nonce_size[method];
|
||||
cipher->method = method;
|
||||
|
||||
return cipher;
|
||||
}
|
||||
|
||||
cipher_t *
|
||||
stream_init(const char *pass, const char *key, const char *method)
|
||||
{
|
||||
int m = TABLE;
|
||||
if (method != NULL) {
|
||||
for (m = TABLE; m < STREAM_CIPHER_NUM; m++)
|
||||
if (strcmp(method, supported_stream_ciphers[m]) == 0) {
|
||||
break;
|
||||
}
|
||||
if (m >= STREAM_CIPHER_NUM) {
|
||||
LOGE("Invalid cipher name: %s, use chacha20-ietf instead", method);
|
||||
m = CHACHA20IETF;
|
||||
}
|
||||
}
|
||||
if (m == TABLE) {
|
||||
LOGE("Table is deprecated");
|
||||
return NULL;
|
||||
}
|
||||
return stream_key_init(m, pass, key);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* encrypt.h - Define the enryptor's interface
|
||||
*
|
||||
* Copyright (C) 2013 - 2019, Max Lv <max.c.lv@gmail.com>
|
||||
*
|
||||
* This file is part of the shadowsocks-libev.
|
||||
*
|
||||
* shadowsocks-libev is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* shadowsocks-libev is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with shadowsocks-libev; see the file COPYING. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _STREAM_H
|
||||
#define _STREAM_H
|
||||
|
||||
#ifndef __MINGW32__
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#ifdef HAVE_STDINT_H
|
||||
#include <stdint.h>
|
||||
#elif HAVE_INTTYPES_H
|
||||
#include <inttypes.h>
|
||||
#endif
|
||||
|
||||
#include <sodium.h>
|
||||
#define STREAM_CIPHER_NUM 21
|
||||
|
||||
#include "crypto.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int stream_encrypt_all(buffer_t *, cipher_t *, size_t);
|
||||
int stream_decrypt_all(buffer_t *, cipher_t *, size_t);
|
||||
int stream_encrypt(buffer_t *, cipher_ctx_t *, size_t);
|
||||
int stream_decrypt(buffer_t *, cipher_ctx_t *, size_t);
|
||||
|
||||
void stream_ctx_init(cipher_t *, cipher_ctx_t *, int);
|
||||
void stream_ctx_release(cipher_ctx_t *);
|
||||
|
||||
cipher_t *stream_init(const char *pass, const char *key, const char *method);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _STREAM_H
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"server":"127.0.0.1",
|
||||
"server_port":8389,
|
||||
"local_port":1081,
|
||||
"password":"salsa20_password",
|
||||
"timeout":60,
|
||||
"method":"salsa20",
|
||||
"local":"127.0.0.1",
|
||||
"fast_open":false
|
||||
}
|
||||
+1
-15
@@ -22,21 +22,13 @@ import threading
|
||||
import time
|
||||
from subprocess import Popen
|
||||
|
||||
# Ciphers to test: AEAD (recommended) + common stream ciphers
|
||||
# Ciphers to test (the stream ciphers were removed).
|
||||
AEAD_CIPHERS = [
|
||||
"aes-128-gcm",
|
||||
"aes-256-gcm",
|
||||
"chacha20-ietf-poly1305",
|
||||
]
|
||||
|
||||
STREAM_CIPHERS = [
|
||||
"aes-128-cfb",
|
||||
"aes-256-cfb",
|
||||
"aes-256-ctr",
|
||||
"chacha20-ietf",
|
||||
]
|
||||
|
||||
ALL_CIPHERS = AEAD_CIPHERS + STREAM_CIPHERS
|
||||
|
||||
# Ports (picked high to avoid conflicts)
|
||||
SERVER_PORT = 18388
|
||||
@@ -312,10 +304,6 @@ def main():
|
||||
"--repeat", type=int, default=1,
|
||||
help="Number of times to repeat each cipher test (default: 1)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stream", action="store_true",
|
||||
help="Include stream ciphers (deprecated, insecure)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--leak-threshold", type=int, default=10240,
|
||||
help="RSS growth threshold in KB to flag as potential leak (default: 10240)"
|
||||
@@ -329,8 +317,6 @@ def main():
|
||||
# Determine ciphers to test
|
||||
if args.cipher:
|
||||
ciphers = [args.cipher]
|
||||
elif args.stream:
|
||||
ciphers = ALL_CIPHERS
|
||||
else:
|
||||
ciphers = AEAD_CIPHERS
|
||||
|
||||
|
||||
@@ -32,13 +32,7 @@ if [ "$http_proxy" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
run_test python tests/test.py $BIN -c tests/aes.json
|
||||
run_test python tests/test.py $BIN -c tests/aes-gcm.json
|
||||
run_test python tests/test.py $BIN -c tests/aes-ctr.json
|
||||
run_test python tests/test.py $BIN -c tests/rc4-md5.json
|
||||
run_test python tests/test.py $BIN -c tests/salsa20.json
|
||||
run_test python tests/test.py $BIN -c tests/chacha20.json
|
||||
run_test python tests/test.py $BIN -c tests/chacha20-ietf.json
|
||||
run_test python tests/test.py $BIN -c tests/chacha20-ietf-poly1305.json
|
||||
run_test python tests/test.py $BIN -c tests/2022-blake3-aes-128-gcm.json
|
||||
run_test python tests/test.py $BIN -c tests/2022-blake3-aes-256-gcm.json
|
||||
|
||||
+9
-1
@@ -58,7 +58,7 @@ class CliTests(unittest.TestCase):
|
||||
self.assertEqual('--printable-salt' in help_result.stdout, name == 'local')
|
||||
if name != 'local':
|
||||
self.assertEqual(self.run_cli(name, '--printable-salt').returncode, 2)
|
||||
for method in ('aes-128-gcm', 'aes-256-cfb', '2022-blake3-aes-256-gcm',
|
||||
for method in ('aes-128-gcm', 'aes-192-gcm', '2022-blake3-aes-256-gcm',
|
||||
'2022-blake3-chacha20-poly1305'):
|
||||
with self.subTest(method=method):
|
||||
result = self.run_cli('local', '-s', '127.0.0.1', '-p', '8388',
|
||||
@@ -88,6 +88,14 @@ class CliTests(unittest.TestCase):
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("option 'printable_salt' must be a boolean", result.stderr)
|
||||
|
||||
def test_removed_stream_ciphers_are_rejected(self):
|
||||
for method in ('aes-256-cfb', 'chacha20-ietf', 'rc4-md5'):
|
||||
with self.subTest(method=method):
|
||||
result = self.run_cli('server', '-s', '127.0.0.1', '-p', '1', '-k', 'test-password',
|
||||
'-m', method)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('no longer supported', result.stderr)
|
||||
|
||||
def test_long_aliases_accept_their_arguments(self):
|
||||
for name in self.binaries:
|
||||
args = ['--config', 'not-loaded-before-help.json', '--cipher', 'aes-128-gcm',
|
||||
|
||||
@@ -292,58 +292,6 @@ test_printable_salt(const char *method, int enabled)
|
||||
ss_free(crypto);
|
||||
}
|
||||
|
||||
/*
|
||||
* Round-trip a multi-segment stream through a stream cipher.
|
||||
*
|
||||
* The segments deliberately have lengths that are not a multiple of the
|
||||
* cipher block size, and there is deliberately more than one of them: only
|
||||
* the first segment carries the nonce, so a codec that mishandles the
|
||||
* post-nonce steady state still round-trips a single segment correctly.
|
||||
*/
|
||||
#if SS_ENABLE_LEGACY
|
||||
static void
|
||||
test_stream_multi_segment_roundtrip(const char *method)
|
||||
{
|
||||
static const char *segments[] = {
|
||||
"first-segment",
|
||||
"second",
|
||||
"a third segment that is a good deal longer than one block",
|
||||
};
|
||||
|
||||
crypto_t *crypto = crypto_init("password", NULL, method);
|
||||
assert(crypto != NULL);
|
||||
|
||||
cipher_ctx_t enc_ctx, dec_ctx;
|
||||
crypto->ctx_init(crypto->cipher, &enc_ctx, 1);
|
||||
crypto->ctx_init(crypto->cipher, &dec_ctx, 0);
|
||||
|
||||
for (size_t i = 0; i < sizeof(segments) / sizeof(segments[0]); i++) {
|
||||
size_t len = strlen(segments[i]);
|
||||
|
||||
buffer_t buf;
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
balloc(&buf, 2048);
|
||||
memcpy(buf.data, segments[i], len);
|
||||
buf.len = len;
|
||||
|
||||
assert(crypto->encrypt(&buf, &enc_ctx, 2048) == CRYPTO_OK);
|
||||
assert(crypto->decrypt(&buf, &dec_ctx, 2048) == CRYPTO_OK);
|
||||
|
||||
assert(buf.len == len);
|
||||
assert(memcmp(buf.data, segments[i], len) == 0);
|
||||
|
||||
bfree(&buf);
|
||||
}
|
||||
|
||||
crypto->ctx_release(&enc_ctx);
|
||||
crypto->ctx_release(&dec_ctx);
|
||||
ppbloom_free();
|
||||
ss_free(crypto->cipher);
|
||||
ss_free(crypto);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
@@ -364,16 +312,6 @@ main(void)
|
||||
test_crypto_parse_key();
|
||||
test_aead_repeat_salt_rejection_releases_context();
|
||||
|
||||
#if SS_ENABLE_LEGACY
|
||||
/* mbedTLS-backed ciphers and libsodium-backed ciphers use different
|
||||
* code paths in stream.cpp, so cover both. */
|
||||
test_stream_multi_segment_roundtrip("aes-256-cfb");
|
||||
test_stream_multi_segment_roundtrip("aes-256-ctr");
|
||||
test_stream_multi_segment_roundtrip("camellia-128-cfb");
|
||||
test_stream_multi_segment_roundtrip("chacha20-ietf");
|
||||
test_stream_multi_segment_roundtrip("salsa20");
|
||||
#else
|
||||
assert(crypto_init("password", NULL, "aes-256-cfb") == NULL);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
// The C++ stream codecs (src/crypto/stream.hpp) differentially against the C
|
||||
// implementation in stream.cpp, plus replay handling and a zero-allocation
|
||||
// steady phase.
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include <sodium.h>
|
||||
|
||||
#include "alloc_audit/alloc_probe.hpp"
|
||||
#include "crypto/stream.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include "crypto.h"
|
||||
#include "ppbloom.h"
|
||||
#include "utils.h"
|
||||
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
extern "C" {
|
||||
int verbose = 0;
|
||||
struct cache *nonce_cache = nullptr;
|
||||
}
|
||||
|
||||
namespace sc = ss::crypto;
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<std::uint8_t> random_bytes(std::size_t n)
|
||||
{
|
||||
std::vector<std::uint8_t> v(n);
|
||||
randombytes_buf(v.data(), n);
|
||||
return v;
|
||||
}
|
||||
|
||||
struct CMethod {
|
||||
crypto_t *crypto = nullptr;
|
||||
explicit CMethod(const char *method)
|
||||
{
|
||||
crypto = crypto_init("password", nullptr, method);
|
||||
SS_CHECK(crypto != nullptr);
|
||||
}
|
||||
~CMethod()
|
||||
{
|
||||
ppbloom_free();
|
||||
ss_free(crypto->cipher);
|
||||
ss_free(crypto);
|
||||
}
|
||||
};
|
||||
|
||||
// Odd sizes so that writes start and end inside 64-byte keystream blocks.
|
||||
std::size_t piece(std::size_t left)
|
||||
{
|
||||
return std::min<std::size_t>(1 + randombytes_uniform(3000), left);
|
||||
}
|
||||
|
||||
void tcp_c_to_cpp(const char *method, const sc::StreamKey &key)
|
||||
{
|
||||
CMethod c(method);
|
||||
cipher_ctx_t enc;
|
||||
c.crypto->ctx_init(c.crypto->cipher, &enc, 1);
|
||||
auto plaintext = random_bytes(60000);
|
||||
std::vector<std::uint8_t> wire;
|
||||
buffer_t buf{};
|
||||
const std::size_t cap = 8192;
|
||||
SS_CHECK(balloc(&buf, cap) > 0);
|
||||
for (std::size_t off = 0; off < plaintext.size();) {
|
||||
std::size_t n = piece(plaintext.size() - off);
|
||||
std::memcpy(buf.data, plaintext.data() + off, n);
|
||||
buf.len = n;
|
||||
SS_CHECK(c.crypto->encrypt(&buf, &enc, cap) == CRYPTO_OK);
|
||||
wire.insert(wire.end(), buf.data, buf.data + buf.len);
|
||||
off += n;
|
||||
}
|
||||
bfree(&buf);
|
||||
c.crypto->ctx_release(&enc);
|
||||
|
||||
sc::StreamTcpDecryptor dec;
|
||||
SS_CHECK(dec.init(key, {}));
|
||||
std::vector<std::uint8_t> got;
|
||||
std::vector<std::uint8_t> out(1000); // smaller than some inputs on purpose
|
||||
for (std::size_t off = 0; off < wire.size();) {
|
||||
std::size_t n = std::min<std::size_t>(1 + randombytes_uniform(5000), wire.size() - off);
|
||||
std::span<const std::uint8_t> in(wire.data() + off, n);
|
||||
while (!in.empty()) {
|
||||
auto step = dec.decrypt(in, out);
|
||||
SS_CHECK(step);
|
||||
got.insert(got.end(), out.begin(), out.begin() + step->produced);
|
||||
in = in.subspan(step->consumed);
|
||||
}
|
||||
off += n;
|
||||
}
|
||||
SS_CHECK(got == plaintext);
|
||||
}
|
||||
|
||||
void tcp_cpp_to_c(const char *method, const sc::StreamKey &key)
|
||||
{
|
||||
CMethod c(method);
|
||||
cipher_ctx_t dec;
|
||||
c.crypto->ctx_init(c.crypto->cipher, &dec, 0);
|
||||
sc::StreamTcpEncryptor enc;
|
||||
SS_CHECK(enc.init(key, {}));
|
||||
auto plaintext = random_bytes(60000);
|
||||
std::vector<std::uint8_t> got;
|
||||
buffer_t buf{};
|
||||
const std::size_t cap = 8192;
|
||||
SS_CHECK(balloc(&buf, cap) > 0);
|
||||
for (std::size_t off = 0; off < plaintext.size();) {
|
||||
std::size_t n = piece(plaintext.size() - off);
|
||||
std::vector<std::uint8_t> wire(enc.sealed_size(n));
|
||||
auto w = enc.seal({ plaintext.data() + off, n }, wire);
|
||||
SS_CHECK(w && *w == wire.size());
|
||||
std::memcpy(buf.data, wire.data(), wire.size());
|
||||
buf.len = wire.size();
|
||||
int rc = c.crypto->decrypt(&buf, &dec, cap);
|
||||
SS_CHECK(rc == CRYPTO_OK || (rc == CRYPTO_NEED_MORE && buf.len == 0));
|
||||
got.insert(got.end(), buf.data, buf.data + buf.len);
|
||||
off += n;
|
||||
}
|
||||
bfree(&buf);
|
||||
c.crypto->ctx_release(&dec);
|
||||
SS_CHECK(got == plaintext);
|
||||
}
|
||||
|
||||
void udp_both_ways(const char *method, const sc::StreamKey &key)
|
||||
{
|
||||
CMethod c(method);
|
||||
const std::size_t cap = 65536;
|
||||
for (std::size_t size : { 1u, 63u, 64u, 65u, 1400u }) {
|
||||
auto payload = random_bytes(size);
|
||||
buffer_t buf{};
|
||||
SS_CHECK(balloc(&buf, cap) > 0);
|
||||
std::memcpy(buf.data, payload.data(), size);
|
||||
buf.len = size;
|
||||
SS_CHECK(c.crypto->encrypt_all(&buf, c.crypto->cipher, cap) == CRYPTO_OK);
|
||||
std::vector<std::uint8_t> out(size);
|
||||
auto n = sc::stream_udp_open(key, { reinterpret_cast<std::uint8_t *>(buf.data), buf.len }, out, {});
|
||||
SS_CHECK(n && *n == size && std::memcmp(out.data(), payload.data(), size) == 0);
|
||||
|
||||
std::vector<std::uint8_t> packet(key.spec.iv_len + size);
|
||||
auto w = sc::stream_udp_seal(key, payload, packet, {});
|
||||
SS_CHECK(w && *w == packet.size());
|
||||
std::memcpy(buf.data, packet.data(), packet.size());
|
||||
buf.len = packet.size();
|
||||
SS_CHECK(c.crypto->decrypt_all(&buf, c.crypto->cipher, cap) == CRYPTO_OK);
|
||||
SS_CHECK(buf.len == size && std::memcmp(buf.data, payload.data(), size) == 0);
|
||||
bfree(&buf);
|
||||
}
|
||||
}
|
||||
|
||||
struct IvSet {
|
||||
std::vector<std::vector<std::uint8_t> > ivs;
|
||||
static bool seen(void *ctx, std::span<const std::uint8_t> iv) noexcept
|
||||
{
|
||||
for (auto &s : static_cast<IvSet *>(ctx)->ivs) {
|
||||
if (s.size() == iv.size() && std::memcmp(s.data(), iv.data(), iv.size()) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static void remember(void *ctx, std::span<const std::uint8_t> iv) noexcept
|
||||
{
|
||||
static_cast<IvSet *>(ctx)->ivs.emplace_back(iv.begin(), iv.end());
|
||||
}
|
||||
sc::ReplayFilter filter() { return { &seen, &remember, this }; }
|
||||
};
|
||||
|
||||
void replay(const sc::StreamKey &key)
|
||||
{
|
||||
sc::StreamTcpEncryptor enc;
|
||||
SS_CHECK(enc.init(key, {}));
|
||||
std::uint8_t msg[10] = {};
|
||||
std::vector<std::uint8_t> wire(enc.sealed_size(sizeof(msg)));
|
||||
SS_CHECK(enc.seal(msg, wire));
|
||||
IvSet seen;
|
||||
std::uint8_t out[16];
|
||||
sc::StreamTcpDecryptor first, second;
|
||||
SS_CHECK(first.init(key, seen.filter()) && second.init(key, seen.filter()));
|
||||
// An empty read before the IV arrives is a no-op.
|
||||
auto none = first.decrypt(std::span<const std::uint8_t>(), out);
|
||||
SS_CHECK(none && none->consumed == 0 && none->produced == 0 && !first.iv_received());
|
||||
// The IV alone is enough to register it.
|
||||
auto step = first.decrypt(std::span<const std::uint8_t>(wire.data(), key.spec.iv_len), out);
|
||||
SS_CHECK(step && step->produced == 0 && first.iv_received());
|
||||
auto again = second.decrypt(wire, out);
|
||||
SS_CHECK(!again && again.error().code == ss::Errc::crypto);
|
||||
}
|
||||
|
||||
void steady(const sc::StreamKey &key)
|
||||
{
|
||||
static std::uint8_t plain[4096], wire[4096 + 16], out[4096];
|
||||
for (int conn = 0; conn < 100; ++conn) {
|
||||
sc::StreamTcpEncryptor enc;
|
||||
sc::StreamTcpDecryptor dec;
|
||||
SS_CHECK(enc.init(key, {}) && dec.init(key, {}));
|
||||
for (std::size_t n : { (std::size_t)1, (std::size_t)100, (std::size_t)4000 }) {
|
||||
auto w = enc.seal({ plain, n }, wire);
|
||||
SS_CHECK(w);
|
||||
auto step = dec.decrypt({ wire, *w }, out);
|
||||
SS_CHECK(step && step->produced == n);
|
||||
}
|
||||
auto p = sc::stream_udp_seal(key, { plain, 1200 }, wire, {});
|
||||
SS_CHECK(p && sc::stream_udp_open(key, { wire, *p }, out, {}));
|
||||
}
|
||||
}
|
||||
|
||||
const char *const kMethods[] = { "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr",
|
||||
"aes-256-ctr", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
|
||||
"salsa20", "chacha20", "chacha20-ietf" };
|
||||
|
||||
sc::StreamKey key_for(const char *name)
|
||||
{
|
||||
auto spec = sc::find_stream(name);
|
||||
SS_CHECK(spec.has_value());
|
||||
auto key = sc::StreamKey::from_password(spec.value(), "password");
|
||||
SS_CHECK(key);
|
||||
return *key;
|
||||
}
|
||||
|
||||
void test_methods()
|
||||
{
|
||||
for (const char *m : kMethods) {
|
||||
sc::StreamKey key = key_for(m);
|
||||
tcp_c_to_cpp(m, key);
|
||||
tcp_cpp_to_c(m, key);
|
||||
udp_both_ways(m, key);
|
||||
replay(key);
|
||||
std::printf("ok %s\n", m);
|
||||
}
|
||||
SS_CHECK(!sc::find_stream("rc4-md5") && !sc::find_stream("aes-256-gcm"));
|
||||
}
|
||||
|
||||
void test_no_heap_in_steady_state()
|
||||
{
|
||||
sc::StreamKey keys[sizeof(kMethods) / sizeof(kMethods[0])];
|
||||
std::size_t i = 0;
|
||||
for (const char *m : kMethods) {
|
||||
keys[i] = key_for(m);
|
||||
steady(keys[i++]);
|
||||
}
|
||||
ss::test::alloc_mark();
|
||||
for (const sc::StreamKey &key : keys) {
|
||||
steady(key);
|
||||
}
|
||||
ss::test::alloc_mark();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
SS_CHECK(sodium_init() != -1);
|
||||
SS_RUN(test_methods);
|
||||
SS_RUN(test_no_heap_in_steady_state);
|
||||
return 0;
|
||||
}
|
||||
@@ -15,7 +15,6 @@ from interop import TCPHandler, TCPOrigin, UDPHandler, UDPOrigin, free_port, pee
|
||||
|
||||
METHODS = ["2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305",
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305"]
|
||||
LEGACY = ["aes-256-cfb", "aes-128-ctr", "camellia-256-cfb", "chacha20-ietf", "salsa20"]
|
||||
|
||||
|
||||
def tcp_case(port, size):
|
||||
@@ -47,23 +46,20 @@ def udp_case(port):
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--bin", required=True, type=Path)
|
||||
parser.add_argument("--legacy", action="store_true", help="also run the stream ciphers")
|
||||
parser.add_argument("--fast-open", action="store_true")
|
||||
args = parser.parse_args()
|
||||
bins = args.bin.resolve()
|
||||
server, tunnel = str(bins / "ss-server"), str(bins / "ss-tunnel")
|
||||
rust_server = shutil.which("ssserver")
|
||||
methods = METHODS + (LEGACY if args.legacy else [])
|
||||
failures = 0
|
||||
with TCPOrigin(("127.0.0.1", 0), TCPHandler) as tcp, UDPOrigin(("127.0.0.1", 0), UDPHandler) as udp:
|
||||
for origin in (tcp, udp):
|
||||
threading.Thread(target=origin.serve_forever, daemon=True).start()
|
||||
try:
|
||||
for method in methods:
|
||||
stream = method in LEGACY
|
||||
for peer in ("C server",) + (("Rust server",) if rust_server and not stream else ()):
|
||||
for method in METHODS:
|
||||
for peer in ("C server",) + (("Rust server",) if rust_server else ()):
|
||||
key = base64.b64encode(os.urandom(16 if "aes-128" in method else 32)).decode()
|
||||
secret = ["-k", "password"] if stream else ["-k", key]
|
||||
secret = ["-k", key]
|
||||
sport, tport, uport = free_port(), free_port(), free_port()
|
||||
if peer == "C server":
|
||||
srv = [server, "-s", "127.0.0.1", "-p", str(sport), "-m", method, "-u"] + secret
|
||||
|
||||
Reference in new issue
Block a user