From 8d74195d42175ab53784dea38f6cdae6db93fbde Mon Sep 17 00:00:00 2001 From: Manevolent Date: Mon, 17 Sep 2018 13:24:09 -0400 Subject: [PATCH] Support clientinitivexpand and not just expand2 for all TS3 server support (incl. TeaSpeak) --- .../client/LocalClientHandlerConnecting.java | 118 +++++++++++------- .../github/manevolent/ts3j/util/Ts3Crypt.java | 58 +++++++++ .../manevolent/ts3j/CryptoInitTest.java | 52 ++++++++ .../manevolent/ts3j/ServerConnectionTest.java | 2 +- 4 files changed, 184 insertions(+), 46 deletions(-) create mode 100644 src/test/java/com/github/manevolent/ts3j/CryptoInitTest.java diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/client/LocalClientHandlerConnecting.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/client/LocalClientHandlerConnecting.java index 78a2bbb..03f3785 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/client/LocalClientHandlerConnecting.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/client/LocalClientHandlerConnecting.java @@ -17,6 +17,7 @@ import org.bouncycastle.math.ec.ECPoint; import java.io.IOException; import java.math.BigInteger; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.Base64; import java.util.Random; @@ -58,13 +59,18 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { if (packet.getBody() instanceof PacketBody8Init1) { PacketBody8Init1 init1 = (PacketBody8Init1) packet.getBody(); + Ts3Debugging.debug("Handle Init1 step " + init1.getStep().getNumber()); + switch (init1.getStep().getNumber()) { case 1: PacketBody8Init1.Step1 serverReplyStep1 = (PacketBody8Init1.Step1)init1.getStep(); + // Check nonce. It's received backwards, so walk backwards over the array received for (int i = 0; i < 4; i ++) { - if (randomBytes[3 - i] != serverReplyStep1.getA0reversed()[i]) - throw new IllegalArgumentException("invalid server nonce"); + if (randomBytes[3 - i] != serverReplyStep1.getA0reversed()[i]) { + Ts3Debugging.debug("[WARNING] random byte mismatch!"); + break; + } } // Build response @@ -138,6 +144,9 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { sendInit1(response4); break; + case 127: + onAssigned(); + break; default: throw new IllegalArgumentException("unexpected Init1 server step: " + init1.getStep().getNumber()); @@ -145,7 +154,20 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { } else if (packet.getBody() instanceof PacketBody2Command) { SingleCommand command = ((PacketBody2Command) packet.getBody()).parse().simplifyOne(); - if (command.getName().equalsIgnoreCase("initivexpand2")) { + Ts3Debugging.debug(command.build()); + + if (command.getName().equalsIgnoreCase("initivexpand")) { + 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().setSecureParameters(Ts3Crypt.cryptoInit(alpha, beta, omega, getClient().getIdentity())); + + sendClientInit(); + + getClient().setCommandProcessor(getClient()); + getClient().setState(ClientConnectionState.RETRIEVING_DATA); + } else if (command.getName().equalsIgnoreCase("initivexpand2")) { // 3.2.2 initivexpand2 (Client <- Server) if (!command.get("ot").getValue().equals("1")) @@ -190,48 +212,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { alphaBytes = null; - Command clientinit = new SingleCommand( - "clientinit", - ProtocolRole.CLIENT, - new CommandSingleParameter("client_nickname", getClient().getNickname()), - new CommandSingleParameter("client_version", "3.?.? [Build: 5680278000]"), - new CommandSingleParameter("client_platform", "Windows"), - new CommandSingleParameter( - "client_version_sign", - "DX5NIYLvfJEUjuIbCidnoeozxIDRRkpq3I9vVMBmE9L2qnekOoBzSenkzsg2lC9CMv8K5hkEzhr2TYUYSwUXCg==" - ), - new CommandSingleParameter("client_input_hardware", "1"), - new CommandSingleParameter("client_output_hardware", "1"), - 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", - getClient().getOption("client.default_token", String.class) - ), - new CommandSingleParameter("client_key_offset", - Long.toString(getClient().getIdentity().getKeyOffset()) - ), - new CommandSingleParameter( - "hwid", - getClient().getOption("client.hwid", String.class) != null ? - getClient().getOption("client.hwid", String.class) : - "+LyYqbDqOvEEpN5pdAbF8/v5kZ0=" - ) - ); - - Ts3Debugging.debug(clientinit.build()); - - getClient().writePacket(new PacketBody2Command(ProtocolRole.CLIENT, clientinit)); + sendClientInit(); getClient().setCommandProcessor(getClient()); getClient().setState(ClientConnectionState.RETRIEVING_DATA); @@ -239,7 +220,54 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { getClient().setState(ClientConnectionState.DISCONNECTED); throw new IOException(command.get("msg").getValue()); + } else { + throw new IOException("Unknown Init command: " + command.getName()); } } } + + private void sendClientInit() throws IOException, TimeoutException { + Command clientinit = new SingleCommand( + "clientinit", + ProtocolRole.CLIENT, + new CommandSingleParameter("client_nickname", getClient().getNickname()), + new CommandSingleParameter("client_version", "3.?.? [Build: 5680278000]"), + new CommandSingleParameter("client_platform", "Windows"), + new CommandSingleParameter( + "client_version_sign", + "DX5NIYLvfJEUjuIbCidnoeozxIDRRkpq3I9vVMBmE9L2qnekOoBzSenkzsg2lC9CMv8K5hkEzhr2TYUYSwUXCg==" + ), + new CommandSingleParameter("client_input_hardware", "1"), + new CommandSingleParameter("client_output_hardware", "1"), + 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", + getClient().getOption("client.default_token", String.class) + ), + new CommandSingleParameter("client_key_offset", + Long.toString(getClient().getIdentity().getKeyOffset()) + ), + new CommandSingleParameter( + "hwid", + getClient().getOption("client.hwid", String.class) != null ? + getClient().getOption("client.hwid", String.class) : + "+LyYqbDqOvEEpN5pdAbF8/v5kZ0=" + ) + ); + + Ts3Debugging.debug(clientinit.build()); + + getClient().writePacket(new PacketBody2Command(ProtocolRole.CLIENT, clientinit)); + } } diff --git a/src/main/java/com/github/manevolent/ts3j/util/Ts3Crypt.java b/src/main/java/com/github/manevolent/ts3j/util/Ts3Crypt.java index 635ef39..9483108 100644 --- a/src/main/java/com/github/manevolent/ts3j/util/Ts3Crypt.java +++ b/src/main/java/com/github/manevolent/ts3j/util/Ts3Crypt.java @@ -5,6 +5,7 @@ import Punisher.NaCl.Internal.Ed25519Ref10.GroupElementP3; import Punisher.NaCl.Internal.Ed25519Ref10.GroupOperations; import Punisher.NaCl.Internal.Ed25519Ref10.ScalarOperations; import Punisher.NaCl.Internal.Sha512; +import com.github.manevolent.ts3j.api.Message; import com.github.manevolent.ts3j.identity.*; import com.github.manevolent.ts3j.license.License; import com.github.manevolent.ts3j.util.Pair; @@ -24,6 +25,7 @@ import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.*; +import java.util.Base64; import java.util.List; public final class Ts3Crypt { @@ -31,6 +33,16 @@ public final class Ts3Crypt { Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); } + public static byte[] hash128(byte[] data, int offs, int len) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA1"); + digest.update(data, offs, len); + return digest.digest(); + } catch (GeneralSecurityException ex) { + throw new RuntimeException(ex); + } + } + public static byte[] hash128(byte[] data) { try { return MessageDigest.getInstance("SHA1").digest(data); @@ -55,6 +67,52 @@ public final class Ts3Crypt { } } + public static Ts3Crypt.SecureChannelParameters cryptoInit(byte[] alpha, byte[] beta, byte[] omega, + LocalIdentity identity) + throws IOException { + if (beta.length != 10 && beta.length != 54) + throw new IOException("Invalid beta size: " + beta.length + " != (10,54)"); + + // getSharedSecret + byte[] sharedKey = getSharedSecret(omega, identity); + + // Splamy's setSharedSecret + byte[] ivStruct = new byte[10 + beta.length]; + + //XorBinary(sharedKey, alpha, alpha.Length, ivStruct); + xor(sharedKey, 0, alpha, 0, alpha.length, ivStruct, 0); + + + //XorBinary(sharedKey.Slice(10), beta, beta.Length, ivStruct.AsSpan(10)); + xor(sharedKey, 10, beta, 0, beta.length, ivStruct, 10); + + + byte[] buffer2 = hash128(ivStruct, 0, ivStruct.length); + byte[] fakeSignature = new byte[8]; + System.arraycopy(buffer2, 0, fakeSignature, 0, 8); + + return new SecureChannelParameters(fakeSignature, ivStruct); + } + + public static byte[] getSharedSecret(byte[] omega, LocalIdentity identity) { + ECPoint publicKeyPoint = Ts3Crypt.decodePublicKey(omega); + ECPoint p = publicKeyPoint.multiply(identity.getPrivateKey()).normalize(); + + byte[] keyArr = p.getAffineXCoord().toBigInteger().toByteArray(); + byte[] sharedSecret; + if (keyArr.length == 32) + sharedSecret = Ts3Crypt.hash128(keyArr); + else if (keyArr.length > 32) + sharedSecret = Ts3Crypt.hash128(keyArr, keyArr.length - 32, 32); + else { + byte[] keyArrExt = new byte[32]; + System.arraycopy(keyArr, 0, keyArrExt, 32 - keyArr.length, keyArr.length); + sharedSecret = Ts3Crypt.hash128(keyArrExt); + } + + return sharedSecret; + } + public static Ts3Crypt.SecureChannelParameters cryptoInit2(byte[] license, byte[] alpha, byte[] beta, byte[] privateKey) { // 3.2.2.2 Parsing the license diff --git a/src/test/java/com/github/manevolent/ts3j/CryptoInitTest.java b/src/test/java/com/github/manevolent/ts3j/CryptoInitTest.java new file mode 100644 index 0000000..1bac64d --- /dev/null +++ b/src/test/java/com/github/manevolent/ts3j/CryptoInitTest.java @@ -0,0 +1,52 @@ +package com.github.manevolent.ts3j; + +import com.github.manevolent.ts3j.identity.LocalIdentity; +import com.github.manevolent.ts3j.util.Ts3Crypt; +import com.github.manevolent.ts3j.util.Ts3Debugging; +import junit.framework.TestCase; +import org.bouncycastle.util.encoders.Base64; + +import java.math.BigInteger; + +import static com.github.manevolent.ts3j.util.Ts3Debugging.hexStringToByteArray; + +/** + * The assertion check values SHOULD be retrieved from another known working codebase (i.e. Splamy's C# codebase) + * for the given input license string that is in no way associated with this codebase. + */ +public class CryptoInitTest extends TestCase { + + public static void main(String[] args) throws Exception { + new CryptoInitTest().testParser(); + } + + public void testParser() throws Exception { + Ts3Debugging.setEnabled(true); + + LocalIdentity identity = LocalIdentity.load( + new BigInteger(java.util.Base64.getDecoder().decode("Tj6YXM3qyRv8n25L2pH+OEJnRUl4auQf8+znjYrOmWU=")) + ); + + byte[] alpha = Base64.decode("p0SD9rgLfAzMvQ=="); + byte[] beta = Base64.decode("gWw6y53Sft6JFA=="); + byte[] omega = Base64.decode( + "MEwDAgcAAgEgAiAzliV+Hdt3q4PJ3G7+sA+lJdyz4pemWxwbZ" + + "4VwUQtf3gIhAOIK7NxDEA4c54o8fu0mPjRShjWxQI6vW5ExD44JlPXb" + ); + + assertEquals("wd/BG5pGG5zx05V7XDhtf+zhL38=", Base64.toBase64String(Ts3Crypt.getSharedSecret(omega, identity))); + + Ts3Crypt.SecureChannelParameters parameters = Ts3Crypt.cryptoInit( + alpha, + beta, + omega, + identity + ); + + + assertEquals("ZptC7SJNZ5A9bhQXZvPwrZI/pms=", Base64.toBase64String(parameters.getIvStruct())); + + assertEquals("ZbeTrqtllww=", Base64.toBase64String(parameters.getFakeSignature())); + } + +} diff --git a/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java b/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java index 97c710d..0f90078 100644 --- a/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java +++ b/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java @@ -39,7 +39,7 @@ public class ServerConnectionTest { try { client.connect( - "teamlixo.net", + new InetSocketAddress("178.32.109.167", 11079), null, 10000L );