171 lines
5.6 KiB
C#
171 lines
5.6 KiB
C#
using System.Data;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security;
|
|
using Microsoft.Data.SqlClient;
|
|
|
|
namespace e_suite.Database.Migrator.SqlWrapper;
|
|
|
|
public static class SecureSqlParameterExtensions
|
|
{
|
|
[DllImport("kernel32.dll", EntryPoint = "CopyMemory")]
|
|
private static extern void CopyMemory(IntPtr dest, IntPtr src, IntPtr count);
|
|
[DllImport("kernel32.dll", EntryPoint = "RtlZeroMemory")]
|
|
private static extern void ZeroMemory(IntPtr ptr, IntPtr count);
|
|
|
|
/// <summary>
|
|
/// You must dispose the return value as soon as SqlCommand.Execute* is called.
|
|
/// </summary>
|
|
public static IDisposable AddSecure(this SqlParameterCollection collection, string name, SecureString secureString)
|
|
{
|
|
var value = new SecureStringParameterValue(secureString);
|
|
collection.Add(name, SqlDbType.NVarChar).Value = value;
|
|
return value;
|
|
}
|
|
|
|
private sealed class SecureStringParameterValue : IConvertible, IDisposable
|
|
{
|
|
private readonly SecureString? _secureString;
|
|
private int _length;
|
|
private string? _insecureManagedCopy;
|
|
private GCHandle _insecureManagedCopyGcHandle;
|
|
|
|
public SecureStringParameterValue(SecureString secureString)
|
|
{
|
|
_secureString = secureString;
|
|
}
|
|
|
|
#region IConvertible
|
|
|
|
public TypeCode GetTypeCode()
|
|
{
|
|
return TypeCode.String;
|
|
}
|
|
|
|
public string ToString(IFormatProvider? provider)
|
|
{
|
|
if (_insecureManagedCopy != null) return _insecureManagedCopy;
|
|
if (_secureString == null || _secureString.Length == 0) return string.Empty;
|
|
|
|
// We waited till the last possible minute.
|
|
|
|
// Here's the plan:
|
|
// 1. Create a new managed string initialized to zero
|
|
// 2. Pin the managed string so the GC leaves it alone
|
|
// 3. Copy the contents of the SecureString into the managed string
|
|
// 4. Use the string as a SqlParameter
|
|
// 5. Zero the managed string after Execute* is called and free the GC handle
|
|
|
|
_length = _secureString.Length;
|
|
_insecureManagedCopy = new string('\0', _length);
|
|
_insecureManagedCopyGcHandle = GCHandle.Alloc(_insecureManagedCopy, GCHandleType.Pinned); // Do not allow the GC to move this around and leave copies behind
|
|
|
|
try
|
|
{
|
|
// This is the only way to read the contents, sadly.
|
|
// SecureStringToBSTR picks where to put it, so we have to copy it from there and zerofree the unmanaged copy as fast as possible.
|
|
IntPtr insecureUnmanagedCopy = Marshal.SecureStringToBSTR(_secureString);
|
|
try
|
|
{
|
|
CopyMemory(_insecureManagedCopyGcHandle.AddrOfPinnedObject(), insecureUnmanagedCopy, (IntPtr)(_length * 2));
|
|
}
|
|
finally
|
|
{
|
|
if (insecureUnmanagedCopy != IntPtr.Zero) Marshal.ZeroFreeBSTR(insecureUnmanagedCopy);
|
|
}
|
|
|
|
// Now the string managed string has the contents in the clear.
|
|
return _insecureManagedCopy;
|
|
}
|
|
catch
|
|
{
|
|
Dispose();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_insecureManagedCopy == null) return;
|
|
_insecureManagedCopy = null;
|
|
ZeroMemory(_insecureManagedCopyGcHandle.AddrOfPinnedObject(), (IntPtr)(_length * 2));
|
|
_insecureManagedCopyGcHandle.Free();
|
|
}
|
|
|
|
public bool ToBoolean(IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public char ToChar(IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public sbyte ToSByte(IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public byte ToByte(IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public short ToInt16(IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public ushort ToUInt16(IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public int ToInt32(IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public uint ToUInt32(IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public long ToInt64(IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public ulong ToUInt64(IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public float ToSingle(IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public double ToDouble(IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public decimal ToDecimal(IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public DateTime ToDateTime(IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public object ToType(Type conversionType, IFormatProvider? provider)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |