Complete step 2 of AuthKey generation

This commit is contained in:
Lonami Exo 2018-07-24 13:49:39 +02:00
parent 80ef876d92
commit e404594ad5
8 changed files with 245 additions and 16 deletions

View File

@ -70,7 +70,7 @@ msg_new_detailed_info#809db6df answer_msg_id:long bytes:int status:int = MsgDeta
req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;
req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:bytes q:bytes public_key_fingerprint:long encrypted_data:string = Server_DH_Params;
req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:bytes q:bytes public_key_fingerprint:long encrypted_data:bytes = Server_DH_Params;
set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:bytes = Set_client_DH_params_answer;

View File

@ -4,7 +4,6 @@ import io.github.lonamiwebs.overgram.crypto.Authenticator;
import io.github.lonamiwebs.overgram.network.MTProtoPlainSender;
import io.github.lonamiwebs.overgram.network.connection.Connection;
import io.github.lonamiwebs.overgram.network.connection.TcpFull;
import io.github.lonamiwebs.overgram.utils.BinaryWriter;
import java.io.IOException;

View File

@ -1,22 +1,82 @@
package io.github.lonamiwebs.overgram.crypto;
import io.github.lonamiwebs.overgram.network.MTProtoPlainSender;
import io.github.lonamiwebs.overgram.tl.Abstract;
import io.github.lonamiwebs.overgram.tl.Functions;
import io.github.lonamiwebs.overgram.tl.TLObject;
import io.github.lonamiwebs.overgram.tl.Types;
import io.github.lonamiwebs.overgram.utils.RSA;
import io.github.lonamiwebs.overgram.utils.Utils;
import javafx.util.Pair;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
public class Authenticator {
public static void doAuthentication(final MTProtoPlainSender sender) throws IOException, ClassNotFoundException {
final byte[] nonceBytes = new byte[16];
new SecureRandom().nextBytes(nonceBytes);
final BigInteger nonce = new BigInteger(nonceBytes);
final BigInteger nonce = new BigInteger(Utils.randomBytes(16));
final Types.ResPQ resPq = sender.send(
new Functions.ReqPqMulti().nonce(nonce));
final TLObject result = sender.send(new Functions.ReqPqMulti().nonce(nonce));
assert result instanceof Types.ResPQ;
if (!resPq.nonce().equals(nonce)) {
throw new SecurityException("Step 1 response nonce invalid");
}
// Note that Java uses big endian, which is correct here
final BigInteger pq = new BigInteger(resPq.pq());
final Pair<BigInteger, BigInteger> pqPair = Utils.factorize(pq);
final BigInteger p = pqPair.getKey();
final BigInteger q = pqPair.getValue();
// Once again, as big endian
final byte[] pBytes = p.toByteArray();
final byte[] qBytes = q.toByteArray();
final BigInteger newNonce = new BigInteger(Utils.randomBytes(32));
final byte[] pqInnerData = new Types.PQInnerData()
.pq(resPq.pq())
.p(pBytes)
.q(qBytes)
.nonce(resPq.nonce())
.serverNonce(resPq.serverNonce())
.newNonce(newNonce)
.serializeToBytes();
byte[] cipherText = null;
long targetFingerprint = 0;
for (final long fingerprint : resPq.serverPublicKeyFingerprints()) {
cipherText = RSA.encrypt(fingerprint, pqInnerData);
if (cipherText != null) {
targetFingerprint = fingerprint;
break;
}
}
if (cipherText == null) {
throw new SecurityException("Step 2 could not find a known RSA key");
}
final Abstract.ServerDHParams abstractDhParams = sender.send(
new Functions.ReqDHParams()
.nonce(resPq.nonce())
.serverNonce(resPq.serverNonce())
.p(pBytes)
.q(qBytes)
.publicKeyFingerprint(targetFingerprint)
.encryptedData(cipherText)
);
if (abstractDhParams instanceof Types.ServerDHParamsFail) {
// We could also check its nonce etc. but not needed
throw new SecurityException("Step 2 generation of DH params failed");
}
final Types.ServerDHParamsOk dhParams = (Types.ServerDHParamsOk) abstractDhParams;
if (!dhParams.nonce().equals(resPq.nonce())) {
throw new SecurityException("Step 2 DH params have invalid nonce");
}
if (!dhParams.serverNonce().equals(resPq.serverNonce())) {
throw new SecurityException("Step 2 DH params have invalid nonce");
}
}
}

View File

@ -8,7 +8,6 @@ import io.github.lonamiwebs.overgram.utils.BinaryWriter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class MTProtoPlainSender {
private final Connection connection;
@ -19,7 +18,7 @@ public class MTProtoPlainSender {
this.state = new MTProtoState();
}
public TLObject send(final TLRequest request) throws IOException, ClassNotFoundException {
public <T extends TLObject> T send(final TLRequest request) throws IOException, ClassNotFoundException {
final long msgId = state.getNewMsgId();
final BinaryWriter writer = new BinaryWriter();
@ -42,7 +41,7 @@ public class MTProtoPlainSender {
throw new RuntimeException();
}
final BinaryReader reader = new BinaryReader(ByteBuffer.wrap(body).order(ByteOrder.LITTLE_ENDIAN));
final BinaryReader reader = new BinaryReader(ByteBuffer.wrap(body));
final long authKeyId = reader.readLong();
assert authKeyId == 0;
@ -52,6 +51,7 @@ public class MTProtoPlainSender {
final int length = reader.readInt();
assert length > 0;
return reader.readTl();
//noinspection unchecked
return (T) reader.readTl();
}
}

View File

@ -7,4 +7,10 @@ public abstract class TLObject {
public abstract void serialize(final BinaryWriter writer);
public abstract void deserialize(final BinaryReader reader) throws ClassNotFoundException;
public byte[] serializeToBytes() {
final BinaryWriter writer = new BinaryWriter();
serialize(writer);
return writer.toBytes();
}
}

View File

@ -5,6 +5,7 @@ import io.github.lonamiwebs.overgram.tl.Types;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@ -13,7 +14,7 @@ public class BinaryReader {
private final ByteBuffer buffer;
public BinaryReader(final ByteBuffer buffer) {
this.buffer = buffer;
this.buffer = buffer.order(ByteOrder.LITTLE_ENDIAN);
}
public int readInt() {
@ -47,13 +48,13 @@ public class BinaryReader {
public BigInteger readInt128() {
final byte[] bytes = new byte[16];
buffer.get(bytes);
return new BigInteger(bytes);
return new BigInteger(Utils.reversed(bytes));
}
public BigInteger readInt256() {
final byte[] bytes = new byte[32];
buffer.get(bytes);
return new BigInteger(bytes);
return new BigInteger(Utils.reversed(bytes));
}
public double readDouble() {

View File

@ -0,0 +1,50 @@
package io.github.lonamiwebs.overgram.utils;
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
import java.math.BigInteger;
public class RSA {
// TODO Load keys from "-----BEGIN RSA PUBLIC KEY-----" properely
public final BigInteger n;
public final BigInteger e;
private RSA(final BigInteger n, final BigInteger e) {
this.n = n;
this.e = e;
}
private static RSA getKey(final long fingerprint) {
// Java can't into switch(long) apparently
if (fingerprint == -7306692244673891685L) {
return new RSA(new BigInteger("25081407810410225030931722734886059247598515157516470397242545867550116598436968553551465554653745201634977779380884774534457386795922003815072071558370597290368737862981871277312823942822144802509055492512145589734772907225259038113414940384446493111736999668652848440655603157665903721517224934142301456312994547591626081517162758808439979745328030376796953660042629868902013177751703385501412640560275067171555763725421377065095231095517201241069856888933358280729674273422117201596511978645878544308102076746465468955910659145532699238576978901011112475698963666091510778777356966351191806495199073754705289253783"), new BigInteger("65537"));
} else if (fingerprint == -5738946642031285640L) {
return new RSA(new BigInteger("22347337644621997830323797217583448833849627595286505527328214795712874535417149457567295215523199212899872122674023936713124024124676488204889357563104452250187725437815819680799441376434162907889288526863223004380906766451781702435861040049293189979755757428366240570457372226323943522935844086838355728767565415115131238950994049041950699006558441163206523696546297006014416576123345545601004508537089192869558480948139679182328810531942418921113328804749485349441503927570568778905918696883174575510385552845625481490900659718413892216221539684717773483326240872061786759868040623935592404144262688161923519030977"), new BigInteger("65537"));
} else if (fingerprint == -4344800451088585951L) {
return new RSA(new BigInteger("24403446649145068056824081744112065346446136066297307473868293895086332508101251964919587745984311372853053253457835208829824428441874946556659953519213382748319518214765985662663680818277989736779506318868003755216402538945900388706898101286548187286716959100102939636333452457308619454821845196109544157601096359148241435922125602449263164512290854366930013825808102403072317738266383237191313714482187326643144603633877219028262697593882410403273959074350849923041765639673335775605842311578109726403165298875058941765362622936097839775380070572921007586266115476975819175319995527916042178582540628652481530373407"), new BigInteger("65537"));
} else if (fingerprint == 8205599988028290019L) {
return new RSA(new BigInteger("24573455207957565047870011785254215390918912369814947541785386299516827003508659346069416840622922416779652050319196701077275060353178142796963682024347858398319926119639265555410256455471016400261630917813337515247954638555325280392998950756512879748873422896798579889820248358636937659872379948616822902110696986481638776226860777480684653756042166610633513404129518040549077551227082262066602286208338952016035637334787564972991208252928951876463555456715923743181359826124083963758009484867346318483872552977652588089928761806897223231500970500186019991032176060579816348322451864584743414550721639495547636008351"), new BigInteger("65537"));
} else {
return null;
}
}
public static byte[] encrypt(final long fingerprint, final byte[] data) throws SecurityException {
final RSA key = getKey(fingerprint);
if (key == null) {
return null;
}
final ByteOutputStream toEncrypt = new ByteOutputStream(256);
toEncrypt.write(0); // big endian unsigned big integer needs the sign bit 0
toEncrypt.write(Utils.sha1digest(data)); // 20 bytes
toEncrypt.write(data); // length
toEncrypt.write(Utils.randomBytes(235 - data.length)); // left random padding
final BigInteger payload = new BigInteger(toEncrypt.getBytes());
final BigInteger encrypted = payload.modPow(key.e, key.n);
return encrypted.toByteArray();
}
}

View File

@ -0,0 +1,113 @@
package io.github.lonamiwebs.overgram.utils;
import javafx.util.Pair;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class Utils {
private static MessageDigest sha1;
private static SecureRandom random;
static {
random = new SecureRandom();
try {
sha1 = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException ignored) {
}
}
public static BigInteger randomBigInteger(final BigInteger max) {
BigInteger result;
do {
result = new BigInteger(max.bitLength(), random);
} while (result.compareTo(max) >= 0);
return result;
}
public static byte[] randomBytes(final int size) {
final byte[] result = new byte[size];
random.nextBytes(result);
return result;
}
// Note that this reverses IN PLACE and returns the same pointer
public static byte[] reversed(final byte[] bytes) {
for (int i = bytes.length / 2; i-- != 0; ) {
final byte temp = bytes[i];
bytes[i] = bytes[bytes.length - i - 1];
bytes[bytes.length - i - 1] = temp;
}
return bytes;
}
public static Pair<BigInteger, BigInteger> factorize(final BigInteger pq) {
final BigInteger two = new BigInteger("2");
if (pq.mod(two).equals(BigInteger.ZERO)) {
return new Pair<>(two, pq.divide(two));
}
BigInteger y = BigInteger.ONE.add(randomBigInteger(pq.subtract(two)));
BigInteger c = BigInteger.ONE.add(randomBigInteger(pq.subtract(two)));
BigInteger m = BigInteger.ONE.add(randomBigInteger(pq.subtract(two)));
BigInteger g = BigInteger.ONE;
BigInteger r = BigInteger.ONE;
BigInteger q = BigInteger.ONE;
BigInteger x = BigInteger.ZERO;
BigInteger ys = BigInteger.ZERO;
BigInteger countDown;
while (g.equals(BigInteger.ONE)) {
x = y;
countDown = r;
while (!countDown.equals(BigInteger.ZERO)) {
countDown = countDown.subtract(BigInteger.ONE);
y = (y.modPow(two, pq).add(c)).mod(pq);
}
BigInteger k = BigInteger.ZERO;
while (k.compareTo(r) < 0 && g.equals(BigInteger.ONE)) {
ys = y;
countDown = m.min(r.subtract(k));
while (!countDown.equals(BigInteger.ZERO)) {
countDown = countDown.subtract(BigInteger.ONE);
y = y.modPow(two, pq).add(c).mod(pq);
q = q.multiply(x.subtract(y).abs()).mod(pq);
}
g = q.gcd(pq);
k = k.add(m);
}
r = r.multiply(two);
}
if (g.equals(pq)) {
do {
ys = ys.modPow(two, pq).add(c).mod(pq);
g = x.subtract(ys).abs().gcd(pq);
} while (g.compareTo(BigInteger.ONE) <= 0);
}
x = g;
y = pq.divide(g);
return new Pair<>(x.min(y), x.max(y));
}
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);
sha1.reset();
return result;
}
}