Derive the server's unique id from its handshake key

TS3 derives a virtual server's unique identifier from its public key the
same way as a client's: base64(SHA-1(key)). The server never tells a
guest otherwise, as serverinfo is refused and neither initserver nor
notifyserverupdated carries it. The socket now keeps the key from
initivexpand(2) and offers getServerUid().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 10:35:41 +00:00
co-authored by Claude Opus 5.5
parent 8273a27160
commit c47bbdf96c
3 changed files with 44 additions and 0 deletions
@@ -204,6 +204,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
byte[] alpha = Base64.getDecoder().decode(command.get("alpha").getValue()); // alpha
byte[] beta = Base64.getDecoder().decode(command.get("beta").getValue()); // beta
byte[] omega = Base64.getDecoder().decode(command.get("omega").getValue()); // omega
getClient().setServerPublicKey(command.get("omega").getValue());
getClient().setSecureParameters(Ts3Crypt.cryptoInit(alpha, beta, omega, getClient().getIdentity()));
@@ -224,6 +225,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
byte[] beta = Base64.getDecoder().decode(command.get("beta").getValue()); // beta
byte[] omega = Base64.getDecoder().decode(command.get("omega").getValue()); // omega
getClient().setServerPublicKey(command.get("omega").getValue());
byte[] proof = Base64.getDecoder().decode(command.get("proof").getValue());
Pair<byte[], byte[]> keyPair = Ts3Crypt.generateKeypair();
@@ -32,6 +32,9 @@ import java.io.IOException;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Function;
@@ -59,6 +62,9 @@ public class LocalTeamspeakClientSocket
/** The virtual server's properties as announced by <b>initserver</b>. */
private volatile Map<String, String> serverProperties = Collections.emptyMap();
/** The public key the server identified itself with in the handshake, base64 as sent. */
private volatile String serverPublicKey;
/**
* The internal command buffer
* <p>
@@ -325,6 +331,7 @@ public class LocalTeamspeakClientSocket
serverId = null;
serverProperties = Collections.emptyMap();
serverPublicKey = null;
remote = null;
}
@@ -399,6 +406,7 @@ public class LocalTeamspeakClientSocket
// Free stuff
serverId = null;
serverProperties = Collections.emptyMap();
serverPublicKey = null;
synchronized (commandSendLock) {
@@ -840,6 +848,25 @@ public class LocalTeamspeakClientSocket
return serverProperties;
}
public void setServerPublicKey(String serverPublicKey) {
this.serverPublicKey = serverPublicKey;
}
/**
* The virtual server's unique identifier, or null before the handshake. It is derived from
* the server's public key as a client's is from its own; the server tells a client no other
* way, as guests may not read it with <b>serverinfo</b>.
*/
public Uid getServerUid() {
String key = serverPublicKey;
if (key == null) return null;
try {
return new Uid(MessageDigest.getInstance("SHA-1").digest(key.getBytes(StandardCharsets.US_ASCII)));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
/** The virtual server's name, or null if it was not announced. */
public String getServerName() {
return serverProperties.get("virtualserver_name");
@@ -0,0 +1,15 @@
package com.github.manevolent.ts3j;
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
import junit.framework.TestCase;
public class ServerUidTest extends TestCase {
/** A real server's handshake key, and the unique id the official client files its chat logs under. */
public void testDerivedFromTheHandshakeKey() {
LocalTeamspeakClientSocket client = new LocalTeamspeakClientSocket();
assertNull(client.getServerUid());
client.setServerPublicKey("MEwDAgcAAgEgAiEAtg1UQ4UpfiyTjrYPQcWV7Pa04/HTWr/viRhqEOg7+MQCIH/FqUKJDpBJDn/v01KOR+snSWlcoxOsfb2R8C02T3/z");
assertEquals("+Tyg2JtxE8vRNZp+JiUBnmBh0MY=", client.getServerUid().toBase64());
}
}