mirror of
https://github.com/wiz0u/WTelegramClient.git
synced 2026-01-28 11:14:18 +01:00
Implement Future Salts mecanism to prevent replay attacks
This commit is contained in:
parent
753ac12eb1
commit
30618cb316
2
.github/dev.yml
vendored
2
.github/dev.yml
vendored
|
|
@ -2,7 +2,7 @@ pr: none
|
|||
trigger:
|
||||
- master
|
||||
|
||||
name: 3.4.3-dev.$(Rev:r)
|
||||
name: 3.5.1-dev.$(Rev:r)
|
||||
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
|
|
|
|||
2
.github/release.yml
vendored
2
.github/release.yml
vendored
|
|
@ -1,7 +1,7 @@
|
|||
pr: none
|
||||
trigger: none
|
||||
|
||||
name: 3.4.$(Rev:r)
|
||||
name: 3.5.$(Rev:r)
|
||||
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
|
|
|
|||
|
|
@ -684,7 +684,7 @@ namespace WTelegram
|
|||
private static readonly char[] QueryOrFragment = new[] { '?', '#' };
|
||||
|
||||
/// <summary>Return information about a chat/channel based on Invite Link</summary>
|
||||
/// <param name="url">Channel or Invite Link, like https://t.me/+InviteHash, https://t.me/joinchat/InviteHash or https://t.me/channelname</param>
|
||||
/// <param name="url">Public link or Invite link, like https://t.me/+InviteHash, https://t.me/joinchat/InviteHash or https://t.me/channelname<br/>Also work without https:// prefix</param>
|
||||
/// <param name="join"><see langword="true"/> to also join the chat/channel</param>
|
||||
/// <returns>a Chat or Channel, possibly partial Channel information only (with flag <see cref="Channel.Flags.min"/>)</returns>
|
||||
public async Task<ChatBase> AnalyzeInviteLink(string url, bool join = false)
|
||||
|
|
@ -693,7 +693,7 @@ namespace WTelegram
|
|||
start = url.IndexOf('/', start + 2) + 1;
|
||||
int end = url.IndexOfAny(QueryOrFragment, start);
|
||||
if (end == -1) end = url.Length;
|
||||
if (start == 0 || end == start) throw new ArgumentException("Invalid URI");
|
||||
if (start == 0 || end == start) throw new ArgumentException("Invalid URL");
|
||||
string hash;
|
||||
if (url[start] == '+')
|
||||
hash = url[(start + 1)..end];
|
||||
|
|
@ -740,11 +740,11 @@ namespace WTelegram
|
|||
return !ci.flags.HasFlag(ChatInvite.Flags.channel)
|
||||
? new Chat { title = ci.title, photo = chatPhoto, participants_count = ci.participants_count }
|
||||
: new Channel { title = ci.title, photo = chatPhoto, participants_count = ci.participants_count,
|
||||
restriction_reason = rrAbout,
|
||||
flags = (ci.flags.HasFlag(ChatInvite.Flags.broadcast) ? Channel.Flags.broadcast | Channel.Flags.min : Channel.Flags.min) |
|
||||
(ci.flags.HasFlag(ChatInvite.Flags.public_) ? Channel.Flags.has_username : 0) |
|
||||
(ci.flags.HasFlag(ChatInvite.Flags.megagroup) ? Channel.Flags.megagroup : 0) |
|
||||
(ci.flags.HasFlag(ChatInvite.Flags.request_needed) ? Channel.Flags.join_request : 0) };
|
||||
restriction_reason = rrAbout, flags = Channel.Flags.min |
|
||||
(ci.flags.HasFlag(ChatInvite.Flags.broadcast) ? Channel.Flags.broadcast : 0) |
|
||||
(ci.flags.HasFlag(ChatInvite.Flags.public_) ? Channel.Flags.has_username : 0) |
|
||||
(ci.flags.HasFlag(ChatInvite.Flags.megagroup) ? Channel.Flags.megagroup : 0) |
|
||||
(ci.flags.HasFlag(ChatInvite.Flags.request_needed) ? Channel.Flags.join_request : 0) };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -758,7 +758,7 @@ namespace WTelegram
|
|||
int start = url.IndexOf("//");
|
||||
start = url.IndexOf('/', start + 2) + 1;
|
||||
int slash = url.IndexOf('/', start + 2);
|
||||
if (start == 0 || slash == -1) throw new ArgumentException("Invalid URI");
|
||||
if (start == 0 || slash == -1) throw new ArgumentException("Invalid URL");
|
||||
int end = url.IndexOfAny(QueryOrFragment, slash + 1);
|
||||
if (end == -1) end = url.Length;
|
||||
ChatBase chat;
|
||||
|
|
|
|||
|
|
@ -430,13 +430,13 @@ namespace WTelegram
|
|||
_dcSession.ServerTicksOffset = (msgId >> 32) * 10000000 - DateTime.UtcNow.Ticks + 621355968000000000L;
|
||||
var msgStamp = MsgIdToStamp(_lastRecvMsgId = msgId);
|
||||
|
||||
if (serverSalt != _dcSession.Salt) // salt change happens every 30 min
|
||||
if (serverSalt != _dcSession.Salt && serverSalt != _dcSession.Salts?.Values.ElementAtOrDefault(1))
|
||||
{
|
||||
Helpers.Log(2, $"{_dcSession.DcID}>Server salt has changed: {_dcSession.Salt:X} -> {serverSalt:X}");
|
||||
Helpers.Log(3, $"{_dcSession.DcID}>Server salt has changed: {_dcSession.Salt:X} -> {serverSalt:X}");
|
||||
_dcSession.Salt = serverSalt;
|
||||
_saltChangeCounter += 1200; // counter is decreased by KeepAlive (we have margin of 10 min)
|
||||
if (_saltChangeCounter >= 1800)
|
||||
if (++_saltChangeCounter >= 10)
|
||||
throw new WTException("Server salt changed too often! Security issue?");
|
||||
CheckSalt();
|
||||
}
|
||||
if ((seqno & 1) != 0) lock (_msgsToAck) _msgsToAck.Add(msgId);
|
||||
|
||||
|
|
@ -473,6 +473,35 @@ namespace WTelegram
|
|||
};
|
||||
}
|
||||
|
||||
internal void CheckSalt()
|
||||
{
|
||||
lock (_session)
|
||||
{
|
||||
_dcSession.Salts ??= new();
|
||||
if (_dcSession.Salts.Count != 0)
|
||||
{
|
||||
var keys = _dcSession.Salts.Keys;
|
||||
if (keys[^1] == DateTime.MaxValue) return; // GetFutureSalts ongoing
|
||||
var now = DateTime.UtcNow.AddTicks(_dcSession.ServerTicksOffset);
|
||||
for (; keys.Count > 1 && keys[1] < now; _dcSession.Salt = _dcSession.Salts.Values[0])
|
||||
_dcSession.Salts.RemoveAt(0);
|
||||
if (_dcSession.Salts.Count > 48) return;
|
||||
}
|
||||
_dcSession.Salts[DateTime.MaxValue] = 0;
|
||||
}
|
||||
Task.Delay(5000).ContinueWith(_ => this.GetFutureSalts(128).ContinueWith(gfs =>
|
||||
{
|
||||
lock (_session)
|
||||
{
|
||||
_dcSession.Salts.Remove(DateTime.MaxValue);
|
||||
foreach (var entry in gfs.Result.salts)
|
||||
_dcSession.Salts[entry.valid_since] = entry.salt;
|
||||
_dcSession.Salt = _dcSession.Salts.Values[0];
|
||||
_session.Save();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
internal MsgContainer ReadMsgContainer(BinaryReader reader)
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
|
|
@ -648,7 +677,8 @@ namespace WTelegram
|
|||
}
|
||||
break;
|
||||
case 48: // incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it)
|
||||
_dcSession.Salt = ((BadServerSalt)badMsgNotification).new_server_salt; //TODO: GetFutureSalts
|
||||
_dcSession.Salt = ((BadServerSalt)badMsgNotification).new_server_salt;
|
||||
CheckSalt();
|
||||
break;
|
||||
default:
|
||||
retryLast = false;
|
||||
|
|
@ -817,7 +847,7 @@ namespace WTelegram
|
|||
#endif
|
||||
await _networkStream.WriteAsync(preamble, 0, preamble.Length, _cts.Token);
|
||||
|
||||
_saltChangeCounter = 0;
|
||||
_dcSession.Salts?.Remove(DateTime.MaxValue);
|
||||
_reactorTask = Reactor(_networkStream, _cts);
|
||||
_sendSemaphore.Release();
|
||||
|
||||
|
|
@ -840,7 +870,6 @@ namespace WTelegram
|
|||
query = new TL.Methods.Help_GetConfig()
|
||||
});
|
||||
_session.DcOptions = TLConfig.dc_options;
|
||||
_saltChangeCounter = 0;
|
||||
if (_dcSession.DataCenter == null)
|
||||
{
|
||||
_dcSession.DataCenter = _session.DcOptions.Where(dc => dc.id == TLConfig.this_dc)
|
||||
|
|
@ -856,7 +885,7 @@ namespace WTelegram
|
|||
{
|
||||
lock (_session) _session.Save();
|
||||
}
|
||||
Helpers.Log(2, $"Connected to {(TLConfig.test_mode ? "Test DC" : "DC")} {TLConfig.this_dc}... {TLConfig.flags & (Config.Flags)~0xE00U}");
|
||||
Helpers.Log(2, $"Connected to {(TLConfig.test_mode ? "Test DC" : "DC")} {TLConfig.this_dc}... {TLConfig.flags & (Config.Flags)~0x18E00U}");
|
||||
}
|
||||
|
||||
private async Task KeepAlive(CancellationToken ct)
|
||||
|
|
@ -865,7 +894,6 @@ namespace WTelegram
|
|||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(Math.Abs(PingInterval) * 1000, ct);
|
||||
if (_saltChangeCounter > 0) _saltChangeCounter -= Math.Abs(PingInterval);
|
||||
if (PingInterval <= 0)
|
||||
await this.Ping(ping_id++);
|
||||
else // see https://core.telegram.org/api/optimisation#grouping-updates
|
||||
|
|
@ -1224,6 +1252,7 @@ namespace WTelegram
|
|||
}
|
||||
else
|
||||
{
|
||||
CheckSalt();
|
||||
using var clearStream = new MemoryStream(1024);
|
||||
using var clearWriter = new BinaryWriter(clearStream);
|
||||
clearWriter.Write(_dcSession.AuthKey, 88, 32);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace WTelegram
|
|||
public byte[] AuthKey; // 2048-bit = 256 bytes
|
||||
public long UserId;
|
||||
public long Salt;
|
||||
public SortedList<DateTime, long> Salts;
|
||||
public int Seqno;
|
||||
public long ServerTicksOffset;
|
||||
public long LastSentMsgId;
|
||||
|
|
|
|||
Loading…
Reference in a new issue