Skip to content

Support Mbed TLS 4.x - #3064

Open
yegorich wants to merge 2 commits into
shadowsocks:masterfrom
yegorich:feature/mbedtls-4
Open

yegorich wants to merge 2 commits into
shadowsocks:masterfrom
yegorich:feature/mbedtls-4

Conversation

@yegorich

@yegorich yegorich commented Oct 9, 2026

Copy link
Copy Markdown

Mbed TLS 4.0 (October 2025) moved the cryptography into TF-PSA-Crypto and made PSA Crypto its only
crypto API. Many modules that shadowsocks-c uses are private there:

  • the cipher module (mbedtls/cipher.h);
  • AES, MD5, entropy and CTR-DRBG;
  • HMAC through md.

So a system mode build against 4.x fails. FindMbedTLS.cmake also rejects 4.x.

4.1 is the new LTS branch (supported until March 2029), and support for 3.6 LTS ends in March 2027.
Distributions are starting to switch: Homebrew's mbedtls is 4.2.0 already, and we are preparing
the switch in Buildroot, which builds shadowsocks against its own Mbed TLS package.

This PR adds 4.x support for system mode. With 2.x and 3.x the code calls the same mbed TLS functions
as before, and the bundled 3.6.7 build is unchanged.

Changes

Cipher wrappers. crypto.c gets small crypto_cipher_*() wrappers. aead.c, stream.c and
aead2022_udp.c now call these instead of mbedtls_cipher_*(), so the version checks live in
one place. The existing 2.x/3.x #if for the AEAD functions moved there as well.

  • 2.x/3.x: each wrapper calls the matching mbedtls_cipher_*() function.
  • 4.x: the wrappers use PSA, and cipher_kt_t/cipher_evp_t become small structs with the PSA key
    type and algorithm, the key ID and a cipher operation:
Use Mbed TLS 4.x
AES-GCM (incl. AEAD-2022) psa_aead_encrypt()/psa_aead_decrypt()
AES-CFB, AES-CTR, Camellia-CFB stream ciphers multi-part psa_cipher_*()
AEAD-2022 UDP separate header AES-ECB with psa_cipher_encrypt()/psa_cipher_decrypt()
HKDF HMAC with psa_mac_*()
MD5 (crypto_md5()) psa_hash_compute()

Other code changes:

  • crypto_init() calls psa_crypto_init(). 4.x needs it before any hashing, including through md,
    where mbedtls_md_starts() otherwise fails.
  • Digests are looked up with mbedtls_md_info_from_type(). mbedtls_md_info_from_string() is private
    in 4.x.
  • test_crypto.c calls psa_crypto_init() itself, because its first tests run before crypto_init().

CMake. FindMbedTLS.cmake:

  • locates the prefix by mbedtls/version.h instead of mbedtls/cipher.h;
  • prefers libtfpsacrypto over libmbedcrypto;
  • accepts 4.x;
  • with 4.x, checks PSA_WANT_ALG_CFB instead of MBEDTLS_CIPHER_MODE_CFB;
  • with 4.x, compares the TF-PSA-Crypto version of the headers and the library, because
    mbedtls_version_get_string() is in libmbedtls, which isn't linked.

The pkg-config file lists -ltfpsacrypto with 4.x.

CI. The second commit adds a macOS job that builds against Homebrew's mbedtls (4.2.0) in system
mode and runs ctest. Feel free to drop it if you'd rather not add a job.

Testing

On Linux x86_64 with gcc 11, in system mode, against three Mbed TLS versions: 2.28.0 (Ubuntu
22.04), 3.6.7 and 4.1.1. On all three, the project's own sources build without warnings (with
SS_WARNINGS_AS_ERRORS) and ctest passes: 41 tests, with test_interop_rust skipped because
shadowsocks-rust isn't installed. With 4.1.1 the binaries link libtfpsacrypto.

The default bundled build also passes ctest.

Mixed-version runs. I ran ss-server from one build and ss-local/ss-tunnel from another.
Each run downloaded a 2 MB file over TCP through ss-local (checked with SHA-256) and sent UDP
datagrams of 1 to 1400 bytes through ss-tunnel to an echo server. I used these ciphers:

  • aes-128-gcm, aes-192-gcm, aes-256-gcm;
  • chacha20-ietf-poly1305;
  • 2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm;
  • aes-128-cfb, aes-256-cfb, aes-192-ctr, camellia-256-cfb.
Server Client Result
4.1.1 4.1.1 all ciphers, TCP and UDP
4.1.1 3.6.7 all ciphers, TCP and UDP
3.6.7 4.1.1 all ciphers, TCP and UDP
4.1.1 2.28.0 all ciphers, TCP and UDP
2.28.0 4.1.1 all ciphers, TCP and UDP
  • Negative check: with a wrong key on the client, every cipher fails, as expected.
  • AES-256-GCM through Mbed TLS: with AES-NI, aes-256-gcm uses libsodium. To cover the Mbed TLS
    path, I ran the 4.1.1 ss-server under qemu-x86_64 -cpu qemu64, where libsodium reports
    AES-256-GCM as unavailable. It worked with a native 3.6.7 client (libsodium AES-256-GCM).
  • Sanitizers: a 4.1.1 build with ENABLE_SANITIZERS=ON passes all non-memcheck tests and the
    mixed-version runs without sanitizer errors.
    • The memcheck tests fail under ASan: valgrind can't run ASan binaries. This also happens on
      master.
    • LeakSanitizer reports a 10-byte leak from parse_addr() in ss-server's main(). This also
      happens on master, so it's unrelated.
  • Stress test: tests/stress_test.py passes with 4.1.1. aes-128-gcm throughput through Mbed TLS
    is the same with 3.6.7 and 4.1.1 (about 2.0 to 2.6 Gbps here, 300 MB).
  • clang-tidy: clang-tidy 14 reports no warnings for the changed files with 2.28, 3.6.7 and 4.1.1.

Not tested:

  • 4.2.0 and macOS, which the CI job covers;
  • the bundled build with a 4.x source tree (the bundled dependency stays 3.6.7);
  • Windows/MinGW;
  • the interop test against shadowsocks-rust.

Mbed TLS 4.0 moved the cryptography into TF-PSA-Crypto and made PSA
Crypto its only crypto API. The cipher, AES, MD5, entropy and CTR-DRBG
modules and HMAC through md are private, so the build against a system
Mbed TLS 4.x fails.

Add cipher wrappers in crypto.c that use the mbed TLS cipher module with
2.x and 3.x, as before, and PSA with 4.x:

- AES-GCM uses psa_aead_encrypt()/psa_aead_decrypt().
- The AES-CFB, AES-CTR and Camellia-CFB stream ciphers use multi-part
  PSA cipher operations.
- The AEAD-2022 UDP separate header uses AES-ECB through PSA.
- HKDF computes the HMAC with psa_mac_*(), and MD5 uses
  psa_hash_compute().
- crypto_init() calls psa_crypto_init(), which 4.x needs before any
  hashing, including through md.

Digests are looked up with mbedtls_md_info_from_type(), as
mbedtls_md_info_from_string() is private in 4.x too.

FindMbedTLS.cmake finds 4.x by mbedtls/version.h and links
libtfpsacrypto. With 4.x it checks PSA_WANT_ALG_CFB and compares the
TF-PSA-Crypto version of the headers and the library.

Signed-off-by: Yegor Yefremov <yegorslists@googlemail.com>
Assisted-by: Claude:claude-opus-5
Homebrew's mbedtls formula is Mbed TLS 4.x. Build against it in system
mode on macOS and run the unit tests, so that the PSA code paths are
covered.

Signed-off-by: Yegor Yefremov <yegorslists@googlemail.com>
Assisted-by: Claude:claude-opus-5

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant