diff --git a/src/main/java/Punisher/NaCl/Internal/Ed25519Ref10/GroupOperations.java b/src/main/java/Punisher/NaCl/Internal/Ed25519Ref10/GroupOperations.java index 5dcfc1c..2a0303b 100644 --- a/src/main/java/Punisher/NaCl/Internal/Ed25519Ref10/GroupOperations.java +++ b/src/main/java/Punisher/NaCl/Internal/Ed25519Ref10/GroupOperations.java @@ -1065,6 +1065,6 @@ public class GroupOperations FieldOperations.fe_mul(x, h.X, recip); FieldOperations.fe_mul(y, h.Y, recip); FieldOperations.fe_tobytes(s, offset, y); - s[offset + 31] ^= (byte) (FieldOperations.fe_isnegative(x) << 7); + //s[offset + 31] ^= (byte) (FieldOperations.fe_isnegative(x) << 7); } } diff --git a/src/main/java/com/github/manevolent/ts3j/command/SimpleCommand.java b/src/main/java/com/github/manevolent/ts3j/command/SimpleCommand.java index 8762f2f..6dbb0b3 100644 --- a/src/main/java/com/github/manevolent/ts3j/command/SimpleCommand.java +++ b/src/main/java/com/github/manevolent/ts3j/command/SimpleCommand.java @@ -4,7 +4,6 @@ import com.github.manevolent.ts3j.command.part.CommandOption; import com.github.manevolent.ts3j.command.part.CommandParameter; import com.github.manevolent.ts3j.command.part.CommandSingleParameter; import com.github.manevolent.ts3j.protocol.ProtocolRole; -import com.github.manevolent.ts3j.util.Ts3Logging; import com.github.manevolent.ts3j.util.Ts3String; import java.util.*; diff --git a/src/main/java/com/github/manevolent/ts3j/identity/Identity.java b/src/main/java/com/github/manevolent/ts3j/identity/Identity.java index 3a36703..8e89a87 100644 --- a/src/main/java/com/github/manevolent/ts3j/identity/Identity.java +++ b/src/main/java/com/github/manevolent/ts3j/identity/Identity.java @@ -1,16 +1,10 @@ package com.github.manevolent.ts3j.identity; import com.github.manevolent.ts3j.util.Ts3Crypt; -import com.github.manevolent.ts3j.util.Ts3Logging; -import org.bouncycastle.asn1.ASN1Encodable; -import org.bouncycastle.asn1.DERBitString; -import org.bouncycastle.asn1.DERInteger; -import org.bouncycastle.asn1.DERSequence; +import com.github.manevolent.ts3j.util.Ts3Debugging; import org.bouncycastle.math.ec.ECPoint; -import java.io.IOException; import java.nio.charset.Charset; -import java.security.DigestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; @@ -83,7 +77,7 @@ public class Identity { if (curr > best) { - Ts3Logging.debug("Improved identity security level: from " + best + " to " + curr); + Ts3Debugging.debug("Improved identity security level: from " + best + " to " + curr); keyOffset = lastCheckedKeyOffset; best = curr; } diff --git a/src/main/java/com/github/manevolent/ts3j/identity/LocalIdentity.java b/src/main/java/com/github/manevolent/ts3j/identity/LocalIdentity.java index c061b6b..31c5ac9 100644 --- a/src/main/java/com/github/manevolent/ts3j/identity/LocalIdentity.java +++ b/src/main/java/com/github/manevolent/ts3j/identity/LocalIdentity.java @@ -15,10 +15,6 @@ import java.math.BigInteger; import java.security.*; public class LocalIdentity extends Identity { - static { - Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); - } - private final BigInteger privateKey; public LocalIdentity(ECPoint publicKey, BigInteger privateKey) { @@ -61,4 +57,8 @@ public class LocalIdentity extends Identity { public byte[] sign(byte[] data) { return Ts3Crypt.createSignature(privateKey, data); } + + public BigInteger getPrivateKey() { + return privateKey; + } } diff --git a/src/main/java/com/github/manevolent/ts3j/license/License.java b/src/main/java/com/github/manevolent/ts3j/license/License.java index c9fd389..160e77a 100644 --- a/src/main/java/com/github/manevolent/ts3j/license/License.java +++ b/src/main/java/com/github/manevolent/ts3j/license/License.java @@ -1,13 +1,10 @@ package com.github.manevolent.ts3j.license; import Punisher.NaCl.Internal.Ed25519Ref10.*; -import com.github.manevolent.ts3j.util.Ts3Logging; +import com.github.manevolent.ts3j.util.Ts3Crypt; import java.nio.ByteBuffer; import java.nio.charset.Charset; -import java.security.DigestException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; @@ -137,19 +134,19 @@ public class License { while (buffer.remaining() > 0) { int c = buffer.get(); switch (c) { - case 0: - break; + case 0x00: + byte[] stringBytes = new byte[len]; + buffer.position(buffer.position() - len); + buffer.get(stringBytes); + + return new String(stringBytes, Charset.forName("UTF8")); default: len++; break; } } - byte[] stringBytes = new byte[len]; - buffer.position(buffer.position() - len); - buffer.get(stringBytes); - - return new String(stringBytes, Charset.forName("UTF8")); + throw new IllegalStateException("string not null terminated"); } public static void writeNullTerminatedLicenseString(ByteBuffer buffer, String s) { @@ -160,13 +157,28 @@ public class License { public static List readLicenses(ByteBuffer buffer) { List licenses = new ArrayList<>(); + byte licenseVersion = buffer.get(); + if (licenseVersion != 0x01) + throw new IllegalArgumentException("invalid license version"); + while (buffer.remaining() > 0) { - byte licenseVersion = buffer.get(); - if (licenseVersion != 0x01) - throw new IllegalArgumentException("invalid license version"); + int start = buffer.position(); License license = new License(); license.read(buffer); + + int end = buffer.position(); + + int len = end - start; + + byte[] hashableLicense = new byte[len - 1]; + System.arraycopy(buffer.array(), start + 1, hashableLicense, 0, len - 1); + + byte[] licenseHash = Ts3Crypt.hash512(hashableLicense); + + byte[] splicedHash = new byte[32]; + System.arraycopy(licenseHash, 0, splicedHash, 0, 32); + license.computedHash = splicedHash; licenses.add(license); } @@ -174,40 +186,15 @@ public class License { } public byte[] getHash() { - if (computedHash == null) { - ByteBuffer buffer = ByteBuffer.allocate(getSize()); - write(buffer); - - MessageDigest digest; - - try { - digest = MessageDigest.getInstance("SHA512"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - - digest.update(buffer.array(), 2, buffer.array().length - 2); - - byte[] sliced = new byte[32]; - System.arraycopy(digest.digest(), 0, sliced, 0, 32); - - Ts3Logging.debug(Ts3Logging.getHex(buffer.array())); - - return sliced; - } - return computedHash; } public byte[] deriveKey(byte[] parent) { // ScalarOperations.sc_clamp(Hash); - Ts3Logging.debug("ScalarOperations.sc_clamp: " + Ts3Logging.getHex(getHash())); ScalarOperations.sc_clamp(getHash(), 0); - Ts3Logging.debug("ScalarOperations.sc_clamp: " + Ts3Logging.getHex(getHash())); // GroupOperations.ge_frombytes_negate_vartime(out var pubkey, Key); GroupElementP3 pubkey = new GroupElementP3(); - Ts3Logging.debug("ScalarOperations.ge_frombytes_negate_vartime: " + Ts3Logging.getHex(getPublicKey())); GroupOperations.ge_frombytes_negate_vartime(pubkey, getPublicKey(), 0); // GroupOperations.ge_frombytes_negate_vartime(out var parkey, parent); @@ -232,7 +219,7 @@ public class License { byte[] final_ = new byte[32]; GroupOperations.ge_p3_tobytes(final_, 0, r2); - final_[1] ^= 0x80; + //final_[1] ^= 0x80; return final_; } diff --git a/src/main/java/com/github/manevolent/ts3j/license/ServerLicenseUse.java b/src/main/java/com/github/manevolent/ts3j/license/ServerLicenseUse.java index ce8fa22..3fb255f 100644 --- a/src/main/java/com/github/manevolent/ts3j/license/ServerLicenseUse.java +++ b/src/main/java/com/github/manevolent/ts3j/license/ServerLicenseUse.java @@ -1,7 +1,6 @@ package com.github.manevolent.ts3j.license; import com.github.manevolent.ts3j.enums.LicenseType; -import com.github.manevolent.ts3j.util.Ts3Logging; import java.nio.ByteBuffer; diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody2Command.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody2Command.java index 46024c7..e47f9e9 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody2Command.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody2Command.java @@ -1,5 +1,6 @@ package com.github.manevolent.ts3j.protocol.packet; +import com.github.manevolent.ts3j.command.Command; import com.github.manevolent.ts3j.command.SimpleCommand; import com.github.manevolent.ts3j.protocol.ProtocolRole; @@ -13,6 +14,16 @@ public class PacketBody2Command extends PacketBody { super(PacketBodyType.COMMAND, role); } + public PacketBody2Command(ProtocolRole role, String command) { + this(role); + + this.text = command; + } + + public PacketBody2Command(ProtocolRole role, Command command) { + this(role, command.build()); + } + public String getText() { return text; } @@ -27,12 +38,12 @@ public class PacketBody2Command extends PacketBody { @Override public void read(ByteBuffer buffer) { - text = Charset.forName("ASCII").decode(buffer).toString(); + text = Charset.forName("UTF8").decode(buffer).toString(); } @Override public void write(ByteBuffer buffer) { - buffer.put(text.getBytes(Charset.forName("ASCII"))); + buffer.put(text.getBytes(Charset.forName("UTF8"))); } @Override diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody8Init1.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody8Init1.java index 80826f2..559450d 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody8Init1.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody8Init1.java @@ -10,10 +10,6 @@ import java.nio.ByteOrder; import static com.github.manevolent.ts3j.protocol.packet.PacketBodyType.INIT1; public class PacketBody8Init1 extends PacketBody { - private static final byte[] LOW_LEVEL_MAC = new byte[] { - 0x54, 0x53, 0x33, 0x49, 0x4E, 0x49, 0x54, 0x31 - }; - private byte[] version = new byte[4]; // 4 bytes private Step step; // step instance @@ -25,7 +21,6 @@ public class PacketBody8Init1 extends PacketBody { @Override public void setHeaderValues(PacketHeader header) { - header.setMac(LOW_LEVEL_MAC); header.setPacketFlag(HeaderFlag.UNENCRYPTED, true); header.setPacketId((short)101); } diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnecting.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnecting.java index c7403d7..1a3ed77 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnecting.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnecting.java @@ -4,12 +4,11 @@ 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; import com.github.manevolent.ts3j.util.Ts3Crypt; -import com.github.manevolent.ts3j.util.Ts3Logging; +import com.github.manevolent.ts3j.util.Ts3Debugging; import javafx.util.Pair; import org.bouncycastle.math.ec.ECPoint; @@ -46,9 +45,9 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { } private void sendInit1(PacketBody8Init1 packet) throws IOException, TimeoutException { - Ts3Logging.debug("Connecting: sending " + packet.getClass().getSimpleName() - + ":" - + packet.getStep().getClass().getSimpleName() + "..." + Ts3Debugging.debug("Connecting: sending " + packet.getClass().getSimpleName() + + ":" + + packet.getStep().getClass().getSimpleName() + "..." ); packet.setVersion(new byte[] { 0x09, (byte)0x83, (byte)0x8C, (byte)0xCF }); @@ -58,7 +57,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { @Override public void handlePacket(Packet packet) throws IOException, TimeoutException { - Ts3Logging.debug("Connecting: handling " + packet.getBody().getClass().getSimpleName()); + Ts3Debugging.debug("Connecting: handling " + packet.getBody().getClass().getSimpleName()); if (packet.getBody() instanceof PacketBody8Init1) { PacketBody8Init1 init1 = (PacketBody8Init1) packet.getBody(); @@ -102,7 +101,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { byte[] solution = x.modPow(BigInteger.valueOf(2L).pow(serverReplyStep3.getLevel()), n).toByteArray(); - Ts3Logging.debug(Ts3Logging.getHex(solution)); + Ts3Debugging.debug(Ts3Debugging.getHex(solution)); System.arraycopy( solution, Math.abs(solution.length - 64), @@ -155,19 +154,19 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { if (command.getName().equalsIgnoreCase("initivexpand2")) { // 3.2.2 initivexpand2 (Client <- Server) - Ts3Logging.debug("initivexpand2"); + Ts3Debugging.debug(command.build()); if (!command.get("ot").getValue().equals("1")) throw new IllegalArgumentException("ot constant != 1: " + command.get("ot").getValue()); byte[] license = Base64.getDecoder().decode(command.get("l").getValue()); - byte[] licsense_validation = command.get("tvd").getValue() == null ? + byte[] licsense_validation = !command.has("tvd") ? null : Base64.getDecoder().decode(command.get("tvd").getValue()); byte[] beta = Base64.getDecoder().decode(command.get("beta").getValue()); // beta byte[] omega = Base64.getDecoder().decode(command.get("omega").getValue()); // omega - byte[] proof = Base64.getDecoder().decode(command.get("proof").getValue()); // ecdh_sign(1) + byte[] proof = Base64.getDecoder().decode(command.get("proof").getValue()); Pair keyPair = Ts3Crypt.generateKeypair(); @@ -178,53 +177,69 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { ECPoint publicKey = Ts3Crypt.decodePublicKey(omega); if (!Ts3Crypt.verifySignature(publicKey, license, proof)) - throw new SecurityException("invalid proof signature: " + Ts3Logging.getHex(proof)); + throw new SecurityException("invalid proof signature: " + Ts3Debugging.getHex(proof)); - // generate proof for clientek - byte[] toSign = new byte[86]; - System.arraycopy(keyPair.getKey(), 0, toSign, 0, 32); - System.arraycopy(beta, 0, toSign, 32, 54); - byte[] sign = getClient().getIdentity().sign(toSign); + byte[] signature = Ts3Crypt.generateClientEkProof( + keyPair.getKey(), + beta, + getClient().getIdentity() + ); - Ts3Logging.debug("Created proof (clientek): " + Ts3Logging.getHex(sign)); + Ts3Debugging.debug("Sending clientek..."); - // Send clientek (telling them the shared secret) PacketBody2Command clientek = new PacketBody2Command(ProtocolRole.CLIENT); clientek.setText( new SimpleCommand( "clientek", ProtocolRole.CLIENT, new CommandSingleParameter("ek", Base64.getEncoder().encodeToString(keyPair.getKey())), - new CommandSingleParameter("proof", Base64.getEncoder().encodeToString(sign)) + new CommandSingleParameter("proof", Base64.getEncoder().encodeToString(signature)) ).build() ); + + Ts3Debugging.debug(clientek.getText()); + getClient().writePacket(clientek); getClient().setSecureParameters( Ts3Crypt.cryptoInit2(license, alphaBytes, omega, proof, beta, keyPair.getValue()) ); + Ts3Debugging.debug("Sending clientinit..."); + PacketBody2Command clientinit = new PacketBody2Command(ProtocolRole.CLIENT); clientinit.setText( new SimpleCommand( - "clientinit", ProtocolRole.CLIENT, + "clientinit", + ProtocolRole.CLIENT, new CommandSingleParameter("client_nickname", "test"), - new CommandSingleParameter("client_version", "3.0.19.3"), + new CommandSingleParameter("client_version", "3.1 [Build: 1471417187]"), new CommandSingleParameter("client_platform", "Windows"), - new CommandSingleParameter("client_version_sign", "a1OYzvM18mrmfUQBUgxYBxYz2DUU6y5k3/mEL6FurzU0y97Bd1FL7+PRpcHyPkg4R+kKAFZ1nhyzbgkGphDWDg=="), - new CommandSingleParameter("client_input_hardware", "true"), - new CommandSingleParameter("client_output_hardware", "false"), + new CommandSingleParameter( + "client_version_sign", + "gDEgQf/BiOQZdAheKccM1XWcMUj2OUQqt75oFuvF2c0MQMXyv88cZQdUuckKbcBRp7RpmLInto4PIgd7mPO7BQ==" + ), + 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_meta_data", ""), new CommandSingleParameter("client_default_token", ""), - new CommandSingleParameter("client_key_offset", Long.toString(getClient().getIdentity().getKeyOffset())), - new CommandSingleParameter("hwid", "87056c6e1268aaf5055abf8256415e0e,408978b6d98810cc03f0aa16a4c75600") + new CommandSingleParameter( + "client_key_offset", + Long.toString(getClient().getIdentity().getKeyOffset()) + ), + new CommandSingleParameter( + "hwid", + "+LyYqbDqOvJJpN5pdAbF8/v5kZ0=" + ) ).build() ); + Ts3Debugging.debug(clientinit.getText()); + getClient().writePacket(clientinit); } } diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/transformation/DefaultPacketTransformation.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/transformation/DefaultPacketTransformation.java index b1759d6..fe5e38d 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/transformation/DefaultPacketTransformation.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/transformation/DefaultPacketTransformation.java @@ -12,7 +12,7 @@ public class DefaultPacketTransformation extends PacketTransformation { {0x6D, 0x5C, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6C, 0x6C, 0x33, 0x32, 0x2E, 0x63, 0x70, 0x6C}; public DefaultPacketTransformation() { - super(key); + super(key, new byte[8]); } @Override diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/transformation/PacketTransformation.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/transformation/PacketTransformation.java index 9511b01..ff78856 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/transformation/PacketTransformation.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/transformation/PacketTransformation.java @@ -3,7 +3,7 @@ package com.github.manevolent.ts3j.protocol.packet.transformation; import com.github.manevolent.ts3j.protocol.Packet; import com.github.manevolent.ts3j.protocol.ProtocolRole; import com.github.manevolent.ts3j.protocol.header.PacketHeader; -import com.github.manevolent.ts3j.util.Ts3Logging; +import com.github.manevolent.ts3j.util.Ts3Crypt; import javafx.util.Pair; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.InvalidCipherTextException; @@ -14,48 +14,38 @@ import org.bouncycastle.crypto.params.KeyParameter; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; public class PacketTransformation { private static final int MAC_LEN = 8; - private static MessageDigest SHA256; - - { - try { - SHA256 = MessageDigest.getInstance("SHA256"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - - private final byte[] key; + private final byte[] ivStruct; + private final byte[] fakeMac; private final EAXBlockCipher cipher; - public PacketTransformation(byte[] key) { - this.key = key; + public PacketTransformation(byte[] ivStruct, byte[] fakeMac) { + this.ivStruct = ivStruct; this.cipher = new EAXBlockCipher(new AESEngine()); + this.fakeMac = fakeMac; + } + + public byte[] getFakeMac() { + return fakeMac; } public Pair computeParameters(PacketHeader header) { ByteBuffer temporaryByteBuffer = ByteBuffer - .allocate(key.length == 20 ? 26 : 70) + .allocate(ivStruct.length == 20 ? 26 : 70) .order(ByteOrder.BIG_ENDIAN); temporaryByteBuffer.put((byte) (header.getRole() == ProtocolRole.SERVER ? 0x30 : 0x31)); temporaryByteBuffer.put((byte) (header.getType().getIndex() & 0xFF)); - temporaryByteBuffer.put((byte) (header.getGeneration() & 0xFF)); + temporaryByteBuffer.putInt(header.getGeneration() & 0x000000FFFFFF); - if (key.length == 20) { - //temporary[6 - 26] = SIV[0 - 20] - temporaryByteBuffer.put(key, 0, 20); - } else { - //temporary[6 - 70] = SIV[0 - 64] - temporaryByteBuffer.put(key, 0, 64); - } + temporaryByteBuffer.put(ivStruct); - byte[] key_nonce = SHA256.digest(temporaryByteBuffer.array()); + byte[] arrayHash = temporaryByteBuffer.array(); + + byte[] key_nonce = Ts3Crypt.hash256(arrayHash); byte[] key = new byte[16]; byte[] nonce = new byte[16]; @@ -63,11 +53,11 @@ public class PacketTransformation { System.arraycopy(key_nonce, 0, key, 0, 16); System.arraycopy(key_nonce, 16, nonce, 0, 16); - // key[0] = key[0] xor ((PId & 0xFF00) >> 8) - // key[1] = key[1] xor ((PId & 0x00FF) >> 0) + // ivStruct[0] = ivStruct[0] xor ((PId & 0xFF00) >> 8) + // ivStruct[1] = ivStruct[1] xor ((PId & 0x00FF) >> 0) - key[0] = (byte) (key[0] ^ ((byte) (header.getType().getIndex() & 0xFF00) >> 8)); - key[1] = (byte) (key[0] ^ ((byte) (header.getType().getIndex() & 0x00FF) >> 0)); + key[0] = (byte) (key[0] ^ ((byte) (header.getPacketId() & 0xFF) >> 8) & 0xFF); + key[1] = (byte) (key[1] ^ ((byte) (header.getPacketId() & 0xFF) >> 0) & 0xFF); return new Pair<>(key, nonce); } @@ -120,8 +110,6 @@ public class PacketTransformation { // Rest of header outputBuffer.put(headerWithoutMac); - Ts3Logging.debug("WRITE HEADER: " + Ts3Logging.getHex(outputBuffer.array(), headerBuffer.limit())); - // Encrypted body outputBuffer.put(result, 0, len - MAC_LEN); @@ -146,7 +134,6 @@ public class PacketTransformation { int len; synchronized (cipher) { - cipher.init(false, cipherParameters); result = new byte[cipher.getOutputSize(dataLen + MAC_LEN)]; @@ -160,10 +147,8 @@ public class PacketTransformation { if (len != dataLen) throw new IllegalArgumentException(len + " != " + dataLen); - } - return result; } } diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java index b15b343..f87e793 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java @@ -20,7 +20,7 @@ import com.github.manevolent.ts3j.protocol.packet.transformation.DefaultPacketTr import com.github.manevolent.ts3j.protocol.packet.transformation.PacketTransformation; import com.github.manevolent.ts3j.protocol.socket.AbstractTeamspeakSocket; import com.github.manevolent.ts3j.util.Ts3Crypt; -import com.github.manevolent.ts3j.util.Ts3Logging; +import com.github.manevolent.ts3j.util.Ts3Debugging; import java.io.IOException; import java.net.DatagramPacket; @@ -32,6 +32,10 @@ import java.util.concurrent.*; public abstract class AbstractTeamspeakClientSocket extends AbstractTeamspeakSocket implements TeamspeakClientSocket { + private static final byte[] INIT1_MAC = new byte[] { + 0x54, 0x53, 0x33, 0x49, 0x4E, 0x49, 0x54, 0x31 + }; + private final Object connectionStateLock = new Object(); private final LinkedBlockingDeque readQueue = new LinkedBlockingDeque<>(65536); private final Map reassemblyQueue = new LinkedHashMap<>(); @@ -85,7 +89,7 @@ public abstract class AbstractTeamspeakClientSocket } public final void setSecureParameters(Ts3Crypt.SecureChannelParameters parameters) { - setTransformation(new PacketTransformation(parameters.getIvStruct())); + setTransformation(new PacketTransformation(parameters.getIvStruct(), parameters.getFakeSignature())); } protected abstract NetworkPacket readNetworkPacket() throws IOException; @@ -95,7 +99,7 @@ public abstract class AbstractTeamspeakClientSocket public void setState(ClientConnectionState state) throws IOException, TimeoutException { synchronized (connectionStateLock) { if (state != this.connectionState) { - Ts3Logging.debug("State changing: " + state.name()); + Ts3Debugging.debug("State changing: " + state.name()); boolean wasDisconnected = this.connectionState == ClientConnectionState.DISCONNECTED; @@ -110,7 +114,7 @@ public abstract class AbstractTeamspeakClientSocket if (state == ClientConnectionState.DISCONNECTED) clientOptions.clear(); - Ts3Logging.debug("State changed: " + state.name()); + Ts3Debugging.debug("State changed: " + state.name()); } } @@ -120,11 +124,11 @@ public abstract class AbstractTeamspeakClientSocket ((LocalClientHandler) handler).handleConnectionStateChanging(state); if (handler == null || handler.getClass() != state.getHandlerClass()) { - Ts3Logging.debug("Assigning " + state.getHandlerClass().getName() + " handler..."); + Ts3Debugging.debug("Assigning " + state.getHandlerClass().getName() + " handler..."); setHandler(state.createHandler(this)); - Ts3Logging.debug("Assigned " + state.getHandlerClass().getName() + " handler."); + Ts3Debugging.debug("Assigned " + state.getHandlerClass().getName() + " handler."); } } @@ -206,10 +210,6 @@ public abstract class AbstractTeamspeakClientSocket // Ensure type matches packet.getHeader().setType(packet.getBody().getType()); - // Make sure new protocol is set if it must be set - if (getState() == ClientConnectionState.CONNECTING) - packet.getHeader().setPacketFlag(HeaderFlag.NEW_PROTOCOL, true); - // Set header values packet.getBody().setHeaderValues(packet.getHeader()); @@ -226,6 +226,10 @@ public abstract class AbstractTeamspeakClientSocket packet.getHeader().setPacketId(packetId); } + if (packet.getHeader().getType() == PacketBodyType.COMMAND || + packet.getHeader().getType() == PacketBodyType.COMMAND_LOW) + packet.getHeader().setPacketFlag(HeaderFlag.NEW_PROTOCOL, true); + if (packet.getSize() > 500) { if (!packet.getHeader().getType().isSplittable()) throw new IllegalArgumentException("packet too large: " + packet.getSize()); @@ -272,10 +276,18 @@ public abstract class AbstractTeamspeakClientSocket // Flush to a buffer ByteBuffer outputBuffer; - if (packet.getBody().getType().canEncrypt() && - !packet.getHeader().getPacketFlag(HeaderFlag.UNENCRYPTED)) { + if (!packet.getHeader().getPacketFlag(HeaderFlag.UNENCRYPTED)) { + if (!packet.getHeader().getType().canEncrypt()) + throw new IllegalArgumentException("packet flagged as encrypted but this would be a violation"); + outputBuffer = getTransformation().encrypt(packet); } else { + if (packet.getHeader().getType() == PacketBodyType.INIT1) { + System.arraycopy(INIT1_MAC, 0, packet.getHeader().getMac(), 0, 8); + } else { + System.arraycopy(transformation.getFakeMac(), 0, packet.getHeader().getMac(), 0, 8); + } + outputBuffer = ByteBuffer.allocate(packet.getSize()); outputBuffer.order(ByteOrder.BIG_ENDIAN); @@ -304,7 +316,10 @@ public abstract class AbstractTeamspeakClientSocket else response = null; - Ts3Logging.debug("[PROTOCOL] WRITE " + networkPacket.getHeader().getType().name()); + Ts3Debugging.debug("[PROTOCOL] WRITE " + networkPacket.getHeader().getType().name()); + for (HeaderFlag flag : HeaderFlag.values()) + Ts3Debugging.debug(flag.name() + ": " + networkPacket.getHeader().getPacketFlag(flag)); + writeNetworkPacket(networkPacket); // If necessary, fulfill promise for the packet acknowledgement or throw TimeoutException @@ -343,12 +358,9 @@ public abstract class AbstractTeamspeakClientSocket boolean fragment = networkPacket.getHeader().getPacketFlag(HeaderFlag.FRAGMENTED); if (networkPacket.getHeader().getType().isSplittable()) { - if (fragment) - throw new IllegalArgumentException("packet is fragment, but not splittable"); - else { - fragment = reassemblyQueue.get(networkPacket.getHeader().getType()).isReassembling(); - } - } + fragment = fragment || reassemblyQueue.get(networkPacket.getHeader().getType()).isReassembling(); + } else if (fragment) + throw new IllegalArgumentException("packet is fragment, but not splittable"); boolean encrypted = !networkPacket.getHeader().getPacketFlag(HeaderFlag.UNENCRYPTED) && networkPacket.getHeader().getType().canEncrypt(); @@ -370,9 +382,14 @@ public abstract class AbstractTeamspeakClientSocket ); } + Ts3Debugging.debug("[NETWORK] READ " + + packet.getHeader().getType() + " BODY " + + Ts3Debugging.getHex(packetBuffer.array()) + ); + // Fragment handling if (fragment) { - Ts3Logging.debug("[PROTOCOL] READ " + networkPacket.getHeader().getType().name() + " fragment"); + Ts3Debugging.debug("[PROTOCOL] READ " + networkPacket.getHeader().getType().name() + " fragment"); packet.setBody( new PacketBodyFragment( @@ -381,7 +398,7 @@ public abstract class AbstractTeamspeakClientSocket ) ); } else { - Ts3Logging.debug("[PROTOCOL] READ " + networkPacket.getHeader().getType().name()); + Ts3Debugging.debug("[PROTOCOL] READ " + networkPacket.getHeader().getType().name()); } // Compression? @@ -408,6 +425,12 @@ public abstract class AbstractTeamspeakClientSocket } } + // Find if the packet must be reassembled + if (packet.getBody() instanceof PacketBodyFragment) { + packet = reassemblyQueue.get(packet.getHeader().getType()).put(packet); + if (packet == null) continue; + } + // Find if the packet is itself an acknowledgement switch (packet.getHeader().getType()) { case ACK: @@ -504,13 +527,13 @@ public abstract class AbstractTeamspeakClientSocket try { Packet packet = readQueue.take(); - Ts3Logging.debug("Processing " + packet + "..."); + Ts3Debugging.debug("Processing " + packet + "..."); getHandler().handlePacket(packet); - Ts3Logging.debug("Processed " + packet + "."); + Ts3Debugging.debug("Processed " + packet + "."); } catch (Exception e) { if (e instanceof InterruptedException) continue; - Ts3Logging.debug("Problem handling packet", e); + Ts3Debugging.debug("Problem handling packet", e); } } } @@ -525,7 +548,7 @@ public abstract class AbstractTeamspeakClientSocket } catch (Exception e) { if (e instanceof InterruptedException) continue; - Ts3Logging.debug("Problem reading packet", e); + Ts3Debugging.debug("Problem reading packet", e); } } } @@ -629,11 +652,9 @@ public abstract class AbstractTeamspeakClientSocket if (!(packet.getBody() instanceof PacketBodyFragment)) throw new IllegalArgumentException("packet fragment object is not representative of a fragment"); - boolean fragmentationEnded = oldState; - queue.put(packet.getHeader().getPacketId(), packet); - if (fragmentationEnded) + if (oldState) return reassemble(packet); return null; diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/LocalTeamspeakClientSocket.java b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/LocalTeamspeakClientSocket.java index c839105..01901c5 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/LocalTeamspeakClientSocket.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/LocalTeamspeakClientSocket.java @@ -4,7 +4,7 @@ import com.github.manevolent.ts3j.identity.LocalIdentity; import com.github.manevolent.ts3j.protocol.NetworkPacket; import com.github.manevolent.ts3j.protocol.client.ClientConnectionState; import com.github.manevolent.ts3j.protocol.header.PacketHeader; -import com.github.manevolent.ts3j.util.Ts3Logging; +import com.github.manevolent.ts3j.util.Ts3Debugging; import java.io.IOException; import java.net.DatagramPacket; @@ -36,7 +36,7 @@ public class LocalTeamspeakClientSocket extends AbstractTeamspeakClientSocket { protected NetworkPacket readNetworkPacket() throws IOException { socket.receive(packet); - Ts3Logging.debug("[NETWORK] READ len=" + packet.getLength() + " from " + packet.getSocketAddress()); + Ts3Debugging.debug("[NETWORK] READ len=" + packet.getLength() + " from " + packet.getSocketAddress()); PacketHeader header; @@ -61,10 +61,11 @@ public class LocalTeamspeakClientSocket extends AbstractTeamspeakClientSocket { @Override protected void writeNetworkPacket(NetworkPacket packet) throws IOException { - Ts3Logging.debug("[NETWORK] WRITE id=" + packet.getHeader().getPacketId() + " len=" + + Ts3Debugging.debug("[NETWORK] WRITE id=" + packet.getHeader().getPacketId() + " len=" + packet.getDatagram().getLength() + " to " + socket.getRemoteSocketAddress()); + Ts3Debugging.debug("[NETWORK] WRITE " + Ts3Debugging.getHex(packet.getBuffer().array())); socket.send(packet.getDatagram()); } @@ -89,7 +90,7 @@ public class LocalTeamspeakClientSocket extends AbstractTeamspeakClientSocket { public void connect(InetSocketAddress remote, String password, long timeout) throws IOException, TimeoutException { try { - Ts3Logging.debug("Connecting to " + remote + "..."); + Ts3Debugging.debug("Connecting to " + remote + "..."); ClientConnectionState connectionState = getState(); 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 0857246..c92d31a 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.identity.*; import com.github.manevolent.ts3j.license.License; import javafx.util.Pair; import org.bouncycastle.asn1.*; @@ -22,34 +23,50 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; +import java.security.*; import java.util.List; public final class Ts3Crypt { + static { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + } + + public static byte[] hash128(byte[] data) { + try { + return MessageDigest.getInstance("SHA1").digest(data); + } catch (GeneralSecurityException ex) { + throw new RuntimeException(ex); + } + } + + public static byte[] hash256(byte[] data) { + try { + return MessageDigest.getInstance("SHA256").digest(data); + } catch (GeneralSecurityException ex) { + throw new RuntimeException(ex); + } + } + + public static byte[] hash512(byte[] data) { + try { + return MessageDigest.getInstance("SHA512").digest(data); + } catch (GeneralSecurityException ex) { + throw new RuntimeException(ex); + } + } + public static Ts3Crypt.SecureChannelParameters cryptoInit2(byte[] license, byte[] alpha, byte[] omega, byte[] proof, byte[] beta, byte[] privateKey) { // 3.2.2.2 Parsing the license List licenses = License.readLicenses(ByteBuffer.wrap(license)); - Ts3Logging.debug("License.deriveKey: " + Ts3Logging.getHex(license)); byte[] key = License.deriveKey(licenses); - Ts3Logging.debug("License.deriveKey: " + Ts3Logging.getHex(key)); - - Ts3Logging.debug("Ts3Crypt.generateSharedSecret2: " + Ts3Logging.getHex(key) + " " + Ts3Logging.getHex(privateKey)); byte[] sharedSecret = Ts3Crypt.generateSharedSecret2(key, privateKey); - Ts3Logging.debug("Ts3Crypt.generateSharedSecret2: " + Ts3Logging.getHex(sharedSecret)); - Ts3Logging.debug("Ts3Crypt.SecureChannelParameters: " + Ts3Logging.getHex(alpha) + " " + Ts3Logging.getHex(beta) + - " " + Ts3Logging.getHex(sharedSecret)); Ts3Crypt.SecureChannelParameters parameters = Ts3Crypt.getSecureParameters(alpha, beta, sharedSecret); - Ts3Logging.debug("Computed fake fingerprint: " + Ts3Logging.getHex(parameters.getFakeSignature())); - Ts3Logging.debug("Computed IV struct: " + Ts3Logging.getHex(parameters.getIvStruct())); - return parameters; } @@ -62,6 +79,30 @@ public final class Ts3Crypt { outBuf[i + outOffs] = (byte)(a[i + aoffs] ^ b[i + boffs]); } + public static byte[] generateClientEkProof(byte[] key, byte[] beta, + LocalIdentity identity) { + return generateClientEkProof(key, beta, identity.getPrivateKey()); + } + + public static byte[] generateClientEkProof(byte[] key, byte[] beta, + BigInteger privateKey) { + // generate proof for clientek + byte[] data = new byte[86]; + System.arraycopy(key, 0, data, 0, 32); + System.arraycopy(beta, 0, data, 32, 54); + + return createSignature(privateKey, data); + } + + public static boolean verifyClientEkProof(byte[] key, byte[] beta, byte[] signature, ECPoint publicKey) { + // generate proof for clientek + byte[] data = new byte[86]; + System.arraycopy(key, 0, data, 0, 32); + System.arraycopy(beta, 0, data, 32, 54); + + return verifySignature(publicKey, data, signature); + } + public static SecureChannelParameters getSecureParameters(byte[] alpha, byte[] beta, byte[] sharedKey) { if (beta.length != 10 && beta.length != 54) throw new IllegalArgumentException("invalid beta size (" + beta.length + ")"); @@ -72,13 +113,7 @@ public final class Ts3Crypt { xor(sharedKey, 0, alpha, 0, alpha.length, ivStruct, 0); xor(sharedKey, 10, beta, 0, beta.length, ivStruct, 10); - byte[] buffer; - - try { - buffer = MessageDigest.getInstance("SHA1").digest(ivStruct); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } + byte[] buffer = hash128(ivStruct); System.arraycopy(buffer, 0, fakeSignature, 0, 8); @@ -191,6 +226,9 @@ public final class Ts3Crypt { byte[] publicKey = new byte[32]; GroupOperations.ge_p3_tobytes(publicKey, 0, A); + Ts3Debugging.debug(publicKey); + Ts3Debugging.debug(privateKey); + return new Pair<>(publicKey, privateKey); } diff --git a/src/main/java/com/github/manevolent/ts3j/util/Ts3Logging.java b/src/main/java/com/github/manevolent/ts3j/util/Ts3Debugging.java similarity index 65% rename from src/main/java/com/github/manevolent/ts3j/util/Ts3Logging.java rename to src/main/java/com/github/manevolent/ts3j/util/Ts3Debugging.java index 76dbba6..dde9d68 100644 --- a/src/main/java/com/github/manevolent/ts3j/util/Ts3Logging.java +++ b/src/main/java/com/github/manevolent/ts3j/util/Ts3Debugging.java @@ -1,6 +1,16 @@ package com.github.manevolent.ts3j.util; -public final class Ts3Logging { +public final class Ts3Debugging { + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } + private static final String HEXES = "0123456789ABCDEF"; public static String getHex(byte[] raw) { return getHex(raw, raw.length); @@ -22,6 +32,10 @@ public final class Ts3Logging { System.err.println("[DEBUG] " + message.toString()); } + public static void debug(byte[] message) { + debug(getHex(message)); + } + public static void debug(String message, Throwable ex) { System.err.println("[DEBUG] " + message); ex.printStackTrace(); diff --git a/src/test/java/com/github/manevolent/ts3j/ClientEkTest.java b/src/test/java/com/github/manevolent/ts3j/ClientEkTest.java new file mode 100644 index 0000000..5bae4c2 --- /dev/null +++ b/src/test/java/com/github/manevolent/ts3j/ClientEkTest.java @@ -0,0 +1,43 @@ +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 java.math.BigInteger; +import java.util.Base64; + +public class ClientEkTest extends TestCase { + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } + + public static void main(String[] args) throws Exception { + new ClientEkTest().testParser(); + } + + public void testParser() throws Exception { + LocalIdentity keyPair = LocalIdentity.load( + Ts3Crypt.decodePublicKey(Base64.getDecoder() + .decode("MEsDAgcAAgEgAiBpPRbTliVt9KxtIz8saYdwcnNgcwaKLbKYSpDNO87u9gIgSWWPKcSJ9P6VZKJfRdpWwcfMdJv+NA9/hXUtz1uwRVI=")), + new BigInteger(Base64.getDecoder().decode("Tj6YXM3qyRv8n25L2pH+OEJnRUl4auQf8+znjYrOmWU=")), + 2294, + 2295 + ); + + byte[] key = hexStringToByteArray("5763FA00EBC38DCE3953AFCACD8CFF2209208D15BB7D9B699D61A5675EDA9370"); + byte[] beta = hexStringToByteArray("EA581E7CBADED7C47B9CBA3D3EEFB33A49B61ED0F0B45DF50D900C12F551B5F6B0EFEE3F593FB225184C78C06DB2D688669B70ABAC6D"); + + byte[] signature = Ts3Crypt.generateClientEkProof(key, beta, keyPair.getPrivateKey()); + + assertEquals(true, Ts3Crypt.verifyClientEkProof(key, beta, signature, keyPair.getPublicKey())); + } + +} diff --git a/src/test/java/com/github/manevolent/ts3j/CryptoInit2Test.java b/src/test/java/com/github/manevolent/ts3j/CryptoInit2Test.java new file mode 100644 index 0000000..0b3d58e --- /dev/null +++ b/src/test/java/com/github/manevolent/ts3j/CryptoInit2Test.java @@ -0,0 +1,30 @@ +package com.github.manevolent.ts3j; + +import com.github.manevolent.ts3j.util.Ts3Crypt; +import com.github.manevolent.ts3j.util.Ts3Debugging; +import junit.framework.TestCase; + +import static com.github.manevolent.ts3j.util.Ts3Debugging.hexStringToByteArray; + +public class CryptoInit2Test extends TestCase { + + public static void main(String[] args) throws Exception { + new CryptoInit2Test().testParser(); + } + + public void testParser() throws Exception { + byte[] licenseBytes = hexStringToByteArray("0100358541498A24ACD30157918B8F50955C0DAE970AB65372CBE407415FCF3E029B02084D15E00AA793600700000020416E6F6E796D6F7573000047D9E4DC25AA2E90ACD4DB5FA61C8FED369B346D84C2CA2FCCCA86F73AFEF092200A77C8810A787141"); + byte[] alpha = hexStringToByteArray("9500A5DB3B50ACECAB81"); + byte[] omega = hexStringToByteArray("304B03020700020120022025D13CF0C5A17A467961B04418E015941D6C6558DA6AEC79CFFDDA1DEF8AB55202200F3B3F0D3A4F1C5C02AD1F9A3A5114AC58E89905792FBB83AD4FFE7F82ACAADA"); + byte[] proof = hexStringToByteArray("3046022100F4B921DDB2C9737D9AB100BA251D11216DF1601C62AF17150E6FC29A2C6EEC0F022100FC8948E595BBD895A2FDFE0EB22A5241655BD47591F8D2733035CF76A72245AD"); + byte[] beta = hexStringToByteArray("EAFFC9A8BC996B25C8AA700264E99E372ECCDEB1C121D6EC0F4D49FB46CEEBA4E3C724B3070FD70CB03D7BC08129205690ECE228CA7C"); + byte[] privateKey = hexStringToByteArray("102E591ABA4508129E812FF3437E2DDD3CA1F1EC341117CA3514CC347A7C2A77"); + + Ts3Crypt.SecureChannelParameters result = + Ts3Crypt.cryptoInit2(licenseBytes, alpha, omega, proof, beta, privateKey); + + assertEquals(Ts3Debugging.getHex(result.getIvStruct()), "E4082A92F71C96A947452F5582EF2879B2051ED2D3F2C6B0643CF5A266EE6B5180573C2F5F3F1C4AC579188366F16AE0EADC3AAF860805D8F2A831E9E49F4513"); + assertEquals(Ts3Debugging.getHex(result.getFakeSignature()), "54F2B4D661E0F9AB"); + } + +} diff --git a/src/test/java/com/github/manevolent/ts3j/EncodingTest.java b/src/test/java/com/github/manevolent/ts3j/EncodingTest.java new file mode 100644 index 0000000..1472e95 --- /dev/null +++ b/src/test/java/com/github/manevolent/ts3j/EncodingTest.java @@ -0,0 +1,33 @@ +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 java.math.BigInteger; +import java.nio.charset.Charset; +import java.util.Base64; + +public class EncodingTest extends TestCase { + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } + + public static void main(String[] args) throws Exception { + new EncodingTest().testParser(); + } + + public void testParser() throws Exception { + byte[] encoded = "aW<#CD$)(A@$)2398i4mc20xA#W$)Aw3m403w49xw4xamw4c;w9re __//\\uc".getBytes(Charset.forName("UTF8")); + String base64 = Base64.getEncoder().encodeToString(encoded); + assertEquals(base64, "YVc8I0NEJCkoQUAkKTIzOThpNG1jMjB4QSNXJClBdzNtNDAzdzQ5eHc0eGFtdzRjO3c5cmUgIF9fLy9cdWM="); + } + +} diff --git a/src/test/java/com/github/manevolent/ts3j/EncryptionTest.java b/src/test/java/com/github/manevolent/ts3j/EncryptionTest.java new file mode 100644 index 0000000..12a283f --- /dev/null +++ b/src/test/java/com/github/manevolent/ts3j/EncryptionTest.java @@ -0,0 +1,58 @@ +package com.github.manevolent.ts3j; + +import com.github.manevolent.ts3j.protocol.Packet; +import com.github.manevolent.ts3j.protocol.ProtocolRole; +import com.github.manevolent.ts3j.protocol.header.ClientPacketHeader; +import com.github.manevolent.ts3j.protocol.header.HeaderFlag; +import com.github.manevolent.ts3j.protocol.packet.PacketBody2Command; +import com.github.manevolent.ts3j.protocol.packet.PacketBodyType; +import com.github.manevolent.ts3j.protocol.packet.transformation.PacketTransformation; +import com.github.manevolent.ts3j.util.Ts3Crypt; +import com.github.manevolent.ts3j.util.Ts3Debugging; +import javafx.util.Pair; +import junit.framework.TestCase; + +import java.math.BigInteger; +import java.util.Base64; + +public class EncryptionTest extends TestCase { + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } + + public static void main(String[] args) throws Exception { + new EncryptionTest().testParser(); + } + + public void testParser() throws Exception { + byte[] fakeFingerprint = Base64.getDecoder().decode("XuhegJMKpa0="); + byte[] ivStruct = Base64.getDecoder().decode("/rn6nR71hV8eFl+15WO68fRU8pOCBw3t0FmcG5c7WNxIkeZ1NtaWTVMBde0cdU5tTKwOl8sE6gpHjnCEF4hhDw=="); + + PacketTransformation transformation = new PacketTransformation(ivStruct, fakeFingerprint); + + Packet packet = new Packet(ProtocolRole.CLIENT); + ClientPacketHeader header = new ClientPacketHeader(); + header.setType(PacketBodyType.COMMAND); + header.setPacketFlag(HeaderFlag.NEW_PROTOCOL, true); + header.setPacketId(1); + packet.setHeader(header); + + Pair params = transformation.computeParameters(header); + + assertEquals(Base64.getEncoder().encodeToString(params.getKey()), "BF+lO776+e45u+qYAOHihg=="); + assertEquals(Base64.getEncoder().encodeToString(params.getValue()), "1IVcTMuizpDHjQgn2yGCgg=="); + + packet.setBody(new PacketBody2Command(ProtocolRole.CLIENT, "clientinit client_nickname=TestClient client_version=3.1.8\\s[Build:\\s1516614607] client_platform=Windows client_input_hardware=1 client_output_hardware=1 client_default_channel= client_default_channel_password= client_server_password=QL0AFWMIX8NRZTKeof9cXsvbvu8= client_meta_data= client_version_sign=gDEgQf\\/BiOQZdAheKccM1XWcMUj2OUQqt75oFuvF2c0MQMXyv88cZQdUuckKbcBRp7RpmLInto4PIgd7mPO7BQ== client_key_offset=6199 client_nickname_phonetic= client_default_token= hwid=+LyYqbDqOvJJpN5pdAbF8\\/v5kZ0=")); + + byte[] packetBytes = transformation.encrypt(packet).array(); + + Ts3Debugging.debug(Base64.getEncoder().encode(packetBytes)); + } + +} diff --git a/src/test/java/com/github/manevolent/ts3j/LicenseDerivationTest.java b/src/test/java/com/github/manevolent/ts3j/LicenseDerivationTest.java new file mode 100644 index 0000000..6e3b219 --- /dev/null +++ b/src/test/java/com/github/manevolent/ts3j/LicenseDerivationTest.java @@ -0,0 +1,31 @@ +package com.github.manevolent.ts3j; + +import com.github.manevolent.ts3j.license.License; +import com.github.manevolent.ts3j.util.Ts3Debugging; +import junit.framework.TestCase; + +import java.nio.ByteBuffer; + +public class LicenseDerivationTest extends TestCase { + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } + + public static void main(String[] args) throws Exception { + new LicenseDerivationTest().testParser(); + } + + public void testParser() throws Exception { + byte[] licenseBytes = hexStringToByteArray("0100358541498A24ACD30157918B8F50955C0DAE970AB65372CBE407415FCF3E029B02084D15E00AA793600700000020416E6F6E796D6F75730000AA43CD339C6E30E4E848F2FF031B93146B2E4925B28B33A3E8054D2101D06FF7200A776C5E0A78151E"); + byte[] derrivedKey = License.deriveKey(License.readLicenses(ByteBuffer.wrap(licenseBytes))); + assertEquals(Ts3Debugging.getHex(derrivedKey), "B4EBE337EA2AA9F37C3E2FF8889D24B08B7439E654513B8BF342D1D174895C30"); + + } + +} diff --git a/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java b/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java index 9c3bc33..4c8f339 100644 --- a/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java +++ b/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java @@ -3,25 +3,34 @@ package com.github.manevolent.ts3j; import com.github.manevolent.ts3j.identity.LocalIdentity; import com.github.manevolent.ts3j.protocol.client.ClientConnectionState; import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket; +import com.github.manevolent.ts3j.util.Ts3Crypt; import junit.framework.TestCase; +import java.math.BigInteger; import java.net.InetSocketAddress; +import java.util.Base64; public class ServerConnectionTest extends TestCase { - public ServerConnectionTest(String name) { - super(name); + public ServerConnectionTest() { + super("ServerConnectionTest"); } public void testParser() throws Exception { try { - LocalIdentity localIdentity = LocalIdentity.generateNew(8); + LocalIdentity localIdentity = LocalIdentity.load( + Ts3Crypt.decodePublicKey(Base64.getDecoder() + .decode("MEsDAgcAAgEgAiBpPRbTliVt9KxtIz8saYdwcnNgcwaKLbKYSpDNO87u9gIgSWWPKcSJ9P6VZKJfRdpWwcfMdJv+NA9/hXUtz1uwRVI=")), + new BigInteger(Base64.getDecoder().decode("Tj6YXM3qyRv8n25L2pH+OEJnRUl4auQf8+znjYrOmWU=")), + 2294, + 2295 + ); LocalTeamspeakClientSocket client = new LocalTeamspeakClientSocket(); client.setIdentity(localIdentity); client.connect(new InetSocketAddress( - "ts.teamlixo.net", + "voice.teamspeak.com", 9987), null, 10000L @@ -32,4 +41,8 @@ public class ServerConnectionTest extends TestCase { ex.printStackTrace(); } } + + public static void main(String[] args) throws Exception { + new ServerConnectionTest().testParser(); + } }