I'm trying to get the Win32 SSPI API to validate a challenge response from a client. The call to AcceptSecurityContext is always failing with either SEC_E_INVALID_TOKEN (0x80090308) or SEC_E_INTERNAL_ERROR (0x80090304).
I've stripped down the problem to this sample code:
#define SECURITY_WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <security.h>
#include <wdigest.h>
#include <cstdlib>
#include <string>
#include <iostream>
#pragma comment(lib, "Secur32.lib")
using namespace std::string_literals;
int main()
{
auto const realm = L"realm"s;
auto const client_method = "GET"s;
auto const client_target = L"HTTP/TARGETMACHINE"s;
auto const client_uri = L"/"s;
auto const client_uri_utf8 = "/"s;
// server generate digest challenge
PSecPkgInfoW package_info;
auto result = QuerySecurityPackageInfoW(const_cast<LPWSTR>(WDIGEST_SP_NAME_W), &package_info);
if (result != SEC_E_OK)
{
return EXIT_FAILURE;
}
CredHandle serverCredHandle;
TimeStamp lifetime;
result = AcquireCredentialsHandleW(nullptr, const_cast<LPWSTR>(WDIGEST_SP_NAME_W), SECPKG_CRED_INBOUND, nullptr, nullptr, nullptr, nullptr, &serverCredHandle, &lifetime);
if (result != SEC_E_OK)
{
return EXIT_FAILURE;
}
SecBuffer challengeInBuffers[5];
// token
challengeInBuffers[0].BufferType = SECBUFFER_TOKEN;
challengeInBuffers[0].cbBuffer = 0;
challengeInBuffers[0].pvBuffer = nullptr;
// method
challengeInBuffers[1].BufferType = SECBUFFER_PKG_PARAMS;
challengeInBuffers[1].cbBuffer = 0;
challengeInBuffers[1].pvBuffer = nullptr;
// uri
challengeInBuffers[2].BufferType = SECBUFFER_PKG_PARAMS;
challengeInBuffers[2].cbBuffer = 0;
challengeInBuffers[2].pvBuffer = nullptr;
// body hash
challengeInBuffers[3].BufferType = SECBUFFER_PKG_PARAMS;
challengeInBuffers[3].cbBuffer = 0;
challengeInBuffers[3].pvBuffer = nullptr;
// realm
challengeInBuffers[4].BufferType = SECBUFFER_PKG_PARAMS;
challengeInBuffers[4].cbBuffer = realm.size() * sizeof(wchar_t);
challengeInBuffers[4].pvBuffer = const_cast<void*>(static_cast<void const*>(realm.c_str()));
SecBufferDesc challengeInBufferDesc;
challengeInBufferDesc.ulVersion = 0;
challengeInBufferDesc.cBuffers = 5;
challengeInBufferDesc.pBuffers = challengeInBuffers;
std::string challenge;
challenge.resize(package_info->cbMaxToken);
SecBuffer challengeOutBuffer;
challengeOutBuffer.BufferType = SECBUFFER_TOKEN;
challengeOutBuffer.cbBuffer = challenge.size();
challengeOutBuffer.pvBuffer = const_cast<void*>(static_cast<void const*>(challenge.data()));
SecBufferDesc challengeOutBufferDesc;
challengeOutBufferDesc.ulVersion = 0;
challengeOutBufferDesc.cBuffers = 1;
challengeOutBufferDesc.pBuffers = &challengeOutBuffer;
CtxtHandle serverContextHandle;
unsigned long outContextAttributes;
result = AcceptSecurityContext(&serverCredHandle, nullptr, &challengeInBufferDesc, 0, SECURITY_NETWORK_DREP, &serverContextHandle, &challengeOutBufferDesc, &outContextAttributes, &lifetime);
if (result != SEC_I_CONTINUE_NEEDED)
{
return EXIT_FAILURE;
}
challenge.resize(challengeOutBuffer.cbBuffer);
std::cout << "Challenge: [" << challenge << "]\n";
// client challenge response generation
SEC_WINNT_AUTH_IDENTITY_W auth_data;
auth_data.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
auth_data.User = nullptr;
auth_data.UserLength = 0;
auth_data.Domain = nullptr;
auth_data.DomainLength = 0;
auth_data.Password = nullptr;
auth_data.PasswordLength = 0;
CredHandle clientCredHandle;
result = AcquireCredentialsHandleW(nullptr, const_cast<LPWSTR>(WDIGEST_SP_NAME_W), SECPKG_CRED_OUTBOUND, nullptr, &auth_data, nullptr, nullptr, &clientCredHandle, &lifetime);
if (result != SEC_E_OK)
{
return EXIT_FAILURE;
}
SecBuffer challengeResponseInBuffers[4];
// token
challengeResponseInBuffers[0].BufferType = SECBUFFER_TOKEN;
challengeResponseInBuffers[0].cbBuffer = challenge.size();
challengeResponseInBuffers[0].pvBuffer = const_cast<void*>(static_cast<void const*>(challenge.data()));
// method
challengeResponseInBuffers[1].BufferType = SECBUFFER_PKG_PARAMS;
challengeResponseInBuffers[1].cbBuffer = client_method.size();
challengeResponseInBuffers[1].pvBuffer = const_cast<void*>(static_cast<void const*>(client_method.data()));
// body hash
challengeResponseInBuffers[2].BufferType = SECBUFFER_PKG_PARAMS;
challengeResponseInBuffers[2].cbBuffer = 0;
challengeResponseInBuffers[2].pvBuffer = nullptr;
// target
challengeResponseInBuffers[3].BufferType = SECBUFFER_STREAM;
challengeResponseInBuffers[3].cbBuffer = client_target.size() * sizeof(wchar_t);
challengeResponseInBuffers[3].pvBuffer = const_cast<void*>(static_cast<void const*>(client_target.data()));
SecBufferDesc challengeResponseInBufferDesc;
challengeResponseInBufferDesc.ulVersion = 0;
challengeResponseInBufferDesc.cBuffers = 4;
challengeResponseInBufferDesc.pBuffers = challengeResponseInBuffers;
std::string challengeResponse;
challengeResponse.resize(package_info->cbMaxToken);
SecBuffer challengeResponseOutBuffer;
challengeResponseOutBuffer.BufferType = SECBUFFER_TOKEN;
challengeResponseOutBuffer.cbBuffer = challengeResponse.size();
challengeResponseOutBuffer.pvBuffer = const_cast<void*>(static_cast<void const*>(challengeResponse.data()));
SecBufferDesc challengeResponseOutBufferDesc;
challengeResponseOutBufferDesc.ulVersion = 0;
challengeResponseOutBufferDesc.cBuffers = 1;
challengeResponseOutBufferDesc.pBuffers = &challengeResponseOutBuffer;
CtxtHandle clientContextHandle;
result = InitializeSecurityContextW(&clientCredHandle, nullptr, const_cast<SEC_WCHAR*>(client_uri.c_str()), 0, 0, SECURITY_NETWORK_DREP, &challengeResponseInBufferDesc, 0, &clientContextHandle, &challengeResponseOutBufferDesc, &outContextAttributes, &lifetime);
if(result != SEC_E_OK)
{
return EXIT_FAILURE;
}
challengeResponse.resize(challengeResponseOutBuffer.cbBuffer);
std::cout << "Challenge Response: [" << challengeResponse << "]\n";
// server verify challenge response
SecBuffer verifyChallengeResponseInBuffers[5];
// token
verifyChallengeResponseInBuffers[0].BufferType = SECBUFFER_TOKEN;
verifyChallengeResponseInBuffers[0].cbBuffer = challengeResponse.size();
verifyChallengeResponseInBuffers[0].pvBuffer = const_cast<void*>(static_cast<void const*>(challengeResponse.data()));
// method
verifyChallengeResponseInBuffers[1].BufferType = SECBUFFER_PKG_PARAMS;
verifyChallengeResponseInBuffers[1].cbBuffer = client_method.size();
verifyChallengeResponseInBuffers[1].pvBuffer = const_cast<void*>(static_cast<void const*>(client_method.data()));
// uri
verifyChallengeResponseInBuffers[2].BufferType = SECBUFFER_PKG_PARAMS;
verifyChallengeResponseInBuffers[2].cbBuffer = client_uri_utf8.size();
verifyChallengeResponseInBuffers[2].pvBuffer = const_cast<void*>(static_cast<void const*>(client_uri_utf8.data()));
// body hash
verifyChallengeResponseInBuffers[3].BufferType = SECBUFFER_PKG_PARAMS;
verifyChallengeResponseInBuffers[3].cbBuffer = 0;
verifyChallengeResponseInBuffers[3].pvBuffer = nullptr;
// realm
verifyChallengeResponseInBuffers[4].BufferType = SECBUFFER_PKG_PARAMS;
verifyChallengeResponseInBuffers[4].cbBuffer = realm.size() * sizeof(wchar_t);
verifyChallengeResponseInBuffers[4].pvBuffer = const_cast<void*>(static_cast<void const*>(realm.c_str()));
SecBufferDesc verifyChallengeResponseInBufferDesc;
verifyChallengeResponseInBufferDesc.ulVersion = 0;
verifyChallengeResponseInBufferDesc.cBuffers = 5;
verifyChallengeResponseInBufferDesc.pBuffers = verifyChallengeResponseInBuffers;
std::string verifyChallengeResponse;
verifyChallengeResponse.resize(package_info->cbMaxToken);
SecBuffer verifyChallengeResponseOutBuffer;
challengeOutBuffer.BufferType = SECBUFFER_TOKEN;
challengeOutBuffer.cbBuffer = verifyChallengeResponse.size();
challengeOutBuffer.pvBuffer = const_cast<void*>(static_cast<void const*>(verifyChallengeResponse.data()));
SecBufferDesc verifyChallengeResponseOutBufferDesc;
verifyChallengeResponseOutBufferDesc.ulVersion = 0;
verifyChallengeResponseOutBufferDesc.cBuffers = 1;
verifyChallengeResponseOutBufferDesc.pBuffers = &verifyChallengeResponseOutBuffer;
result = AcceptSecurityContext(&serverCredHandle, &serverContextHandle, &verifyChallengeResponseInBufferDesc, 0, SECURITY_NETWORK_DREP, &serverContextHandle, &verifyChallengeResponseOutBufferDesc, &outContextAttributes, &lifetime);
if (result != SEC_I_COMPLETE_NEEDED)
{
std::cout << "Challenge Response Verification Failed with [0x" << std::hex << result << "]";
return EXIT_FAILURE;
}
std::cout << "Challenge Response Verified";
return EXIT_SUCCESS;
}
The output I get is:
Challenge: [qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v1db70e06e6d35dfd59df6fcd8d3cf7f7671d23e810144d5012972e89ccadaa398f05e8d5ab7a9c0eb3d52b00f4446530312ac45e30f4ac02c",charset=utf-8,realm="realm"]
Challenge Response: [username="",realm="realm",nonce="+Upgraded+v1db70e06e6d35dfd59df6fcd8d3cf7f7671d23e810144d5012972e89ccadaa398f05e8d5ab7a9c0eb3d52b00f4446530312ac45e30f4ac02c",uri="/",cnonce="+Upgraded+v15c1d89757bd55776df6e2788dcdca9220f6aa1af03856d237a0e37a8136b5a44",nc=00000001,algorithm=MD5-sess,response="ee315ddeed6f2d3d68b6cafff9f7d52e",qop="auth",charset=utf-8,hashed-dirs="service-name,channel-binding",service-name="",channel-binding="00000000000000000000000000000000"]
Challenge Response Verification Failed with [0x80090308]
I've tried varying the inputs to the challenge / challenge response and verification parts with no real results.
The MSDN documentation doesn't seem to be a lot of help and most of my insights to this digest API have come from the .net reference source here: https://referencesource.microsoft.com/#System/net/System/Net/_NTAuthentication.cs
Anyone know what I am doing wrong?
UPDATE:
Output from my machine with UseLogonCredential set to "1" (although I tested with it test to "1" and "0" with the exact same output) for the sample application in the answer below I get the following output:
Capabilities: 0x800304
wVersion: 0x1
Max Token Size: 0x1000
SERVER: AcquireCredentialsHandle SUCCESS
CLIENT: AcquireCredentialsHandle SUCCESS
CLIENT: InitializeSecurityContext: SEC_I__CONTINUE_NEEDED
SERVER: AcceptSecurityContext: SEC_I__CONTINUE_NEEDED
CLIENT: InitializeSecurityContext: SEC_I_CONTINUE_NEEDED
AcceptSecurityContext SUCCESS
InitializeSecurityContext (2) failed with 0x8009030A
This to me looks like it's working but I don't see why you need the last client side InitializeSecurityContext anyway as the server side has verified it?
Also I can take out the first "CLIENT: InitializeSecurityContext" as it's not needed and returns a "blank" token anyway and it still works fine if removed.
What I can't get going is using the current users credentials for the client, I have to supply a SEC_WINNT_AUTH_IDENTITY with valid credentials. Is there any reason for this?
The wdigest package is disabled by default since it uses MD5 which is not secure. You must enable the package in the registry to use the code. KB2871997 and Wdigest – Part 1. And why do you want to use wdigest since it isn’t secure anymore.
#include <windows.h>
#include <stdio.h>
#define SECURITY_WIN32
#include <sspi.h>
void wmain(int argc, WCHAR *argv[])
{
SECURITY_STATUS ss;
SecPkgInfo *spi = {0};
ss = QuerySecurityPackageInfo(L"wdigest", &spi);
if (ss != SEC_E_OK)
{
wprintf(L"QuerySecurityPackageInfo failed with %u\n", ss);
return;
}
wprintf(L"Capabilities: 0x%X\n", spi->fCapabilities);
wprintf(L"wVersion: 0x%X\n", spi->wVersion);
wprintf(L"Max Token Size: 0x%X\n", spi->cbMaxToken);
LPBYTE lpbOut = NULL;
lpbOut = (LPBYTE)LocalAlloc(LPTR, spi->cbMaxToken);
if (lpbOut == NULL)
{
wprintf(L"LocalAlloc failed with %u\n", GetLastError());
return;
}
CredHandle ServerCred;
CredHandle ClientCred;
TimeStamp LifeTime;
// server side
ss = AcquireCredentialsHandle(NULL, L"wdigest", SECPKG_CRED_INBOUND, NULL, NULL, NULL, NULL, &ServerCred, &LifeTime);
if (ss != SEC_E_OK)
{
wprintf(L"AcquireCredentialsHandle failed with %u\n", ss);
return;
}
else
wprintf(L"SERVER: AcquireCredentialsHandle SUCCESS\n");
// client side
SEC_WINNT_AUTH_IDENTITY AuthIdentity;
WCHAR szDomain[] = L"domain";
WCHAR szPassword[] = L"password";
WCHAR szUser[] = L"user";
AuthIdentity.Domain = (unsigned short *)szDomain;
AuthIdentity.DomainLength = (unsigned long)wcslen(szDomain);
AuthIdentity.Password = (unsigned short *)szPassword;
AuthIdentity.PasswordLength = (unsigned long)wcslen(szPassword);
AuthIdentity.User = (unsigned short *)szUser;
AuthIdentity.UserLength = (unsigned long)wcslen(szUser);
AuthIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
/*
AuthIdentity.Domain = (unsigned short *)NULL;
AuthIdentity.DomainLength = (unsigned long)0;
AuthIdentity.Password = (unsigned short *)NULL;
AuthIdentity.PasswordLength = (unsigned long)0;
AuthIdentity.User = (unsigned short *)NULL;
AuthIdentity.UserLength = (unsigned long)0;
AuthIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;*/
ss = AcquireCredentialsHandle(NULL, L"wdigest", SECPKG_CRED_OUTBOUND, NULL, &AuthIdentity, NULL, NULL, &ClientCred, &LifeTime);
if (ss != SEC_E_OK)
{
wprintf(L"OUTBOUND: AcquireCredentialsHandle failed with 0x%X\n", ss);
return;
}
else
wprintf(L"CLIENT: AcquireCredentialsHandle SUCCESS\n");
SecBufferDesc OutputBuffers;
SecBuffer TempTokensOut[6];
OutputBuffers.ulVersion = SECBUFFER_VERSION;
OutputBuffers.cBuffers = 1;
OutputBuffers.pBuffers = TempTokensOut;
TempTokensOut[0].BufferType = SECBUFFER_TOKEN;
TempTokensOut[0].cbBuffer = spi->cbMaxToken;
TempTokensOut[0].pvBuffer = lpbOut;
CtxtHandle ClientCtxtHandle;
ULONG ClientContextRetFlags = 0;
BOOL bContinue = FALSE;
ss = InitializeSecurityContext(&ClientCred, NULL, L"HTTP//test.com", /*ISC_REQ_INTEGRITY | ISC_REQ_CONFIDENTIALITY | */ISC_REQ_REPLAY_DETECT | ISC_REQ_CONNECTION, NULL, SECURITY_NATIVE_DREP, NULL, NULL,
&ClientCtxtHandle, &OutputBuffers, &ClientContextRetFlags, &LifeTime);
if (ss == SEC_I_CONTINUE_NEEDED)
{
bContinue = TRUE;
wprintf(L"CLIENT: InitializeSecurityContext: SEC_I__CONTINUE_NEEDED\n");
}
else
{
if (ss != SEC_E_OK)
{
wprintf(L"InitializeSecurityContext failed with 0x%X\n", ss);
return;
}
}
SecBufferDesc InputBuffers;
SecBuffer TempTokensIn[10];
LPBYTE lpbIn = NULL;
lpbIn = (LPBYTE)LocalAlloc(LPTR, spi->cbMaxToken);
if (lpbIn == NULL)
{
wprintf(L"LocalAlloc failed with %u\n", GetLastError());
return;
}
InputBuffers.ulVersion = SECBUFFER_VERSION;
InputBuffers.cBuffers = 1;
InputBuffers.pBuffers = TempTokensIn;
TempTokensIn[0].BufferType = SECBUFFER_TOKEN;
TempTokensIn[0].cbBuffer = TempTokensOut[0].cbBuffer;
TempTokensIn[0].pvBuffer = TempTokensOut[0].pvBuffer;
OutputBuffers.ulVersion = SECBUFFER_VERSION;
OutputBuffers.cBuffers = 1;
OutputBuffers.pBuffers = TempTokensOut;
TempTokensOut[0].BufferType = SECBUFFER_TOKEN;
TempTokensOut[0].cbBuffer = spi->cbMaxToken;
TempTokensOut[0].pvBuffer = lpbOut;
ULONG TargetDataRep = 0;
CtxtHandle ServerCtxtHandle;
ULONG ServerContextRetFlags = 0;
ss = AcceptSecurityContext(&ServerCred, NULL, &InputBuffers, 0 /*ASC_REQ_INTEGRITY | ASC_REQ_CONFIDENTIALITY*/, TargetDataRep, &ServerCtxtHandle, &OutputBuffers, &ServerContextRetFlags, &LifeTime);
if (ss == SEC_I_CONTINUE_NEEDED)
{
bContinue = TRUE;
wprintf(L"SERVER: AcceptSecurityContext: SEC_I__CONTINUE_NEEDED\n");
}
else
{
if (ss != SEC_E_OK)
{
wprintf(L"AcceptSecurityContext failed with 0x%X\n", ss);
return;
}
}
InputBuffers.ulVersion = SECBUFFER_VERSION;
InputBuffers.cBuffers = 1;
InputBuffers.pBuffers = TempTokensIn;
TempTokensIn[0].BufferType = SECBUFFER_TOKEN;
TempTokensIn[0].cbBuffer = TempTokensOut[0].cbBuffer;
TempTokensIn[0].pvBuffer = TempTokensOut[0].pvBuffer;
OutputBuffers.ulVersion = SECBUFFER_VERSION;
OutputBuffers.cBuffers = 1;
OutputBuffers.pBuffers = TempTokensOut;
TempTokensOut[0].BufferType = SECBUFFER_TOKEN;
TempTokensOut[0].cbBuffer = spi->cbMaxToken;
TempTokensOut[0].pvBuffer = lpbOut;
ss = InitializeSecurityContext(&ClientCred, NULL, L"HTTP//test.com", /*ISC_REQ_INTEGRITY | ISC_REQ_CONFIDENTIALITY | */ISC_REQ_REPLAY_DETECT | ISC_REQ_CONNECTION, NULL, SECURITY_NATIVE_DREP, &InputBuffers, NULL,
&ClientCtxtHandle, &OutputBuffers, &ClientContextRetFlags, &LifeTime);
if (ss == SEC_I_CONTINUE_NEEDED)
{
bContinue = TRUE;
wprintf(L"CLIENT: InitializeSecurityContext: SEC_I_CONTINUE_NEEDED\n");
}
else
{
if (ss != SEC_E_OK)
{
wprintf(L"InitializeSecurityContext (2) failed with 0x%X\n", ss);
return;
}
}
InputBuffers.ulVersion = SECBUFFER_VERSION;
InputBuffers.cBuffers = 1;
InputBuffers.pBuffers = TempTokensIn;
TempTokensIn[0].BufferType = SECBUFFER_TOKEN;
TempTokensIn[0].cbBuffer = TempTokensOut[0].cbBuffer;
TempTokensIn[0].pvBuffer = TempTokensOut[0].pvBuffer;
OutputBuffers.ulVersion = SECBUFFER_VERSION;
OutputBuffers.cBuffers = 1;
OutputBuffers.pBuffers = TempTokensOut;
TempTokensOut[0].BufferType = SECBUFFER_TOKEN;
TempTokensOut[0].cbBuffer = spi->cbMaxToken;
TempTokensOut[0].pvBuffer = lpbOut;
ss = AcceptSecurityContext(&ServerCred, NULL, &InputBuffers, 0 /*ASC_REQ_INTEGRITY | ASC_REQ_CONFIDENTIALITY*/, TargetDataRep, &ServerCtxtHandle, &OutputBuffers, &ServerContextRetFlags, &LifeTime);
if (ss == SEC_I_CONTINUE_NEEDED)
{
bContinue = TRUE;
}
else if (ss != SEC_E_OK)
{
wprintf(L"AcceptSecurityContext (2) failed with 0x%X\n", ss);
return;
}
else
wprintf(L"AcceptSecurityContext SUCCESS\n");
InputBuffers.ulVersion = SECBUFFER_VERSION;
InputBuffers.cBuffers = 1;
InputBuffers.pBuffers = TempTokensIn;
TempTokensIn[0].BufferType = SECBUFFER_TOKEN;
TempTokensIn[0].cbBuffer = TempTokensOut[0].cbBuffer;
TempTokensIn[0].pvBuffer = TempTokensOut[0].pvBuffer;
OutputBuffers.ulVersion = SECBUFFER_VERSION;
OutputBuffers.cBuffers = 1;
OutputBuffers.pBuffers = TempTokensOut;
TempTokensOut[0].BufferType = SECBUFFER_TOKEN;
TempTokensOut[0].cbBuffer = spi->cbMaxToken;
TempTokensOut[0].pvBuffer = lpbOut;
ss = InitializeSecurityContext(&ClientCred, NULL, L"HTTP//test.com", /*ISC_REQ_INTEGRITY | ISC_REQ_CONFIDENTIALITY |*/ ISC_REQ_REPLAY_DETECT | ISC_REQ_CONNECTION, NULL, SECURITY_NATIVE_DREP, &InputBuffers, NULL,
&ClientCtxtHandle, &OutputBuffers, &ClientContextRetFlags, &LifeTime);
if (ss == SEC_I_CONTINUE_NEEDED)
{
bContinue = TRUE;
}
else if (ss != SEC_E_OK)
{
wprintf(L"InitializeSecurityContext (2) failed with 0x%X\n", ss);
return;
}
else
wprintf(L"InitializeSecurityContext SUCCESS\n");
ss = FreeCredentialsHandle(&ClientCred);
if (ss != SEC_E_OK)
{
wprintf(L"FreeCredentialsHandle failed with %u\n", ss);
return;
}
ss = FreeCredentialsHandle(&ServerCred);
if (ss != SEC_E_OK)
{
wprintf(L"FreeCredentialsHandle failed with %u\n", ss);
return;
}
if (lpbOut != NULL)
LocalFree((HLOCAL)lpbOut);
if (lpbIn != NULL)
LocalFree((HLOCAL)lpbIn);
if (spi != NULL)
FreeContextBuffer((PVOID)spi);
}
Can this sample work when the wdigest package is enabled ?