Skip to content

Fix AES-CBC with mbedTLS 3.x: restore PKCS7 padding and apply caller IV - #142

Open
yveshughes wants to merge 1 commit into
moonlight-stream:masterfrom
yveshughes:fix-mbedtls3-cbc
Open

Fix AES-CBC with mbedTLS 3.x: restore PKCS7 padding and apply caller IV#142
yveshughes wants to merge 1 commit into
moonlight-stream:masterfrom
yveshughes:fix-mbedtls3-cbc

Conversation

@yveshughes

Copy link
Copy Markdown

Problem

Building with USE_MBEDTLS=ON against mbedTLS 3.x breaks both AES-CBC uses of PlatformCrypto.c, while the same code works with mbedTLS 2.x and OpenSSL. There are two separate divergences from the OpenSSL path:

1. mbedTLS 3.x removed the default PKCS7 padding mode for CBC contexts

In mbedTLS 2.x, mbedtls_cipher_setup() initialized CBC contexts with PKCS7 padding by default (matching OpenSSL's EVP layer, which also defaults to PKCS7). In mbedTLS 3.x this default was removed — a freshly set-up CBC context has no padding mode, and mbedtls_cipher_finish() fails with MBEDTLS_ERR_CIPHER_BAD_INPUT_DATA (-0x6100) on any input.

Since PltDecryptMessage() never set a padding mode explicitly, every ALGORITHM_AES_CBC + CIPHER_FLAG_FINISH operation fails. The user-visible symptom is Failed to decrypt audio packet on every audio packet when streaming from Sunshine or GameStream with an mbedTLS-3.x-built client — audio is completely broken.

Fix: explicitly set MBEDTLS_PADDING_PKCS7 on CBC contexts at first-time init in both PltEncryptMessage() and PltDecryptMessage(), guarded by #ifdef MBEDTLS_CIPHER_MODE_WITH_PADDING.

2. The caller's IV is never applied without CIPHER_FLAG_RESET_IV

The OpenSSL path passes the caller's IV into the first EVP_EncryptInit_ex()/EVP_DecryptInit_ex() call even when CIPHER_FLAG_RESET_IV is not set. The mbedTLS path only calls mbedtls_cipher_set_iv() when that flag is present, so without it the context keeps the all-zero IV from setup and the first block is en/decrypted with the wrong IV.

This affects the pre-Gen 7 CBC remote input encryption in InputStream.c, which initializes the context once with the real IV and then relies on cross-packet CBC chaining (no CIPHER_FLAG_RESET_IV).

Fix: apply the caller's IV (set_iv + reset) during first-time initialization for non-GCM contexts. GCM is excluded because it passes its per-message IV directly to mbedtls_cipher_auth_{encrypt,decrypt}_ext().

Verification

Tested on Linux against mbedTLS 3.6.2 with standalone host-side tests cross-checking against OpenSSL:

  • AES-128-CBC/PKCS7 packets encrypted through the OpenSSL PltEncryptMessage() path (Sunshine's audio packet format) decrypt correctly through the fixed mbedTLS PltDecryptMessage() path.
  • mbedTLS PltEncryptMessage() CBC output is byte-identical to OpenSSL output across a sequence of packets on one context, confirming the cross-packet CBC chaining behavior (input-stream style, no RESET_IV) matches.

GCM paths (video/control, Gen 7+ input) are unaffected: they always pass CIPHER_FLAG_RESET_IV or use the auth-crypt entry points, and padding does not apply to GCM.

Related: #75 migrated to the mbedTLS 3.x API surface but didn't cover this behavioral change in mbedtls_cipher_setup().

🤖 Generated with Claude Code

mbedtls_cipher_setup() in mbedTLS 3.x no longer installs a default
padding mode for CBC contexts (mbedTLS 2.x defaulted to PKCS7, as does
OpenSSL's EVP layer). Without an explicit padding mode,
mbedtls_cipher_finish() fails with MBEDTLS_ERR_CIPHER_BAD_INPUT_DATA
(-0x6100) on every AES-CBC operation that uses CIPHER_FLAG_FINISH,
which breaks audio decryption ("Failed to decrypt audio packet") for
any client built with USE_MBEDTLS against mbedTLS 3.x. Set PKCS7
explicitly for CBC contexts at init, guarded by
MBEDTLS_CIPHER_MODE_WITH_PADDING.

Additionally, the mbedTLS path never applied the caller-provided IV
unless CIPHER_FLAG_RESET_IV was set, leaving the context IV zeroed
from setup. The OpenSSL path passes the IV to the first
EVP_*Init_ex() call regardless of that flag, which the pre-Gen 7 CBC
remote input encryption in InputStream.c relies on (initial caller-set
IV followed by cross-packet CBC chaining). Apply the caller's IV
during first-time initialization for non-GCM contexts to match.

Verified against OpenSSL: AES-128-CBC/PKCS7 packets encrypted via the
OpenSSL path decrypt correctly through the mbedTLS path, and mbedTLS
CBC encryption byte-matches OpenSSL output including cross-packet
chaining.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@trexx

trexx commented Aug 7, 2026

Copy link
Copy Markdown

Ran in to this whilst updating moonlight-android.

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.

2 participants