diff --git a/src/Client.cs b/src/Client.cs index 59056d7..6d5f8fb 100644 --- a/src/Client.cs +++ b/src/Client.cs @@ -1227,7 +1227,7 @@ namespace WTelegram => UploadFileAsync(File.OpenRead(pathname), Path.GetFileName(pathname), progress); /// Helper function to upload a file to Telegram - /// Content of the file to upload + /// Content of the file to upload. This method close/dispose the stream /// Name of the file /// (optional) Callback for tracking the progression of the transfer /// an or than can be used in various requests @@ -1406,7 +1406,7 @@ namespace WTelegram using var stream = await response.Content.ReadAsStreamAsync(); mimeType = response.Content.Headers.ContentType?.MediaType; if (response.Content.Headers.ContentLength is long length) - return await UploadFileAsync(new Helpers.StreamWithLength { length = length, innerStream = stream }, filename); + return await UploadFileAsync(new Helpers.IndirectStream(stream) { ContentLength = length }, filename); else { using var ms = new MemoryStream(); diff --git a/src/Helpers.cs b/src/Helpers.cs index 71a044c..355275f 100644 --- a/src/Helpers.cs +++ b/src/Helpers.cs @@ -253,20 +253,22 @@ namespace WTelegram 0x3f, 0x00 }; - internal class StreamWithLength : Stream + public class IndirectStream : Stream { - public Stream innerStream; - public long length; - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => false; - public override long Length => length; - public override long Position { get => innerStream.Position; set => throw new NotSupportedException(); } - public override void Flush() { } - public override int Read(byte[] buffer, int offset, int count) => innerStream.Read(buffer, offset, count); - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - public override void SetLength(long value) => throw new NotSupportedException(); - public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + public IndirectStream(Stream innerStream) => _innerStream = innerStream; + public long? ContentLength; + protected readonly Stream _innerStream; + public override bool CanRead => _innerStream.CanRead; + public override bool CanSeek => _innerStream.CanSeek; + public override bool CanWrite => _innerStream.CanWrite; + public override long Length => ContentLength ?? _innerStream.Length; + public override long Position { get => _innerStream.Position; set => _innerStream.Position = value; } + public override void Flush() => _innerStream.Flush(); + public override int Read(byte[] buffer, int offset, int count) => _innerStream.Read(buffer, offset, count); + public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin); + public override void SetLength(long value) => _innerStream.SetLength(value); + public override void Write(byte[] buffer, int offset, int count) => _innerStream.Write(buffer, offset, count); + protected override void Dispose(bool disposing) => _innerStream.Dispose(); } } } diff --git a/src/TlsStream.cs b/src/TlsStream.cs index 7634835..3391f67 100644 --- a/src/TlsStream.cs +++ b/src/TlsStream.cs @@ -11,28 +11,15 @@ using System.Threading.Tasks; namespace WTelegram { - class TlsStream : Stream + class TlsStream : Helpers.IndirectStream { - public TlsStream(Stream innerStream) => _innerStream = innerStream; - private readonly Stream _innerStream; + public TlsStream(Stream innerStream) : base(innerStream) { } private int _tlsFrameleft; private readonly byte[] _tlsSendHeader = new byte[] { 0x17, 0x03, 0x03, 0, 0 }; private readonly byte[] _tlsReadHeader = new byte[5]; - static readonly byte[] TlsServerHelloPart3 = new byte[] { 0x14, 0x03, 0x03, 0x00, 0x01, 0x01, 0x17, 0x03, 0x03 }; + static readonly byte[] TlsServerHello3 = new byte[] { 0x14, 0x03, 0x03, 0x00, 0x01, 0x01, 0x17, 0x03, 0x03 }; static readonly byte[] TlsClientPrefix = new byte[] { 0x14, 0x03, 0x03, 0x00, 0x01, 0x01 }; - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => true; - public override long Length => throw new NotSupportedException(); - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - public override void Flush() => _innerStream.Flush(); - public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - public override void SetLength(long value) => throw new NotSupportedException(); - public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - protected override void Dispose(bool disposing) => _innerStream.Dispose(); - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct) { if (_tlsFrameleft == 0) @@ -71,9 +58,9 @@ namespace WTelegram if (part1[0] == 0x16 && part1[1] == 0x03 && part1[2] == 0x03) { var part2size = BinaryPrimitives.ReadUInt16BigEndian(part1.AsSpan(3)); - var part23 = new byte[part2size + TlsServerHelloPart3.Length + 2]; + var part23 = new byte[part2size + TlsServerHello3.Length + 2]; if (await stream.FullReadAsync(part23, part23.Length, ct) == part23.Length) - if (TlsServerHelloPart3.SequenceEqual(part23.Skip(part2size).Take(TlsServerHelloPart3.Length))) + if (TlsServerHello3.SequenceEqual(part23.Skip(part2size).Take(TlsServerHello3.Length))) { var part4size = BinaryPrimitives.ReadUInt16BigEndian(part23.AsSpan(part23.Length - 2)); var part4 = new byte[part4size]; @@ -98,42 +85,50 @@ namespace WTelegram throw new ApplicationException("TLS Handshake failed"); } - static readonly byte[] TlsClientHello1 = new byte[] { + static readonly byte[] TlsClientHello1 = new byte[11] { 0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03 }; - static readonly byte[] TlsClientHello2 = new byte[] { + // digest[32] + // 0x20 + // random[32] + // 0x00, 0x20, grease(0) GREASE are two identical bytes ending with nibble 'A' + static readonly byte[] TlsClientHello2 = new byte[34] { 0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x01, 0x00, 0x01, 0x93 }; - static readonly byte[] TlsClientHello3 = new byte[] { + // grease(2), 0x00, 0x00, 0x00, 0x00 + // len { len { 0x00 len { domain } } } len is 16-bit big-endian length of the following block of data + static readonly byte[] TlsClientHello3 = new byte[101] { 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, - 0, 0, // grease 4 + 0x4A, 0x4A, // = grease(4) 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, - 0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x12, 0x00, + 0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x12, 0x00, 0x10, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x33, 0x00, 0x2b, 0x00, 0x29, - 0, 0, // grease 4 + 0x4A, 0x4A, // = grease(4) 0x00, 0x01, 0x00, 0x00, 0x1d, 0x00, 0x20 }; - static readonly byte[] TlsClientHello4 = new byte[] { + // random[32] = public key + static readonly byte[] TlsClientHello4 = new byte[35] { 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2b, 0x00, 0x0b, 0x0a, - 0, 0, // grease 6 + 0x6A, 0x6A, // = grease(6) 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x02, - 0, 0, // grease 3 + 0x3A, 0x3A, // = grease(3) 0x00, 0x01, 0x00, 0x00, 0x15 }; + // len { padding } padding with NUL bytes to reach 517 bytes static byte[] TlsClientHello(byte[] key, byte[] domain) { int dlen = domain.Length; var greases = new byte[7]; Encryption.RNG.GetBytes(greases); - for (int i = 0; i < 7; i++) greases[i] = (byte)((greases[i] & 0xF0) + 0x0A); - if (greases[3] == greases[2]) greases[3] = (byte)(0x10 ^ greases[3]); + for (int i = 0; i < 7; i++) greases[i] = (byte)((greases[i] & 0xF0) + 0x0A); + if (greases[3] == greases[2]) greases[3] ^= 0x10; var buffer = new byte[517]; TlsClientHello1.CopyTo(buffer, 0); - TlsClientHello2.CopyTo(buffer, 80); - buffer[43] = buffer[77] = 0x20; Encryption.RNG.GetBytes(buffer, 44, 32); + buffer[43] = buffer[77] = 0x20; buffer[78] = buffer[79] = greases[0]; + TlsClientHello2.CopyTo(buffer, 80); buffer[114] = buffer[115] = greases[2]; buffer[121] = (byte)(dlen + 5); buffer[123] = (byte)(dlen + 3); @@ -156,7 +151,6 @@ namespace WTelegram stamp ^= (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); BinaryPrimitives.WriteInt32LittleEndian(digest.AsSpan(28), stamp); digest.CopyTo(buffer, 11); - return buffer; } }