diff --git a/OPENSSL-GUIDE b/OPENSSL-GUIDE new file mode 100644 index 000000000..42eaa8fee --- /dev/null +++ b/OPENSSL-GUIDE @@ -0,0 +1,15 @@ +OpenSSL library guide for VS2017 + +# Read NOTES.WIN and NOTES.PERL + +# use Visual Studio native tools command prompt +# use activeperl, install NASM assembler +ppm install dmake + +# Win32 x86 +set PATH=D:\NASM-32;%PATH% +perl Configure VC-WIN32 --release --prefix=C:\Users\home\Downloads\openssl-1.1.0g\x86-build --openssldir=C:\Users\home\Downloads\openssl-1.1.0g\x86-install +nmake +nmake test +# to rebuild +nmake distclean diff --git a/shadowsocks-csharp/Controller/Service/Listener.cs b/shadowsocks-csharp/Controller/Service/Listener.cs index 27f29d9d3..1d70ea773 100644 --- a/shadowsocks-csharp/Controller/Service/Listener.cs +++ b/shadowsocks-csharp/Controller/Service/Listener.cs @@ -85,6 +85,7 @@ public void Start(Configuration config) // Start an asynchronous socket to listen for connections. Logging.Info("Shadowsocks started"); + Logging.Info(Encryption.EncryptorFactory.DumpRegisteredEncryptor()); _tcpSocket.BeginAccept(new AsyncCallback(AcceptCallback), _tcpSocket); UDPState udpState = new UDPState(); udpState.socket = _udpSocket; diff --git a/shadowsocks-csharp/Data/libcrypto-1_1.dll.gz b/shadowsocks-csharp/Data/libcrypto-1_1.dll.gz new file mode 100644 index 000000000..ed19e1d1c Binary files /dev/null and b/shadowsocks-csharp/Data/libcrypto-1_1.dll.gz differ diff --git a/shadowsocks-csharp/Encryption/AEAD/AEADEncryptor.cs b/shadowsocks-csharp/Encryption/AEAD/AEADEncryptor.cs index 08464db07..a4e42eae7 100644 --- a/shadowsocks-csharp/Encryption/AEAD/AEADEncryptor.cs +++ b/shadowsocks-csharp/Encryption/AEAD/AEADEncryptor.cs @@ -127,9 +127,9 @@ public virtual void InitCipher(byte[] salt, bool isEncrypt, bool isUdp) public static void randBytes(byte[] buf, int length) { RNG.GetBytes(buf, length); } - public abstract int cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen); + public abstract void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen); - public abstract int cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen); + public abstract void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen); #region TCP diff --git a/shadowsocks-csharp/Encryption/AEAD/AEADMbedTLSEncryptor.cs b/shadowsocks-csharp/Encryption/AEAD/AEADMbedTLSEncryptor.cs index c445c691a..2f73f9e4a 100644 --- a/shadowsocks-csharp/Encryption/AEAD/AEADMbedTLSEncryptor.cs +++ b/shadowsocks-csharp/Encryption/AEAD/AEADMbedTLSEncryptor.cs @@ -19,7 +19,7 @@ public AEADMbedTLSEncryptor(string method, string password) { } - private static Dictionary _ciphers = new Dictionary + private static readonly Dictionary _ciphers = new Dictionary { {"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)}, @@ -48,6 +48,7 @@ public override void InitCipher(byte[] salt, bool isEncrypt, bool isUdp) { _decryptCtx = ctx; } + MbedTLS.cipher_init(ctx); if (MbedTLS.cipher_setup(ctx, MbedTLS.cipher_info_from_string(_innerLibName)) != 0) throw new System.Exception("Cannot initialize mbed TLS cipher context"); @@ -67,7 +68,7 @@ private void CipherSetKey(bool isEncrypt, byte[] key) if (ret != 0) throw new System.Exception("failed to finish preparation"); } - public override int cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) + public override void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) { // buf: all plaintext // outbuf: ciphertext + tag @@ -87,18 +88,18 @@ public override int cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext /* cipher */ ciphertext, ref olen, tagbuf, (uint) tagLen); - if (ret != 0) throw new CryptoErrorException(); + if (ret != 0) throw new CryptoErrorException(String.Format("ret is {0}", ret)); Debug.Assert(olen == plen); // attach tag to ciphertext Array.Copy(tagbuf, 0, ciphertext, (int) plen, tagLen); clen = olen + (uint) tagLen; - return ret; + break; default: throw new System.Exception("not implemented"); } } - public override int cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) + public override void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) { // buf: ciphertext + tag // outbuf: plaintext @@ -116,10 +117,10 @@ public override int cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext ciphertext, (uint) (clen - tagLen), plaintext, ref olen, tagbuf, (uint) tagLen); - if (ret != 0) throw new CryptoErrorException(); + if (ret != 0) throw new CryptoErrorException(String.Format("ret is {0}", ret)); Debug.Assert(olen == clen - tagLen); plen = olen; - return ret; + break; default: throw new System.Exception("not implemented"); } @@ -163,6 +164,7 @@ protected virtual void Dispose(bool disposing) Marshal.FreeHGlobal(_encryptCtx); _encryptCtx = IntPtr.Zero; } + if (_decryptCtx != IntPtr.Zero) { MbedTLS.cipher_free(_decryptCtx); diff --git a/shadowsocks-csharp/Encryption/AEAD/AEADOpenSSLEncryptor.cs b/shadowsocks-csharp/Encryption/AEAD/AEADOpenSSLEncryptor.cs new file mode 100644 index 000000000..e122e4b8c --- /dev/null +++ b/shadowsocks-csharp/Encryption/AEAD/AEADOpenSSLEncryptor.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using Shadowsocks.Encryption.Exception; + +namespace Shadowsocks.Encryption.AEAD +{ + public class AEADOpenSSLEncryptor + : AEADEncryptor, IDisposable + { + const int CIPHER_AES = 1; + const int CIPHER_CHACHA20IETFPOLY1305 = 2; + + private byte[] _opensslEncSubkey; + private byte[] _opensslDecSubkey; + + private IntPtr _encryptCtx = IntPtr.Zero; + private IntPtr _decryptCtx = IntPtr.Zero; + + private IntPtr _cipherInfoPtr = IntPtr.Zero; + + public AEADOpenSSLEncryptor(string method, string password) + : base(method, password) + { + _opensslEncSubkey = new byte[keyLen]; + _opensslDecSubkey = new byte[keyLen]; + } + + private static readonly Dictionary _ciphers = new Dictionary + { + {"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)}, + {"chacha20-ietf-poly1305", new EncryptorInfo("chacha20-poly1305", 32, 32, 12, 16, CIPHER_CHACHA20IETFPOLY1305)} + }; + + public static List SupportedCiphers() + { + return new List(_ciphers.Keys); + } + + protected override Dictionary getCiphers() + { + return _ciphers; + } + + public override void InitCipher(byte[] salt, bool isEncrypt, bool isUdp) + { + base.InitCipher(salt, isEncrypt, isUdp); + _cipherInfoPtr = OpenSSL.GetCipherInfo(_innerLibName); + if (_cipherInfoPtr == IntPtr.Zero) throw new System.Exception("openssl: cipher not found"); + IntPtr ctx = OpenSSL.EVP_CIPHER_CTX_new(); + if (ctx == IntPtr.Zero) throw new System.Exception("openssl: fail to create ctx"); + + if (isEncrypt) + { + _encryptCtx = ctx; + } + else + { + _decryptCtx = ctx; + } + + DeriveSessionKey(isEncrypt ? _encryptSalt : _decryptSalt, _Masterkey, + isEncrypt ? _opensslEncSubkey : _opensslDecSubkey); + + var ret = OpenSSL.EVP_CipherInit_ex(ctx, _cipherInfoPtr, IntPtr.Zero, null, null, + isEncrypt ? OpenSSL.OPENSSL_ENCRYPT : OpenSSL.OPENSSL_DECRYPT); + if (ret != 1) throw new System.Exception("openssl: fail to init ctx"); + + ret = OpenSSL.EVP_CIPHER_CTX_set_key_length(ctx, keyLen); + if (ret != 1) throw new System.Exception("openssl: fail to set key length"); + + ret = OpenSSL.EVP_CIPHER_CTX_ctrl(ctx, OpenSSL.EVP_CTRL_AEAD_SET_IVLEN, + nonceLen, IntPtr.Zero); + if (ret != 1) throw new System.Exception("openssl: fail to set AEAD nonce length"); + + ret = OpenSSL.EVP_CipherInit_ex(ctx, IntPtr.Zero, IntPtr.Zero, + isEncrypt ? _opensslEncSubkey : _opensslDecSubkey, + null, + isEncrypt ? OpenSSL.OPENSSL_ENCRYPT : OpenSSL.OPENSSL_DECRYPT); + if (ret != 1) throw new System.Exception("openssl: cannot set key"); + OpenSSL.EVP_CIPHER_CTX_set_padding(ctx, 0); + } + + public override void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) + { + OpenSSL.SetCtxNonce(_encryptCtx, _encNonce, true); + // buf: all plaintext + // outbuf: ciphertext + tag + int ret; + int tmpLen = 0; + clen = 0; + var tagBuf = new byte[tagLen]; + + ret = OpenSSL.EVP_CipherUpdate(_encryptCtx, ciphertext, out tmpLen, + plaintext, (int) plen); + if (ret != 1) throw new CryptoErrorException("openssl: fail to encrypt AEAD"); + clen += (uint) tmpLen; + // For AEAD cipher, it should not output anything + ret = OpenSSL.EVP_CipherFinal_ex(_encryptCtx, ciphertext, ref tmpLen); + if (ret != 1) throw new CryptoErrorException("openssl: fail to finalize AEAD"); + if (tmpLen > 0) + { + throw new System.Exception("openssl: fail to finish AEAD"); + } + + OpenSSL.AEADGetTag(_encryptCtx, tagBuf, tagLen); + Array.Copy(tagBuf, 0, ciphertext, clen, tagLen); + clen += (uint) tagLen; + } + + public override void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) + { + OpenSSL.SetCtxNonce(_decryptCtx, _decNonce, false); + // buf: ciphertext + tag + // outbuf: plaintext + int ret; + int tmpLen = 0; + plen = 0; + + // split tag + byte[] tagbuf = new byte[tagLen]; + Array.Copy(ciphertext, (int) (clen - tagLen), tagbuf, 0, tagLen); + OpenSSL.AEADSetTag(_decryptCtx, tagbuf, tagLen); + + ret = OpenSSL.EVP_CipherUpdate(_decryptCtx, + plaintext, out tmpLen, ciphertext, (int) (clen - tagLen)); + if (ret != 1) throw new CryptoErrorException("openssl: fail to decrypt AEAD"); + plen += (uint) tmpLen; + + // For AEAD cipher, it should not output anything + ret = OpenSSL.EVP_CipherFinal_ex(_decryptCtx, plaintext, ref tmpLen); + if (ret <= 0) + { + // If this is not successful authenticated + throw new CryptoErrorException(String.Format("ret is {0}", ret)); + } + + if (tmpLen > 0) + { + throw new System.Exception("openssl: fail to finish AEAD"); + } + } + + #region IDisposable + + private bool _disposed; + + // instance based lock + private readonly object _lock = new object(); + + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~AEADOpenSSLEncryptor() + { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + lock (_lock) + { + if (_disposed) return; + _disposed = true; + } + + if (disposing) + { + // free managed objects + } + + // free unmanaged objects + if (_encryptCtx != IntPtr.Zero) + { + OpenSSL.EVP_CIPHER_CTX_free(_encryptCtx); + _encryptCtx = IntPtr.Zero; + } + + if (_decryptCtx != IntPtr.Zero) + { + OpenSSL.EVP_CIPHER_CTX_free(_decryptCtx); + _decryptCtx = IntPtr.Zero; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/shadowsocks-csharp/Encryption/AEAD/AEADSodiumEncryptor.cs b/shadowsocks-csharp/Encryption/AEAD/AEADSodiumEncryptor.cs index c78e5322b..75835739e 100644 --- a/shadowsocks-csharp/Encryption/AEAD/AEADSodiumEncryptor.cs +++ b/shadowsocks-csharp/Encryption/AEAD/AEADSodiumEncryptor.cs @@ -22,7 +22,7 @@ public AEADSodiumEncryptor(string method, string password) _sodiumDecSubkey = new byte[keyLen]; } - private static Dictionary _ciphers = new Dictionary + private static readonly Dictionary _ciphers = new Dictionary { {"chacha20-ietf-poly1305", new EncryptorInfo(32, 32, 12, 16, CIPHER_CHACHA20IETFPOLY1305)}, {"aes-256-gcm", new EncryptorInfo(32, 32, 12, 16, CIPHER_AES256GCM)}, @@ -46,7 +46,7 @@ public override void InitCipher(byte[] salt, bool isEncrypt, bool isUdp) } - public override int cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) + public override void cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext, ref uint clen) { Debug.Assert(_sodiumEncSubkey != null); // buf: all plaintext @@ -75,13 +75,12 @@ public override int cipherEncrypt(byte[] plaintext, uint plen, byte[] ciphertext default: throw new System.Exception("not implemented"); } - if (ret != 0) throw new CryptoErrorException(); + if (ret != 0) throw new CryptoErrorException(String.Format("ret is {0}", ret)); Logging.Dump("after cipherEncrypt: cipher", ciphertext, (int) encClen); clen = (uint) encClen; - return ret; } - public override int cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) + public override void cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext, ref uint plen) { Debug.Assert(_sodiumDecSubkey != null); // buf: ciphertext + tag @@ -111,10 +110,9 @@ public override int cipherDecrypt(byte[] ciphertext, uint clen, byte[] plaintext throw new System.Exception("not implemented"); } - if (ret != 0) throw new CryptoErrorException(); + if (ret != 0) throw new CryptoErrorException(String.Format("ret is {0}", ret)); Logging.Dump("after cipherDecrypt: plain", plaintext, (int) decPlen); plen = (uint) decPlen; - return ret; } public override void Dispose() diff --git a/shadowsocks-csharp/Encryption/EncryptorFactory.cs b/shadowsocks-csharp/Encryption/EncryptorFactory.cs index 35e03dc0f..169a67ce5 100644 --- a/shadowsocks-csharp/Encryption/EncryptorFactory.cs +++ b/shadowsocks-csharp/Encryption/EncryptorFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Text; using Shadowsocks.Encryption.AEAD; using Shadowsocks.Encryption.Stream; @@ -26,21 +27,42 @@ static EncryptorFactory() AEADSodiumEncryptorSupportedCiphers.Remove("aes-256-gcm"); } - foreach (string method in StreamMbedTLSEncryptor.SupportedCiphers()) + // XXX: sequence matters, OpenSSL > Sodium > MbedTLS + foreach (string method in StreamOpenSSLEncryptor.SupportedCiphers()) { - _registeredEncryptors.Add(method, typeof(StreamMbedTLSEncryptor)); + if (!_registeredEncryptors.ContainsKey(method)) + _registeredEncryptors.Add(method, typeof(StreamOpenSSLEncryptor)); } + foreach (string method in StreamSodiumEncryptor.SupportedCiphers()) { - _registeredEncryptors.Add(method, typeof(StreamSodiumEncryptor)); + if (!_registeredEncryptors.ContainsKey(method)) + _registeredEncryptors.Add(method, typeof(StreamSodiumEncryptor)); } - foreach (string method in AEADMbedTLSEncryptorSupportedCiphers) + + foreach (string method in StreamMbedTLSEncryptor.SupportedCiphers()) { - _registeredEncryptors.Add(method, typeof(AEADMbedTLSEncryptor)); + if (!_registeredEncryptors.ContainsKey(method)) + _registeredEncryptors.Add(method, typeof(StreamMbedTLSEncryptor)); } + + + foreach (string method in AEADOpenSSLEncryptor.SupportedCiphers()) + { + if (!_registeredEncryptors.ContainsKey(method)) + _registeredEncryptors.Add(method, typeof(AEADOpenSSLEncryptor)); + } + foreach (string method in AEADSodiumEncryptorSupportedCiphers) { - _registeredEncryptors.Add(method, typeof(AEADSodiumEncryptor)); + if (!_registeredEncryptors.ContainsKey(method)) + _registeredEncryptors.Add(method, typeof(AEADSodiumEncryptor)); + } + + foreach (string method in AEADMbedTLSEncryptorSupportedCiphers) + { + if (!_registeredEncryptors.ContainsKey(method)) + _registeredEncryptors.Add(method, typeof(AEADMbedTLSEncryptor)); } } @@ -50,12 +72,29 @@ public static IEncryptor GetEncryptor(string method, string password) { method = "aes-256-cfb"; } + method = method.ToLowerInvariant(); Type t = _registeredEncryptors[method]; + ConstructorInfo c = t.GetConstructor(ConstructorTypes); if (c == null) throw new System.Exception("Invalid ctor"); IEncryptor result = (IEncryptor) c.Invoke(new object[] {method, password}); return result; } + + public static string DumpRegisteredEncryptor() + { + var sb = new StringBuilder(); + sb.Append(Environment.NewLine); + 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("========================="); + return sb.ToString(); + } } } \ No newline at end of file diff --git a/shadowsocks-csharp/Encryption/OpenSSL.cs b/shadowsocks-csharp/Encryption/OpenSSL.cs new file mode 100644 index 000000000..1dcd12642 --- /dev/null +++ b/shadowsocks-csharp/Encryption/OpenSSL.cs @@ -0,0 +1,161 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; +using Shadowsocks.Controller; +using Shadowsocks.Encryption.Exception; +using Shadowsocks.Properties; +using Shadowsocks.Util; + +namespace Shadowsocks.Encryption +{ + // XXX: only for OpenSSL 1.1.0 and higher + public static class OpenSSL + { + private const string DLLNAME = "libcrypto-1_1.dll"; + + public const int OPENSSL_ENCRYPT = 1; + public const int OPENSSL_DECRYPT = 0; + + public const int EVP_CTRL_AEAD_SET_IVLEN = 0x9; + public const int EVP_CTRL_AEAD_GET_TAG = 0x10; + public const int EVP_CTRL_AEAD_SET_TAG = 0x11; + + static OpenSSL() + { + string dllPath = Utils.GetTempPath(DLLNAME); + try + { + FileManager.UncompressFile(dllPath, Resources.libcrypto_1_1_dll); + } + catch (IOException) + { + } + catch (System.Exception e) + { + Logging.LogUsefulException(e); + } + LoadLibrary(dllPath); + } + + public static IntPtr GetCipherInfo(string cipherName) + { + var name = Encoding.ASCII.GetBytes(cipherName); + Array.Resize(ref name, name.Length + 1); + return EVP_get_cipherbyname(name); + } + + /// + /// Need init cipher context after EVP_CipherFinal_ex to reuse context + /// + /// + /// + /// + public static void SetCtxNonce(IntPtr ctx, byte[] nonce, bool isEncrypt) + { + var ret = EVP_CipherInit_ex(ctx, IntPtr.Zero, + IntPtr.Zero, null, + nonce, + isEncrypt ? OPENSSL_ENCRYPT : OPENSSL_DECRYPT); + if (ret != 1) throw new System.Exception("openssl: fail to set AEAD nonce"); + } + + public static void AEADGetTag(IntPtr ctx, byte[] tagbuf, int taglen) + { + IntPtr tagBufIntPtr = IntPtr.Zero; + try + { + tagBufIntPtr = Marshal.AllocHGlobal(taglen); + var ret = EVP_CIPHER_CTX_ctrl(ctx, + EVP_CTRL_AEAD_GET_TAG, taglen, tagBufIntPtr); + if (ret != 1) throw new CryptoErrorException("openssl: fail to get AEAD tag"); + // take tag from unmanaged memory + Marshal.Copy(tagBufIntPtr, tagbuf, 0, taglen); + } + finally + { + if (tagBufIntPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(tagBufIntPtr); + } + } + } + + public static void AEADSetTag(IntPtr ctx, byte[] tagbuf, int taglen) + { + IntPtr tagBufIntPtr = IntPtr.Zero; + try + { + // allocate unmanaged memory for tag + tagBufIntPtr = Marshal.AllocHGlobal(taglen); + + // copy tag to unmanaged memory + Marshal.Copy(tagbuf, 0, tagBufIntPtr, taglen); + + var ret = EVP_CIPHER_CTX_ctrl(ctx, + EVP_CTRL_AEAD_SET_TAG, taglen, tagBufIntPtr); + + if (ret != 1) throw new CryptoErrorException("openssl: fail to set AEAD tag"); + + } + finally + { + if (tagBufIntPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(tagBufIntPtr); + } + } + } + + [DllImport("Kernel32.dll")] + private static extern IntPtr LoadLibrary(string path); + + [SuppressUnmanagedCodeSecurity] + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr EVP_CIPHER_CTX_new(); + + [SuppressUnmanagedCodeSecurity] + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern void EVP_CIPHER_CTX_free(IntPtr ctx); + + [SuppressUnmanagedCodeSecurity] + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int EVP_CIPHER_CTX_reset(IntPtr ctx); + + [SuppressUnmanagedCodeSecurity] + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int EVP_CipherInit_ex(IntPtr ctx, IntPtr type, + IntPtr impl, byte[] key, byte[] iv, int enc); + + [SuppressUnmanagedCodeSecurity] + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int EVP_CipherUpdate(IntPtr ctx, byte[] outb, + out int outl, byte[] inb, int inl); + + [SuppressUnmanagedCodeSecurity] + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int EVP_CipherFinal_ex(IntPtr ctx, byte[] outm, ref int outl); + + [SuppressUnmanagedCodeSecurity] + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int EVP_CIPHER_CTX_set_padding(IntPtr x, int padding); + + [SuppressUnmanagedCodeSecurity] + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int EVP_CIPHER_CTX_set_key_length(IntPtr x, int keylen); + + [SuppressUnmanagedCodeSecurity] + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern int EVP_CIPHER_CTX_ctrl(IntPtr ctx, int type, int arg, IntPtr ptr); + + /// + /// simulate NUL-terminated string + /// + /// + /// + [SuppressUnmanagedCodeSecurity] + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr EVP_get_cipherbyname(byte[] name); + } +} \ No newline at end of file diff --git a/shadowsocks-csharp/Encryption/Stream/StreamOpenSSLEncryptor.cs b/shadowsocks-csharp/Encryption/Stream/StreamOpenSSLEncryptor.cs new file mode 100644 index 000000000..9fb3b0da8 --- /dev/null +++ b/shadowsocks-csharp/Encryption/Stream/StreamOpenSSLEncryptor.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Shadowsocks.Encryption.Exception; + + +namespace Shadowsocks.Encryption.Stream +{ + public class StreamOpenSSLEncryptor + : StreamEncryptor, IDisposable + { + const int CIPHER_RC4 = 1; + const int CIPHER_AES = 2; + const int CIPHER_CAMELLIA = 3; + const int CIPHER_BLOWFISH = 4; + const int CIPHER_CHACHA20_IETF = 5; + + private IntPtr _encryptCtx = IntPtr.Zero; + private IntPtr _decryptCtx = IntPtr.Zero; + + public StreamOpenSSLEncryptor(string method, string password) + : base(method, password) + { + } + + // XXX: name=RC4,blkSz=1,keyLen=16,ivLen=0, do NOT pass IV to it + private static readonly Dictionary _ciphers = new Dictionary + { + { "aes-128-cfb", new EncryptorInfo("AES-128-CFB", 16, 16, CIPHER_AES) }, + { "aes-192-cfb", new EncryptorInfo("AES-192-CFB", 24, 16, CIPHER_AES) }, + { "aes-256-cfb", new EncryptorInfo("AES-256-CFB", 32, 16, CIPHER_AES) }, + { "aes-128-ctr", new EncryptorInfo("aes-128-ctr", 16, 16, CIPHER_AES) }, + { "aes-192-ctr", new EncryptorInfo("aes-192-ctr", 24, 16, CIPHER_AES) }, + { "aes-256-ctr", new EncryptorInfo("aes-256-ctr", 32, 16, CIPHER_AES) }, + { "bf-cfb", new EncryptorInfo("bf-cfb", 16, 8, CIPHER_BLOWFISH) }, + { "camellia-128-cfb", new EncryptorInfo("CAMELLIA-128-CFB", 16, 16, CIPHER_CAMELLIA) }, + { "camellia-192-cfb", new EncryptorInfo("CAMELLIA-192-CFB", 24, 16, CIPHER_CAMELLIA) }, + { "camellia-256-cfb", new EncryptorInfo("CAMELLIA-256-CFB", 32, 16, CIPHER_CAMELLIA) }, + { "rc4-md5", new EncryptorInfo("RC4", 16, 16, CIPHER_RC4) }, + // it's using ivLen=16, not compatible + //{ "chacha20-ietf", new EncryptorInfo("chacha20", 32, 12, CIPHER_CHACHA20_IETF) } + }; + + public static List SupportedCiphers() + { + return new List(_ciphers.Keys); + } + + protected override Dictionary getCiphers() + { + return _ciphers; + } + + protected override void initCipher(byte[] iv, bool isEncrypt) + { + base.initCipher(iv, isEncrypt); + IntPtr cipherInfo = OpenSSL.GetCipherInfo(_innerLibName); + if (cipherInfo == IntPtr.Zero) throw new System.Exception("openssl: cipher not found"); + IntPtr ctx = OpenSSL.EVP_CIPHER_CTX_new(); + if (ctx == IntPtr.Zero) throw new System.Exception("fail to create ctx"); + + if (isEncrypt) + { + _encryptCtx = ctx; + } + else + { + _decryptCtx = ctx; + } + + byte[] realkey; + if (_method == "rc4-md5") + { + byte[] temp = new byte[keyLen + ivLen]; + realkey = new byte[keyLen]; + Array.Copy(_key, 0, temp, 0, keyLen); + Array.Copy(iv, 0, temp, keyLen, ivLen); + realkey = MbedTLS.MD5(temp); + } + else + { + realkey = _key; + } + + var ret = OpenSSL.EVP_CipherInit_ex(ctx, cipherInfo, IntPtr.Zero, null,null, + isEncrypt ? OpenSSL.OPENSSL_ENCRYPT : OpenSSL.OPENSSL_DECRYPT); + if (ret != 1) throw new System.Exception("openssl: fail to set key length"); + ret = OpenSSL.EVP_CIPHER_CTX_set_key_length(ctx, keyLen); + if (ret != 1) throw new System.Exception("openssl: fail to set key length"); + ret = OpenSSL.EVP_CipherInit_ex(ctx, IntPtr.Zero, IntPtr.Zero, realkey, + _method == "rc4-md5" ? null : iv, + isEncrypt ? OpenSSL.OPENSSL_ENCRYPT : OpenSSL.OPENSSL_DECRYPT); + if (ret != 1) throw new System.Exception("openssl: cannot set key and iv"); + OpenSSL.EVP_CIPHER_CTX_set_padding(ctx, 0); + } + + protected override void cipherUpdate(bool isEncrypt, int length, byte[] buf, byte[] outbuf) + { + // C# could be multi-threaded + if (_disposed) + { + throw new ObjectDisposedException(this.ToString()); + } + + int outlen = 0; + var ret = OpenSSL.EVP_CipherUpdate(isEncrypt ? _encryptCtx : _decryptCtx, + outbuf, out outlen, buf, length); + if (ret != 1) + throw new CryptoErrorException(String.Format("ret is {0}", ret)); + Debug.Assert(outlen == length); + } + + #region IDisposable + + private bool _disposed; + + // instance based lock + private readonly object _lock = new object(); + + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~StreamOpenSSLEncryptor() + { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + lock (_lock) + { + if (_disposed) return; + _disposed = true; + } + + if (disposing) + { + // free managed objects + } + + // free unmanaged objects + if (_encryptCtx != IntPtr.Zero) + { + OpenSSL.EVP_CIPHER_CTX_free(_encryptCtx); + _encryptCtx = IntPtr.Zero; + } + + if (_decryptCtx != IntPtr.Zero) + { + OpenSSL.EVP_CIPHER_CTX_free(_decryptCtx); + _decryptCtx = IntPtr.Zero; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/shadowsocks-csharp/Encryption/Stream/StreamSodiumEncryptor.cs b/shadowsocks-csharp/Encryption/Stream/StreamSodiumEncryptor.cs index 492ca9458..523fab46f 100644 --- a/shadowsocks-csharp/Encryption/Stream/StreamSodiumEncryptor.cs +++ b/shadowsocks-csharp/Encryption/Stream/StreamSodiumEncryptor.cs @@ -27,7 +27,7 @@ public StreamSodiumEncryptor(string method, string password) _decryptBuf = new byte[MAX_INPUT_SIZE + SODIUM_BLOCK_SIZE]; } - private static Dictionary _ciphers = new Dictionary { + private static readonly Dictionary _ciphers = new Dictionary { { "salsa20", new EncryptorInfo(32, 8, CIPHER_SALSA20) }, { "chacha20", new EncryptorInfo(32, 8, CIPHER_CHACHA20) }, { "chacha20-ietf", new EncryptorInfo(32, 12, CIPHER_CHACHA20_IETF) } diff --git a/shadowsocks-csharp/Properties/Resources.Designer.cs b/shadowsocks-csharp/Properties/Resources.Designer.cs index ab946b69f..00c41ddfb 100644 --- a/shadowsocks-csharp/Properties/Resources.Designer.cs +++ b/shadowsocks-csharp/Properties/Resources.Designer.cs @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ // -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 // -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 // //------------------------------------------------------------------------------ @@ -13,13 +13,13 @@ namespace Shadowsocks.Properties { /// - /// A strongly-typed resource class, for looking up localized strings, etc. + /// 一个强类型的资源类,用于查找本地化的字符串等。 /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { @@ -33,7 +33,7 @@ internal Resources() { } /// - /// Returns the cached ResourceManager instance used by this class. + /// 返回此类使用的缓存的 ResourceManager 实例。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { @@ -47,8 +47,8 @@ internal Resources() { } /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. + /// 使用此强类型资源类,为所有资源查找 + /// 重写当前线程的 CurrentUICulture 属性。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { @@ -61,7 +61,7 @@ internal Resources() { } /// - /// Looks up a localized resource of type System.Byte[]. + /// 查找 System.Byte[] 类型的本地化资源。 /// internal static byte[] abp_js { get { @@ -71,28 +71,28 @@ internal static byte[] abp_js { } /// - /// Looks up a localized string similar to # translation for Japanese + /// 查找类似 # translation for Japanese /// ///Shadowsocks=Shadowsocks /// ///# Menu items /// - ///Enable System Proxy=システムの代理を有効にする + ///Enable System Proxy=システム プロキシを有効にする ///Mode=モード ///PAC=PAC ///Global=全般 - ///Servers=サーバ - ///Edit Servers...=サーバーを編集する... + ///Servers=サーバー + ///Edit Servers...=サーバーの編集... ///Statistics Config...=統計情報の設定... ///Start on Boot=システムと同時に起動 - ///Forward Proxy...=代理を転送する... - ///Allow Clients from LAN=LANからのクライアントを許可する + ///Forward Proxy...=フォワードプロキシの設定... + ///Allow Clients from LAN=LAN からのアクセスを許可 ///Local PAC=ローカル PAC ///Online PAC=オンライン PAC - ///Edit Local PAC File...=ローカル PAC ファイルを編集する... - ///Update Local PAC from GFWList=GFWList から、ローカル PACを更新する - ///Edit User Rule for GFWList...=利用者規則を編集する... - ///Secure Local [rest of string was truncated]";. + ///Edit Local PAC File...=ローカル PAC ファイルの編集... + ///Update Local PAC from GFWList=GFWList からローカル PAC を更新 + ///Edit User Rule for GFWList...=ユーザールールの編集... + ///Secure Local [字符串的其余部分被截断]"; 的本地化字符串。 /// internal static string ja { get { @@ -101,7 +101,17 @@ internal static string ja { } /// - /// Looks up a localized resource of type System.Byte[]. + /// 查找 System.Byte[] 类型的本地化资源。 + /// + internal static byte[] libcrypto_1_1_dll { + get { + object obj = ResourceManager.GetObject("libcrypto_1_1_dll", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// 查找 System.Byte[] 类型的本地化资源。 /// internal static byte[] libsscrypto_dll { get { @@ -111,7 +121,7 @@ internal static byte[] libsscrypto_dll { } /// - /// Looks up a localized resource of type System.Byte[]. + /// 查找 System.Byte[] 类型的本地化资源。 /// internal static byte[] mgwz_dll { get { @@ -121,14 +131,14 @@ internal static byte[] mgwz_dll { } /// - /// Looks up a localized string similar to listen-address __PRIVOXY_BIND_IP__:__PRIVOXY_BIND_PORT__ + /// 查找类似 listen-address __PRIVOXY_BIND_IP__:__PRIVOXY_BIND_PORT__ ///toggle 0 ///logfile ss_privoxy.log ///show-on-task-bar 0 ///activity-animation 0 ///forward-socks5 / 127.0.0.1:__SOCKS_PORT__ . ///hide-console - ///. + /// 的本地化字符串。 /// internal static string privoxy_conf { get { @@ -137,7 +147,7 @@ internal static string privoxy_conf { } /// - /// Looks up a localized resource of type System.Byte[]. + /// 查找 System.Byte[] 类型的本地化资源。 /// internal static byte[] privoxy_exe { get { @@ -147,7 +157,7 @@ internal static byte[] privoxy_exe { } /// - /// Looks up a localized resource of type System.Byte[]. + /// 查找 System.Byte[] 类型的本地化资源。 /// internal static byte[] proxy_pac_txt { get { @@ -157,7 +167,7 @@ internal static byte[] proxy_pac_txt { } /// - /// Looks up a localized resource of type System.Drawing.Bitmap. + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap ss16 { get { @@ -167,7 +177,7 @@ internal static System.Drawing.Bitmap ss16 { } /// - /// Looks up a localized resource of type System.Drawing.Bitmap. + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap ss20 { get { @@ -177,7 +187,7 @@ internal static System.Drawing.Bitmap ss20 { } /// - /// Looks up a localized resource of type System.Drawing.Bitmap. + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap ss24 { get { @@ -187,7 +197,7 @@ internal static System.Drawing.Bitmap ss24 { } /// - /// Looks up a localized resource of type System.Drawing.Bitmap. + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap ssIn24 { get { @@ -197,7 +207,7 @@ internal static System.Drawing.Bitmap ssIn24 { } /// - /// Looks up a localized resource of type System.Drawing.Bitmap. + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap ssOut24 { get { @@ -207,7 +217,7 @@ internal static System.Drawing.Bitmap ssOut24 { } /// - /// Looks up a localized resource of type System.Drawing.Bitmap. + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// internal static System.Drawing.Bitmap ssw128 { get { @@ -217,7 +227,7 @@ internal static System.Drawing.Bitmap ssw128 { } /// - /// Looks up a localized resource of type System.Byte[]. + /// 查找 System.Byte[] 类型的本地化资源。 /// internal static byte[] sysproxy_exe { get { @@ -227,7 +237,7 @@ internal static byte[] sysproxy_exe { } /// - /// Looks up a localized resource of type System.Byte[]. + /// 查找 System.Byte[] 类型的本地化资源。 /// internal static byte[] sysproxy64_exe { get { @@ -237,9 +247,9 @@ internal static byte[] sysproxy64_exe { } /// - /// Looks up a localized string similar to ! Put user rules line by line in this file. + /// 查找类似 ! Put user rules line by line in this file. ///! See https://adblockplus.org/en/filter-cheatsheet - ///. + /// 的本地化字符串。 /// internal static string user_rule { get { @@ -248,7 +258,7 @@ internal static string user_rule { } /// - /// Looks up a localized string similar to # translation for Simplified Chinese + /// 查找类似 # translation for Simplified Chinese /// ///Shadowsocks=Shadowsocks /// @@ -270,7 +280,7 @@ internal static string user_rule { ///Update Local PAC from GFWList=从 GFWList 更新本地 PAC ///Edit User Rule for GFWList...=编辑 GFWList 的用户规则... ///Secure Local PAC=保护本地 PAC - ///Cop [rest of string was truncated]";. + ///Cop [字符串的其余部分被截断]"; 的本地化字符串。 /// internal static string zh_CN { get { @@ -279,7 +289,7 @@ internal static string zh_CN { } /// - /// Looks up a localized string similar to # translation for Traditional Chinese + /// 查找类似 # translation for Traditional Chinese /// ///Shadowsocks=Shadowsocks /// @@ -300,7 +310,7 @@ internal static string zh_CN { ///Edit Local PAC File...=編輯本機 PAC 檔案... ///Update Local PAC from GFWList=從 GFWList 更新本機 PAC ///Edit User Rule for GFWList...=編輯 GFWList 的使用者規則... - ///Secure Local PAC=安全本機 [rest of string was truncated]";. + ///Secure Local PAC=安全本機 [字符串的其余部分被截断]"; 的本地化字符串。 /// internal static string zh_TW { get { diff --git a/shadowsocks-csharp/Properties/Resources.resx b/shadowsocks-csharp/Properties/Resources.resx index 1e2a09f68..616d76122 100755 --- a/shadowsocks-csharp/Properties/Resources.resx +++ b/shadowsocks-csharp/Properties/Resources.resx @@ -124,6 +124,9 @@ ..\Data\ja.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + ..\Data\libcrypto-1_1.dll.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\data\libsscrypto.dll.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index 3627879b5..d9d4b80f3 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -103,6 +103,7 @@ + @@ -110,10 +111,12 @@ + + @@ -250,6 +253,7 @@ Designer + diff --git a/test/UnitTest.cs b/test/UnitTest.cs index eca8c0c1e..f46478c6f 100755 --- a/test/UnitTest.cs +++ b/test/UnitTest.cs @@ -227,6 +227,47 @@ private void RunSingleSodiumEncryptionThread() } } + [TestMethod] + public void TestOpenSSLEncryption() + { + // run it once before the multi-threading test to initialize global tables + RunSingleOpenSSLEncryptionThread(); + List threads = new List(); + for (int i = 0; i < 10; i++) + { + Thread t = new Thread(new ThreadStart(RunSingleOpenSSLEncryptionThread)); + threads.Add(t); + t.Start(); + } + foreach (Thread t in threads) + { + t.Join(); + } + RNG.Close(); + Assert.IsFalse(encryptionFailed); + } + + private void RunSingleOpenSSLEncryptionThread() + { + try + { + for (int i = 0; i < 100; i++) + { + var random = new Random(); + IEncryptor encryptor; + IEncryptor decryptor; + encryptor = new StreamOpenSSLEncryptor("aes-256-cfb", "barfoo!"); + decryptor = new StreamOpenSSLEncryptor("aes-256-cfb", "barfoo!"); + RunEncryptionRound(encryptor, decryptor); + } + } + catch + { + encryptionFailed = true; + throw; + } + } + [TestMethod] public void ParseAndGenerateShadowsocksUrl() {