Now throwing WTException. Class hierarchy is TL.RpcException : WTelegram.WTException : ApplicationException

This commit is contained in:
Wizou 2023-04-02 13:44:23 +02:00
parent 621a88bb9f
commit 9af6404eff
12 changed files with 108 additions and 102 deletions

2
.github/dev.yml vendored
View file

@ -2,7 +2,7 @@ pr: none
trigger: trigger:
- master - master
name: 3.3.4-dev.$(Rev:r) name: 3.4.1-dev.$(Rev:r)
pool: pool:
vmImage: ubuntu-latest vmImage: ubuntu-latest

2
.github/release.yml vendored
View file

@ -1,7 +1,7 @@
pr: none pr: none
trigger: none trigger: none
name: 3.3.$(Rev:r) name: 3.4.$(Rev:r)
pool: pool:
vmImage: ubuntu-latest vmImage: ubuntu-latest

View file

@ -55,7 +55,7 @@ namespace WTelegram
lock (tasks) tasks[file_part] = task; lock (tasks) tasks[file_part] = task;
if (!isBig) if (!isBig)
md5.TransformBlock(bytes, 0, read, null, 0); md5.TransformBlock(bytes, 0, read, null, 0);
if (read < FilePartSize && bytesLeft != 0) throw new ApplicationException($"Failed to fully read stream ({read},{bytesLeft})"); if (read < FilePartSize && bytesLeft != 0) throw new WTException($"Failed to fully read stream ({read},{bytesLeft})");
async Task SavePart(int file_part, byte[] bytes) async Task SavePart(int file_part, byte[] bytes)
{ {
@ -331,7 +331,7 @@ namespace WTelegram
if (fileSize != 0 && fileOffset >= fileSize) if (fileSize != 0 && fileOffset >= fileSize)
{ {
if (await task != ((fileSize - 1) % FilePartSize) + 1) if (await task != ((fileSize - 1) % FilePartSize) + 1)
throw new ApplicationException("Downloaded file size does not match expected file size"); throw new WTException("Downloaded file size does not match expected file size");
break; break;
} }
@ -362,7 +362,7 @@ namespace WTelegram
_parallelTransfers.Release(); _parallelTransfers.Release();
} }
if (fileBase is not Upload_File fileData) if (fileBase is not Upload_File fileData)
throw new ApplicationException("Upload_GetFile returned unsupported " + fileBase?.GetType().Name); throw new WTException("Upload_GetFile returned unsupported " + fileBase?.GetType().Name);
if (fileData.bytes.Length != FilePartSize) abort = true; if (fileData.bytes.Length != FilePartSize) abort = true;
if (fileData.bytes.Length != 0) if (fileData.bytes.Length != 0)
{ {
@ -480,7 +480,7 @@ namespace WTelegram
mds.messages = messageList.ToArray(); mds.messages = messageList.ToArray();
return mds; return mds;
case Messages_Dialogs md: return md; case Messages_Dialogs md: return md;
default: throw new ApplicationException("Messages_GetDialogs returned unexpected " + dialogs?.GetType().Name); default: throw new WTException("Messages_GetDialogs returned unexpected " + dialogs?.GetType().Name);
} }
} }
@ -762,13 +762,13 @@ namespace WTelegram
long chatId = long.Parse(url[(start + 2)..slash]); long chatId = long.Parse(url[(start + 2)..slash]);
var chats = await this.Channels_GetChannels(new InputChannel(chatId, 0)); var chats = await this.Channels_GetChannels(new InputChannel(chatId, 0));
if (!chats.chats.TryGetValue(chatId, out chat)) if (!chats.chats.TryGetValue(chatId, out chat))
throw new ApplicationException($"Channel {chatId} not found"); throw new WTException($"Channel {chatId} not found");
} }
else else
{ {
var resolved = await this.Contacts_ResolveUsername(url[start..slash]); var resolved = await this.Contacts_ResolveUsername(url[start..slash]);
chat = resolved.Chat; chat = resolved.Chat;
if (chat is null) throw new ApplicationException($"@{url[start..slash]} is not a Chat/Channel"); if (chat is null) throw new WTException($"@{url[start..slash]} is not a Chat/Channel");
} }
int msgId = int.Parse(url[(slash + 1)..end]); int msgId = int.Parse(url[(slash + 1)..end]);
return await this.Channels_GetMessages((Channel)chat, msgId) as Messages_ChannelMessages; return await this.Channels_GetMessages((Channel)chat, msgId) as Messages_ChannelMessages;

View file

@ -120,7 +120,7 @@ namespace WTelegram
internal Task<string> ConfigAsync(string what) => Task.Run(() => Config(what)); internal Task<string> ConfigAsync(string what) => Task.Run(() => Config(what));
internal string Config(string what) internal string Config(string what)
=> _config(what) ?? DefaultConfig(what) ?? throw new ApplicationException("You must provide a config value for " + what); => _config(what) ?? DefaultConfig(what) ?? throw new WTException("You must provide a config value for " + what);
/// <summary>Default config values, used if your Config callback returns <see langword="null"/></summary> /// <summary>Default config values, used if your Config callback returns <see langword="null"/></summary>
public static string DefaultConfig(string what) => what switch public static string DefaultConfig(string what) => what switch
@ -232,7 +232,7 @@ namespace WTelegram
if ((dcSession?.AuthKeyID ?? 0) == 0) // we will need to negociate an AuthKey => can't use media_only DC if ((dcSession?.AuthKeyID ?? 0) == 0) // we will need to negociate an AuthKey => can't use media_only DC
flags &= ~DcOption.Flags.media_only; flags &= ~DcOption.Flags.media_only;
var dcOptions = _session.DcOptions.Where(dc => dc.id == dcId).OrderBy(dc => dc.flags ^ flags); var dcOptions = _session.DcOptions.Where(dc => dc.id == dcId).OrderBy(dc => dc.flags ^ flags);
var dcOption = dcOptions.FirstOrDefault() ?? throw new ApplicationException($"Could not find adequate dc_option for DC {dcId}"); var dcOption = dcOptions.FirstOrDefault() ?? throw new WTException($"Could not find adequate dc_option for DC {dcId}");
dcSession ??= new Session.DCSession { Id = Helpers.RandomLong() }; // create new session only if not already existing dcSession ??= new Session.DCSession { Id = Helpers.RandomLong() }; // create new session only if not already existing
dcSession.DataCenter = dcOption; dcSession.DataCenter = dcOption;
return _session.DCSessions[dcId] = dcSession; return _session.DCSessions[dcId] = dcSession;
@ -267,7 +267,7 @@ namespace WTelegram
{ {
var authorization = await altSession.Client.Auth_ImportAuthorization(exported.id, exported.bytes); var authorization = await altSession.Client.Auth_ImportAuthorization(exported.id, exported.bytes);
if (authorization is not Auth_Authorization { user: User user }) if (authorization is not Auth_Authorization { user: User user })
throw new ApplicationException("Failed to get Authorization: " + authorization.GetType().Name); throw new WTException("Failed to get Authorization: " + authorization.GetType().Name);
altSession.UserId = user.id; altSession.UserId = user.id;
} }
} }
@ -289,19 +289,19 @@ namespace WTelegram
try try
{ {
if (await stream.FullReadAsync(data, 4, cts.Token) != 4) if (await stream.FullReadAsync(data, 4, cts.Token) != 4)
throw new ApplicationException(ConnectionShutDown); throw new WTException(ConnectionShutDown);
#if OBFUSCATION #if OBFUSCATION
_recvCtr.EncryptDecrypt(data, 4); _recvCtr.EncryptDecrypt(data, 4);
#endif #endif
int payloadLen = BinaryPrimitives.ReadInt32LittleEndian(data); int payloadLen = BinaryPrimitives.ReadInt32LittleEndian(data);
if (payloadLen <= 0) if (payloadLen <= 0)
throw new ApplicationException("Could not read frame data : Invalid payload length"); throw new WTException("Could not read frame data : Invalid payload length");
else if (payloadLen > data.Length) else if (payloadLen > data.Length)
data = new byte[payloadLen]; data = new byte[payloadLen];
else if (Math.Max(payloadLen, MinBufferSize) < data.Length / 4) else if (Math.Max(payloadLen, MinBufferSize) < data.Length / 4)
data = new byte[Math.Max(payloadLen, MinBufferSize)]; data = new byte[Math.Max(payloadLen, MinBufferSize)];
if (await stream.FullReadAsync(data, payloadLen, cts.Token) != payloadLen) if (await stream.FullReadAsync(data, payloadLen, cts.Token) != payloadLen)
throw new ApplicationException("Could not read frame data : Connection shut down"); throw new WTException("Could not read frame data : Connection shut down");
#if OBFUSCATION #if OBFUSCATION
_recvCtr.EncryptDecrypt(data, payloadLen); _recvCtr.EncryptDecrypt(data, payloadLen);
#endif #endif
@ -310,7 +310,7 @@ namespace WTelegram
catch (Exception ex) // an exception in RecvAsync is always fatal catch (Exception ex) // an exception in RecvAsync is always fatal
{ {
if (cts.IsCancellationRequested) return; if (cts.IsCancellationRequested) return;
bool disconnectedAltDC = !IsMainDC && ex is ApplicationException { Message: ConnectionShutDown } or IOException { InnerException: SocketException }; bool disconnectedAltDC = !IsMainDC && ex is WTException { Message: ConnectionShutDown } or IOException { InnerException: SocketException };
if (disconnectedAltDC) if (disconnectedAltDC)
Helpers.Log(3, $"{_dcSession.DcID}>Alt DC disconnected: {ex.Message}"); Helpers.Log(3, $"{_dcSession.DcID}>Alt DC disconnected: {ex.Message}");
else else
@ -378,35 +378,35 @@ namespace WTelegram
throw new RpcException(error_code, TransportError(error_code)); throw new RpcException(error_code, TransportError(error_code));
} }
if (dataLen < 24) // authKeyId+msgId+length+ctorNb | authKeyId+msgKey if (dataLen < 24) // authKeyId+msgId+length+ctorNb | authKeyId+msgKey
throw new ApplicationException($"Packet payload too small: {dataLen}"); throw new WTException($"Packet payload too small: {dataLen}");
long authKeyId = BinaryPrimitives.ReadInt64LittleEndian(data); long authKeyId = BinaryPrimitives.ReadInt64LittleEndian(data);
if (authKeyId != _dcSession.AuthKeyID) if (authKeyId != _dcSession.AuthKeyID)
throw new ApplicationException($"Received a packet encrypted with unexpected key {authKeyId:X}"); throw new WTException($"Received a packet encrypted with unexpected key {authKeyId:X}");
if (authKeyId == 0) // Unencrypted message if (authKeyId == 0) // Unencrypted message
{ {
using var reader = new BinaryReader(new MemoryStream(data, 8, dataLen - 8)); using var reader = new BinaryReader(new MemoryStream(data, 8, dataLen - 8));
long msgId = _lastRecvMsgId = reader.ReadInt64(); long msgId = _lastRecvMsgId = reader.ReadInt64();
if ((msgId & 1) == 0) throw new ApplicationException($"Invalid server msgId {msgId}"); if ((msgId & 1) == 0) throw new WTException($"Invalid server msgId {msgId}");
int length = reader.ReadInt32(); int length = reader.ReadInt32();
dataLen -= 20; dataLen -= 20;
if (length > dataLen || length < dataLen - (_paddedMode ? 15 : 0)) if (length > dataLen || length < dataLen - (_paddedMode ? 15 : 0))
throw new ApplicationException($"Unexpected unencrypted/padding length {dataLen} - {length}"); throw new WTException($"Unexpected unencrypted/padding length {dataLen} - {length}");
var obj = reader.ReadTLObject(); var obj = reader.ReadTLObject();
Helpers.Log(1, $"{_dcSession.DcID}>Receiving {obj.GetType().Name,-40} {MsgIdToStamp(msgId):u} clear{((msgId & 2) == 0 ? "" : " NAR")}"); Helpers.Log(1, $"{_dcSession.DcID}>Receiving {obj.GetType().Name,-40} {MsgIdToStamp(msgId):u} clear{((msgId & 2) == 0 ? "" : " NAR")}");
if (_bareRpc == null) throw new ApplicationException("Shouldn't receive unencrypted packet at this point"); if (_bareRpc == null) throw new WTException("Shouldn't receive unencrypted packet at this point");
return obj; return obj;
} }
else else
{ {
byte[] decrypted_data = EncryptDecryptMessage(data.AsSpan(24, (dataLen - 24) & ~0xF), false, 8, _dcSession.AuthKey, data, 8, _sha256Recv); byte[] decrypted_data = EncryptDecryptMessage(data.AsSpan(24, (dataLen - 24) & ~0xF), false, 8, _dcSession.AuthKey, data, 8, _sha256Recv);
if (decrypted_data.Length < 36) // header below+ctorNb if (decrypted_data.Length < 36) // header below+ctorNb
throw new ApplicationException($"Decrypted packet too small: {decrypted_data.Length}"); throw new WTException($"Decrypted packet too small: {decrypted_data.Length}");
_sha256Recv.TransformBlock(_dcSession.AuthKey, 96, 32, null, 0); _sha256Recv.TransformBlock(_dcSession.AuthKey, 96, 32, null, 0);
_sha256Recv.TransformFinalBlock(decrypted_data, 0, decrypted_data.Length); _sha256Recv.TransformFinalBlock(decrypted_data, 0, decrypted_data.Length);
if (!data.AsSpan(8, 16).SequenceEqual(_sha256Recv.Hash.AsSpan(8, 16))) if (!data.AsSpan(8, 16).SequenceEqual(_sha256Recv.Hash.AsSpan(8, 16)))
throw new ApplicationException("Mismatch between MsgKey & decrypted SHA256"); throw new WTException("Mismatch between MsgKey & decrypted SHA256");
_sha256Recv.Initialize(); _sha256Recv.Initialize();
using var reader = new BinaryReader(new MemoryStream(decrypted_data)); using var reader = new BinaryReader(new MemoryStream(decrypted_data));
var serverSalt = reader.ReadInt64(); // int64 salt var serverSalt = reader.ReadInt64(); // int64 salt
@ -415,10 +415,10 @@ namespace WTelegram
var seqno = reader.ReadInt32(); // int32 msg_seqno var seqno = reader.ReadInt32(); // int32 msg_seqno
var length = reader.ReadInt32(); // int32 message_data_length var length = reader.ReadInt32(); // int32 message_data_length
if (length < 0 || length % 4 != 0) throw new ApplicationException($"Invalid message_data_length: {length}"); if (length < 0 || length % 4 != 0) throw new WTException($"Invalid message_data_length: {length}");
if (decrypted_data.Length - 32 - length is < 12 or > 1024) throw new ApplicationException($"Invalid message padding length: {decrypted_data.Length - 32}-{length}"); if (decrypted_data.Length - 32 - length is < 12 or > 1024) throw new WTException($"Invalid message padding length: {decrypted_data.Length - 32}-{length}");
if (sessionId != _dcSession.Id) throw new ApplicationException($"Unexpected session ID: {sessionId} != {_dcSession.Id}"); if (sessionId != _dcSession.Id) throw new WTException($"Unexpected session ID: {sessionId} != {_dcSession.Id}");
if ((msgId & 1) == 0) throw new ApplicationException($"msg_id is not odd: {msgId}"); if ((msgId & 1) == 0) throw new WTException($"msg_id is not odd: {msgId}");
if (!_dcSession.CheckNewMsgId(msgId)) if (!_dcSession.CheckNewMsgId(msgId))
{ {
Helpers.Log(3, $"{_dcSession.DcID}>Ignoring duplicate or old msg_id {msgId}"); Helpers.Log(3, $"{_dcSession.DcID}>Ignoring duplicate or old msg_id {msgId}");
@ -434,7 +434,7 @@ namespace WTelegram
_dcSession.Salt = serverSalt; _dcSession.Salt = serverSalt;
_saltChangeCounter += 1200; // counter is decreased by KeepAlive (we have margin of 10 min) _saltChangeCounter += 1200; // counter is decreased by KeepAlive (we have margin of 10 min)
if (_saltChangeCounter >= 1800) if (_saltChangeCounter >= 1800)
throw new ApplicationException("Server salt changed too often! Security issue?"); throw new WTException("Server salt changed too often! Security issue?");
} }
if ((seqno & 1) != 0) lock (_msgsToAck) _msgsToAck.Add(msgId); if ((seqno & 1) != 0) lock (_msgsToAck) _msgsToAck.Add(msgId);
@ -590,7 +590,7 @@ namespace WTelegram
return; return;
} }
else if (_dcSession.AuthKeyID == 0) else if (_dcSession.AuthKeyID == 0)
throw new ApplicationException($"Received a {obj.GetType()} incompatible with expected bare {rpc?.type}"); throw new WTException($"Received a {obj.GetType()} incompatible with expected bare {rpc?.type}");
lock (_pendingRpcs) lock (_pendingRpcs)
_pendingRpcs[_bareRpc.msgId] = _bareRpc; _pendingRpcs[_bareRpc.msgId] = _bareRpc;
} }
@ -664,7 +664,7 @@ namespace WTelegram
else if (PullPendingRequest(badMsgNotification.bad_msg_id) is Rpc rpc) else if (PullPendingRequest(badMsgNotification.bad_msg_id) is Rpc rpc)
{ {
if (_bareRpc?.msgId == badMsgNotification.bad_msg_id) _bareRpc = null; if (_bareRpc?.msgId == badMsgNotification.bad_msg_id) _bareRpc = null;
rpc.tcs.SetException(new ApplicationException($"BadMsgNotification {badMsgNotification.error_code}")); rpc.tcs.SetException(new WTException($"BadMsgNotification {badMsgNotification.error_code}"));
} }
else else
RaiseUpdate(obj); RaiseUpdate(obj);
@ -879,7 +879,7 @@ namespace WTelegram
/// <param name="loginInfo">First call should be with phone number<br/>Further calls should be with the requested configuration value</param> /// <param name="loginInfo">First call should be with phone number<br/>Further calls should be with the requested configuration value</param>
/// <returns>Configuration item requested to continue login, or <see langword="null"/> when login is successful<br/> /// <returns>Configuration item requested to continue login, or <see langword="null"/> when login is successful<br/>
/// Possible values: <b>verification_code</b>, <b>name</b> (signup), <b>password</b> (2FA), <b>email</b> &amp; <b>email_verification_code</b> (email registration)</returns> /// Possible values: <b>verification_code</b>, <b>name</b> (signup), <b>password</b> (2FA), <b>email</b> &amp; <b>email_verification_code</b> (email registration)</returns>
/// <exception cref="ApplicationException"/><exception cref="RpcException"/> /// <exception cref="WTException"/><exception cref="RpcException"/>
public async Task<string> Login(string loginInfo) public async Task<string> Login(string loginInfo)
{ {
if (_loginCfg.request == default) RunLoginAsync(loginInfo); if (_loginCfg.request == default) RunLoginAsync(loginInfo);
@ -997,7 +997,7 @@ namespace WTelegram
} }
var mismatch = $"Current logged user {self.id} mismatched user_id or phone_number"; var mismatch = $"Current logged user {self.id} mismatched user_id or phone_number";
Helpers.Log(3, mismatch); Helpers.Log(3, mismatch);
if (!reloginOnFailedResume) throw new ApplicationException(mismatch); if (!reloginOnFailedResume) throw new WTException(mismatch);
} }
catch (RpcException ex) when (reloginOnFailedResume) catch (RpcException ex) when (reloginOnFailedResume)
{ {
@ -1157,7 +1157,7 @@ namespace WTelegram
public User LoginAlreadyDone(Auth_AuthorizationBase authorization) public User LoginAlreadyDone(Auth_AuthorizationBase authorization)
{ {
if (authorization is not Auth_Authorization { user: User user }) if (authorization is not Auth_Authorization { user: User user })
throw new ApplicationException("Failed to get Authorization: " + authorization.GetType().Name); throw new WTException("Failed to get Authorization: " + authorization.GetType().Name);
_session.UserId = _dcSession.UserId = user.id; _session.UserId = _dcSession.UserId = user.id;
lock (_session) _session.Save(); lock (_session) _session.Save();
return User = user; return User = user;
@ -1190,7 +1190,7 @@ namespace WTelegram
private async Task SendAsync(IObject msg, bool isContent, Rpc rpc = null) private async Task SendAsync(IObject msg, bool isContent, Rpc rpc = null)
{ {
if (_reactorTask == null) throw new ApplicationException("You must connect to Telegram first"); if (_reactorTask == null) throw new WTException("You must connect to Telegram first");
isContent &= _dcSession.AuthKeyID != 0; isContent &= _dcSession.AuthKeyID != 0;
(long msgId, int seqno) = NewMsgId(isContent); (long msgId, int seqno) = NewMsgId(isContent);
if (rpc != null) if (rpc != null)
@ -1212,7 +1212,7 @@ namespace WTelegram
if (_dcSession.AuthKeyID == 0) // send unencrypted message if (_dcSession.AuthKeyID == 0) // send unencrypted message
{ {
if (_bareRpc == null) throw new ApplicationException($"Shouldn't send a {msg.GetType().Name} unencrypted"); if (_bareRpc == null) throw new WTException($"Shouldn't send a {msg.GetType().Name} unencrypted");
writer.Write(0L); // int64 auth_key_id = 0 (Unencrypted) writer.Write(0L); // int64 auth_key_id = 0 (Unencrypted)
writer.Write(msgId); // int64 message_id writer.Write(msgId); // int64 message_id
writer.Write(0); // int32 message_data_length (to be patched) writer.Write(0); // int32 message_data_length (to be patched)
@ -1273,7 +1273,7 @@ namespace WTelegram
internal async Task<T> InvokeBare<T>(IMethod<T> request) internal async Task<T> InvokeBare<T>(IMethod<T> request)
{ {
if (_bareRpc != null) throw new ApplicationException("A bare request is already undergoing"); if (_bareRpc != null) throw new WTException("A bare request is already undergoing");
retry: retry:
_bareRpc = new Rpc { type = typeof(T) }; _bareRpc = new Rpc { type = typeof(T) };
await SendAsync(request, false, _bareRpc); await SendAsync(request, false, _bareRpc);
@ -1352,7 +1352,7 @@ namespace WTelegram
case ReactorError: case ReactorError:
goto retry; goto retry;
default: default:
throw new ApplicationException($"{query.GetType().Name} call got a result of type {result.GetType().Name} instead of {typeof(T).Name}"); throw new WTException($"{query.GetType().Name} call got a result of type {result.GetType().Name} instead of {typeof(T).Name}");
} }
} }
} }

View file

@ -23,7 +23,7 @@ namespace WTelegram
{ {
AesECB.Mode = CipherMode.ECB; AesECB.Mode = CipherMode.ECB;
AesECB.Padding = PaddingMode.Zeros; AesECB.Padding = PaddingMode.Zeros;
if (AesECB.BlockSize != 128) throw new ApplicationException("AES Blocksize is not 16 bytes"); if (AesECB.BlockSize != 128) throw new WTException("AES Blocksize is not 16 bytes");
} }
internal static async Task CreateAuthorizationKey(Client client, Session.DCSession session) internal static async Task CreateAuthorizationKey(Client client, Session.DCSession session)
@ -36,9 +36,9 @@ namespace WTelegram
var nonce = new Int128(RNG); var nonce = new Int128(RNG);
var resPQ = await client.ReqPqMulti(nonce); var resPQ = await client.ReqPqMulti(nonce);
//2) //2)
if (resPQ.nonce != nonce) throw new ApplicationException("Nonce mismatch"); if (resPQ.nonce != nonce) throw new WTException("Nonce mismatch");
var fingerprint = resPQ.server_public_key_fingerprints.FirstOrDefault(PublicKeys.ContainsKey); var fingerprint = resPQ.server_public_key_fingerprints.FirstOrDefault(PublicKeys.ContainsKey);
if (fingerprint == 0) throw new ApplicationException("Couldn't match any server_public_key_fingerprints"); if (fingerprint == 0) throw new WTException("Couldn't match any server_public_key_fingerprints");
var publicKey = PublicKeys[fingerprint]; var publicKey = PublicKeys[fingerprint];
Helpers.Log(2, $"Selected public key with fingerprint {fingerprint:X}"); Helpers.Log(2, $"Selected public key with fingerprint {fingerprint:X}");
//3) //3)
@ -73,7 +73,7 @@ namespace WTelegram
clearStream.Write(aes_key, 0, 32); // write aes_key as prefix for initial Sha256 computation clearStream.Write(aes_key, 0, 32); // write aes_key as prefix for initial Sha256 computation
writer.WriteTLObject(pqInnerData); writer.WriteTLObject(pqInnerData);
int clearLength = (int)clearStream.Position - 32; // length before padding int clearLength = (int)clearStream.Position - 32; // length before padding
if (clearLength > 144) throw new ApplicationException("PQInnerData too big"); if (clearLength > 144) throw new WTException("PQInnerData too big");
byte[] clearBuffer = clearStream.GetBuffer(); byte[] clearBuffer = clearStream.GetBuffer();
RNG.GetBytes(clearBuffer, 32 + clearLength, 192 - clearLength); RNG.GetBytes(clearBuffer, 32 + clearLength, 192 - clearLength);
sha256.ComputeHash(clearBuffer, 0, 32 + 192).CopyTo(clearBuffer, 224); // append Sha256 sha256.ComputeHash(clearBuffer, 0, 32 + 192).CopyTo(clearBuffer, 224); // append Sha256
@ -91,22 +91,22 @@ namespace WTelegram
var serverDHparams = await client.ReqDHParams(pqInnerData.nonce, pqInnerData.server_nonce, pqInnerData.p, pqInnerData.q, fingerprint, encrypted_data); var serverDHparams = await client.ReqDHParams(pqInnerData.nonce, pqInnerData.server_nonce, pqInnerData.p, pqInnerData.q, fingerprint, encrypted_data);
//5) //5)
var localTime = DateTimeOffset.UtcNow; var localTime = DateTimeOffset.UtcNow;
if (serverDHparams is not ServerDHParamsOk serverDHparamsOk) throw new ApplicationException("not server_DH_params_ok"); if (serverDHparams is not ServerDHParamsOk serverDHparamsOk) throw new WTException("not server_DH_params_ok");
if (serverDHparamsOk.nonce != nonce) throw new ApplicationException("Nonce mismatch"); if (serverDHparamsOk.nonce != nonce) throw new WTException("Nonce mismatch");
if (serverDHparamsOk.server_nonce != resPQ.server_nonce) throw new ApplicationException("Server Nonce mismatch"); if (serverDHparamsOk.server_nonce != resPQ.server_nonce) throw new WTException("Server Nonce mismatch");
var (tmp_aes_key, tmp_aes_iv) = ConstructTmpAESKeyIV(resPQ.server_nonce, pqInnerData.new_nonce); var (tmp_aes_key, tmp_aes_iv) = ConstructTmpAESKeyIV(resPQ.server_nonce, pqInnerData.new_nonce);
var answer = AES_IGE_EncryptDecrypt(serverDHparamsOk.encrypted_answer, tmp_aes_key, tmp_aes_iv, false); var answer = AES_IGE_EncryptDecrypt(serverDHparamsOk.encrypted_answer, tmp_aes_key, tmp_aes_iv, false);
using var answerReader = new BinaryReader(new MemoryStream(answer)); using var answerReader = new BinaryReader(new MemoryStream(answer));
var answerHash = answerReader.ReadBytes(20); var answerHash = answerReader.ReadBytes(20);
var answerObj = answerReader.ReadTLObject(); var answerObj = answerReader.ReadTLObject();
if (answerObj is not ServerDHInnerData serverDHinnerData) throw new ApplicationException("not server_DH_inner_data"); if (answerObj is not ServerDHInnerData serverDHinnerData) throw new WTException("not server_DH_inner_data");
long padding = answerReader.BaseStream.Length - answerReader.BaseStream.Position; long padding = answerReader.BaseStream.Length - answerReader.BaseStream.Position;
if (padding >= 16) throw new ApplicationException("Too much pad"); if (padding >= 16) throw new WTException("Too much pad");
if (!Enumerable.SequenceEqual(sha1.ComputeHash(answer, 20, answer.Length - (int)padding - 20), answerHash)) if (!Enumerable.SequenceEqual(sha1.ComputeHash(answer, 20, answer.Length - (int)padding - 20), answerHash))
throw new ApplicationException("Answer SHA1 mismatch"); throw new WTException("Answer SHA1 mismatch");
if (serverDHinnerData.nonce != nonce) throw new ApplicationException("Nonce mismatch"); if (serverDHinnerData.nonce != nonce) throw new WTException("Nonce mismatch");
if (serverDHinnerData.server_nonce != resPQ.server_nonce) throw new ApplicationException("Server Nonce mismatch"); if (serverDHinnerData.server_nonce != resPQ.server_nonce) throw new WTException("Server Nonce mismatch");
var g_a = BigEndianInteger(serverDHinnerData.g_a); var g_a = BigEndianInteger(serverDHinnerData.g_a);
var dh_prime = BigEndianInteger(serverDHinnerData.dh_prime); var dh_prime = BigEndianInteger(serverDHinnerData.dh_prime);
CheckGoodPrime(dh_prime, serverDHinnerData.g); CheckGoodPrime(dh_prime, serverDHinnerData.g);
@ -149,15 +149,15 @@ namespace WTelegram
var authKeyHash = sha1.ComputeHash(authKey); var authKeyHash = sha1.ComputeHash(authKey);
retry_id = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash); // (auth_key_aux_hash) retry_id = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash); // (auth_key_aux_hash)
//9) //9)
if (setClientDHparamsAnswer is not DhGenOk dhGenOk) throw new ApplicationException("not dh_gen_ok"); if (setClientDHparamsAnswer is not DhGenOk dhGenOk) throw new WTException("not dh_gen_ok");
if (dhGenOk.nonce != nonce) throw new ApplicationException("Nonce mismatch"); if (dhGenOk.nonce != nonce) throw new WTException("Nonce mismatch");
if (dhGenOk.server_nonce != resPQ.server_nonce) throw new ApplicationException("Server Nonce mismatch"); if (dhGenOk.server_nonce != resPQ.server_nonce) throw new WTException("Server Nonce mismatch");
var expected_new_nonceN = new byte[32 + 1 + 8]; var expected_new_nonceN = new byte[32 + 1 + 8];
pqInnerData.new_nonce.raw.CopyTo(expected_new_nonceN, 0); pqInnerData.new_nonce.raw.CopyTo(expected_new_nonceN, 0);
expected_new_nonceN[32] = 1; expected_new_nonceN[32] = 1;
Array.Copy(authKeyHash, 0, expected_new_nonceN, 33, 8); // (auth_key_aux_hash) Array.Copy(authKeyHash, 0, expected_new_nonceN, 33, 8); // (auth_key_aux_hash)
if (!Enumerable.SequenceEqual(dhGenOk.new_nonce_hash1.raw, sha1.ComputeHash(expected_new_nonceN).Skip(4))) if (!Enumerable.SequenceEqual(dhGenOk.new_nonce_hash1.raw, sha1.ComputeHash(expected_new_nonceN).Skip(4)))
throw new ApplicationException("setClientDHparamsAnswer.new_nonce_hashN mismatch"); throw new WTException("setClientDHparamsAnswer.new_nonce_hashN mismatch");
session.AuthKeyID = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash.AsSpan(12)); session.AuthKeyID = BinaryPrimitives.ReadInt64LittleEndian(authKeyHash.AsSpan(12));
session.AuthKey = authKey; session.AuthKey = authKey;
@ -188,7 +188,7 @@ namespace WTelegram
{ {
Helpers.Log(2, "Verifying encryption key safety... (this should happen only once per DC)"); Helpers.Log(2, "Verifying encryption key safety... (this should happen only once per DC)");
// check that 2^2047 <= p < 2^2048 // check that 2^2047 <= p < 2^2048
if (p.GetBitLength() != 2048) throw new ApplicationException("p is not 2048-bit number"); if (p.GetBitLength() != 2048) throw new WTException("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. // check that g generates a cyclic subgroup of prime order (p - 1) / 2, i.e. is a quadratic residue mod p.
if (g switch if (g switch
{ {
@ -200,11 +200,11 @@ namespace WTelegram
7 => (int)(p % 7) is not 3 and not 5 and not 6, 7 => (int)(p % 7) is not 3 and not 5 and not 6,
_ => true, _ => true,
}) })
throw new ApplicationException("Bad prime mod 4g"); throw new WTException("Bad prime mod 4g");
// check whether p is a safe prime (meaning that both p and (p - 1) / 2 are prime) // check whether p is a safe prime (meaning that both p and (p - 1) / 2 are prime)
if (SafePrimes.Contains(p)) return; if (SafePrimes.Contains(p)) return;
if (!p.IsProbablePrime()) throw new ApplicationException("p is not a prime number"); if (!p.IsProbablePrime()) throw new WTException("p is not a prime number");
if (!((p - 1) / 2).IsProbablePrime()) throw new ApplicationException("(p - 1) / 2 is not a prime number"); if (!((p - 1) / 2).IsProbablePrime()) throw new WTException("(p - 1) / 2 is not a prime number");
SafePrimes.Add(p); SafePrimes.Add(p);
} }
@ -233,7 +233,7 @@ namespace WTelegram
// check that g, g_a and g_b are greater than 1 and less than dh_prime - 1. // 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. // We recommend checking that g_a and g_b are between 2^{2048-64} and dh_prime - 2^{2048-64} as well.
if (g.GetBitLength() < 2048 - 64 || (dh_prime - g).GetBitLength() < 2048 - 64) if (g.GetBitLength() < 2048 - 64 || (dh_prime - g).GetBitLength() < 2048 - 64)
throw new ApplicationException("g^a or g^b is not between 2^{2048-64} and dh_prime - 2^{2048-64}"); throw new WTException("g^a or g^b is not between 2^{2048-64} and dh_prime - 2^{2048-64}");
} }
public static void LoadPublicKey(string pem) public static void LoadPublicKey(string pem)
@ -298,7 +298,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
internal static byte[] AES_IGE_EncryptDecrypt(Span<byte> input, byte[] aes_key, byte[] aes_iv, bool encrypt) internal static byte[] AES_IGE_EncryptDecrypt(Span<byte> input, byte[] aes_key, byte[] aes_iv, bool encrypt)
{ {
if (input.Length % 16 != 0) throw new ApplicationException("AES_IGE input size not divisible by 16"); if (input.Length % 16 != 0) throw new WTException("AES_IGE input size not divisible by 16");
using var aesCrypto = encrypt ? AesECB.CreateEncryptor(aes_key, null) : AesECB.CreateDecryptor(aes_key, null); using var aesCrypto = encrypt ? AesECB.CreateEncryptor(aes_key, null) : AesECB.CreateDecryptor(aes_key, null);
var output = new byte[input.Length]; var output = new byte[input.Length];
@ -396,7 +396,7 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
RNG.GetBytes(algo.salt1, salt1len, 32); RNG.GetBytes(algo.salt1, salt1len, 32);
} }
else else
throw new ApplicationException("2FA authentication uses an unsupported algo: " + accountPassword.current_algo?.GetType().Name); throw new WTException("2FA authentication uses an unsupported algo: " + accountPassword.current_algo?.GetType().Name);
var g = new BigInteger(algo.g); var g = new BigInteger(algo.g);
var p = BigEndianInteger(algo.p); var p = BigEndianInteger(algo.p);

View file

@ -253,4 +253,10 @@ namespace WTelegram
protected override void Dispose(bool disposing) => _innerStream.Dispose(); protected override void Dispose(bool disposing) => _innerStream.Dispose();
} }
} }
public class WTException : ApplicationException
{
public WTException(string message) : base(message) { }
public WTException(string message, Exception innerException) : base(message, innerException) { }
}
} }

View file

@ -95,7 +95,7 @@ namespace WTelegram
public void Load(Stream input) public void Load(Stream input)
{ {
using var reader = new BinaryReader(input, Encoding.UTF8, true); using var reader = new BinaryReader(input, Encoding.UTF8, true);
if (reader.ReadInt32() != 0) throw new ApplicationException("Unrecognized Secrets format"); if (reader.ReadInt32() != 0) throw new WTException("Unrecognized Secrets format");
dh = (Messages_DhConfig)reader.ReadTLObject(); dh = (Messages_DhConfig)reader.ReadTLObject();
if (dh?.p != null) dh_prime = BigEndianInteger(dh.p); if (dh?.p != null) dh_prime = BigEndianInteger(dh.p);
int count = reader.ReadInt32(); int count = reader.ReadInt32();
@ -128,15 +128,15 @@ namespace WTelegram
{ {
var mdhcb = await client.Messages_GetDhConfig(dh?.version ?? 0, 256); var mdhcb = await client.Messages_GetDhConfig(dh?.version ?? 0, 256);
if (mdhcb is Messages_DhConfigNotModified { random: var random }) if (mdhcb is Messages_DhConfigNotModified { random: var random })
_ = dh ?? throw new ApplicationException("DhConfigNotModified on zero version"); _ = dh ?? throw new WTException("DhConfigNotModified on zero version");
else if (mdhcb is Messages_DhConfig dhc) else if (mdhcb is Messages_DhConfig dhc)
{ {
var p = BigEndianInteger(dhc.p); var p = BigEndianInteger(dhc.p);
CheckGoodPrime(p, dhc.g); CheckGoodPrime(p, dhc.g);
(dh, dh_prime, random, dh.random) = (dhc, p, dhc.random, null); (dh, dh_prime, random, dh.random) = (dhc, p, dhc.random, null);
} }
else throw new ApplicationException("Unexpected DHConfig response: " + mdhcb?.GetType().Name); else throw new WTException("Unexpected DHConfig response: " + mdhcb?.GetType().Name);
if (random.Length != 256) throw new ApplicationException("Invalid DHConfig random"); if (random.Length != 256) throw new WTException("Invalid DHConfig random");
var salt = new byte[256]; var salt = new byte[256];
RNG.GetBytes(salt); RNG.GetBytes(salt);
for (int i = 0; i < 256; i++) salt[i] ^= random[i]; for (int i = 0; i < 256; i++) salt[i] ^= random[i];
@ -146,7 +146,7 @@ namespace WTelegram
/// <summary>Initiate a secret chat with the given user.<br/>(chat must be acknowledged by remote user before being active)</summary> /// <summary>Initiate a secret chat with the given user.<br/>(chat must be acknowledged by remote user before being active)</summary>
/// <param name="user">The remote user</param> /// <param name="user">The remote user</param>
/// <returns>Secret Chat ID</returns> /// <returns>Secret Chat ID</returns>
/// <exception cref="ApplicationException"></exception> /// <exception cref="WTException"></exception>
public async Task<int> Request(InputUserBase user) public async Task<int> Request(InputUserBase user)
{ {
int chat_id; int chat_id;
@ -164,7 +164,7 @@ namespace WTelegram
CheckGoodGaAndGb(g_a, dh_prime); CheckGoodGaAndGb(g_a, dh_prime);
var ecb = await client.Messages_RequestEncryption(user, chat_id, g_a.To256Bytes()); var ecb = await client.Messages_RequestEncryption(user, chat_id, g_a.To256Bytes());
if (ecb is not EncryptedChatWaiting ecw || ecw.id != chat_id || ecw.participant_id != chat.participant_id) if (ecb is not EncryptedChatWaiting ecw || ecw.id != chat_id || ecw.participant_id != chat.participant_id)
throw new ApplicationException("Invalid " + ecb?.GetType().Name); throw new WTException("Invalid " + ecb?.GetType().Name);
chat.peer.access_hash = ecw.access_hash; chat.peer.access_hash = ecw.access_hash;
return chat_id; return chat_id;
} }
@ -173,7 +173,7 @@ namespace WTelegram
/// <remarks>If update.chat is <see cref="EncryptedChatRequested"/>, you might want to first make sure you want to accept this secret chat initiated by user <see cref="EncryptedChatRequested.admin_id"/></remarks> /// <remarks>If update.chat is <see cref="EncryptedChatRequested"/>, you might want to first make sure you want to accept this secret chat initiated by user <see cref="EncryptedChatRequested.admin_id"/></remarks>
/// <param name="acceptChatRequests">Incoming requests for secret chats are automatically: accepted (<see langword="true"/>), rejected (<see langword="false"/>) or ignored (<see langword="null"/>)</param> /// <param name="acceptChatRequests">Incoming requests for secret chats are automatically: accepted (<see langword="true"/>), rejected (<see langword="false"/>) or ignored (<see langword="null"/>)</param>
/// <returns><see langword="true"/> if the update was handled successfully</returns> /// <returns><see langword="true"/> if the update was handled successfully</returns>
/// <exception cref="ApplicationException"></exception> /// <exception cref="WTException"></exception>
public async Task<bool> HandleUpdate(UpdateEncryption update, bool? acceptChatRequests = true) public async Task<bool> HandleUpdate(UpdateEncryption update, bool? acceptChatRequests = true)
{ {
try try
@ -188,8 +188,8 @@ namespace WTelegram
var gab = BigInteger.ModPow(g_b, a, dh_prime); var gab = BigInteger.ModPow(g_b, a, dh_prime);
chat.flags &= ~SecretChat.Flags.requestChat; chat.flags &= ~SecretChat.Flags.requestChat;
SetAuthKey(chat, gab.To256Bytes()); SetAuthKey(chat, gab.To256Bytes());
if (ec.key_fingerprint != chat.key_fingerprint) throw new ApplicationException("Invalid fingerprint on accepted secret chat"); if (ec.key_fingerprint != chat.key_fingerprint) throw new WTException("Invalid fingerprint on accepted secret chat");
if (ec.access_hash != chat.peer.access_hash || ec.participant_id != chat.participant_id) throw new ApplicationException("Invalid peer on accepted secret chat"); if (ec.access_hash != chat.peer.access_hash || ec.participant_id != chat.participant_id) throw new WTException("Invalid peer on accepted secret chat");
await SendNotifyLayer(chat); await SendNotifyLayer(chat);
return true; return true;
} }
@ -227,7 +227,7 @@ namespace WTelegram
var ecb = await client.Messages_AcceptEncryption(chat.peer, g_b.ToByteArray(true, true), chat.key_fingerprint); var ecb = await client.Messages_AcceptEncryption(chat.peer, g_b.ToByteArray(true, true), chat.key_fingerprint);
if (ecb is not EncryptedChat ec || ec.id != ecr.id || ec.access_hash != ecr.access_hash || if (ecb is not EncryptedChat ec || ec.id != ecr.id || ec.access_hash != ecr.access_hash ||
ec.admin_id != ecr.admin_id || ec.key_fingerprint != chat.key_fingerprint) ec.admin_id != ecr.admin_id || ec.key_fingerprint != chat.key_fingerprint)
throw new ApplicationException("Inconsistent accepted secret chat"); throw new WTException("Inconsistent accepted secret chat");
await SendNotifyLayer(chat); await SendNotifyLayer(chat);
return true; return true;
} }
@ -274,7 +274,7 @@ namespace WTelegram
/// <returns>Confirmation of sent message</returns> /// <returns>Confirmation of sent message</returns>
public async Task<Messages_SentEncryptedMessage> SendMessage(int chatId, DecryptedMessageBase msg, bool silent = false, InputEncryptedFileBase file = null) public async Task<Messages_SentEncryptedMessage> SendMessage(int chatId, DecryptedMessageBase msg, bool silent = false, InputEncryptedFileBase file = null)
{ {
if (!chats.TryGetValue(chatId, out var chat)) throw new ApplicationException("Secret chat not found"); if (!chats.TryGetValue(chatId, out var chat)) throw new WTException("Secret chat not found");
try try
{ {
var dml = new TL.Layer23.DecryptedMessageLayer var dml = new TL.Layer23.DecryptedMessageLayer
@ -336,14 +336,14 @@ namespace WTelegram
private IObject Decrypt(SecretChat chat, byte[] data, int dataLen) private IObject Decrypt(SecretChat chat, byte[] data, int dataLen)
{ {
if (dataLen < 32) // authKeyId+msgKey+(length+ctorNb) if (dataLen < 32) // authKeyId+msgKey+(length+ctorNb)
throw new ApplicationException($"Encrypted packet too small: {data.Length}"); throw new WTException($"Encrypted packet too small: {data.Length}");
var authKey = chat.authKey; var authKey = chat.authKey;
long authKeyId = BinaryPrimitives.ReadInt64LittleEndian(data); long authKeyId = BinaryPrimitives.ReadInt64LittleEndian(data);
if (authKeyId == chat.key_fingerprint) if (authKeyId == chat.key_fingerprint)
if (!chat.flags.HasFlag(SecretChat.Flags.commitKey)) CheckPFS(chat); if (!chat.flags.HasFlag(SecretChat.Flags.commitKey)) CheckPFS(chat);
else { chat.flags &= ~SecretChat.Flags.commitKey; Array.Clear(chat.salt, 0, chat.salt.Length); } else { chat.flags &= ~SecretChat.Flags.commitKey; Array.Clear(chat.salt, 0, chat.salt.Length); }
else if (chat.flags.HasFlag(SecretChat.Flags.commitKey) && authKeyId == BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(chat.salt).AsSpan(12))) authKey = chat.salt; else if (chat.flags.HasFlag(SecretChat.Flags.commitKey) && authKeyId == BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(chat.salt).AsSpan(12))) authKey = chat.salt;
else throw new ApplicationException($"Received a packet encrypted with unexpected key {authKeyId:X}"); else throw new WTException($"Received a packet encrypted with unexpected key {authKeyId:X}");
int x = (int)(chat.flags & SecretChat.Flags.originator); int x = (int)(chat.flags & SecretChat.Flags.originator);
byte[] decrypted_data = EncryptDecryptMessage(data.AsSpan(24, dataLen - 24), false, x, authKey, data, 8, sha256); byte[] decrypted_data = EncryptDecryptMessage(data.AsSpan(24, dataLen - 24), false, x, authKey, data, 8, sha256);
var length = BinaryPrimitives.ReadInt32LittleEndian(decrypted_data); var length = BinaryPrimitives.ReadInt32LittleEndian(decrypted_data);
@ -354,11 +354,11 @@ namespace WTelegram
sha256.TransformBlock(authKey, 88 + x, 32, null, 0); sha256.TransformBlock(authKey, 88 + x, 32, null, 0);
sha256.TransformFinalBlock(decrypted_data, 0, decrypted_data.Length); sha256.TransformFinalBlock(decrypted_data, 0, decrypted_data.Length);
if (success = data.AsSpan(8, 16).SequenceEqual(sha256.Hash.AsSpan(8, 16))) if (success = data.AsSpan(8, 16).SequenceEqual(sha256.Hash.AsSpan(8, 16)))
if (decrypted_data.Length - 4 - length is < 12 or > 1024) throw new ApplicationException($"Invalid MTProto2 padding length: {decrypted_data.Length - 4}-{length}"); if (decrypted_data.Length - 4 - length is < 12 or > 1024) throw new WTException($"Invalid MTProto2 padding length: {decrypted_data.Length - 4}-{length}");
else if (chat.remoteLayer < Layer.MTProto2) chat.remoteLayer = Layer.MTProto2; else if (chat.remoteLayer < Layer.MTProto2) chat.remoteLayer = Layer.MTProto2;
} }
if (!success) throw new ApplicationException("Could not decrypt message"); if (!success) throw new WTException("Could not decrypt message");
if (length % 4 != 0) throw new ApplicationException($"Invalid message_data_length: {length}"); if (length % 4 != 0) throw new WTException($"Invalid message_data_length: {length}");
using var reader = new BinaryReader(new MemoryStream(decrypted_data, 4, length)); using var reader = new BinaryReader(new MemoryStream(decrypted_data, 4, length));
return reader.ReadTLObject(); return reader.ReadTLObject();
} }
@ -370,16 +370,16 @@ namespace WTelegram
/// You can use the generic properties to access their fields /// You can use the generic properties to access their fields
/// <para>May return an empty array if msg was already previously received or is not the next message in sequence. /// <para>May return an empty array if msg was already previously received or is not the next message in sequence.
/// <br/>May return multiple messages if missing messages are finally received (using <paramref name="fillGaps"/> = true)</para></returns> /// <br/>May return multiple messages if missing messages are finally received (using <paramref name="fillGaps"/> = true)</para></returns>
/// <exception cref="ApplicationException"></exception> /// <exception cref="WTException"></exception>
public ICollection<DecryptedMessageBase> DecryptMessage(EncryptedMessageBase msg, bool fillGaps = true) public ICollection<DecryptedMessageBase> DecryptMessage(EncryptedMessageBase msg, bool fillGaps = true)
{ {
if (!chats.TryGetValue(msg.ChatId, out var chat)) throw new ApplicationException("Secret chat not found"); if (!chats.TryGetValue(msg.ChatId, out var chat)) throw new WTException("Secret chat not found");
try try
{ {
var obj = Decrypt(chat, msg.Bytes, msg.Bytes.Length); var obj = Decrypt(chat, msg.Bytes, msg.Bytes.Length);
if (obj is not TL.Layer23.DecryptedMessageLayer dml) throw new ApplicationException("Decrypted object is not DecryptedMessageLayer"); if (obj is not TL.Layer23.DecryptedMessageLayer dml) throw new WTException("Decrypted object is not DecryptedMessageLayer");
if (dml.random_bytes.Length < 15) throw new ApplicationException("Not enough random_bytes"); if (dml.random_bytes.Length < 15) throw new WTException("Not enough random_bytes");
if (((dml.out_seq_no ^ dml.in_seq_no) & 1) != 1 || ((dml.out_seq_no ^ chat.in_seq_no) & 1) != 0) throw new ApplicationException("Invalid seq_no parities"); if (((dml.out_seq_no ^ dml.in_seq_no) & 1) != 1 || ((dml.out_seq_no ^ chat.in_seq_no) & 1) != 0) throw new WTException("Invalid seq_no parities");
if (dml.layer > chat.remoteLayer) chat.remoteLayer = dml.layer; if (dml.layer > chat.remoteLayer) chat.remoteLayer = dml.layer;
//Debug.WriteLine($"<\t{dml.in_seq_no}\t{dml.out_seq_no}\t\t\t\t\t\texpected:{chat.out_seq_no}/{chat.in_seq_no + 2}"); //Debug.WriteLine($"<\t{dml.in_seq_no}\t{dml.out_seq_no}\t\t\t\t\t\texpected:{chat.out_seq_no}/{chat.in_seq_no + 2}");
if (dml.out_seq_no <= chat.in_seq_no) return Array.Empty<DecryptedMessageBase>(); // already received message if (dml.out_seq_no <= chat.in_seq_no) return Array.Empty<DecryptedMessageBase>(); // already received message
@ -510,7 +510,7 @@ namespace WTelegram
} }
break; // we lost, process with the larger exchange_id RequestKey break; // we lost, process with the larger exchange_id RequestKey
case 0: break; case 0: break;
default: throw new ApplicationException("Invalid RequestKey"); default: throw new WTException("Invalid RequestKey");
} }
var g_a = BigEndianInteger(request.g_a); var g_a = BigEndianInteger(request.g_a);
var salt = new byte[256]; var salt = new byte[256];
@ -529,9 +529,9 @@ namespace WTelegram
break; break;
case TL.Layer23.DecryptedMessageActionAcceptKey accept: case TL.Layer23.DecryptedMessageActionAcceptKey accept:
if ((chat.flags & (SecretChat.Flags.requestChat | SecretChat.Flags.renewKey | SecretChat.Flags.acceptKey)) != SecretChat.Flags.renewKey) if ((chat.flags & (SecretChat.Flags.requestChat | SecretChat.Flags.renewKey | SecretChat.Flags.acceptKey)) != SecretChat.Flags.renewKey)
throw new ApplicationException("Invalid AcceptKey"); throw new WTException("Invalid AcceptKey");
if (accept.exchange_id != chat.exchange_id) if (accept.exchange_id != chat.exchange_id)
throw new ApplicationException("AcceptKey: exchange_id mismatch"); throw new WTException("AcceptKey: exchange_id mismatch");
var a = BigEndianInteger(chat.salt); var a = BigEndianInteger(chat.salt);
g_b = BigEndianInteger(accept.g_b); g_b = BigEndianInteger(accept.g_b);
CheckGoodGaAndGb(g_b, dh_prime); CheckGoodGaAndGb(g_b, dh_prime);
@ -539,7 +539,7 @@ namespace WTelegram
var authKey = gab.To256Bytes(); var authKey = gab.To256Bytes();
key_fingerprint = BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(authKey).AsSpan(12)); key_fingerprint = BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(authKey).AsSpan(12));
if (accept.key_fingerprint != key_fingerprint) if (accept.key_fingerprint != key_fingerprint)
throw new ApplicationException("AcceptKey: key_fingerprint mismatch"); throw new WTException("AcceptKey: key_fingerprint mismatch");
_ = SendMessage(chat.ChatId, new TL.Layer23.DecryptedMessageService { random_id = Helpers.RandomLong(), _ = SendMessage(chat.ChatId, new TL.Layer23.DecryptedMessageService { random_id = Helpers.RandomLong(),
action = new TL.Layer23.DecryptedMessageActionCommitKey { exchange_id = accept.exchange_id, key_fingerprint = accept.key_fingerprint } }); action = new TL.Layer23.DecryptedMessageActionCommitKey { exchange_id = accept.exchange_id, key_fingerprint = accept.key_fingerprint } });
chat.salt = chat.authKey; // A may only discard the previous key after a message encrypted with the new key has been received. chat.salt = chat.authKey; // A may only discard the previous key after a message encrypted with the new key has been received.
@ -548,10 +548,10 @@ namespace WTelegram
break; break;
case TL.Layer23.DecryptedMessageActionCommitKey commit: case TL.Layer23.DecryptedMessageActionCommitKey commit:
if ((chat.flags & (SecretChat.Flags.requestChat | SecretChat.Flags.renewKey | SecretChat.Flags.acceptKey)) != SecretChat.Flags.acceptKey) if ((chat.flags & (SecretChat.Flags.requestChat | SecretChat.Flags.renewKey | SecretChat.Flags.acceptKey)) != SecretChat.Flags.acceptKey)
throw new ApplicationException("Invalid RequestKey"); throw new WTException("Invalid RequestKey");
key_fingerprint = BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(chat.salt).AsSpan(12)); key_fingerprint = BinaryPrimitives.ReadInt64LittleEndian(sha1.ComputeHash(chat.salt).AsSpan(12));
if (commit.exchange_id != chat.exchange_id | commit.key_fingerprint != key_fingerprint) if (commit.exchange_id != chat.exchange_id | commit.key_fingerprint != key_fingerprint)
throw new ApplicationException("CommitKey: data mismatch"); throw new WTException("CommitKey: data mismatch");
chat.flags &= ~SecretChat.Flags.acceptKey; chat.flags &= ~SecretChat.Flags.acceptKey;
authKey = chat.authKey; authKey = chat.authKey;
SetAuthKey(chat, chat.salt); SetAuthKey(chat, chat.salt);
@ -614,7 +614,7 @@ namespace WTelegram
var res = md5.TransformFinalBlock(iv, 0, 32); var res = md5.TransformFinalBlock(iv, 0, 32);
long fingerprint = BinaryPrimitives.ReadInt64LittleEndian(md5.Hash); long fingerprint = BinaryPrimitives.ReadInt64LittleEndian(md5.Hash);
fingerprint ^= fingerprint >> 32; fingerprint ^= fingerprint >> 32;
if (encryptedFile.key_fingerprint != (int)fingerprint) throw new ApplicationException("Encrypted file fingerprint mismatch"); if (encryptedFile.key_fingerprint != (int)fingerprint) throw new WTException("Encrypted file fingerprint mismatch");
using var decryptStream = new AES_IGE_Stream(outputStream, size, key, iv); using var decryptStream = new AES_IGE_Stream(outputStream, size, key, iv);
var fileLocation = encryptedFile.ToFileLocation(); var fileLocation = encryptedFile.ToFileLocation();

View file

@ -104,12 +104,12 @@ namespace WTelegram
{ {
var input = new byte[length]; var input = new byte[length];
if (store.Read(input, 0, length) != length) if (store.Read(input, 0, length) != length)
throw new ApplicationException($"Can't read session block ({store.Position}, {length})"); throw new WTException($"Can't read session block ({store.Position}, {length})");
using var sha256 = SHA256.Create(); using var sha256 = SHA256.Create();
using var decryptor = aes.CreateDecryptor(rgbKey, input[0..16]); using var decryptor = aes.CreateDecryptor(rgbKey, input[0..16]);
var utf8Json = decryptor.TransformFinalBlock(input, 16, input.Length - 16); var utf8Json = decryptor.TransformFinalBlock(input, 16, input.Length - 16);
if (!sha256.ComputeHash(utf8Json, 32, utf8Json.Length - 32).SequenceEqual(utf8Json[0..32])) if (!sha256.ComputeHash(utf8Json, 32, utf8Json.Length - 32).SequenceEqual(utf8Json[0..32]))
throw new ApplicationException("Integrity check failed in session loading"); throw new WTException("Integrity check failed in session loading");
session = JsonSerializer.Deserialize<Session>(utf8Json.AsSpan(32), Helpers.JsonOptions); session = JsonSerializer.Deserialize<Session>(utf8Json.AsSpan(32), Helpers.JsonOptions);
Helpers.Log(2, "Loaded previous session"); Helpers.Log(2, "Loaded previous session");
} }
@ -124,7 +124,7 @@ namespace WTelegram
catch (Exception ex) catch (Exception ex)
{ {
store.Dispose(); store.Dispose();
throw new ApplicationException($"Exception while reading session file: {ex.Message}\nUse the correct api_hash/id/key, or delete the file to start a new session", ex); throw new WTException($"Exception while reading session file: {ex.Message}\nUse the correct api_hash/id/key, or delete the file to start a new session", ex);
} }
} }

View file

@ -37,10 +37,10 @@ namespace TL
public static void CollectUsersChats(this IPeerResolver structure, Dictionary<long, User> users, Dictionary<long, ChatBase> chats) public static void CollectUsersChats(this IPeerResolver structure, Dictionary<long, User> users, Dictionary<long, ChatBase> chats)
=> structure.UserOrChat(new CollectorPeer { _users = users, _chats = chats }); => structure.UserOrChat(new CollectorPeer { _users = users, _chats = chats });
public static Task<Messages_Chats> Messages_GetChats(this Client _) => throw new ApplicationException("The method you're looking for is Messages_GetAllChats"); public static Task<Messages_Chats> Messages_GetChats(this Client _) => throw new WTException("The method you're looking for is Messages_GetAllChats");
public static Task<Messages_Chats> Channels_GetChannels(this Client _) => throw new ApplicationException("The method you're looking for is Messages_GetAllChats"); public static Task<Messages_Chats> Channels_GetChannels(this Client _) => throw new WTException("The method you're looking for is Messages_GetAllChats");
public static Task<UserBase[]> Users_GetUsers(this Client _) => throw new ApplicationException("The method you're looking for is Messages_GetAllDialogs"); public static Task<UserBase[]> Users_GetUsers(this Client _) => throw new WTException("The method you're looking for is Messages_GetAllDialogs");
public static Task<Messages_MessagesBase> Messages_GetMessages(this Client _) => throw new ApplicationException("If you want to get the messages from a chat, use Messages_GetHistory"); public static Task<Messages_MessagesBase> Messages_GetMessages(this Client _) => throw new WTException("If you want to get the messages from a chat, use Messages_GetHistory");
} }
public static class Markdown public static class Markdown

View file

@ -32,7 +32,7 @@ namespace TL
public abstract class DecryptedMessageMedia : IObject public abstract class DecryptedMessageMedia : IObject
{ {
public virtual string MimeType { get; } public virtual string MimeType { get; }
internal virtual (long size, byte[] key, byte[] iv) SizeKeyIV { get => default; set => throw new ApplicationException("Incompatible DecryptedMessageMedia"); } internal virtual (long size, byte[] key, byte[] iv) SizeKeyIV { get => default; set => throw new WTelegram.WTException("Incompatible DecryptedMessageMedia"); }
} }
/// <summary>Object describes the action to which a service message is linked. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessageAction"/></para></summary> /// <summary>Object describes the action to which a service message is linked. <para>See <a href="https://corefork.telegram.org/type/DecryptedMessageAction"/></para></summary>

View file

@ -28,7 +28,7 @@ namespace TL
public IfFlagAttribute(int bit) => Bit = bit; public IfFlagAttribute(int bit) => Bit = bit;
} }
public class RpcException : Exception public class RpcException : WTelegram.WTException
{ {
public readonly int Code; public readonly int Code;
/// <summary>The value of X in the message, -1 if no variable X was found</summary> /// <summary>The value of X in the message, -1 if no variable X was found</summary>
@ -73,7 +73,7 @@ namespace TL
using (var gzipReader = new BinaryReader(new GZipStream(new MemoryStream(reader.ReadTLBytes()), CompressionMode.Decompress))) using (var gzipReader = new BinaryReader(new GZipStream(new MemoryStream(reader.ReadTLBytes()), CompressionMode.Decompress)))
return ReadTLObject(gzipReader); return ReadTLObject(gzipReader);
if (!Layer.Table.TryGetValue(ctorNb, out var type)) if (!Layer.Table.TryGetValue(ctorNb, out var type))
throw new ApplicationException($"Cannot find type for ctor #{ctorNb:x}"); throw new WTelegram.WTException($"Cannot find type for ctor #{ctorNb:x}");
if (type == null) return null; // nullable ctor (class meaning is associated with null) if (type == null) return null; // nullable ctor (class meaning is associated with null)
var tlDef = type.GetCustomAttribute<TLDefAttribute>(); var tlDef = type.GetCustomAttribute<TLDefAttribute>();
var obj = Activator.CreateInstance(type, true); var obj = Activator.CreateInstance(type, true);
@ -153,7 +153,7 @@ namespace TL
0x997275b5 => true, 0x997275b5 => true,
0xbc799737 => false, 0xbc799737 => false,
Layer.RpcErrorCtor => reader.ReadTLObject(Layer.RpcErrorCtor), Layer.RpcErrorCtor => reader.ReadTLObject(Layer.RpcErrorCtor),
var value => throw new ApplicationException($"Invalid boolean value #{value:x}") var value => throw new WTelegram.WTException($"Invalid boolean value #{value:x}")
}; };
case TypeCode.Object: case TypeCode.Object:
if (type.IsArray) if (type.IsArray)
@ -235,7 +235,7 @@ namespace TL
return array; return array;
} }
else else
throw new ApplicationException($"Cannot deserialize {type.Name} with ctor #{ctorNb:x}"); throw new WTelegram.WTException($"Cannot deserialize {type.Name} with ctor #{ctorNb:x}");
} }
internal static Dictionary<long, T> ReadTLDictionary<T>(this BinaryReader reader, Func<T, long> getID) where T : class internal static Dictionary<long, T> ReadTLDictionary<T>(this BinaryReader reader, Func<T, long> getID) where T : class
@ -243,7 +243,7 @@ namespace TL
uint ctorNb = reader.ReadUInt32(); uint ctorNb = reader.ReadUInt32();
var elementType = typeof(T); var elementType = typeof(T);
if (ctorNb != Layer.VectorCtor) if (ctorNb != Layer.VectorCtor)
throw new ApplicationException($"Cannot deserialize Vector<{elementType.Name}> with ctor #{ctorNb:x}"); throw new WTelegram.WTException($"Cannot deserialize Vector<{elementType.Name}> with ctor #{ctorNb:x}");
int count = reader.ReadInt32(); int count = reader.ReadInt32();
var dict = new Dictionary<long, T>(count); var dict = new Dictionary<long, T>(count);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)

View file

@ -27,7 +27,7 @@ namespace WTelegram
if (await _innerStream.FullReadAsync(_tlsReadHeader, 5, ct) != 5) if (await _innerStream.FullReadAsync(_tlsReadHeader, 5, ct) != 5)
return 0; return 0;
if (_tlsReadHeader[0] != 0x17 || _tlsReadHeader[1] != 0x03 || _tlsReadHeader[2] != 0x03) if (_tlsReadHeader[0] != 0x17 || _tlsReadHeader[1] != 0x03 || _tlsReadHeader[2] != 0x03)
throw new ApplicationException("Could not read frame data : Invalid TLS header"); throw new WTException("Could not read frame data : Invalid TLS header");
_tlsFrameleft = (_tlsReadHeader[3] << 8) + _tlsReadHeader[4]; _tlsFrameleft = (_tlsReadHeader[3] << 8) + _tlsReadHeader[4];
} }
var read = await _innerStream.ReadAsync(buffer, offset, Math.Min(count, _tlsFrameleft), ct); var read = await _innerStream.ReadAsync(buffer, offset, Math.Min(count, _tlsFrameleft), ct);
@ -82,7 +82,7 @@ namespace WTelegram
} }
} }
} }
throw new ApplicationException("TLS Handshake failed"); throw new WTException("TLS Handshake failed");
} }
static readonly byte[] TlsClientHello1 = new byte[11] { static readonly byte[] TlsClientHello1 = new byte[11] {