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:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-12
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
-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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user