mirror of
https://github.com/wiz0u/WTelegramClient.git
synced 2025-12-06 06:52:01 +01:00
Support for TLS MTProxy
This commit is contained in:
parent
16258cb5ba
commit
d6d656c8fe
20
EXAMPLES.md
20
EXAMPLES.md
|
|
@ -14,16 +14,6 @@ and add at least these variables with adequate value: **api_id, api_hash, phone_
|
|||
Remember that these are just simple example codes that you should adjust to your needs.
|
||||
In real production code, you might want to properly test the success of each operation or handle exceptions.
|
||||
|
||||
<a name="join-channel"></a>
|
||||
### Join a channel/group by @channelname
|
||||
```csharp
|
||||
using var client = new WTelegram.Client(Environment.GetEnvironmentVariable);
|
||||
await client.LoginUserIfNeeded();
|
||||
var resolved = await client.Contacts_ResolveUsername("channelname"); // without the @
|
||||
if (resolved.Chat is Channel channel)
|
||||
await client.Channels_JoinChannel(channel);
|
||||
```
|
||||
|
||||
<a name="msg-by-name"></a>
|
||||
### Send a message to someone by @username
|
||||
```csharp
|
||||
|
|
@ -218,6 +208,16 @@ var channel = (Channel)chats.chats[1234567890]; // the channel we want
|
|||
var participants = await client.Channels_GetAllParticipants(channel);
|
||||
```
|
||||
|
||||
<a name="join-channel"></a>
|
||||
### Join a channel/group by @channelname
|
||||
```csharp
|
||||
using var client = new WTelegram.Client(Environment.GetEnvironmentVariable);
|
||||
await client.LoginUserIfNeeded();
|
||||
var resolved = await client.Contacts_ResolveUsername("channelname"); // without the @
|
||||
if (resolved.Chat is Channel channel)
|
||||
await client.Channels_JoinChannel(channel);
|
||||
```
|
||||
|
||||
<a name="add-members"></a>
|
||||
### Add/Invite/Remove someone in a chat
|
||||
```csharp
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ namespace WTelegram
|
|||
private readonly Session _session;
|
||||
private Session.DCSession _dcSession;
|
||||
private TcpClient _tcpClient;
|
||||
private NetworkStream _networkStream;
|
||||
private Stream _networkStream;
|
||||
private IObject _lastSentMsg;
|
||||
private long _lastRecvMsgId;
|
||||
private readonly List<long> _msgsToAck = new();
|
||||
|
|
@ -223,6 +223,7 @@ namespace WTelegram
|
|||
|
||||
private async Task DoConnectAsync()
|
||||
{
|
||||
_cts = new();
|
||||
IPEndPoint endpoint = null;
|
||||
byte[] preamble, secret = null;
|
||||
int dcId = _dcSession?.DcID ?? 0;
|
||||
|
|
@ -235,9 +236,10 @@ namespace WTelegram
|
|||
var server = parms["server"];
|
||||
int port = int.Parse(parms["port"]);
|
||||
var str = parms["secret"]; // can be hex or base64
|
||||
secret = str.All("0123456789ABCDEFabcdef".Contains) ? Convert.FromHexString(str) :
|
||||
var secretBytes = secret = str.All("0123456789ABCDEFabcdef".Contains) ? Convert.FromHexString(str) :
|
||||
System.Convert.FromBase64String(str.Replace('_', '/').Replace('-', '+') + new string('=', (2147483644 - str.Length) % 4));
|
||||
if ((secret.Length == 17 && secret[0] == 0xDD) || (secret.Length >= 21 && secret[0] == 0xEE))
|
||||
var tlsMode = secret.Length >= 21 && secret[0] == 0xEE;
|
||||
if (tlsMode || (secret.Length == 17 && secret[0] == 0xDD))
|
||||
{
|
||||
_paddedMode = true;
|
||||
secret = secret[1..17];
|
||||
|
|
@ -245,6 +247,9 @@ namespace WTelegram
|
|||
else if (secret.Length != 16) throw new ArgumentException("Invalid/unsupported secret", nameof(secret));
|
||||
Helpers.Log(2, $"Connecting to DC {dcId} via MTProxy {server}:{port}...");
|
||||
_tcpClient = await TcpHandler(server, port);
|
||||
_networkStream = _tcpClient.GetStream();
|
||||
if (tlsMode)
|
||||
_networkStream = await TlsStream.HandshakeAsync(_networkStream, secret, secretBytes[17..], _cts.Token);
|
||||
#else
|
||||
throw new Exception("Library was not compiled with OBFUSCATION symbol");
|
||||
#endif
|
||||
|
|
@ -305,8 +310,8 @@ namespace WTelegram
|
|||
throw;
|
||||
}
|
||||
_tcpClient = tcpClient;
|
||||
_networkStream = _tcpClient.GetStream();
|
||||
}
|
||||
_networkStream = _tcpClient.GetStream();
|
||||
|
||||
byte protocolId = (byte)(_paddedMode ? 0xDD : 0xEE);
|
||||
#if OBFUSCATION
|
||||
|
|
@ -314,9 +319,8 @@ namespace WTelegram
|
|||
#else
|
||||
preamble = new byte[] { protocolId, protocolId, protocolId, protocolId };
|
||||
#endif
|
||||
await _networkStream.WriteAsync(preamble, 0, preamble.Length);
|
||||
await _networkStream.WriteAsync(preamble, 0, preamble.Length, _cts.Token);
|
||||
|
||||
_cts = new();
|
||||
_saltChangeCounter = 0;
|
||||
_reactorTask = Reactor(_networkStream, _cts);
|
||||
_sendSemaphore.Release();
|
||||
|
|
@ -445,7 +449,7 @@ namespace WTelegram
|
|||
}
|
||||
}
|
||||
|
||||
private async Task Reactor(NetworkStream stream, CancellationTokenSource cts)
|
||||
private async Task Reactor(Stream stream, CancellationTokenSource cts)
|
||||
{
|
||||
const int MinBufferSize = 1024;
|
||||
var data = new byte[MinBufferSize];
|
||||
|
|
@ -454,17 +458,19 @@ namespace WTelegram
|
|||
IObject obj = null;
|
||||
try
|
||||
{
|
||||
if (await FullReadAsync(stream, data, 4, cts.Token) != 4)
|
||||
if (await stream.FullReadAsync(data, 4, cts.Token) != 4)
|
||||
throw new ApplicationException(ConnectionShutDown);
|
||||
#if OBFUSCATION
|
||||
_recvCtr.EncryptDecrypt(data, 4);
|
||||
#endif
|
||||
int payloadLen = BinaryPrimitives.ReadInt32LittleEndian(data);
|
||||
if (payloadLen > data.Length)
|
||||
if (payloadLen <= 0)
|
||||
throw new ApplicationException("Could not read frame data : Invalid payload length");
|
||||
else if (payloadLen > data.Length)
|
||||
data = new byte[payloadLen];
|
||||
else if (Math.Max(payloadLen, MinBufferSize) < data.Length / 4)
|
||||
data = new byte[Math.Max(payloadLen, MinBufferSize)];
|
||||
if (await FullReadAsync(stream, 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");
|
||||
#if OBFUSCATION
|
||||
_recvCtr.EncryptDecrypt(data, payloadLen);
|
||||
|
|
@ -629,17 +635,6 @@ namespace WTelegram
|
|||
};
|
||||
}
|
||||
|
||||
private static async Task<int> FullReadAsync(Stream stream, byte[] buffer, int length, CancellationToken ct = default)
|
||||
{
|
||||
for (int offset = 0; offset < length;)
|
||||
{
|
||||
var read = await stream.ReadAsync(buffer, offset, length - offset, ct);
|
||||
if (read == 0) return offset;
|
||||
offset += read;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private async Task<long> SendAsync(IObject msg, bool isContent)
|
||||
{
|
||||
if (_dcSession.AuthKeyID != 0 && isContent && CheckMsgsToAck() is MsgsAck msgsAck)
|
||||
|
|
@ -743,7 +738,7 @@ namespace WTelegram
|
|||
seqno = reader.ReadInt32(),
|
||||
bytes = reader.ReadInt32(),
|
||||
};
|
||||
if ((msg.seqno & 1) != 0) lock(_msgsToAck) _msgsToAck.Add(msg.msg_id);
|
||||
if ((msg.seqno & 1) != 0) lock (_msgsToAck) _msgsToAck.Add(msg.msg_id);
|
||||
var pos = reader.BaseStream.Position;
|
||||
try
|
||||
{
|
||||
|
|
@ -801,11 +796,30 @@ namespace WTelegram
|
|||
}
|
||||
else
|
||||
{
|
||||
result = reader.ReadTLObject();
|
||||
if (MsgIdToStamp(msgId) >= _session.SessionStart)
|
||||
Helpers.Log(4, $" → {result?.GetType().Name,-37} for unknown msgId #{(short)msgId.GetHashCode():X4}");
|
||||
string typeName;
|
||||
var ctorNb = reader.ReadUInt32();
|
||||
if (ctorNb == Layer.VectorCtor)
|
||||
{
|
||||
reader.BaseStream.Position -= 4;
|
||||
var array = reader.ReadTLVector(typeof(IObject[]));
|
||||
if (array.Length > 0)
|
||||
{
|
||||
for (type = array.GetValue(0).GetType(); type.BaseType != typeof(object);) type = type.BaseType;
|
||||
typeName = type.Name + "[]";
|
||||
}
|
||||
else
|
||||
typeName = "object[]";
|
||||
result = array;
|
||||
}
|
||||
else
|
||||
Helpers.Log(1, $" → {result?.GetType().Name,-37} for past msgId #{(short)msgId.GetHashCode():X4}");
|
||||
{
|
||||
result = reader.ReadTLObject(ctorNb);
|
||||
typeName = result?.GetType().Name;
|
||||
}
|
||||
if (MsgIdToStamp(msgId) >= _session.SessionStart)
|
||||
Helpers.Log(4, $" → {typeName,-37} for unknown msgId #{(short)msgId.GetHashCode():X4}");
|
||||
else
|
||||
Helpers.Log(1, $" → {typeName,-37} for past msgId #{(short)msgId.GetHashCode():X4}");
|
||||
}
|
||||
return new RpcResult { req_msg_id = msgId, result = result };
|
||||
}
|
||||
|
|
@ -1219,7 +1233,7 @@ namespace WTelegram
|
|||
for (long bytesLeft = length; !abort && bytesLeft != 0; file_part++)
|
||||
{
|
||||
var bytes = new byte[Math.Min(FilePartSize, bytesLeft)];
|
||||
read = await FullReadAsync(stream, bytes, bytes.Length);
|
||||
read = await stream.FullReadAsync(bytes, bytes.Length, default);
|
||||
await _parallelTransfers.WaitAsync();
|
||||
bytesLeft -= read;
|
||||
var task = SavePart(file_part, bytes);
|
||||
|
|
@ -1369,7 +1383,7 @@ namespace WTelegram
|
|||
/// <returns>MIME type of the document/thumbnail</returns>
|
||||
public async Task<string> DownloadFileAsync(Document document, Stream outputStream, PhotoSizeBase thumbSize = null, ProgressCallback progress = null)
|
||||
{
|
||||
if (thumbSize is PhotoStrippedSize psp)
|
||||
if (thumbSize is PhotoStrippedSize psp)
|
||||
return InflateStrippedThumb(outputStream, psp.bytes) ? "image/jpeg" : null;
|
||||
var fileLocation = document.ToFileLocation(thumbSize);
|
||||
var fileType = await DownloadFileAsync(fileLocation, outputStream, document.dc_id, thumbSize?.FileSize ?? document.size, progress);
|
||||
|
|
@ -1617,7 +1631,7 @@ namespace WTelegram
|
|||
return new Updates { date = DateTime.UtcNow, users = new(), updates = Array.Empty<Update>(),
|
||||
chats = (await this.Messages_GetChats(new[] { chat.chat_id })).chats };
|
||||
case InputPeerChannel channel:
|
||||
return await this.Channels_EditAdmin(channel, user,
|
||||
return await this.Channels_EditAdmin(channel, user,
|
||||
new ChatAdminRights { flags = is_admin ? (ChatAdminRights.Flags)0x8BF : 0 }, null);
|
||||
default:
|
||||
throw new ArgumentException("This method works on Chat & Channel only");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WTelegram
|
||||
{
|
||||
|
|
@ -40,6 +42,19 @@ namespace WTelegram
|
|||
#endif
|
||||
}
|
||||
|
||||
public static async Task<int> FullReadAsync(this Stream stream, byte[] buffer, int length, CancellationToken ct)
|
||||
{
|
||||
for (int offset = 0; offset < length;)
|
||||
{
|
||||
#pragma warning disable CA1835
|
||||
var read = await stream.ReadAsync(buffer, offset, length - offset, ct);
|
||||
#pragma warning restore CA1835
|
||||
if (read == 0) return offset;
|
||||
offset += read;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
internal static byte[] ToBigEndian(ulong value) // variable-size buffer
|
||||
{
|
||||
int i = 1;
|
||||
|
|
|
|||
|
|
@ -358,8 +358,10 @@ namespace TL
|
|||
partial class Contacts_ResolvedPeer
|
||||
{
|
||||
public static implicit operator InputPeer(Contacts_ResolvedPeer resolved) => resolved.UserOrChat.ToInputPeer();
|
||||
/// <returns>A <see cref="TL.User"/>, or <see langword="null"/> if the username was for a channel</returns>
|
||||
public User User => peer is PeerUser pu ? users[pu.user_id] : null;
|
||||
public ChatBase Chat => peer is PeerChat or PeerChannel ? chats[peer.ID] : null;
|
||||
/// <returns>A <see cref="Channel"/> or <see cref="ChannelForbidden"/>, or <see langword="null"/> if the username was for a user</returns>
|
||||
public ChatBase Chat => peer is PeerChannel or PeerChat ? chats[peer.ID] : null;
|
||||
}
|
||||
|
||||
partial class Updates_ChannelDifferenceBase
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// This file is generated automatically using the Generator class
|
||||
// This file is generated automatically
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace TL
|
||||
{
|
||||
|
|
@ -14,7 +15,8 @@ namespace TL
|
|||
internal const uint MsgContainerCtor = 0x73F1F8DC;
|
||||
internal const uint BadMsgCtor = 0xA7EFF811;
|
||||
|
||||
internal readonly static Dictionary<uint, Type> Table = new()
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public readonly static Dictionary<uint, Type> Table = new()
|
||||
{
|
||||
[0xF35C6D01] = typeof(RpcResult),
|
||||
[0x5BB8E511] = typeof(_Message),
|
||||
|
|
|
|||
161
src/TlsStream.cs
Normal file
161
src/TlsStream.cs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// necessary for .NET Standard 2.0 compilation:
|
||||
#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
|
||||
|
||||
namespace WTelegram
|
||||
{
|
||||
class TlsStream : Stream
|
||||
{
|
||||
public TlsStream(Stream innerStream) => _innerStream = innerStream;
|
||||
private readonly Stream _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[] 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();
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct)
|
||||
{
|
||||
if (_tlsFrameleft == 0)
|
||||
{
|
||||
if (await _innerStream.FullReadAsync(_tlsReadHeader, 5, ct) != 5)
|
||||
return 0;
|
||||
if (_tlsReadHeader[0] != 0x17 || _tlsReadHeader[1] != 0x03 || _tlsReadHeader[2] != 0x03)
|
||||
throw new ApplicationException("Could not read frame data : Invalid TLS header");
|
||||
_tlsFrameleft = (_tlsReadHeader[3] << 8) + _tlsReadHeader[4];
|
||||
}
|
||||
var read = await _innerStream.ReadAsync(buffer, offset, Math.Min(count, _tlsFrameleft), ct);
|
||||
_tlsFrameleft -= read;
|
||||
return read;
|
||||
}
|
||||
|
||||
public override async Task WriteAsync(byte[] buffer, int start, int count, CancellationToken ct)
|
||||
{
|
||||
for (int offset = 0; offset < count;)
|
||||
{
|
||||
int len = Math.Min(count - offset, 2878);
|
||||
_tlsSendHeader[3] = (byte)(len >> 8);
|
||||
_tlsSendHeader[4] = (byte)len;
|
||||
await _innerStream.WriteAsync(_tlsSendHeader, 0, _tlsSendHeader.Length, ct);
|
||||
await _innerStream.WriteAsync(buffer, start + offset, len, ct);
|
||||
offset += len;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<TlsStream> HandshakeAsync(Stream stream, byte[] key, byte[] domain, CancellationToken ct)
|
||||
{
|
||||
var clientHello = TlsClientHello(key, domain);
|
||||
await stream.WriteAsync(clientHello, 0, clientHello.Length, ct);
|
||||
|
||||
var part1 = new byte[5];
|
||||
if (await stream.FullReadAsync(part1, 5, ct) == 5)
|
||||
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];
|
||||
if (await stream.FullReadAsync(part23, part23.Length, ct) == part23.Length)
|
||||
if (TlsServerHelloPart3.SequenceEqual(part23.Skip(part2size).Take(TlsServerHelloPart3.Length)))
|
||||
{
|
||||
var part4size = BinaryPrimitives.ReadUInt16BigEndian(part23.AsSpan(part23.Length - 2));
|
||||
var part4 = new byte[part4size];
|
||||
if (await stream.FullReadAsync(part4, part4size, ct) == part4size)
|
||||
{
|
||||
var serverDigest = part23[6..38];
|
||||
Array.Clear(part23, 6, 32); // clear server digest from received parts
|
||||
var hmc = new HMACSHA256(key); // hash the client digest + all received parts
|
||||
hmc.TransformBlock(clientHello, 11, 32, null, 0);
|
||||
hmc.TransformBlock(part1, 0, part1.Length, null, 0);
|
||||
hmc.TransformBlock(part23, 0, part23.Length, null, 0);
|
||||
hmc.TransformFinalBlock(part4, 0, part4.Length);
|
||||
if (serverDigest.SequenceEqual(hmc.Hash))
|
||||
{
|
||||
Helpers.Log(2, "TLS Handshake succeeded");
|
||||
await stream.WriteAsync(TlsClientPrefix, 0, TlsClientPrefix.Length, ct);
|
||||
return new TlsStream(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new ApplicationException("TLS Handshake failed");
|
||||
}
|
||||
|
||||
static readonly byte[] TlsClientHello1 = new byte[] {
|
||||
0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03 };
|
||||
static readonly byte[] TlsClientHello2 = new byte[] {
|
||||
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[] {
|
||||
0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08,
|
||||
0, 0, // 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,
|
||||
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
|
||||
0x00, 0x01, 0x00, 0x00, 0x1d, 0x00, 0x20 };
|
||||
static readonly byte[] TlsClientHello4 = new byte[] {
|
||||
0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2b, 0x00, 0x0b, 0x0a,
|
||||
0, 0, // grease 6
|
||||
0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x02,
|
||||
0, 0, // grease 3
|
||||
0x00, 0x01, 0x00, 0x00, 0x15 };
|
||||
|
||||
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]);
|
||||
|
||||
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[78] = buffer[79] = greases[0];
|
||||
buffer[114] = buffer[115] = greases[2];
|
||||
buffer[121] = (byte)(dlen + 5);
|
||||
buffer[123] = (byte)(dlen + 3);
|
||||
buffer[126] = (byte)dlen;
|
||||
domain.CopyTo(buffer, 127);
|
||||
TlsClientHello3.CopyTo(buffer, 127 + dlen);
|
||||
buffer[142 + dlen] = buffer[143 + dlen] = greases[4];
|
||||
buffer[219 + dlen] = buffer[220 + dlen] = greases[4];
|
||||
Encryption.RNG.GetBytes(buffer, 228 + dlen, 32); // public key
|
||||
TlsClientHello4.CopyTo(buffer, 260 + dlen);
|
||||
buffer[271 + dlen] = buffer[272 + dlen] = greases[6];
|
||||
buffer[288 + dlen] = buffer[289 + dlen] = greases[3];
|
||||
buffer[296 + dlen] = (byte)(220 - dlen);
|
||||
|
||||
// patch-in digest with timestamp
|
||||
using var hmac = new HMACSHA256(key);
|
||||
var digest = hmac.ComputeHash(buffer);
|
||||
var stamp = BinaryPrimitives.ReadInt32LittleEndian(digest.AsSpan(28));
|
||||
stamp ^= (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
BinaryPrimitives.WriteInt32LittleEndian(digest.AsSpan(28), stamp);
|
||||
digest.CopyTo(buffer, 11);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue