using Shadowsocks.Protocol.Shadowsocks.Crypto; using System; using System.Collections.Generic; using System.IO.Pipelines; using System.Net; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Threading.Tasks; namespace Shadowsocks.Protocol.Shadowsocks { public class AeadClient : IStreamClient { private CryptoParameter cryptoParameter; private readonly byte[] mainKey; /// /// ss-subkey /// private static ReadOnlySpan _ssSubKeyInfo => new byte[] { 0x73, 0x73, 0x2d, 0x73, 0x75, 0x62, 0x6b, 0x65, 0x79 }; public AeadClient(CryptoParameter parameter, string password) { cryptoParameter = parameter; mainKey = CryptoUtils.SSKDF(password, parameter.KeySize); if (!parameter.IsAead) throw new NotSupportedException($"Unsupported method."); } public AeadClient(CryptoParameter parameter, byte[] key) { cryptoParameter = parameter; mainKey = key; } public Task Connect(EndPoint destination, IDuplexPipe client, IDuplexPipe server) => // destination is ignored, this is just a converter Task.WhenAll(ConvertUplink(client, server), ConvertDownlink(client, server)); public async Task ConvertUplink(IDuplexPipe client, IDuplexPipe server) { var up = cryptoParameter.GetCrypto(); var pmp = new ProtocolMessagePipe(server); var salt = new SaltMessage(16, true); await pmp.WriteAsync(salt); var key = new byte[cryptoParameter.KeySize]; HKDF.DeriveKey(HashAlgorithmName.SHA1, mainKey, key, salt.Salt.Span, _ssSubKeyInfo); up.Init(key, null); Memory nonce = new byte[cryptoParameter.NonceSize]; nonce.Span.Fill(0); // TODO write salt with data while (true) { var result = await client.Input.ReadAsync(); if (result.IsCanceled || result.IsCompleted) return; // TODO compress into one chunk when possible foreach (var item in result.Buffer) { foreach (var i in SplitBigChunk(item)) { await pmp.WriteAsync(new AeadBlockMessage(up, nonce, cryptoParameter) { // in send routine, Data is readonly Data = MemoryMarshal.AsMemory(i), }); } } client.Input.AdvanceTo(result.Buffer.End); } } public async Task ConvertDownlink(IDuplexPipe client, IDuplexPipe server) { var down = cryptoParameter.GetCrypto(); var pmp = new ProtocolMessagePipe(server); var salt = await pmp.ReadAsync(new SaltMessage(cryptoParameter.KeySize)); var key = new byte[cryptoParameter.KeySize]; HKDF.DeriveKey(HashAlgorithmName.SHA1, mainKey, key, salt.Salt.Span, _ssSubKeyInfo); down.Init(key, null); Memory nonce = new byte[cryptoParameter.NonceSize]; nonce.Span.Fill(0); while (true) { try { var block = await pmp.ReadAsync(new AeadBlockMessage(down, nonce, cryptoParameter)); await client.Output.WriteAsync(block.Data); client.Output.Advance(block.Data.Length); } catch (FormatException) { return; } } } public List> SplitBigChunk(ReadOnlyMemory mem) { var l = new List>(mem.Length / 0x3fff + 1); while (mem.Length > 0x3fff) { l.Add(mem.Slice(0, 0x3fff)); mem = mem.Slice(0x4000); } l.Add(mem); return l; } } }