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:
Max Lv authored and GitHub committed 2026-10-15 10:15:53 +08:00
1 parent 002b4093fd
commit f1f31c3c05
47 files changed
+71 -1786

No files matched your search

+2 -2
View File
@@ -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
View File
@@ -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)
+5 -11
View File
@@ -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
View File
@@ -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.
-26
View File
@@ -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)
-1
View File
@@ -307,6 +307,5 @@
#cmakedefine01 SS_ENABLE_REGEX
#cmakedefine01 SS_ENABLE_PLUGINS
#cmakedefine01 SS_ENABLE_LEGACY
#endif
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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]}"
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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")
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
-3
View File
@@ -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
-287
View File
@@ -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
-159
View File
@@ -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
View File
@@ -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
-18
View File
@@ -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
-20
View File
@@ -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
View File
@@ -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
View File
@@ -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
-22
View File
@@ -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})
-10
View File
@@ -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
}
-10
View File
@@ -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
}
-10
View File
@@ -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
}
-10
View File
@@ -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
}
-10
View File
@@ -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
}
-27
View File
@@ -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;
-5
View File
@@ -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
-654
View File
@@ -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);
}
-62
View File
@@ -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
-10
View File
@@ -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
View File
@@ -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
-6
View File
@@ -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
View File
@@ -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',
-62
View File
@@ -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;
}
-260
View File
@@ -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;
}
+3 -7
View File
@@ -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