PSDropNew/IntelliTect.Security/CredentialManager.cs

using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
 
namespace IntelliTect.Security
{
    public static class CredentialManager
    {
        public static int WriteCredential( string key, string user, string secret )
        {
            // Validations.
 
            byte[] byteArray = Encoding.Unicode.GetBytes( secret );
            if ( byteArray.Length > 512 )
            {
                throw new ArgumentOutOfRangeException( "The secret message has exceeded 512 bytes." );
            }
 
            // Go ahead with what we have are stuff it into the CredMan structures.
            Credential cred = new Credential
            {
                TargetName = key,
                UserName = user,
                CredentialBlob = secret,
                CredentialBlobSize = (UInt32) Encoding.Unicode.GetBytes( secret ).Length,
                AttributeCount = 0,
                Attributes = IntPtr.Zero,
                Comment = null,
                TargetAlias = null,
                Type = CRED_TYPE.GENERIC,
                Persist = CRED_PERSIST.ENTERPRISE
            };
 
            NativeCredential ncred = NativeCredential.GetNativeCredential( cred );
            // Write the info into the CredMan storage.
            bool written = NativeMethods.CredWrite( ref ncred, 0 );
            int lastError = Marshal.GetLastWin32Error();
            if ( written )
            {
                return 0;
            }
            string message = $"CredWrite failed with the error code {lastError}.";
            throw new Exception( message );
        }
 
        public static NetworkCredential ReadCredential( string key )
        {
            IntPtr nCredPtr;
            NetworkCredential result;
 
            // Make the API call using the P/Invoke signature
            bool read = NativeMethods.CredRead( key, CRED_TYPE.GENERIC, 0, out nCredPtr );
            int lastError = Marshal.GetLastWin32Error();
 
            // If the API was successful then...
            if ( read )
            {
                using ( CriticalCredentialHandle critCred = new CriticalCredentialHandle( nCredPtr ) )
                {
                    Credential cred = critCred.GetCredential();
                    result = new NetworkCredential(
                            cred.UserName,
                            cred.CredentialBlob );
                }
            }
            else
            {
                //1168 is "element not found" -- ignore that one and return empty string:
                if ( lastError != 1168 )
                {
                    string message = $"ReadCred failed with the error code {lastError}.";
                    throw new Exception( message );
                }
                result = null;
            }
            return result;
        }
 
        public static bool DeleteCredential( string key )
        {
            // Make the API call using the P/Invoke signature
            bool read = NativeMethods.CredDelete( key, CRED_TYPE.GENERIC, 0 );
            int lastError = Marshal.GetLastWin32Error();
 
            // If the API was successful then...
            if ( !read )
            {
                if ( lastError != 1168 )
                {
                    string message = $"DeleteCred failed with the error code {lastError}.";
                    throw new Exception( message );
                }
                return false;
            }
            return true;
        }
 
        private enum CRED_PERSIST : uint
        {
            SESSION = 1,
            LOCAL_MACHINE = 2,
            ENTERPRISE = 3
        }
 
        private enum CRED_TYPE : uint
        {
            GENERIC = 1,
            DOMAIN_PASSWORD = 2,
            DOMAIN_CERTIFICATE = 3,
            DOMAIN_VISIBLE_PASSWORD = 4,
            GENERIC_CERTIFICATE = 5,
            DOMAIN_EXTENDED = 6,
            MAXIMUM = 7, // Maximum supported cred type
            MAXIMUM_EX = ( MAXIMUM + 1000 ) // Allow new applications to run on old OSes
        }
 
        [StructLayout( LayoutKind.Sequential, CharSet = CharSet.Unicode )]
        private struct Credential
        {
            public UInt32 Flags;
            public CRED_TYPE Type;
            public string TargetName;
            public string Comment;
            public readonly System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
            public UInt32 CredentialBlobSize;
            public string CredentialBlob;
            public CRED_PERSIST Persist;
            public UInt32 AttributeCount;
            public IntPtr Attributes;
            public string TargetAlias;
            public string UserName;
        }
 
        [StructLayout( LayoutKind.Sequential, CharSet = CharSet.Unicode )]
        private struct NativeCredential : IDisposable
        {
            public readonly UInt32 Flags;
            public CRED_TYPE Type;
            public IntPtr TargetName;
            public IntPtr Comment;
            public readonly System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
            public UInt32 CredentialBlobSize;
            public IntPtr CredentialBlob;
            public UInt32 Persist;
            public UInt32 AttributeCount;
            public IntPtr Attributes;
            public IntPtr TargetAlias;
            public IntPtr UserName;
 
            /// <summary>
            /// This method derives a NativeCredential instance from a given Credential instance.
            /// </summary>
            /// <param name="credential">The managed Credential counterpart containing data to be stored.</param>
            /// <returns>
            /// A NativeCredential instance that is derived from the given Credential
            /// instance.
            /// </returns>
            internal static NativeCredential GetNativeCredential( Credential credential )
            {
                NativeCredential nativeCredential = new NativeCredential();
                nativeCredential.AttributeCount = 0;
                nativeCredential.Attributes = IntPtr.Zero;
                nativeCredential.Comment = IntPtr.Zero;
                nativeCredential.TargetAlias = IntPtr.Zero;
                nativeCredential.Type = credential.Type;
                nativeCredential.Persist = (UInt32) credential.Persist;
                nativeCredential.CredentialBlobSize = credential.CredentialBlobSize;
                nativeCredential.TargetName = Marshal.StringToCoTaskMemUni( credential.TargetName );
                nativeCredential.CredentialBlob = Marshal.StringToCoTaskMemUni( credential.CredentialBlob );
                nativeCredential.UserName = Marshal.StringToCoTaskMemUni( credential.UserName );
                return nativeCredential;
            }
 
            public void Dispose()
            {
                Dispose( true );
                GC.SuppressFinalize( this );
            }
 
            public void Dispose( bool disposing )
            {
                if ( disposing )
                {
                    // Release managed resources.
                }
 
                // Free the unmanaged resource ...
 
                Attributes = IntPtr.Zero;
                Comment = IntPtr.Zero;
                CredentialBlob = IntPtr.Zero;
                TargetAlias = IntPtr.Zero;
                TargetName = IntPtr.Zero;
                UserName = IntPtr.Zero;
            }
 
            //~NativeCredential()
            //{
            // Dispose(false);
            //}
        }
 
        private static class NativeMethods
        {
            [DllImport( "Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true )]
            public static extern bool CredRead( string target,
                    CRED_TYPE type,
                    int reservedFlag,
                    out IntPtr CredentialPtr );
 
            [DllImport( "Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true )]
            public static extern bool CredWrite( [In] ref NativeCredential userCredential, [In] UInt32 flags );
 
            [DllImport( "Advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode, SetLastError = true )]
            public static extern bool CredDelete( [In] string target, [In] CRED_TYPE type, [In] UInt32 flags );
 
            [DllImport( "Advapi32.dll", EntryPoint = "CredFree", SetLastError = true )]
            public static extern bool CredFree( [In] IntPtr cred );
        }
 
        #region Critical Handle Type definition
 
        private sealed class CriticalCredentialHandle : CriticalHandleZeroOrMinusOneIsInvalid
        {
            // Set the handle.
            internal CriticalCredentialHandle( IntPtr preexistingHandle )
            {
                SetHandle( preexistingHandle );
            }
 
            internal Credential GetCredential()
            {
                if ( !IsInvalid )
                {
                    // Get the Credential from the mem location
                    NativeCredential ncred = (NativeCredential) Marshal.PtrToStructure( handle,
                            typeof(NativeCredential) );
 
                    // Create a managed Credential type and fill it with data from the native counterpart.
                    Credential cred = new Credential
                    {
                        CredentialBlobSize = ncred.CredentialBlobSize,
                        CredentialBlob = Marshal.PtrToStringUni( ncred.CredentialBlob,
                                (int) ncred.CredentialBlobSize / 2 ),
                        UserName = Marshal.PtrToStringUni( ncred.UserName ),
                        TargetName = Marshal.PtrToStringUni( ncred.TargetName ),
                        TargetAlias = Marshal.PtrToStringUni( ncred.TargetAlias ),
                        Type = ncred.Type,
                        Flags = ncred.Flags,
                        Persist = (CRED_PERSIST) ncred.Persist
                    };
                    return cred;
                }
                throw new InvalidOperationException( "Invalid CriticalHandle!" );
            }
 
            // Perform any specific actions to release the handle in the ReleaseHandle method.
            // Often, you need to use Pinvoke to make a call into the Win32 API to release the
            // handle. In this case, however, we can use the Marshal class to release the unmanaged memory.
 
            protected override bool ReleaseHandle()
            {
                // If the handle was set, free it. Return success.
                if ( !IsInvalid )
                {
                    // NOTE: We should also ZERO out the memory allocated to the handle, before free'ing it
                    // so there are no traces of the sensitive data left in memory.
                    NativeMethods.CredFree( handle );
                    // Mark the handle as invalid for future users.
                    SetHandleAsInvalid();
                    return true;
                }
                // Return false.
                return false;
            }
        }
 
        #endregion
    }
}