@@ -4,7 +4,7 @@ using System.IO; | |||
using System.Net.Sockets; | |||
using System.Net; | |||
using System.Diagnostics; | |||
using System.Text; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Controller | |||
@@ -70,6 +70,18 @@ namespace Shadowsocks.Controller | |||
WriteToLogFile("[D] " + o); | |||
} | |||
[Conditional("DEBUG")] | |||
public static void Dump(string tag, byte[] arr, int length) | |||
{ | |||
var sb = new StringBuilder($"{Environment.NewLine}{tag}: "); | |||
for (int i = 0; i < length - 1; i++) { | |||
sb.Append($"0x{arr[i]:X2}, "); | |||
} | |||
sb.Append($"0x{arr[length - 1]:X2}"); | |||
sb.Append(Environment.NewLine); | |||
Debug(sb.ToString()); | |||
} | |||
[Conditional("DEBUG")] | |||
public static void Debug(EndPoint local, EndPoint remote, int len, string header = null, string tailer = null) | |||
{ | |||
@@ -4,12 +4,14 @@ using System.Linq; | |||
using System.Net; | |||
using System.Net.Sockets; | |||
using System.Timers; | |||
using Shadowsocks.Controller.Strategy; | |||
using Shadowsocks.Encryption; | |||
using Shadowsocks.Encryption.AEAD; | |||
using Shadowsocks.Encryption.Exception; | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Proxy; | |||
using Shadowsocks.Util.Sockets; | |||
using static Shadowsocks.Encryption.EncryptorBase; | |||
namespace Shadowsocks.Controller | |||
{ | |||
@@ -74,7 +76,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
handlersToClose.AddRange(Handlers); | |||
} | |||
handlersToClose.ForEach(h=>h.Close()); | |||
handlersToClose.ForEach(h => h.Close()); | |||
} | |||
public void UpdateInboundCounter(Server server, long n) | |||
@@ -93,7 +95,7 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
class TCPHandler | |||
internal class TCPHandler | |||
{ | |||
class AsyncSession | |||
{ | |||
@@ -114,7 +116,7 @@ namespace Shadowsocks.Controller | |||
State = state; | |||
} | |||
public AsyncSession(AsyncSession session, T state): base(session.Remote) | |||
public AsyncSession(AsyncSession session, T state) : base(session.Remote) | |||
{ | |||
State = state; | |||
} | |||
@@ -123,46 +125,66 @@ namespace Shadowsocks.Controller | |||
private readonly int _serverTimeout; | |||
private readonly int _proxyTimeout; | |||
// Size of receive buffer. | |||
public static readonly int RecvSize = 8192; | |||
public static readonly int RecvReserveSize = IVEncryptor.ONETIMEAUTH_BYTES + IVEncryptor.AUTH_BYTES; // reserve for one-time auth | |||
public static readonly int BufferSize = RecvSize + RecvReserveSize + 32; | |||
// each recv size. | |||
public const int RecvSize = 2048; | |||
// overhead of one chunk, reserved for AEAD ciphers | |||
public const int ChunkOverheadSize = 16 * 2 /* two tags */ + AEADEncryptor.CHUNK_LEN_BYTES; | |||
// max chunk size | |||
public const uint MaxChunkSize = AEADEncryptor.CHUNK_LEN_MASK + AEADEncryptor.CHUNK_LEN_BYTES + 16 * 2; | |||
// In general, the ciphertext length, we should take overhead into account | |||
public const int BufferSize = RecvSize + (int)MaxChunkSize + 32 /* max salt len */; | |||
public DateTime lastActivity; | |||
private ShadowsocksController _controller; | |||
private Configuration _config; | |||
private TCPRelay _tcprelay; | |||
private Socket _connection; | |||
private ShadowsocksController _controller; | |||
private Configuration _config; | |||
private TCPRelay _tcprelay; | |||
private Socket _connection; | |||
private IEncryptor _encryptor; | |||
private Server _server; | |||
private IEncryptor _encryptor; | |||
private Server _server; | |||
private AsyncSession _currentRemoteSession; | |||
private bool _proxyConnected; | |||
private bool _destConnected; | |||
private bool _proxyConnected; | |||
private bool _destConnected; | |||
private byte _command; | |||
private byte[] _firstPacket; | |||
private int _firstPacketLength; | |||
private byte _command; | |||
private byte[] _firstPacket; | |||
private int _firstPacketLength; | |||
private const int CMD_CONNECT = 0x01; | |||
private const int CMD_UDP_ASSOC = 0x03; | |||
private int _totalRead = 0; | |||
private int _totalWrite = 0; | |||
private int _addrBufLength = -1; | |||
private byte[] _remoteRecvBuffer = new byte[BufferSize]; | |||
private byte[] _remoteSendBuffer = new byte[BufferSize]; | |||
private byte[] _connetionRecvBuffer = new byte[BufferSize]; | |||
private byte[] _connetionSendBuffer = new byte[BufferSize]; | |||
private int _totalRead = 0; | |||
private int _totalWrite = 0; | |||
private bool _connectionShutdown = false; | |||
private bool _remoteShutdown = false; | |||
private bool _closed = false; | |||
// remote -> local proxy (ciphertext, before decrypt) | |||
private byte[] _remoteRecvBuffer = new byte[BufferSize]; | |||
// client -> local proxy (plaintext, before encrypt) | |||
private byte[] _connetionRecvBuffer = new byte[BufferSize]; | |||
// local proxy -> remote (plaintext, after decrypt) | |||
private byte[] _remoteSendBuffer = new byte[BufferSize]; | |||
// local proxy -> client (ciphertext, before decrypt) | |||
private byte[] _connetionSendBuffer = new byte[BufferSize]; | |||
private bool _connectionShutdown = false; | |||
private bool _remoteShutdown = false; | |||
private bool _closed = false; | |||
// instance-based lock without static | |||
private readonly object _encryptionLock = new object(); | |||
private readonly object _decryptionLock = new object(); | |||
private readonly object _closeConnLock = new object(); | |||
private readonly object _encryptionLock = new object(); | |||
private readonly object _decryptionLock = new object(); | |||
private readonly object _closeConnLock = new object(); | |||
private DateTime _startConnectTime; | |||
private DateTime _startReceivingTime; | |||
@@ -184,17 +206,18 @@ namespace Shadowsocks.Controller | |||
public void CreateRemote() | |||
{ | |||
Server server = _controller.GetAServer(IStrategyCallerType.TCP, (IPEndPoint)_connection.RemoteEndPoint, _destEndPoint); | |||
Server server = _controller.GetAServer(IStrategyCallerType.TCP, (IPEndPoint)_connection.RemoteEndPoint, | |||
_destEndPoint); | |||
if (server == null || server.server == "") | |||
throw new ArgumentException("No server configured"); | |||
lock (_encryptionLock) | |||
{ | |||
lock (_decryptionLock) | |||
{ | |||
_encryptor = EncryptorFactory.GetEncryptor(server.method, server.password, server.auth, false); | |||
} | |||
} | |||
_encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); | |||
this._server = server; | |||
/* prepare address buffer length for AEAD */ | |||
Logging.Debug($"_addrBufLength={_addrBufLength}"); | |||
_encryptor.AddrBufLength = _addrBufLength; | |||
} | |||
public void Start(byte[] firstPacket, int length) | |||
@@ -269,7 +292,8 @@ namespace Shadowsocks.Controller | |||
response = new byte[] { 0, 91 }; | |||
Logging.Error("socks 5 protocol error"); | |||
} | |||
_connection.BeginSend(response, 0, response.Length, SocketFlags.None, new AsyncCallback(HandshakeSendCallback), null); | |||
_connection.BeginSend(response, 0, response.Length, SocketFlags.None, | |||
HandshakeSendCallback, null); | |||
} | |||
else | |||
Close(); | |||
@@ -296,8 +320,8 @@ namespace Shadowsocks.Controller | |||
// Skip first 3 bytes, and read 2 more bytes to analysis the address. | |||
// 2 more bytes is designed if address is domain then we don't need to read once more to get the addr length. | |||
// TODO validate | |||
_connection.BeginReceive(_connetionRecvBuffer, 0, 3 + 2, SocketFlags.None, | |||
new AsyncCallback(handshakeReceive2Callback), null); | |||
_connection.BeginReceive(_connetionRecvBuffer, 0, 3 + ADDR_ATYP_LEN + 1, SocketFlags.None, | |||
HandshakeReceive2Callback, null); | |||
} | |||
catch (Exception e) | |||
{ | |||
@@ -306,7 +330,7 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
private void handshakeReceive2Callback(IAsyncResult ar) | |||
private void HandshakeReceive2Callback(IAsyncResult ar) | |||
{ | |||
if (_closed) return; | |||
try | |||
@@ -315,20 +339,20 @@ namespace Shadowsocks.Controller | |||
if (bytesRead >= 5) | |||
{ | |||
_command = _connetionRecvBuffer[1]; | |||
if (_command != 1 && _command != 3) | |||
if (_command != CMD_CONNECT && _command != CMD_UDP_ASSOC) | |||
{ | |||
Logging.Debug("Unsupported CMD=" + _command); | |||
Close(); | |||
} | |||
else | |||
{ | |||
if (_command == 1) | |||
if (_command == CMD_CONNECT) | |||
{ | |||
byte[] response = { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 }; | |||
_connection.BeginSend(response, 0, response.Length, SocketFlags.None, | |||
new AsyncCallback(ResponseCallback), null); | |||
ResponseCallback, null); | |||
} | |||
else if (_command == 3) | |||
else if (_command == CMD_UDP_ASSOC) | |||
{ | |||
ReadAddress(HandleUDPAssociate); | |||
} | |||
@@ -336,7 +360,8 @@ namespace Shadowsocks.Controller | |||
} | |||
else | |||
{ | |||
Logging.Debug("failed to recv data in Shadowsocks.Controller.TCPHandler.handshakeReceive2Callback()"); | |||
Logging.Debug( | |||
"failed to recv data in Shadowsocks.Controller.TCPHandler.handshakeReceive2Callback()"); | |||
Close(); | |||
} | |||
} | |||
@@ -368,15 +393,15 @@ namespace Shadowsocks.Controller | |||
switch (atyp) | |||
{ | |||
case 1: // IPv4 address, 4 bytes | |||
ReadAddress(4 + 2 - 1, onSuccess); | |||
case ATYP_IPv4: // IPv4 address, 4 bytes | |||
ReadAddress(4 + ADDR_PORT_LEN - 1, onSuccess); | |||
break; | |||
case 3: // domain name, length + str | |||
case ATYP_DOMAIN: // domain name, length + str | |||
int len = _connetionRecvBuffer[4]; | |||
ReadAddress(len + 2, onSuccess); | |||
ReadAddress(len + ADDR_PORT_LEN, onSuccess); | |||
break; | |||
case 4: // IPv6 address, 16 bytes | |||
ReadAddress(16 + 2 - 1, onSuccess); | |||
case ATYP_IPv6: // IPv6 address, 16 bytes | |||
ReadAddress(16 + ADDR_PORT_LEN - 1, onSuccess); | |||
break; | |||
default: | |||
Logging.Debug("Unsupported ATYP=" + atyp); | |||
@@ -387,10 +412,12 @@ namespace Shadowsocks.Controller | |||
private void ReadAddress(int bytesRemain, Action onSuccess) | |||
{ | |||
Array.Copy(_connetionRecvBuffer, 3, _connetionRecvBuffer, 0, 2); | |||
// drop [ VER | CMD | RSV ] | |||
Array.Copy(_connetionRecvBuffer, 3, _connetionRecvBuffer, 0, ADDR_ATYP_LEN + 1); | |||
// Read the remain address bytes | |||
_connection.BeginReceive(_connetionRecvBuffer, 2, RecvSize - 2, SocketFlags.None, OnAddressFullyRead, new object[] {bytesRemain, onSuccess}); | |||
_connection.BeginReceive(_connetionRecvBuffer, 2, RecvSize - 2, SocketFlags.None, OnAddressFullyRead, | |||
new object[] { bytesRemain, onSuccess }); | |||
} | |||
private void OnAddressFullyRead(IAsyncResult ar) | |||
@@ -400,10 +427,10 @@ namespace Shadowsocks.Controller | |||
{ | |||
int bytesRead = _connection.EndReceive(ar); | |||
var states = (object[]) ar.AsyncState; | |||
var states = (object[])ar.AsyncState; | |||
int bytesRemain = (int)states[0]; | |||
var onSuccess = (Action) states[1]; | |||
var onSuccess = (Action)states[1]; | |||
if (bytesRead >= bytesRemain) | |||
{ | |||
@@ -411,35 +438,39 @@ namespace Shadowsocks.Controller | |||
int atyp = _connetionRecvBuffer[0]; | |||
string dst_addr = "Unknown"; | |||
int dst_port = -1; | |||
string dstAddr = "Unknown"; | |||
int dstPort = -1; | |||
switch (atyp) | |||
{ | |||
case 1: // IPv4 address, 4 bytes | |||
dst_addr = new IPAddress(_connetionRecvBuffer.Skip(1).Take(4).ToArray()).ToString(); | |||
dst_port = (_connetionRecvBuffer[5] << 8) + _connetionRecvBuffer[6]; | |||
case ATYP_IPv4: // IPv4 address, 4 bytes | |||
dstAddr = new IPAddress(_connetionRecvBuffer.Skip(1).Take(4).ToArray()).ToString(); | |||
dstPort = (_connetionRecvBuffer[5] << 8) + _connetionRecvBuffer[6]; | |||
_addrBufLength = ADDR_ATYP_LEN + 4 + ADDR_PORT_LEN; | |||
break; | |||
case 3: // domain name, length + str | |||
case ATYP_DOMAIN: // domain name, length + str | |||
int len = _connetionRecvBuffer[1]; | |||
dst_addr = System.Text.Encoding.UTF8.GetString(_connetionRecvBuffer, 2, len); | |||
dst_port = (_connetionRecvBuffer[len + 2] << 8) + _connetionRecvBuffer[len + 3]; | |||
dstAddr = System.Text.Encoding.UTF8.GetString(_connetionRecvBuffer, 2, len); | |||
dstPort = (_connetionRecvBuffer[len + 2] << 8) + _connetionRecvBuffer[len + 3]; | |||
_addrBufLength = ADDR_ATYP_LEN + 1 + len + ADDR_PORT_LEN; | |||
break; | |||
case 4: // IPv6 address, 16 bytes | |||
dst_addr = $"[{new IPAddress(_connetionRecvBuffer.Skip(1).Take(16).ToArray())}]"; | |||
dst_port = (_connetionRecvBuffer[17] << 8) + _connetionRecvBuffer[18]; | |||
case ATYP_IPv6: // IPv6 address, 16 bytes | |||
dstAddr = $"[{new IPAddress(_connetionRecvBuffer.Skip(1).Take(16).ToArray())}]"; | |||
dstPort = (_connetionRecvBuffer[17] << 8) + _connetionRecvBuffer[18]; | |||
_addrBufLength = ADDR_ATYP_LEN + 16 + ADDR_PORT_LEN; | |||
break; | |||
} | |||
if (_config.isVerboseLogging) | |||
{ | |||
Logging.Info($"connect to {dst_addr}:{dst_port}"); | |||
Logging.Info($"connect to {dstAddr}:{dstPort}"); | |||
} | |||
_destEndPoint = SocketUtil.GetEndPoint(dst_addr, dst_port); | |||
_destEndPoint = SocketUtil.GetEndPoint(dstAddr, dstPort); | |||
onSuccess.Invoke(); | |||
onSuccess.Invoke(); /* StartConnect() */ | |||
} | |||
else | |||
{ | |||
@@ -459,21 +490,21 @@ namespace Shadowsocks.Controller | |||
IPEndPoint endPoint = (IPEndPoint)_connection.LocalEndPoint; | |||
byte[] address = endPoint.Address.GetAddressBytes(); | |||
int port = endPoint.Port; | |||
byte[] response = new byte[4 + address.Length + 2]; | |||
byte[] response = new byte[4 + address.Length + ADDR_PORT_LEN]; | |||
response[0] = 5; | |||
switch (endPoint.AddressFamily) | |||
{ | |||
case AddressFamily.InterNetwork: | |||
response[3] = 1; | |||
response[3] = ATYP_IPv4; | |||
break; | |||
case AddressFamily.InterNetworkV6: | |||
response[3] = 4; | |||
response[3] = ATYP_IPv6; | |||
break; | |||
} | |||
address.CopyTo(response, 4); | |||
response[response.Length - 1] = (byte)(port & 0xFF); | |||
response[response.Length - 2] = (byte)((port >> 8) & 0xFF); | |||
_connection.BeginSend(response, 0, response.Length, SocketFlags.None, new AsyncCallback(ReadAll), true); | |||
_connection.BeginSend(response, 0, response.Length, SocketFlags.None, ReadAll, true); | |||
} | |||
private void ReadAll(IAsyncResult ar) | |||
@@ -484,14 +515,16 @@ namespace Shadowsocks.Controller | |||
if (ar.AsyncState != null) | |||
{ | |||
_connection.EndSend(ar); | |||
_connection.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(ReadAll), null); | |||
_connection.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, | |||
ReadAll, null); | |||
} | |||
else | |||
{ | |||
int bytesRead = _connection.EndReceive(ar); | |||
if (bytesRead > 0) | |||
{ | |||
_connection.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(ReadAll), null); | |||
_connection.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, | |||
ReadAll, null); | |||
} | |||
else | |||
Close(); | |||
@@ -522,7 +555,10 @@ namespace Shadowsocks.Controller | |||
public AsyncSession Session; | |||
public Server Server; | |||
public ServerTimer(int p) : base(p) { } | |||
public ServerTimer(int p) : base(p) | |||
{ | |||
} | |||
} | |||
private void StartConnect() | |||
@@ -533,7 +569,7 @@ namespace Shadowsocks.Controller | |||
// Setting up proxy | |||
IProxy remote; | |||
EndPoint proxyEP; | |||
EndPoint proxyEP = null; | |||
if (_config.proxy.useProxy) | |||
{ | |||
switch (_config.proxy.proxyType) | |||
@@ -552,7 +588,6 @@ namespace Shadowsocks.Controller | |||
else | |||
{ | |||
remote = new DirectConnect(); | |||
proxyEP = null; | |||
} | |||
var session = new AsyncSession(remote); | |||
@@ -567,9 +602,8 @@ namespace Shadowsocks.Controller | |||
_currentRemoteSession = session; | |||
} | |||
ProxyTimer proxyTimer = new ProxyTimer(_proxyTimeout); | |||
proxyTimer.AutoReset = false; | |||
proxyTimer.Elapsed += proxyConnectTimer_Elapsed; | |||
ProxyTimer proxyTimer = new ProxyTimer(_proxyTimeout) { AutoReset = false }; | |||
proxyTimer.Elapsed += ProxyConnectTimer_Elapsed; | |||
proxyTimer.Enabled = true; | |||
proxyTimer.Session = session; | |||
@@ -579,7 +613,8 @@ namespace Shadowsocks.Controller | |||
_proxyConnected = false; | |||
// Connect to the proxy server. | |||
remote.BeginConnectProxy(proxyEP, new AsyncCallback(ProxyConnectCallback), new AsyncSession<ProxyTimer>(remote, proxyTimer)); | |||
remote.BeginConnectProxy(proxyEP, ProxyConnectCallback, | |||
new AsyncSession<ProxyTimer>(remote, proxyTimer)); | |||
} | |||
catch (Exception e) | |||
{ | |||
@@ -588,10 +623,10 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
private void proxyConnectTimer_Elapsed(object sender, ElapsedEventArgs e) | |||
private void ProxyConnectTimer_Elapsed(object sender, ElapsedEventArgs e) | |||
{ | |||
var timer = (ProxyTimer) sender; | |||
timer.Elapsed -= proxyConnectTimer_Elapsed; | |||
var timer = (ProxyTimer)sender; | |||
timer.Elapsed -= ProxyConnectTimer_Elapsed; | |||
timer.Enabled = false; | |||
timer.Dispose(); | |||
@@ -609,18 +644,17 @@ namespace Shadowsocks.Controller | |||
private void ProxyConnectCallback(IAsyncResult ar) | |||
{ | |||
Server server = null; | |||
if (_closed) | |||
{ | |||
return; | |||
} | |||
try | |||
{ | |||
var session = (AsyncSession<ProxyTimer>) ar.AsyncState; | |||
var session = (AsyncSession<ProxyTimer>)ar.AsyncState; | |||
ProxyTimer timer = session.State; | |||
var destEndPoint = timer.DestEndPoint; | |||
server = timer.Server; | |||
timer.Elapsed -= proxyConnectTimer_Elapsed; | |||
var server = timer.Server; | |||
timer.Elapsed -= ProxyConnectTimer_Elapsed; | |||
timer.Enabled = false; | |||
timer.Dispose(); | |||
@@ -640,16 +674,16 @@ namespace Shadowsocks.Controller | |||
} | |||
_startConnectTime = DateTime.Now; | |||
ServerTimer connectTimer = new ServerTimer(_serverTimeout); | |||
connectTimer.AutoReset = false; | |||
connectTimer.Elapsed += destConnectTimer_Elapsed; | |||
ServerTimer connectTimer = new ServerTimer(_serverTimeout) { AutoReset = false }; | |||
connectTimer.Elapsed += DestConnectTimer_Elapsed; | |||
connectTimer.Enabled = true; | |||
connectTimer.Session = session; | |||
connectTimer.Server = server; | |||
_destConnected = false; | |||
// Connect to the remote endpoint. | |||
remote.BeginConnectDest(destEndPoint, new AsyncCallback(ConnectCallback), new AsyncSession<ServerTimer>(session, connectTimer)); | |||
remote.BeginConnectDest(destEndPoint, ConnectCallback, | |||
new AsyncSession<ServerTimer>(session, connectTimer)); | |||
} | |||
catch (ArgumentException) | |||
{ | |||
@@ -661,10 +695,10 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
private void destConnectTimer_Elapsed(object sender, ElapsedEventArgs e) | |||
private void DestConnectTimer_Elapsed(object sender, ElapsedEventArgs e) | |||
{ | |||
var timer = (ServerTimer)sender; | |||
timer.Elapsed -= destConnectTimer_Elapsed; | |||
timer.Elapsed -= DestConnectTimer_Elapsed; | |||
timer.Enabled = false; | |||
timer.Dispose(); | |||
@@ -687,17 +721,17 @@ namespace Shadowsocks.Controller | |||
if (_closed) return; | |||
try | |||
{ | |||
var session = (AsyncSession<ServerTimer>) ar.AsyncState; | |||
var session = (AsyncSession<ServerTimer>)ar.AsyncState; | |||
ServerTimer timer = session.State; | |||
_server = timer.Server; | |||
timer.Elapsed -= destConnectTimer_Elapsed; | |||
timer.Elapsed -= DestConnectTimer_Elapsed; | |||
timer.Enabled = false; | |||
timer.Dispose(); | |||
var remote = session.Remote; | |||
// Complete the connection. | |||
remote.EndConnectDest(ar); | |||
_destConnected = true; | |||
if (_config.isVerboseLogging) | |||
@@ -727,19 +761,11 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
// private static readonly Random Rnd = new Random(); | |||
private void TryReadAvailableData() | |||
{ | |||
int available = Math.Min(_connection.Available, RecvSize - _firstPacketLength); | |||
if (available > 0) | |||
{ | |||
// Pick a random chunk size, or is it truly necessary? Random packet size is some sort of 'characteristic' itself. | |||
//lock (Rnd) | |||
//{ | |||
// available = Rnd.Next(available + 1); | |||
//} | |||
var size = _connection.Receive(_connetionRecvBuffer, _firstPacketLength, available, | |||
SocketFlags.None); | |||
@@ -753,7 +779,8 @@ namespace Shadowsocks.Controller | |||
try | |||
{ | |||
_startReceivingTime = DateTime.Now; | |||
session.Remote.BeginReceive(_remoteRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(PipeRemoteReceiveCallback), session); | |||
session.Remote.BeginReceive(_remoteRecvBuffer, 0, RecvSize, SocketFlags.None, | |||
PipeRemoteReceiveCallback, session); | |||
TryReadAvailableData(); | |||
Logging.Debug($"_firstPacketLength = {_firstPacketLength}"); | |||
@@ -771,19 +798,38 @@ namespace Shadowsocks.Controller | |||
if (_closed) return; | |||
try | |||
{ | |||
var session = (AsyncSession) ar.AsyncState; | |||
var session = (AsyncSession)ar.AsyncState; | |||
int bytesRead = session.Remote.EndReceive(ar); | |||
_totalRead += bytesRead; | |||
_tcprelay.UpdateInboundCounter(_server, bytesRead); | |||
if (bytesRead > 0) | |||
{ | |||
lastActivity = DateTime.Now; | |||
int bytesToSend; | |||
int bytesToSend = -1; | |||
lock (_decryptionLock) | |||
{ | |||
_encryptor.Decrypt(_remoteRecvBuffer, bytesRead, _remoteSendBuffer, out bytesToSend); | |||
try | |||
{ | |||
_encryptor.Decrypt(_remoteRecvBuffer, bytesRead, _remoteSendBuffer, out bytesToSend); | |||
} | |||
catch (CryptoErrorException) | |||
{ | |||
Logging.Error("decryption error"); | |||
Close(); | |||
return; | |||
} | |||
} | |||
if (bytesToSend == 0) | |||
{ | |||
// need more to decrypt | |||
Logging.Debug("Need more to decrypt"); | |||
session.Remote.BeginReceive(_remoteRecvBuffer, 0, RecvSize, SocketFlags.None, | |||
PipeRemoteReceiveCallback, session); | |||
return; | |||
} | |||
_connection.BeginSend(_remoteSendBuffer, 0, bytesToSend, SocketFlags.None, new AsyncCallback(PipeConnectionSendCallback), session); | |||
Logging.Debug($"start sending {bytesToSend}"); | |||
_connection.BeginSend(_remoteSendBuffer, 0, bytesToSend, SocketFlags.None, | |||
PipeConnectionSendCallback, new object[] { session, bytesToSend }); | |||
IStrategy strategy = _controller.GetCurrentStrategy(); | |||
strategy?.UpdateLastRead(_server); | |||
} | |||
@@ -808,7 +854,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
int bytesRead = _connection.EndReceive(ar); | |||
var session = (AsyncSession) ar.AsyncState; | |||
var session = (AsyncSession)ar.AsyncState; | |||
var remote = session.Remote; | |||
if (bytesRead > 0) | |||
@@ -835,11 +881,21 @@ namespace Shadowsocks.Controller | |||
int bytesToSend; | |||
lock (_encryptionLock) | |||
{ | |||
_encryptor.Encrypt(_connetionRecvBuffer, length, _connetionSendBuffer, out bytesToSend); | |||
try | |||
{ | |||
_encryptor.Encrypt(_connetionRecvBuffer, length, _connetionSendBuffer, out bytesToSend); | |||
} | |||
catch (CryptoErrorException) | |||
{ | |||
Logging.Debug("encryption error"); | |||
Close(); | |||
return; | |||
} | |||
} | |||
_tcprelay.UpdateOutboundCounter(_server, bytesToSend); | |||
_startSendingTime = DateTime.Now; | |||
session.Remote.BeginSend(_connetionSendBuffer, 0, bytesToSend, SocketFlags.None, new AsyncCallback(PipeRemoteSendCallback), session); | |||
session.Remote.BeginSend(_connetionSendBuffer, 0, bytesToSend, SocketFlags.None, | |||
PipeRemoteSendCallback, new object[] { session, bytesToSend }); | |||
IStrategy strategy = _controller.GetCurrentStrategy(); | |||
strategy?.UpdateLastWrite(_server); | |||
} | |||
@@ -849,9 +905,21 @@ namespace Shadowsocks.Controller | |||
if (_closed) return; | |||
try | |||
{ | |||
var session = (AsyncSession)ar.AsyncState; | |||
session.Remote.EndSend(ar); | |||
_connection.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(PipeConnectionReceiveCallback), session); | |||
var container = (object[])ar.AsyncState; | |||
var session = (AsyncSession)container[0]; | |||
var bytesShouldSend = (int)container[1]; | |||
int bytesSent = session.Remote.EndSend(ar); | |||
int bytesRemaining = bytesShouldSend - bytesSent; | |||
if (bytesRemaining > 0) | |||
{ | |||
Logging.Info("reconstruct _connetionSendBuffer to re-send"); | |||
Buffer.BlockCopy(_connetionSendBuffer, bytesSent, _connetionSendBuffer, 0, bytesRemaining); | |||
session.Remote.BeginSend(_connetionSendBuffer, 0, bytesRemaining, SocketFlags.None, | |||
PipeRemoteSendCallback, new object[] { session, bytesRemaining }); | |||
return; | |||
} | |||
_connection.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, | |||
PipeConnectionReceiveCallback, session); | |||
} | |||
catch (Exception e) | |||
{ | |||
@@ -860,13 +928,26 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
// In general, we assume there is no delay between local proxy and client, add this for sanity | |||
private void PipeConnectionSendCallback(IAsyncResult ar) | |||
{ | |||
try | |||
{ | |||
var session = (AsyncSession)ar.AsyncState; | |||
_connection.EndSend(ar); | |||
session.Remote.BeginReceive(_remoteRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(PipeRemoteReceiveCallback), session); | |||
var container = (object[])ar.AsyncState; | |||
var session = (AsyncSession)container[0]; | |||
var bytesShouldSend = (int)container[1]; | |||
var bytesSent = _connection.EndSend(ar); | |||
var bytesRemaining = bytesShouldSend - bytesSent; | |||
if (bytesRemaining > 0) | |||
{ | |||
Logging.Info("reconstruct _remoteSendBuffer to re-send"); | |||
Buffer.BlockCopy(_remoteSendBuffer, bytesSent, _remoteSendBuffer, 0, bytesRemaining); | |||
_connection.BeginSend(_remoteSendBuffer, 0, bytesRemaining, SocketFlags.None, | |||
PipeConnectionSendCallback, new object[] { session, bytesRemaining }); | |||
return; | |||
} | |||
session.Remote.BeginReceive(_remoteRecvBuffer, 0, RecvSize, SocketFlags.None, | |||
PipeRemoteReceiveCallback, session); | |||
} | |||
catch (Exception e) | |||
{ | |||
@@ -875,4 +956,4 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -3,7 +3,6 @@ using System.Collections.Generic; | |||
using System.Net; | |||
using System.Net.Sockets; | |||
using System.Runtime.CompilerServices; | |||
using Shadowsocks.Controller.Strategy; | |||
using Shadowsocks.Encryption; | |||
using Shadowsocks.Model; | |||
@@ -13,7 +12,9 @@ namespace Shadowsocks.Controller | |||
class UDPRelay : Listener.Service | |||
{ | |||
private ShadowsocksController _controller; | |||
private LRUCache<IPEndPoint, UDPHandler> _cache; | |||
// TODO: choose a smart number | |||
private LRUCache<IPEndPoint, UDPHandler> _cache = new LRUCache<IPEndPoint, UDPHandler>(512); | |||
public long outbound = 0; | |||
public long inbound = 0; | |||
@@ -21,7 +22,6 @@ namespace Shadowsocks.Controller | |||
public UDPRelay(ShadowsocksController controller) | |||
{ | |||
this._controller = controller; | |||
this._cache = new LRUCache<IPEndPoint, UDPHandler>(512); // todo: choose a smart number | |||
} | |||
public override bool Handle(byte[] firstPacket, int length, Socket socket, object state) | |||
@@ -78,12 +78,12 @@ namespace Shadowsocks.Controller | |||
public void Send(byte[] data, int length) | |||
{ | |||
IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password, _server.auth, true); | |||
byte[] dataIn = new byte[length - 3 + IVEncryptor.ONETIMEAUTH_BYTES]; | |||
IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password); | |||
byte[] dataIn = new byte[length - 3]; | |||
Array.Copy(data, 3, dataIn, 0, length - 3); | |||
byte[] dataOut = new byte[length - 3 + 16 + IVEncryptor.ONETIMEAUTH_BYTES]; | |||
byte[] dataOut = new byte[length - 3 + 16]; | |||
int outlen; | |||
encryptor.Encrypt(dataIn, length - 3, dataOut, out outlen); | |||
encryptor.EncryptUDP(dataIn, length - 3, dataOut, out outlen); | |||
Logging.Debug(_localEndPoint, _remoteEndPoint, outlen, "UDP Relay"); | |||
_remote?.SendTo(dataOut, outlen, SocketFlags.None, _remoteEndPoint); | |||
} | |||
@@ -106,14 +106,15 @@ namespace Shadowsocks.Controller | |||
byte[] dataOut = new byte[bytesRead]; | |||
int outlen; | |||
IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password, _server.auth, true); | |||
encryptor.Decrypt(_buffer, bytesRead, dataOut, out outlen); | |||
IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password); | |||
encryptor.DecryptUDP(_buffer, bytesRead, dataOut, out outlen); | |||
byte[] sendBuf = new byte[outlen + 3]; | |||
Array.Copy(dataOut, 0, sendBuf, 3, outlen); | |||
Logging.Debug(_localEndPoint, _remoteEndPoint, outlen, "UDP Relay"); | |||
_local?.SendTo(sendBuf, outlen + 3, 0, _localEndPoint); | |||
Receive(); | |||
} | |||
catch (ObjectDisposedException) | |||
@@ -126,6 +127,8 @@ namespace Shadowsocks.Controller | |||
} | |||
finally | |||
{ | |||
// No matter success or failed, we keep receiving | |||
} | |||
} | |||
@@ -143,13 +146,12 @@ namespace Shadowsocks.Controller | |||
{ | |||
// TODO: need more think about handle other Exceptions, or should remove this catch(). | |||
} | |||
finally | |||
{ | |||
} | |||
} | |||
} | |||
} | |||
#region LRU cache | |||
// cc by-sa 3.0 http://stackoverflow.com/a/3719378/1124054 | |||
class LRUCache<K, V> where V : UDPRelay.UDPHandler | |||
{ | |||
@@ -212,4 +214,6 @@ namespace Shadowsocks.Controller | |||
public K key; | |||
public V value; | |||
} | |||
#endregion | |||
} |
@@ -0,0 +1,347 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.Net; | |||
using System.Text; | |||
using Cyotek.Collections.Generic; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Encryption.Exception; | |||
using Shadowsocks.Encryption.Stream; | |||
namespace Shadowsocks.Encryption.AEAD | |||
{ | |||
public abstract class AEADEncryptor | |||
: EncryptorBase | |||
{ | |||
// We are using the same saltLen and keyLen | |||
private const string Info = "ss-subkey"; | |||
private static readonly byte[] InfoBytes = Encoding.ASCII.GetBytes(Info); | |||
// for UDP only | |||
protected static byte[] _udpTmpBuf = new byte[MAX_INPUT_SIZE]; | |||
// every connection should create its own buffer | |||
private CircularBuffer<byte> _encCircularBuffer = new CircularBuffer<byte>(MAX_INPUT_SIZE * 2, false); | |||
private CircularBuffer<byte> _decCircularBuffer = new CircularBuffer<byte>(MAX_INPUT_SIZE * 2, false); | |||
public const int CHUNK_LEN_BYTES = 2; | |||
public const uint CHUNK_LEN_MASK = 0x3FFFu; | |||
protected Dictionary<string, EncryptorInfo> ciphers; | |||
protected string _method; | |||
protected int _cipher; | |||
// internal name in the crypto library | |||
protected string _innerLibName; | |||
protected EncryptorInfo CipherInfo; | |||
protected static byte[] _Masterkey = null; | |||
protected byte[] _sessionKey; | |||
protected int keyLen; | |||
protected int saltLen; | |||
protected int tagLen; | |||
protected int nonceLen; | |||
protected byte[] _encryptSalt; | |||
protected byte[] _decryptSalt; | |||
protected object _nonceIncrementLock = new object(); | |||
protected byte[] _encNonce; | |||
protected byte[] _decNonce; | |||
// Is first packet | |||
protected bool _decryptSaltReceived; | |||
protected bool _encryptSaltSent; | |||
// Is first chunk(tcp request) | |||
protected bool _tcpRequestSent; | |||
public AEADEncryptor(string method, string password) | |||
: base(method, password) | |||
{ | |||
InitEncryptorInfo(method); | |||
InitKey(password); | |||
// Initialize all-zero nonce for each connection | |||
_encNonce = new byte[nonceLen]; | |||
_decNonce = new byte[nonceLen]; | |||
} | |||
protected abstract Dictionary<string, EncryptorInfo> getCiphers(); | |||
protected void InitEncryptorInfo(string method) | |||
{ | |||
method = method.ToLower(); | |||
_method = method; | |||
ciphers = getCiphers(); | |||
CipherInfo = ciphers[_method]; | |||
_innerLibName = CipherInfo.InnerLibName; | |||
_cipher = CipherInfo.Type; | |||
if (_cipher == 0) { | |||
throw new System.Exception("method not found"); | |||
} | |||
keyLen = CipherInfo.KeySize; | |||
saltLen = CipherInfo.SaltSize; | |||
tagLen = CipherInfo.TagSize; | |||
nonceLen = CipherInfo.NonceSize; | |||
} | |||
protected void InitKey(string password) | |||
{ | |||
byte[] passbuf = Encoding.UTF8.GetBytes(password); | |||
// init master key | |||
if (_Masterkey == null) _Masterkey = new byte[keyLen]; | |||
if (_Masterkey.Length < keyLen) Array.Resize(ref _Masterkey, keyLen); | |||
DeriveKey(passbuf, _Masterkey); | |||
// init session key | |||
if (_sessionKey == null) _sessionKey = new byte[keyLen]; | |||
} | |||
public void DeriveKey(byte[] password, byte[] key) | |||
{ | |||
StreamEncryptor.LegacyDeriveKey(password, key); | |||
} | |||
public void DeriveSessionKey(byte[] salt, byte[] masterKey, byte[] sessionKey) | |||
{ | |||
int ret = MbedTLS.hkdf(salt, saltLen, masterKey, keyLen, InfoBytes, InfoBytes.Length, sessionKey, | |||
keyLen); | |||
if (ret != 0) throw new System.Exception("failed to generate session key"); | |||
} | |||
protected void IncrementNonce(bool isEncrypt) | |||
{ | |||
lock (_nonceIncrementLock) { | |||
Sodium.sodium_increment(isEncrypt ? _encNonce : _decNonce, nonceLen); | |||
} | |||
} | |||
public virtual void InitCipher(byte[] salt, bool isEncrypt, bool isUdp) | |||
{ | |||
if (isEncrypt) { | |||
_encryptSalt = new byte[saltLen]; | |||
Array.Copy(salt, _encryptSalt, saltLen); | |||
} else { | |||
_decryptSalt = new byte[saltLen]; | |||
Array.Copy(salt, _decryptSalt, saltLen); | |||
} | |||
Logging.Dump("Salt", salt, saltLen); | |||
} | |||
public static void randBytes(byte[] buf, int length) { RNG.GetBytes(buf, length); } | |||
public abstract int cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen); | |||
public abstract int cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen); | |||
#region TCP | |||
public override void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
Debug.Assert(_encCircularBuffer != null, "_encCircularBuffer != null"); | |||
_encCircularBuffer.Put(buf, 0, length); | |||
outlength = 0; | |||
Logging.Debug("---Start Encryption"); | |||
if (! _encryptSaltSent) { | |||
_encryptSaltSent = true; | |||
// Generate salt | |||
byte[] saltBytes = new byte[saltLen]; | |||
randBytes(saltBytes, saltLen); | |||
InitCipher(saltBytes, true, false); | |||
Array.Copy(saltBytes, 0, outbuf, 0, saltLen); | |||
outlength = saltLen; | |||
Logging.Debug($"_encryptSaltSent outlength {outlength}"); | |||
} | |||
if (! _tcpRequestSent) { | |||
_tcpRequestSent = true; | |||
// The first TCP request | |||
int encAddrBufLength; | |||
byte[] encAddrBufBytes = new byte[AddrBufLength + tagLen * 2 + CHUNK_LEN_BYTES]; | |||
byte[] addrBytes = _encCircularBuffer.Get(AddrBufLength); | |||
ChunkEncrypt(addrBytes, AddrBufLength, encAddrBufBytes, out encAddrBufLength); | |||
Debug.Assert(encAddrBufLength == AddrBufLength + tagLen * 2 + CHUNK_LEN_BYTES); | |||
Array.Copy(encAddrBufBytes, 0, outbuf, outlength, encAddrBufLength); | |||
outlength += encAddrBufLength; | |||
Logging.Debug($"_tcpRequestSent outlength {outlength}"); | |||
} | |||
// handle other chunks | |||
while (true) { | |||
uint bufSize = (uint)_encCircularBuffer.Size; | |||
if (bufSize <= 0) return; | |||
var chunklength = (int)Math.Min(bufSize, CHUNK_LEN_MASK); | |||
byte[] chunkBytes = _encCircularBuffer.Get(chunklength); | |||
int encChunkLength; | |||
byte[] encChunkBytes = new byte[chunklength + tagLen * 2 + CHUNK_LEN_BYTES]; | |||
ChunkEncrypt(chunkBytes, chunklength, encChunkBytes, out encChunkLength); | |||
Debug.Assert(encChunkLength == chunklength + tagLen * 2 + CHUNK_LEN_BYTES); | |||
Buffer.BlockCopy(encChunkBytes, 0, outbuf, outlength, encChunkLength); | |||
outlength += encChunkLength; | |||
Logging.Debug("chunks enc outlength " + outlength); | |||
// check if we have enough space for outbuf | |||
if (outlength + TCPHandler.ChunkOverheadSize > TCPHandler.BufferSize) { | |||
Logging.Debug("enc outbuf almost full, giving up"); | |||
return; | |||
} | |||
bufSize = (uint)_encCircularBuffer.Size; | |||
if (bufSize <= 0) { | |||
Logging.Debug("No more data to encrypt, leaving"); | |||
return; | |||
} | |||
} | |||
} | |||
public override void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
Debug.Assert(_decCircularBuffer != null, "_decCircularBuffer != null"); | |||
int bufSize; | |||
outlength = 0; | |||
// drop all into buffer | |||
_decCircularBuffer.Put(buf, 0, length); | |||
Logging.Debug("---Start Decryption"); | |||
if (! _decryptSaltReceived) { | |||
bufSize = _decCircularBuffer.Size; | |||
// check if we get the leading salt | |||
if (bufSize <= saltLen) { | |||
// need more | |||
return; | |||
} | |||
_decryptSaltReceived = true; | |||
byte[] salt = _decCircularBuffer.Get(saltLen); | |||
InitCipher(salt, false, false); | |||
Logging.Debug("get salt len " + saltLen); | |||
} | |||
// handle chunks | |||
while (true) { | |||
bufSize = _decCircularBuffer.Size; | |||
// check if we have any data | |||
if (bufSize <= 0) { | |||
Logging.Debug("No data in _decCircularBuffer"); | |||
return; | |||
} | |||
// first get chunk length | |||
if (bufSize <= CHUNK_LEN_BYTES + tagLen) { | |||
// so we only have chunk length and its tag? | |||
return; | |||
} | |||
#region Chunk Decryption | |||
byte[] encLenBytes = _decCircularBuffer.Peek(CHUNK_LEN_BYTES + tagLen); | |||
uint decChunkLenLength = 0; | |||
byte[] decChunkLenBytes = new byte[CHUNK_LEN_BYTES]; | |||
// try to dec chunk len | |||
cipherDecrypt(encLenBytes, CHUNK_LEN_BYTES + (uint)tagLen, decChunkLenBytes, ref decChunkLenLength); | |||
Debug.Assert(decChunkLenLength == CHUNK_LEN_BYTES); | |||
// finally we get the real chunk len | |||
ushort chunkLen = (ushort) IPAddress.NetworkToHostOrder((short)BitConverter.ToUInt16(decChunkLenBytes, 0)); | |||
if (chunkLen > CHUNK_LEN_MASK) | |||
{ | |||
// we get invalid chunk | |||
Logging.Error($"Invalid chunk length: {chunkLen}"); | |||
throw new CryptoErrorException(); | |||
} | |||
Logging.Debug("Get the real chunk len:" + chunkLen); | |||
bufSize = _decCircularBuffer.Size; | |||
if (bufSize < CHUNK_LEN_BYTES + tagLen /* we haven't remove them */+ chunkLen + tagLen) { | |||
Logging.Debug("No more data to decrypt one chunk"); | |||
return; | |||
} | |||
IncrementNonce(false); | |||
// we have enough data to decrypt one chunk | |||
// drop chunk len and its tag from buffer | |||
_decCircularBuffer.Get(CHUNK_LEN_BYTES + tagLen); | |||
byte[] encChunkBytes = _decCircularBuffer.Get(chunkLen + tagLen); | |||
byte[] decChunkBytes = new byte[chunkLen]; | |||
uint decChunkLen = 0; | |||
cipherDecrypt(encChunkBytes, chunkLen + (uint)tagLen, decChunkBytes, ref decChunkLen); | |||
Debug.Assert(decChunkLen == chunkLen); | |||
IncrementNonce(false); | |||
#endregion | |||
// output to outbuf | |||
Buffer.BlockCopy(decChunkBytes, 0, outbuf, outlength, (int) decChunkLen); | |||
outlength += (int)decChunkLen; | |||
Logging.Debug("aead dec outlength " + outlength); | |||
if (outlength + 100 > TCPHandler.BufferSize) | |||
{ | |||
Logging.Debug("dec outbuf almost full, giving up"); | |||
return; | |||
} | |||
bufSize = _decCircularBuffer.Size; | |||
// check if we already done all of them | |||
if (bufSize <= 0) { | |||
Logging.Debug("No data in _decCircularBuffer, already all done"); | |||
return; | |||
} | |||
} | |||
} | |||
#endregion | |||
#region UDP | |||
public override void EncryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
// Generate salt | |||
randBytes(outbuf, saltLen); | |||
InitCipher(outbuf, true, true); | |||
uint olen = 0; | |||
lock (_udpTmpBuf) { | |||
cipherEncrypt(buf, (uint) length, _udpTmpBuf, ref olen); | |||
Debug.Assert(olen == length + tagLen); | |||
Buffer.BlockCopy(_udpTmpBuf, 0, outbuf, saltLen, (int) olen); | |||
outlength = (int) (saltLen + olen); | |||
} | |||
} | |||
public override void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
InitCipher(buf, false, true); | |||
uint olen = 0; | |||
lock (_udpTmpBuf) { | |||
// copy remaining data to first pos | |||
Buffer.BlockCopy(buf, saltLen, buf, 0, length - saltLen); | |||
cipherDecrypt(buf, (uint) (length - saltLen), _udpTmpBuf, ref olen); | |||
Buffer.BlockCopy(_udpTmpBuf, 0, outbuf, 0, (int) olen); | |||
outlength = (int) olen; | |||
} | |||
} | |||
#endregion | |||
// we know the plaintext length before encryption, so we can do it in one operation | |||
private void ChunkEncrypt(byte[] plaintext, int plainLen, byte[] ciphertext, out int cipherLen) | |||
{ | |||
if (plainLen > CHUNK_LEN_MASK) { | |||
Logging.Error("enc chunk too big"); | |||
throw new CryptoErrorException(); | |||
} | |||
// encrypt len | |||
byte[] encLenBytes = new byte[CHUNK_LEN_BYTES + tagLen]; | |||
uint encChunkLenLength = 0; | |||
byte[] lenbuf = BitConverter.GetBytes((ushort) IPAddress.HostToNetworkOrder((short)plainLen)); | |||
cipherEncrypt(lenbuf, CHUNK_LEN_BYTES, encLenBytes, ref encChunkLenLength); | |||
Debug.Assert(encChunkLenLength == CHUNK_LEN_BYTES + tagLen); | |||
IncrementNonce(true); | |||
// encrypt corresponding data | |||
byte[] encBytes = new byte[plainLen + tagLen]; | |||
uint encBufLength = 0; | |||
cipherEncrypt(plaintext, (uint) plainLen, encBytes, ref encBufLength); | |||
Debug.Assert(encBufLength == plainLen + tagLen); | |||
IncrementNonce(true); | |||
// construct outbuf | |||
Array.Copy(encLenBytes, 0, ciphertext, 0, (int) encChunkLenLength); | |||
Buffer.BlockCopy(encBytes, 0, ciphertext, (int) encChunkLenLength, (int) encBufLength); | |||
cipherLen = (int) (encChunkLenLength + encBufLength); | |||
} | |||
} | |||
} |
@@ -0,0 +1,176 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.Runtime.InteropServices; | |||
using Shadowsocks.Encryption.Exception; | |||
namespace Shadowsocks.Encryption.AEAD | |||
{ | |||
public class AEADMbedTLSEncryptor | |||
: AEADEncryptor, IDisposable | |||
{ | |||
const int CIPHER_AES = 1; | |||
private IntPtr _encryptCtx = IntPtr.Zero; | |||
private IntPtr _decryptCtx = IntPtr.Zero; | |||
public AEADMbedTLSEncryptor(string method, string password) | |||
: base(method, password) | |||
{ | |||
} | |||
private static Dictionary<string, EncryptorInfo> _ciphers = new Dictionary<string, EncryptorInfo> | |||
{ | |||
{"aes-128-gcm", new EncryptorInfo("AES-128-GCM", 16, 16, 12, 16, CIPHER_AES)}, | |||
{"aes-192-gcm", new EncryptorInfo("AES-192-GCM", 24, 24, 12, 16, CIPHER_AES)}, | |||
{"aes-256-gcm", new EncryptorInfo("AES-256-GCM", 32, 32, 12, 16, CIPHER_AES)}, | |||
}; | |||
public static List<string> SupportedCiphers() | |||
{ | |||
return new List<string>(_ciphers.Keys); | |||
} | |||
protected override Dictionary<string, EncryptorInfo> getCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
public override void InitCipher(byte[] salt, bool isEncrypt, bool isUdp) | |||
{ | |||
base.InitCipher(salt, isEncrypt, isUdp); | |||
IntPtr ctx = Marshal.AllocHGlobal(MbedTLS.cipher_get_size_ex()); | |||
if (isEncrypt) | |||
{ | |||
_encryptCtx = ctx; | |||
} | |||
else | |||
{ | |||
_decryptCtx = ctx; | |||
} | |||
MbedTLS.cipher_init(ctx); | |||
if (MbedTLS.cipher_setup(ctx, MbedTLS.cipher_info_from_string(_innerLibName)) != 0) | |||
throw new System.Exception("Cannot initialize mbed TLS cipher context"); | |||
DeriveSessionKey(isEncrypt ? _encryptSalt : _decryptSalt, | |||
_Masterkey, _sessionKey); | |||
CipherSetKey(isEncrypt, _sessionKey); | |||
} | |||
private void CipherSetKey(bool isEncrypt, byte[] key) | |||
{ | |||
IntPtr ctx = isEncrypt ? _encryptCtx : _decryptCtx; | |||
int ret = MbedTLS.cipher_setkey(ctx, key, keyLen * 8, | |||
isEncrypt ? MbedTLS.MBEDTLS_ENCRYPT : MbedTLS.MBEDTLS_DECRYPT); | |||
if (ret != 0) throw new System.Exception("failed to set key"); | |||
ret = MbedTLS.cipher_reset(ctx); | |||
if (ret != 0) throw new System.Exception("failed to finish preparation"); | |||
} | |||
public override int cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) | |||
{ | |||
// buf: all plaintext | |||
// outbuf: ciphertext + tag | |||
int ret; | |||
byte[] tagbuf = new byte[tagLen]; | |||
uint olen = 0; | |||
switch (_cipher) | |||
{ | |||
case CIPHER_AES: | |||
ret = MbedTLS.cipher_auth_encrypt(_encryptCtx, | |||
/* nonce */ | |||
_encNonce, (uint) nonceLen, | |||
/* AD */ | |||
IntPtr.Zero, 0, | |||
/* plain */ | |||
plaintext, plen, | |||
/* cipher */ | |||
ciphertext, ref olen, | |||
tagbuf, (uint) tagLen); | |||
if (ret != 0) throw new CryptoErrorException(); | |||
Debug.Assert(olen == plen); | |||
// attach tag to ciphertext | |||
Array.Copy(tagbuf, 0, ciphertext, (int) plen, tagLen); | |||
clen = olen + (uint) tagLen; | |||
return ret; | |||
default: | |||
throw new System.Exception("not implemented"); | |||
} | |||
} | |||
public override int cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) | |||
{ | |||
// buf: ciphertext + tag | |||
// outbuf: plaintext | |||
int ret; | |||
uint olen = 0; | |||
// split tag | |||
byte[] tagbuf = new byte[tagLen]; | |||
Array.Copy(ciphertext, (int) (clen - tagLen), tagbuf, 0, tagLen); | |||
switch (_cipher) | |||
{ | |||
case CIPHER_AES: | |||
ret = MbedTLS.cipher_auth_decrypt(_decryptCtx, | |||
_decNonce, (uint) nonceLen, | |||
IntPtr.Zero, 0, | |||
ciphertext, (uint) (clen - tagLen), | |||
plaintext, ref olen, | |||
tagbuf, (uint) tagLen); | |||
if (ret != 0) throw new CryptoErrorException(); | |||
Debug.Assert(olen == clen - tagLen); | |||
plen = olen; | |||
return ret; | |||
default: | |||
throw new System.Exception("not implemented"); | |||
} | |||
} | |||
#region IDisposable | |||
private bool _disposed; | |||
// instance based lock | |||
private readonly object _lock = new object(); | |||
public override void Dispose() | |||
{ | |||
Dispose(true); | |||
GC.SuppressFinalize(this); | |||
} | |||
~AEADMbedTLSEncryptor() | |||
{ | |||
Dispose(false); | |||
} | |||
protected virtual void Dispose(bool disposing) | |||
{ | |||
lock (_lock) | |||
{ | |||
if (_disposed) return; | |||
_disposed = true; | |||
} | |||
if (disposing) | |||
{ | |||
// free managed objects | |||
} | |||
// free unmanaged objects | |||
if (_encryptCtx != IntPtr.Zero) | |||
{ | |||
MbedTLS.cipher_free(_encryptCtx); | |||
Marshal.FreeHGlobal(_encryptCtx); | |||
_encryptCtx = IntPtr.Zero; | |||
} | |||
if (_decryptCtx != IntPtr.Zero) | |||
{ | |||
MbedTLS.cipher_free(_decryptCtx); | |||
Marshal.FreeHGlobal(_decryptCtx); | |||
_decryptCtx = IntPtr.Zero; | |||
} | |||
} | |||
#endregion | |||
} | |||
} |
@@ -0,0 +1,108 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Encryption.Exception; | |||
namespace Shadowsocks.Encryption.AEAD | |||
{ | |||
public class AEADSodiumEncryptor | |||
: AEADEncryptor, IDisposable | |||
{ | |||
private const int CIPHER_CHACHA20IETFPOLY1305 = 1; | |||
private byte[] _sodiumEncSubkey; | |||
private byte[] _sodiumDecSubkey; | |||
public AEADSodiumEncryptor(string method, string password) | |||
: base(method, password) | |||
{ | |||
_sodiumEncSubkey = new byte[keyLen]; | |||
_sodiumDecSubkey = new byte[keyLen]; | |||
} | |||
private static Dictionary<string, EncryptorInfo> _ciphers = new Dictionary<string, EncryptorInfo> | |||
{ | |||
{"chacha20-ietf-poly1305", new EncryptorInfo(32, 32, 12, 16, CIPHER_CHACHA20IETFPOLY1305)}, | |||
}; | |||
public static List<string> SupportedCiphers() | |||
{ | |||
return new List<string>(_ciphers.Keys); | |||
} | |||
protected override Dictionary<string, EncryptorInfo> getCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
public override void InitCipher(byte[] salt, bool isEncrypt, bool isUdp) | |||
{ | |||
base.InitCipher(salt, isEncrypt, isUdp); | |||
DeriveSessionKey(isEncrypt ? _encryptSalt : _decryptSalt, _Masterkey, | |||
isEncrypt ? _sodiumEncSubkey : _sodiumDecSubkey); | |||
} | |||
public override int cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) | |||
{ | |||
Debug.Assert(_sodiumEncSubkey != null); | |||
// buf: all plaintext | |||
// outbuf: ciphertext + tag | |||
int ret; | |||
ulong encClen = 0; | |||
Logging.Dump("_encNonce before enc", _encNonce, nonceLen); | |||
Logging.Dump("_sodiumEncSubkey", _sodiumEncSubkey, keyLen); | |||
Logging.Dump("before cipherEncrypt: plain", plaintext, (int) plen); | |||
switch (_cipher) | |||
{ | |||
case CIPHER_CHACHA20IETFPOLY1305: | |||
ret = Sodium.crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext, ref encClen, | |||
plaintext, (ulong) plen, | |||
null, 0, | |||
null, _encNonce, | |||
_sodiumEncSubkey); | |||
break; | |||
default: | |||
throw new System.Exception("not implemented"); | |||
} | |||
if (ret != 0) throw new CryptoErrorException(); | |||
Logging.Dump("after cipherEncrypt: cipher", ciphertext, (int) encClen); | |||
clen = (uint) encClen; | |||
return ret; | |||
} | |||
public override int cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) | |||
{ | |||
Debug.Assert(_sodiumDecSubkey != null); | |||
// buf: ciphertext + tag | |||
// outbuf: plaintext | |||
int ret; | |||
ulong decPlen = 0; | |||
Logging.Dump("_decNonce before dec", _decNonce, nonceLen); | |||
Logging.Dump("_sodiumDecSubkey", _sodiumDecSubkey, keyLen); | |||
Logging.Dump("before cipherDecrypt: cipher", ciphertext, (int) clen); | |||
switch (_cipher) | |||
{ | |||
case CIPHER_CHACHA20IETFPOLY1305: | |||
ret = Sodium.crypto_aead_chacha20poly1305_ietf_decrypt(plaintext, ref decPlen, | |||
null, | |||
ciphertext, (ulong) clen, | |||
null, 0, | |||
_decNonce, _sodiumDecSubkey); | |||
break; | |||
default: | |||
throw new System.Exception("not implemented"); | |||
} | |||
if (ret != 0) throw new CryptoErrorException(); | |||
Logging.Dump("after cipherDecrypt: plain", plaintext, (int) decPlen); | |||
plen = (uint) decPlen; | |||
return ret; | |||
} | |||
public override void Dispose() | |||
{ | |||
} | |||
} | |||
} |
@@ -1,16 +1,20 @@ | |||
using System.Text; | |||
namespace Shadowsocks.Encryption | |||
namespace Shadowsocks.Encryption | |||
{ | |||
public struct EncryptorInfo | |||
public class EncryptorInfo | |||
{ | |||
public int KeySize; | |||
public int IvSize; | |||
public int SaltSize; | |||
public int TagSize; | |||
public int NonceSize; | |||
public int Type; | |||
public string InnerLibName; | |||
// For those who make use of internal crypto method name | |||
// e.g. mbed TLS | |||
#region Stream ciphers | |||
public EncryptorInfo(string innerLibName, int keySize, int ivSize, int type) | |||
{ | |||
this.KeySize = keySize; | |||
@@ -26,6 +30,32 @@ namespace Shadowsocks.Encryption | |||
this.Type = type; | |||
this.InnerLibName = string.Empty; | |||
} | |||
#endregion | |||
#region AEAD ciphers | |||
public EncryptorInfo(string innerLibName, int keySize, int saltSize, int nonceSize, int tagSize, int type) | |||
{ | |||
this.KeySize = keySize; | |||
this.SaltSize = saltSize; | |||
this.NonceSize = nonceSize; | |||
this.TagSize = tagSize; | |||
this.Type = type; | |||
this.InnerLibName = innerLibName; | |||
} | |||
public EncryptorInfo(int keySize, int saltSize, int nonceSize, int tagSize, int type) | |||
{ | |||
this.KeySize = keySize; | |||
this.SaltSize = saltSize; | |||
this.NonceSize = nonceSize; | |||
this.TagSize = tagSize; | |||
this.Type = type; | |||
this.InnerLibName = string.Empty; | |||
} | |||
#endregion | |||
} | |||
public abstract class EncryptorBase | |||
@@ -33,30 +63,33 @@ namespace Shadowsocks.Encryption | |||
{ | |||
public const int MAX_INPUT_SIZE = 32768; | |||
protected EncryptorBase(string method, string password, bool onetimeauth, bool isudp) | |||
public const int MAX_DOMAIN_LEN = 255; | |||
public const int ADDR_PORT_LEN = 2; | |||
public const int ADDR_ATYP_LEN = 1; | |||
public const int ATYP_IPv4 = 0x01; | |||
public const int ATYP_DOMAIN = 0x03; | |||
public const int ATYP_IPv6 = 0x04; | |||
protected EncryptorBase(string method, string password) | |||
{ | |||
Method = method; | |||
Password = password; | |||
OnetimeAuth = onetimeauth; | |||
IsUDP = isudp; | |||
} | |||
protected string Method; | |||
protected string Password; | |||
protected bool OnetimeAuth; | |||
protected bool IsUDP; | |||
protected byte[] GetPasswordHash() | |||
{ | |||
byte[] inputBytes = Encoding.UTF8.GetBytes(Password); | |||
byte[] hash = MbedTLS.MD5(inputBytes); | |||
return hash; | |||
} | |||
public abstract void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength); | |||
public abstract void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength); | |||
public abstract void EncryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength); | |||
public abstract void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength); | |||
public abstract void Dispose(); | |||
public int AddrBufLength { get; set; } = - 1; | |||
} | |||
} | |||
} |
@@ -1,29 +1,38 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Reflection; | |||
using Shadowsocks.Encryption.AEAD; | |||
using Shadowsocks.Encryption.Stream; | |||
namespace Shadowsocks.Encryption | |||
{ | |||
public static class EncryptorFactory | |||
{ | |||
private static Dictionary<string, Type> _registeredEncryptors; | |||
private static Dictionary<string, Type> _registeredEncryptors = new Dictionary<string, Type>(); | |||
private static Type[] _constructorTypes = new Type[] { typeof(string), typeof(string), typeof(bool), typeof(bool) }; | |||
private static readonly Type[] ConstructorTypes = {typeof(string), typeof(string)}; | |||
static EncryptorFactory() | |||
{ | |||
_registeredEncryptors = new Dictionary<string, Type>(); | |||
foreach (string method in MbedTLSEncryptor.SupportedCiphers()) | |||
foreach (string method in StreamMbedTLSEncryptor.SupportedCiphers()) | |||
{ | |||
_registeredEncryptors.Add(method, typeof(MbedTLSEncryptor)); | |||
_registeredEncryptors.Add(method, typeof(StreamMbedTLSEncryptor)); | |||
} | |||
foreach (string method in SodiumEncryptor.SupportedCiphers()) | |||
foreach (string method in StreamSodiumEncryptor.SupportedCiphers()) | |||
{ | |||
_registeredEncryptors.Add(method, typeof(SodiumEncryptor)); | |||
_registeredEncryptors.Add(method, typeof(StreamSodiumEncryptor)); | |||
} | |||
foreach (string method in AEADMbedTLSEncryptor.SupportedCiphers()) | |||
{ | |||
_registeredEncryptors.Add(method, typeof(AEADMbedTLSEncryptor)); | |||
} | |||
foreach (string method in AEADSodiumEncryptor.SupportedCiphers()) | |||
{ | |||
_registeredEncryptors.Add(method, typeof(AEADSodiumEncryptor)); | |||
} | |||
} | |||
public static IEncryptor GetEncryptor(string method, string password, bool onetimeauth, bool isudp) | |||
public static IEncryptor GetEncryptor(string method, string password) | |||
{ | |||
if (method.IsNullOrEmpty()) | |||
{ | |||
@@ -31,9 +40,10 @@ namespace Shadowsocks.Encryption | |||
} | |||
method = method.ToLowerInvariant(); | |||
Type t = _registeredEncryptors[method]; | |||
ConstructorInfo c = t.GetConstructor(_constructorTypes); | |||
IEncryptor result = (IEncryptor)c.Invoke(new object[] { method, password, onetimeauth, isudp }); | |||
ConstructorInfo c = t.GetConstructor(ConstructorTypes); | |||
if (c == null) throw new System.Exception("Invalid ctor"); | |||
IEncryptor result = (IEncryptor) c.Invoke(new object[] {method, password}); | |||
return result; | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,17 @@ | |||
namespace Shadowsocks.Encryption.Exception | |||
{ | |||
public class CryptoErrorException : System.Exception | |||
{ | |||
public CryptoErrorException() | |||
{ | |||
} | |||
public CryptoErrorException(string msg) : base(msg) | |||
{ | |||
} | |||
public CryptoErrorException(string message, System.Exception innerException) : base(message, innerException) | |||
{ | |||
} | |||
} | |||
} |
@@ -4,7 +4,11 @@ namespace Shadowsocks.Encryption | |||
{ | |||
public interface IEncryptor : IDisposable | |||
{ | |||
/* length == -1 means not used */ | |||
int AddrBufLength { set; get; } | |||
void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength); | |||
void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength); | |||
void EncryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength); | |||
void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength); | |||
} | |||
} |
@@ -1,288 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Concurrent; | |||
using System.Text; | |||
using System.Net; | |||
namespace Shadowsocks.Encryption | |||
{ | |||
public abstract class IVEncryptor | |||
: EncryptorBase | |||
{ | |||
public const int MAX_KEY_LENGTH = 64; | |||
public const int MAX_IV_LENGTH = 16; | |||
protected static byte[] tempbuf = new byte[MAX_INPUT_SIZE]; | |||
protected Dictionary<string, EncryptorInfo> ciphers; | |||
private static readonly ConcurrentDictionary<string, byte[]> CachedKeys = new ConcurrentDictionary<string, byte[]>(); | |||
protected byte[] _encryptIV; | |||
protected byte[] _decryptIV; | |||
protected bool _decryptIVReceived; | |||
protected bool _encryptIVSent; | |||
protected string _method; | |||
protected int _cipher; | |||
// internal name in the crypto library | |||
protected string _innerLibName; | |||
protected EncryptorInfo CipherInfo; | |||
protected byte[] _key; | |||
protected int keyLen; | |||
protected int ivLen; | |||
public IVEncryptor(string method, string password, bool onetimeauth, bool isudp) | |||
: base(method, password, onetimeauth, isudp) | |||
{ | |||
InitKey(method, password); | |||
} | |||
protected abstract Dictionary<string, EncryptorInfo> getCiphers(); | |||
private void InitKey(string method, string password) | |||
{ | |||
method = method.ToLower(); | |||
_method = method; | |||
string k = method + ":" + password; | |||
ciphers = getCiphers(); | |||
CipherInfo = ciphers[_method]; | |||
_innerLibName = CipherInfo.InnerLibName; | |||
_cipher = CipherInfo.Type; | |||
if (_cipher == 0) | |||
{ | |||
throw new Exception("method not found"); | |||
} | |||
keyLen = CipherInfo.KeySize; | |||
ivLen = CipherInfo.IvSize; | |||
_key = CachedKeys.GetOrAdd(k, (nk) => | |||
{ | |||
byte[] passbuf = Encoding.UTF8.GetBytes(password); | |||
byte[] key = new byte[32]; | |||
bytesToKey(passbuf, key); | |||
return key; | |||
}); | |||
} | |||
protected void bytesToKey(byte[] password, byte[] key) | |||
{ | |||
byte[] result = new byte[password.Length + 16]; | |||
int i = 0; | |||
byte[] md5sum = null; | |||
while (i < key.Length) | |||
{ | |||
if (i == 0) | |||
{ | |||
md5sum = MbedTLS.MD5(password); | |||
} | |||
else | |||
{ | |||
md5sum.CopyTo(result, 0); | |||
password.CopyTo(result, md5sum.Length); | |||
md5sum = MbedTLS.MD5(result); | |||
} | |||
md5sum.CopyTo(key, i); | |||
i += md5sum.Length; | |||
} | |||
} | |||
protected virtual void initCipher(byte[] iv, bool isCipher) | |||
{ | |||
if (ivLen > 0) | |||
{ | |||
if (isCipher) | |||
{ | |||
_encryptIV = new byte[ivLen]; | |||
Array.Copy(iv, _encryptIV, ivLen); | |||
} | |||
else | |||
{ | |||
_decryptIV = new byte[ivLen]; | |||
Array.Copy(iv, _decryptIV, ivLen); | |||
} | |||
} | |||
} | |||
protected abstract void cipherUpdate(bool isCipher, int length, byte[] buf, byte[] outbuf); | |||
#region OneTimeAuth | |||
public const int ONETIMEAUTH_FLAG = 0x10; | |||
public const int ADDRTYPE_MASK = 0xEF; | |||
public const int ONETIMEAUTH_BYTES = 10; | |||
public const int CLEN_BYTES = 2; | |||
public const int AUTH_BYTES = ONETIMEAUTH_BYTES + CLEN_BYTES; | |||
private uint _otaChunkCounter; | |||
private byte[] _otaChunkKeyBuffer; | |||
protected int OtaGetHeadLen(byte[] buf, int length) | |||
{ | |||
int len = 0; | |||
int atyp = length > 0 ? (buf[0] & ADDRTYPE_MASK) : 0; | |||
if (atyp == 1) | |||
{ | |||
len = 7; // atyp (1 bytes) + ipv4 (4 bytes) + port (2 bytes) | |||
} | |||
else if (atyp == 3 && length > 1) | |||
{ | |||
int nameLen = buf[1]; | |||
len = 4 + nameLen; // atyp (1 bytes) + name length (1 bytes) + name (n bytes) + port (2 bytes) | |||
} | |||
else if (atyp == 4) | |||
{ | |||
len = 19; // atyp (1 bytes) + ipv6 (16 bytes) + port (2 bytes) | |||
} | |||
if (len == 0 || len > length) | |||
throw new Exception($"invalid header with addr type {atyp}"); | |||
return len; | |||
} | |||
private byte[] OtaGenHash(byte[] msg, int msg_len) | |||
{ | |||
byte[] auth = new byte[ONETIMEAUTH_BYTES]; | |||
byte[] hash = new byte[20]; | |||
byte[] auth_key = new byte[MAX_IV_LENGTH + MAX_KEY_LENGTH]; | |||
Buffer.BlockCopy(_encryptIV, 0, auth_key, 0, ivLen); | |||
Buffer.BlockCopy(_key, 0, auth_key, ivLen, keyLen); | |||
Sodium.ss_sha1_hmac_ex(auth_key, (uint)(ivLen + keyLen), | |||
msg, 0, (uint)msg_len, hash); | |||
Buffer.BlockCopy(hash, 0, auth, 0, ONETIMEAUTH_BYTES); | |||
return auth; | |||
} | |||
private void OtaUpdateKeyBuffer() | |||
{ | |||
if (_otaChunkKeyBuffer == null) | |||
{ | |||
_otaChunkKeyBuffer = new byte[MAX_IV_LENGTH + 4]; | |||
Buffer.BlockCopy(_encryptIV, 0, _otaChunkKeyBuffer, 0, ivLen); | |||
} | |||
byte[] counter_bytes = BitConverter.GetBytes((uint)IPAddress.HostToNetworkOrder((int)_otaChunkCounter)); | |||
Buffer.BlockCopy(counter_bytes, 0, _otaChunkKeyBuffer, ivLen, 4); | |||
_otaChunkCounter++; | |||
} | |||
private byte[] OtaGenChunkHash(byte[] buf, int offset, int len) | |||
{ | |||
byte[] hash = new byte[20]; | |||
OtaUpdateKeyBuffer(); | |||
Sodium.ss_sha1_hmac_ex(_otaChunkKeyBuffer, (uint)_otaChunkKeyBuffer.Length, | |||
buf, offset, (uint)len, hash); | |||
return hash; | |||
} | |||
private void OtaAuthBuffer4Tcp(byte[] buf, ref int length) | |||
{ | |||
if (!_encryptIVSent) | |||
{ | |||
int headLen = OtaGetHeadLen(buf, length); | |||
int dataLen = length - headLen; | |||
buf[0] |= ONETIMEAUTH_FLAG; | |||
byte[] hash = OtaGenHash(buf, headLen); | |||
Buffer.BlockCopy(buf, headLen, buf, headLen + ONETIMEAUTH_BYTES + AUTH_BYTES, dataLen); | |||
Buffer.BlockCopy(hash, 0, buf, headLen, ONETIMEAUTH_BYTES); | |||
if (dataLen == 0) | |||
{ | |||
length = headLen + ONETIMEAUTH_BYTES; | |||
} | |||
else | |||
{ | |||
hash = OtaGenChunkHash(buf, headLen + ONETIMEAUTH_BYTES + AUTH_BYTES, dataLen); | |||
Buffer.BlockCopy(hash, 0, buf, headLen + ONETIMEAUTH_BYTES + CLEN_BYTES, ONETIMEAUTH_BYTES); | |||
byte[] lenBytes = BitConverter.GetBytes((ushort) IPAddress.HostToNetworkOrder((short) dataLen)); | |||
Buffer.BlockCopy(lenBytes, 0, buf, headLen + ONETIMEAUTH_BYTES, CLEN_BYTES); | |||
length = headLen + ONETIMEAUTH_BYTES + AUTH_BYTES + dataLen; | |||
} | |||
} | |||
else | |||
{ | |||
byte[] hash = OtaGenChunkHash(buf, 0, length); | |||
Buffer.BlockCopy(buf, 0, buf, AUTH_BYTES, length); | |||
byte[] lenBytes = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)length)); | |||
Buffer.BlockCopy(lenBytes, 0, buf, 0, CLEN_BYTES); | |||
Buffer.BlockCopy(hash, 0, buf, CLEN_BYTES, ONETIMEAUTH_BYTES); | |||
length += AUTH_BYTES; | |||
} | |||
} | |||
private void OtaAuthBuffer4Udp(byte[] buf, ref int length) | |||
{ | |||
buf[0] |= ONETIMEAUTH_FLAG; | |||
byte[] hash = OtaGenHash(buf, length); | |||
Buffer.BlockCopy(hash, 0, buf, length, ONETIMEAUTH_BYTES); | |||
length += ONETIMEAUTH_BYTES; | |||
} | |||
private void OtaAuthBuffer(byte[] buf, ref int length) | |||
{ | |||
if (OnetimeAuth && ivLen > 0) | |||
{ | |||
if (!IsUDP) | |||
{ | |||
OtaAuthBuffer4Tcp(buf, ref length); | |||
} | |||
else | |||
{ | |||
OtaAuthBuffer4Udp(buf, ref length); | |||
} | |||
} | |||
} | |||
#endregion | |||
protected static void randBytes(byte[] buf, int length) | |||
{ | |||
RNG.GetBytes(buf, length); | |||
} | |||
public override void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
if (!_encryptIVSent) | |||
{ | |||
// Generate IV | |||
randBytes(outbuf, ivLen); | |||
initCipher(outbuf, true); | |||
outlength = length + ivLen; | |||
OtaAuthBuffer(buf, ref length); | |||
_encryptIVSent = true; | |||
lock (tempbuf) | |||
{ | |||
cipherUpdate(true, length, buf, tempbuf); | |||
outlength = length + ivLen; | |||
Buffer.BlockCopy(tempbuf, 0, outbuf, ivLen, length); | |||
} | |||
} | |||
else | |||
{ | |||
OtaAuthBuffer(buf, ref length); | |||
outlength = length; | |||
cipherUpdate(true, length, buf, outbuf); | |||
} | |||
} | |||
public override void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
if (!_decryptIVReceived) | |||
{ | |||
_decryptIVReceived = true; | |||
initCipher(buf, false); | |||
outlength = length - ivLen; | |||
lock (tempbuf) | |||
{ | |||
// C# could be multi-threaded | |||
Buffer.BlockCopy(buf, ivLen, tempbuf, 0, length - ivLen); | |||
cipherUpdate(false, length - ivLen, tempbuf, outbuf); | |||
} | |||
} | |||
else | |||
{ | |||
outlength = length; | |||
cipherUpdate(false, length, buf, outbuf); | |||
} | |||
} | |||
} | |||
} |
@@ -1,76 +1,105 @@ | |||
using System; | |||
using System.IO; | |||
using System.Runtime.InteropServices; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Encryption | |||
{ | |||
public class MbedTLS | |||
{ | |||
const string DLLNAME = "libsscrypto"; | |||
public const int MBEDTLS_ENCRYPT = 1; | |||
public const int MBEDTLS_DECRYPT = 0; | |||
static MbedTLS() | |||
{ | |||
string dllPath = Utils.GetTempPath("libsscrypto.dll"); | |||
try | |||
{ | |||
FileManager.UncompressFile(dllPath, Resources.libsscrypto_dll); | |||
} | |||
catch (IOException) | |||
{ | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
} | |||
LoadLibrary(dllPath); | |||
} | |||
public static byte[] MD5(byte[] input) | |||
{ | |||
byte[] output = new byte[16]; | |||
md5(input, (uint)input.Length, output); | |||
return output; | |||
} | |||
[DllImport("Kernel32.dll")] | |||
private static extern IntPtr LoadLibrary(string path); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern IntPtr cipher_info_from_string(string cipher_name); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern void cipher_init(IntPtr ctx); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int cipher_setup(IntPtr ctx, IntPtr cipher_info); | |||
// XXX: Check operation before using it | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int cipher_setkey(IntPtr ctx, byte[] key, int key_bitlen, int operation); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int cipher_set_iv(IntPtr ctx, byte[] iv, int iv_len); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int cipher_reset(IntPtr ctx); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int cipher_update(IntPtr ctx, byte[] input, int ilen, byte[] output, ref int olen); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern void cipher_free(IntPtr ctx); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern void md5(byte[] input, uint ilen, byte[] output); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int cipher_get_size_ex(); | |||
} | |||
} | |||
using System; | |||
using System.IO; | |||
using System.Runtime.InteropServices; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Encryption | |||
{ | |||
public static class MbedTLS | |||
{ | |||
private const string DLLNAME = "libsscrypto.dll"; | |||
public const int MBEDTLS_ENCRYPT = 1; | |||
public const int MBEDTLS_DECRYPT = 0; | |||
static MbedTLS() | |||
{ | |||
string dllPath = Utils.GetTempPath(DLLNAME); | |||
try | |||
{ | |||
FileManager.UncompressFile(dllPath, Resources.libsscrypto_dll); | |||
} | |||
catch (IOException) | |||
{ | |||
} | |||
catch (System.Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
} | |||
LoadLibrary(dllPath); | |||
} | |||
public static byte[] MD5(byte[] input) | |||
{ | |||
byte[] output = new byte[16]; | |||
md5(input, (uint) input.Length, output); | |||
return output; | |||
} | |||
[DllImport("Kernel32.dll")] | |||
private static extern IntPtr LoadLibrary(string path); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern void md5(byte[] input, uint ilen, byte[] output); | |||
/// <summary> | |||
/// Get cipher ctx size for unmanaged memory allocation | |||
/// </summary> | |||
/// <returns></returns> | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int cipher_get_size_ex(); | |||
#region Cipher layer wrappers | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern IntPtr cipher_info_from_string(string cipher_name); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern void cipher_init(IntPtr ctx); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int cipher_setup(IntPtr ctx, IntPtr cipher_info); | |||
// XXX: Check operation before using it | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int cipher_setkey(IntPtr ctx, byte[] key, int key_bitlen, int operation); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int cipher_set_iv(IntPtr ctx, byte[] iv, int iv_len); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int cipher_reset(IntPtr ctx); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int cipher_update(IntPtr ctx, byte[] input, int ilen, byte[] output, ref int olen); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern void cipher_free(IntPtr ctx); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int cipher_auth_encrypt(IntPtr ctx, | |||
byte[] iv, uint iv_len, | |||
IntPtr ad, uint ad_len, | |||
byte[] input, uint ilen, | |||
byte[] output, ref uint olen, | |||
byte[] tag, uint tag_len); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int cipher_auth_decrypt(IntPtr ctx, | |||
byte[] iv, uint iv_len, | |||
IntPtr ad, uint ad_len, | |||
byte[] input, uint ilen, | |||
byte[] output, ref uint olen, | |||
byte[] tag, uint tag_len); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int hkdf(byte[] salt, | |||
int salt_len, byte[] ikm, int ikm_len, | |||
byte[] info, int info_len, byte[] okm, | |||
int okm_len); | |||
#endregion | |||
} | |||
} |
@@ -28,16 +28,17 @@ namespace Shadowsocks.Encryption | |||
public static void GetBytes(byte[] buf) | |||
{ | |||
_rng.GetBytes(buf); | |||
GetBytes(buf, buf.Length); | |||
} | |||
public static void GetBytes(byte[] buf, int len) | |||
{ | |||
if (_rng == null) Reload(); | |||
try | |||
{ | |||
_rng.GetBytes(buf, 0, len); | |||
} | |||
catch (Exception) | |||
catch (System.Exception) | |||
{ | |||
// the backup way | |||
byte[] tmp = new byte[len]; | |||
@@ -1,20 +1,22 @@ | |||
using System; | |||
using System.IO; | |||
using System.Runtime.InteropServices; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Encryption | |||
{ | |||
public class Sodium | |||
public static class Sodium | |||
{ | |||
const string DLLNAME = "libsscrypto"; | |||
private const string DLLNAME = "libsscrypto.dll"; | |||
private static bool _initialized = false; | |||
private static readonly object _initLock = new object(); | |||
static Sodium() | |||
{ | |||
string dllPath = Utils.GetTempPath("libsscrypto.dll"); | |||
string dllPath = Utils.GetTempPath(DLLNAME); | |||
try | |||
{ | |||
FileManager.UncompressFile(dllPath, Resources.libsscrypto_dll); | |||
@@ -22,29 +24,63 @@ namespace Shadowsocks.Encryption | |||
catch (IOException) | |||
{ | |||
} | |||
catch (Exception e) | |||
catch (System.Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
} | |||
LoadLibrary(dllPath); | |||
lock (_initLock) | |||
{ | |||
if (!_initialized) | |||
{ | |||
if (sodium_init() == -1) | |||
{ | |||
throw new System.Exception("Failed to initialize sodium"); | |||
} | |||
else /* 1 means already initialized; 0 means success */ | |||
{ | |||
_initialized = true; | |||
} | |||
} | |||
} | |||
} | |||
[DllImport("Kernel32.dll")] | |||
private static extern IntPtr LoadLibrary(string path); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int crypto_stream_salsa20_xor_ic(byte[] c, byte[] m, ulong mlen, byte[] n, ulong ic, byte[] k); | |||
private static extern int sodium_init(); | |||
#region AEAD | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int crypto_stream_chacha20_xor_ic(byte[] c, byte[] m, ulong mlen, byte[] n, ulong ic, byte[] k); | |||
public static extern int sodium_increment(byte[] n, int nlen); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int crypto_stream_chacha20_ietf_xor_ic(byte[] c, byte[] m, ulong mlen, byte[] n, uint ic, byte[] k); | |||
public static extern int crypto_aead_chacha20poly1305_ietf_encrypt(byte[] c, ref ulong clen_p, byte[] m, | |||
ulong mlen, byte[] ad, ulong adlen, byte[] nsec, byte[] npub, byte[] k); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern void ss_sha1_hmac_ex(byte[] key, uint keylen, | |||
byte[] input, int ioff, uint ilen, | |||
byte[] output); | |||
} | |||
} | |||
public static extern int crypto_aead_chacha20poly1305_ietf_decrypt(byte[] m, ref ulong mlen_p, | |||
byte[] nsec, byte[] c, ulong clen, byte[] ad, ulong adlen, byte[] npub, byte[] k); | |||
#endregion | |||
#region Stream | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int crypto_stream_salsa20_xor_ic(byte[] c, byte[] m, ulong mlen, byte[] n, ulong ic, | |||
byte[] k); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int crypto_stream_chacha20_xor_ic(byte[] c, byte[] m, ulong mlen, byte[] n, ulong ic, | |||
byte[] k); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public static extern int crypto_stream_chacha20_ietf_xor_ic(byte[] c, byte[] m, ulong mlen, byte[] n, uint ic, | |||
byte[] k); | |||
#endregion | |||
} | |||
} |
@@ -0,0 +1,181 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.Text; | |||
using Cyotek.Collections.Generic; | |||
using Shadowsocks.Controller; | |||
namespace Shadowsocks.Encryption.Stream | |||
{ | |||
public abstract class StreamEncryptor | |||
: EncryptorBase | |||
{ | |||
// for UDP only | |||
protected static byte[] _udpTmpBuf = new byte[MAX_INPUT_SIZE]; | |||
// every connection should create its own buffer | |||
private CircularBuffer<byte> _encCircularBuffer = new CircularBuffer<byte>(TCPHandler.BufferSize * 2, false); | |||
private CircularBuffer<byte> _decCircularBuffer = new CircularBuffer<byte>(TCPHandler.BufferSize * 2, false); | |||
protected Dictionary<string, EncryptorInfo> ciphers; | |||
protected byte[] _encryptIV; | |||
protected byte[] _decryptIV; | |||
// Is first packet | |||
protected bool _decryptIVReceived; | |||
protected bool _encryptIVSent; | |||
protected string _method; | |||
protected int _cipher; | |||
// internal name in the crypto library | |||
protected string _innerLibName; | |||
protected EncryptorInfo CipherInfo; | |||
// long-time master key | |||
protected static byte[] _key = null; | |||
protected int keyLen; | |||
protected int ivLen; | |||
public StreamEncryptor(string method, string password) | |||
: base(method, password) | |||
{ | |||
InitEncryptorInfo(method); | |||
InitKey(password); | |||
} | |||
protected abstract Dictionary<string, EncryptorInfo> getCiphers(); | |||
private void InitEncryptorInfo(string method) | |||
{ | |||
method = method.ToLower(); | |||
_method = method; | |||
ciphers = getCiphers(); | |||
CipherInfo = ciphers[_method]; | |||
_innerLibName = CipherInfo.InnerLibName; | |||
_cipher = CipherInfo.Type; | |||
if (_cipher == 0) { | |||
throw new System.Exception("method not found"); | |||
} | |||
keyLen = CipherInfo.KeySize; | |||
ivLen = CipherInfo.IvSize; | |||
} | |||
private void InitKey(string password) | |||
{ | |||
byte[] passbuf = Encoding.UTF8.GetBytes(password); | |||
if (_key == null) _key = new byte[keyLen]; | |||
if (_key.Length < keyLen) Array.Resize(ref _key, keyLen); | |||
LegacyDeriveKey(passbuf, _key); | |||
} | |||
public static void LegacyDeriveKey(byte[] password, byte[] key) | |||
{ | |||
byte[] result = new byte[password.Length + 16]; | |||
int i = 0; | |||
byte[] md5sum = null; | |||
while (i < key.Length) { | |||
if (i == 0) { | |||
md5sum = MbedTLS.MD5(password); | |||
} else { | |||
md5sum.CopyTo(result, 0); | |||
password.CopyTo(result, md5sum.Length); | |||
md5sum = MbedTLS.MD5(result); | |||
} | |||
md5sum.CopyTo(key, i); | |||
i += md5sum.Length; | |||
} | |||
} | |||
protected virtual void initCipher(byte[] iv, bool isEncrypt) | |||
{ | |||
if (isEncrypt) { | |||
_encryptIV = new byte[ivLen]; | |||
Array.Copy(iv, _encryptIV, ivLen); | |||
} else { | |||
_decryptIV = new byte[ivLen]; | |||
Array.Copy(iv, _decryptIV, ivLen); | |||
} | |||
} | |||
protected abstract void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf); | |||
protected static void randBytes(byte[] buf, int length) { RNG.GetBytes(buf, length); } | |||
#region TCP | |||
public override void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
int cipherOffset = 0; | |||
Debug.Assert(_encCircularBuffer != null, "_encCircularBuffer != null"); | |||
_encCircularBuffer.Put(buf, 0, length); | |||
if (! _encryptIVSent) { | |||
// Generate IV | |||
byte[] ivBytes = new byte[ivLen]; | |||
randBytes(ivBytes, ivLen); | |||
initCipher(ivBytes, true); | |||
Array.Copy(ivBytes, 0, outbuf, 0, ivLen); | |||
cipherOffset = ivLen; | |||
_encryptIVSent = true; | |||
} | |||
int size = _encCircularBuffer.Size; | |||
byte[] plain = _encCircularBuffer.Get(size); | |||
byte[] cipher = new byte[size]; | |||
cipherUpdate(true, size, plain, cipher); | |||
Buffer.BlockCopy(cipher, 0, outbuf, cipherOffset, size); | |||
outlength = size + cipherOffset; | |||
} | |||
public override void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
Debug.Assert(_decCircularBuffer != null, "_circularBuffer != null"); | |||
_decCircularBuffer.Put(buf, 0, length); | |||
if (! _decryptIVReceived) { | |||
if (_decCircularBuffer.Size <= ivLen) { | |||
// we need more data | |||
outlength = 0; | |||
return; | |||
} | |||
// start decryption | |||
_decryptIVReceived = true; | |||
byte[] iv = _decCircularBuffer.Get(ivLen); | |||
initCipher(iv, false); | |||
} | |||
byte[] cipher = _decCircularBuffer.ToArray(); | |||
cipherUpdate(false, cipher.Length, cipher, outbuf); | |||
_decCircularBuffer.Clear(); | |||
outlength = cipher.Length; | |||
// done the decryption | |||
} | |||
#endregion | |||
#region UDP | |||
public override void EncryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
// Generate IV | |||
randBytes(outbuf, ivLen); | |||
initCipher(outbuf, true); | |||
lock (_udpTmpBuf) { | |||
cipherUpdate(true, length, buf, _udpTmpBuf); | |||
outlength = length + ivLen; | |||
Buffer.BlockCopy(_udpTmpBuf, 0, outbuf, ivLen, length); | |||
} | |||
} | |||
public override void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
// Get IV from first pos | |||
initCipher(buf, false); | |||
outlength = length - ivLen; | |||
lock (_udpTmpBuf) { | |||
// C# could be multi-threaded | |||
Buffer.BlockCopy(buf, ivLen, _udpTmpBuf, 0, length - ivLen); | |||
cipherUpdate(false, length - ivLen, _udpTmpBuf, outbuf); | |||
} | |||
} | |||
#endregion | |||
} | |||
} |
@@ -1,11 +1,12 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Runtime.InteropServices; | |||
using Shadowsocks.Encryption.Exception; | |||
namespace Shadowsocks.Encryption | |||
namespace Shadowsocks.Encryption.Stream | |||
{ | |||
public class MbedTLSEncryptor | |||
: IVEncryptor, IDisposable | |||
public class StreamMbedTLSEncryptor | |||
: StreamEncryptor, IDisposable | |||
{ | |||
const int CIPHER_RC4 = 1; | |||
const int CIPHER_AES = 2; | |||
@@ -15,8 +16,8 @@ namespace Shadowsocks.Encryption | |||
private IntPtr _encryptCtx = IntPtr.Zero; | |||
private IntPtr _decryptCtx = IntPtr.Zero; | |||
public MbedTLSEncryptor(string method, string password, bool onetimeauth, bool isudp) | |||
: base(method, password, onetimeauth, isudp) | |||
public StreamMbedTLSEncryptor(string method, string password) | |||
: base(method, password) | |||
{ | |||
} | |||
@@ -44,11 +45,11 @@ namespace Shadowsocks.Encryption | |||
return _ciphers; | |||
} | |||
protected override void initCipher(byte[] iv, bool isCipher) | |||
protected override void initCipher(byte[] iv, bool isEncrypt) | |||
{ | |||
base.initCipher(iv, isCipher); | |||
base.initCipher(iv, isEncrypt); | |||
IntPtr ctx = Marshal.AllocHGlobal(MbedTLS.cipher_get_size_ex()); | |||
if (isCipher) | |||
if (isEncrypt) | |||
{ | |||
_encryptCtx = ctx; | |||
} | |||
@@ -71,7 +72,7 @@ namespace Shadowsocks.Encryption | |||
} | |||
MbedTLS.cipher_init(ctx); | |||
if (MbedTLS.cipher_setup( ctx, MbedTLS.cipher_info_from_string( _innerLibName ) ) != 0 ) | |||
throw new Exception("Cannot initialize mbed TLS cipher context"); | |||
throw new System.Exception("Cannot initialize mbed TLS cipher context"); | |||
/* | |||
* MbedTLS takes key length by bit | |||
* cipher_setkey() will set the correct key schedule | |||
@@ -84,24 +85,24 @@ namespace Shadowsocks.Encryption | |||
* | |||
*/ | |||
if (MbedTLS.cipher_setkey(ctx, realkey, keyLen * 8, | |||
isCipher ? MbedTLS.MBEDTLS_ENCRYPT : MbedTLS.MBEDTLS_DECRYPT) != 0 ) | |||
throw new Exception("Cannot set mbed TLS cipher key"); | |||
isEncrypt ? MbedTLS.MBEDTLS_ENCRYPT : MbedTLS.MBEDTLS_DECRYPT) != 0 ) | |||
throw new System.Exception("Cannot set mbed TLS cipher key"); | |||
if (MbedTLS.cipher_set_iv(ctx, iv, ivLen) != 0) | |||
throw new Exception("Cannot set mbed TLS cipher IV"); | |||
throw new System.Exception("Cannot set mbed TLS cipher IV"); | |||
if (MbedTLS.cipher_reset(ctx) != 0) | |||
throw new Exception("Cannot finalize mbed TLS cipher context"); | |||
throw new System.Exception("Cannot finalize mbed TLS cipher context"); | |||
} | |||
protected override void cipherUpdate(bool isCipher, int length, byte[] buf, byte[] outbuf) | |||
protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) | |||
{ | |||
// C# could be multi-threaded | |||
if (_disposed) | |||
{ | |||
throw new ObjectDisposedException(this.ToString()); | |||
} | |||
if (MbedTLS.cipher_update(isCipher ? _encryptCtx : _decryptCtx, | |||
if (MbedTLS.cipher_update(isEncrypt ? _encryptCtx : _decryptCtx, | |||
buf, length, outbuf, ref length) != 0 ) | |||
throw new Exception("Cannot update mbed TLS cipher context"); | |||
throw new CryptoErrorException(); | |||
} | |||
#region IDisposable | |||
@@ -117,7 +118,7 @@ namespace Shadowsocks.Encryption | |||
GC.SuppressFinalize(this); | |||
} | |||
~MbedTLSEncryptor() | |||
~StreamMbedTLSEncryptor() | |||
{ | |||
Dispose(false); | |||
} |
@@ -1,10 +1,11 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using Shadowsocks.Encryption.Exception; | |||
namespace Shadowsocks.Encryption | |||
namespace Shadowsocks.Encryption.Stream | |||
{ | |||
public class SodiumEncryptor | |||
: IVEncryptor, IDisposable | |||
public class StreamSodiumEncryptor | |||
: StreamEncryptor, IDisposable | |||
{ | |||
const int CIPHER_SALSA20 = 1; | |||
const int CIPHER_CHACHA20 = 2; | |||
@@ -19,8 +20,8 @@ namespace Shadowsocks.Encryption | |||
protected byte[] _encryptBuf; | |||
protected byte[] _decryptBuf; | |||
public SodiumEncryptor(string method, string password, bool onetimeauth, bool isudp) | |||
: base(method, password, onetimeauth, isudp) | |||
public StreamSodiumEncryptor(string method, string password) | |||
: base(method, password) | |||
{ | |||
_encryptBuf = new byte[MAX_INPUT_SIZE + SODIUM_BLOCK_SIZE]; | |||
_decryptBuf = new byte[MAX_INPUT_SIZE + SODIUM_BLOCK_SIZE]; | |||
@@ -42,15 +43,16 @@ namespace Shadowsocks.Encryption | |||
return new List<string>(_ciphers.Keys); | |||
} | |||
protected override void cipherUpdate(bool isCipher, int length, byte[] buf, byte[] outbuf) | |||
protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) | |||
{ | |||
// TODO write a unidirection cipher so we don't have to if if if | |||
int bytesRemaining; | |||
ulong ic; | |||
byte[] sodiumBuf; | |||
byte[] iv; | |||
int ret = -1; | |||
if (isCipher) | |||
if (isEncrypt) | |||
{ | |||
bytesRemaining = _encryptBytesRemaining; | |||
ic = _encryptIC; | |||
@@ -70,21 +72,23 @@ namespace Shadowsocks.Encryption | |||
switch (_cipher) | |||
{ | |||
case CIPHER_SALSA20: | |||
Sodium.crypto_stream_salsa20_xor_ic(sodiumBuf, sodiumBuf, (ulong)(padding + length), iv, ic, _key); | |||
ret = Sodium.crypto_stream_salsa20_xor_ic(sodiumBuf, sodiumBuf, (ulong)(padding + length), iv, ic, _key); | |||
break; | |||
case CIPHER_CHACHA20: | |||
Sodium.crypto_stream_chacha20_xor_ic(sodiumBuf, sodiumBuf, (ulong)(padding + length), iv, ic, _key); | |||
ret = Sodium.crypto_stream_chacha20_xor_ic(sodiumBuf, sodiumBuf, (ulong)(padding + length), iv, ic, _key); | |||
break; | |||
case CIPHER_CHACHA20_IETF: | |||
Sodium.crypto_stream_chacha20_ietf_xor_ic(sodiumBuf, sodiumBuf, (ulong)(padding + length), iv, (uint)ic, _key); | |||
ret = Sodium.crypto_stream_chacha20_ietf_xor_ic(sodiumBuf, sodiumBuf, (ulong)(padding + length), iv, (uint)ic, _key); | |||
break; | |||
} | |||
if (ret != 0) throw new CryptoErrorException(); | |||
Buffer.BlockCopy(sodiumBuf, padding, outbuf, 0, length); | |||
padding += length; | |||
ic += (ulong)padding / SODIUM_BLOCK_SIZE; | |||
bytesRemaining = padding % SODIUM_BLOCK_SIZE; | |||
if (isCipher) | |||
if (isEncrypt) | |||
{ | |||
_encryptBytesRemaining = bytesRemaining; | |||
_encryptIC = ic; |
@@ -2,6 +2,7 @@ | |||
<packages> | |||
<package id="Caseless.Fody" version="1.4.2" targetFramework="net462" developmentDependency="true" /> | |||
<package id="Costura.Fody" version="1.3.3.0" targetFramework="net462" developmentDependency="true" /> | |||
<package id="Cyotek.CircularBuffer" version="1.0.2" targetFramework="net462" /> | |||
<package id="Fody" version="1.29.4" targetFramework="net462" developmentDependency="true" /> | |||
<package id="GlobalHotKey" version="1.1.0" targetFramework="net462" /> | |||
<package id="Newtonsoft.Json" version="10.0.1" targetFramework="net462" /> | |||
@@ -66,6 +66,9 @@ | |||
<ApplicationManifest>app.manifest</ApplicationManifest> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<Reference Include="Cyotek.Collections.Generic.CircularBuffer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=58daa28b0b2de221, processorArchitecture=MSIL"> | |||
<HintPath>3rd\Cyotek.CircularBuffer.1.0.2\lib\net20\Cyotek.Collections.Generic.CircularBuffer.dll</HintPath> | |||
</Reference> | |||
<Reference Include="GlobalHotKey, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL"> | |||
<HintPath>3rd\GlobalHotKey.1.1.0\lib\GlobalHotKey.dll</HintPath> | |||
<Private>True</Private> | |||
@@ -94,6 +97,19 @@ | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Compile Include="Controller\System\Hotkeys\HotkeyCallbacks.cs" /> | |||
<Compile Include="Encryption\AEAD\AEADEncryptor.cs" /> | |||
<Compile Include="Encryption\AEAD\AEADMbedTLSEncryptor.cs" /> | |||
<Compile Include="Encryption\AEAD\AEADSodiumEncryptor.cs" /> | |||
<Compile Include="Encryption\EncryptorBase.cs" /> | |||
<Compile Include="Encryption\EncryptorFactory.cs" /> | |||
<Compile Include="Encryption\Exception\CryptoException.cs" /> | |||
<Compile Include="Encryption\IEncryptor.cs" /> | |||
<Compile Include="Encryption\MbedTLS.cs" /> | |||
<Compile Include="Encryption\RNG.cs" /> | |||
<Compile Include="Encryption\Sodium.cs" /> | |||
<Compile Include="Encryption\Stream\StreamEncryptor.cs" /> | |||
<Compile Include="Encryption\Stream\StreamMbedTLSEncryptor.cs" /> | |||
<Compile Include="Encryption\Stream\StreamSodiumEncryptor.cs" /> | |||
<Compile Include="Model\HotKeyConfig.cs" /> | |||
<Compile Include="Model\ProxyConfig.cs" /> | |||
<Compile Include="Properties\Settings.Designer.cs"> | |||
@@ -116,14 +132,6 @@ | |||
<Compile Include="Controller\Service\PortForwarder.cs" /> | |||
<Compile Include="Controller\Service\UDPRelay.cs" /> | |||
<Compile Include="Controller\Service\UpdateChecker.cs" /> | |||
<Compile Include="Encryption\EncryptorBase.cs" /> | |||
<Compile Include="Encryption\EncryptorFactory.cs" /> | |||
<Compile Include="Encryption\IVEncryptor.cs" /> | |||
<Compile Include="Encryption\MbedTLS.cs" /> | |||
<Compile Include="Encryption\MbedTLSEncryptor.cs" /> | |||
<Compile Include="Encryption\Sodium.cs" /> | |||
<Compile Include="Encryption\SodiumEncryptor.cs" /> | |||
<Compile Include="Encryption\IEncryptor.cs" /> | |||
<Compile Include="Controller\Service\PACServer.cs" /> | |||
<Compile Include="Model\LogViewerConfig.cs" /> | |||
<Compile Include="Model\Server.cs" /> | |||
@@ -144,7 +152,6 @@ | |||
<Compile Include="Controller\System\Hotkeys\Hotkeys.cs" /> | |||
<Compile Include="Util\ProcessManagement\Job.cs" /> | |||
<Compile Include="Util\ProcessManagement\ThreadUtil.cs" /> | |||
<Compile Include="Encryption\RNG.cs" /> | |||
<Compile Include="Util\Sockets\LineReader.cs" /> | |||
<Compile Include="Util\Sockets\SocketUtil.cs" /> | |||
<Compile Include="Util\Sockets\WrappedSocket.cs" /> | |||
@@ -7,6 +7,7 @@ using System.Windows.Input; | |||
using System.Threading; | |||
using System.Collections.Generic; | |||
using Shadowsocks.Controller.Hotkeys; | |||
using Shadowsocks.Encryption.Stream; | |||
namespace test | |||
{ | |||
@@ -70,7 +71,7 @@ namespace test | |||
{ | |||
RNG.Reload(); | |||
byte[] plain = new byte[16384]; | |||
byte[] cipher = new byte[plain.Length + 16 + IVEncryptor.ONETIMEAUTH_BYTES + IVEncryptor.AUTH_BYTES]; | |||
byte[] cipher = new byte[plain.Length + 16]; | |||
byte[] plain2 = new byte[plain.Length + 16]; | |||
int outLen = 0; | |||
int outLen2 = 0; | |||
@@ -130,8 +131,8 @@ namespace test | |||
{ | |||
IEncryptor encryptor; | |||
IEncryptor decryptor; | |||
encryptor = new MbedTLSEncryptor("aes-256-cfb", "barfoo!", false, false); | |||
decryptor = new MbedTLSEncryptor("aes-256-cfb", "barfoo!", false, false); | |||
encryptor = new StreamMbedTLSEncryptor("aes-256-cfb", "barfoo!"); | |||
decryptor = new StreamMbedTLSEncryptor("aes-256-cfb", "barfoo!"); | |||
RunEncryptionRound(encryptor, decryptor); | |||
} | |||
} | |||
@@ -171,8 +172,8 @@ namespace test | |||
var random = new Random(); | |||
IEncryptor encryptor; | |||
IEncryptor decryptor; | |||
encryptor = new MbedTLSEncryptor("rc4-md5", "barfoo!", false, false); | |||
decryptor = new MbedTLSEncryptor("rc4-md5", "barfoo!", false, false); | |||
encryptor = new StreamMbedTLSEncryptor("rc4-md5", "barfoo!"); | |||
decryptor = new StreamMbedTLSEncryptor("rc4-md5", "barfoo!"); | |||
RunEncryptionRound(encryptor, decryptor); | |||
} | |||
} | |||
@@ -212,8 +213,8 @@ namespace test | |||
var random = new Random(); | |||
IEncryptor encryptor; | |||
IEncryptor decryptor; | |||
encryptor = new SodiumEncryptor("salsa20", "barfoo!", false, false); | |||
decryptor = new SodiumEncryptor("salsa20", "barfoo!", false, false); | |||
encryptor = new StreamSodiumEncryptor("salsa20", "barfoo!"); | |||
decryptor = new StreamSodiumEncryptor("salsa20", "barfoo!"); | |||
RunEncryptionRound(encryptor, decryptor); | |||
} | |||
} | |||