Support clientinitivexpand and not just expand2 for all TS3 server support (incl. TeaSpeak)

This commit is contained in:
Manevolent
2018-09-17 13:24:09 -04:00
parent d271b7a358
commit 8d74195d42
4 changed files with 184 additions and 46 deletions
@@ -17,6 +17,7 @@ import org.bouncycastle.math.ec.ECPoint;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Base64; import java.util.Base64;
import java.util.Random; import java.util.Random;
@@ -58,13 +59,18 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
if (packet.getBody() instanceof PacketBody8Init1) { if (packet.getBody() instanceof PacketBody8Init1) {
PacketBody8Init1 init1 = (PacketBody8Init1) packet.getBody(); PacketBody8Init1 init1 = (PacketBody8Init1) packet.getBody();
Ts3Debugging.debug("Handle Init1 step " + init1.getStep().getNumber());
switch (init1.getStep().getNumber()) { switch (init1.getStep().getNumber()) {
case 1: case 1:
PacketBody8Init1.Step1 serverReplyStep1 = (PacketBody8Init1.Step1)init1.getStep(); PacketBody8Init1.Step1 serverReplyStep1 = (PacketBody8Init1.Step1)init1.getStep();
// Check nonce. It's received backwards, so walk backwards over the array received // Check nonce. It's received backwards, so walk backwards over the array received
for (int i = 0; i < 4; i ++) { for (int i = 0; i < 4; i ++) {
if (randomBytes[3 - i] != serverReplyStep1.getA0reversed()[i]) if (randomBytes[3 - i] != serverReplyStep1.getA0reversed()[i]) {
throw new IllegalArgumentException("invalid server nonce"); Ts3Debugging.debug("[WARNING] random byte mismatch!");
break;
}
} }
// Build response // Build response
@@ -138,6 +144,9 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
sendInit1(response4); sendInit1(response4);
break; break;
case 127:
onAssigned();
break;
default: default:
throw new IllegalArgumentException("unexpected Init1 server step: " + init1.getStep().getNumber()); throw new IllegalArgumentException("unexpected Init1 server step: " + init1.getStep().getNumber());
@@ -145,7 +154,20 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
} else if (packet.getBody() instanceof PacketBody2Command) { } else if (packet.getBody() instanceof PacketBody2Command) {
SingleCommand command = ((PacketBody2Command) packet.getBody()).parse().simplifyOne(); SingleCommand command = ((PacketBody2Command) packet.getBody()).parse().simplifyOne();
if (command.getName().equalsIgnoreCase("initivexpand2")) { Ts3Debugging.debug(command.build());
if (command.getName().equalsIgnoreCase("initivexpand")) {
byte[] alpha = Base64.getDecoder().decode(command.get("alpha").getValue()); // alpha
byte[] beta = Base64.getDecoder().decode(command.get("beta").getValue()); // beta
byte[] omega = Base64.getDecoder().decode(command.get("omega").getValue()); // omega
getClient().setSecureParameters(Ts3Crypt.cryptoInit(alpha, beta, omega, getClient().getIdentity()));
sendClientInit();
getClient().setCommandProcessor(getClient());
getClient().setState(ClientConnectionState.RETRIEVING_DATA);
} else if (command.getName().equalsIgnoreCase("initivexpand2")) {
// 3.2.2 initivexpand2 (Client <- Server) // 3.2.2 initivexpand2 (Client <- Server)
if (!command.get("ot").getValue().equals("1")) if (!command.get("ot").getValue().equals("1"))
@@ -190,6 +212,21 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
alphaBytes = null; alphaBytes = null;
sendClientInit();
getClient().setCommandProcessor(getClient());
getClient().setState(ClientConnectionState.RETRIEVING_DATA);
} else if (command.getName().equals("error")) {
getClient().setState(ClientConnectionState.DISCONNECTED);
throw new IOException(command.get("msg").getValue());
} else {
throw new IOException("Unknown Init command: " + command.getName());
}
}
}
private void sendClientInit() throws IOException, TimeoutException {
Command clientinit = new SingleCommand( Command clientinit = new SingleCommand(
"clientinit", "clientinit",
ProtocolRole.CLIENT, ProtocolRole.CLIENT,
@@ -232,14 +269,5 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
Ts3Debugging.debug(clientinit.build()); Ts3Debugging.debug(clientinit.build());
getClient().writePacket(new PacketBody2Command(ProtocolRole.CLIENT, clientinit)); getClient().writePacket(new PacketBody2Command(ProtocolRole.CLIENT, clientinit));
getClient().setCommandProcessor(getClient());
getClient().setState(ClientConnectionState.RETRIEVING_DATA);
} else if (command.getName().equals("error")) {
getClient().setState(ClientConnectionState.DISCONNECTED);
throw new IOException(command.get("msg").getValue());
}
}
} }
} }
@@ -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.api.Message;
import com.github.manevolent.ts3j.identity.*; import com.github.manevolent.ts3j.identity.*;
import com.github.manevolent.ts3j.license.License; import com.github.manevolent.ts3j.license.License;
import com.github.manevolent.ts3j.util.Pair; import com.github.manevolent.ts3j.util.Pair;
@@ -24,6 +25,7 @@ import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.*; import java.security.*;
import java.util.Base64;
import java.util.List; import java.util.List;
public final class Ts3Crypt { public final class Ts3Crypt {
@@ -31,6 +33,16 @@ public final class Ts3Crypt {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
} }
public static byte[] hash128(byte[] data, int offs, int len) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA1");
digest.update(data, offs, len);
return digest.digest();
} catch (GeneralSecurityException ex) {
throw new RuntimeException(ex);
}
}
public static byte[] hash128(byte[] data) { public static byte[] hash128(byte[] data) {
try { try {
return MessageDigest.getInstance("SHA1").digest(data); return MessageDigest.getInstance("SHA1").digest(data);
@@ -55,6 +67,52 @@ public final class Ts3Crypt {
} }
} }
public static Ts3Crypt.SecureChannelParameters cryptoInit(byte[] alpha, byte[] beta, byte[] omega,
LocalIdentity identity)
throws IOException {
if (beta.length != 10 && beta.length != 54)
throw new IOException("Invalid beta size: " + beta.length + " != (10,54)");
// getSharedSecret
byte[] sharedKey = getSharedSecret(omega, identity);
// Splamy's setSharedSecret
byte[] ivStruct = new byte[10 + beta.length];
//XorBinary(sharedKey, alpha, alpha.Length, ivStruct);
xor(sharedKey, 0, alpha, 0, alpha.length, ivStruct, 0);
//XorBinary(sharedKey.Slice(10), beta, beta.Length, ivStruct.AsSpan(10));
xor(sharedKey, 10, beta, 0, beta.length, ivStruct, 10);
byte[] buffer2 = hash128(ivStruct, 0, ivStruct.length);
byte[] fakeSignature = new byte[8];
System.arraycopy(buffer2, 0, fakeSignature, 0, 8);
return new SecureChannelParameters(fakeSignature, ivStruct);
}
public static byte[] getSharedSecret(byte[] omega, LocalIdentity identity) {
ECPoint publicKeyPoint = Ts3Crypt.decodePublicKey(omega);
ECPoint p = publicKeyPoint.multiply(identity.getPrivateKey()).normalize();
byte[] keyArr = p.getAffineXCoord().toBigInteger().toByteArray();
byte[] sharedSecret;
if (keyArr.length == 32)
sharedSecret = Ts3Crypt.hash128(keyArr);
else if (keyArr.length > 32)
sharedSecret = Ts3Crypt.hash128(keyArr, keyArr.length - 32, 32);
else {
byte[] keyArrExt = new byte[32];
System.arraycopy(keyArr, 0, keyArrExt, 32 - keyArr.length, keyArr.length);
sharedSecret = Ts3Crypt.hash128(keyArrExt);
}
return sharedSecret;
}
public static Ts3Crypt.SecureChannelParameters cryptoInit2(byte[] license, byte[] alpha, public static Ts3Crypt.SecureChannelParameters cryptoInit2(byte[] license, byte[] alpha,
byte[] beta, byte[] privateKey) { byte[] beta, byte[] privateKey) {
// 3.2.2.2 Parsing the license // 3.2.2.2 Parsing the license
@@ -0,0 +1,52 @@
package com.github.manevolent.ts3j;
import com.github.manevolent.ts3j.identity.LocalIdentity;
import com.github.manevolent.ts3j.util.Ts3Crypt;
import com.github.manevolent.ts3j.util.Ts3Debugging;
import junit.framework.TestCase;
import org.bouncycastle.util.encoders.Base64;
import java.math.BigInteger;
import static com.github.manevolent.ts3j.util.Ts3Debugging.hexStringToByteArray;
/**
* The assertion check values SHOULD be retrieved from another known working codebase (i.e. Splamy's C# codebase)
* for the given input license string that is in no way associated with this codebase.
*/
public class CryptoInitTest extends TestCase {
public static void main(String[] args) throws Exception {
new CryptoInitTest().testParser();
}
public void testParser() throws Exception {
Ts3Debugging.setEnabled(true);
LocalIdentity identity = LocalIdentity.load(
new BigInteger(java.util.Base64.getDecoder().decode("Tj6YXM3qyRv8n25L2pH+OEJnRUl4auQf8+znjYrOmWU="))
);
byte[] alpha = Base64.decode("p0SD9rgLfAzMvQ==");
byte[] beta = Base64.decode("gWw6y53Sft6JFA==");
byte[] omega = Base64.decode(
"MEwDAgcAAgEgAiAzliV+Hdt3q4PJ3G7+sA+lJdyz4pemWxwbZ" +
"4VwUQtf3gIhAOIK7NxDEA4c54o8fu0mPjRShjWxQI6vW5ExD44JlPXb"
);
assertEquals("wd/BG5pGG5zx05V7XDhtf+zhL38=", Base64.toBase64String(Ts3Crypt.getSharedSecret(omega, identity)));
Ts3Crypt.SecureChannelParameters parameters = Ts3Crypt.cryptoInit(
alpha,
beta,
omega,
identity
);
assertEquals("ZptC7SJNZ5A9bhQXZvPwrZI/pms=", Base64.toBase64String(parameters.getIvStruct()));
assertEquals("ZbeTrqtllww=", Base64.toBase64String(parameters.getFakeSignature()));
}
}
@@ -39,7 +39,7 @@ public class ServerConnectionTest {
try { try {
client.connect( client.connect(
"teamlixo.net", new InetSocketAddress("178.32.109.167", 11079),
null, null,
10000L 10000L
); );