@@ -121,14 +121,21 @@ after_build: | |||
$WorkingFolder = "$env:APPVEYOR_BUILD_FOLDER\working" | |||
$ExeFileName = "Shadowsocks-$env:APPVEYOR_BUILD_VERSION-$env:CONFIGURATION.exe" | |||
$DllFileName = "Shadowsocks.dll" | |||
$ExeFile = "$WorkingFolder\$DNVer\$ExeFileName" | |||
$DllFile = "$WorkingFolder\$DNVer\$DllFileName" | |||
$ExeHashFile = "$Exefile.hash" | |||
$DllHashFile = "$DllFile.hash" | |||
New-Item $WorkingFolder -ItemType Directory -Force | |||
Copy-Item $env:APPVEYOR_BUILD_FOLDER\shadowsocks-csharp\bin\$env:CONFIGURATION\net472\Shadowsocks.exe $WorkingFolder\Shadowsocks.exe | |||
Copy-Item $env:APPVEYOR_BUILD_FOLDER\shadowsocks-csharp\bin\$env:CONFIGURATION\netcoreapp3.1\Shadowsocks.exe $WorkingFolder\Shadowsocks.exe | |||
Copy-Item $env:APPVEYOR_BUILD_FOLDER\shadowsocks-csharp\bin\$env:CONFIGURATION\netcoreapp3.1\Shadowsocks.dll $WorkingFolder\Shadowsocks.dll | |||
Copy-Item $WorkingFolder\Shadowsocks.exe $ExeFile | |||
Copy-Item $WorkingFolder\Shadowsocks.dll $DllFile | |||
CalculateHash -file $Exefile | Out-File -FilePath $ExeHashFile | |||
CalculateHash -file $DllFile | Out-File -FilePath $DllHashFile | |||
Push-AppveyorArtifact $ExeFile | |||
Push-AppveyorArtifact $ExeHashFile | |||
@@ -137,13 +144,17 @@ after_build: | |||
if ($env:configuration -eq 'Release') | |||
{ | |||
$ReleaseFile = "$WorkingFolder\Shadowsocks.exe" | |||
$ReleaseFile2 = "$WorkingFolder\Shadowsocks.dll" | |||
$HashFile = "$ReleaseFile.hash" | |||
$HashFile2 = "$ReleaseFile2.hash" | |||
$ZipFile = "$WorkingFolder\Shadowsocks-$env:APPVEYOR_BUILD_VERSION.zip" | |||
$ZipHashFile = "$ZipFile.hash" | |||
# Calculate exe Hash and archieve both exe and hash to zip | |||
CalculateHash -file $ReleaseFile | Out-File -FilePath $hashFile | |||
7z a $ZipFile $ReleaseFile | |||
7z a $ZipFile $ReleaseFile2 | |||
7z a $ZipFile $HashFile | |||
7z a $ZipFile $HashFile2 | |||
Push-AppveyorArtifact $ZipFile | |||
# Calculate zip Hash | |||
CalculateHash -file $ZipFile | Out-File -FilePath $ZipHashFile | |||
@@ -148,7 +148,9 @@ namespace Shadowsocks.Controller | |||
private TCPRelay _tcprelay; | |||
private Socket _connection; | |||
private IEncryptor _encryptor; | |||
private IEncryptor encryptor; | |||
// workaround | |||
private IEncryptor decryptor; | |||
private Server _server; | |||
private AsyncSession _currentRemoteSession; | |||
@@ -216,13 +218,14 @@ namespace Shadowsocks.Controller | |||
if (server == null || server.server == "") | |||
throw new ArgumentException("No server configured"); | |||
_encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); | |||
encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); | |||
decryptor = EncryptorFactory.GetEncryptor(server.method, server.password); | |||
this._server = server; | |||
/* prepare address buffer length for AEAD */ | |||
Logger.Debug($"_addrBufLength={_addrBufLength}"); | |||
_encryptor.AddrBufLength = _addrBufLength; | |||
encryptor.AddressBufferLength = _addrBufLength; | |||
decryptor.AddressBufferLength = _addrBufLength; | |||
} | |||
public void Start(byte[] firstPacket, int length) | |||
@@ -272,14 +275,6 @@ namespace Shadowsocks.Controller | |||
Logger.LogUsefulException(e); | |||
} | |||
} | |||
lock (_encryptionLock) | |||
{ | |||
lock (_decryptionLock) | |||
{ | |||
_encryptor?.Dispose(); | |||
} | |||
} | |||
} | |||
private void HandshakeReceive() | |||
@@ -825,7 +820,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
try | |||
{ | |||
_encryptor.Decrypt(_remoteRecvBuffer, bytesRead, _remoteSendBuffer, out bytesToSend); | |||
decryptor.Decrypt(_remoteRecvBuffer, bytesRead, _remoteSendBuffer, out bytesToSend); | |||
} | |||
catch (CryptoErrorException) | |||
{ | |||
@@ -898,7 +893,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
try | |||
{ | |||
_encryptor.Encrypt(_connetionRecvBuffer, length, _connetionSendBuffer, out bytesToSend); | |||
encryptor.Encrypt(_connetionRecvBuffer, length, _connetionSendBuffer, out bytesToSend); | |||
} | |||
catch (CryptoErrorException) | |||
{ | |||
@@ -0,0 +1,109 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Security.Cryptography; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Shadowsocks.Encryption.AEAD | |||
{ | |||
public class AEADAesGcmNativeEncryptor : AEADEncryptor | |||
{ | |||
public AEADAesGcmNativeEncryptor(string method, string password) : base(method, password) | |||
{ | |||
} | |||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||
{ | |||
{"aes-128-gcm", new CipherInfo("aes-128-gcm", 16, 16, 12, 16, CipherFamily.AesGcm)}, | |||
{"aes-192-gcm", new CipherInfo("aes-192-gcm", 24, 24, 12, 16, CipherFamily.AesGcm)}, | |||
{"aes-256-gcm", new CipherInfo("aes-256-gcm", 32, 32, 12, 16, CipherFamily.AesGcm)}, | |||
}; | |||
protected override Dictionary<string, CipherInfo> getCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
public override void InitCipher(byte[] salt, bool isEncrypt, bool isUdp) | |||
{ | |||
base.InitCipher(salt, isEncrypt, isUdp); | |||
DeriveSessionKey(isEncrypt ? encryptSalt : decryptSalt, | |||
_Masterkey, sessionKey); | |||
} | |||
public override void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) | |||
{ | |||
using (var aes = new AesGcm(sessionKey)) | |||
{ | |||
byte[] tag = new byte[tagLen]; | |||
byte[] cipherWithoutTag = new byte[clen]; | |||
Array.Copy(ciphertext, 0, cipherWithoutTag, 0, clen - tagLen); | |||
Array.Copy(ciphertext, clen - tagLen, tag, 0, tagLen); | |||
aes.Decrypt(decNonce, ciphertext, cipherWithoutTag, tag); | |||
} | |||
/*var cipher = new GcmBlockCipher(new AesEngine()); | |||
AeadParameters parameters = new AeadParameters(new KeyParameter(_sessionKey), tagLen * 8, _decNonce); | |||
cipher.Init(false, parameters); | |||
var plaintextBC = new byte[cipher.GetOutputSize((int)clen)]; | |||
var len = cipher.ProcessBytes(ciphertext, 0, (int)clen, plaintextBC, 0); | |||
cipher.DoFinal(plaintextBC, len); | |||
plen = (uint)(plaintextBC.Length); | |||
Array.Copy(plaintextBC, 0, plaintext, 0, plen);*/ | |||
} | |||
public override void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) | |||
{ | |||
using (var aes = new AesGcm(sessionKey)) | |||
{ | |||
byte[] tag = new byte[tagLen]; | |||
byte[] cipherWithoutTag = new byte[clen]; | |||
aes.Encrypt(encNonce, plaintext, cipherWithoutTag, tag); | |||
cipherWithoutTag.CopyTo(ciphertext, 0); | |||
tag.CopyTo(ciphertext, clen); | |||
} | |||
/* | |||
var cipher = new GcmBlockCipher(new AesEngine()); | |||
AeadParameters parameters = new AeadParameters(new KeyParameter(_sessionKey), tagLen * 8, _encNonce); | |||
cipher.Init(true, parameters); | |||
var ciphertextBC = new byte[cipher.GetOutputSize((int)plen)]; | |||
var len = cipher.ProcessBytes(plaintext, 0, (int)plen, ciphertextBC, 0); | |||
cipher.DoFinal(ciphertextBC, len); | |||
clen = (uint)(ciphertextBC.Length); | |||
Array.Copy(ciphertextBC, 0, ciphertext, 0, clen);*/ | |||
} | |||
public override byte[] CipherDecrypt2(byte[] cipher) | |||
{ | |||
var (cipherMem, tagMem) = GetCipherTextAndTagMem(cipher); | |||
Span<byte> plainMem = new Span<byte>(new byte[cipherMem.Length]); | |||
using var aes = new AesGcm(sessionKey); | |||
aes.Decrypt(decNonce.AsSpan(), cipherMem.Span, tagMem.Span, plainMem); | |||
return plainMem.ToArray(); | |||
} | |||
public override byte[] CipherEncrypt2(byte[] plain) | |||
{ | |||
Span<byte> d = new Span<byte>(new byte[plain.Length + tagLen]); | |||
//Span<byte> cipherMem = new Span<byte>(new byte[plain.Length]); | |||
//Span<byte> tagMem = new Span<byte>(new byte[tagLen]); | |||
using var aes = new AesGcm(sessionKey); | |||
aes.Encrypt(encNonce.AsSpan(), plain.AsSpan(), d.Slice(0, plain.Length), d.Slice(plain.Length)); | |||
return d.ToArray(); | |||
} | |||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
} | |||
} |
@@ -9,24 +9,21 @@ using System.Threading.Tasks; | |||
namespace Shadowsocks.Encryption.AEAD | |||
{ | |||
public class AEADBouncyCastleEncryptor : AEADEncryptor, IDisposable | |||
public class AEADBouncyCastleEncryptor : AEADEncryptor | |||
{ | |||
static int CIPHER_AES = 1; // dummy | |||
public AEADBouncyCastleEncryptor(string method, string password) | |||
: base(method, password) | |||
public AEADBouncyCastleEncryptor(string method, string password) : base(method, password) | |||
{ | |||
} | |||
private static readonly Dictionary<string, EncryptorInfo> _ciphers = new Dictionary<string, EncryptorInfo> | |||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||
{ | |||
{"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)}, | |||
{"aes-128-gcm", new CipherInfo("aes-128-gcm", 16, 16, 12, 16, CipherFamily.AesGcm)}, | |||
{"aes-192-gcm", new CipherInfo("aes-192-gcm", 24, 24, 12, 16, CipherFamily.AesGcm)}, | |||
{"aes-256-gcm", new CipherInfo("aes-256-gcm", 32, 32, 12, 16, CipherFamily.AesGcm)}, | |||
}; | |||
protected override Dictionary<string, EncryptorInfo> getCiphers() | |||
protected override Dictionary<string, CipherInfo> getCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
@@ -35,27 +32,27 @@ namespace Shadowsocks.Encryption.AEAD | |||
{ | |||
base.InitCipher(salt, isEncrypt, isUdp); | |||
DeriveSessionKey(isEncrypt ? _encryptSalt : _decryptSalt, | |||
_Masterkey, _sessionKey); | |||
DeriveSessionKey(isEncrypt ? encryptSalt : decryptSalt, | |||
_Masterkey, sessionKey); | |||
} | |||
public override void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) | |||
{ | |||
var cipher = new GcmBlockCipher(new AesEngine()); | |||
AeadParameters parameters = new AeadParameters(new KeyParameter(_sessionKey), tagLen * 8, _decNonce); | |||
AeadParameters parameters = new AeadParameters(new KeyParameter(sessionKey), tagLen * 8, decNonce); | |||
cipher.Init(false, parameters); | |||
var plaintextBC = new byte[cipher.GetOutputSize((int)clen)]; | |||
var len = cipher.ProcessBytes(ciphertext, 0, (int)clen, plaintextBC, 0); | |||
cipher.DoFinal(plaintextBC, len); | |||
plen = (uint)(plaintextBC.Length); | |||
Array.Copy(plaintextBC, 0, plaintext, 0, plen); | |||
//var plaintextBC = new byte[cipher.GetOutputSize(ciphertext.Length)]; | |||
var len = cipher.ProcessBytes(ciphertext, 0, ciphertext.Length, plaintext, 0); | |||
cipher.DoFinal(plaintext, len); | |||
//plen = (uint)(plaintext.Length); | |||
//Array.Copy(plaintextBC, 0, plaintext, 0, plaintext.Length); | |||
} | |||
public override void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) | |||
{ | |||
var cipher = new GcmBlockCipher(new AesEngine()); | |||
AeadParameters parameters = new AeadParameters(new KeyParameter(_sessionKey), tagLen * 8, _encNonce); | |||
AeadParameters parameters = new AeadParameters(new KeyParameter(sessionKey), tagLen * 8, encNonce); | |||
cipher.Init(true, parameters); | |||
var ciphertextBC = new byte[cipher.GetOutputSize((int)plen)]; | |||
@@ -65,13 +62,35 @@ namespace Shadowsocks.Encryption.AEAD | |||
Array.Copy(ciphertextBC, 0, ciphertext, 0, clen); | |||
} | |||
public static List<string> SupportedCiphers() | |||
public override byte[] CipherDecrypt2(byte[] cipher) | |||
{ | |||
return new List<string>(_ciphers.Keys); | |||
var aes = new GcmBlockCipher(new AesEngine()); | |||
AeadParameters parameters = new AeadParameters(new KeyParameter(sessionKey), tagLen * 8, decNonce); | |||
aes.Init(false, parameters); | |||
byte[] plain = new byte[aes.GetOutputSize(cipher.Length)]; | |||
var len = aes.ProcessBytes(cipher, 0, cipher.Length, plain, 0); | |||
aes.DoFinal(plain, len); | |||
return plain; | |||
} | |||
public override void Dispose() | |||
public override byte[] CipherEncrypt2(byte[] plain) | |||
{ | |||
var aes = new GcmBlockCipher(new AesEngine()); | |||
AeadParameters parameters = new AeadParameters(new KeyParameter(sessionKey), tagLen * 8, encNonce); | |||
aes.Init(true, parameters); | |||
var cipher = new byte[aes.GetOutputSize(plain.Length)]; | |||
var len = aes.ProcessBytes(plain, 0, plain.Length, cipher, 0); | |||
aes.DoFinal(cipher, len); | |||
return cipher; | |||
} | |||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
} | |||
} |
@@ -11,8 +11,7 @@ using Shadowsocks.Encryption.Stream; | |||
namespace Shadowsocks.Encryption.AEAD | |||
{ | |||
public abstract class AEADEncryptor | |||
: EncryptorBase | |||
public abstract class AEADEncryptor : EncryptorBase | |||
{ | |||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||
// We are using the same saltLen and keyLen | |||
@@ -29,26 +28,27 @@ namespace Shadowsocks.Encryption.AEAD | |||
public const int CHUNK_LEN_BYTES = 2; | |||
public const uint CHUNK_LEN_MASK = 0x3FFFu; | |||
protected Dictionary<string, EncryptorInfo> ciphers; | |||
protected Dictionary<string, CipherInfo> ciphers; | |||
protected string _method; | |||
protected int _cipher; | |||
protected CipherFamily _cipher; | |||
// internal name in the crypto library | |||
protected string _innerLibName; | |||
protected EncryptorInfo CipherInfo; | |||
protected CipherInfo CipherInfo; | |||
protected static byte[] _Masterkey = null; | |||
protected byte[] _sessionKey; | |||
protected byte[] sessionKey; | |||
protected int keyLen; | |||
protected int saltLen; | |||
protected int tagLen; | |||
protected int nonceLen; | |||
protected byte[] _encryptSalt; | |||
protected byte[] _decryptSalt; | |||
protected byte[] encryptSalt; | |||
protected byte[] decryptSalt; | |||
protected object _nonceIncrementLock = new object(); | |||
protected byte[] _encNonce; | |||
protected byte[] _decNonce; | |||
protected byte[] encNonce; | |||
protected byte[] decNonce; | |||
// Is first packet | |||
protected bool _decryptSaltReceived; | |||
protected bool _encryptSaltSent; | |||
@@ -62,11 +62,11 @@ namespace Shadowsocks.Encryption.AEAD | |||
InitEncryptorInfo(method); | |||
InitKey(password); | |||
// Initialize all-zero nonce for each connection | |||
_encNonce = new byte[nonceLen]; | |||
_decNonce = new byte[nonceLen]; | |||
encNonce = new byte[nonceLen]; | |||
decNonce = new byte[nonceLen]; | |||
} | |||
protected abstract Dictionary<string, EncryptorInfo> getCiphers(); | |||
protected abstract Dictionary<string, CipherInfo> getCiphers(); | |||
protected void InitEncryptorInfo(string method) | |||
{ | |||
@@ -74,15 +74,12 @@ namespace Shadowsocks.Encryption.AEAD | |||
_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; | |||
var parameter = (AEADCipherParameter)CipherInfo.CipherParameter; | |||
keyLen = parameter.KeySize; | |||
saltLen = parameter.SaltSize; | |||
tagLen = parameter.TagSize; | |||
nonceLen = parameter.NonceSize; | |||
} | |||
protected void InitKey(string password) | |||
@@ -93,7 +90,7 @@ namespace Shadowsocks.Encryption.AEAD | |||
if (_Masterkey.Length != keyLen) Array.Resize(ref _Masterkey, keyLen); | |||
DeriveKey(passbuf, _Masterkey, keyLen); | |||
// init session key | |||
if (_sessionKey == null) _sessionKey = new byte[keyLen]; | |||
if (sessionKey == null) sessionKey = new byte[keyLen]; | |||
} | |||
public void DeriveKey(byte[] password, byte[] key, int keylen) | |||
@@ -103,32 +100,69 @@ namespace Shadowsocks.Encryption.AEAD | |||
public void DeriveSessionKey(byte[] salt, byte[] masterKey, byte[] sessionKey) | |||
{ | |||
CryptoUtils.HKDF(keyLen, masterKey, salt, InfoBytes).CopyTo(sessionKey,0); | |||
CryptoUtils.HKDF(keyLen, masterKey, salt, InfoBytes).CopyTo(sessionKey, 0); | |||
} | |||
protected void IncrementNonce(bool isEncrypt) | |||
{ | |||
lock (_nonceIncrementLock) { | |||
CryptoUtils.SodiumIncrement(isEncrypt ? _encNonce : _decNonce); | |||
lock (_nonceIncrementLock) | |||
{ | |||
CryptoUtils.SodiumIncrement(isEncrypt ? encNonce : decNonce); | |||
} | |||
} | |||
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); | |||
if (isEncrypt) | |||
{ | |||
encryptSalt = new byte[saltLen]; | |||
Array.Copy(salt, encryptSalt, saltLen); | |||
} | |||
else | |||
{ | |||
decryptSalt = new byte[saltLen]; | |||
Array.Copy(salt, decryptSalt, saltLen); | |||
} | |||
logger.Dump("Salt", salt, saltLen); | |||
} | |||
public static void randBytes(byte[] buf, int length) { RNG.GetBytes(buf, length); } | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
/// <param name="plaintext">Input, plain text</param> | |||
/// <param name="plen">plaintext.Length</param> | |||
/// <param name="ciphertext">Output, allocated by caller, tag space included, | |||
/// length = plaintext.Length + tagLen, [enc][tag] order</param> | |||
/// <param name="clen">Should be same as ciphertext.Length</param> | |||
public abstract void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen); | |||
// plain -> cipher + tag | |||
public abstract byte[] CipherEncrypt2(byte[] plain); | |||
// cipher + tag -> plain | |||
public abstract byte[] CipherDecrypt2(byte[] cipher); | |||
public (Memory<byte>, Memory<byte>) GetCipherTextAndTagMem(byte[] cipher) | |||
{ | |||
var mc = cipher.AsMemory(); | |||
var clen = mc.Length - tagLen; | |||
var c = mc.Slice(0, clen); | |||
var t = mc.Slice(clen); | |||
return (c, t); | |||
} | |||
public (byte[], byte[]) GetCipherTextAndTag(byte[] cipher) | |||
{ | |||
var (c, t) = GetCipherTextAndTagMem(cipher); | |||
return (c.ToArray(), t.ToArray()); | |||
} | |||
/// <summary> | |||
/// | |||
/// </summary> | |||
/// <param name="ciphertext">Cipher text in [enc][tag] order</param> | |||
/// <param name="clen">ciphertext.Length</param> | |||
/// <param name="plaintext">Output plain text may with additional data allocated by caller</param> | |||
/// <param name="plen">Output, should be used plain text length</param> | |||
public abstract void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen); | |||
#region TCP | |||
@@ -140,32 +174,35 @@ namespace Shadowsocks.Encryption.AEAD | |||
_encCircularBuffer.Put(buf, 0, length); | |||
outlength = 0; | |||
logger.Debug("---Start Encryption"); | |||
if (! _encryptSaltSent) { | |||
if (!_encryptSaltSent) | |||
{ | |||
_encryptSaltSent = true; | |||
// Generate salt | |||
byte[] saltBytes = new byte[saltLen]; | |||
randBytes(saltBytes, saltLen); | |||
RNG.GetBytes(saltBytes, saltLen); | |||
InitCipher(saltBytes, true, false); | |||
Array.Copy(saltBytes, 0, outbuf, 0, saltLen); | |||
outlength = saltLen; | |||
logger.Debug($"_encryptSaltSent outlength {outlength}"); | |||
} | |||
if (! _tcpRequestSent) { | |||
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); | |||
byte[] encAddrBufBytes = new byte[AddressBufferLength + tagLen * 2 + CHUNK_LEN_BYTES]; | |||
byte[] addrBytes = _encCircularBuffer.Get(AddressBufferLength); | |||
ChunkEncrypt(addrBytes, AddressBufferLength, encAddrBufBytes, out encAddrBufLength); | |||
Debug.Assert(encAddrBufLength == AddressBufferLength + tagLen * 2 + CHUNK_LEN_BYTES); | |||
Array.Copy(encAddrBufBytes, 0, outbuf, outlength, encAddrBufLength); | |||
outlength += encAddrBufLength; | |||
logger.Debug($"_tcpRequestSent outlength {outlength}"); | |||
} | |||
// handle other chunks | |||
while (true) { | |||
while (true) | |||
{ | |||
uint bufSize = (uint)_encCircularBuffer.Size; | |||
if (bufSize <= 0) return; | |||
var chunklength = (int)Math.Min(bufSize, CHUNK_LEN_MASK); | |||
@@ -178,12 +215,14 @@ namespace Shadowsocks.Encryption.AEAD | |||
outlength += encChunkLength; | |||
logger.Debug("chunks enc outlength " + outlength); | |||
// check if we have enough space for outbuf | |||
if (outlength + TCPHandler.ChunkOverheadSize > TCPHandler.BufferSize) { | |||
if (outlength + TCPHandler.ChunkOverheadSize > TCPHandler.BufferSize) | |||
{ | |||
logger.Debug("enc outbuf almost full, giving up"); | |||
return; | |||
} | |||
bufSize = (uint)_encCircularBuffer.Size; | |||
if (bufSize <= 0) { | |||
if (bufSize <= 0) | |||
{ | |||
logger.Debug("No more data to encrypt, leaving"); | |||
return; | |||
} | |||
@@ -200,10 +239,12 @@ namespace Shadowsocks.Encryption.AEAD | |||
_decCircularBuffer.Put(buf, 0, length); | |||
logger.Debug("---Start Decryption"); | |||
if (! _decryptSaltReceived) { | |||
if (!_decryptSaltReceived) | |||
{ | |||
bufSize = _decCircularBuffer.Size; | |||
// check if we get the leading salt | |||
if (bufSize <= saltLen) { | |||
if (bufSize <= saltLen) | |||
{ | |||
// need more | |||
return; | |||
} | |||
@@ -214,16 +255,19 @@ namespace Shadowsocks.Encryption.AEAD | |||
} | |||
// handle chunks | |||
while (true) { | |||
while (true) | |||
{ | |||
bufSize = _decCircularBuffer.Size; | |||
// check if we have any data | |||
if (bufSize <= 0) { | |||
if (bufSize <= 0) | |||
{ | |||
logger.Debug("No data in _decCircularBuffer"); | |||
return; | |||
} | |||
// first get chunk length | |||
if (bufSize <= CHUNK_LEN_BYTES + tagLen) { | |||
if (bufSize <= CHUNK_LEN_BYTES + tagLen) | |||
{ | |||
// so we only have chunk length and its tag? | |||
return; | |||
} | |||
@@ -231,13 +275,16 @@ namespace Shadowsocks.Encryption.AEAD | |||
#region Chunk Decryption | |||
byte[] encLenBytes = _decCircularBuffer.Peek(CHUNK_LEN_BYTES + tagLen); | |||
uint decChunkLenLength = 0; | |||
byte[] decChunkLenBytes = new byte[CHUNK_LEN_BYTES]; | |||
//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); | |||
//cipherDecrypt(encLenBytes, CHUNK_LEN_BYTES + (uint)tagLen, decChunkLenBytes, ref decChunkLenLength); | |||
byte[] decChunkLenBytes = CipherDecrypt2(encLenBytes); | |||
// Debug.Assert(decChunkLenLength == CHUNK_LEN_BYTES); | |||
// finally we get the real chunk len | |||
ushort chunkLen = (ushort) IPAddress.NetworkToHostOrder((short)BitConverter.ToUInt16(decChunkLenBytes, 0)); | |||
ushort chunkLen = (ushort)IPAddress.NetworkToHostOrder((short)BitConverter.ToUInt16(decChunkLenBytes, 0)); | |||
if (chunkLen > CHUNK_LEN_MASK) | |||
{ | |||
// we get invalid chunk | |||
@@ -246,7 +293,8 @@ namespace Shadowsocks.Encryption.AEAD | |||
} | |||
logger.Debug("Get the real chunk len:" + chunkLen); | |||
bufSize = _decCircularBuffer.Size; | |||
if (bufSize < CHUNK_LEN_BYTES + tagLen /* we haven't remove them */+ chunkLen + tagLen) { | |||
if (bufSize < CHUNK_LEN_BYTES + tagLen /* we haven't remove them */+ chunkLen + tagLen) | |||
{ | |||
logger.Debug("No more data to decrypt one chunk"); | |||
return; | |||
} | |||
@@ -256,17 +304,19 @@ namespace Shadowsocks.Encryption.AEAD | |||
// drop chunk len and its tag from buffer | |||
_decCircularBuffer.Skip(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); | |||
//byte[] decChunkBytes = new byte[chunkLen]; | |||
//uint decChunkLen = 0; | |||
//cipherDecrypt(encChunkBytes, chunkLen + (uint)tagLen, decChunkBytes, ref decChunkLen); | |||
byte[] decChunkBytes = CipherDecrypt2(encChunkBytes); | |||
//Debug.Assert(decChunkLen == chunkLen); | |||
IncrementNonce(false); | |||
#endregion | |||
// output to outbuf | |||
Buffer.BlockCopy(decChunkBytes, 0, outbuf, outlength, (int) decChunkLen); | |||
outlength += (int)decChunkLen; | |||
decChunkBytes.CopyTo(outbuf, outlength); | |||
// Buffer.BlockCopy(decChunkBytes, 0, outbuf, outlength, (int)decChunkLen); | |||
outlength += decChunkBytes.Length; | |||
logger.Debug("aead dec outlength " + outlength); | |||
if (outlength + 100 > TCPHandler.BufferSize) | |||
{ | |||
@@ -275,7 +325,8 @@ namespace Shadowsocks.Encryption.AEAD | |||
} | |||
bufSize = _decCircularBuffer.Size; | |||
// check if we already done all of them | |||
if (bufSize <= 0) { | |||
if (bufSize <= 0) | |||
{ | |||
logger.Debug("No data in _decCircularBuffer, already all done"); | |||
return; | |||
} | |||
@@ -289,59 +340,74 @@ namespace Shadowsocks.Encryption.AEAD | |||
public override void EncryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
// Generate salt | |||
randBytes(outbuf, saltLen); | |||
RNG.GetBytes(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); | |||
//uint olen = 0; | |||
lock (_udpTmpBuf) | |||
{ | |||
//cipherEncrypt(buf, (uint)length, _udpTmpBuf, ref olen); | |||
var plain = buf.AsSpan().Slice(0, length).ToArray(); // mmp | |||
var cipher = CipherEncrypt2(plain); | |||
//Debug.Assert(olen == length + tagLen); | |||
Buffer.BlockCopy(cipher, 0, outbuf, saltLen, length + tagLen); | |||
//Buffer.BlockCopy(_udpTmpBuf, 0, outbuf, saltLen, (int)olen); | |||
outlength = (int)(saltLen + cipher.Length); | |||
} | |||
} | |||
public override void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
InitCipher(buf, false, true); | |||
uint olen = 0; | |||
lock (_udpTmpBuf) { | |||
//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; | |||
byte[] b = buf.AsSpan().Slice(0, length - saltLen).ToArray(); | |||
byte[] o = CipherDecrypt2(b); | |||
//cipherDecrypt(buf, (uint)(length - saltLen), _udpTmpBuf, ref olen); | |||
Buffer.BlockCopy(o, 0, outbuf, 0, o.Length); | |||
outlength = o.Length; | |||
} | |||
} | |||
#endregion | |||
// we know the plaintext length before encryption, so we can do it in one operation | |||
// plain -> [len][data] | |||
private void ChunkEncrypt(byte[] plaintext, int plainLen, byte[] ciphertext, out int cipherLen) | |||
{ | |||
if (plainLen > CHUNK_LEN_MASK) { | |||
if (plainLen > CHUNK_LEN_MASK) | |||
{ | |||
logger.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); | |||
//byte[] encLenBytes = new byte[CHUNK_LEN_BYTES + tagLen]; | |||
//uint encChunkLenLength = 0; | |||
// always 2 byte | |||
byte[] lenbuf = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)plainLen)); | |||
//cipherEncrypt(lenbuf, CHUNK_LEN_BYTES, encLenBytes, ref encChunkLenLength); | |||
byte[] encLenBytes = CipherEncrypt2(lenbuf); | |||
//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); | |||
//byte[] encBytes = new byte[plainLen + tagLen]; | |||
//uint encBufLength = 0; | |||
//cipherEncrypt(plaintext, (uint)plainLen, encBytes, ref encBufLength); | |||
byte[] encBytes = CipherEncrypt2(plaintext); | |||
//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); | |||
encLenBytes.CopyTo(ciphertext, 0); | |||
encBytes.CopyTo(ciphertext, encLenBytes.Length); | |||
//Array.Copy(encLenBytes, 0, ciphertext, 0, (int)encChunkLenLength); | |||
//Buffer.BlockCopy(encBytes, 0, ciphertext, (int)encChunkLenLength, (int)encBufLength); | |||
cipherLen = encLenBytes.Length + encBytes.Length; | |||
} | |||
} | |||
} |
@@ -0,0 +1,81 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using NaCl.Core; | |||
using NaCl.Core.Base; | |||
namespace Shadowsocks.Encryption.AEAD | |||
{ | |||
public class AEADNaClEncryptor : AEADEncryptor | |||
{ | |||
SnufflePoly1305 enc; | |||
SnufflePoly1305 dec; | |||
public AEADNaClEncryptor(string method, string password) : base(method, password) | |||
{ | |||
} | |||
public override void InitCipher(byte[] salt, bool isEncrypt, bool isUdp) | |||
{ | |||
base.InitCipher(salt, isEncrypt, isUdp); | |||
DeriveSessionKey(isEncrypt ? encryptSalt : decryptSalt, | |||
_Masterkey, sessionKey); | |||
SnufflePoly1305 tmp; | |||
switch (_cipher) | |||
{ | |||
default: | |||
case CipherFamily.Chacha20Poly1305: | |||
tmp = new ChaCha20Poly1305(sessionKey); | |||
break; | |||
case CipherFamily.XChacha20Poly1305: | |||
tmp = new XChaCha20Poly1305(sessionKey); | |||
break; | |||
} | |||
if (isEncrypt) enc = tmp; | |||
else dec = tmp; | |||
} | |||
public override void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) | |||
{ | |||
var pt = dec.Decrypt(ciphertext, null, decNonce); | |||
pt.CopyTo(plaintext, 0); | |||
plen = (uint)pt.Length; | |||
} | |||
public override void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) | |||
{ | |||
var ct = enc.Encrypt(plaintext, null, encNonce); | |||
ct.CopyTo(ciphertext, 0); | |||
clen = (uint)ct.Length; | |||
} | |||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||
{ | |||
{"chacha20-ietf-poly1305", new CipherInfo("chacha20-ietf-poly1305",32, 32, 12, 16, CipherFamily.Chacha20Poly1305)}, | |||
{"xchacha20-ietf-poly1305", new CipherInfo("xchacha20-ietf-poly1305",32, 32, 24, 16, CipherFamily.XChacha20Poly1305)}, | |||
}; | |||
protected override Dictionary<string, CipherInfo> getCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
public override byte[] CipherEncrypt2(byte[] plain) | |||
{ | |||
return enc.Encrypt(plain, null, encNonce); | |||
} | |||
public override byte[] CipherDecrypt2(byte[] cipher) | |||
{ | |||
return dec.Decrypt(cipher, null, decNonce); | |||
} | |||
} | |||
} |
@@ -1,48 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
namespace Shadowsocks.Encryption.AEAD | |||
{ | |||
public class AEADNativeEncryptor : AEADEncryptor | |||
{ | |||
public AEADNativeEncryptor(string method, string password) | |||
: base(method, password) | |||
{ | |||
} | |||
public override void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) | |||
{ | |||
Array.Copy(ciphertext, plaintext, 0); | |||
plen = clen; | |||
} | |||
public override void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) | |||
{ | |||
Array.Copy(plaintext, ciphertext, 0); | |||
clen = plen; | |||
} | |||
public override void Dispose() | |||
{ | |||
return; | |||
} | |||
private static Dictionary<string, EncryptorInfo> _ciphers = new Dictionary<string, EncryptorInfo>() | |||
{ | |||
{"plain-fake-aead",new EncryptorInfo("PLAIN_AEAD",0,0,0,0,0) } | |||
}; | |||
protected override Dictionary<string, EncryptorInfo> getCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
public static IEnumerable<string> SupportedCiphers() | |||
{ | |||
return _ciphers.Keys; | |||
} | |||
} | |||
} |
@@ -0,0 +1,91 @@ | |||
using Shadowsocks.Controller; | |||
using System; | |||
namespace Shadowsocks.Encryption | |||
{ | |||
public enum CipherFamily | |||
{ | |||
Plain, | |||
Table, | |||
AesGcm, | |||
AesCfb, | |||
AesCtr, | |||
Chacha20, | |||
Chacha20Poly1305, | |||
XChacha20Poly1305, | |||
Rc4, | |||
Rc4Md5, | |||
} | |||
public enum CipherStandardState | |||
{ | |||
InUse, | |||
Deprecated, | |||
Hidden, | |||
} | |||
public class CipherParameter | |||
{ | |||
public int KeySize; | |||
} | |||
public class StreamCipherParameter : CipherParameter | |||
{ | |||
public int IvSize; | |||
} | |||
public class AEADCipherParameter : CipherParameter | |||
{ | |||
public int SaltSize; | |||
public int TagSize; | |||
public int NonceSize; | |||
} | |||
public class CipherInfo | |||
{ | |||
public string Name; | |||
public CipherFamily Type; | |||
public CipherParameter CipherParameter; | |||
public CipherStandardState StandardState = CipherStandardState.InUse; | |||
#region Stream ciphers | |||
public CipherInfo(string name, int keySize, int ivSize, CipherFamily type) | |||
{ | |||
Type = type; | |||
Name = name; | |||
StandardState = CipherStandardState.Hidden; | |||
CipherParameter = new StreamCipherParameter | |||
{ | |||
KeySize = keySize, | |||
IvSize = ivSize, | |||
}; | |||
} | |||
#endregion | |||
#region AEAD ciphers | |||
public CipherInfo(string name, int keySize, int saltSize, int nonceSize, int tagSize, CipherFamily type) | |||
{ | |||
Type = type; | |||
Name = name; | |||
CipherParameter = new AEADCipherParameter | |||
{ | |||
KeySize = keySize, | |||
SaltSize = saltSize, | |||
NonceSize = nonceSize, | |||
TagSize = tagSize, | |||
}; | |||
} | |||
#endregion | |||
public override string ToString() | |||
{ | |||
return StandardState == CipherStandardState.InUse ? Name : $"{Name} ({I18N.GetString("deprecated")})"; | |||
} | |||
} | |||
} |
@@ -1,65 +1,6 @@ | |||
namespace Shadowsocks.Encryption | |||
{ | |||
public class EncryptorInfo | |||
{ | |||
public int KeySize; | |||
public int IvSize; | |||
public int SaltSize; | |||
public int TagSize; | |||
public int NonceSize; | |||
public int Type; | |||
public string InnerLibName; | |||
// For those who make use of internal crypto method name | |||
// e.g. mbed TLS | |||
#region Stream ciphers | |||
public EncryptorInfo(string innerLibName, int keySize, int ivSize, int type) | |||
{ | |||
this.KeySize = keySize; | |||
this.IvSize = ivSize; | |||
this.Type = type; | |||
this.InnerLibName = innerLibName; | |||
} | |||
public EncryptorInfo(int keySize, int ivSize, int type) | |||
{ | |||
this.KeySize = keySize; | |||
this.IvSize = ivSize; | |||
this.Type = type; | |||
this.InnerLibName = string.Empty; | |||
} | |||
#endregion | |||
#region AEAD ciphers | |||
public EncryptorInfo(string innerLibName, int keySize, int saltSize, int nonceSize, int tagSize, int type) | |||
{ | |||
this.KeySize = keySize; | |||
this.SaltSize = saltSize; | |||
this.NonceSize = nonceSize; | |||
this.TagSize = tagSize; | |||
this.Type = type; | |||
this.InnerLibName = innerLibName; | |||
} | |||
public EncryptorInfo(int keySize, int saltSize, int nonceSize, int tagSize, int type) | |||
{ | |||
this.KeySize = keySize; | |||
this.SaltSize = saltSize; | |||
this.NonceSize = nonceSize; | |||
this.TagSize = tagSize; | |||
this.Type = type; | |||
this.InnerLibName = string.Empty; | |||
} | |||
#endregion | |||
} | |||
public abstract class EncryptorBase | |||
: IEncryptor | |||
public abstract class EncryptorBase : IEncryptor | |||
{ | |||
public const int MAX_INPUT_SIZE = 32768; | |||
@@ -81,7 +22,7 @@ | |||
protected string Method; | |||
protected string Password; | |||
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); | |||
@@ -90,8 +31,6 @@ | |||
public abstract void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength); | |||
public abstract void Dispose(); | |||
public int AddrBufLength { get; set; } = - 1; | |||
public int AddressBufferLength { get; set; } = -1; | |||
} | |||
} |
@@ -10,27 +10,51 @@ namespace Shadowsocks.Encryption | |||
public static class EncryptorFactory | |||
{ | |||
private static Dictionary<string, Type> _registeredEncryptors = new Dictionary<string, Type>(); | |||
private static Dictionary<string, CipherInfo> ciphers = new Dictionary<string, CipherInfo>(); | |||
private static readonly Type[] ConstructorTypes = { typeof(string), typeof(string) }; | |||
static EncryptorFactory() | |||
{ | |||
foreach (string method in StreamNativeEncryptor.SupportedCiphers()) | |||
foreach (var method in StreamTableNativeEncryptor.SupportedCiphers()) | |||
{ | |||
if (!_registeredEncryptors.ContainsKey(method)) | |||
_registeredEncryptors.Add(method, typeof(StreamNativeEncryptor)); | |||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||
{ | |||
ciphers.Add(method.Key, method.Value); | |||
_registeredEncryptors.Add(method.Key, typeof(StreamTableNativeEncryptor)); | |||
} | |||
} | |||
foreach (string method in AEADNativeEncryptor.SupportedCiphers()) | |||
foreach (var method in StreamRc4NativeEncryptor.SupportedCiphers()) | |||
{ | |||
if (!_registeredEncryptors.ContainsKey(method)) | |||
_registeredEncryptors.Add(method, typeof(AEADNativeEncryptor)); | |||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||
{ | |||
ciphers.Add(method.Key, method.Value); | |||
_registeredEncryptors.Add(method.Key, typeof(StreamRc4NativeEncryptor)); | |||
} | |||
} | |||
foreach (string method in AEADBouncyCastleEncryptor.SupportedCiphers()) | |||
foreach (var method in AEADAesGcmNativeEncryptor.SupportedCiphers()) | |||
{ | |||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||
{ | |||
ciphers.Add(method.Key, method.Value); | |||
_registeredEncryptors.Add(method.Key, typeof(AEADAesGcmNativeEncryptor)); | |||
} | |||
} | |||
foreach (var method in AEADNaClEncryptor.SupportedCiphers()) | |||
{ | |||
if (!_registeredEncryptors.ContainsKey(method)) | |||
_registeredEncryptors.Add(method, typeof(AEADBouncyCastleEncryptor)); | |||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||
{ | |||
ciphers.Add(method.Key, method.Value); | |||
_registeredEncryptors.Add(method.Key, typeof(AEADNaClEncryptor)); | |||
} | |||
} | |||
foreach (var method in StreamRc4NativeEncryptor.SupportedCiphers()) | |||
{ | |||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||
{ | |||
ciphers.Add(method.Key, method.Value); | |||
_registeredEncryptors.Add(method.Key, typeof(StreamRc4NativeEncryptor)); | |||
} | |||
} | |||
} | |||
@@ -55,15 +79,25 @@ namespace Shadowsocks.Encryption | |||
{ | |||
var sb = new StringBuilder(); | |||
sb.Append(Environment.NewLine); | |||
sb.AppendLine("========================="); | |||
sb.AppendLine("-------------------------"); | |||
sb.AppendLine("Registered Encryptor Info"); | |||
foreach (var encryptor in _registeredEncryptors) | |||
{ | |||
sb.AppendLine(String.Format("{0}=>{1}", encryptor.Key, encryptor.Value.Name)); | |||
} | |||
sb.AppendLine("========================="); | |||
// use ----- instead of =======, so when user paste it to Github, it won't became title | |||
sb.AppendLine("-------------------------"); | |||
return sb.ToString(); | |||
} | |||
public static CipherInfo GetCipherInfo(string name) | |||
{ | |||
return ciphers[name]; | |||
} | |||
public static IEnumerable<CipherInfo> ListAvaliableCiphers() | |||
{ | |||
return ciphers.Values; | |||
} | |||
} | |||
} |
@@ -1,11 +1,12 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
namespace Shadowsocks.Encryption | |||
{ | |||
public interface IEncryptor : IDisposable | |||
public interface IEncryptor | |||
{ | |||
/* length == -1 means not used */ | |||
int AddrBufLength { set; get; } | |||
int AddressBufferLength { set; get; } | |||
void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength); | |||
void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength); | |||
void EncryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength); | |||
@@ -7,8 +7,7 @@ using Shadowsocks.Controller; | |||
namespace Shadowsocks.Encryption.Stream | |||
{ | |||
public abstract class StreamEncryptor | |||
: EncryptorBase | |||
public abstract class StreamEncryptor : EncryptorBase | |||
{ | |||
// for UDP only | |||
protected static byte[] _udpTmpBuf = new byte[65536]; | |||
@@ -17,7 +16,7 @@ namespace Shadowsocks.Encryption.Stream | |||
private ByteCircularBuffer _encCircularBuffer = new ByteCircularBuffer(TCPHandler.BufferSize * 2); | |||
private ByteCircularBuffer _decCircularBuffer = new ByteCircularBuffer(TCPHandler.BufferSize * 2); | |||
protected Dictionary<string, EncryptorInfo> ciphers; | |||
protected Dictionary<string, CipherInfo> ciphers; | |||
protected byte[] _encryptIV; | |||
protected byte[] _decryptIV; | |||
@@ -27,10 +26,10 @@ namespace Shadowsocks.Encryption.Stream | |||
protected bool _encryptIVSent; | |||
protected string _method; | |||
protected int _cipher; | |||
protected CipherFamily _cipher; | |||
// internal name in the crypto library | |||
protected string _innerLibName; | |||
protected EncryptorInfo CipherInfo; | |||
protected CipherInfo CipherInfo; | |||
// long-time master key | |||
protected static byte[] _key = null; | |||
protected int keyLen; | |||
@@ -43,7 +42,7 @@ namespace Shadowsocks.Encryption.Stream | |||
InitKey(password); | |||
} | |||
protected abstract Dictionary<string, EncryptorInfo> getCiphers(); | |||
protected abstract Dictionary<string, CipherInfo> getCiphers(); | |||
private void InitEncryptorInfo(string method) | |||
{ | |||
@@ -51,13 +50,10 @@ namespace Shadowsocks.Encryption.Stream | |||
_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; | |||
var parameter = (StreamCipherParameter)CipherInfo.CipherParameter; | |||
keyLen = parameter.KeySize; | |||
ivLen = parameter.IvSize; | |||
} | |||
private void InitKey(string password) | |||
@@ -73,10 +69,14 @@ namespace Shadowsocks.Encryption.Stream | |||
byte[] result = new byte[password.Length + MD5_LEN]; | |||
int i = 0; | |||
byte[] md5sum = null; | |||
while (i < keylen) { | |||
if (i == 0) { | |||
while (i < keylen) | |||
{ | |||
if (i == 0) | |||
{ | |||
md5sum = CryptoUtils.MD5(password); | |||
} else { | |||
} | |||
else | |||
{ | |||
Array.Copy(md5sum, 0, result, 0, MD5_LEN); | |||
Array.Copy(password, 0, result, MD5_LEN, password.Length); | |||
md5sum = CryptoUtils.MD5(result); | |||
@@ -88,10 +88,13 @@ namespace Shadowsocks.Encryption.Stream | |||
protected virtual void initCipher(byte[] iv, bool isEncrypt) | |||
{ | |||
if (isEncrypt) { | |||
if (isEncrypt) | |||
{ | |||
_encryptIV = new byte[ivLen]; | |||
Array.Copy(iv, _encryptIV, ivLen); | |||
} else { | |||
} | |||
else | |||
{ | |||
_decryptIV = new byte[ivLen]; | |||
Array.Copy(iv, _decryptIV, ivLen); | |||
} | |||
@@ -108,12 +111,13 @@ namespace Shadowsocks.Encryption.Stream | |||
int cipherOffset = 0; | |||
Debug.Assert(_encCircularBuffer != null, "_encCircularBuffer != null"); | |||
_encCircularBuffer.Put(buf, 0, length); | |||
if (! _encryptIVSent) { | |||
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; | |||
@@ -130,16 +134,22 @@ namespace Shadowsocks.Encryption.Stream | |||
{ | |||
Debug.Assert(_decCircularBuffer != null, "_circularBuffer != null"); | |||
_decCircularBuffer.Put(buf, 0, length); | |||
if (! _decryptIVReceived) { | |||
if (_decCircularBuffer.Size <= ivLen) { | |||
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); | |||
if (ivLen > 0) | |||
{ | |||
byte[] iv = _decCircularBuffer.Get(ivLen); | |||
initCipher(iv, false); | |||
} | |||
else initCipher(Array.Empty<byte>(), false); | |||
} | |||
byte[] cipher = _decCircularBuffer.ToArray(); | |||
cipherUpdate(false, cipher.Length, cipher, outbuf); | |||
@@ -158,7 +168,8 @@ namespace Shadowsocks.Encryption.Stream | |||
// Generate IV | |||
randBytes(outbuf, ivLen); | |||
initCipher(outbuf, true); | |||
lock (_udpTmpBuf) { | |||
lock (_udpTmpBuf) | |||
{ | |||
cipherUpdate(true, length, buf, _udpTmpBuf); | |||
outlength = length + ivLen; | |||
Buffer.BlockCopy(_udpTmpBuf, 0, outbuf, ivLen, length); | |||
@@ -170,7 +181,8 @@ namespace Shadowsocks.Encryption.Stream | |||
// Get IV from first pos | |||
initCipher(buf, false); | |||
outlength = length - ivLen; | |||
lock (_udpTmpBuf) { | |||
lock (_udpTmpBuf) | |||
{ | |||
// C# could be multi-threaded | |||
Buffer.BlockCopy(buf, ivLen, _udpTmpBuf, 0, length - ivLen); | |||
cipherUpdate(false, length - ivLen, _udpTmpBuf, outbuf); | |||
@@ -0,0 +1,116 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace Shadowsocks.Encryption.Stream | |||
{ | |||
public class StreamRc4NativeEncryptor : StreamEncryptor | |||
{ | |||
byte[] realkey = new byte[256]; | |||
byte[] sbox = new byte[256]; | |||
public StreamRc4NativeEncryptor(string method, string password) : base(method, password) | |||
{ | |||
} | |||
protected override void initCipher(byte[] iv, bool isEncrypt) | |||
{ | |||
base.initCipher(iv, isEncrypt); | |||
if (_cipher == CipherFamily.Rc4Md5) | |||
{ | |||
byte[] temp = new byte[keyLen + ivLen]; | |||
Array.Copy(_key, 0, temp, 0, keyLen); | |||
Array.Copy(iv, 0, temp, keyLen, ivLen); | |||
realkey = CryptoUtils.MD5(temp); | |||
} | |||
else | |||
{ | |||
realkey = _key; | |||
} | |||
sbox = SBox(realkey); | |||
} | |||
protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) | |||
{ | |||
var ctx = isEncrypt ? enc_ctx : dec_ctx; | |||
byte[] t = new byte[length]; | |||
Array.Copy(buf, t, length); | |||
RC4(ctx, sbox, t, length); | |||
Array.Copy(t, outbuf, length); | |||
} | |||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||
{ | |||
// original RC4 doesn't use IV | |||
{ "rc4", new CipherInfo("rc4", 16, 0, CipherFamily.Rc4) }, | |||
{ "rc4-md5", new CipherInfo("rc4-md5", 16, 16, CipherFamily.Rc4Md5) }, | |||
}; | |||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
protected override Dictionary<string, CipherInfo> getCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
#region RC4 | |||
class Context | |||
{ | |||
public int index1 = 0; | |||
public int index2 = 0; | |||
} | |||
private Context enc_ctx = new Context(); | |||
private Context dec_ctx = new Context(); | |||
private byte[] SBox(byte[] key) | |||
{ | |||
byte[] s = new byte[256]; | |||
for (int i = 0; i < 256; i++) | |||
{ | |||
s[i] = (byte)i; | |||
} | |||
for (int i = 0, j = 0; i < 256; i++) | |||
{ | |||
j = (j + key[i % key.Length] + s[i]) & 255; | |||
Swap(s, i, j); | |||
} | |||
return s; | |||
} | |||
private void RC4(Context ctx, byte[] s, byte[] data, int length) | |||
{ | |||
for (int n = 0; n < length; n++) | |||
{ | |||
byte b = data[n]; | |||
ctx.index1 = (ctx.index1 + 1) & 255; | |||
ctx.index2 = (ctx.index2 + s[ctx.index1]) & 255; | |||
Swap(s, ctx.index1, ctx.index2); | |||
data[n] = (byte)(b ^ s[(s[ctx.index1] + s[ctx.index2]) & 255]); | |||
} | |||
} | |||
private static void Swap(byte[] s, int i, int j) | |||
{ | |||
byte c = s[i]; | |||
s[i] = s[j]; | |||
s[j] = c; | |||
} | |||
#endregion | |||
} | |||
} |
@@ -6,46 +6,19 @@ using System.Threading.Tasks; | |||
namespace Shadowsocks.Encryption.Stream | |||
{ | |||
public class StreamNativeEncryptor : StreamEncryptor | |||
public class StreamTableNativeEncryptor : StreamEncryptor | |||
{ | |||
const int Plain = 0; | |||
const int Table = 1; | |||
const int Rc4 = 2; | |||
const int Rc4Md5 = 3; | |||
string _password; | |||
byte[] realkey; | |||
byte[] sbox; | |||
public StreamNativeEncryptor(string method, string password) : base(method, password) | |||
public StreamTableNativeEncryptor(string method, string password) : base(method, password) | |||
{ | |||
_password = password; | |||
} | |||
public override void Dispose() | |||
{ | |||
return; | |||
} | |||
protected override void initCipher(byte[] iv, bool isEncrypt) | |||
{ | |||
base.initCipher(iv, isEncrypt); | |||
if (_cipher >= Rc4) | |||
{ | |||
if (_cipher == Rc4Md5) | |||
{ | |||
byte[] temp = new byte[keyLen + ivLen]; | |||
Array.Copy(_key, 0, temp, 0, keyLen); | |||
Array.Copy(iv, 0, temp, keyLen, ivLen); | |||
realkey = CryptoUtils.MD5(temp); | |||
} | |||
else | |||
{ | |||
realkey = _key; | |||
} | |||
sbox = SBox(realkey); | |||
} | |||
else if (_cipher == Table) | |||
if (_cipher == CipherFamily.Table) | |||
{ | |||
ulong a = BitConverter.ToUInt64(CryptoUtils.MD5(Encoding.UTF8.GetBytes(_password)), 0); | |||
for (int i = 0; i < 256; i++) | |||
@@ -65,7 +38,7 @@ namespace Shadowsocks.Encryption.Stream | |||
protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) | |||
{ | |||
if (_cipher == Table) | |||
if (_cipher == CipherFamily.Table) | |||
{ | |||
var table = isEncrypt ? _encryptTable : _decryptTable; | |||
for (int i = 0; i < length; i++) | |||
@@ -73,36 +46,24 @@ namespace Shadowsocks.Encryption.Stream | |||
outbuf[i] = table[buf[i]]; | |||
} | |||
} | |||
else if (_cipher == Plain) | |||
else if (_cipher == CipherFamily.Plain) | |||
{ | |||
Array.Copy(buf, outbuf, length); | |||
} | |||
else | |||
{ | |||
var ctx = isEncrypt ? enc_ctx : dec_ctx; | |||
byte[] t = new byte[length]; | |||
Array.Copy(buf, t, length); | |||
RC4(ctx, sbox, t, length); | |||
Array.Copy(t, outbuf, length); | |||
} | |||
} | |||
private static readonly Dictionary<string, EncryptorInfo> _ciphers = new Dictionary<string, EncryptorInfo> | |||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||
{ | |||
{"plain", new EncryptorInfo("PLAIN", 0, 0, Plain) }, | |||
{"table", new EncryptorInfo("TABLE", 0, 0, Table) }, | |||
{ "rc4", new EncryptorInfo("RC4", 16, 0, Rc4) }, // original RC4 doesn't use IV | |||
{ "rc4-md5", new EncryptorInfo("RC4", 16, 16, Rc4Md5) }, | |||
{"plain", new CipherInfo("plain", 0, 0, CipherFamily.Plain) }, | |||
{"table", new CipherInfo("table", 0, 0, CipherFamily.Table) }, | |||
}; | |||
public static IEnumerable<string> SupportedCiphers() | |||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||
{ | |||
return _ciphers.Keys; | |||
return _ciphers; | |||
} | |||
protected override Dictionary<string, EncryptorInfo> getCiphers() | |||
protected override Dictionary<string, CipherInfo> getCiphers() | |||
{ | |||
return _ciphers; | |||
} | |||
@@ -156,58 +117,5 @@ namespace Shadowsocks.Encryption.Stream | |||
return sorted; | |||
} | |||
#endregion | |||
#region RC4 | |||
class Context | |||
{ | |||
public int index1 = 0; | |||
public int index2 = 0; | |||
} | |||
private Context enc_ctx = new Context(); | |||
private Context dec_ctx = new Context(); | |||
private byte[] SBox(byte[] key) | |||
{ | |||
byte[] s = new byte[256]; | |||
for (int i = 0; i < 256; i++) | |||
{ | |||
s[i] = (byte)i; | |||
} | |||
for (int i = 0, j = 0; i < 256; i++) | |||
{ | |||
j = (j + key[i % key.Length] + s[i]) & 255; | |||
Swap(s, i, j); | |||
} | |||
return s; | |||
} | |||
private void RC4(Context ctx, byte[] s, byte[] data, int length) | |||
{ | |||
for (int n = 0; n < length; n++) | |||
{ | |||
byte b = data[n]; | |||
ctx.index1 = (ctx.index1 + 1) & 255; | |||
ctx.index2 = (ctx.index2 + s[ctx.index1]) & 255; | |||
Swap(s, ctx.index1, ctx.index2); | |||
data[n] = (byte)(b ^ s[(s[ctx.index1] + s[ctx.index2]) & 255]); | |||
} | |||
} | |||
private static void Swap(byte[] s, int i, int j) | |||
{ | |||
byte c = s[i]; | |||
s[i] = s[j]; | |||
s[j] = c; | |||
} | |||
#endregion | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Encryption; | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Properties; | |||
using System; | |||
@@ -19,98 +20,11 @@ namespace Shadowsocks.View | |||
private bool isChange = false; | |||
private class EncryptionMethod | |||
{ | |||
public readonly string name; | |||
public readonly bool deprecated; | |||
// Edit here to add/delete encryption method displayed in UI | |||
private static string[] deprecatedMethod = new string[] | |||
{ | |||
"rc4-md5", | |||
"salsa20", | |||
"chacha20", | |||
"bf-cfb", | |||
"rc4", | |||
"plain", | |||
"table", | |||
}; | |||
private static string[] inuseMethod = new string[] | |||
{ | |||
"aes-256-gcm", | |||
"aes-192-gcm", | |||
"aes-128-gcm", | |||
"chacha20-ietf-poly1305", | |||
"xchacha20-ietf-poly1305", | |||
"chacha20-ietf", | |||
"aes-256-cfb", | |||
"aes-192-cfb", | |||
"aes-128-cfb", | |||
"aes-256-ctr", | |||
"aes-192-ctr", | |||
"aes-128-ctr", | |||
"camellia-256-cfb", | |||
"camellia-192-cfb", | |||
"camellia-128-cfb", | |||
}; | |||
public static EncryptionMethod[] AllMethods | |||
{ | |||
get | |||
{ | |||
if (!init) Init(); | |||
return allMethods; | |||
} | |||
} | |||
private static bool init = false; | |||
private static EncryptionMethod[] allMethods; | |||
private static Dictionary<string, EncryptionMethod> methodByName = new Dictionary<string, EncryptionMethod>(); | |||
private static void Init() | |||
{ | |||
var all = new List<EncryptionMethod>(); | |||
all.AddRange(inuseMethod.Select(i => new EncryptionMethod(i, false))); | |||
all.AddRange(deprecatedMethod.Select(d => new EncryptionMethod(d, true))); | |||
allMethods = all.ToArray(); | |||
foreach (var item in all) | |||
{ | |||
methodByName[item.name] = item; | |||
} | |||
init = true; | |||
} | |||
public static EncryptionMethod GetMethod(string name) | |||
{ | |||
if (!init) Init(); | |||
bool success = methodByName.TryGetValue(name, out EncryptionMethod method); | |||
if (!success) | |||
{ | |||
string defaultMethod = Server.DefaultMethod; | |||
MessageBox.Show(I18N.GetString("Encryption method {0} not exist, will replace with {1}", name, defaultMethod), I18N.GetString("Shadowsocks")); | |||
return methodByName[defaultMethod]; | |||
} | |||
return method; | |||
} | |||
private EncryptionMethod(string name, bool deprecated) | |||
{ | |||
this.name = name; | |||
this.deprecated = deprecated; | |||
} | |||
public override string ToString() | |||
{ | |||
return deprecated ? $"{name} ({I18N.GetString("deprecated")})" : name; | |||
} | |||
} | |||
public ConfigForm(ShadowsocksController controller) | |||
{ | |||
Font = SystemFonts.MessageBoxFont; | |||
InitializeComponent(); | |||
EncryptionSelect.Items.AddRange(EncryptionMethod.AllMethods); | |||
EncryptionSelect.Items.AddRange(EncryptorFactory.ListAvaliableCiphers().ToArray()); | |||
// a dirty hack | |||
ServersListBox.Dock = DockStyle.Fill; | |||
@@ -205,7 +119,7 @@ namespace Shadowsocks.View | |||
server = address, | |||
server_port = addressPort.Value, | |||
password = serverPassword, | |||
method = ((EncryptionMethod)EncryptionSelect.SelectedItem).name, | |||
method = ((CipherInfo)EncryptionSelect.SelectedItem).Name, | |||
plugin = PluginTextBox.Text, | |||
plugin_opts = PluginOptionsTextBox.Text, | |||
plugin_args = PluginArgumentsTextBox.Text, | |||
@@ -406,7 +320,7 @@ namespace Shadowsocks.View | |||
IPTextBox.Text = server.server; | |||
ServerPortTextBox.Text = server.server_port.ToString(); | |||
PasswordTextBox.Text = server.password; | |||
EncryptionSelect.SelectedItem = EncryptionMethod.GetMethod(server.method ?? Server.DefaultMethod); | |||
EncryptionSelect.SelectedItem = EncryptorFactory.GetCipherInfo(server.method ?? Server.DefaultMethod); | |||
PluginTextBox.Text = server.plugin; | |||
PluginOptionsTextBox.Text = server.plugin_opts; | |||
PluginArgumentsTextBox.Text = server.plugin_args; | |||
@@ -5,8 +5,13 @@ | |||
<package id="Costura.Fody" version="3.3.3" targetFramework="net472" /> | |||
<package id="Fody" version="4.2.1" targetFramework="net472" developmentDependency="true" /> | |||
<package id="GlobalHotKey" version="1.1.0" targetFramework="net472" /> | |||
<package id="NaCl.Core" version="1.2.0" targetFramework="net472" /> | |||
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net472" /> | |||
<package id="NLog" version="4.6.8" targetFramework="net472" /> | |||
<package id="StringEx.CS" version="0.3.1" targetFramework="net472" developmentDependency="true" /> | |||
<package id="System.Buffers" version="4.4.0" targetFramework="net472" /> | |||
<package id="System.Memory" version="4.5.2" targetFramework="net472" /> | |||
<package id="System.Numerics.Vectors" version="4.4.0" targetFramework="net472" /> | |||
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net472" /> | |||
<package id="ZXing.Net" version="0.16.5" targetFramework="net472" /> | |||
</packages> |
@@ -11,7 +11,12 @@ | |||
<Version>4.1.9.2</Version> | |||
<AssemblyName>Shadowsocks</AssemblyName> | |||
<ApplicationIcon>shadowsocks.ico</ApplicationIcon> | |||
<StartupObject /> | |||
<StartupObject>Shadowsocks.Program</StartupObject> | |||
<Nullable>enable</Nullable> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> | |||
<DefineConstants>TRACE</DefineConstants> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
@@ -58,6 +63,7 @@ | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="BouncyCastle.NetCore" Version="1.8.5" /> | |||
<!--TODO: Remove Fody related stuff, as they're either not actually used or has NET Core built-in alternate--> | |||
<PackageReference Include="Caseless.Fody" Version="1.9.0" /> | |||
<PackageReference Include="Costura.Fody" Version="4.1.0" /> | |||
<PackageReference Include="Fody" Version="6.1.1"> | |||
@@ -66,16 +72,17 @@ | |||
</PackageReference> | |||
<PackageReference Include="Microsoft.VisualBasic" Version="10.3.0" /> | |||
<PackageReference Include="NaCl.Core" Version="1.2.0" /> | |||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> | |||
<PackageReference Include="NLog" Version="4.6.8" /> | |||
<PackageReference Include="System.Data.SqlClient" Version="4.8.1" /> | |||
<PackageReference Include="ZXing.Net" Version="0.16.5" /> | |||
<PackageReference Include="StringEx.CS" Version="0.3.1"> | |||
<PrivateAssets>all</PrivateAssets> | |||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||
</PackageReference> | |||
<PackageReference Include="System.Data.SqlClient" Version="4.8.1" /> | |||
<PackageReference Include="System.Management" Version="4.7.0" /> | |||
<PackageReference Include="ZXing.Net" Version="0.16.5" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
@@ -1,6 +1,7 @@ | |||
using Microsoft.VisualStudio.TestTools.UnitTesting; | |||
using Shadowsocks.Encryption; | |||
using Shadowsocks.Encryption.Stream; | |||
using Shadowsocks.Encryption.AEAD; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading; | |||
@@ -10,7 +11,7 @@ namespace Shadowsocks.Test | |||
[TestClass] | |||
public class CryptographyTest | |||
{ | |||
Random random = new Random(); | |||
[TestMethod] | |||
public void TestMD5() | |||
{ | |||
@@ -18,7 +19,6 @@ namespace Shadowsocks.Test | |||
{ | |||
System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create(); | |||
byte[] bytes = new byte[len]; | |||
var random = new Random(); | |||
random.NextBytes(bytes); | |||
string md5str = Convert.ToBase64String(md5.ComputeHash(bytes)); | |||
string md5str2 = Convert.ToBase64String(CryptoUtils.MD5(bytes)); | |||
@@ -32,12 +32,12 @@ namespace Shadowsocks.Test | |||
byte[] plain = new byte[16384]; | |||
byte[] cipher = new byte[plain.Length + 100];// AEAD with IPv4 address type needs +100 | |||
byte[] plain2 = new byte[plain.Length + 16]; | |||
int outLen = 0; | |||
int outLen2 = 0; | |||
var random = new Random(); | |||
//random = new Random(); | |||
random.NextBytes(plain); | |||
encryptor.Encrypt(plain, plain.Length, cipher, out outLen); | |||
decryptor.Decrypt(cipher, outLen, plain2, out outLen2); | |||
encryptor.Encrypt(plain, plain.Length, cipher, out int outLen); | |||
decryptor.Decrypt(cipher, outLen, plain2, out int outLen2); | |||
Assert.AreEqual(plain.Length, outLen2); | |||
for (int j = 0; j < plain.Length; j++) | |||
{ | |||
@@ -59,40 +59,48 @@ namespace Shadowsocks.Test | |||
} | |||
} | |||
private static bool encryptionFailed = false; | |||
private static object locker = new object(); | |||
const string password = "barfoo!"; | |||
[TestMethod] | |||
public void TestBouncyCastleAEADEncryption() | |||
private void RunSingleEncryptionThread(Type enc, Type dec, string method) | |||
{ | |||
encryptionFailed = false; | |||
// run it once before the multi-threading test to initialize global tables | |||
RunSingleBouncyCastleAEADEncryptionThread(); | |||
List<Thread> threads = new List<Thread>(); | |||
for (int i = 0; i < 10; i++) | |||
var ector = enc.GetConstructor(new Type[] { typeof(string), typeof(string) }); | |||
var dctor = dec.GetConstructor(new Type[] { typeof(string), typeof(string) }); | |||
try | |||
{ | |||
Thread t = new Thread(new ThreadStart(RunSingleBouncyCastleAEADEncryptionThread)); threads.Add(t); | |||
t.Start(); | |||
IEncryptor encryptor = (IEncryptor)ector.Invoke(new object[] { method, password }); | |||
IEncryptor decryptor = (IEncryptor)dctor.Invoke(new object[] { method, password }); | |||
encryptor.AddressBufferLength = 1 + 4 + 2;// ADDR_ATYP_LEN + 4 + ADDR_PORT_LEN; | |||
decryptor.AddressBufferLength = 1 + 4 + 2;// ADDR_ATYP_LEN + 4 + ADDR_PORT_LEN; | |||
for (int i = 0; i < 16; i++) | |||
{ | |||
RunEncryptionRound(encryptor, decryptor); | |||
} | |||
} | |||
foreach (Thread t in threads) | |||
catch | |||
{ | |||
t.Join(); | |||
encryptionFailed = true; | |||
throw; | |||
} | |||
RNG.Close(); | |||
Assert.IsFalse(encryptionFailed); | |||
} | |||
[TestMethod] | |||
public void TestNativeEncryption() | |||
private static bool encryptionFailed = false; | |||
private void TestEncryptionMethod(Type enc, string method) | |||
{ | |||
TestEncryptionMethod(enc, enc, method); | |||
} | |||
private void TestEncryptionMethod(Type enc, Type dec, string method) | |||
{ | |||
encryptionFailed = false; | |||
// run it once before the multi-threading test to initialize global tables | |||
RunSingleNativeEncryptionThread(); | |||
RunSingleEncryptionThread(enc, dec, method); | |||
List<Thread> threads = new List<Thread>(); | |||
for (int i = 0; i < 10; i++) | |||
for (int i = 0; i < 8; i++) | |||
{ | |||
Thread t = new Thread(new ThreadStart(RunSingleNativeEncryptionThread)); | |||
threads.Add(t); | |||
Thread t = new Thread(new ThreadStart(() => RunSingleEncryptionThread(enc, dec, method))); threads.Add(t); | |||
t.Start(); | |||
} | |||
foreach (Thread t in threads) | |||
@@ -103,47 +111,44 @@ namespace Shadowsocks.Test | |||
Assert.IsFalse(encryptionFailed); | |||
} | |||
private void RunSingleNativeEncryptionThread() | |||
[TestMethod] | |||
public void TestBouncyCastleAEADEncryption() | |||
{ | |||
try | |||
{ | |||
for (int i = 0; i < 100; i++) | |||
{ | |||
IEncryptor encryptorN; | |||
IEncryptor decryptorN; | |||
encryptorN = new StreamNativeEncryptor("rc4-md5", "barfoo!"); | |||
decryptorN = new StreamNativeEncryptor("rc4-md5", "barfoo!"); | |||
RunEncryptionRound(encryptorN, decryptorN); | |||
} | |||
} | |||
catch | |||
{ | |||
encryptionFailed = true; | |||
throw; | |||
} | |||
TestEncryptionMethod(typeof(AEADBouncyCastleEncryptor), "aes-128-gcm"); | |||
TestEncryptionMethod(typeof(AEADBouncyCastleEncryptor), "aes-192-gcm"); | |||
TestEncryptionMethod(typeof(AEADBouncyCastleEncryptor), "aes-256-gcm"); | |||
} | |||
private void RunSingleBouncyCastleAEADEncryptionThread() | |||
[TestMethod] | |||
public void TestAesGcmNativeAEADEncryption() | |||
{ | |||
try | |||
{ | |||
for (int i = 0; i < 100; i++) | |||
{ | |||
var random = new Random(); | |||
IEncryptor encryptor; | |||
IEncryptor decryptor; | |||
encryptor = new Encryption.AEAD.AEADBouncyCastleEncryptor("aes-256-gcm", "barfoo!"); | |||
encryptor.AddrBufLength = 1 + 4 + 2;// ADDR_ATYP_LEN + 4 + ADDR_PORT_LEN; | |||
decryptor = new Encryption.AEAD.AEADBouncyCastleEncryptor("aes-256-gcm", "barfoo!"); | |||
decryptor.AddrBufLength = 1 + 4 + 2;// ADDR_ATYP_LEN + 4 + ADDR_PORT_LEN; | |||
RunEncryptionRound(encryptor, decryptor); | |||
} | |||
} | |||
catch | |||
{ | |||
encryptionFailed = true; | |||
throw; | |||
} | |||
TestEncryptionMethod(typeof(AEADAesGcmNativeEncryptor), "aes-128-gcm"); | |||
TestEncryptionMethod(typeof(AEADAesGcmNativeEncryptor), "aes-192-gcm"); | |||
TestEncryptionMethod(typeof(AEADAesGcmNativeEncryptor), "aes-256-gcm"); | |||
} | |||
[TestMethod] | |||
public void TestNaClAEADEncryption() | |||
{ | |||
TestEncryptionMethod(typeof(AEADNaClEncryptor), "chacha20-ietf-poly1305"); | |||
TestEncryptionMethod(typeof(AEADNaClEncryptor), "xchacha20-ietf-poly1305"); | |||
} | |||
[TestMethod] | |||
public void TestNativeEncryption() | |||
{ | |||
TestEncryptionMethod(typeof(StreamTableNativeEncryptor), "plain"); | |||
TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4"); | |||
TestEncryptionMethod(typeof(StreamRc4NativeEncryptor), "rc4-md5"); | |||
} | |||
[TestMethod] | |||
public void TestNativeTableEncryption() | |||
{ | |||
// Too slow, run once to save CPU | |||
var enc = new StreamTableNativeEncryptor("table", "barfoo!"); | |||
var dec = new StreamTableNativeEncryptor("table", "barfoo!"); | |||
RunEncryptionRound(enc, dec); | |||
} | |||
} | |||
} |
@@ -6,6 +6,10 @@ | |||
<IsPackable>false</IsPackable> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> | |||
<WarningLevel>0</WarningLevel> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="GlobalHotKeyCore" Version="1.2.0" /> | |||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> | |||