Still not getting the last packet to work.

This commit is contained in:
Manevolent
2018-07-26 22:07:05 -04:00
parent a0009693e2
commit 5cdedba51c
21 changed files with 448 additions and 181 deletions
@@ -1065,6 +1065,6 @@ public class GroupOperations
FieldOperations.fe_mul(x, h.X, recip); FieldOperations.fe_mul(x, h.X, recip);
FieldOperations.fe_mul(y, h.Y, recip); FieldOperations.fe_mul(y, h.Y, recip);
FieldOperations.fe_tobytes(s, offset, y); FieldOperations.fe_tobytes(s, offset, y);
s[offset + 31] ^= (byte) (FieldOperations.fe_isnegative(x) << 7); //s[offset + 31] ^= (byte) (FieldOperations.fe_isnegative(x) << 7);
} }
} }
@@ -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.CommandParameter;
import com.github.manevolent.ts3j.command.part.CommandSingleParameter; import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
import com.github.manevolent.ts3j.protocol.ProtocolRole; import com.github.manevolent.ts3j.protocol.ProtocolRole;
import com.github.manevolent.ts3j.util.Ts3Logging;
import com.github.manevolent.ts3j.util.Ts3String; import com.github.manevolent.ts3j.util.Ts3String;
import java.util.*; import java.util.*;
@@ -1,16 +1,10 @@
package com.github.manevolent.ts3j.identity; package com.github.manevolent.ts3j.identity;
import com.github.manevolent.ts3j.util.Ts3Crypt; import com.github.manevolent.ts3j.util.Ts3Crypt;
import com.github.manevolent.ts3j.util.Ts3Logging; import com.github.manevolent.ts3j.util.Ts3Debugging;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.math.ec.ECPoint;
import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.DigestException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Base64; import java.util.Base64;
@@ -83,7 +77,7 @@ public class Identity {
if (curr > best) 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; keyOffset = lastCheckedKeyOffset;
best = curr; best = curr;
} }
@@ -15,10 +15,6 @@ import java.math.BigInteger;
import java.security.*; import java.security.*;
public class LocalIdentity extends Identity { public class LocalIdentity extends Identity {
static {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}
private final BigInteger privateKey; private final BigInteger privateKey;
public LocalIdentity(ECPoint publicKey, BigInteger privateKey) { public LocalIdentity(ECPoint publicKey, BigInteger privateKey) {
@@ -61,4 +57,8 @@ public class LocalIdentity extends Identity {
public byte[] sign(byte[] data) { public byte[] sign(byte[] data) {
return Ts3Crypt.createSignature(privateKey, data); return Ts3Crypt.createSignature(privateKey, data);
} }
public BigInteger getPrivateKey() {
return privateKey;
}
} }
@@ -1,13 +1,10 @@
package com.github.manevolent.ts3j.license; package com.github.manevolent.ts3j.license;
import Punisher.NaCl.Internal.Ed25519Ref10.*; 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.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -137,19 +134,19 @@ public class License {
while (buffer.remaining() > 0) { while (buffer.remaining() > 0) {
int c = buffer.get(); int c = buffer.get();
switch (c) { switch (c) {
case 0: case 0x00:
break; byte[] stringBytes = new byte[len];
buffer.position(buffer.position() - len);
buffer.get(stringBytes);
return new String(stringBytes, Charset.forName("UTF8"));
default: default:
len++; len++;
break; break;
} }
} }
byte[] stringBytes = new byte[len]; throw new IllegalStateException("string not null terminated");
buffer.position(buffer.position() - len);
buffer.get(stringBytes);
return new String(stringBytes, Charset.forName("UTF8"));
} }
public static void writeNullTerminatedLicenseString(ByteBuffer buffer, String s) { public static void writeNullTerminatedLicenseString(ByteBuffer buffer, String s) {
@@ -160,13 +157,28 @@ public class License {
public static List<License> readLicenses(ByteBuffer buffer) { public static List<License> readLicenses(ByteBuffer buffer) {
List<License> licenses = new ArrayList<>(); List<License> licenses = new ArrayList<>();
while (buffer.remaining() > 0) {
byte licenseVersion = buffer.get(); byte licenseVersion = buffer.get();
if (licenseVersion != 0x01) if (licenseVersion != 0x01)
throw new IllegalArgumentException("invalid license version"); throw new IllegalArgumentException("invalid license version");
while (buffer.remaining() > 0) {
int start = buffer.position();
License license = new License(); License license = new License();
license.read(buffer); 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); licenses.add(license);
} }
@@ -174,40 +186,15 @@ public class License {
} }
public byte[] getHash() { 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; return computedHash;
} }
public byte[] deriveKey(byte[] parent) { public byte[] deriveKey(byte[] parent) {
// ScalarOperations.sc_clamp(Hash); // ScalarOperations.sc_clamp(Hash);
Ts3Logging.debug("ScalarOperations.sc_clamp: " + Ts3Logging.getHex(getHash()));
ScalarOperations.sc_clamp(getHash(), 0); ScalarOperations.sc_clamp(getHash(), 0);
Ts3Logging.debug("ScalarOperations.sc_clamp: " + Ts3Logging.getHex(getHash()));
// GroupOperations.ge_frombytes_negate_vartime(out var pubkey, Key); // GroupOperations.ge_frombytes_negate_vartime(out var pubkey, Key);
GroupElementP3 pubkey = new GroupElementP3(); 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(pubkey, getPublicKey(), 0);
// GroupOperations.ge_frombytes_negate_vartime(out var parkey, parent); // GroupOperations.ge_frombytes_negate_vartime(out var parkey, parent);
@@ -232,7 +219,7 @@ public class License {
byte[] final_ = new byte[32]; byte[] final_ = new byte[32];
GroupOperations.ge_p3_tobytes(final_, 0, r2); GroupOperations.ge_p3_tobytes(final_, 0, r2);
final_[1] ^= 0x80; //final_[1] ^= 0x80;
return final_; return final_;
} }
@@ -1,7 +1,6 @@
package com.github.manevolent.ts3j.license; package com.github.manevolent.ts3j.license;
import com.github.manevolent.ts3j.enums.LicenseType; import com.github.manevolent.ts3j.enums.LicenseType;
import com.github.manevolent.ts3j.util.Ts3Logging;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@@ -1,5 +1,6 @@
package com.github.manevolent.ts3j.protocol.packet; 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.command.SimpleCommand;
import com.github.manevolent.ts3j.protocol.ProtocolRole; import com.github.manevolent.ts3j.protocol.ProtocolRole;
@@ -13,6 +14,16 @@ public class PacketBody2Command extends PacketBody {
super(PacketBodyType.COMMAND, role); 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() { public String getText() {
return text; return text;
} }
@@ -27,12 +38,12 @@ public class PacketBody2Command extends PacketBody {
@Override @Override
public void read(ByteBuffer buffer) { public void read(ByteBuffer buffer) {
text = Charset.forName("ASCII").decode(buffer).toString(); text = Charset.forName("UTF8").decode(buffer).toString();
} }
@Override @Override
public void write(ByteBuffer buffer) { public void write(ByteBuffer buffer) {
buffer.put(text.getBytes(Charset.forName("ASCII"))); buffer.put(text.getBytes(Charset.forName("UTF8")));
} }
@Override @Override
@@ -10,10 +10,6 @@ import java.nio.ByteOrder;
import static com.github.manevolent.ts3j.protocol.packet.PacketBodyType.INIT1; import static com.github.manevolent.ts3j.protocol.packet.PacketBodyType.INIT1;
public class PacketBody8Init1 extends PacketBody { 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 byte[] version = new byte[4]; // 4 bytes
private Step step; // step instance private Step step; // step instance
@@ -25,7 +21,6 @@ public class PacketBody8Init1 extends PacketBody {
@Override @Override
public void setHeaderValues(PacketHeader header) { public void setHeaderValues(PacketHeader header) {
header.setMac(LOW_LEVEL_MAC);
header.setPacketFlag(HeaderFlag.UNENCRYPTED, true); header.setPacketFlag(HeaderFlag.UNENCRYPTED, true);
header.setPacketId((short)101); header.setPacketId((short)101);
} }
@@ -4,12 +4,11 @@ import com.github.manevolent.ts3j.command.SimpleCommand;
import com.github.manevolent.ts3j.command.part.CommandSingleParameter; import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
import com.github.manevolent.ts3j.protocol.Packet; import com.github.manevolent.ts3j.protocol.Packet;
import com.github.manevolent.ts3j.protocol.ProtocolRole; 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.PacketBody2Command;
import com.github.manevolent.ts3j.protocol.packet.PacketBody8Init1; import com.github.manevolent.ts3j.protocol.packet.PacketBody8Init1;
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket; import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
import com.github.manevolent.ts3j.util.Ts3Crypt; 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 javafx.util.Pair;
import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.math.ec.ECPoint;
@@ -46,7 +45,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
} }
private void sendInit1(PacketBody8Init1 packet) throws IOException, TimeoutException { private void sendInit1(PacketBody8Init1 packet) throws IOException, TimeoutException {
Ts3Logging.debug("Connecting: sending " + packet.getClass().getSimpleName() Ts3Debugging.debug("Connecting: sending " + packet.getClass().getSimpleName()
+ ":" + ":"
+ packet.getStep().getClass().getSimpleName() + "..." + packet.getStep().getClass().getSimpleName() + "..."
); );
@@ -58,7 +57,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
@Override @Override
public void handlePacket(Packet packet) throws IOException, TimeoutException { 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) { if (packet.getBody() instanceof PacketBody8Init1) {
PacketBody8Init1 init1 = (PacketBody8Init1) packet.getBody(); PacketBody8Init1 init1 = (PacketBody8Init1) packet.getBody();
@@ -102,7 +101,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
byte[] solution = byte[] solution =
x.modPow(BigInteger.valueOf(2L).pow(serverReplyStep3.getLevel()), n).toByteArray(); x.modPow(BigInteger.valueOf(2L).pow(serverReplyStep3.getLevel()), n).toByteArray();
Ts3Logging.debug(Ts3Logging.getHex(solution)); Ts3Debugging.debug(Ts3Debugging.getHex(solution));
System.arraycopy( System.arraycopy(
solution, Math.abs(solution.length - 64), solution, Math.abs(solution.length - 64),
@@ -155,19 +154,19 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
if (command.getName().equalsIgnoreCase("initivexpand2")) { if (command.getName().equalsIgnoreCase("initivexpand2")) {
// 3.2.2 initivexpand2 (Client <- Server) // 3.2.2 initivexpand2 (Client <- Server)
Ts3Logging.debug("initivexpand2"); Ts3Debugging.debug(command.build());
if (!command.get("ot").getValue().equals("1")) if (!command.get("ot").getValue().equals("1"))
throw new IllegalArgumentException("ot constant != 1: " + command.get("ot").getValue()); throw new IllegalArgumentException("ot constant != 1: " + command.get("ot").getValue());
byte[] license = Base64.getDecoder().decode(command.get("l").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 : null :
Base64.getDecoder().decode(command.get("tvd").getValue()); Base64.getDecoder().decode(command.get("tvd").getValue());
byte[] beta = Base64.getDecoder().decode(command.get("beta").getValue()); // beta byte[] beta = Base64.getDecoder().decode(command.get("beta").getValue()); // beta
byte[] omega = Base64.getDecoder().decode(command.get("omega").getValue()); // omega 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<byte[], byte[]> keyPair = Ts3Crypt.generateKeypair(); Pair<byte[], byte[]> keyPair = Ts3Crypt.generateKeypair();
@@ -178,53 +177,69 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
ECPoint publicKey = Ts3Crypt.decodePublicKey(omega); ECPoint publicKey = Ts3Crypt.decodePublicKey(omega);
if (!Ts3Crypt.verifySignature(publicKey, license, proof)) 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[] signature = Ts3Crypt.generateClientEkProof(
byte[] toSign = new byte[86]; keyPair.getKey(),
System.arraycopy(keyPair.getKey(), 0, toSign, 0, 32); beta,
System.arraycopy(beta, 0, toSign, 32, 54); getClient().getIdentity()
byte[] sign = getClient().getIdentity().sign(toSign); );
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); PacketBody2Command clientek = new PacketBody2Command(ProtocolRole.CLIENT);
clientek.setText( clientek.setText(
new SimpleCommand( new SimpleCommand(
"clientek", ProtocolRole.CLIENT, "clientek", ProtocolRole.CLIENT,
new CommandSingleParameter("ek", Base64.getEncoder().encodeToString(keyPair.getKey())), new CommandSingleParameter("ek", Base64.getEncoder().encodeToString(keyPair.getKey())),
new CommandSingleParameter("proof", Base64.getEncoder().encodeToString(sign)) new CommandSingleParameter("proof", Base64.getEncoder().encodeToString(signature))
).build() ).build()
); );
Ts3Debugging.debug(clientek.getText());
getClient().writePacket(clientek); getClient().writePacket(clientek);
getClient().setSecureParameters( getClient().setSecureParameters(
Ts3Crypt.cryptoInit2(license, alphaBytes, omega, proof, beta, keyPair.getValue()) Ts3Crypt.cryptoInit2(license, alphaBytes, omega, proof, beta, keyPair.getValue())
); );
Ts3Debugging.debug("Sending clientinit...");
PacketBody2Command clientinit = new PacketBody2Command(ProtocolRole.CLIENT); PacketBody2Command clientinit = new PacketBody2Command(ProtocolRole.CLIENT);
clientinit.setText( clientinit.setText(
new SimpleCommand( new SimpleCommand(
"clientinit", ProtocolRole.CLIENT, "clientinit",
ProtocolRole.CLIENT,
new CommandSingleParameter("client_nickname", "test"), 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_platform", "Windows"),
new CommandSingleParameter("client_version_sign", "a1OYzvM18mrmfUQBUgxYBxYz2DUU6y5k3/mEL6FurzU0y97Bd1FL7+PRpcHyPkg4R+kKAFZ1nhyzbgkGphDWDg=="), new CommandSingleParameter(
new CommandSingleParameter("client_input_hardware", "true"), "client_version_sign",
new CommandSingleParameter("client_output_hardware", "false"), "gDEgQf/BiOQZdAheKccM1XWcMUj2OUQqt75oFuvF2c0MQMXyv88cZQdUuckKbcBRp7RpmLInto4PIgd7mPO7BQ=="
),
new CommandSingleParameter("client_input_hardware", "1"),
new CommandSingleParameter("client_output_hardware", "1"),
new CommandSingleParameter("client_default_channel", ""), new CommandSingleParameter("client_default_channel", ""),
new CommandSingleParameter("client_default_channel_password", ""), new CommandSingleParameter("client_default_channel_password", ""),
new CommandSingleParameter("client_server_password", ""), new CommandSingleParameter("client_server_password", ""),
new CommandSingleParameter("client_nickname_phonetic", "test"), new CommandSingleParameter("client_nickname_phonetic", "test"),
new CommandSingleParameter("client_meta_data", ""), new CommandSingleParameter("client_meta_data", ""),
new CommandSingleParameter("client_default_token", ""), new CommandSingleParameter("client_default_token", ""),
new CommandSingleParameter("client_key_offset", Long.toString(getClient().getIdentity().getKeyOffset())), new CommandSingleParameter(
new CommandSingleParameter("hwid", "87056c6e1268aaf5055abf8256415e0e,408978b6d98810cc03f0aa16a4c75600") "client_key_offset",
Long.toString(getClient().getIdentity().getKeyOffset())
),
new CommandSingleParameter(
"hwid",
"+LyYqbDqOvJJpN5pdAbF8/v5kZ0="
)
).build() ).build()
); );
Ts3Debugging.debug(clientinit.getText());
getClient().writePacket(clientinit); getClient().writePacket(clientinit);
} }
} }
@@ -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}; {0x6D, 0x5C, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6C, 0x6C, 0x33, 0x32, 0x2E, 0x63, 0x70, 0x6C};
public DefaultPacketTransformation() { public DefaultPacketTransformation() {
super(key); super(key, new byte[8]);
} }
@Override @Override
@@ -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.Packet;
import com.github.manevolent.ts3j.protocol.ProtocolRole; import com.github.manevolent.ts3j.protocol.ProtocolRole;
import com.github.manevolent.ts3j.protocol.header.PacketHeader; 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 javafx.util.Pair;
import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.InvalidCipherTextException;
@@ -14,48 +14,38 @@ import org.bouncycastle.crypto.params.KeyParameter;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class PacketTransformation { public class PacketTransformation {
private static final int MAC_LEN = 8; private static final int MAC_LEN = 8;
private static MessageDigest SHA256; private final byte[] ivStruct;
private final byte[] fakeMac;
{
try {
SHA256 = MessageDigest.getInstance("SHA256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
private final byte[] key;
private final EAXBlockCipher cipher; private final EAXBlockCipher cipher;
public PacketTransformation(byte[] key) { public PacketTransformation(byte[] ivStruct, byte[] fakeMac) {
this.key = key; this.ivStruct = ivStruct;
this.cipher = new EAXBlockCipher(new AESEngine()); this.cipher = new EAXBlockCipher(new AESEngine());
this.fakeMac = fakeMac;
}
public byte[] getFakeMac() {
return fakeMac;
} }
public Pair<byte[], byte[]> computeParameters(PacketHeader header) { public Pair<byte[], byte[]> computeParameters(PacketHeader header) {
ByteBuffer temporaryByteBuffer = ByteBuffer ByteBuffer temporaryByteBuffer = ByteBuffer
.allocate(key.length == 20 ? 26 : 70) .allocate(ivStruct.length == 20 ? 26 : 70)
.order(ByteOrder.BIG_ENDIAN); .order(ByteOrder.BIG_ENDIAN);
temporaryByteBuffer.put((byte) (header.getRole() == ProtocolRole.SERVER ? 0x30 : 0x31)); temporaryByteBuffer.put((byte) (header.getRole() == ProtocolRole.SERVER ? 0x30 : 0x31));
temporaryByteBuffer.put((byte) (header.getType().getIndex() & 0xFF)); temporaryByteBuffer.put((byte) (header.getType().getIndex() & 0xFF));
temporaryByteBuffer.put((byte) (header.getGeneration() & 0xFF)); temporaryByteBuffer.putInt(header.getGeneration() & 0x000000FFFFFF);
if (key.length == 20) { temporaryByteBuffer.put(ivStruct);
//temporary[6 - 26] = SIV[0 - 20]
temporaryByteBuffer.put(key, 0, 20);
} else {
//temporary[6 - 70] = SIV[0 - 64]
temporaryByteBuffer.put(key, 0, 64);
}
byte[] key_nonce = SHA256.digest(temporaryByteBuffer.array()); byte[] arrayHash = temporaryByteBuffer.array();
byte[] key_nonce = Ts3Crypt.hash256(arrayHash);
byte[] key = new byte[16]; byte[] key = new byte[16];
byte[] nonce = 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, 0, key, 0, 16);
System.arraycopy(key_nonce, 16, nonce, 0, 16); System.arraycopy(key_nonce, 16, nonce, 0, 16);
// key[0] = key[0] xor ((PId & 0xFF00) >> 8) // ivStruct[0] = ivStruct[0] xor ((PId & 0xFF00) >> 8)
// key[1] = key[1] xor ((PId & 0x00FF) >> 0) // ivStruct[1] = ivStruct[1] xor ((PId & 0x00FF) >> 0)
key[0] = (byte) (key[0] ^ ((byte) (header.getType().getIndex() & 0xFF00) >> 8)); key[0] = (byte) (key[0] ^ ((byte) (header.getPacketId() & 0xFF) >> 8) & 0xFF);
key[1] = (byte) (key[0] ^ ((byte) (header.getType().getIndex() & 0x00FF) >> 0)); key[1] = (byte) (key[1] ^ ((byte) (header.getPacketId() & 0xFF) >> 0) & 0xFF);
return new Pair<>(key, nonce); return new Pair<>(key, nonce);
} }
@@ -120,8 +110,6 @@ public class PacketTransformation {
// Rest of header // Rest of header
outputBuffer.put(headerWithoutMac); outputBuffer.put(headerWithoutMac);
Ts3Logging.debug("WRITE HEADER: " + Ts3Logging.getHex(outputBuffer.array(), headerBuffer.limit()));
// Encrypted body // Encrypted body
outputBuffer.put(result, 0, len - MAC_LEN); outputBuffer.put(result, 0, len - MAC_LEN);
@@ -146,7 +134,6 @@ public class PacketTransformation {
int len; int len;
synchronized (cipher) { synchronized (cipher) {
cipher.init(false, cipherParameters); cipher.init(false, cipherParameters);
result = new byte[cipher.getOutputSize(dataLen + MAC_LEN)]; result = new byte[cipher.getOutputSize(dataLen + MAC_LEN)];
@@ -160,10 +147,8 @@ public class PacketTransformation {
if (len != dataLen) if (len != dataLen)
throw new IllegalArgumentException(len + " != " + dataLen); throw new IllegalArgumentException(len + " != " + dataLen);
} }
return result; return result;
} }
} }
@@ -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.packet.transformation.PacketTransformation;
import com.github.manevolent.ts3j.protocol.socket.AbstractTeamspeakSocket; import com.github.manevolent.ts3j.protocol.socket.AbstractTeamspeakSocket;
import com.github.manevolent.ts3j.util.Ts3Crypt; 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.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
@@ -32,6 +32,10 @@ import java.util.concurrent.*;
public abstract class AbstractTeamspeakClientSocket public abstract class AbstractTeamspeakClientSocket
extends AbstractTeamspeakSocket extends AbstractTeamspeakSocket
implements TeamspeakClientSocket { 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 Object connectionStateLock = new Object();
private final LinkedBlockingDeque<Packet> readQueue = new LinkedBlockingDeque<>(65536); private final LinkedBlockingDeque<Packet> readQueue = new LinkedBlockingDeque<>(65536);
private final Map<PacketBodyType, Reassembly> reassemblyQueue = new LinkedHashMap<>(); private final Map<PacketBodyType, Reassembly> reassemblyQueue = new LinkedHashMap<>();
@@ -85,7 +89,7 @@ public abstract class AbstractTeamspeakClientSocket
} }
public final void setSecureParameters(Ts3Crypt.SecureChannelParameters parameters) { 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; protected abstract NetworkPacket readNetworkPacket() throws IOException;
@@ -95,7 +99,7 @@ public abstract class AbstractTeamspeakClientSocket
public void setState(ClientConnectionState state) throws IOException, TimeoutException { public void setState(ClientConnectionState state) throws IOException, TimeoutException {
synchronized (connectionStateLock) { synchronized (connectionStateLock) {
if (state != this.connectionState) { if (state != this.connectionState) {
Ts3Logging.debug("State changing: " + state.name()); Ts3Debugging.debug("State changing: " + state.name());
boolean wasDisconnected = this.connectionState == ClientConnectionState.DISCONNECTED; boolean wasDisconnected = this.connectionState == ClientConnectionState.DISCONNECTED;
@@ -110,7 +114,7 @@ public abstract class AbstractTeamspeakClientSocket
if (state == ClientConnectionState.DISCONNECTED) if (state == ClientConnectionState.DISCONNECTED)
clientOptions.clear(); 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); ((LocalClientHandler) handler).handleConnectionStateChanging(state);
if (handler == null || handler.getClass() != state.getHandlerClass()) { 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)); 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 // Ensure type matches
packet.getHeader().setType(packet.getBody().getType()); 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 // Set header values
packet.getBody().setHeaderValues(packet.getHeader()); packet.getBody().setHeaderValues(packet.getHeader());
@@ -226,6 +226,10 @@ public abstract class AbstractTeamspeakClientSocket
packet.getHeader().setPacketId(packetId); 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.getSize() > 500) {
if (!packet.getHeader().getType().isSplittable()) if (!packet.getHeader().getType().isSplittable())
throw new IllegalArgumentException("packet too large: " + packet.getSize()); throw new IllegalArgumentException("packet too large: " + packet.getSize());
@@ -272,10 +276,18 @@ public abstract class AbstractTeamspeakClientSocket
// Flush to a buffer // Flush to a buffer
ByteBuffer outputBuffer; ByteBuffer outputBuffer;
if (packet.getBody().getType().canEncrypt() && if (!packet.getHeader().getPacketFlag(HeaderFlag.UNENCRYPTED)) {
!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); outputBuffer = getTransformation().encrypt(packet);
} else { } 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 = ByteBuffer.allocate(packet.getSize());
outputBuffer.order(ByteOrder.BIG_ENDIAN); outputBuffer.order(ByteOrder.BIG_ENDIAN);
@@ -304,7 +316,10 @@ public abstract class AbstractTeamspeakClientSocket
else else
response = null; 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); writeNetworkPacket(networkPacket);
// If necessary, fulfill promise for the packet acknowledgement or throw TimeoutException // 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); boolean fragment = networkPacket.getHeader().getPacketFlag(HeaderFlag.FRAGMENTED);
if (networkPacket.getHeader().getType().isSplittable()) { if (networkPacket.getHeader().getType().isSplittable()) {
if (fragment) fragment = fragment || reassemblyQueue.get(networkPacket.getHeader().getType()).isReassembling();
} else if (fragment)
throw new IllegalArgumentException("packet is fragment, but not splittable"); throw new IllegalArgumentException("packet is fragment, but not splittable");
else {
fragment = reassemblyQueue.get(networkPacket.getHeader().getType()).isReassembling();
}
}
boolean encrypted = !networkPacket.getHeader().getPacketFlag(HeaderFlag.UNENCRYPTED) && boolean encrypted = !networkPacket.getHeader().getPacketFlag(HeaderFlag.UNENCRYPTED) &&
networkPacket.getHeader().getType().canEncrypt(); 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 // Fragment handling
if (fragment) { if (fragment) {
Ts3Logging.debug("[PROTOCOL] READ " + networkPacket.getHeader().getType().name() + " fragment"); Ts3Debugging.debug("[PROTOCOL] READ " + networkPacket.getHeader().getType().name() + " fragment");
packet.setBody( packet.setBody(
new PacketBodyFragment( new PacketBodyFragment(
@@ -381,7 +398,7 @@ public abstract class AbstractTeamspeakClientSocket
) )
); );
} else { } else {
Ts3Logging.debug("[PROTOCOL] READ " + networkPacket.getHeader().getType().name()); Ts3Debugging.debug("[PROTOCOL] READ " + networkPacket.getHeader().getType().name());
} }
// Compression? // 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 // Find if the packet is itself an acknowledgement
switch (packet.getHeader().getType()) { switch (packet.getHeader().getType()) {
case ACK: case ACK:
@@ -504,13 +527,13 @@ public abstract class AbstractTeamspeakClientSocket
try { try {
Packet packet = readQueue.take(); Packet packet = readQueue.take();
Ts3Logging.debug("Processing " + packet + "..."); Ts3Debugging.debug("Processing " + packet + "...");
getHandler().handlePacket(packet); getHandler().handlePacket(packet);
Ts3Logging.debug("Processed " + packet + "."); Ts3Debugging.debug("Processed " + packet + ".");
} catch (Exception e) { } catch (Exception e) {
if (e instanceof InterruptedException) continue; 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) { } catch (Exception e) {
if (e instanceof InterruptedException) continue; 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)) if (!(packet.getBody() instanceof PacketBodyFragment))
throw new IllegalArgumentException("packet fragment object is not representative of a fragment"); throw new IllegalArgumentException("packet fragment object is not representative of a fragment");
boolean fragmentationEnded = oldState;
queue.put(packet.getHeader().getPacketId(), packet); queue.put(packet.getHeader().getPacketId(), packet);
if (fragmentationEnded) if (oldState)
return reassemble(packet); return reassemble(packet);
return null; return null;
@@ -4,7 +4,7 @@ import com.github.manevolent.ts3j.identity.LocalIdentity;
import com.github.manevolent.ts3j.protocol.NetworkPacket; import com.github.manevolent.ts3j.protocol.NetworkPacket;
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState; import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
import com.github.manevolent.ts3j.protocol.header.PacketHeader; 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.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
@@ -36,7 +36,7 @@ public class LocalTeamspeakClientSocket extends AbstractTeamspeakClientSocket {
protected NetworkPacket readNetworkPacket() throws IOException { protected NetworkPacket readNetworkPacket() throws IOException {
socket.receive(packet); 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; PacketHeader header;
@@ -61,10 +61,11 @@ public class LocalTeamspeakClientSocket extends AbstractTeamspeakClientSocket {
@Override @Override
protected void writeNetworkPacket(NetworkPacket packet) throws IOException { 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() packet.getDatagram().getLength()
+ " to " + + " to " +
socket.getRemoteSocketAddress()); socket.getRemoteSocketAddress());
Ts3Debugging.debug("[NETWORK] WRITE " + Ts3Debugging.getHex(packet.getBuffer().array()));
socket.send(packet.getDatagram()); socket.send(packet.getDatagram());
} }
@@ -89,7 +90,7 @@ public class LocalTeamspeakClientSocket extends AbstractTeamspeakClientSocket {
public void connect(InetSocketAddress remote, String password, long timeout) public void connect(InetSocketAddress remote, String password, long timeout)
throws IOException, TimeoutException { throws IOException, TimeoutException {
try { try {
Ts3Logging.debug("Connecting to " + remote + "..."); Ts3Debugging.debug("Connecting to " + remote + "...");
ClientConnectionState connectionState = getState(); ClientConnectionState connectionState = getState();
@@ -5,6 +5,7 @@ import Punisher.NaCl.Internal.Ed25519Ref10.GroupElementP3;
import Punisher.NaCl.Internal.Ed25519Ref10.GroupOperations; import Punisher.NaCl.Internal.Ed25519Ref10.GroupOperations;
import Punisher.NaCl.Internal.Ed25519Ref10.ScalarOperations; import Punisher.NaCl.Internal.Ed25519Ref10.ScalarOperations;
import Punisher.NaCl.Internal.Sha512; import Punisher.NaCl.Internal.Sha512;
import com.github.manevolent.ts3j.identity.*;
import com.github.manevolent.ts3j.license.License; import com.github.manevolent.ts3j.license.License;
import javafx.util.Pair; import javafx.util.Pair;
import org.bouncycastle.asn1.*; import org.bouncycastle.asn1.*;
@@ -22,34 +23,50 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.MessageDigest; import java.security.*;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.List; import java.util.List;
public final class Ts3Crypt { 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, public static Ts3Crypt.SecureChannelParameters cryptoInit2(byte[] license, byte[] alpha,
byte[] omega, byte[] proof, byte[] omega, byte[] proof,
byte[] beta, byte[] privateKey) { byte[] beta, byte[] privateKey) {
// 3.2.2.2 Parsing the license // 3.2.2.2 Parsing the license
List<License> licenses = License.readLicenses(ByteBuffer.wrap(license)); List<License> licenses = License.readLicenses(ByteBuffer.wrap(license));
Ts3Logging.debug("License.deriveKey: " + Ts3Logging.getHex(license));
byte[] key = License.deriveKey(licenses); 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); 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.SecureChannelParameters parameters =
Ts3Crypt.getSecureParameters(alpha, beta, sharedSecret); 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; return parameters;
} }
@@ -62,6 +79,30 @@ public final class Ts3Crypt {
outBuf[i + outOffs] = (byte)(a[i + aoffs] ^ b[i + boffs]); 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) { public static SecureChannelParameters getSecureParameters(byte[] alpha, byte[] beta, byte[] sharedKey) {
if (beta.length != 10 && beta.length != 54) if (beta.length != 10 && beta.length != 54)
throw new IllegalArgumentException("invalid beta size (" + beta.length + ")"); 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, 0, alpha, 0, alpha.length, ivStruct, 0);
xor(sharedKey, 10, beta, 0, beta.length, ivStruct, 10); xor(sharedKey, 10, beta, 0, beta.length, ivStruct, 10);
byte[] buffer; byte[] buffer = hash128(ivStruct);
try {
buffer = MessageDigest.getInstance("SHA1").digest(ivStruct);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
System.arraycopy(buffer, 0, fakeSignature, 0, 8); System.arraycopy(buffer, 0, fakeSignature, 0, 8);
@@ -191,6 +226,9 @@ public final class Ts3Crypt {
byte[] publicKey = new byte[32]; byte[] publicKey = new byte[32];
GroupOperations.ge_p3_tobytes(publicKey, 0, A); GroupOperations.ge_p3_tobytes(publicKey, 0, A);
Ts3Debugging.debug(publicKey);
Ts3Debugging.debug(privateKey);
return new Pair<>(publicKey, privateKey); return new Pair<>(publicKey, privateKey);
} }
@@ -1,6 +1,16 @@
package com.github.manevolent.ts3j.util; 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"; private static final String HEXES = "0123456789ABCDEF";
public static String getHex(byte[] raw) { public static String getHex(byte[] raw) {
return getHex(raw, raw.length); return getHex(raw, raw.length);
@@ -22,6 +32,10 @@ public final class Ts3Logging {
System.err.println("[DEBUG] " + message.toString()); System.err.println("[DEBUG] " + message.toString());
} }
public static void debug(byte[] message) {
debug(getHex(message));
}
public static void debug(String message, Throwable ex) { public static void debug(String message, Throwable ex) {
System.err.println("[DEBUG] " + message); System.err.println("[DEBUG] " + message);
ex.printStackTrace(); ex.printStackTrace();
@@ -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()));
}
}
@@ -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");
}
}
@@ -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=");
}
}
@@ -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<byte[], byte[]> 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));
}
}
@@ -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");
}
}
@@ -3,25 +3,34 @@ package com.github.manevolent.ts3j;
import com.github.manevolent.ts3j.identity.LocalIdentity; import com.github.manevolent.ts3j.identity.LocalIdentity;
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState; import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket; import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
import com.github.manevolent.ts3j.util.Ts3Crypt;
import junit.framework.TestCase; import junit.framework.TestCase;
import java.math.BigInteger;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Base64;
public class ServerConnectionTest extends TestCase { public class ServerConnectionTest extends TestCase {
public ServerConnectionTest(String name) { public ServerConnectionTest() {
super(name); super("ServerConnectionTest");
} }
public void testParser() throws Exception { public void testParser() throws Exception {
try { 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(); LocalTeamspeakClientSocket client = new LocalTeamspeakClientSocket();
client.setIdentity(localIdentity); client.setIdentity(localIdentity);
client.connect(new InetSocketAddress( client.connect(new InetSocketAddress(
"ts.teamlixo.net", "voice.teamspeak.com",
9987), 9987),
null, null,
10000L 10000L
@@ -32,4 +41,8 @@ public class ServerConnectionTest extends TestCase {
ex.printStackTrace(); ex.printStackTrace();
} }
} }
public static void main(String[] args) throws Exception {
new ServerConnectionTest().testParser();
}
} }