@@ -39,6 +39,9 @@ namespace Shadowsocks.Controller | |||||
public void CheckUpdate(Configuration config, int delay) | public void CheckUpdate(Configuration config, int delay) | ||||
{ | { | ||||
#if DEBUG | |||||
return; | |||||
#endif | |||||
CheckUpdateTimer timer = new CheckUpdateTimer(delay); | CheckUpdateTimer timer = new CheckUpdateTimer(delay); | ||||
timer.AutoReset = false; | timer.AutoReset = false; | ||||
timer.Elapsed += Timer_Elapsed; | timer.Elapsed += Timer_Elapsed; | ||||
@@ -58,6 +61,10 @@ namespace Shadowsocks.Controller | |||||
public void CheckUpdate(Configuration config) | public void CheckUpdate(Configuration config) | ||||
{ | { | ||||
#if DEBUG | |||||
return; | |||||
#endif | |||||
this.config = config; | this.config = config; | ||||
try | try | ||||
@@ -24,8 +24,10 @@ namespace Shadowsocks.Encryption | |||||
public enum CipherStandardState | public enum CipherStandardState | ||||
{ | { | ||||
InUse, | InUse, | ||||
Deprecated, | |||||
Hidden, | |||||
Deprecated, // popup warning when updated | |||||
Hidden, // enabled by hidden flag in config file | |||||
Unstable, // not in standard list or wip, only gui info | |||||
} | } | ||||
public class CipherParameter | public class CipherParameter | ||||
@@ -55,11 +57,12 @@ namespace Shadowsocks.Encryption | |||||
public CipherStandardState StandardState = CipherStandardState.InUse; | public CipherStandardState StandardState = CipherStandardState.InUse; | ||||
#region Stream ciphers | #region Stream ciphers | ||||
public CipherInfo(string name, int keySize, int ivSize, CipherFamily type) | |||||
public CipherInfo(string name, int keySize, int ivSize, CipherFamily type, CipherStandardState state = CipherStandardState.Hidden) | |||||
{ | { | ||||
Type = type; | Type = type; | ||||
Name = name; | Name = name; | ||||
StandardState = CipherStandardState.Hidden; | |||||
StandardState = state; | |||||
CipherParameter = new StreamCipherParameter | CipherParameter = new StreamCipherParameter | ||||
{ | { | ||||
KeySize = keySize, | KeySize = keySize, | ||||
@@ -70,10 +73,11 @@ namespace Shadowsocks.Encryption | |||||
#endregion | #endregion | ||||
#region AEAD ciphers | #region AEAD ciphers | ||||
public CipherInfo(string name, int keySize, int saltSize, int nonceSize, int tagSize, CipherFamily type) | |||||
public CipherInfo(string name, int keySize, int saltSize, int nonceSize, int tagSize, CipherFamily type, CipherStandardState state = CipherStandardState.InUse) | |||||
{ | { | ||||
Type = type; | Type = type; | ||||
Name = name; | Name = name; | ||||
StandardState = state; | |||||
CipherParameter = new AEADCipherParameter | CipherParameter = new AEADCipherParameter | ||||
{ | { | ||||
@@ -87,7 +91,7 @@ namespace Shadowsocks.Encryption | |||||
public override string ToString() | public override string ToString() | ||||
{ | { | ||||
return StandardState == CipherStandardState.InUse ? Name : $"{Name} ({I18N.GetString("deprecated")})"; | |||||
return StandardState == CipherStandardState.InUse ? Name : $"{Name} ({I18N.GetString(StandardState.ToString().ToLower())})"; | |||||
} | } | ||||
public string ToString(bool verbose) | public string ToString(bool verbose) | ||||
{ | { | ||||
@@ -2,6 +2,8 @@ | |||||
{ | { | ||||
public abstract class EncryptorBase : IEncryptor | public abstract class EncryptorBase : IEncryptor | ||||
{ | { | ||||
private static int _currentId = 0; | |||||
public const int MAX_INPUT_SIZE = 32768; | public const int MAX_INPUT_SIZE = 32768; | ||||
public const int MAX_DOMAIN_LEN = 255; | public const int MAX_DOMAIN_LEN = 255; | ||||
@@ -14,15 +16,21 @@ | |||||
public const int MD5_LEN = 16; | public const int MD5_LEN = 16; | ||||
// for debugging only, give it a number to trace data stream | |||||
public readonly int instanceId; | |||||
protected EncryptorBase(string method, string password) | protected EncryptorBase(string method, string password) | ||||
{ | { | ||||
instanceId = _currentId; | |||||
_currentId++; | |||||
Method = method; | Method = method; | ||||
Password = password; | Password = password; | ||||
} | } | ||||
protected string Method; | protected string Method; | ||||
protected string Password; | protected string Password; | ||||
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); | ||||
@@ -31,6 +39,11 @@ | |||||
public abstract void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength); | public abstract void DecryptUDP(byte[] buf, int length, byte[] outbuf, out int outlength); | ||||
public override string ToString() | |||||
{ | |||||
return $"{instanceId}({Method},{Password})"; | |||||
} | |||||
public int AddressBufferLength { get; set; } = -1; | public int AddressBufferLength { get; set; } = -1; | ||||
} | } | ||||
} | } |
@@ -33,6 +33,15 @@ namespace Shadowsocks.Encryption | |||||
_registeredEncryptors.Add(method.Key, typeof(StreamRc4NativeEncryptor)); | _registeredEncryptors.Add(method.Key, typeof(StreamRc4NativeEncryptor)); | ||||
} | } | ||||
} | } | ||||
foreach (var method in StreamAesBouncyCastleEncryptor.SupportedCiphers()) | |||||
{ | |||||
if (!_registeredEncryptors.ContainsKey(method.Key)) | |||||
{ | |||||
ciphers.Add(method.Key, method.Value); | |||||
_registeredEncryptors.Add(method.Key, typeof(StreamAesBouncyCastleEncryptor)); | |||||
} | |||||
} | |||||
foreach (var method in AEADAesGcmNativeEncryptor.SupportedCiphers()) | foreach (var method in AEADAesGcmNativeEncryptor.SupportedCiphers()) | ||||
{ | { | ||||
@@ -66,7 +75,8 @@ namespace Shadowsocks.Encryption | |||||
t = _registeredEncryptors[DefaultCipher]; | t = _registeredEncryptors[DefaultCipher]; | ||||
} | } | ||||
ConstructorInfo c = t.GetConstructor(ConstructorTypes); | |||||
ConstructorInfo c = t?.GetConstructor(ConstructorTypes) ?? | |||||
throw new TypeLoadException("can't load constructor"); | |||||
if (c == null) throw new System.Exception("Invalid ctor"); | if (c == null) throw new System.Exception("Invalid ctor"); | ||||
IEncryptor result = (IEncryptor)c.Invoke(new object[] { method, password }); | IEncryptor result = (IEncryptor)c.Invoke(new object[] { method, password }); | ||||
return result; | return result; | ||||
@@ -0,0 +1,77 @@ | |||||
using Org.BouncyCastle.Crypto; | |||||
using Org.BouncyCastle.Crypto.Engines; | |||||
using Org.BouncyCastle.Crypto.Modes; | |||||
using Org.BouncyCastle.Crypto.Parameters; | |||||
using Org.BouncyCastle.Security; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
namespace Shadowsocks.Encryption.Stream | |||||
{ | |||||
public class StreamAesBouncyCastleEncryptor : StreamEncryptor | |||||
{ | |||||
IBufferedCipher c; | |||||
public StreamAesBouncyCastleEncryptor(string method, string password) : base(method, password) | |||||
{ | |||||
c = new BufferedBlockCipher(new CfbBlockCipher(new AesEngine(), 128)); | |||||
// c = CipherUtilities.GetCipher("AES/CFB/NoPadding"); | |||||
} | |||||
protected override void initCipher(byte[] iv, bool isEncrypt) | |||||
{ | |||||
base.initCipher(iv, isEncrypt); | |||||
c.Init(isEncrypt, new ParametersWithIV(new KeyParameter(key), iv)); | |||||
} | |||||
protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) | |||||
{ | |||||
var i = buf.AsSpan().Slice(0, length); | |||||
if (isEncrypt) CipherEncrypt(i, outbuf); | |||||
else CipherDecrypt(outbuf, i); | |||||
} | |||||
protected override int CipherEncrypt(Span<byte> plain, Span<byte> cipher) | |||||
{ | |||||
CipherUpdate(plain, cipher); | |||||
return plain.Length; | |||||
} | |||||
protected override int CipherDecrypt(Span<byte> plain, Span<byte> cipher) | |||||
{ | |||||
CipherUpdate(cipher, plain); | |||||
return cipher.Length; | |||||
} | |||||
private void CipherUpdate(Span<byte> i, Span<byte> o) | |||||
{ | |||||
// c.Reset(); | |||||
var ob = new byte[o.Length]; | |||||
int blklen = c.ProcessBytes(i.ToArray(), 0, i.Length, ob, 0); | |||||
int restlen = i.Length - blklen; | |||||
if (restlen != 0) | |||||
{ | |||||
// may be problem, c is block cipher? | |||||
c.DoFinal(ob, blklen); | |||||
} | |||||
ob.CopyTo(o); | |||||
} | |||||
#region Ciphers | |||||
private static readonly Dictionary<string, CipherInfo> _ciphers = new Dictionary<string, CipherInfo> | |||||
{ | |||||
{"aes-256-cfb",new CipherInfo("aes-256-cfb", 32, 16, CipherFamily.AesCfb, CipherStandardState.Unstable)}, | |||||
}; | |||||
public static Dictionary<string, CipherInfo> SupportedCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
protected override Dictionary<string, CipherInfo> getCiphers() | |||||
{ | |||||
return _ciphers; | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@@ -4,11 +4,13 @@ using System.Diagnostics; | |||||
using System.Text; | using System.Text; | ||||
using Shadowsocks.Encryption.CircularBuffer; | using Shadowsocks.Encryption.CircularBuffer; | ||||
using Shadowsocks.Controller; | using Shadowsocks.Controller; | ||||
using NLog; | |||||
namespace Shadowsocks.Encryption.Stream | namespace Shadowsocks.Encryption.Stream | ||||
{ | { | ||||
public abstract class StreamEncryptor : EncryptorBase | public abstract class StreamEncryptor : EncryptorBase | ||||
{ | { | ||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||||
// for UDP only | // for UDP only | ||||
protected static byte[] _udpTmpBuf = new byte[65536]; | protected static byte[] _udpTmpBuf = new byte[65536]; | ||||
@@ -115,6 +117,7 @@ namespace Shadowsocks.Encryption.Stream | |||||
byte[] plain = buffer.Get(size); | byte[] plain = buffer.Get(size); | ||||
byte[] cipher = new byte[size]; | byte[] cipher = new byte[size]; | ||||
cipherUpdate(true, size, plain, cipher); | cipherUpdate(true, size, plain, cipher); | ||||
Buffer.BlockCopy(cipher, 0, outbuf, cipherOffset, size); | Buffer.BlockCopy(cipher, 0, outbuf, cipherOffset, size); | ||||
outlength = size + cipherOffset; | outlength = size + cipherOffset; | ||||
} | } | ||||
@@ -12,6 +12,36 @@ namespace Shadowsocks.Test | |||||
public class CryptographyTest | public class CryptographyTest | ||||
{ | { | ||||
Random random = new Random(); | Random random = new Random(); | ||||
private void ArrayEqual<T>(IEnumerable<T> expected, IEnumerable<T> actual, string msg = "") | |||||
{ | |||||
var e1 = expected.GetEnumerator(); | |||||
var e2 = actual.GetEnumerator(); | |||||
int ctr = 0; | |||||
while (true) | |||||
{ | |||||
var e1next = e1.MoveNext(); | |||||
var e2next = e2.MoveNext(); | |||||
if (e1next && e2next) | |||||
{ | |||||
Assert.AreEqual(e1.Current, e2.Current, "at " + ctr); | |||||
} | |||||
else if (!e1next && !e2next) | |||||
{ | |||||
return; | |||||
} | |||||
else if (!e1next) | |||||
{ | |||||
Assert.Fail($"actual longer than expected ({ctr}) {msg}"); | |||||
} | |||||
else | |||||
{ | |||||
Assert.Fail($"actual shorter than expected ({ctr}) {msg}"); | |||||
} | |||||
} | |||||
} | |||||
[TestMethod] | [TestMethod] | ||||
public void TestMD5() | public void TestMD5() | ||||
{ | { | ||||
@@ -26,37 +56,26 @@ namespace Shadowsocks.Test | |||||
} | } | ||||
} | } | ||||
private void RunEncryptionRound(IEncryptor encryptor, IEncryptor decryptor) | |||||
private void SingleEncryptionTestCase(IEncryptor encryptor, IEncryptor decryptor, int length) | |||||
{ | { | ||||
RNG.Reload(); | RNG.Reload(); | ||||
byte[] plain = new byte[16384]; | |||||
byte[] plain = new byte[length]; | |||||
byte[] cipher = new byte[plain.Length + 100];// AEAD with IPv4 address type needs +100 | byte[] cipher = new byte[plain.Length + 100];// AEAD with IPv4 address type needs +100 | ||||
byte[] plain2 = new byte[plain.Length + 16]; | byte[] plain2 = new byte[plain.Length + 16]; | ||||
//random = new Random(); | |||||
random.NextBytes(plain); | random.NextBytes(plain); | ||||
encryptor.Encrypt(plain, plain.Length, cipher, out int outLen); | |||||
encryptor.Encrypt(plain, length, cipher, out int outLen); | |||||
decryptor.Decrypt(cipher, outLen, plain2, out int outLen2); | decryptor.Decrypt(cipher, outLen, plain2, out int outLen2); | ||||
Assert.AreEqual(plain.Length, outLen2); | |||||
for (int j = 0; j < plain.Length; j++) | |||||
{ | |||||
Assert.AreEqual(plain[j], plain2[j]); | |||||
} | |||||
encryptor.Encrypt(plain, 1000, cipher, out outLen); | |||||
decryptor.Decrypt(cipher, outLen, plain2, out outLen2); | |||||
Assert.AreEqual(1000, outLen2); | |||||
for (int j = 0; j < outLen2; j++) | |||||
{ | |||||
Assert.AreEqual(plain[j], plain2[j]); | |||||
} | |||||
encryptor.Encrypt(plain, 12333, cipher, out outLen); | |||||
decryptor.Decrypt(cipher, outLen, plain2, out outLen2); | |||||
Assert.AreEqual(12333, outLen2); | |||||
for (int j = 0; j < outLen2; j++) | |||||
{ | |||||
Assert.AreEqual(plain[j], plain2[j]); | |||||
} | |||||
Assert.AreEqual(length, outLen2); | |||||
ArrayEqual<byte>(plain.AsSpan().Slice(0, length).ToArray(), plain2.AsSpan().Slice(0, length).ToArray()); | |||||
} | |||||
private void RunEncryptionRound(IEncryptor encryptor, IEncryptor decryptor) | |||||
{ | |||||
SingleEncryptionTestCase(encryptor, decryptor, 16384); | |||||
SingleEncryptionTestCase(encryptor, decryptor, 7); | |||||
SingleEncryptionTestCase(encryptor, decryptor, 1000); | |||||
SingleEncryptionTestCase(encryptor, decryptor, 12333); | |||||
} | } | ||||
const string password = "barfoo!"; | const string password = "barfoo!"; | ||||
@@ -141,5 +160,10 @@ namespace Shadowsocks.Test | |||||
var dec = new StreamTableNativeEncryptor("table", "barfoo!"); | var dec = new StreamTableNativeEncryptor("table", "barfoo!"); | ||||
RunEncryptionRound(enc, dec); | RunEncryptionRound(enc, dec); | ||||
} | } | ||||
[TestMethod] | |||||
public void TestStreamAesBouncyCastleEncryption() | |||||
{ | |||||
TestEncryptionMethod(typeof(StreamAesBouncyCastleEncryptor), "aes-256-cfb"); | |||||
} | |||||
} | } | ||||
} | } |