2021-08-04 00:40:09 +02:00
|
|
|
|
using System;
|
2022-01-23 01:28:10 +01:00
|
|
|
|
using System.Buffers.Binary;
|
2021-09-23 09:27:52 +02:00
|
|
|
|
using System.Collections.Generic;
|
2021-08-04 00:40:09 +02:00
|
|
|
|
using System.IO;
|
2021-08-06 07:28:54 +02:00
|
|
|
|
using System.Linq;
|
2021-09-23 09:27:52 +02:00
|
|
|
|
using System.Net;
|
2021-08-06 07:28:54 +02:00
|
|
|
|
using System.Security.Cryptography;
|
2021-08-04 00:40:09 +02:00
|
|
|
|
using System.Text.Json;
|
|
|
|
|
|
|
|
|
|
|
|
namespace WTelegram
|
|
|
|
|
|
{
|
2022-01-27 17:34:16 +01:00
|
|
|
|
internal class Session : IDisposable
|
2021-08-04 00:40:09 +02:00
|
|
|
|
{
|
2022-01-25 16:37:09 +01:00
|
|
|
|
public int ApiId;
|
2021-11-07 09:09:15 +01:00
|
|
|
|
public long UserId;
|
2021-09-23 09:27:52 +02:00
|
|
|
|
public int MainDC;
|
|
|
|
|
|
public Dictionary<int, DCSession> DCSessions = new();
|
2021-10-01 02:22:26 +02:00
|
|
|
|
public TL.DcOption[] DcOptions;
|
2021-09-23 09:27:52 +02:00
|
|
|
|
|
|
|
|
|
|
public class DCSession
|
|
|
|
|
|
{
|
2021-09-28 16:12:20 +02:00
|
|
|
|
public long Id;
|
2021-09-23 09:27:52 +02:00
|
|
|
|
public long AuthKeyID;
|
|
|
|
|
|
public byte[] AuthKey; // 2048-bit = 256 bytes
|
2021-09-25 19:43:39 +02:00
|
|
|
|
public long UserId;
|
2021-09-23 09:27:52 +02:00
|
|
|
|
public long Salt;
|
|
|
|
|
|
public int Seqno;
|
|
|
|
|
|
public long ServerTicksOffset;
|
|
|
|
|
|
public long LastSentMsgId;
|
|
|
|
|
|
public TL.DcOption DataCenter;
|
|
|
|
|
|
|
2021-09-28 16:12:20 +02:00
|
|
|
|
internal Client Client;
|
|
|
|
|
|
internal int DcID => DataCenter?.id ?? 0;
|
2021-09-23 09:27:52 +02:00
|
|
|
|
internal IPEndPoint EndPoint => DataCenter == null ? null : new(IPAddress.Parse(DataCenter.ip_address), DataCenter.port);
|
2021-12-04 00:14:15 +01:00
|
|
|
|
internal void Renew() { Helpers.Log(3, $"Renewing session on DC {DcID}..."); Id = Helpers.RandomLong(); Seqno = 0; LastSentMsgId = 0; }
|
2021-09-23 09:27:52 +02:00
|
|
|
|
}
|
2021-08-04 00:40:09 +02:00
|
|
|
|
|
|
|
|
|
|
public DateTime SessionStart => _sessionStart;
|
|
|
|
|
|
private readonly DateTime _sessionStart = DateTime.UtcNow;
|
2021-09-29 04:38:39 +02:00
|
|
|
|
private readonly SHA256 _sha256 = SHA256.Create();
|
2022-01-26 22:00:52 +01:00
|
|
|
|
private Stream _store;
|
2022-02-04 02:51:14 +01:00
|
|
|
|
private byte[] _reuseKey; // used only if AES Encryptor.CanReuseTransform = false (Mono)
|
2022-01-27 17:34:16 +01:00
|
|
|
|
private byte[] _encrypted = new byte[16];
|
|
|
|
|
|
private ICryptoTransform _encryptor;
|
|
|
|
|
|
private Utf8JsonWriter _jsonWriter;
|
|
|
|
|
|
private readonly MemoryStream _jsonStream = new(4096);
|
|
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
|
{
|
|
|
|
|
|
_sha256.Dispose();
|
|
|
|
|
|
_store.Dispose();
|
|
|
|
|
|
_encryptor.Dispose();
|
|
|
|
|
|
_jsonWriter.Dispose();
|
|
|
|
|
|
_jsonStream.Dispose();
|
|
|
|
|
|
}
|
2021-08-04 00:40:09 +02:00
|
|
|
|
|
2022-01-26 22:00:52 +01:00
|
|
|
|
internal static Session LoadOrCreate(Stream store, byte[] rgbKey)
|
2021-08-04 00:40:09 +02:00
|
|
|
|
{
|
2022-01-27 17:34:16 +01:00
|
|
|
|
using var aes = Aes.Create();
|
|
|
|
|
|
Session session = null;
|
2022-01-26 11:08:33 +01:00
|
|
|
|
try
|
2021-08-04 00:40:09 +02:00
|
|
|
|
{
|
2022-01-26 22:00:52 +01:00
|
|
|
|
var length = (int)store.Length;
|
|
|
|
|
|
if (length > 0)
|
2021-08-04 00:40:09 +02:00
|
|
|
|
{
|
2022-01-23 01:28:10 +01:00
|
|
|
|
var input = new byte[length];
|
2022-01-26 22:00:52 +01:00
|
|
|
|
if (store.Read(input, 0, length) != length)
|
|
|
|
|
|
throw new ApplicationException($"Can't read session block ({store.Position}, {length})");
|
2022-01-27 17:34:16 +01:00
|
|
|
|
using var sha256 = SHA256.Create();
|
|
|
|
|
|
using var decryptor = aes.CreateDecryptor(rgbKey, input[0..16]);
|
|
|
|
|
|
var utf8Json = decryptor.TransformFinalBlock(input, 16, input.Length - 16);
|
|
|
|
|
|
if (!sha256.ComputeHash(utf8Json, 32, utf8Json.Length - 32).SequenceEqual(utf8Json[0..32]))
|
|
|
|
|
|
throw new ApplicationException("Integrity check failed in session loading");
|
|
|
|
|
|
session = JsonSerializer.Deserialize<Session>(utf8Json.AsSpan(32), Helpers.JsonOptions);
|
2021-08-04 10:11:07 +02:00
|
|
|
|
Helpers.Log(2, "Loaded previous session");
|
2021-08-04 00:40:09 +02:00
|
|
|
|
}
|
2022-01-26 11:08:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2022-01-26 22:00:52 +01:00
|
|
|
|
store.Dispose();
|
2022-01-26 11:08:33 +01:00
|
|
|
|
throw new ApplicationException($"Exception while reading session file: {ex.Message}\nDelete the file to start a new session", ex);
|
2021-08-04 00:40:09 +02:00
|
|
|
|
}
|
2022-01-27 17:34:16 +01:00
|
|
|
|
session ??= new Session();
|
|
|
|
|
|
session._store = store;
|
|
|
|
|
|
Encryption.RNG.GetBytes(session._encrypted, 0, 16);
|
|
|
|
|
|
session._encryptor = aes.CreateEncryptor(rgbKey, session._encrypted);
|
2022-02-04 02:51:14 +01:00
|
|
|
|
if (!session._encryptor.CanReuseTransform) session._reuseKey = rgbKey;
|
2022-01-27 17:34:16 +01:00
|
|
|
|
session._jsonWriter = new Utf8JsonWriter(session._jsonStream, default);
|
|
|
|
|
|
return session;
|
2021-08-06 07:28:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-01-27 17:34:16 +01:00
|
|
|
|
internal void Save() // must be called with lock(session)
|
2021-08-06 07:28:54 +02:00
|
|
|
|
{
|
2022-01-27 17:34:16 +01:00
|
|
|
|
JsonSerializer.Serialize(_jsonWriter, this, Helpers.JsonOptions);
|
|
|
|
|
|
var utf8Json = _jsonStream.GetBuffer();
|
|
|
|
|
|
var utf8JsonLen = (int)_jsonStream.Position;
|
|
|
|
|
|
int encryptedLen = 64 + (utf8JsonLen & ~15);
|
2022-03-20 13:09:25 +01:00
|
|
|
|
lock (_store) // while updating _encrypted buffer and writing to store
|
|
|
|
|
|
{
|
|
|
|
|
|
if (encryptedLen > _encrypted.Length)
|
|
|
|
|
|
Array.Copy(_encrypted, _encrypted = new byte[encryptedLen + 256], 16);
|
|
|
|
|
|
_encryptor.TransformBlock(_sha256.ComputeHash(utf8Json, 0, utf8JsonLen), 0, 32, _encrypted, 16);
|
|
|
|
|
|
_encryptor.TransformBlock(utf8Json, 0, encryptedLen - 64, _encrypted, 48);
|
|
|
|
|
|
_encryptor.TransformFinalBlock(utf8Json, encryptedLen - 64, utf8JsonLen & 15).CopyTo(_encrypted, encryptedLen - 16);
|
|
|
|
|
|
if (!_encryptor.CanReuseTransform) // under Mono, AES encryptor is not reusable
|
|
|
|
|
|
using (var aes = Aes.Create())
|
|
|
|
|
|
_encryptor = aes.CreateEncryptor(_reuseKey, _encrypted[0..16]);
|
|
|
|
|
|
_store.Position = 0;
|
|
|
|
|
|
_store.Write(_encrypted, 0, encryptedLen);
|
|
|
|
|
|
_store.SetLength(encryptedLen);
|
|
|
|
|
|
}
|
2022-01-27 17:34:16 +01:00
|
|
|
|
_jsonStream.Position = 0;
|
|
|
|
|
|
_jsonWriter.Reset();
|
2021-08-04 00:40:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-01-26 22:00:52 +01:00
|
|
|
|
|
|
|
|
|
|
internal class SessionStore : FileStream
|
|
|
|
|
|
{
|
|
|
|
|
|
public override long Length { get; }
|
|
|
|
|
|
public override long Position { get => base.Position; set { } }
|
|
|
|
|
|
public override void SetLength(long value) { }
|
2022-01-27 17:34:16 +01:00
|
|
|
|
private readonly byte[] _header = new byte[8];
|
|
|
|
|
|
private int _nextPosition = 8;
|
2022-01-26 22:00:52 +01:00
|
|
|
|
|
|
|
|
|
|
public SessionStore(string pathname)
|
2022-02-24 16:44:27 +01:00
|
|
|
|
: base(pathname, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, 1) // no in-app buffering
|
2022-01-26 22:00:52 +01:00
|
|
|
|
{
|
|
|
|
|
|
if (base.Read(_header, 0, 8) == 8)
|
|
|
|
|
|
{
|
|
|
|
|
|
var position = BinaryPrimitives.ReadInt32LittleEndian(_header);
|
|
|
|
|
|
var length = BinaryPrimitives.ReadInt32LittleEndian(_header.AsSpan(4));
|
|
|
|
|
|
if (position < 0 || length < 0 || position >= 65536 || length >= 32768) { position = 0; length = (int)base.Length; }
|
|
|
|
|
|
base.Position = position;
|
|
|
|
|
|
Length = length;
|
|
|
|
|
|
_nextPosition = position + length;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void Write(byte[] buffer, int offset, int count)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_nextPosition > count * 3) _nextPosition = 8;
|
|
|
|
|
|
base.Position = _nextPosition;
|
|
|
|
|
|
base.Write(buffer, offset, count);
|
|
|
|
|
|
BinaryPrimitives.WriteInt32LittleEndian(_header, _nextPosition);
|
|
|
|
|
|
BinaryPrimitives.WriteInt32LittleEndian(_header.AsSpan(4), count);
|
|
|
|
|
|
_nextPosition += count;
|
|
|
|
|
|
base.Position = 0;
|
|
|
|
|
|
base.Write(_header, 0, 8);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-08-04 00:40:09 +02:00
|
|
|
|
}
|