I have an HTTP server that authenticates clients with Negotiate protocol. I can run the server both as a console application and as a windows service. I also use a specific domain account as a logon identity. The server calls SSPI AcquireCredentialsHandle
and AcceptSecurityContext and exchanges binary tokens with client. I can successfully login with another domain account from remote machines (probably via Kerberos) and from the same machine where the server is running (seems to be using NTLM) when the server
is running as a console application. However when I start it as a windows service I'm getting SEC_E_INVALID_HANDLE when I try to log in from the same machine where the server is running.
Here is how it happens:
1. Client sends HTTP GET request http://localhost:8082
2. Server responds with HTTP 401 and "WWW-Authenticate: Negotiate" header.
3. Client sends Authorization header and includes the following data:
607306062B0601050502 A0 693067 A0 30`s..+..... i0g 0302E060A2B06010401823702020A06090...+....7.....2A864882 F7 1201020206092A864886 F7 *H÷......*H÷12010202060A2B06010401823702021E......+....7...
A2 3304314E544C4D5353500001000000¢3.1NTLMSSP.....97 B2 08 E2 040004002D00000005000500².â....-.......280000000601 B1 1D0000000F50414345(.....±.....PACE4D424C4148 MBLAH
4.Server responds with HTTP 401 error and negotiate header prompting to continue:
A1 81 CE 3081 CB A0 030A0101 A1 0C060A2B¡Î0Ë ....¡...+06010401823702020A A2 81 B5 0481 B2 4E....7...¢µ.²N544C4D53535000020000000800080038 TLMSSP.........8I had to truncate a few lines...
5.Client sends Authorization header:
A1 773075 A0 030A0101 A2 5A04584E544C¡w0u ....¢Z.XNTL4D535350000300000000000000580000 MSSP.........X..00000000005800000000000000580000.....X.......X..I had to truncate a few lines
6.Server responds with HTTP 200 and negotiate header:
A1 1B3019 A0 030A0100 A3 120410010000¡.0. ....£......004387 E0 88 C1 36 E3 A9 00000000.CàÁ6ã©....
Now the step #6 works fine if the server is running in console mode, but fails with invalid handle error as a windows service. In both cases I'm using same domain accounts. Is there any additional privileges I need to grant to that user, or could be related
to some restrictions like session 0 isolation?
Here is the server code authenticating clients:
public static WinAuthResult Authenticate(string clientId, byte[] clientTokenBytes, string securityPackage, ILogger logger)
{
if (clientTokenBytes == null || clientTokenBytes.Length == 0)
{
ClearContext(clientId);
throw new Win32Exception(Secur32.SEC_E_INVALID_TOKEN);
}
var serverCredExpiry = new Secur32.SECURITY_INTEGER();
var serverCredHandle = new Secur32.SecHandle();
var acquireResult = Secur32.AcquireCredentialsHandle(null, securityPackage, Secur32.SECPKG_CRED_INBOUND, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, out serverCredHandle, out serverCredExpiry);
if (acquireResult != Secur32.SEC_E_OK)
throw new Win32Exception(acquireResult);
var oldContextExists = contexts.ContainsKey(clientId);
var oldContextHandle = GetContextHandle(clientId);
var newContextHandle = new Secur32.SecHandle();
var clientToken = new Secur32.SecBufferDesc(clientTokenBytes);
var outputToken = new Secur32.SecBufferDesc(61440);
var contextAttributes = (uint)0;
var outputCresExpiry = new Secur32.SECURITY_INTEGER();
int acceptResult;
if (!oldContextExists)
{
acceptResult = Secur32.AcceptSecurityContext(
ref serverCredHandle,
IntPtr.Zero,
ref clientToken,
0,
Secur32.SECURITY_NATIVE_DREP,
ref newContextHandle,
ref outputToken,
out contextAttributes,
out outputCresExpiry);
}
else
{
acceptResult = Secur32.AcceptSecurityContext(
ref serverCredHandle,
ref oldContextHandle,
ref clientToken,
0,
Secur32.SECURITY_NATIVE_DREP,
ref newContextHandle,
ref outputToken,
out contextAttributes,
out outputCresExpiry);
}
if (acceptResult == Secur32.SEC_E_OK)
{
ClearContext(clientId);
return new WinAuthResult(false, outputToken.GetSecBufferByteArray());
}
else if (acceptResult == Secur32.SEC_I_CONTINUE_NEEDED)
{
contexts[clientId] = newContextHandle;
return new WinAuthResult(true, outputToken.GetSecBufferByteArray());
}
else
{
ClearContext(clientId);
throw new Win32Exception(acceptResult);
}
}