# myTeamSpeak client protocol (reverse-engineered) Recovered from the official TS3 client binaries (`TeamSpeak3-Client-linux_amd64/ts3client_linux_amd64` and `re-android/.../libteamspeak_client.so`) plus a captured real login/sync session. This documents what is needed to talk to myTeamSpeak for synchronization of bookmarks/identities/etc. ## Transport Not gRPC-over-HTTP2 in the usual sense. It is **HTTP/1.1 POST** (cpp-httplib client) to: https://clientapi.myteamspeak.com/ Endpoints: `authentication`, `session`, `synchronization`, `user`, `integration`, `messenger`, `tschat`, `addon`. Headers: `Content-Type: application/ts3cloud`, `Accept: */*`, `Connection: close`, `User-Agent: cpp-httplib/0.11.1`. Cloudflare fronts it and 403s anything that doesn't look right. Request body framing: <1 byte: len of method name> e.g. `05 "login" `, `12 "requestServerItems" `. Response body = the serialized protobuf reply (Content-Type comes back `application/json` but the body is protobuf, not JSON). ## Services / methods (see myteamspeak.recovered.proto for full schemas) - **LoginService** (`/authentication`): `login(LoginData) -> LoginSession`, `loginWithAuthToken`, `loginWithRenewalToken`, `session`, `deleteSession`, `requestAuthToken`. - **SynchronizationService** (`/synchronization`): `requestServerItems(Sync_Request_ItemClasses) -> Sync_Reply_ItemClasses` (download), `synchronizeItems(...)` (upload/merge). ### Login flow (observed) 1. POST `/authentication` `login` with `LoginData{email(1), password(2)}`. `password` is NOT the plaintext — see Crypto below. 2. Reply `LoginSession`: `key(1)`, `session(2)=UUID` (bearer for later calls), `limits(3)`, `uuid(4)`, `error(5)=200` on success (ErrorCommon.ERROR_LOGIN_OK), `purge(6)`, `username(8)`, `myts_id_data(9)=MyTeamSpeakIdData`, `mytsid_user_cert(10)`, `alternative_login_info(16).renewal_token`. 3. POST `/synchronization` `requestServerItems` with `{session(1), classes(2), globalversion(3)}`. Advertise each local item as `{item_uuid(1), item_version(2)}` where `item_version` is exactly the local plaintext `Item_Data.sync_version_uuid`; the initial request builder does not attach blobs. 4. Reconcile the server's comparison reply. Decrypt any returned `item_blob`, honor deletion/not-in-db details, and retain its opaque global and per-class version cursors. 5. If local changes are requested, POST `synchronizeItems` with those reply cursors. Adds/changes carry the full encrypted `Item_Data` frame; deletions use the detail deletion flag. See [CRYPTO_RE_SALT.md](CRYPTO_RE_SALT.md#5-synchronization-and-upload-state-machine) for the recovered state machine and confidence boundaries. ## Crypto status myTeamSpeak sync is **end-to-end encrypted**, using TeamSpeak's own `teamcrypto` library (AES-256-GCM + SHA-2 via mbedtls + a CTR-DRBG), statically linked — NOT OpenSSL's KDFs and NOT libsodium. This is why the sync store on disk is plaintext but the wire blobs are not. Recovered constructions: - The login `password` field is base64 of 48 bytes from PBKDF2-HMAC-SHA512 with 10,000 iterations: password=`password`, salt=`asciiLower(email) + "ts3Login" + password`. - The account wrapping key is the same KDF with salt purpose `ts3Encryption` and a 32-byte output. - `LoginSession.key` v2 is `02 || GCM-tag[16] || IV[12] || ciphertext[32]`, AES-256-GCM without AAD. Its plaintext is the 32-byte item key. - `MyTeamSpeakIdData`: `user_public_key(1)` = 32 bytes (X25519/Ed25519), `user_private_key(2).encrypted_user_private_key` = 112 bytes (the account private key, wrapped by a password-derived key), `account_creation_time(3)`, `my_teamspeak_id(4)` = `01 20 <32 bytes>`, `public_signature(5)`. - `Account_Data` / `Auth_Token_Package` show the recovery model: `encrypted_encryption_password`, `encryption_tag`, `iv` — a random data key wrapped by the password-derived key, and separately by the **recovery key** (32 bytes, base64), so the recovery key is the route to decrypt items without the password. - Item blobs are `IV[16] || AES-256-CTR ciphertext || SHA512(plaintext)`. CTR increments its full 128-bit counter little-endian. The original capture contained the complete ciphertext but only the first 27 of 64 digest bytes; those bytes exactly match the recovered plaintext hash. ## Remaining reverse-engineering gaps Password login, key unwrap, item encryption/integrity framing, two-phase comparison, and the local manipulation flags are recovered. The remaining uncertainties are narrower: where ordinary edits rotate `sync_version_uuid`, the exact lifecycle of `last_known_version`, the official collision winner/merge policy, and instruction-level confirmation of the deletion-detail builder. Recovery-key import is also not yet recovered. The read-only pull is implemented in `com.ts3client.myts` (see IMPLEMENTATION.md); destructive upload should remain gated until the deletion mapping has a fixture or controlled integration test.