Files
ts3java/ts3-client/docs/myteamspeak/PROTOCOL.md
ericek111 a7aa122728 Sign in to myTeamSpeak and import what the account synchronises
The myTeamSpeak client protocol and its end-to-end encryption are
reverse-engineered (docs/myteamspeak): an HTTP POST of a framed protobuf,
a PBKDF2-HMAC-SHA512 login token and account key, an AES-GCM wrapped
item key and AES-CTR items with a SHA-512 trailer.

Options on desktop and Settings on Android gain a myTeamSpeak page: sign
in and out, and the account's bookmarks and identities, each picked for
import and marked when already here. The client stays signed in the way
the official one does, keeping the derived token and key rather than the
password; each read signs in afresh, and a sign-in the server no longer
takes signs out. Nothing is written to the account.

Items are the same Item_Data as the local TeamSpeak store, so they go
through the existing decoder and importer; a bookmark chosen alone brings
the identity it connects with, found by UUID or, as some name it, by
the identity's name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 01:07:17 +00:00

82 lines
5.0 KiB
Markdown

# 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/<endpoint>
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> <method-name ASCII> <serialized protobuf request>
e.g. `05 "login" <LoginData>`, `12 "requestServerItems" <Sync_Request_ItemClasses>`.
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.