Backend/e-suite.API/e-suite.Database.Migrator/SqlWrapper/SecureSqlParameterExtensions.cs
2026-01-20 21:50:10 +00:00

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