Client authentication is done. It will now put an API client in a server, however we clearly need to work on decompression, and we also need to accept and handle ping packets appropriately.
Then we can start working on the text-based protocol Teamspeak uses.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -218,6 +218,7 @@ public class License {
|
||||
|
||||
byte[] final_ = new byte[32];
|
||||
GroupOperations.ge_p3_tobytes(final_, 0, r2);
|
||||
final_[31] ^= 0x80;
|
||||
|
||||
return final_;
|
||||
}
|
||||
|
||||
+28
-22
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-11
@@ -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) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -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());
|
||||
}
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ public interface TeamspeakClientSocket extends TeamspeakSocket {
|
||||
default <T extends Object> T getOption(String key, Class<T> 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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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=");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user