mirror of
https://github.com/wiz0u/WTelegramClient.git
synced 2025-12-06 06:52:01 +01:00
Implement Telegram protocol safety checks
This commit is contained in:
parent
e205244cf7
commit
f2a1dbc20d
2
ci.yml
2
ci.yml
|
|
@ -2,7 +2,7 @@ pr: none
|
|||
trigger:
|
||||
- master
|
||||
|
||||
name: 0.9.2-ci.$(Rev:r)
|
||||
name: 0.9.3-ci.$(Rev:r)
|
||||
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
|
|
|
|||
|
|
@ -233,7 +233,6 @@ namespace WTelegram
|
|||
}
|
||||
var buffer = memStream.GetBuffer();
|
||||
int frameLength = (int)memStream.Length;
|
||||
//TODO: support Quick Ack?
|
||||
BinaryPrimitives.WriteInt32LittleEndian(buffer, frameLength + 4); // patch frame_len with correct value
|
||||
uint crc = Force.Crc32.Crc32Algorithm.Compute(buffer, 0, frameLength);
|
||||
writer.Write(crc); // int32 frame_crc
|
||||
|
|
@ -298,8 +297,9 @@ namespace WTelegram
|
|||
if (data.Length < 24) // authKeyId+msgId+length+ctorNb | authKeyId+msgKey
|
||||
throw new ApplicationException($"Packet payload too small: {data.Length}");
|
||||
|
||||
//TODO: ignore msgId <= lastRecvMsgId, ignore MsgId >= 30 sec in the future or < 300 sec in the past
|
||||
long authKeyId = BinaryPrimitives.ReadInt64LittleEndian(data);
|
||||
if (authKeyId != _session.AuthKeyID)
|
||||
throw new ApplicationException($"Received a packet encrypted with unexpected key {authKeyId:X}");
|
||||
if (authKeyId == 0) // Unencrypted message
|
||||
{
|
||||
using var reader = new BinaryReader(new MemoryStream(data, 8, data.Length - 8));
|
||||
|
|
@ -312,8 +312,6 @@ namespace WTelegram
|
|||
Helpers.Log(1, $"Receiving {obj.GetType().Name,-50} {_session.MsgIdToStamp(msgId):u} {((msgId & 2) == 0 ? "": "NAR")} unencrypted");
|
||||
return obj;
|
||||
}
|
||||
else if (authKeyId != _session.AuthKeyID)
|
||||
throw new ApplicationException($"Received a packet encrypted with unexpected key {authKeyId:X}");
|
||||
else
|
||||
{
|
||||
#if MTPROTO1
|
||||
|
|
@ -330,6 +328,7 @@ namespace WTelegram
|
|||
var msgId = _lastRecvMsgId = reader.ReadInt64();// int64 message_id
|
||||
var seqno = reader.ReadInt32(); // int32 msg_seqno
|
||||
var length = reader.ReadInt32(); // int32 message_data_length
|
||||
var msgStamp = _session.MsgIdToStamp(msgId);
|
||||
|
||||
if (serverSalt != _session.Salt)
|
||||
{
|
||||
|
|
@ -341,6 +340,8 @@ namespace WTelegram
|
|||
if (sessionId != _session.Id) throw new ApplicationException($"Unexpected session ID {_session.Id} != {_session.Id}");
|
||||
if ((msgId & 1) == 0) throw new ApplicationException($"Invalid server msgId {msgId}");
|
||||
if ((seqno & 1) != 0) lock(_msgsToAck) _msgsToAck.Add(msgId);
|
||||
if ((msgStamp - DateTime.UtcNow).Ticks / TimeSpan.TicksPerSecond is > 30 or < -300)
|
||||
return null;
|
||||
#if MTPROTO1
|
||||
if (decrypted_data.Length - 32 - length is < 0 or > 15) throw new ApplicationException($"Unexpected decrypted message_data_length {length} / {decrypted_data.Length - 32}");
|
||||
if (!data.AsSpan(8, 16).SequenceEqual(Sha1Recv.ComputeHash(decrypted_data, 0, 32 + length).AsSpan(4)))
|
||||
|
|
@ -355,18 +356,18 @@ namespace WTelegram
|
|||
var ctorNb = reader.ReadUInt32();
|
||||
if (ctorNb == Schema.MsgContainer)
|
||||
{
|
||||
Helpers.Log(1, $"Receiving {"MsgContainer",-50} {_session.MsgIdToStamp(msgId):u} (svc)");
|
||||
Helpers.Log(1, $"Receiving {"MsgContainer",-50} {msgStamp:u} (svc)");
|
||||
return ReadMsgContainer(reader);
|
||||
}
|
||||
else if (ctorNb == Schema.RpcResult)
|
||||
{
|
||||
Helpers.Log(1, $"Receiving {"RpcResult",-50} {_session.MsgIdToStamp(msgId):u}");
|
||||
Helpers.Log(1, $"Receiving {"RpcResult",-50} {msgStamp:u}");
|
||||
return ReadRpcResult(reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
var obj = reader.ReadTLObject(ctorNb);
|
||||
Helpers.Log(1, $"Receiving {obj.GetType().Name,-50} {_session.MsgIdToStamp(msgId):u} {((seqno & 1) != 0 ? "" : "(svc)")} {((msgId & 2) == 0 ? "" : "NAR")}");
|
||||
Helpers.Log(1, $"Receiving {obj.GetType().Name,-50} {msgStamp:u} {((seqno & 1) != 0 ? "" : "(svc)")} {((msgId & 2) == 0 ? "" : "NAR")}");
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
|
@ -534,10 +535,20 @@ namespace WTelegram
|
|||
|
||||
private async Task Reactor(CancellationToken ct)
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
try
|
||||
{
|
||||
var obj = await RecvInternalAsync(ct);
|
||||
await HandleMessageAsync(obj);
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
var obj = await RecvInternalAsync(ct);
|
||||
if (obj == null) continue; // ignored message :|
|
||||
await HandleMessageAsync(obj);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{ }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Helpers.Log(5, $"An exception occured in the reactor: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,10 +30,10 @@ namespace WTelegram
|
|||
return result;
|
||||
}
|
||||
|
||||
internal static int GetBitLength(this BigInteger bigInteger)
|
||||
internal static long GetBitLength(this BigInteger bigInteger)
|
||||
{
|
||||
var bytes = bigInteger.ToByteArray();
|
||||
var length = bytes.Length * 8;
|
||||
var length = bytes.LongLength * 8L;
|
||||
int lastByte = bytes[^1];
|
||||
while ((lastByte & 0x80) == 0) { length--; lastByte = (lastByte << 1) + 1; }
|
||||
return length;
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ namespace WTelegram
|
|||
RNG.GetBytes(bData);
|
||||
var b = BigEndianInteger(bData);
|
||||
var g_b = BigInteger.ModPow(serverDHinnerData.g, b, dh_prime);
|
||||
ValidityChecksDH(g_a, g_b, dh_prime);
|
||||
var clientDHinnerData = new ClientDHInnerData
|
||||
{
|
||||
nonce = nonce,
|
||||
|
|
@ -193,7 +194,7 @@ namespace WTelegram
|
|||
|
||||
private static void ValidityChecks(BigInteger p, int g)
|
||||
{
|
||||
//TODO: check whether p is a safe prime (meaning that both p and (p - 1) / 2 are prime)
|
||||
Helpers.Log(2, "Verifying encryption key safety... (this happens only during session negociation)");
|
||||
// check that 2^2047 <= p < 2^2048
|
||||
if (p.GetBitLength() != 2048) throw new ApplicationException("p is not 2048-bit number");
|
||||
// check that g generates a cyclic subgroup of prime order (p - 1) / 2, i.e. is a quadratic residue mod p.
|
||||
|
|
@ -209,8 +210,19 @@ namespace WTelegram
|
|||
_ => true,
|
||||
})
|
||||
throw new ApplicationException("Bad prime mod 4g");
|
||||
//TODO: check that g, g_a and g_b are greater than 1 and less than dh_prime - 1.
|
||||
// check whether p is a safe prime (meaning that both p and (p - 1) / 2 are prime)
|
||||
if (!p.IsProbablePrime()) throw new ApplicationException("p is not a prime number");
|
||||
if (!((p - 1) / 2).IsProbablePrime()) throw new ApplicationException("(p - 1) / 2 is not a prime number");
|
||||
}
|
||||
|
||||
private static void ValidityChecksDH(BigInteger g_a, BigInteger g_b, BigInteger dh_prime)
|
||||
{
|
||||
// check that g, g_a and g_b are greater than 1 and less than dh_prime - 1.
|
||||
// We recommend checking that g_a and g_b are between 2^{2048-64} and dh_prime - 2^{2048-64} as well.
|
||||
var l = BigInteger.One << (2048 - 64);
|
||||
var r = dh_prime - l;
|
||||
if (g_a < l || g_a > r || g_b < l || g_b > r)
|
||||
throw new ApplicationException("g^a or g^b is not between 2^{2048-64} and dh_prime - 2^{2048-64}");
|
||||
}
|
||||
|
||||
[TLDef(0x7A19CB76)] //RSA_public_key#7a19cb76 n:bytes e:bytes = RSAPublicKey
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ namespace WTelegram
|
|||
{
|
||||
public class Generator
|
||||
{
|
||||
//TODO: generate BinaryReader/Writer serialization for objects too?
|
||||
readonly Dictionary<int, string> ctorToTypes = new();
|
||||
readonly HashSet<string> allTypes = new();
|
||||
readonly Dictionary<int, Dictionary<string, TypeInfo>> typeInfosByLayer = new();
|
||||
|
|
@ -25,7 +24,7 @@ namespace WTelegram
|
|||
{
|
||||
Console.WriteLine("Fetch web pages...");
|
||||
#if DEBUG
|
||||
currentLayer = await Task.FromResult(0);
|
||||
currentLayer = await Task.FromResult(TL.Schema.Layer);
|
||||
#else
|
||||
using var http = new HttpClient();
|
||||
var html = await http.GetStringAsync("https://core.telegram.org/api/layers");
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ namespace WTelegram
|
|||
{
|
||||
public static class Helpers
|
||||
{
|
||||
// int argument is the LogLevel: https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel
|
||||
public static Action<int, string> Log { get; set; } = DefaultLogger;
|
||||
|
||||
public static readonly System.Text.Json.JsonSerializerOptions JsonOptions = new(System.Text.Json.JsonSerializerDefaults.Web) { IncludeFields = true, WriteIndented = true };
|
||||
|
|
@ -39,7 +40,7 @@ namespace WTelegram
|
|||
{
|
||||
int i;
|
||||
var temp = value;
|
||||
for (i = 1; (temp >>= 8) != 0; i++);
|
||||
for (i = 1; (temp >>= 8) != 0; i++) ;
|
||||
var result = new byte[i];
|
||||
while (--i >= 0) { result[i] = (byte)value; value >>= 8; }
|
||||
return result;
|
||||
|
|
@ -54,6 +55,15 @@ namespace WTelegram
|
|||
return result;
|
||||
}
|
||||
|
||||
internal static byte[] To256Bytes(this BigInteger bi)
|
||||
{
|
||||
var bigEndian = bi.ToByteArray(true, true);
|
||||
if (bigEndian.Length == 256) return bigEndian;
|
||||
var result = new byte[256];
|
||||
bigEndian.CopyTo(result, 256 - bigEndian.Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static ulong PQFactorize(ulong pq) // ported from https://github.com/tdlib/td/blob/master/tdutils/td/utils/crypto.cpp#L90
|
||||
{
|
||||
if (pq < 2) return 1;
|
||||
|
|
@ -136,13 +146,44 @@ namespace WTelegram
|
|||
}
|
||||
}
|
||||
|
||||
internal static byte[] To256Bytes(this BigInteger bi)
|
||||
// Miller–Rabin primality test
|
||||
public static bool IsProbablePrime(this BigInteger n, int k = 64)
|
||||
{
|
||||
var bigEndian = bi.ToByteArray(true, true);
|
||||
if (bigEndian.Length == 256) return bigEndian;
|
||||
var result = new byte[256];
|
||||
bigEndian.CopyTo(result, 256 - bigEndian.Length);
|
||||
return result;
|
||||
var n_minus_one = n - BigInteger.One;
|
||||
if (n_minus_one.Sign <= 0) return false;
|
||||
|
||||
int s;
|
||||
var d = n_minus_one;
|
||||
for (s = 0; d.IsEven; s++) d >>= 1;
|
||||
|
||||
var bitLen = n.GetBitLength();
|
||||
var randomBytes = new byte[bitLen / 8 + 1];
|
||||
var lastByteMask = (byte)((1 << (int)(bitLen % 8)) - 1);
|
||||
BigInteger a;
|
||||
for (int i = 0; i < k; i++)
|
||||
{
|
||||
do
|
||||
{
|
||||
Encryption.RNG.GetBytes(randomBytes);
|
||||
randomBytes[^1] &= lastByteMask; // we don't want more bits than necessary
|
||||
a = new BigInteger(randomBytes);
|
||||
}
|
||||
while (a < 3 || a >= n_minus_one);
|
||||
a--;
|
||||
|
||||
var x = BigInteger.ModPow(a, d, n);
|
||||
if (x.IsOne || x == n_minus_one) continue;
|
||||
|
||||
int r;
|
||||
for (r = s - 1; r > 0; r--)
|
||||
{
|
||||
x = BigInteger.ModPow(x, 2, n);
|
||||
if (x.IsOne) return false;
|
||||
if (x == n_minus_one) break;
|
||||
}
|
||||
if (r == 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue