@@ -39,6 +39,9 @@ namespace Shadowsocks.Controller | |||
public void CheckUpdate(Configuration config, int delay) | |||
{ | |||
#if DEBUG | |||
return; | |||
#endif | |||
CheckUpdateTimer timer = new CheckUpdateTimer(delay); | |||
timer.AutoReset = false; | |||
timer.Elapsed += Timer_Elapsed; | |||
@@ -58,6 +61,10 @@ namespace Shadowsocks.Controller | |||
public void CheckUpdate(Configuration config) | |||
{ | |||
#if DEBUG | |||
return; | |||
#endif | |||
this.config = config; | |||
try | |||
@@ -24,8 +24,10 @@ namespace Shadowsocks.Encryption | |||
public enum CipherStandardState | |||
{ | |||
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 | |||
@@ -55,11 +57,12 @@ namespace Shadowsocks.Encryption | |||
public CipherStandardState StandardState = CipherStandardState.InUse; | |||
#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; | |||
Name = name; | |||
StandardState = CipherStandardState.Hidden; | |||
StandardState = state; | |||
CipherParameter = new StreamCipherParameter | |||
{ | |||
KeySize = keySize, | |||
@@ -70,10 +73,11 @@ namespace Shadowsocks.Encryption | |||
#endregion | |||
#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; | |||
Name = name; | |||
StandardState = state; | |||
CipherParameter = new AEADCipherParameter | |||
{ | |||
@@ -87,7 +91,7 @@ namespace Shadowsocks.Encryption | |||
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) | |||
{ | |||
@@ -2,6 +2,8 @@ | |||
{ | |||
public abstract class EncryptorBase : IEncryptor | |||
{ | |||
private static int _currentId = 0; | |||
public const int MAX_INPUT_SIZE = 32768; | |||
public const int MAX_DOMAIN_LEN = 255; | |||
@@ -14,15 +16,21 @@ | |||
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) | |||
{ | |||
instanceId = _currentId; | |||
_currentId++; | |||
Method = method; | |||
Password = password; | |||
} | |||
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); | |||
@@ -31,6 +39,11 @@ | |||
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; | |||
} | |||
} |
@@ -33,6 +33,15 @@ namespace Shadowsocks.Encryption | |||
_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()) | |||
{ | |||
@@ -66,7 +75,8 @@ namespace Shadowsocks.Encryption | |||
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"); | |||
IEncryptor result = (IEncryptor)c.Invoke(new object[] { method, password }); | |||
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 Shadowsocks.Encryption.CircularBuffer; | |||
using Shadowsocks.Controller; | |||
using NLog; | |||
namespace Shadowsocks.Encryption.Stream | |||
{ | |||
public abstract class StreamEncryptor : EncryptorBase | |||
{ | |||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||
// for UDP only | |||
protected static byte[] _udpTmpBuf = new byte[65536]; | |||
@@ -115,6 +117,7 @@ namespace Shadowsocks.Encryption.Stream | |||
byte[] plain = buffer.Get(size); | |||
byte[] cipher = new byte[size]; | |||
cipherUpdate(true, size, plain, cipher); | |||
Buffer.BlockCopy(cipher, 0, outbuf, cipherOffset, size); | |||
outlength = size + cipherOffset; | |||
} | |||
@@ -12,6 +12,36 @@ namespace Shadowsocks.Test | |||
public class CryptographyTest | |||
{ | |||
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] | |||
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(); | |||
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[] plain2 = new byte[plain.Length + 16]; | |||
//random = new Random(); | |||
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); | |||
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!"; | |||
@@ -141,5 +160,10 @@ namespace Shadowsocks.Test | |||
var dec = new StreamTableNativeEncryptor("table", "barfoo!"); | |||
RunEncryptionRound(enc, dec); | |||
} | |||
[TestMethod] | |||
public void TestStreamAesBouncyCastleEncryption() | |||
{ | |||
TestEncryptionMethod(typeof(StreamAesBouncyCastleEncryptor), "aes-256-cfb"); | |||
} | |||
} | |||
} |