Added SecretChats.DownloadFile and media.MimeType to simplify download & decryption

This commit is contained in:
Wizou 2022-10-20 23:12:43 +02:00
parent 82d852e071
commit b9587e3997
8 changed files with 74 additions and 30 deletions

View file

@ -404,6 +404,8 @@ WTelegram.Helpers.Log = (lvl, str) => _logger.Log((LogLevel)lvl, str);
WTelegram.Helpers.Log = (lvl, str) => { };
```
The `lvl` argument correspond to standard [LogLevel values](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel#fields)
<a name="2FA"></a>
### Change 2FA password
```csharp

View file

@ -91,10 +91,11 @@ Type a command, or a message to send to the active secret chat:");
{
if (msg.Media != null && unem.message is EncryptedMessage { file: EncryptedFile ef })
{
Console.WriteLine($"{unem.message.ChatId}> {msg.Message} [file being downloaded to media.jpg]");
using var output = File.Create("media.jpg"); // not necessarily a JPG, check the msg.Media mime_type
using var decryptStream = new WTelegram.AES_IGE_Stream(output, msg.Media);
await Client.DownloadFileAsync(ef, decryptStream, ef.dc_id, ef.size);
int slash = msg.Media.MimeType?.IndexOf('/') ?? 0; // quick & dirty conversion from MIME type to file extension
var filename = slash > 0 ? $"media.{msg.Media.MimeType[(slash + 1)..]}" : "media.bin";
Console.WriteLine($"{unem.message.ChatId}> {msg.Message} [attached file downloaded to {filename}]");
using var output = File.Create(filename);
await Secrets.DownloadFile(ef, msg.Media, output);
}
else if (msg.Action == null) Console.WriteLine($"{unem.message.ChatId}> {msg.Message}");
else Console.WriteLine($"{unem.message.ChatId}> Service Message {msg.Action.GetType().Name[22..]}");

View file

@ -1054,7 +1054,7 @@ namespace WTelegram
catch (RpcException e) when (e.Code == 400 && e.Message is "CODE_INVALID" or "EMAIL_TOKEN_INVALID")
{
Helpers.Log(4, "Wrong email verification code!");
if (retry == MaxCodePwdAttempts) throw;
if (retry >= MaxCodePwdAttempts) throw;
}
if (verified is Account_EmailVerifiedLogin verifiedLogin) // (it should always be)
sentCode = verifiedLogin.sent_code;
@ -1084,7 +1084,7 @@ namespace WTelegram
catch (RpcException e) when (e.Code == 400 && e.Message == "PHONE_CODE_INVALID")
{
Helpers.Log(4, "Wrong verification code!");
if (retry == MaxCodePwdAttempts) throw;
if (retry >= MaxCodePwdAttempts) throw;
}
catch (RpcException e) when (e.Code == 401 && e.Message == "SESSION_PASSWORD_NEEDED")
{
@ -1099,7 +1099,7 @@ namespace WTelegram
catch (RpcException pe) when (pe.Code == 400 && pe.Message == "PASSWORD_HASH_INVALID")
{
Helpers.Log(4, "Wrong password!");
if (pwdRetry == MaxCodePwdAttempts) throw;
if (pwdRetry >= MaxCodePwdAttempts) throw;
}
}
}

View file

@ -526,16 +526,13 @@ j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB
#endif
}
/// <summary>Stream for encryption/decryption of AES-256 with infinite garble extension (IGE) </summary>
public class AES_IGE_Stream : Helpers.IndirectStream
internal class AES_IGE_Stream : Helpers.IndirectStream
{
private readonly ICryptoTransform aesCrypto;
private readonly byte[] prevBytes;
/// <summary>Decryption of AES-256 IGE file with key/iv obtained from media structure</summary>
public AES_IGE_Stream(Stream stream, DecryptedMessageMedia media) : this(stream, media.SizeKeyIV) { }
public AES_IGE_Stream(Stream stream, (int size, byte[] key, byte[] iv) t) : this(stream, t.key, t.iv) { ContentLength = t.size; }
public AES_IGE_Stream(Stream stream, byte[] key, byte[] iv, bool encrypt = false) : base(stream)
public AES_IGE_Stream(Stream stream, int size, byte[] key, byte[] iv) : this(stream, key, iv, false) { ContentLength = size; }
public AES_IGE_Stream(Stream stream, byte[] key, byte[] iv, bool encrypt) : base(stream)
{
aesCrypto = encrypt ? Encryption.AesECB.CreateEncryptor(key, null) : Encryption.AesECB.CreateDecryptor(key, null);
if (encrypt) prevBytes = (byte[])iv.Clone();

View file

@ -595,5 +595,28 @@ namespace WTelegram
_ => null
};
}
/// <summary>Download and decrypt an encrypted file from Telegram Secret Chat into the outputStream</summary>
/// <param name="encryptedFile">The encrypted file to download &amp; decrypt</param>
/// <param name="media">The associated message media structure</param>
/// <param name="outputStream">Stream to write the decrypted file content to. This method does not close/dispose the stream</param>
/// <param name="progress">(optional) Callback for tracking the progression of the transfer</param>
/// <returns>The mime type of the decrypted file, <see langword="null"/> if unknown</returns>
public async Task<string> DownloadFile(EncryptedFile encryptedFile, DecryptedMessageMedia media, Stream outputStream, Client.ProgressCallback progress = null)
{
var (size, key, iv) = media.SizeKeyIV;
if (key == null || iv == null) throw new ArgumentException("Media has no information about encrypted file", nameof(media));
using var md5 = MD5.Create();
md5.TransformBlock(key, 0, 32, null, 0);
var res = md5.TransformFinalBlock(iv, 0, 32);
long fingerprint = BinaryPrimitives.ReadInt64LittleEndian(md5.Hash);
fingerprint ^= fingerprint >> 32;
if (encryptedFile.key_fingerprint != (int)fingerprint) throw new ApplicationException("Encrypted file fingerprint mismatch");
using var decryptStream = new AES_IGE_Stream(outputStream, size, key, iv);
var fileLocation = encryptedFile.ToFileLocation();
await client.DownloadFileAsync(fileLocation, decryptStream, encryptedFile.dc_id, encryptedFile.size, progress);
return media.MimeType;
}
}
}

View file

@ -78,7 +78,7 @@ namespace TL
partial class Peer
{
public abstract long ID { get; }
abstract internal IPeerInfo UserOrChat(Dictionary<long, User> users, Dictionary<long, ChatBase> chats);
internal abstract IPeerInfo UserOrChat(Dictionary<long, User> users, Dictionary<long, ChatBase> chats);
}
partial class PeerUser
{

View file

@ -288,8 +288,8 @@ namespace TL
/// <summary>Register device to receive <a href="https://corefork.telegram.org/api/push-updates">PUSH notifications</a> <para>See <a href="https://corefork.telegram.org/method/account.registerDevice"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/account.registerDevice#possible-errors">details</a>)</para></summary>
/// <param name="no_muted">Avoid receiving (silent and invisible background) notifications. Useful to save battery.</param>
/// <param name="token_type">Device token type.<br/><strong>Possible values</strong>:<br/><c>1</c> - APNS (device token for apple push)<br/><c>2</c> - FCM (firebase token for google firebase)<br/><c>3</c> - MPNS (channel URI for microsoft push)<br/><c>4</c> - Simple push (endpoint for firefox's simple push API)<br/><c>5</c> - Ubuntu phone (token for ubuntu push)<br/><c>6</c> - Blackberry (token for blackberry push)<br/><c>7</c> - Unused<br/><c>8</c> - WNS (windows push)<br/><c>9</c> - APNS VoIP (token for apple push VoIP)<br/><c>10</c> - Web push (web push, see below)<br/><c>11</c> - MPNS VoIP (token for microsoft push VoIP)<br/><c>12</c> - Tizen (token for tizen push)<br/><br/>For <c>10</c> web push, the token must be a JSON-encoded object containing the keys described in <a href="https://corefork.telegram.org/api/push-updates">PUSH updates</a></param>
/// <param name="token">Device token</param>
/// <param name="token_type">Device token type, see <a href="https://corefork.telegram.org/api/push-updates#subscribing-to-notifications">PUSH updates</a> for the possible values.</param>
/// <param name="token">Device token, see <a href="https://corefork.telegram.org/api/push-updates#subscribing-to-notifications">PUSH updates</a> for the possible values.</param>
/// <param name="app_sandbox">If <see cref="Bool.True"/> is transmitted, a sandbox-certificate will be used during transmission.</param>
/// <param name="secret">For FCM and APNS VoIP, optional encryption key used to encrypt push notifications</param>
/// <param name="other_uids">List of user identifiers of other users currently using the client</param>
@ -305,8 +305,8 @@ namespace TL
});
/// <summary>Deletes a device by its token, stops sending PUSH-notifications to it. <para>See <a href="https://corefork.telegram.org/method/account.unregisterDevice"/></para> <para>Possible <see cref="RpcException"/> codes: 400 (<a href="https://corefork.telegram.org/method/account.unregisterDevice#possible-errors">details</a>)</para></summary>
/// <param name="token_type">Device token type.<br/><strong>Possible values</strong>:<br/><c>1</c> - APNS (device token for apple push)<br/><c>2</c> - FCM (firebase token for google firebase)<br/><c>3</c> - MPNS (channel URI for microsoft push)<br/><c>4</c> - Simple push (endpoint for firefox's simple push API)<br/><c>5</c> - Ubuntu phone (token for ubuntu push)<br/><c>6</c> - Blackberry (token for blackberry push)<br/><c>7</c> - Unused<br/><c>8</c> - WNS (windows push)<br/><c>9</c> - APNS VoIP (token for apple push VoIP)<br/><c>10</c> - Web push (web push, see below)<br/><c>11</c> - MPNS VoIP (token for microsoft push VoIP)<br/><c>12</c> - Tizen (token for tizen push)<br/><br/>For <c>10</c> web push, the token must be a JSON-encoded object containing the keys described in <a href="https://corefork.telegram.org/api/push-updates">PUSH updates</a></param>
/// <param name="token">Device token</param>
/// <param name="token_type">Device token type, see <a href="https://corefork.telegram.org/api/push-updates#subscribing-to-notifications">PUSH updates</a> for the possible values.</param>
/// <param name="token">Device token, see <a href="https://corefork.telegram.org/api/push-updates#subscribing-to-notifications">PUSH updates</a> for the possible values.</param>
/// <param name="other_uids">List of user identifiers of other users currently using the client</param>
public static Task<bool> Account_UnregisterDevice(this Client client, int token_type, string token, params long[] other_uids)
=> client.Invoke(new Account_UnregisterDevice
@ -4570,7 +4570,7 @@ namespace TL
/// <param name="video_stopped">If set, the user's video will be disabled by default upon joining.</param>
/// <param name="call">The group call</param>
/// <param name="join_as">Join the group call, presenting yourself as the specified user/channel</param>
/// <param name="invite_hash">The invitation hash from the <a href="https://corefork.telegram.org/api/links#voice-chatvideo-chatlivestream-links">invite link »</a>, if provided allows speaking in a livestream or muted group chat.</param>
/// <param name="invite_hash">The invitation hash from the <a href="https://corefork.telegram.org/api/links#video-chatlivestream-links">invite link »</a>, if provided allows speaking in a livestream or muted group chat.</param>
/// <param name="params_">WebRTC parameters</param>
public static Task<UpdatesBase> Phone_JoinGroupCall(this Client client, InputGroupCall call, InputPeer join_as, DataJSON params_, bool muted = false, bool video_stopped = false, string invite_hash = null)
=> client.Invoke(new Phone_JoinGroupCall
@ -4714,7 +4714,7 @@ namespace TL
peer = peer,
});
/// <summary>Get an <a href="https://corefork.telegram.org/api/links#voice-chatvideo-chatlivestream-links">invite link</a> for a group call or livestream <para>See <a href="https://corefork.telegram.org/method/phone.exportGroupCallInvite"/></para> <para>Possible <see cref="RpcException"/> codes: 403 (<a href="https://corefork.telegram.org/method/phone.exportGroupCallInvite#possible-errors">details</a>)</para></summary>
/// <summary>Get an <a href="https://corefork.telegram.org/api/links#video-chatlivestream-links">invite link</a> for a group call or livestream <para>See <a href="https://corefork.telegram.org/method/phone.exportGroupCallInvite"/></para> <para>Possible <see cref="RpcException"/> codes: 403 (<a href="https://corefork.telegram.org/method/phone.exportGroupCallInvite#possible-errors">details</a>)</para></summary>
/// <param name="can_self_unmute">For livestreams or muted group chats, if set, users that join using this link will be able to speak without explicitly requesting permission by (for example by raising their hand).</param>
/// <param name="call">The group call</param>
public static Task<Phone_ExportedGroupCallInvite> Phone_ExportGroupCallInvite(this Client client, InputGroupCall call, bool can_self_unmute = false)

View file

@ -31,7 +31,8 @@ namespace TL
/// <remarks>a <c>null</c> value means <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaEmpty">decryptedMessageMediaEmpty</a></remarks>
public abstract class DecryptedMessageMedia : IObject
{
public virtual (int size, byte[] key, byte[] iv) SizeKeyIV { get => default; set => throw new ApplicationException("Incompatible DecryptedMessageMedia"); }
public virtual string MimeType { get; }
internal virtual (int size, byte[] key, byte[] iv) SizeKeyIV { get => default; set => throw new ApplicationException("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>
@ -108,7 +109,8 @@ namespace TL
/// <summary>Initialization vector</summary>
public byte[] iv;
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
public override string MimeType => "image/jpeg";
internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// <summary>Video attached to an encrypted message. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaVideo"/></para></summary>
[TLDef(0x4CEE6EF3)]
@ -133,7 +135,7 @@ namespace TL
/// <summary>Initialization vector</summary>
public byte[] iv;
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// <summary>GeoPoint attached to an encrypted message. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaGeoPoint"/></para></summary>
[TLDef(0x35480A59)]
@ -177,7 +179,10 @@ namespace TL
/// <summary>Initialization</summary>
public byte[] iv;
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
/// <summary>File MIME-type</summary>
public override string MimeType => mime_type;
internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// <summary>Audio file attached to a secret chat message. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaAudio"/></para></summary>
[TLDef(0x6080758F)]
@ -192,7 +197,7 @@ namespace TL
/// <summary>Initialization vector</summary>
public byte[] iv;
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// <summary>Setting of a message lifetime after reading. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageActionSetMessageTTL"/></para></summary>
@ -305,7 +310,10 @@ namespace TL
/// <summary>Initialization vector</summary>
public byte[] iv;
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
/// <summary>MIME-type of the video file<br/>Parameter added in Layer 17.</summary>
public override string MimeType => mime_type;
internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// <summary>Audio file attached to a secret chat message. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaAudio"/></para></summary>
[TLDef(0x57E0A9CB)]
@ -322,7 +330,10 @@ namespace TL
/// <summary>Initialization vector</summary>
public byte[] iv;
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
/// <summary>MIME-type of the audio file<br/>Parameter added in Layer 13.</summary>
public override string MimeType => mime_type;
internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// <summary>Request for the other party in a Secret Chat to automatically resend a contiguous range of previously sent messages, as explained in <a href="https://corefork.telegram.org/api/end-to-end/seq_no">Sequence number is Secret Chats</a>. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageActionResend"/></para></summary>
@ -463,7 +474,8 @@ namespace TL
/// <summary>Caption</summary>
public string caption;
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
public override string MimeType => "image/jpeg";
internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// <summary>Video attached to an encrypted message. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaVideo"/></para></summary>
[TLDef(0x970C8C0E)]
@ -492,7 +504,10 @@ namespace TL
/// <summary>Caption</summary>
public string caption;
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
/// <summary>MIME-type of the video file<br/>Parameter added in Layer 17.</summary>
public override string MimeType => mime_type;
internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// <summary>Document attached to a message in a secret chat. <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaDocument"/></para></summary>
[TLDef(0x7AFE8AE2)]
@ -517,7 +532,10 @@ namespace TL
/// <summary>Caption</summary>
public string caption;
public override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
/// <summary>File MIME-type</summary>
public override string MimeType => mime_type;
internal override (int, byte[], byte[]) SizeKeyIV { get => (size, key, iv); set => (size, key, iv) = value; }
}
/// <summary>Venue <para>See <a href="https://corefork.telegram.org/constructor/decryptedMessageMediaVenue"/></para></summary>
[TLDef(0x8A0DF56F)]
@ -727,6 +745,9 @@ namespace TL
public int dc_id;
/// <summary>Attributes for media types</summary>
public DocumentAttribute[] attributes;
/// <summary>Mime type</summary>
public override string MimeType => mime_type;
}
/// <summary>File is currently unavailable. <para>See <a href="https://corefork.telegram.org/constructor/fileLocationUnavailable"/></para></summary>