Attempt packing and unpacking messages

This commit is contained in:
Lonami Exo 2018-07-25 16:04:43 +02:00
parent 3c688d845d
commit bdd89efacc
10 changed files with 213 additions and 43 deletions

View File

@ -180,12 +180,16 @@ public class Generator {
writer.write(arg.javaSetCheck()); writer.write(arg.javaSetCheck());
writer.write(") { "); writer.write(") { ");
} }
if (!arg.flags) { if (arg.flags) {
writer.write("writer.write(" + VARIABLE_SUFFIX + i + ");");
} else {
writer.write("writer.write("); writer.write("writer.write(");
writer.write(arg.name); writer.write(arg.name);
writer.write(");"); writer.write(");");
} else {
writer.write("writer.write(" + VARIABLE_SUFFIX + i);
if (arg.bigIntSize() != 0) {
writer.write(", " + arg.bigIntSize());
}
writer.write(");");
} }
if (arg.flag != null) { if (arg.flag != null) {
writer.write(" }\n"); writer.write(" }\n");

View File

@ -158,6 +158,17 @@ public class TLArg {
return builder.toString(); return builder.toString();
} }
public int bigIntSize() {
switch (types.get(types.size() - 1)) {
case "int128":
return 32;
case "int256":
return 64;
default:
return 0;
}
}
public String javaSetCheck() { public String javaSetCheck() {
return types.size() == 1 && types.get(0).equals("true") ? "" : " != null"; return types.size() == 1 && types.get(0).equals("true") ? "" : " != null";
} }

View File

@ -3,14 +3,19 @@ package io.github.lonamiwebs.overgram;
import io.github.lonamiwebs.overgram.network.MTProtoSender; import io.github.lonamiwebs.overgram.network.MTProtoSender;
import io.github.lonamiwebs.overgram.network.MTProtoState; import io.github.lonamiwebs.overgram.network.MTProtoState;
import io.github.lonamiwebs.overgram.network.connection.TcpFull; import io.github.lonamiwebs.overgram.network.connection.TcpFull;
import io.github.lonamiwebs.overgram.tl.Functions;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class Overgram { public class Overgram {
public static void main(final String... args) throws IOException { public static void main(final String... args) throws IOException, InterruptedException, ExecutionException {
final MTProtoSender sender = new MTProtoSender(new MTProtoState(), new TcpFull()); final MTProtoSender sender = new MTProtoSender(new MTProtoState(), new TcpFull());
try { try {
sender.connect("149.154.167.91", 443); sender.connect("149.154.167.91", 443);
final Future result = sender.send(new Functions.help.GetConfig());
System.out.println(result.get());
} finally { } finally {
sender.disconnect(); sender.disconnect();
} }

View File

@ -1,10 +1,12 @@
package io.github.lonamiwebs.overgram.crypto; package io.github.lonamiwebs.overgram.crypto;
import io.github.lonamiwebs.overgram.utils.BinaryWriter;
import io.github.lonamiwebs.overgram.utils.Utils; import io.github.lonamiwebs.overgram.utils.Utils;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.Arrays;
public class AuthKey { public class AuthKey {
public final byte[] key; public final byte[] key;
@ -21,20 +23,12 @@ public class AuthKey {
} }
public BigInteger calcNewNonceHash(final BigInteger newNonce, final int number) { public BigInteger calcNewNonceHash(final BigInteger newNonce, final int number) {
// Big integer is big endian but we need little endian final BinaryWriter writer = new BinaryWriter(41);
final byte[] data = Utils.sha1digest(Utils.concat( writer.write(newNonce, 32);
Utils.reversed(newNonce.toByteArray()), Utils.concat( writer.write((byte) number);
ByteBuffer.allocate(1).put((byte) number).array(), writer.write(auxHash);
ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(auxHash).array()
)
));
// Little endian once again so read (20..4] final byte[] sha = Utils.sha1digest(writer.toBytes());
final byte[] numberBytes = new byte[16]; return new BigInteger(Utils.reversed(Arrays.copyOfRange(sha, 4, 20)));
for (int i = 16; i-- != 0; ) {
numberBytes[i] = data[19 - i];
}
return new BigInteger(numberBytes);
} }
} }

View File

@ -211,7 +211,12 @@ public class MTProtoSender {
return; return;
} }
final TLMessage message = state.unpackMessage(body); final TLMessage message;
try {
message = state.unpackMessage(body);
} catch (ClassNotFoundException ignored) {
continue;
}
try { try {
processMessage(message); processMessage(message);
} catch (InterruptedException ignored) { } catch (InterruptedException ignored) {

View File

@ -1,10 +1,18 @@
package io.github.lonamiwebs.overgram.network; package io.github.lonamiwebs.overgram.network;
import io.github.lonamiwebs.overgram.crypto.AES;
import io.github.lonamiwebs.overgram.crypto.AuthKey; import io.github.lonamiwebs.overgram.crypto.AuthKey;
import io.github.lonamiwebs.overgram.tl.TLMessage; import io.github.lonamiwebs.overgram.tl.TLMessage;
import io.github.lonamiwebs.overgram.tl.TLObject; import io.github.lonamiwebs.overgram.tl.TLObject;
import io.github.lonamiwebs.overgram.tl.TLRequest;
import io.github.lonamiwebs.overgram.utils.BinaryReader;
import io.github.lonamiwebs.overgram.utils.BinaryWriter;
import io.github.lonamiwebs.overgram.utils.Utils;
import javafx.util.Pair;
import java.nio.ByteBuffer;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Arrays;
public class MTProtoState { public class MTProtoState {
@ -25,19 +33,93 @@ public class MTProtoState {
} }
public TLMessage createMessage(final TLObject object) { public TLMessage createMessage(final TLObject object) {
throw new UnsupportedOperationException(); return new TLMessage(getNewMsgId(), getSeqNo(object instanceof TLRequest), object);
} }
public static Object calcKey(final Object authKey, final byte[] msgKey, final boolean client) { private Pair<byte[], byte[]> calcKey(final byte[] msgKey, final boolean client) {
throw new UnsupportedOperationException(); final int x = client ? 0 : 8;
final byte[] sha256a = Utils.sha256digest(msgKey, ByteBuffer.wrap(authKey.key, x, 36).array());
final byte[] sha256b = Utils.sha256digest(ByteBuffer.wrap(authKey.key, x + 40, 36).array(), msgKey);
final BinaryWriter writer = new BinaryWriter(32);
writer.writeRaw(sha256a, 0, 8);
writer.writeRaw(sha256b, 8, 16);
writer.writeRaw(sha256a, 24, 8);
final byte[] key = writer.toBytes();
writer.clear();
writer.writeRaw(sha256b, 0, 8);
writer.writeRaw(sha256a, 8, 16);
writer.writeRaw(sha256b, 24, 8);
return new Pair<>(key, writer.toBytes());
} }
public byte[] packMessage(final TLMessage message) { public byte[] packMessage(final TLMessage message) {
throw new UnsupportedOperationException(); final BinaryWriter writer = new BinaryWriter();
writer.write(salt);
writer.write(id);
writer.write(message);
int padding = writer.size() % 16;
if (padding != 0) {
writer.writeRaw(Utils.randomBytes(16 - padding));
} }
public TLMessage unpackMessage(final byte[] body) { final byte[] paddedData = writer.toBytes();
throw new UnsupportedOperationException(); writer.clear();
final byte[] msgKeyLarge = Utils.sha256digest(
Arrays.copyOfRange(authKey.key, 88, 88 + 32), paddedData);
final byte[] msgKey = Arrays.copyOfRange(msgKeyLarge, 8, 24);
final Pair<byte[], byte[]> keyIv = calcKey(msgKey, true);
writer.write(authKey.keyId);
writer.writeRaw(msgKey);
writer.writeRaw(AES.encryptIge(paddedData, keyIv.getKey(), keyIv.getValue()));
return writer.toBytes();
}
public TLMessage unpackMessage(final byte[] body) throws ClassNotFoundException {
if (body.length < 8) {
if (body[0] == (byte) 0x6c
&& body[1] == (byte) 0xfe
&& body[2] == (byte) 0xff
&& body[3] == (byte) 0xff) {
// -404 as little endian, broken authorization
throw new RuntimeException();
} else {
throw new RuntimeException();
}
}
final BinaryReader reader = new BinaryReader(ByteBuffer.wrap(body));
final long keyId = reader.readLong();
if (keyId != authKey.keyId) {
throw new SecurityException("Server replied with an invalid auth key");
}
final byte[] msgKey = reader.read(16);
final Pair<byte[], byte[]> keyIv = calcKey(msgKey, false);
final byte[] plainText = AES.decryptIge(reader.read(), keyIv.getKey(), keyIv.getValue());
final byte[] ourKey = Utils.sha256digest(ByteBuffer.wrap(authKey.key, 96, 32).array(), body);
if (!Arrays.equals(msgKey, ourKey)) {
throw new SecurityException("Received message key doesn't match with expected one");
}
final BinaryReader tlReader = new BinaryReader(ByteBuffer.wrap(plainText));
tlReader.readLong(); // remote salt
if (tlReader.readLong() != id) {
throw new SecurityException("Server replied with a wrong session ID");
}
final long remoteMsgId = reader.readLong();
final int remoteSeq = reader.readInt();
reader.readInt(); // inner message length
final TLObject object = reader.readTl();
return new TLMessage(remoteMsgId, remoteSeq, object);
} }
public long getNewMsgId() { public long getNewMsgId() {

View File

@ -1,10 +1,36 @@
package io.github.lonamiwebs.overgram.tl; package io.github.lonamiwebs.overgram.tl;
import io.github.lonamiwebs.overgram.utils.BinaryReader;
import io.github.lonamiwebs.overgram.utils.BinaryWriter;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class TLMessage { public class TLMessage extends TLObject {
public CompletableFuture<Object> future; public CompletableFuture<Object> future;
public long id;
public long id;
public int seqNo;
public TLObject object; public TLObject object;
public TLMessage(final long id, final int seqNo, final TLObject object) {
this.id = id;
this.seqNo = seqNo;
this.object = object;
future = new CompletableFuture<>();
}
@Override
public void serialize(BinaryWriter writer) {
final BinaryWriter tmp = new BinaryWriter();
tmp.write(object);
writer.write(id);
writer.write(seqNo);
writer.write(tmp.size());
writer.writeRaw(tmp.toBytes());
}
@Override
public void deserialize(BinaryReader reader) throws ClassNotFoundException {
throw new UnsupportedOperationException();
}
} }

View File

@ -129,4 +129,16 @@ public class BinaryReader {
public void seek(final int delta) { public void seek(final int delta) {
buffer.position(buffer.position() + delta); buffer.position(buffer.position() + delta);
} }
public byte[] read(final int size) {
final byte[] result = new byte[size];
buffer.get(result);
return result;
}
public byte[] read() {
final byte[] result = new byte[buffer.remaining()];
buffer.get(result);
return result;
}
} }

View File

@ -22,6 +22,14 @@ public class BinaryWriter {
buffer = new ByteArrayOutputStream(128); buffer = new ByteArrayOutputStream(128);
} }
public BinaryWriter(final int capacity) {
buffer = new ByteArrayOutputStream(capacity);
}
public void write(final byte value) {
buffer.write(value);
}
public void write(final int value) { public void write(final int value) {
try { try {
buffer.write(ByteBuffer.allocate(4).order(ORDER).putInt(value).array()); buffer.write(ByteBuffer.allocate(4).order(ORDER).putInt(value).array());
@ -38,20 +46,21 @@ public class BinaryWriter {
} }
} }
public void write(final BigInteger value) { public void write(final BigInteger value, final int size) {
final byte[] bytes = value.toByteArray(); final byte[] bytes = value.toByteArray();
final byte[] result;
// It's really unlike an int256 has only 16 bytes so we're good if (bytes.length == size) {
final int size = bytes.length > 16 ? 32 : 16; result = bytes;
if (ORDER == ByteOrder.BIG_ENDIAN) {
for (int i = 0; i < size; ++i) {
buffer.write(i < bytes.length ? bytes[i] : 0);
}
} else { } else {
for (int i = size; i-- != 0; ) { result = new byte[size];
buffer.write(i < bytes.length ? bytes[i] : 0); final byte fill = value.signum() > 0 ? 0 : (byte) 0xff;
System.arraycopy(bytes, 0, result, size - bytes.length, bytes.length);
for (int i = size - bytes.length; i-- != 0; ) {
result[i] = fill;
} }
} }
buffer.write(ORDER == ByteOrder.BIG_ENDIAN ? result : Utils.reversed(result), 0, size);
} }
public void write(final double value) { public void write(final double value) {
@ -89,10 +98,11 @@ public class BinaryWriter {
} }
public void writeRaw(final byte[] bytes) { public void writeRaw(final byte[] bytes) {
try { buffer.write(bytes, 0, bytes.length);
buffer.write(bytes);
} catch (IOException ignored) {
} }
public void writeRaw(final byte[] bytes, final int start, final int length) {
buffer.write(bytes, start, length);
} }
public void write(final String string) { public void write(final String string) {

View File

@ -11,6 +11,7 @@ import java.util.Arrays;
public class Utils { public class Utils {
private static MessageDigest sha1; private static MessageDigest sha1;
private static MessageDigest sha256;
private static SecureRandom random; private static SecureRandom random;
static { static {
@ -19,6 +20,10 @@ public class Utils {
sha1 = MessageDigest.getInstance("SHA-1"); sha1 = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException ignored) { } catch (NoSuchAlgorithmException ignored) {
} }
try {
sha256 = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException ignored) {
}
} }
public static BigInteger randomBigInteger(final BigInteger max) { public static BigInteger randomBigInteger(final BigInteger max) {
@ -112,16 +117,32 @@ public class Utils {
return new Pair<>(x.min(y), x.max(y)); return new Pair<>(x.min(y), x.max(y));
} }
public static byte[] sha1digest(final byte[] data) throws SecurityException { public static byte[] sha1digest(final byte[]... data) throws SecurityException {
if (sha1 == null) { if (sha1 == null) {
throw new SecurityException("SHA-1 is required but not available"); throw new SecurityException("SHA-1 is required but not available");
} }
final byte[] result = sha1.digest(data); for (final byte[] datum : data) {
sha1.update(datum);
}
final byte[] result = sha1.digest();
sha1.reset(); sha1.reset();
return result; return result;
} }
public static byte[] sha256digest(final byte[]... data) throws SecurityException {
if (sha256 == null) {
throw new SecurityException("SHA-256 is required but not available");
}
for (final byte[] datum : data) {
sha256.update(datum);
}
final byte[] result = sha256.digest();
sha256.reset();
return result;
}
public static Pair<byte[], byte[]> generateKeyDataFromNonce(BigInteger serverNonce, BigInteger newNonce) { public static Pair<byte[], byte[]> generateKeyDataFromNonce(BigInteger serverNonce, BigInteger newNonce) {
final byte[] serverNonceBytes = reversed(serverNonce.toByteArray()); final byte[] serverNonceBytes = reversed(serverNonce.toByteArray());
final byte[] newNonceBytes = reversed(newNonce.toByteArray()); final byte[] newNonceBytes = reversed(newNonce.toByteArray());