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); /// /// You must dispose the return value as soon as SqlCommand.Execute* is called. /// 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 } }