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 = "libsscrypto.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.libsscrypto_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);
}
}