@@ -4,7 +4,7 @@ using System.IO; | |||||
using System.Net.Sockets; | using System.Net.Sockets; | ||||
using System.Net; | using System.Net; | ||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.Text; | |||||
using Shadowsocks.Util; | using Shadowsocks.Util; | ||||
namespace Shadowsocks.Controller | namespace Shadowsocks.Controller | ||||
@@ -70,6 +70,18 @@ namespace Shadowsocks.Controller | |||||
WriteToLogFile("[D] " + o); | 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")] | [Conditional("DEBUG")] | ||||
public static void Debug(EndPoint local, EndPoint remote, int len, string header = null, string tailer = null) | 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; | ||||
using System.Net.Sockets; | using System.Net.Sockets; | ||||
using System.Timers; | using System.Timers; | ||||
using Shadowsocks.Controller.Strategy; | using Shadowsocks.Controller.Strategy; | ||||
using Shadowsocks.Encryption; | using Shadowsocks.Encryption; | ||||
using Shadowsocks.Encryption.AEAD; | |||||
using Shadowsocks.Encryption.Exception; | |||||
using Shadowsocks.Model; | using Shadowsocks.Model; | ||||
using Shadowsocks.Proxy; | using Shadowsocks.Proxy; | ||||
using Shadowsocks.Util.Sockets; | using Shadowsocks.Util.Sockets; | ||||
using static Shadowsocks.Encryption.EncryptorBase; | |||||
namespace Shadowsocks.Controller | namespace Shadowsocks.Controller | ||||
{ | { | ||||
@@ -74,7 +76,7 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
handlersToClose.AddRange(Handlers); | handlersToClose.AddRange(Handlers); | ||||
} | } | ||||
handlersToClose.ForEach(h=>h.Close()); | |||||
handlersToClose.ForEach(h => h.Close()); | |||||
} | } | ||||
public void UpdateInboundCounter(Server server, long n) | public void UpdateInboundCounter(Server server, long n) | ||||
@@ -93,7 +95,7 @@ namespace Shadowsocks.Controller | |||||
} | } | ||||
} | } | ||||
class TCPHandler | |||||
internal class TCPHandler | |||||
{ | { | ||||
class AsyncSession | class AsyncSession | ||||
{ | { | ||||
@@ -114,7 +116,7 @@ namespace Shadowsocks.Controller | |||||
State = state; | State = state; | ||||
} | } | ||||
public AsyncSession(AsyncSession session, T state): base(session.Remote) | |||||
public AsyncSession(AsyncSession session, T state) : base(session.Remote) | |||||
{ | { | ||||
State = state; | State = state; | ||||
} | } | ||||
@@ -123,46 +125,66 @@ namespace Shadowsocks.Controller | |||||
private readonly int _serverTimeout; | private readonly int _serverTimeout; | ||||
private readonly int _proxyTimeout; | 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; | 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 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 | // 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 _startConnectTime; | ||||
private DateTime _startReceivingTime; | private DateTime _startReceivingTime; | ||||
@@ -184,17 +206,18 @@ namespace Shadowsocks.Controller | |||||
public void CreateRemote() | 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 == "") | if (server == null || server.server == "") | ||||
throw new ArgumentException("No server configured"); | 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; | this._server = server; | ||||
/* prepare address buffer length for AEAD */ | |||||
Logging.Debug($"_addrBufLength={_addrBufLength}"); | |||||
_encryptor.AddrBufLength = _addrBufLength; | |||||
} | } | ||||
public void Start(byte[] firstPacket, int length) | public void Start(byte[] firstPacket, int length) | ||||
@@ -269,7 +292,8 @@ namespace Shadowsocks.Controller | |||||
response = new byte[] { 0, 91 }; | response = new byte[] { 0, 91 }; | ||||
Logging.Error("socks 5 protocol error"); | 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 | else | ||||
Close(); | Close(); | ||||
@@ -296,8 +320,8 @@ namespace Shadowsocks.Controller | |||||
// Skip first 3 bytes, and read 2 more bytes to analysis the address. | // 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. | // 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 | // 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) | catch (Exception e) | ||||
{ | { | ||||
@@ -306,7 +330,7 @@ namespace Shadowsocks.Controller | |||||
} | } | ||||
} | } | ||||
private void handshakeReceive2Callback(IAsyncResult ar) | |||||
private void HandshakeReceive2Callback(IAsyncResult ar) | |||||
{ | { | ||||
if (_closed) return; | if (_closed) return; | ||||
try | try | ||||
@@ -315,20 +339,20 @@ namespace Shadowsocks.Controller | |||||
if (bytesRead >= 5) | if (bytesRead >= 5) | ||||
{ | { | ||||
_command = _connetionRecvBuffer[1]; | _command = _connetionRecvBuffer[1]; | ||||
if (_command != 1 && _command != 3) | |||||
if (_command != CMD_CONNECT && _command != CMD_UDP_ASSOC) | |||||
{ | { | ||||
Logging.Debug("Unsupported CMD=" + _command); | Logging.Debug("Unsupported CMD=" + _command); | ||||
Close(); | Close(); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
if (_command == 1) | |||||
if (_command == CMD_CONNECT) | |||||
{ | { | ||||
byte[] response = { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 }; | byte[] response = { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 }; | ||||
_connection.BeginSend(response, 0, response.Length, SocketFlags.None, | _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); | ReadAddress(HandleUDPAssociate); | ||||
} | } | ||||
@@ -336,7 +360,8 @@ namespace Shadowsocks.Controller | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
Logging.Debug("failed to recv data in Shadowsocks.Controller.TCPHandler.handshakeReceive2Callback()"); | |||||
Logging.Debug( | |||||
"failed to recv data in Shadowsocks.Controller.TCPHandler.handshakeReceive2Callback()"); | |||||
Close(); | Close(); | ||||
} | } | ||||
} | } | ||||
@@ -368,15 +393,15 @@ namespace Shadowsocks.Controller | |||||
switch (atyp) | 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; | break; | ||||
case 3: // domain name, length + str | |||||
case ATYP_DOMAIN: // domain name, length + str | |||||
int len = _connetionRecvBuffer[4]; | int len = _connetionRecvBuffer[4]; | ||||
ReadAddress(len + 2, onSuccess); | |||||
ReadAddress(len + ADDR_PORT_LEN, onSuccess); | |||||
break; | 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; | break; | ||||
default: | default: | ||||
Logging.Debug("Unsupported ATYP=" + atyp); | Logging.Debug("Unsupported ATYP=" + atyp); | ||||
@@ -387,10 +412,12 @@ namespace Shadowsocks.Controller | |||||
private void ReadAddress(int bytesRemain, Action onSuccess) | 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 | // 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) | private void OnAddressFullyRead(IAsyncResult ar) | ||||
@@ -400,10 +427,10 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
int bytesRead = _connection.EndReceive(ar); | int bytesRead = _connection.EndReceive(ar); | ||||
var states = (object[]) ar.AsyncState; | |||||
var states = (object[])ar.AsyncState; | |||||
int bytesRemain = (int)states[0]; | int bytesRemain = (int)states[0]; | ||||
var onSuccess = (Action) states[1]; | |||||
var onSuccess = (Action)states[1]; | |||||
if (bytesRead >= bytesRemain) | if (bytesRead >= bytesRemain) | ||||
{ | { | ||||
@@ -411,35 +438,39 @@ namespace Shadowsocks.Controller | |||||
int atyp = _connetionRecvBuffer[0]; | int atyp = _connetionRecvBuffer[0]; | ||||
string dst_addr = "Unknown"; | |||||
int dst_port = -1; | |||||
string dstAddr = "Unknown"; | |||||
int dstPort = -1; | |||||
switch (atyp) | 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; | break; | ||||
case 3: // domain name, length + str | |||||
case ATYP_DOMAIN: // domain name, length + str | |||||
int len = _connetionRecvBuffer[1]; | 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; | 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; | break; | ||||
} | } | ||||
if (_config.isVerboseLogging) | 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 | else | ||||
{ | { | ||||
@@ -459,21 +490,21 @@ namespace Shadowsocks.Controller | |||||
IPEndPoint endPoint = (IPEndPoint)_connection.LocalEndPoint; | IPEndPoint endPoint = (IPEndPoint)_connection.LocalEndPoint; | ||||
byte[] address = endPoint.Address.GetAddressBytes(); | byte[] address = endPoint.Address.GetAddressBytes(); | ||||
int port = endPoint.Port; | 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; | response[0] = 5; | ||||
switch (endPoint.AddressFamily) | switch (endPoint.AddressFamily) | ||||
{ | { | ||||
case AddressFamily.InterNetwork: | case AddressFamily.InterNetwork: | ||||
response[3] = 1; | |||||
response[3] = ATYP_IPv4; | |||||
break; | break; | ||||
case AddressFamily.InterNetworkV6: | case AddressFamily.InterNetworkV6: | ||||
response[3] = 4; | |||||
response[3] = ATYP_IPv6; | |||||
break; | break; | ||||
} | } | ||||
address.CopyTo(response, 4); | address.CopyTo(response, 4); | ||||
response[response.Length - 1] = (byte)(port & 0xFF); | response[response.Length - 1] = (byte)(port & 0xFF); | ||||
response[response.Length - 2] = (byte)((port >> 8) & 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) | private void ReadAll(IAsyncResult ar) | ||||
@@ -484,14 +515,16 @@ namespace Shadowsocks.Controller | |||||
if (ar.AsyncState != null) | if (ar.AsyncState != null) | ||||
{ | { | ||||
_connection.EndSend(ar); | _connection.EndSend(ar); | ||||
_connection.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(ReadAll), null); | |||||
_connection.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, | |||||
ReadAll, null); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
int bytesRead = _connection.EndReceive(ar); | int bytesRead = _connection.EndReceive(ar); | ||||
if (bytesRead > 0) | if (bytesRead > 0) | ||||
{ | { | ||||
_connection.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(ReadAll), null); | |||||
_connection.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, | |||||
ReadAll, null); | |||||
} | } | ||||
else | else | ||||
Close(); | Close(); | ||||
@@ -522,7 +555,10 @@ namespace Shadowsocks.Controller | |||||
public AsyncSession Session; | public AsyncSession Session; | ||||
public Server Server; | public Server Server; | ||||
public ServerTimer(int p) : base(p) { } | |||||
public ServerTimer(int p) : base(p) | |||||
{ | |||||
} | |||||
} | } | ||||
private void StartConnect() | private void StartConnect() | ||||
@@ -533,7 +569,7 @@ namespace Shadowsocks.Controller | |||||
// Setting up proxy | // Setting up proxy | ||||
IProxy remote; | IProxy remote; | ||||
EndPoint proxyEP; | |||||
EndPoint proxyEP = null; | |||||
if (_config.proxy.useProxy) | if (_config.proxy.useProxy) | ||||
{ | { | ||||
switch (_config.proxy.proxyType) | switch (_config.proxy.proxyType) | ||||
@@ -552,7 +588,6 @@ namespace Shadowsocks.Controller | |||||
else | else | ||||
{ | { | ||||
remote = new DirectConnect(); | remote = new DirectConnect(); | ||||
proxyEP = null; | |||||
} | } | ||||
var session = new AsyncSession(remote); | var session = new AsyncSession(remote); | ||||
@@ -567,9 +602,8 @@ namespace Shadowsocks.Controller | |||||
_currentRemoteSession = session; | _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.Enabled = true; | ||||
proxyTimer.Session = session; | proxyTimer.Session = session; | ||||
@@ -579,7 +613,8 @@ namespace Shadowsocks.Controller | |||||
_proxyConnected = false; | _proxyConnected = false; | ||||
// Connect to the proxy server. | // 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) | 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.Enabled = false; | ||||
timer.Dispose(); | timer.Dispose(); | ||||
@@ -609,18 +644,17 @@ namespace Shadowsocks.Controller | |||||
private void ProxyConnectCallback(IAsyncResult ar) | private void ProxyConnectCallback(IAsyncResult ar) | ||||
{ | { | ||||
Server server = null; | |||||
if (_closed) | if (_closed) | ||||
{ | { | ||||
return; | return; | ||||
} | } | ||||
try | try | ||||
{ | { | ||||
var session = (AsyncSession<ProxyTimer>) ar.AsyncState; | |||||
var session = (AsyncSession<ProxyTimer>)ar.AsyncState; | |||||
ProxyTimer timer = session.State; | ProxyTimer timer = session.State; | ||||
var destEndPoint = timer.DestEndPoint; | var destEndPoint = timer.DestEndPoint; | ||||
server = timer.Server; | |||||
timer.Elapsed -= proxyConnectTimer_Elapsed; | |||||
var server = timer.Server; | |||||
timer.Elapsed -= ProxyConnectTimer_Elapsed; | |||||
timer.Enabled = false; | timer.Enabled = false; | ||||
timer.Dispose(); | timer.Dispose(); | ||||
@@ -640,16 +674,16 @@ namespace Shadowsocks.Controller | |||||
} | } | ||||
_startConnectTime = DateTime.Now; | _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.Enabled = true; | ||||
connectTimer.Session = session; | connectTimer.Session = session; | ||||
connectTimer.Server = server; | connectTimer.Server = server; | ||||
_destConnected = false; | _destConnected = false; | ||||
// Connect to the remote endpoint. | // 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) | 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; | var timer = (ServerTimer)sender; | ||||
timer.Elapsed -= destConnectTimer_Elapsed; | |||||
timer.Elapsed -= DestConnectTimer_Elapsed; | |||||
timer.Enabled = false; | timer.Enabled = false; | ||||
timer.Dispose(); | timer.Dispose(); | ||||
@@ -687,17 +721,17 @@ namespace Shadowsocks.Controller | |||||
if (_closed) return; | if (_closed) return; | ||||
try | try | ||||
{ | { | ||||
var session = (AsyncSession<ServerTimer>) ar.AsyncState; | |||||
var session = (AsyncSession<ServerTimer>)ar.AsyncState; | |||||
ServerTimer timer = session.State; | ServerTimer timer = session.State; | ||||
_server = timer.Server; | _server = timer.Server; | ||||
timer.Elapsed -= destConnectTimer_Elapsed; | |||||
timer.Elapsed -= DestConnectTimer_Elapsed; | |||||
timer.Enabled = false; | timer.Enabled = false; | ||||
timer.Dispose(); | timer.Dispose(); | ||||
var remote = session.Remote; | var remote = session.Remote; | ||||
// Complete the connection. | // Complete the connection. | ||||
remote.EndConnectDest(ar); | remote.EndConnectDest(ar); | ||||
_destConnected = true; | _destConnected = true; | ||||
if (_config.isVerboseLogging) | if (_config.isVerboseLogging) | ||||
@@ -727,19 +761,11 @@ namespace Shadowsocks.Controller | |||||
} | } | ||||
} | } | ||||
// private static readonly Random Rnd = new Random(); | |||||
private void TryReadAvailableData() | private void TryReadAvailableData() | ||||
{ | { | ||||
int available = Math.Min(_connection.Available, RecvSize - _firstPacketLength); | int available = Math.Min(_connection.Available, RecvSize - _firstPacketLength); | ||||
if (available > 0) | 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, | var size = _connection.Receive(_connetionRecvBuffer, _firstPacketLength, available, | ||||
SocketFlags.None); | SocketFlags.None); | ||||
@@ -753,7 +779,8 @@ namespace Shadowsocks.Controller | |||||
try | try | ||||
{ | { | ||||
_startReceivingTime = DateTime.Now; | _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(); | TryReadAvailableData(); | ||||
Logging.Debug($"_firstPacketLength = {_firstPacketLength}"); | Logging.Debug($"_firstPacketLength = {_firstPacketLength}"); | ||||
@@ -771,19 +798,38 @@ namespace Shadowsocks.Controller | |||||
if (_closed) return; | if (_closed) return; | ||||
try | try | ||||
{ | { | ||||
var session = (AsyncSession) ar.AsyncState; | |||||
var session = (AsyncSession)ar.AsyncState; | |||||
int bytesRead = session.Remote.EndReceive(ar); | int bytesRead = session.Remote.EndReceive(ar); | ||||
_totalRead += bytesRead; | _totalRead += bytesRead; | ||||
_tcprelay.UpdateInboundCounter(_server, bytesRead); | _tcprelay.UpdateInboundCounter(_server, bytesRead); | ||||
if (bytesRead > 0) | if (bytesRead > 0) | ||||
{ | { | ||||
lastActivity = DateTime.Now; | lastActivity = DateTime.Now; | ||||
int bytesToSend; | |||||
int bytesToSend = -1; | |||||
lock (_decryptionLock) | 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(); | IStrategy strategy = _controller.GetCurrentStrategy(); | ||||
strategy?.UpdateLastRead(_server); | strategy?.UpdateLastRead(_server); | ||||
} | } | ||||
@@ -808,7 +854,7 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
int bytesRead = _connection.EndReceive(ar); | int bytesRead = _connection.EndReceive(ar); | ||||
var session = (AsyncSession) ar.AsyncState; | |||||
var session = (AsyncSession)ar.AsyncState; | |||||
var remote = session.Remote; | var remote = session.Remote; | ||||
if (bytesRead > 0) | if (bytesRead > 0) | ||||
@@ -835,11 +881,21 @@ namespace Shadowsocks.Controller | |||||
int bytesToSend; | int bytesToSend; | ||||
lock (_encryptionLock) | 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); | _tcprelay.UpdateOutboundCounter(_server, bytesToSend); | ||||
_startSendingTime = DateTime.Now; | _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(); | IStrategy strategy = _controller.GetCurrentStrategy(); | ||||
strategy?.UpdateLastWrite(_server); | strategy?.UpdateLastWrite(_server); | ||||
} | } | ||||
@@ -849,9 +905,21 @@ namespace Shadowsocks.Controller | |||||
if (_closed) return; | if (_closed) return; | ||||
try | 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) | 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) | private void PipeConnectionSendCallback(IAsyncResult ar) | ||||
{ | { | ||||
try | 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) | catch (Exception e) | ||||
{ | { | ||||
@@ -875,4 +956,4 @@ namespace Shadowsocks.Controller | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} | |||||
} |
@@ -3,7 +3,6 @@ using System.Collections.Generic; | |||||
using System.Net; | using System.Net; | ||||
using System.Net.Sockets; | using System.Net.Sockets; | ||||
using System.Runtime.CompilerServices; | using System.Runtime.CompilerServices; | ||||
using Shadowsocks.Controller.Strategy; | using Shadowsocks.Controller.Strategy; | ||||
using Shadowsocks.Encryption; | using Shadowsocks.Encryption; | ||||
using Shadowsocks.Model; | using Shadowsocks.Model; | ||||
@@ -13,7 +12,9 @@ namespace Shadowsocks.Controller | |||||
class UDPRelay : Listener.Service | class UDPRelay : Listener.Service | ||||
{ | { | ||||
private ShadowsocksController _controller; | 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 outbound = 0; | ||||
public long inbound = 0; | public long inbound = 0; | ||||
@@ -21,7 +22,6 @@ namespace Shadowsocks.Controller | |||||
public UDPRelay(ShadowsocksController controller) | public UDPRelay(ShadowsocksController controller) | ||||
{ | { | ||||
this._controller = 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) | 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) | 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); | 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; | 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"); | Logging.Debug(_localEndPoint, _remoteEndPoint, outlen, "UDP Relay"); | ||||
_remote?.SendTo(dataOut, outlen, SocketFlags.None, _remoteEndPoint); | _remote?.SendTo(dataOut, outlen, SocketFlags.None, _remoteEndPoint); | ||||
} | } | ||||
@@ -106,14 +106,15 @@ namespace Shadowsocks.Controller | |||||
byte[] dataOut = new byte[bytesRead]; | byte[] dataOut = new byte[bytesRead]; | ||||
int outlen; | 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]; | byte[] sendBuf = new byte[outlen + 3]; | ||||
Array.Copy(dataOut, 0, sendBuf, 3, outlen); | Array.Copy(dataOut, 0, sendBuf, 3, outlen); | ||||
Logging.Debug(_localEndPoint, _remoteEndPoint, outlen, "UDP Relay"); | Logging.Debug(_localEndPoint, _remoteEndPoint, outlen, "UDP Relay"); | ||||
_local?.SendTo(sendBuf, outlen + 3, 0, _localEndPoint); | _local?.SendTo(sendBuf, outlen + 3, 0, _localEndPoint); | ||||
Receive(); | Receive(); | ||||
} | } | ||||
catch (ObjectDisposedException) | catch (ObjectDisposedException) | ||||
@@ -126,6 +127,8 @@ namespace Shadowsocks.Controller | |||||
} | } | ||||
finally | 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(). | // 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 | // cc by-sa 3.0 http://stackoverflow.com/a/3719378/1124054 | ||||
class LRUCache<K, V> where V : UDPRelay.UDPHandler | class LRUCache<K, V> where V : UDPRelay.UDPHandler | ||||
{ | { | ||||
@@ -212,4 +214,6 @@ namespace Shadowsocks.Controller | |||||
public K key; | public K key; | ||||
public V value; | 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 KeySize; | ||||
public int IvSize; | public int IvSize; | ||||
public int SaltSize; | |||||
public int TagSize; | |||||
public int NonceSize; | |||||
public int Type; | public int Type; | ||||
public string InnerLibName; | public string InnerLibName; | ||||
// For those who make use of internal crypto method name | // For those who make use of internal crypto method name | ||||
// e.g. mbed TLS | // e.g. mbed TLS | ||||
#region Stream ciphers | |||||
public EncryptorInfo(string innerLibName, int keySize, int ivSize, int type) | public EncryptorInfo(string innerLibName, int keySize, int ivSize, int type) | ||||
{ | { | ||||
this.KeySize = keySize; | this.KeySize = keySize; | ||||
@@ -26,6 +30,32 @@ namespace Shadowsocks.Encryption | |||||
this.Type = type; | this.Type = type; | ||||
this.InnerLibName = string.Empty; | 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 | public abstract class EncryptorBase | ||||
@@ -33,30 +63,33 @@ namespace Shadowsocks.Encryption | |||||
{ | { | ||||
public const int MAX_INPUT_SIZE = 32768; | 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; | Method = method; | ||||
Password = password; | Password = password; | ||||
OnetimeAuth = onetimeauth; | |||||
IsUDP = isudp; | |||||
} | } | ||||
protected string Method; | protected string Method; | ||||
protected string Password; | 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 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 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 abstract void Dispose(); | ||||
public int AddrBufLength { get; set; } = - 1; | |||||
} | } | ||||
} | |||||
} |
@@ -1,29 +1,38 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Reflection; | using System.Reflection; | ||||
using Shadowsocks.Encryption.AEAD; | |||||
using Shadowsocks.Encryption.Stream; | |||||
namespace Shadowsocks.Encryption | namespace Shadowsocks.Encryption | ||||
{ | { | ||||
public static class EncryptorFactory | 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() | 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()) | if (method.IsNullOrEmpty()) | ||||
{ | { | ||||
@@ -31,9 +40,10 @@ namespace Shadowsocks.Encryption | |||||
} | } | ||||
method = method.ToLowerInvariant(); | method = method.ToLowerInvariant(); | ||||
Type t = _registeredEncryptors[method]; | 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; | 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 | 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 Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength); | ||||
void Decrypt(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) | public static void GetBytes(byte[] buf) | ||||
{ | { | ||||
_rng.GetBytes(buf); | |||||
GetBytes(buf, buf.Length); | |||||
} | } | ||||
public static void GetBytes(byte[] buf, int len) | public static void GetBytes(byte[] buf, int len) | ||||
{ | { | ||||
if (_rng == null) Reload(); | |||||
try | try | ||||
{ | { | ||||
_rng.GetBytes(buf, 0, len); | _rng.GetBytes(buf, 0, len); | ||||
} | } | ||||
catch (Exception) | |||||
catch (System.Exception) | |||||
{ | { | ||||
// the backup way | // the backup way | ||||
byte[] tmp = new byte[len]; | byte[] tmp = new byte[len]; | ||||
@@ -1,20 +1,22 @@ | |||||
using System; | using System; | ||||
using System.IO; | using System.IO; | ||||
using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||
using Shadowsocks.Controller; | using Shadowsocks.Controller; | ||||
using Shadowsocks.Properties; | using Shadowsocks.Properties; | ||||
using Shadowsocks.Util; | using Shadowsocks.Util; | ||||
namespace Shadowsocks.Encryption | 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() | static Sodium() | ||||
{ | { | ||||
string dllPath = Utils.GetTempPath("libsscrypto.dll"); | |||||
string dllPath = Utils.GetTempPath(DLLNAME); | |||||
try | try | ||||
{ | { | ||||
FileManager.UncompressFile(dllPath, Resources.libsscrypto_dll); | FileManager.UncompressFile(dllPath, Resources.libsscrypto_dll); | ||||
@@ -22,29 +24,63 @@ namespace Shadowsocks.Encryption | |||||
catch (IOException) | catch (IOException) | ||||
{ | { | ||||
} | } | ||||
catch (Exception e) | |||||
catch (System.Exception e) | |||||
{ | { | ||||
Logging.LogUsefulException(e); | Logging.LogUsefulException(e); | ||||
} | } | ||||
LoadLibrary(dllPath); | 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")] | [DllImport("Kernel32.dll")] | ||||
private static extern IntPtr LoadLibrary(string path); | private static extern IntPtr LoadLibrary(string path); | ||||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | [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)] | [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)] | [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)] | [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; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Runtime.InteropServices; | 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_RC4 = 1; | ||||
const int CIPHER_AES = 2; | const int CIPHER_AES = 2; | ||||
@@ -15,8 +16,8 @@ namespace Shadowsocks.Encryption | |||||
private IntPtr _encryptCtx = IntPtr.Zero; | private IntPtr _encryptCtx = IntPtr.Zero; | ||||
private IntPtr _decryptCtx = 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; | 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()); | IntPtr ctx = Marshal.AllocHGlobal(MbedTLS.cipher_get_size_ex()); | ||||
if (isCipher) | |||||
if (isEncrypt) | |||||
{ | { | ||||
_encryptCtx = ctx; | _encryptCtx = ctx; | ||||
} | } | ||||
@@ -71,7 +72,7 @@ namespace Shadowsocks.Encryption | |||||
} | } | ||||
MbedTLS.cipher_init(ctx); | MbedTLS.cipher_init(ctx); | ||||
if (MbedTLS.cipher_setup( ctx, MbedTLS.cipher_info_from_string( _innerLibName ) ) != 0 ) | 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 | * MbedTLS takes key length by bit | ||||
* cipher_setkey() will set the correct key schedule | * cipher_setkey() will set the correct key schedule | ||||
@@ -84,24 +85,24 @@ namespace Shadowsocks.Encryption | |||||
* | * | ||||
*/ | */ | ||||
if (MbedTLS.cipher_setkey(ctx, realkey, keyLen * 8, | 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) | 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) | 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 | // C# could be multi-threaded | ||||
if (_disposed) | if (_disposed) | ||||
{ | { | ||||
throw new ObjectDisposedException(this.ToString()); | 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 ) | buf, length, outbuf, ref length) != 0 ) | ||||
throw new Exception("Cannot update mbed TLS cipher context"); | |||||
throw new CryptoErrorException(); | |||||
} | } | ||||
#region IDisposable | #region IDisposable | ||||
@@ -117,7 +118,7 @@ namespace Shadowsocks.Encryption | |||||
GC.SuppressFinalize(this); | GC.SuppressFinalize(this); | ||||
} | } | ||||
~MbedTLSEncryptor() | |||||
~StreamMbedTLSEncryptor() | |||||
{ | { | ||||
Dispose(false); | Dispose(false); | ||||
} | } |
@@ -1,10 +1,11 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | 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_SALSA20 = 1; | ||||
const int CIPHER_CHACHA20 = 2; | const int CIPHER_CHACHA20 = 2; | ||||
@@ -19,8 +20,8 @@ namespace Shadowsocks.Encryption | |||||
protected byte[] _encryptBuf; | protected byte[] _encryptBuf; | ||||
protected byte[] _decryptBuf; | 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]; | _encryptBuf = new byte[MAX_INPUT_SIZE + SODIUM_BLOCK_SIZE]; | ||||
_decryptBuf = 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); | 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 | // TODO write a unidirection cipher so we don't have to if if if | ||||
int bytesRemaining; | int bytesRemaining; | ||||
ulong ic; | ulong ic; | ||||
byte[] sodiumBuf; | byte[] sodiumBuf; | ||||
byte[] iv; | byte[] iv; | ||||
int ret = -1; | |||||
if (isCipher) | |||||
if (isEncrypt) | |||||
{ | { | ||||
bytesRemaining = _encryptBytesRemaining; | bytesRemaining = _encryptBytesRemaining; | ||||
ic = _encryptIC; | ic = _encryptIC; | ||||
@@ -70,21 +72,23 @@ namespace Shadowsocks.Encryption | |||||
switch (_cipher) | switch (_cipher) | ||||
{ | { | ||||
case CIPHER_SALSA20: | 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; | break; | ||||
case CIPHER_CHACHA20: | 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; | break; | ||||
case CIPHER_CHACHA20_IETF: | 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; | break; | ||||
} | } | ||||
if (ret != 0) throw new CryptoErrorException(); | |||||
Buffer.BlockCopy(sodiumBuf, padding, outbuf, 0, length); | Buffer.BlockCopy(sodiumBuf, padding, outbuf, 0, length); | ||||
padding += length; | padding += length; | ||||
ic += (ulong)padding / SODIUM_BLOCK_SIZE; | ic += (ulong)padding / SODIUM_BLOCK_SIZE; | ||||
bytesRemaining = padding % SODIUM_BLOCK_SIZE; | bytesRemaining = padding % SODIUM_BLOCK_SIZE; | ||||
if (isCipher) | |||||
if (isEncrypt) | |||||
{ | { | ||||
_encryptBytesRemaining = bytesRemaining; | _encryptBytesRemaining = bytesRemaining; | ||||
_encryptIC = ic; | _encryptIC = ic; |
@@ -2,6 +2,7 @@ | |||||
<packages> | <packages> | ||||
<package id="Caseless.Fody" version="1.4.2" targetFramework="net462" developmentDependency="true" /> | <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="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="Fody" version="1.29.4" targetFramework="net462" developmentDependency="true" /> | ||||
<package id="GlobalHotKey" version="1.1.0" targetFramework="net462" /> | <package id="GlobalHotKey" version="1.1.0" targetFramework="net462" /> | ||||
<package id="Newtonsoft.Json" version="10.0.1" targetFramework="net462" /> | <package id="Newtonsoft.Json" version="10.0.1" targetFramework="net462" /> | ||||
@@ -66,6 +66,9 @@ | |||||
<ApplicationManifest>app.manifest</ApplicationManifest> | <ApplicationManifest>app.manifest</ApplicationManifest> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <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"> | <Reference Include="GlobalHotKey, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL"> | ||||
<HintPath>3rd\GlobalHotKey.1.1.0\lib\GlobalHotKey.dll</HintPath> | <HintPath>3rd\GlobalHotKey.1.1.0\lib\GlobalHotKey.dll</HintPath> | ||||
<Private>True</Private> | <Private>True</Private> | ||||
@@ -94,6 +97,19 @@ | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<Compile Include="Controller\System\Hotkeys\HotkeyCallbacks.cs" /> | <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\HotKeyConfig.cs" /> | ||||
<Compile Include="Model\ProxyConfig.cs" /> | <Compile Include="Model\ProxyConfig.cs" /> | ||||
<Compile Include="Properties\Settings.Designer.cs"> | <Compile Include="Properties\Settings.Designer.cs"> | ||||
@@ -116,14 +132,6 @@ | |||||
<Compile Include="Controller\Service\PortForwarder.cs" /> | <Compile Include="Controller\Service\PortForwarder.cs" /> | ||||
<Compile Include="Controller\Service\UDPRelay.cs" /> | <Compile Include="Controller\Service\UDPRelay.cs" /> | ||||
<Compile Include="Controller\Service\UpdateChecker.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="Controller\Service\PACServer.cs" /> | ||||
<Compile Include="Model\LogViewerConfig.cs" /> | <Compile Include="Model\LogViewerConfig.cs" /> | ||||
<Compile Include="Model\Server.cs" /> | <Compile Include="Model\Server.cs" /> | ||||
@@ -144,7 +152,6 @@ | |||||
<Compile Include="Controller\System\Hotkeys\Hotkeys.cs" /> | <Compile Include="Controller\System\Hotkeys\Hotkeys.cs" /> | ||||
<Compile Include="Util\ProcessManagement\Job.cs" /> | <Compile Include="Util\ProcessManagement\Job.cs" /> | ||||
<Compile Include="Util\ProcessManagement\ThreadUtil.cs" /> | <Compile Include="Util\ProcessManagement\ThreadUtil.cs" /> | ||||
<Compile Include="Encryption\RNG.cs" /> | |||||
<Compile Include="Util\Sockets\LineReader.cs" /> | <Compile Include="Util\Sockets\LineReader.cs" /> | ||||
<Compile Include="Util\Sockets\SocketUtil.cs" /> | <Compile Include="Util\Sockets\SocketUtil.cs" /> | ||||
<Compile Include="Util\Sockets\WrappedSocket.cs" /> | <Compile Include="Util\Sockets\WrappedSocket.cs" /> | ||||
@@ -7,6 +7,7 @@ using System.Windows.Input; | |||||
using System.Threading; | using System.Threading; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using Shadowsocks.Controller.Hotkeys; | using Shadowsocks.Controller.Hotkeys; | ||||
using Shadowsocks.Encryption.Stream; | |||||
namespace test | namespace test | ||||
{ | { | ||||
@@ -70,7 +71,7 @@ namespace test | |||||
{ | { | ||||
RNG.Reload(); | RNG.Reload(); | ||||
byte[] plain = new byte[16384]; | 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]; | byte[] plain2 = new byte[plain.Length + 16]; | ||||
int outLen = 0; | int outLen = 0; | ||||
int outLen2 = 0; | int outLen2 = 0; | ||||
@@ -130,8 +131,8 @@ namespace test | |||||
{ | { | ||||
IEncryptor encryptor; | IEncryptor encryptor; | ||||
IEncryptor decryptor; | 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); | RunEncryptionRound(encryptor, decryptor); | ||||
} | } | ||||
} | } | ||||
@@ -171,8 +172,8 @@ namespace test | |||||
var random = new Random(); | var random = new Random(); | ||||
IEncryptor encryptor; | IEncryptor encryptor; | ||||
IEncryptor decryptor; | 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); | RunEncryptionRound(encryptor, decryptor); | ||||
} | } | ||||
} | } | ||||
@@ -212,8 +213,8 @@ namespace test | |||||
var random = new Random(); | var random = new Random(); | ||||
IEncryptor encryptor; | IEncryptor encryptor; | ||||
IEncryptor decryptor; | 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); | RunEncryptionRound(encryptor, decryptor); | ||||
} | } | ||||
} | } | ||||