diff --git a/src/main/java/Punisher/NaCl/Internal/Ed25519Ref10/GroupOperations.java b/src/main/java/Punisher/NaCl/Internal/Ed25519Ref10/GroupOperations.java index 26393a9..b89275c 100644 --- a/src/main/java/Punisher/NaCl/Internal/Ed25519Ref10/GroupOperations.java +++ b/src/main/java/Punisher/NaCl/Internal/Ed25519Ref10/GroupOperations.java @@ -1058,26 +1058,14 @@ public class GroupOperations public static void ge_p3_tobytes(byte[] s, int offset, GroupElementP3 h) { FieldElement recip = new FieldElement(); - FieldElement x = new FieldElement(); - FieldElement y = new FieldElement(); + FieldElement h1 = new FieldElement(); + FieldElement h2 = new FieldElement(); FieldOperations.fe_invert(recip, h.Z); - FieldOperations.fe_mul(x, h.X, recip); - FieldOperations.fe_mul(y, h.Y, recip); - FieldOperations.fe_tobytes(s, offset, y); - } + FieldOperations.fe_mul(h1, h.X, recip); + FieldOperations.fe_mul(h2, h.Y, recip); + FieldOperations.fe_tobytes(s, offset, h2); - public static void ge_p3_tobytes_negate(byte[] s, int offset, GroupElementP3 h) - { - FieldElement recip = new FieldElement(); - FieldElement x = new FieldElement(); - FieldElement y = new FieldElement(); - - FieldOperations.fe_invert(recip, h.Z); - FieldOperations.fe_mul(x, h.X, recip); - FieldOperations.fe_mul(y, h.Y, recip); - FieldOperations.fe_tobytes(s, offset, y); - - s[offset + 31] ^= (byte) (FieldOperations.fe_isnegative(x) << 7); + s[offset + 31] ^= (byte) (FieldOperations.fe_isnegative(h1) << 7); } } diff --git a/src/main/java/com/github/manevolent/ts3j/identity/Identity.java b/src/main/java/com/github/manevolent/ts3j/identity/Identity.java index 6f49140..0d4ab9a 100644 --- a/src/main/java/com/github/manevolent/ts3j/identity/Identity.java +++ b/src/main/java/com/github/manevolent/ts3j/identity/Identity.java @@ -28,7 +28,6 @@ public class Identity { public Identity(ECPoint publicKey) { this.publicKey = publicKey; - this.publicKeyString = Base64.getEncoder().encodeToString(Ts3Crypt.encodePublicKey(publicKey)); this.fingerprint = generateFingerprint(); } @@ -98,7 +97,7 @@ public class Identity { int numLen = 0; do { - numBuffer[numLen] = (byte)((offset % 10) & 0xFF); + numBuffer[numLen] = (byte)(48 + (offset % 10)); offset /= 10; numLen ++; } while (offset > 0); @@ -132,7 +131,9 @@ public class Identity { private String generateFingerprint() { synchronized (sha1) { - return Base64.getEncoder().encodeToString(sha1.digest(Ts3Crypt.encodePublicKey(publicKey))); + return Base64.getEncoder().encodeToString( + sha1.digest(getPublicKeyString().getBytes(Charset.forName("ASCII"))) + ); } } } diff --git a/src/main/java/com/github/manevolent/ts3j/identity/LocalIdentity.java b/src/main/java/com/github/manevolent/ts3j/identity/LocalIdentity.java index 31c5ac9..1ca481b 100644 --- a/src/main/java/com/github/manevolent/ts3j/identity/LocalIdentity.java +++ b/src/main/java/com/github/manevolent/ts3j/identity/LocalIdentity.java @@ -24,15 +24,12 @@ public class LocalIdentity extends Identity { } public static LocalIdentity load(ECPoint publicKey, - BigInteger privateKey, - long keyOffset, - long lastCheckedKeyOffset) { - LocalIdentity localIdentity = new LocalIdentity(publicKey, privateKey); + BigInteger privateKey) { + return new LocalIdentity(publicKey, privateKey); + } - localIdentity.setKeyOffset(keyOffset); - localIdentity.setLastCheckedKeyOffset(lastCheckedKeyOffset); - - return localIdentity; + public static LocalIdentity load(BigInteger privateKey) { + return new LocalIdentity(Ts3Crypt.generatePublicKeyFromPrivateKey(privateKey), privateKey); } public static LocalIdentity generateNew(int securityLevel) throws GeneralSecurityException { @@ -48,7 +45,7 @@ public class LocalIdentity extends Identity { ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) keyPair.getPrivate(); ECPublicKeyParameters publicKey = (ECPublicKeyParameters) keyPair.getPublic(); - LocalIdentity localIdentity = load(publicKey.getQ().normalize(), privateKey.getD(), 0, 0); + LocalIdentity localIdentity = load(publicKey.getQ().normalize(), privateKey.getD()); localIdentity.improveSecurity(securityLevel); return localIdentity; diff --git a/src/main/java/com/github/manevolent/ts3j/license/License.java b/src/main/java/com/github/manevolent/ts3j/license/License.java index d2498e5..0f7a41d 100644 --- a/src/main/java/com/github/manevolent/ts3j/license/License.java +++ b/src/main/java/com/github/manevolent/ts3j/license/License.java @@ -218,6 +218,7 @@ public class License { byte[] final_ = new byte[32]; GroupOperations.ge_p3_tobytes(final_, 0, r2); + final_[31] ^= 0x80; return final_; } diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnecting.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnecting.java index 4463a43..26279d7 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnecting.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnecting.java @@ -4,6 +4,7 @@ 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; @@ -45,11 +46,6 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { } private void sendInit1(PacketBody8Init1 packet) throws IOException, TimeoutException { - Ts3Debugging.debug("Connecting: sending " + packet.getClass().getSimpleName() - + ":" - + packet.getStep().getClass().getSimpleName() + "..." - ); - packet.setVersion(new byte[] { 0x09, (byte)0x83, (byte)0x8C, (byte)0xCF }); getClient().writePacket(packet); @@ -57,8 +53,6 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { @Override public void handlePacket(Packet packet) throws IOException, TimeoutException { - Ts3Debugging.debug("Connecting: handling " + packet.getBody().getClass().getSimpleName()); - if (packet.getBody() instanceof PacketBody8Init1) { PacketBody8Init1 init1 = (PacketBody8Init1) packet.getBody(); @@ -101,8 +95,6 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { byte[] solution = x.modPow(BigInteger.valueOf(2L).pow(serverReplyStep3.getLevel()), n).toByteArray(); - Ts3Debugging.debug(Ts3Debugging.getHex(solution)); - System.arraycopy( solution, Math.abs(solution.length - 64), y, solution.length < 64 ? 64 - solution.length : 0, @@ -187,8 +179,6 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { getClient().getIdentity() ); - Ts3Debugging.debug("Sending clientek..."); - getClient().sendCommand( new SimpleCommand( "clientek", ProtocolRole.CLIENT, @@ -199,36 +189,52 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { getClient().setSecureParameters(Ts3Crypt.cryptoInit2(license, alphaBytes, beta, keyPair.getValue())); - Ts3Debugging.debug("Sending clientinit..."); - getClient().sendCommand(new SimpleCommand( "clientinit", ProtocolRole.CLIENT, - new CommandSingleParameter("client_nickname", "test"), - new CommandSingleParameter("client_version", "3.1 [Build: 1471417187]"), + new CommandSingleParameter("client_nickname", getClient().getNickname()), + new CommandSingleParameter("client_version", "3.1.8 [Build: 1516614607]"), new CommandSingleParameter("client_platform", "Windows"), new CommandSingleParameter( "client_version_sign", - "gDEgQf/BiOQZdAheKccM1XWcMUj2OUQqt75oFuvF2c0MQMXyv88cZQdUuckKbcBRp7RpmLInto4PIgd7mPO7BQ==" + "LJ5q+KWT4KwBX7oR/9j9A12hBrq5ds5ony99" + + "f9kepNmqFskhT7gfB51bAJNgAMOzXVCeaItNmc10F2wUNktqCw==" ), 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_default_channel", + getClient().getOption("client.default_channel", String.class) + ), + new CommandSingleParameter("client_default_channel_password", + getClient().getOption("client.default_channel_password", String.class) + ), + new CommandSingleParameter( + "client_server_password", + getClient().getOption("client.server_password", String.class) + ), + new CommandSingleParameter("client_nickname_phonetic", + getClient().getOption("client.nickname_phonetic", String.class) + ), new CommandSingleParameter("client_meta_data", ""), - new CommandSingleParameter("client_default_token", ""), + new CommandSingleParameter("client_default_token", + getClient().getOption("client.default_token", String.class) + ), new CommandSingleParameter( "client_key_offset", Long.toString(getClient().getIdentity().getKeyOffset()) ), new CommandSingleParameter( "hwid", - "+LyYqbDqOvJJpN5pdAbF8/v5kZ0=" + getClient().getOption("client.hwid", String.class) != null ? + getClient().getOption("client.hwid", String.class) : + "+LyYqbDqOvEEpN5pdAbF8/v5kZ0=" ) ) ); + } else if (command.getName().equals("error")) { + getClient().setState(ClientConnectionState.DISCONNECTED); + + throw new IOException(command.get("msg").getValue()); } } } diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java index 040429f..9d99af4 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java @@ -327,15 +327,13 @@ public abstract class AbstractTeamspeakClientSocket packet.getHeader().getPacketId(), response = new PacketResponse( networkPacket, - Integer.MAX_VALUE + networkPacket.getHeader().getType().canResend() ? Integer.MAX_VALUE : 0 ) ); else response = null; Ts3Debugging.debug("[PROTOCOL] WRITE " + networkPacket.getHeader().getType().name()); - for (HeaderFlag flag : HeaderFlag.values()) - Ts3Debugging.debug(flag.name() + ": " + networkPacket.getHeader().getPacketFlag(flag)); writeNetworkPacket(networkPacket); @@ -350,7 +348,10 @@ public abstract class AbstractTeamspeakClientSocket response.waitForAcknowledgement(1000); break; } catch (TimeoutException e) { - response.resend(); + if (response.willResend()) + response.resend(); + else + throw e; } catch (CancellationException e) { throw e; } catch (Exception e) { @@ -398,11 +399,6 @@ public abstract class AbstractTeamspeakClientSocket ); } - Ts3Debugging.debug("[NETWORK] READ " + - packet.getHeader().getType() + " BODY " - + Ts3Debugging.getHex(packetBuffer.array()) - ); - // Fragment handling if (fragment) { Ts3Debugging.debug("[PROTOCOL] READ " + networkPacket.getHeader().getType().name() + " fragment"); @@ -547,6 +543,14 @@ public abstract class AbstractTeamspeakClientSocket this.generationId = generationId; } + public String getNickname( ){ + return getOption("client.nickname", String.class); + } + + public void setNickname(String name) { + setOption("client.nickname", name); + } + private class NetworkHandler implements Runnable { @Override public void run() { @@ -554,9 +558,10 @@ public abstract class AbstractTeamspeakClientSocket try { Packet packet = readQueue.take(); - Ts3Debugging.debug("Processing " + packet + "..."); + // separate these so we getHandler at proper runtime instant rather than get it and let it change + // on us + getHandler().handlePacket(packet); - Ts3Debugging.debug("Processed " + packet + "."); } catch (Exception e) { if (e instanceof InterruptedException) continue; @@ -605,12 +610,25 @@ public abstract class AbstractTeamspeakClientSocket tries ++; } + /** + * Waits for a package acknowledgement. + * @return true if the acknowledgement wasn't null (shouldn't ever happen) + * @throws ExecutionException + * @throws InterruptedException + */ public boolean waitForAcknowledgement() throws ExecutionException, InterruptedException { Packet acknowledgement = future.get(); return acknowledgement != null; } + /** + * Waits for a package acknowledgement. + * @param millis Milliseconds to wait. When transpired with no completion, TimeoutException is thrown. + * @return true if the acknowledgement wasn't null (shouldn't ever happen) + * @throws ExecutionException + * @throws InterruptedException + */ public boolean waitForAcknowledgement(long millis) throws TimeoutException, ExecutionException, InterruptedException { Packet acknowledgement = future.get(millis, TimeUnit.MILLISECONDS); @@ -618,7 +636,7 @@ public abstract class AbstractTeamspeakClientSocket return acknowledgement != null; } - public boolean isWillResend() { + public boolean willResend() { return willResend; } diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/LocalTeamspeakClientSocket.java b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/LocalTeamspeakClientSocket.java index 17372a5..8801451 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/LocalTeamspeakClientSocket.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/LocalTeamspeakClientSocket.java @@ -65,7 +65,6 @@ public class LocalTeamspeakClientSocket extends AbstractTeamspeakClientSocket { packet.getDatagram().getLength() + " to " + socket.getRemoteSocketAddress()); - Ts3Debugging.debug("[NETWORK] WRITE " + Ts3Debugging.getHex(packet.getBuffer().array())); socket.send(packet.getDatagram()); } diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/TeamspeakClientSocket.java b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/TeamspeakClientSocket.java index bc643b5..fbe2e0f 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/TeamspeakClientSocket.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/TeamspeakClientSocket.java @@ -19,7 +19,7 @@ public interface TeamspeakClientSocket extends TeamspeakSocket { default T getOption(String key, Class clazz) { Object value = getOptions().get(key); - if (value == null) return (T) null; + if (value == null) return null; else if (!value.getClass().isAssignableFrom(clazz)) { throw new ClassCastException( diff --git a/src/main/java/com/github/manevolent/ts3j/util/Ts3Crypt.java b/src/main/java/com/github/manevolent/ts3j/util/Ts3Crypt.java index 9772c8d..a1cea6c 100644 --- a/src/main/java/com/github/manevolent/ts3j/util/Ts3Crypt.java +++ b/src/main/java/com/github/manevolent/ts3j/util/Ts3Crypt.java @@ -226,11 +226,15 @@ public final class Ts3Crypt { GroupOperations.ge_scalarmult_base(A, privateKey, 0); byte[] publicKey = new byte[32]; - GroupOperations.ge_p3_tobytes_negate(publicKey, 0, A); + GroupOperations.ge_p3_tobytes(publicKey, 0, A); return new Pair<>(publicKey, privateKey); } + public static ECPoint generatePublicKeyFromPrivateKey(BigInteger privateKey) { + return ECNamedCurveTable.getParameterSpec("prime256v1").getG().multiply(privateKey).normalize(); + } + public static final class SecureChannelParameters { private final byte[] fakeSignature; private final byte[] ivStruct; diff --git a/src/main/java/com/github/manevolent/ts3j/util/Ts3String.java b/src/main/java/com/github/manevolent/ts3j/util/Ts3String.java index 7ab7b32..8ef24b6 100644 --- a/src/main/java/com/github/manevolent/ts3j/util/Ts3String.java +++ b/src/main/java/com/github/manevolent/ts3j/util/Ts3String.java @@ -41,7 +41,7 @@ public final class Ts3String { case '/': sb.append('/'); break; case '\\': sb.append('\\'); break; default: - throw new IllegalArgumentException("invalid escape code: " + c); + throw new IllegalArgumentException("invalid escape code: " + string.charAt(i) + " (" + Character.getName(string.charAt(i)) + ")"); } } else sb.append(c); } diff --git a/src/test/java/com/github/manevolent/ts3j/ClientEkTest.java b/src/test/java/com/github/manevolent/ts3j/ClientEkTest.java index 5bae4c2..7302293 100644 --- a/src/test/java/com/github/manevolent/ts3j/ClientEkTest.java +++ b/src/test/java/com/github/manevolent/ts3j/ClientEkTest.java @@ -8,6 +8,10 @@ import junit.framework.TestCase; import java.math.BigInteger; import java.util.Base64; +/** + * This is a self proof to make sure we can encrypt and decrypt our OWN data. This does not prove that the encrypted + * data would be correct. For that, there are other tests. + */ public class ClientEkTest extends TestCase { public static byte[] hexStringToByteArray(String s) { int len = s.length(); @@ -25,15 +29,14 @@ public class ClientEkTest extends TestCase { 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 + new BigInteger(Base64.getDecoder().decode("Tj6YXM3qyRv8n25L2pH+OEJ" + + "nRUl4auQf8+znjYrOmWU=")) ); - byte[] key = hexStringToByteArray("5763FA00EBC38DCE3953AFCACD8CFF2209208D15BB7D9B699D61A5675EDA9370"); - byte[] beta = hexStringToByteArray("EA581E7CBADED7C47B9CBA3D3EEFB33A49B61ED0F0B45DF50D900C12F551B5F6B0EFEE3F593FB225184C78C06DB2D688669B70ABAC6D"); + byte[] key = hexStringToByteArray("5763FA00EBC38DCE3953AFCACD8CFF2209208D15BB7D" + + "9B699D61A5675EDA9370"); + byte[] beta = hexStringToByteArray("EA581E7CBADED7C47B9CBA3D3EEFB33A49B61ED0F0B45DF" + + "50D900C12F551B5F6B0EFEE3F593FB225184C78C06DB2D688669B70ABAC6D"); byte[] signature = Ts3Crypt.generateClientEkProof(key, beta, keyPair.getPrivateKey()); diff --git a/src/test/java/com/github/manevolent/ts3j/ClientUidGenerationTest.java b/src/test/java/com/github/manevolent/ts3j/ClientUidGenerationTest.java new file mode 100644 index 0000000..9b5ee59 --- /dev/null +++ b/src/test/java/com/github/manevolent/ts3j/ClientUidGenerationTest.java @@ -0,0 +1,37 @@ +package com.github.manevolent.ts3j; + +import com.github.manevolent.ts3j.identity.LocalIdentity; +import com.github.manevolent.ts3j.util.Ts3Crypt; +import junit.framework.TestCase; + +import java.math.BigInteger; +import java.util.Base64; + +public class ClientUidGenerationTest extends TestCase { + public static void main(String[] args) throws Exception { + new ClientUidGenerationTest().testParser(); + } + + public void testParser() throws Exception { + LocalIdentity keyPair = LocalIdentity.load( + new BigInteger(Base64.getDecoder().decode("Tj6YXM3qyRv8n25L2pH+OEJnRUl4auQf8+znjYrOmWU=")) + ); + + keyPair.improveSecurity(10); + + assertEquals(2294, keyPair.getKeyOffset()); + + assertEquals(2295, keyPair.getLastCheckedKeyOffset()); + + assertEquals("MEsDAgcAAgEgAiBpPRbTliVt9KxtIz8saYdwcnNgcwaKLb" + + "KYSpDNO87u9gIgSWWPKcSJ9P6VZKJfRdpWwcfMdJv+NA9/hXUtz1uwRVI=", keyPair.getPublicKeyString()); + + assertEquals("27QHSPRzQe+enxh56eUokkLmsFg=", keyPair.getFingerprint()); + + assertEquals( + "MEsDAgcAAgEgAiBpPRbTliVt9KxtIz8saYdwcnNgcwaKLbKY" + + "SpDNO87u9gIgSWWPKcSJ9P6VZKJfRdpWwcfMdJv+NA9/hXUtz1uwRVI=", + keyPair.getPublicKeyString()); + } + +} diff --git a/src/test/java/com/github/manevolent/ts3j/CryptoInit2Test.java b/src/test/java/com/github/manevolent/ts3j/CryptoInit2Test.java index be90ada..b518381 100644 --- a/src/test/java/com/github/manevolent/ts3j/CryptoInit2Test.java +++ b/src/test/java/com/github/manevolent/ts3j/CryptoInit2Test.java @@ -6,6 +6,10 @@ import junit.framework.TestCase; 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 CryptoInit2Test extends TestCase { public static void main(String[] args) throws Exception { @@ -13,15 +17,20 @@ public class CryptoInit2Test extends TestCase { } public void testParser() throws Exception { - byte[] licenseBytes = hexStringToByteArray("0100358541498A24ACD30157918B8F50955C0DAE970AB65372CBE407415FCF3E029B02084D15E00AA793600700000020416E6F6E796D6F7573000047D9E4DC25AA2E90ACD4DB5FA61C8FED369B346D84C2CA2FCCCA86F73AFEF092200A77C8810A787141"); + byte[] licenseBytes = hexStringToByteArray("0100358541498A24ACD30157918B8F50955C0DAE970AB65372CBE407" + + "415FCF3E029B02084D15E00AA793600700000020416E6F6E796D6F7573000047D9E4DC25AA2E90ACD4DB5FA61C8F" + + "ED369B346D84C2CA2FCCCA86F73AFEF092200A77C8810A787141"); byte[] alpha = hexStringToByteArray("9500A5DB3B50ACECAB81"); - byte[] beta = hexStringToByteArray("EAFFC9A8BC996B25C8AA700264E99E372ECCDEB1C121D6EC0F4D49FB46CEEBA4E3C724B3070FD70CB03D7BC08129205690ECE228CA7C"); - byte[] privateKey = hexStringToByteArray("102E591ABA4508129E812FF3437E2DDD3CA1F1EC341117CA3514CC347A7C2A77"); + byte[] beta = hexStringToByteArray("EAFFC9A8BC996B25C8AA700264E99E372ECCDEB1C121D6EC0F4D49FB46" + + "CEEBA4E3C724B3070FD70CB03D7BC08129205690ECE228CA7C"); + byte[] privateKey = hexStringToByteArray("102E591ABA4508129E812FF3437E2DDD3CA1F1EC341117CA35" + + "14CC347A7C2A77"); Ts3Crypt.SecureChannelParameters result = Ts3Crypt.cryptoInit2(licenseBytes, alpha, beta, privateKey); - assertEquals(Ts3Debugging.getHex(result.getIvStruct()), "E4082A92F71C96A947452F5582EF2879B2051ED2D3F2C6B0643CF5A266EE6B5180573C2F5F3F1C4AC579188366F16AE0EADC3AAF860805D8F2A831E9E49F4513"); + assertEquals(Ts3Debugging.getHex(result.getIvStruct()), "E4082A92F71C96A947452F5582EF2879B2051ED2D3" + + "F2C6B0643CF5A266EE6B5180573C2F5F3F1C4AC579188366F16AE0EADC3AAF860805D8F2A831E9E49F4513"); assertEquals(Ts3Debugging.getHex(result.getFakeSignature()), "54F2B4D661E0F9AB"); } diff --git a/src/test/java/com/github/manevolent/ts3j/LicenseDerivationTest.java b/src/test/java/com/github/manevolent/ts3j/LicenseDerivationTest.java index 6e3b219..d0dcfdf 100644 --- a/src/test/java/com/github/manevolent/ts3j/LicenseDerivationTest.java +++ b/src/test/java/com/github/manevolent/ts3j/LicenseDerivationTest.java @@ -5,7 +5,17 @@ import com.github.manevolent.ts3j.util.Ts3Debugging; import junit.framework.TestCase; import java.nio.ByteBuffer; +import java.util.Base64; +/** + * Tests license derivation. Licenses are part of generating a shared key. The seed for the randomness is an + * ephemeral license at the tail end of a license block. That jumbles up the registers in the hash of the block, + * which is used in the protocol to generate the shared AES keys. What we're doing here is making sure that derivation + * happens appropriately and continues to in the future. + * + * 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 LicenseDerivationTest extends TestCase { public static byte[] hexStringToByteArray(String s) { int len = s.length(); @@ -21,11 +31,38 @@ public class LicenseDerivationTest extends TestCase { 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"); + private void check(String base64License, String expectedResult) { + assertEquals(Base64.getEncoder().encodeToString( + License.deriveKey(License.readLicenses(ByteBuffer.wrap( + Base64.getDecoder().decode(base64License) + ))) + ), expectedResult); + } + public void testParser() throws Exception { + check("AQA1hUFJiiSs0wFXkYuPUJVcDa6XCrZTcsvkB0Ffzz4CmwIITRXgCqeTYA" + + "cAAAAgQW5vbnltb3VzAADSN9wlGHZEHZvX7ImHoqYezibj5byDh0f4oMsG3afDxyAKePI" + + "VCnma1Q==", + "z/bYm6TmHmuAil/osx8eGi6Oits2vIO4i6Bm13RuiGg="); + + check("AQA1hUFJiiSs0wFXkYuPUJVcDa6XCrZTcsvkB0Ffzz4C" + + "mwIITRXgCqeTYAcAAAAgQW5vbnltb3VzAABx1YQfzCiB8b" + + "ZZAdGwXNTLmdhiOpjaH3OOlISy5vrM3iAKePBVCnmZFQ==", + "lrukIi392D7ltdKFp5mURT3Ydk+oWYNjMt3kptbQl6I="); + + + check("AQA1hUFJiiSs0wFXkYuPUJVcDa6XCrZTcsvkB0Ffzz4CmwIITR" + + "XgCqeTYAcAAAAgQW5vbnltb3VzAAAK5C0l+xtOTAZGEA/GHHOySAUEBmq7fN5" + + "PG7uSGPEADiAKePGHCnmaRw==", + "H+UcEreBUkCWN18nTYZp0QQkQqGA8IqzqvJ5qB225Z8="); + + check("AQCvbHFTQDY/terPeilrp/ECU9xCH5U3xC92lYTNaY/0KQ" + + "AJFueAazbsgAAAACVUZWFtU3BlYWsgU3lzdGVtcyBHbWJIAACOaeEfqmGhp2qXAO" + + "QS7IfKw939kEy1i6NSE0OsO4IIPgAJRa7CL1XtIQAAACRUZWFtU3BlYWsgc3lzdGVtcy" + + "BHbWJIAACvbItqlhq4qOHjWFNky0ASbs2BBL//QMVspoGLWmXhzQIJSQCAH" + + "/mxgAYAAAAAVGVhbVNwZWFrIFN5c3RlbXMgR21iSAAAWysE+/5ug1yKk4WiiWMNeB+" + + "E+vQ5RmxglJLi8rW15UsgCnjyQAp5mwA=", + "ZWGGM5QmRdl6AQOgBUZgOAVHcicsm/YPYR3EukrFiGE="); } } diff --git a/src/test/java/com/github/manevolent/ts3j/PublicKeyEncodingTest.java b/src/test/java/com/github/manevolent/ts3j/PublicKeyEncodingTest.java index 49b4545..2746930 100644 --- a/src/test/java/com/github/manevolent/ts3j/PublicKeyEncodingTest.java +++ b/src/test/java/com/github/manevolent/ts3j/PublicKeyEncodingTest.java @@ -9,6 +9,9 @@ import org.bouncycastle.math.ec.ECPoint; import java.math.BigInteger; import java.util.Base64; +/** + * Public keys get encoded to base 64 and get passed around a lot in Teamspeak, especially early on in the protocol. + */ public class PublicKeyEncodingTest extends TestCase { public static byte[] hexStringToByteArray(String s) { int len = s.length(); @@ -26,11 +29,7 @@ public class PublicKeyEncodingTest extends TestCase { 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 + new BigInteger(Base64.getDecoder().decode("Tj6YXM3qyRv8n25L2pH+OEJnRUl4auQf8+znjYrOmWU=")) ); ECPoint point = Ts3Crypt.decodePublicKey(Base64.getDecoder().decode(keyPair.getPublicKeyString())); diff --git a/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java b/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java index 08f2e3f..28931cb 100644 --- a/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java +++ b/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java @@ -28,6 +28,10 @@ public class ServerConnectionTest { client.setIdentity(localIdentity); + client.setNickname("Hello from Java"); + + client.setOption("client.hwid", "JAVAJAVAJAVA"); + client.connect(new InetSocketAddress( "ts.teamlixo.net", 9987),