I am dealing with a nasty issue that has me ready to tear my hair out. I have a C# console application that uses Microsoft's HttpListener class to listen for web requests. The idea is that the console application runs in the background as UserAccountA (low priv). UserAccountB (Administrator, etc) comes along, accesses a webpage through the listener, and has his or her identity impersonated. Same kind of thing IIS or WCF does. I believe I had this working on Windows 7, but now I'm on Windows 8.1 and it's failing again. Maybe I never had it to begin with, or perhaps this is a new twist.
If I run the program through Visual Studio, I can access it with Internet Explorer 11. For some reason, it asks me to type in my local credentials. I'm assuming that's something to do with the out of the box behavior of IE11. Once I type it in, it accepts it. My code starts like this:
protected virtual void Listen(object o)
{
HttpListener h = (HttpListener)o;
while (h.IsListening && (this.Disposed == false))
{
IAsyncResult Result = null;
Result = h.BeginGetContext(new AsyncCallback(this.ListenerCallback), h);
Result.AsyncWaitHandle.WaitOne();
}
}
And is picked up like this (abbreviated):
protected virtual void ListenerCallback(IAsyncResult Result)
{
HttpListener h = (HttpListener)Result.AsyncState;
HttpListenerContext context = null;
System.Security.Principal.WindowsIdentity identity = null;
context = h.EndGetContext(Result);
identity = (System.Security.Principal.WindowsIdentity)context.User.Identity;
using (System.Security.Principal.WindowsImpersonationContext wic = identity.Impersonate())
{
//method call to process request, under impersonation
}
}
I've stepped through this code when using two different accounts (one to host, one to access). I've verified using this statement:
system.security.principal.windowsidentity.getcurrent().name
That the current identity DOES transition. The listener is configured to start up with NTLM AuthenticationScheme.
I suppose the complicated part is in the method that gets invoked during impersonation. We're doing a bit of abstraction, but let me just point at the part that fails. In this case, we are connecting to SQL server using SSPI - so the entire point of the impersonation is to grab the client's existing logon and gracefully get them into SQL Server. Again, right now, this is all local to one computer. it is not even in an active directory domain. Everything is local, with local accounts. It starts like this:
System.Data.Common.DbProviderFactory fact = null;
if (UseSql())
{
fact = System.Data.Common.DbProviderFactories.GetFactory("System.Data.SqlClient");
}
No big deal. We use the provider factory in another project constantly with no issues (but that's just a regular WPF app, nothing like this). Right after this is where we'd set up the SQL login string with the SSPI attribute. But we don't even get that far. This is exactly where it fails, with this message:
A first chance exception of type 'System.IO.FileLoadException' occurred in mscorlib.dll
Additional information: Could not load file or assembly 'System.Data.OracleClient, Version=4.0.0.0, Culture=neutral, > PublicKeyToken=b77a5c561934e089' or one of its dependencies. Either a required impersonation level was not provided, or the provided impersonation level is invalid. (Exception from HRESULT: 0x80070542)
http://i854.photobucket.com/albums/ab103/srVincentVega/error7_zpsmb1xqrp0.png
We're not using Oracle at all, I'm assuming that's just the first thing it barfs on when it goes and tries to open up the DbProviderFactory. To me, the key is that impersonation level thing. In this particular experiment, both accounts (A & B) are local administrators. I've ensured via local Security Policy that they can impersonate accounts after login. And if I look at my event log, the impersonation DOES seem to be working...
Special privileges assigned to new logon.
Subject: Security ID:
pc1\userA
Account Name: userA
Account Domain: pc1 Logon ID: 0xC4D0F3F
Privileges:
SeSecurityPrivilege
SeTakeOwnershipPrivilege
SeLoadDriverPrivilege
SeBackupPrivilege
SeRestorePrivilege
SeDebugPrivilege
SeSystemEnvironmentPrivilege
SeImpersonatePrivilege
I've tried so many variations here that I'm starting to hinder my own analysis. I'm basically back to square one and don't know how to approach this. Does anyone have any advice or tips? This seems like it should be a very simple thing to do. I am not sure why I am having so much trouble. Maybe I just need to toss out the use of the DbProviderFactory and go with OleDb or whatever. But I have not confirmed that I can load ANY libraries at run time after impersonation. Maybe that's something to do. However, I would be very grateful for any help.
Thanks,
John
One last thing, here are reproduction steps:
Create a secondary user on your computer. Log in to create the profile. Log out and log back in as usual. Run Visual Studio as that secondary user (there are a few ways to do this, shift + right click on the VS icon), and within, make a C# console application with this skeleton:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
using (HttpListener h = new HttpListener())
{
h.Prefixes.Add("http://+:8090/");
h.AuthenticationSchemes = AuthenticationSchemes.Ntlm;
h.Start();
Console.WriteLine("Running");
HttpListenerContext context = h.GetContext();
System.Security.Principal.WindowsIdentity identity = null;
identity = (System.Security.Principal.WindowsIdentity)context.User.Identity;
using (System.Security.Principal.WindowsImpersonationContext wic = identity.Impersonate())
{
System.Data.Common.DbProviderFactory fact = null;
fact = System.Data.Common.DbProviderFactories.GetFactory("System.Data.SqlClient");
}
Console.ReadLine();
h.Stop();
}
}
}
}
Debug/Run the console app. Just call up Internet Explorer (vanilla, using your primary account) and access that URL (http://machinename:8090). The exception occurs
http://i854.photobucket.com/albums/ab103/srVincentVega/error8_zpsilkay0bl.png
Well, I read like half-way through and stopped at the exception you are getting:
A first chance exception of type 'System.IO.FileLoadException' occurred in mscorlib.dll
The problem here is that the account that you are using to run the service has limited access to load .Net framework core libraries before it can even perform impersonation. Usually such an account which is used to host a service has admin privileges on the node where the service is hosted.
Also to allow impersonation you need to give the service account the privileges to do so using the local security policies - Local Policies - User Rights Assignment:
Act as part of the operating system
This user right allows a process to impersonate any user without authentication. The process can therefore gain access to the same local resources as that user.
Impersonate a client after authentication
Assigning this privilege to a user allows programs running on behalf of that user to impersonate a client.
Also note if you use kerberos instead of NTLM you would need to setup SPN's as well as delegation targets in AD (Actice Directory).
I don't know if this will assist you but these are things we ran into doing impersonation/delegation.
Oh yeah, impersonation level must be set to delegation if you perform more than one hop to perform impersonation.