WTelegramClient/src/Session.cs

111 lines
4.2 KiB
C#
Raw Normal View History

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;
using System.Linq;
2021-09-23 09:27:52 +02:00
using System.Net;
using System.Security.Cryptography;
2021-08-04 00:40:09 +02:00
using System.Text.Json;
namespace WTelegram
{
internal class Session
{
public long UserId;
2021-09-23 09:27:52 +02:00
public int MainDC;
public Dictionary<int, DCSession> DCSessions = new();
public TL.DcOption[] DcOptions;
2021-09-23 09:27:52 +02:00
public class DCSession
{
public long Id;
2021-09-23 09:27:52 +02:00
public long AuthKeyID;
public byte[] AuthKey; // 2048-bit = 256 bytes
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;
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);
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;
private readonly SHA256 _sha256 = SHA256.Create();
2022-01-23 01:28:10 +01:00
private FileStream _fileStream;
private int _nextPosition;
private byte[] _apiHash; // used as AES key for encryption of session file
2022-01-07 01:14:16 +01:00
private static readonly Aes aes = Aes.Create();
2021-08-04 00:40:09 +02:00
internal static Session LoadOrCreate(string pathname, byte[] apiHash)
2021-08-04 00:40:09 +02:00
{
2022-01-23 01:28:10 +01:00
var header = new byte[8];
var fileStream = new FileStream(pathname, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, 1); // no buffering
if (fileStream.Read(header, 0, 8) == 8)
2021-08-04 00:40:09 +02:00
{
try
{
2022-01-23 01:28:10 +01:00
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)fileStream.Length; }
var input = new byte[length];
fileStream.Position = position;
if (fileStream.Read(input, 0, length) != length)
throw new ApplicationException($"Can't read session block ({position}, {length})");
var session = Load(input, apiHash);
session._fileStream = fileStream;
session._nextPosition = position + length;
session._apiHash = apiHash;
2021-08-04 10:11:07 +02:00
Helpers.Log(2, "Loaded previous session");
2021-08-04 00:40:09 +02:00
return session;
}
catch (Exception ex)
{
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-23 01:28:10 +01:00
return new Session { _fileStream = fileStream, _nextPosition = 8, _apiHash = apiHash };
}
2022-01-23 01:28:10 +01:00
internal void Dispose() => _fileStream.Dispose();
internal static Session Load(byte[] input, byte[] apiHash)
{
using var sha256 = SHA256.Create();
using var decryptor = aes.CreateDecryptor(apiHash, 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");
return JsonSerializer.Deserialize<Session>(utf8Json.AsSpan(32), Helpers.JsonOptions);
2021-08-04 00:40:09 +02:00
}
internal void Save()
{
var utf8Json = JsonSerializer.SerializeToUtf8Bytes(this, Helpers.JsonOptions);
var finalBlock = new byte[16];
2021-08-10 08:25:37 +02:00
var output = new byte[(16 + 32 + utf8Json.Length + 16) & ~15];
Encryption.RNG.GetBytes(output, 0, 16);
using var encryptor = aes.CreateEncryptor(_apiHash, output[0..16]);
encryptor.TransformBlock(_sha256.ComputeHash(utf8Json), 0, 32, output, 16);
encryptor.TransformBlock(utf8Json, 0, utf8Json.Length & ~15, output, 48);
utf8Json.AsSpan(utf8Json.Length & ~15).CopyTo(finalBlock);
encryptor.TransformFinalBlock(finalBlock, 0, utf8Json.Length & 15).CopyTo(output.AsMemory(48 + utf8Json.Length & ~15));
lock (this)
{
2022-01-23 01:28:10 +01:00
if (_nextPosition > output.Length * 3) _nextPosition = 8;
_fileStream.Position = _nextPosition;
_fileStream.Write(output, 0, output.Length);
BinaryPrimitives.WriteInt32LittleEndian(finalBlock, _nextPosition);
BinaryPrimitives.WriteInt32LittleEndian(finalBlock.AsSpan(4), output.Length);
_nextPosition += output.Length;
_fileStream.Position = 0;
_fileStream.Write(finalBlock, 0, 8);
}
2021-08-04 00:40:09 +02:00
}
}
}