Software Is Hardwork

ISimplicityAffinative: The endless pursuit of anti-complexity.
The technology-centric blog of D. P. Bullington.

Email D. P. Bullington View D. P. Bullington\ Follow D. P. Bullington on Twitter Get Software Is Hardwork code on CodePlex

Blog Post(s)

The Right Way to Impersonate Credentials in .NET
Monday, July 6, 2009

I recently discussed the wrong way to impersonate credentials in .NET. All of the code examples I have encountered have introduced a fatal design flaw: restoring an impersonated principal when impersonating yet another principal as in ASP.NET with impersonate=true. The following illustrates correct code based on my current understanding.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

namespace SoftwareIsHardwork.Samples
{
    public class ImpersonationScope : IDisposable
    {
        #region Constructors/Destructors

        public ImpersonationScope(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider)
        {
            IntPtr logonToken = IntPtr.Zero;
            IntPtr logonTokenDuplicate = IntPtr.Zero;

            Debug.WriteLine("Enter impersonation scope");

            if ((object)userName == null)
                throw new ArgumentNullException("userName");

            if ((object)domainName == null)
                throw new ArgumentNullException("domainName");

            if ((object)password == null)
                throw new ArgumentNullException("password");

            if (userName == "")
                throw new ArgumentOutOfRangeException("userName");

            if (domainName == "")
                throw new ArgumentOutOfRangeException("domainName");

            if (password == "")
                throw new ArgumentOutOfRangeException("password");

            try
            {
                Debug.WriteLine("Incoming identity: " + WindowsIdentity.GetCurrent().Name);

                this.processWindowsImpersonationContext = WindowsIdentity.Impersonate(IntPtr.Zero);

                Debug.WriteLine("Process-impersonated identity: " + WindowsIdentity.GetCurrent().Name);

                //if (Win32NativeMethods.RevertToSelf())
                {
                    if (LogonUser(userName,
                                  domainName,
                                  password,
                                  (int)logonType,
                                  (int)logonProvider,
                                  ref logonToken) != 0)
                    {
                        if (DuplicateToken(logonToken, (int)ImpersonationLevel.SecurityImpersonation, ref logonTokenDuplicate) != 0)
                        {
                            this.impersonatedWindowsIdentity = new WindowsIdentity(logonTokenDuplicate);
                            this.threadWindowsImpersonationContext = this.impersonatedWindowsIdentity.Impersonate();

                            Debug.WriteLine("Thread-impersonated identity: " + WindowsIdentity.GetCurrent().Name);
                        }
                        else
                            throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                    else
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                }
            }
            catch
            {
                this.Dispose();
                throw;
            }
            finally
            {
                if (logonToken != IntPtr.Zero)
                {
                    CloseHandle(logonToken);
                    logonToken = IntPtr.Zero;
                }

                if (logonTokenDuplicate != IntPtr.Zero)
                {
                    CloseHandle(logonTokenDuplicate);
                    logonTokenDuplicate = IntPtr.Zero;
                }

                Debug.WriteLine("Leave constructor");
            }
        }

        #endregion

        #region Fields/Constants

        private readonly WindowsIdentity impersonatedWindowsIdentity;
        private readonly WindowsImpersonationContext processWindowsImpersonationContext;
        private readonly WindowsImpersonationContext threadWindowsImpersonationContext;
        private bool disposed;

        #endregion

        #region Methods/Operators

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private static extern bool CloseHandle(IntPtr handle);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern int DuplicateToken(
            IntPtr hToken,
            int impersonationLevel,
            ref IntPtr hNewToken);

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern int LogonUser(
            string lpszUserName,
            string lpszDomain,
            string lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            ref IntPtr phToken);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool RevertToSelf();

        public void Dispose()
        {
            if (this.disposed)
                return;

            try
            {
                Debug.WriteLine("Thread-impersonated identity: " + WindowsIdentity.GetCurrent().Name);

                if ((object)this.threadWindowsImpersonationContext != null)
                {
                    this.threadWindowsImpersonationContext.Undo();
                    this.threadWindowsImpersonationContext.Dispose();
                }

                if ((object)this.impersonatedWindowsIdentity != null)
                    this.impersonatedWindowsIdentity.Dispose();

                Debug.WriteLine("Process-impersonated identity: " + WindowsIdentity.GetCurrent().Name);

                if ((object)this.processWindowsImpersonationContext != null)
                {
                    this.processWindowsImpersonationContext.Undo();
                    this.processWindowsImpersonationContext.Dispose();
                }

                Debug.WriteLine("Outgoing identity: " + WindowsIdentity.GetCurrent().Name);
            }
            finally
            {
                this.disposed = true;
                GC.SuppressFinalize(this);

                Debug.WriteLine("Leave impersonation scope");
            }
        }

        #endregion

        #region Classes/Structs/Interfaces/Enums/Delegates

        public enum ImpersonationLevel
        {
            SecurityAnonymous = 0,
            SecurityIdentification = 1,
            SecurityImpersonation = 2,
            SecurityDelegation = 3
        }

        public enum LogonProvider
        {
            LOGON32_PROVIDER_DEFAULT = 0,
            LOGON32_PROVIDER_WINNT35 = 1,
            LOGON32_PROVIDER_WINNT40 = 2,
            LOGON32_PROVIDER_WINNT50 = 3
        }

        public enum LogonType
        {
            LOGON32_LOGON_INTERACTIVE = 2,
            LOGON32_LOGON_NETWORK = 3,
            LOGON32_LOGON_BATCH = 4,
            LOGON32_LOGON_SERVICE = 5,
            LOGON32_LOGON_UNLOCK = 7,
            LOGON32_LOGON_NETWORK_CLEARTEXT = 8, // Win2K or higher
            LOGON32_LOGON_NEW_CREDENTIALS = 9 // Win2K or higher
        }

        #endregion
    }
}

Sample usage scenario:

using (scope = new ImpersonationScope(username, domain, password, ImpersonationScope.LogonType.LOGON32_LOGON_INTERACTIVE, ImpersonationScope.LogonProvider.LOGON32_PROVIDER_DEFAULT))
{
  // code under impersonation
}

2 comments:

Ben said...

Nice... most of the sample out there don't even perform any clean up at all.

One question though:

Why did you comment out the if (Win32NativeMethods.RevertToSelf()) ?

D. P. Bullington said...

RevertToSelf is not needed because when you impersonate principal IntPtr.Zero, it is essentially reverting to self but remembering who your were impersonating prior. (Think: linked list of impersonation contexts to ensure your return to where you came from.) It was more of a reminder, plus a throw back to the incorrect examples on the web.

Speaking Enagements

  • 11/18/2010 | Charlottesville .NET Users Group | Charlottesville, VA | Topic TBD
  • 09/14/2010 | Hampton Roads .NET Users Group | Cheaspeake, VA | Topic TBD
  • 07/01/2010 | Richmond .NET Users Group | Richmond, VA | Topic TBD
  • (past) 12/08/2009 | Hampton Roads .NET Users Group | Cheaspeake, VA | SharePoint Antithesis - A Case Study in Pragmatic Software Architecture and Engineering Processes
  • (past) 10/04/2009 | Richmond Code Camp 2009.2 | Richmond, VA | Soothing the Pain Points: Data Access, Validation, Rules, UI, Presentation, et. al
  • (past) 07/23/2009 | Charlottesville .NET Users Group | Charlottesville, VA | Debugging on the Windows Platform
  • (past) 05/23/2008 | NoVa CodeCamp 2009.01 | Reston, VA | Going Proxy-less - The WCF Proxy Factory
  • (past) 04/25/2009 | Richmond Code Camp 2009.1 | Richmond, VA | Software Programmer to Software Engineer: Concepts to Span the Divide
  • (past) 02/05/2009 | Richmond .NET Users Group | Richmond, VA | Debugging on the Windows Platform
  • (past) 10/04/2008 | Richmond Code Camp 2008.2 | Richmond, VA | Going Proxy-less - The WCF Proxy Factory

Blog Archive

Post Labels

.NET (64) .NETv4.0 (3) ACID (1) ActiveDirectory (1) ADF (2) Affiliate (1) Agile (6) AJAX (1) Allocator (3) Analysis (1) AOP (4) ASP.NET (6) ASP.NET MVC (1) Assembly (2) BadIdeaPile (1) BagOfBolts (5) Blogger (1) Books (2) BuildMgmt (8) C# (46) ChoDNUG (1) CLR (1) CLRv4.0 (2) CMP (1) CMS (2) CodeCamp (2) COM (1) Conversation (1) Coverage (1) CUI (1) Database (2) DDD (1) DeadFxs (1) Debugging (9) Design (4) DevAuto (3) DevCfg (1) Development (118) DI (6) DiffMerge (1) Domain (1) DTfW (2) EclipseIDE (1) ECM (1) EntityFramework (1) Estimating (1) FileShare (1) Frameworks (7) GAC (2) Google (1) Hardware (2) HRNUG (1) Humor (6) IIS (4) ILDASM (1) Impersonation (2) InstallError (1) IoC (6) KingTodd (1) LinkedIn (1) LINQtoSQL (2) MarketingHype (1) MBUnit (1) Mentoring (22) Metadata (1) Microsoft (7) MOSS2007 (5) MSBuild (2) MSIL (4) MSSCCI (2) NAnt (2) NCore (2) NCover (1) NDatabase (4) NetUse (1) NHibernate (2) NoVaCodeCamp (2) NTSD/CDB (2) NUnit (1) Observation (2) Office (2) OOD (7) OOP (6) OpenSource (14) Opinion (19) Personal (3) PMP (1) Polymorphism (1) PowerPoint (1) PowerShell (2) Presentation (3) Process (4) ProjectManagement (2) PublicKeyToken (1) QA (2) RDNUG (1) Reflection (2) Registry (2) Resharper (1) Reversing (2) RichmondCodeCamp (5) SCM (11) Scrum (5) Security (2) Series (3) Server2008 (4) ServicePack (1) SES (7) SharePoint (7) Silverlight (1) SoC (3) Software (49) SoftwareIsHardwork (17) Speaking (7) SQL (2) SSO (2) StrongName (2) Suite2008 (9) Suite2010 (1) SwEng (19) TechBlunder (1) Testing (14) Thread (3) Tools (8) Troubleshooting (10) Twitter (3) Types (2) UAC (1) UIP (1) Vault (2) VB6 (1) VC (1) Vista (3) VisualStudio (15) VSIP (2) VSTS (1) WCF (4) Web (4) WebForms (1) Win32 (3) WinDBG (3) WindowsIdentity (3) WinForms (1) WIT (1) Workhorse (1) WoW64 (1) WPF (1) WSS3 (2) x64 (2) x86 (2) xUnit (1)

Disclaimer

© D. P. Bullington, all rights reserved. Everything posted on this blog is my personal opinion and does not represent the views of my employer nor serves to infringe on the sovereignty of any nation.