diff --git a/generator/src/main/java/io/github/lonamiwebs/overgram/generator/Generator.java b/generator/src/main/java/io/github/lonamiwebs/overgram/generator/Generator.java index e153015..3ed08f1 100644 --- a/generator/src/main/java/io/github/lonamiwebs/overgram/generator/Generator.java +++ b/generator/src/main/java/io/github/lonamiwebs/overgram/generator/Generator.java @@ -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"); diff --git a/generator/src/main/java/io/github/lonamiwebs/overgram/parser/TLArg.java b/generator/src/main/java/io/github/lonamiwebs/overgram/parser/TLArg.java index 9a41bf5..830b703 100644 --- a/generator/src/main/java/io/github/lonamiwebs/overgram/parser/TLArg.java +++ b/generator/src/main/java/io/github/lonamiwebs/overgram/parser/TLArg.java @@ -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"; } diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/Overgram.java b/lib/src/main/java/io/github/lonamiwebs/overgram/Overgram.java index 8807b08..95ecb20 100644 --- a/lib/src/main/java/io/github/lonamiwebs/overgram/Overgram.java +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/Overgram.java @@ -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(); } diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/crypto/AuthKey.java b/lib/src/main/java/io/github/lonamiwebs/overgram/crypto/AuthKey.java index ea28eec..9aa70b6 100644 --- a/lib/src/main/java/io/github/lonamiwebs/overgram/crypto/AuthKey.java +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/crypto/AuthKey.java @@ -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))); } } diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/network/MTProtoSender.java b/lib/src/main/java/io/github/lonamiwebs/overgram/network/MTProtoSender.java index 638b731..2cdbf35 100644 --- a/lib/src/main/java/io/github/lonamiwebs/overgram/network/MTProtoSender.java +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/network/MTProtoSender.java @@ -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) { diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/network/MTProtoState.java b/lib/src/main/java/io/github/lonamiwebs/overgram/network/MTProtoState.java index 2a9ea49..18bae40 100644 --- a/lib/src/main/java/io/github/lonamiwebs/overgram/network/MTProtoState.java +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/network/MTProtoState.java @@ -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 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 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 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() { diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/tl/TLMessage.java b/lib/src/main/java/io/github/lonamiwebs/overgram/tl/TLMessage.java index 3c50f44..4410f74 100644 --- a/lib/src/main/java/io/github/lonamiwebs/overgram/tl/TLMessage.java +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/tl/TLMessage.java @@ -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 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(); + } } diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryReader.java b/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryReader.java index 55f8dc6..08b06f4 100644 --- a/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryReader.java +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryReader.java @@ -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; + } } diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryWriter.java b/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryWriter.java index 0e92c3f..464f01f 100644 --- a/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryWriter.java +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryWriter.java @@ -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) { diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/utils/Utils.java b/lib/src/main/java/io/github/lonamiwebs/overgram/utils/Utils.java index b9fe5c3..ef40efb 100644 --- a/lib/src/main/java/io/github/lonamiwebs/overgram/utils/Utils.java +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/utils/Utils.java @@ -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 generateKeyDataFromNonce(BigInteger serverNonce, BigInteger newNonce) { final byte[] serverNonceBytes = reversed(serverNonce.toByteArray()); final byte[] newNonceBytes = reversed(newNonce.toByteArray());