Still not getting the last packet to work.
This commit is contained in:
@@ -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.*;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<License> readLicenses(ByteBuffer buffer) {
|
||||
List<License> 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_;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+41
-26
@@ -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<byte[], byte[]> 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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+20
-35
@@ -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<byte[], byte[]> 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;
|
||||
}
|
||||
}
|
||||
|
||||
+49
-28
@@ -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<Packet> readQueue = new LinkedBlockingDeque<>(65536);
|
||||
private final Map<PacketBodyType, Reassembly> 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;
|
||||
|
||||
+5
-4
@@ -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();
|
||||
|
||||
|
||||
@@ -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<License> 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);
|
||||
}
|
||||
|
||||
|
||||
+15
-1
@@ -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();
|
||||
Reference in New Issue
Block a user