Client authentication is done. It will now put an API client in a server, however we clearly need to work on decompression, and we also need to accept and handle ping packets appropriately.

Then we can start working on the text-based protocol Teamspeak uses.
This commit is contained in:
Manevolent
2018-07-27 16:07:29 -04:00
parent 146f2b6564
commit 6007b24b5f
16 changed files with 191 additions and 88 deletions
@@ -28,7 +28,6 @@ public class Identity {
public Identity(ECPoint publicKey) {
this.publicKey = publicKey;
this.publicKeyString = Base64.getEncoder().encodeToString(Ts3Crypt.encodePublicKey(publicKey));
this.fingerprint = generateFingerprint();
}
@@ -98,7 +97,7 @@ public class Identity {
int numLen = 0;
do
{
numBuffer[numLen] = (byte)((offset % 10) & 0xFF);
numBuffer[numLen] = (byte)(48 + (offset % 10));
offset /= 10;
numLen ++;
} while (offset > 0);
@@ -132,7 +131,9 @@ public class Identity {
private String generateFingerprint() {
synchronized (sha1) {
return Base64.getEncoder().encodeToString(sha1.digest(Ts3Crypt.encodePublicKey(publicKey)));
return Base64.getEncoder().encodeToString(
sha1.digest(getPublicKeyString().getBytes(Charset.forName("ASCII")))
);
}
}
}
@@ -24,15 +24,12 @@ public class LocalIdentity extends Identity {
}
public static LocalIdentity load(ECPoint publicKey,
BigInteger privateKey,
long keyOffset,
long lastCheckedKeyOffset) {
LocalIdentity localIdentity = new LocalIdentity(publicKey, privateKey);
BigInteger privateKey) {
return new LocalIdentity(publicKey, privateKey);
}
localIdentity.setKeyOffset(keyOffset);
localIdentity.setLastCheckedKeyOffset(lastCheckedKeyOffset);
return localIdentity;
public static LocalIdentity load(BigInteger privateKey) {
return new LocalIdentity(Ts3Crypt.generatePublicKeyFromPrivateKey(privateKey), privateKey);
}
public static LocalIdentity generateNew(int securityLevel) throws GeneralSecurityException {
@@ -48,7 +45,7 @@ public class LocalIdentity extends Identity {
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) keyPair.getPrivate();
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) keyPair.getPublic();
LocalIdentity localIdentity = load(publicKey.getQ().normalize(), privateKey.getD(), 0, 0);
LocalIdentity localIdentity = load(publicKey.getQ().normalize(), privateKey.getD());
localIdentity.improveSecurity(securityLevel);
return localIdentity;
@@ -218,6 +218,7 @@ public class License {
byte[] final_ = new byte[32];
GroupOperations.ge_p3_tobytes(final_, 0, r2);
final_[31] ^= 0x80;
return final_;
}
@@ -4,6 +4,7 @@ import com.github.manevolent.ts3j.command.SimpleCommand;
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
import com.github.manevolent.ts3j.protocol.Packet;
import com.github.manevolent.ts3j.protocol.ProtocolRole;
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
import com.github.manevolent.ts3j.protocol.packet.PacketBody2Command;
import com.github.manevolent.ts3j.protocol.packet.PacketBody8Init1;
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
@@ -45,11 +46,6 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
}
private void sendInit1(PacketBody8Init1 packet) throws IOException, TimeoutException {
Ts3Debugging.debug("Connecting: sending " + packet.getClass().getSimpleName()
+ ":"
+ packet.getStep().getClass().getSimpleName() + "..."
);
packet.setVersion(new byte[] { 0x09, (byte)0x83, (byte)0x8C, (byte)0xCF });
getClient().writePacket(packet);
@@ -57,8 +53,6 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
@Override
public void handlePacket(Packet packet) throws IOException, TimeoutException {
Ts3Debugging.debug("Connecting: handling " + packet.getBody().getClass().getSimpleName());
if (packet.getBody() instanceof PacketBody8Init1) {
PacketBody8Init1 init1 = (PacketBody8Init1) packet.getBody();
@@ -101,8 +95,6 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
byte[] solution =
x.modPow(BigInteger.valueOf(2L).pow(serverReplyStep3.getLevel()), n).toByteArray();
Ts3Debugging.debug(Ts3Debugging.getHex(solution));
System.arraycopy(
solution, Math.abs(solution.length - 64),
y, solution.length < 64 ? 64 - solution.length : 0,
@@ -187,8 +179,6 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
getClient().getIdentity()
);
Ts3Debugging.debug("Sending clientek...");
getClient().sendCommand(
new SimpleCommand(
"clientek", ProtocolRole.CLIENT,
@@ -199,36 +189,52 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
getClient().setSecureParameters(Ts3Crypt.cryptoInit2(license, alphaBytes, beta, keyPair.getValue()));
Ts3Debugging.debug("Sending clientinit...");
getClient().sendCommand(new SimpleCommand(
"clientinit",
ProtocolRole.CLIENT,
new CommandSingleParameter("client_nickname", "test"),
new CommandSingleParameter("client_version", "3.1 [Build: 1471417187]"),
new CommandSingleParameter("client_nickname", getClient().getNickname()),
new CommandSingleParameter("client_version", "3.1.8 [Build: 1516614607]"),
new CommandSingleParameter("client_platform", "Windows"),
new CommandSingleParameter(
"client_version_sign",
"gDEgQf/BiOQZdAheKccM1XWcMUj2OUQqt75oFuvF2c0MQMXyv88cZQdUuckKbcBRp7RpmLInto4PIgd7mPO7BQ=="
"LJ5q+KWT4KwBX7oR/9j9A12hBrq5ds5ony99" +
"f9kepNmqFskhT7gfB51bAJNgAMOzXVCeaItNmc10F2wUNktqCw=="
),
new CommandSingleParameter("client_input_hardware", "1"),
new CommandSingleParameter("client_output_hardware", "1"),
new CommandSingleParameter("client_default_channel", ""),
new CommandSingleParameter("client_default_channel_password", ""),
new CommandSingleParameter("client_server_password", ""),
new CommandSingleParameter("client_nickname_phonetic", "test"),
new CommandSingleParameter("client_default_channel",
getClient().getOption("client.default_channel", String.class)
),
new CommandSingleParameter("client_default_channel_password",
getClient().getOption("client.default_channel_password", String.class)
),
new CommandSingleParameter(
"client_server_password",
getClient().getOption("client.server_password", String.class)
),
new CommandSingleParameter("client_nickname_phonetic",
getClient().getOption("client.nickname_phonetic", String.class)
),
new CommandSingleParameter("client_meta_data", ""),
new CommandSingleParameter("client_default_token", ""),
new CommandSingleParameter("client_default_token",
getClient().getOption("client.default_token", String.class)
),
new CommandSingleParameter(
"client_key_offset",
Long.toString(getClient().getIdentity().getKeyOffset())
),
new CommandSingleParameter(
"hwid",
"+LyYqbDqOvJJpN5pdAbF8/v5kZ0="
getClient().getOption("client.hwid", String.class) != null ?
getClient().getOption("client.hwid", String.class) :
"+LyYqbDqOvEEpN5pdAbF8/v5kZ0="
)
)
);
} else if (command.getName().equals("error")) {
getClient().setState(ClientConnectionState.DISCONNECTED);
throw new IOException(command.get("msg").getValue());
}
}
}
@@ -327,15 +327,13 @@ public abstract class AbstractTeamspeakClientSocket
packet.getHeader().getPacketId(),
response = new PacketResponse(
networkPacket,
Integer.MAX_VALUE
networkPacket.getHeader().getType().canResend() ? Integer.MAX_VALUE : 0
)
);
else
response = null;
Ts3Debugging.debug("[PROTOCOL] WRITE " + networkPacket.getHeader().getType().name());
for (HeaderFlag flag : HeaderFlag.values())
Ts3Debugging.debug(flag.name() + ": " + networkPacket.getHeader().getPacketFlag(flag));
writeNetworkPacket(networkPacket);
@@ -350,7 +348,10 @@ public abstract class AbstractTeamspeakClientSocket
response.waitForAcknowledgement(1000);
break;
} catch (TimeoutException e) {
response.resend();
if (response.willResend())
response.resend();
else
throw e;
} catch (CancellationException e) {
throw e;
} catch (Exception e) {
@@ -398,11 +399,6 @@ public abstract class AbstractTeamspeakClientSocket
);
}
Ts3Debugging.debug("[NETWORK] READ " +
packet.getHeader().getType() + " BODY "
+ Ts3Debugging.getHex(packetBuffer.array())
);
// Fragment handling
if (fragment) {
Ts3Debugging.debug("[PROTOCOL] READ " + networkPacket.getHeader().getType().name() + " fragment");
@@ -547,6 +543,14 @@ public abstract class AbstractTeamspeakClientSocket
this.generationId = generationId;
}
public String getNickname( ){
return getOption("client.nickname", String.class);
}
public void setNickname(String name) {
setOption("client.nickname", name);
}
private class NetworkHandler implements Runnable {
@Override
public void run() {
@@ -554,9 +558,10 @@ public abstract class AbstractTeamspeakClientSocket
try {
Packet packet = readQueue.take();
Ts3Debugging.debug("Processing " + packet + "...");
// separate these so we getHandler at proper runtime instant rather than get it and let it change
// on us
getHandler().handlePacket(packet);
Ts3Debugging.debug("Processed " + packet + ".");
} catch (Exception e) {
if (e instanceof InterruptedException) continue;
@@ -605,12 +610,25 @@ public abstract class AbstractTeamspeakClientSocket
tries ++;
}
/**
* Waits for a package acknowledgement.
* @return true if the acknowledgement wasn't null (shouldn't ever happen)
* @throws ExecutionException
* @throws InterruptedException
*/
public boolean waitForAcknowledgement()
throws ExecutionException, InterruptedException {
Packet acknowledgement = future.get();
return acknowledgement != null;
}
/**
* Waits for a package acknowledgement.
* @param millis Milliseconds to wait. When transpired with no completion, TimeoutException is thrown.
* @return true if the acknowledgement wasn't null (shouldn't ever happen)
* @throws ExecutionException
* @throws InterruptedException
*/
public boolean waitForAcknowledgement(long millis)
throws TimeoutException, ExecutionException, InterruptedException {
Packet acknowledgement = future.get(millis, TimeUnit.MILLISECONDS);
@@ -618,7 +636,7 @@ public abstract class AbstractTeamspeakClientSocket
return acknowledgement != null;
}
public boolean isWillResend() {
public boolean willResend() {
return willResend;
}
@@ -65,7 +65,6 @@ public class LocalTeamspeakClientSocket extends AbstractTeamspeakClientSocket {
packet.getDatagram().getLength()
+ " to " +
socket.getRemoteSocketAddress());
Ts3Debugging.debug("[NETWORK] WRITE " + Ts3Debugging.getHex(packet.getBuffer().array()));
socket.send(packet.getDatagram());
}
@@ -19,7 +19,7 @@ public interface TeamspeakClientSocket extends TeamspeakSocket {
default <T extends Object> T getOption(String key, Class<T> clazz) {
Object value = getOptions().get(key);
if (value == null) return (T) null;
if (value == null) return null;
else if (!value.getClass().isAssignableFrom(clazz)) {
throw new ClassCastException(
@@ -226,11 +226,15 @@ public final class Ts3Crypt {
GroupOperations.ge_scalarmult_base(A, privateKey, 0);
byte[] publicKey = new byte[32];
GroupOperations.ge_p3_tobytes_negate(publicKey, 0, A);
GroupOperations.ge_p3_tobytes(publicKey, 0, A);
return new Pair<>(publicKey, privateKey);
}
public static ECPoint generatePublicKeyFromPrivateKey(BigInteger privateKey) {
return ECNamedCurveTable.getParameterSpec("prime256v1").getG().multiply(privateKey).normalize();
}
public static final class SecureChannelParameters {
private final byte[] fakeSignature;
private final byte[] ivStruct;
@@ -41,7 +41,7 @@ public final class Ts3String {
case '/': sb.append('/'); break;
case '\\': sb.append('\\'); break;
default:
throw new IllegalArgumentException("invalid escape code: " + c);
throw new IllegalArgumentException("invalid escape code: " + string.charAt(i) + " (" + Character.getName(string.charAt(i)) + ")");
}
} else sb.append(c);
}