Attempt packing and unpacking messages
This commit is contained in:
parent
3c688d845d
commit
bdd89efacc
|
@ -180,12 +180,16 @@ public class Generator {
|
|||
writer.write(arg.javaSetCheck());
|
||||
writer.write(") { ");
|
||||
}
|
||||
if (!arg.flags) {
|
||||
writer.write("writer.write(" + VARIABLE_SUFFIX + i + ");");
|
||||
} else {
|
||||
if (arg.flags) {
|
||||
writer.write("writer.write(");
|
||||
writer.write(arg.name);
|
||||
writer.write(");");
|
||||
} else {
|
||||
writer.write("writer.write(" + VARIABLE_SUFFIX + i);
|
||||
if (arg.bigIntSize() != 0) {
|
||||
writer.write(", " + arg.bigIntSize());
|
||||
}
|
||||
writer.write(");");
|
||||
}
|
||||
if (arg.flag != null) {
|
||||
writer.write(" }\n");
|
||||
|
|
|
@ -158,6 +158,17 @@ public class TLArg {
|
|||
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() {
|
||||
return types.size() == 1 && types.get(0).equals("true") ? "" : " != null";
|
||||
}
|
||||
|
|
|
@ -3,14 +3,19 @@ package io.github.lonamiwebs.overgram;
|
|||
import io.github.lonamiwebs.overgram.network.MTProtoSender;
|
||||
import io.github.lonamiwebs.overgram.network.MTProtoState;
|
||||
import io.github.lonamiwebs.overgram.network.connection.TcpFull;
|
||||
import io.github.lonamiwebs.overgram.tl.Functions;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
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());
|
||||
try {
|
||||
sender.connect("149.154.167.91", 443);
|
||||
final Future result = sender.send(new Functions.help.GetConfig());
|
||||
System.out.println(result.get());
|
||||
} finally {
|
||||
sender.disconnect();
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package io.github.lonamiwebs.overgram.crypto;
|
||||
|
||||
import io.github.lonamiwebs.overgram.utils.BinaryWriter;
|
||||
import io.github.lonamiwebs.overgram.utils.Utils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class AuthKey {
|
||||
public final byte[] key;
|
||||
|
@ -21,20 +23,12 @@ public class AuthKey {
|
|||
}
|
||||
|
||||
public BigInteger calcNewNonceHash(final BigInteger newNonce, final int number) {
|
||||
// Big integer is big endian but we need little endian
|
||||
final byte[] data = Utils.sha1digest(Utils.concat(
|
||||
Utils.reversed(newNonce.toByteArray()), Utils.concat(
|
||||
ByteBuffer.allocate(1).put((byte) number).array(),
|
||||
ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(auxHash).array()
|
||||
)
|
||||
));
|
||||
final BinaryWriter writer = new BinaryWriter(41);
|
||||
writer.write(newNonce, 32);
|
||||
writer.write((byte) number);
|
||||
writer.write(auxHash);
|
||||
|
||||
// Little endian once again so read (20..4]
|
||||
final byte[] numberBytes = new byte[16];
|
||||
for (int i = 16; i-- != 0; ) {
|
||||
numberBytes[i] = data[19 - i];
|
||||
}
|
||||
|
||||
return new BigInteger(numberBytes);
|
||||
final byte[] sha = Utils.sha1digest(writer.toBytes());
|
||||
return new BigInteger(Utils.reversed(Arrays.copyOfRange(sha, 4, 20)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -211,7 +211,12 @@ public class MTProtoSender {
|
|||
return;
|
||||
}
|
||||
|
||||
final TLMessage message = state.unpackMessage(body);
|
||||
final TLMessage message;
|
||||
try {
|
||||
message = state.unpackMessage(body);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
processMessage(message);
|
||||
} catch (InterruptedException ignored) {
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
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.tl.TLMessage;
|
||||
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.util.Arrays;
|
||||
|
||||
public class MTProtoState {
|
||||
|
||||
|
@ -25,19 +33,93 @@ public class MTProtoState {
|
|||
}
|
||||
|
||||
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) {
|
||||
throw new UnsupportedOperationException();
|
||||
private Pair<byte[], byte[]> calcKey(final byte[] msgKey, final boolean client) {
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
|
||||
final byte[] paddedData = writer.toBytes();
|
||||
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) {
|
||||
throw new UnsupportedOperationException();
|
||||
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() {
|
||||
|
|
|
@ -1,10 +1,36 @@
|
|||
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;
|
||||
|
||||
public class TLMessage {
|
||||
public class TLMessage extends TLObject {
|
||||
public CompletableFuture<Object> future;
|
||||
public long id;
|
||||
|
||||
public long id;
|
||||
public int seqNo;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,4 +129,16 @@ public class BinaryReader {
|
|||
public void seek(final int 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,14 @@ public class BinaryWriter {
|
|||
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) {
|
||||
try {
|
||||
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();
|
||||
|
||||
// It's really unlike an int256 has only 16 bytes so we're good
|
||||
final int size = bytes.length > 16 ? 32 : 16;
|
||||
if (ORDER == ByteOrder.BIG_ENDIAN) {
|
||||
for (int i = 0; i < size; ++i) {
|
||||
buffer.write(i < bytes.length ? bytes[i] : 0);
|
||||
}
|
||||
final byte[] result;
|
||||
if (bytes.length == size) {
|
||||
result = bytes;
|
||||
} else {
|
||||
for (int i = size; i-- != 0; ) {
|
||||
buffer.write(i < bytes.length ? bytes[i] : 0);
|
||||
result = new byte[size];
|
||||
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) {
|
||||
|
@ -89,10 +98,11 @@ public class BinaryWriter {
|
|||
}
|
||||
|
||||
public void writeRaw(final byte[] bytes) {
|
||||
try {
|
||||
buffer.write(bytes);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
buffer.write(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public void writeRaw(final byte[] bytes, final int start, final int length) {
|
||||
buffer.write(bytes, start, length);
|
||||
}
|
||||
|
||||
public void write(final String string) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.util.Arrays;
|
|||
public class Utils {
|
||||
|
||||
private static MessageDigest sha1;
|
||||
private static MessageDigest sha256;
|
||||
private static SecureRandom random;
|
||||
|
||||
static {
|
||||
|
@ -19,6 +20,10 @@ public class Utils {
|
|||
sha1 = MessageDigest.getInstance("SHA-1");
|
||||
} catch (NoSuchAlgorithmException ignored) {
|
||||
}
|
||||
try {
|
||||
sha256 = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public static BigInteger randomBigInteger(final BigInteger max) {
|
||||
|
@ -112,16 +117,32 @@ public class Utils {
|
|||
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) {
|
||||
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();
|
||||
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) {
|
||||
final byte[] serverNonceBytes = reversed(serverNonce.toByteArray());
|
||||
final byte[] newNonceBytes = reversed(newNonce.toByteArray());
|
||||
|
|
Loading…
Reference in New Issue