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 } }