Files

1751 lines
53 KiB
C

/**
* WinPR: Windows Portable Runtime
* Negotiate Security Package
*
* Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2017 Dorian Ducournau <dorian.ducournau@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <winpr/config.h>
#include <winpr/crt.h>
#include <winpr/wtypes.h>
#include <winpr/assert.h>
#include <winpr/sspi.h>
#include <winpr/tchar.h>
#include <winpr/registry.h>
#include <winpr/build-config.h>
#include <winpr/asn1.h>
#include "negotiate.h"
#include "../NTLM/ntlm.h"
#include "../NTLM/ntlm_export.h"
#include "../Kerberos/kerberos.h"
#include "../sspi.h"
#include "../../utils.h"
#include "../../log.h"
#define TAG WINPR_TAG("negotiate")
#define NEGO_REG_KEY "Software\\%s\\SSPI\\Negotiate"
static const char PACKAGE_NAME_DISABLE_ALL[] = "none";
static const char PACKAGE_NAME_NTLM[] = "ntlm";
static const char PACKAGE_NAME_KERBEROS[] = "kerberos";
static const char PACKAGE_NAME_KERBEROS_U2U[] = "u2u";
typedef struct
{
const TCHAR* name;
const SecurityFunctionTableA* table;
const SecurityFunctionTableW* table_w;
} SecPkg;
struct Mech_st
{
const WinPrAsn1_OID* oid;
const SecPkg* pkg;
const UINT flags;
const BOOL preferred;
};
typedef struct
{
const Mech* mech;
CredHandle cred;
BOOL valid;
} MechCred;
const SecPkgInfoA NEGOTIATE_SecPkgInfoA = {
0x00083BB3, /* fCapabilities */
1, /* wVersion */
0x0009, /* wRPCID */
0x00002FE0, /* cbMaxToken */
"Negotiate", /* Name */
"Microsoft Package Negotiator" /* Comment */
};
static WCHAR NEGOTIATE_SecPkgInfoW_NameBuffer[32] = WINPR_C_ARRAY_INIT;
static WCHAR NEGOTIATE_SecPkgInfoW_CommentBuffer[32] = WINPR_C_ARRAY_INIT;
const SecPkgInfoW NEGOTIATE_SecPkgInfoW = {
0x00083BB3, /* fCapabilities */
1, /* wVersion */
0x0009, /* wRPCID */
0x00002FE0, /* cbMaxToken */
NEGOTIATE_SecPkgInfoW_NameBuffer, /* Name */
NEGOTIATE_SecPkgInfoW_CommentBuffer /* Comment */
};
static const WinPrAsn1_OID spnego_OID = { 6, (BYTE*)"\x2b\x06\x01\x05\x05\x02" };
static const WinPrAsn1_OID kerberos_u2u_OID = { 10,
(BYTE*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03" };
static const WinPrAsn1_OID kerberos_OID = { 9, (BYTE*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
static const WinPrAsn1_OID kerberos_wrong_OID = { 9,
(BYTE*)"\x2a\x86\x48\x82\xf7\x12\x01\x02\x02" };
static const WinPrAsn1_OID ntlm_OID = { 10, (BYTE*)"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" };
static const WinPrAsn1_OID negoex_OID = { 10, (BYTE*)"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e" };
#ifdef WITH_KRB5
static const SecPkg SecPkgTable[] = {
{ KERBEROS_SSP_NAME, &KERBEROS_SecurityFunctionTableA, &KERBEROS_SecurityFunctionTableW },
{ KERBEROS_SSP_NAME, &KERBEROS_SecurityFunctionTableA, &KERBEROS_SecurityFunctionTableW },
{ NTLM_SSP_NAME, &NTLM_SecurityFunctionTableA, &NTLM_SecurityFunctionTableW }
};
static const Mech MechTable[] = {
{ &kerberos_u2u_OID, &SecPkgTable[0], ISC_REQ_INTEGRITY | ISC_REQ_USE_SESSION_KEY, TRUE },
{ &kerberos_OID, &SecPkgTable[1], ISC_REQ_INTEGRITY, TRUE },
{ &ntlm_OID, &SecPkgTable[2], 0, FALSE },
};
#else
static const SecPkg SecPkgTable[] = { { NTLM_SSP_NAME, &NTLM_SecurityFunctionTableA,
&NTLM_SecurityFunctionTableW } };
static const Mech MechTable[] = {
{ &ntlm_OID, &SecPkgTable[0], 0, FALSE },
};
#endif
static const size_t MECH_COUNT = sizeof(MechTable) / sizeof(Mech);
enum NegState
{
NOSTATE = -1,
ACCEPT_COMPLETED,
ACCEPT_INCOMPLETE,
REJECT,
REQUEST_MIC
};
typedef struct
{
enum NegState negState;
BOOL init;
WinPrAsn1_OID supportedMech;
SecBuffer mechTypes;
SecBuffer mechToken;
SecBuffer mic;
} NegToken;
static const NegToken empty_neg_token = { NOSTATE, FALSE,
{ 0, nullptr }, { 0, 0, nullptr },
{ 0, 0, nullptr }, { 0, 0, nullptr } };
static NEGOTIATE_CONTEXT* negotiate_ContextNew(NEGOTIATE_CONTEXT* init_context)
{
NEGOTIATE_CONTEXT* context = nullptr;
WINPR_ASSERT(init_context);
context = calloc(1, sizeof(NEGOTIATE_CONTEXT));
if (!context)
return nullptr;
if (init_context->spnego)
{
init_context->mechTypes.pvBuffer = malloc(init_context->mechTypes.cbBuffer);
if (!init_context->mechTypes.pvBuffer)
{
free(context);
return nullptr;
}
}
*context = *init_context;
return context;
}
static void negotiate_ContextFree(NEGOTIATE_CONTEXT* context)
{
WINPR_ASSERT(context);
if (context->mechTypes.pvBuffer)
free(context->mechTypes.pvBuffer);
free(context);
}
static const char* negotiate_mech_name(const WinPrAsn1_OID* oid)
{
if (sspi_gss_oid_compare(oid, &spnego_OID))
return "SPNEGO (1.3.6.1.5.5.2)";
else if (sspi_gss_oid_compare(oid, &kerberos_u2u_OID))
return "Kerberos user to user (1.2.840.113554.1.2.2.3)";
else if (sspi_gss_oid_compare(oid, &kerberos_OID))
return "Kerberos (1.2.840.113554.1.2.2)";
else if (sspi_gss_oid_compare(oid, &kerberos_wrong_OID))
return "Kerberos [wrong OID] (1.2.840.48018.1.2.2)";
else if (sspi_gss_oid_compare(oid, &ntlm_OID))
return "NTLM (1.3.6.1.4.1.311.2.2.10)";
else if (sspi_gss_oid_compare(oid, &negoex_OID))
return "NegoEx (1.3.6.1.4.1.311.2.2.30)";
else
return "Unknown mechanism";
}
static const Mech* negotiate_GetMechByOID(const WinPrAsn1_OID* oid)
{
WINPR_ASSERT(oid);
WinPrAsn1_OID testOid = *oid;
if (sspi_gss_oid_compare(&testOid, &kerberos_wrong_OID))
{
testOid.len = kerberos_OID.len;
testOid.data = kerberos_OID.data;
}
for (size_t i = 0; i < MECH_COUNT; i++)
{
if (sspi_gss_oid_compare(&testOid, MechTable[i].oid))
return &MechTable[i];
}
return nullptr;
}
static PSecHandle negotiate_FindCredential(MechCred* creds, const Mech* mech)
{
WINPR_ASSERT(creds);
if (!mech)
return nullptr;
for (size_t i = 0; i < MECH_COUNT; i++)
{
MechCred* cred = &creds[i];
if (cred->mech == mech)
{
if (cred->valid)
return &cred->cred;
return nullptr;
}
}
return nullptr;
}
static BOOL negotiate_get_dword(HKEY hKey, const char* subkey, DWORD* pdwValue)
{
DWORD dwValue = 0;
DWORD dwType = 0;
DWORD dwSize = sizeof(dwValue);
LONG rc = RegQueryValueExA(hKey, subkey, nullptr, &dwType, (BYTE*)&dwValue, &dwSize);
if (rc != ERROR_SUCCESS)
return FALSE;
if (dwType != REG_DWORD)
return FALSE;
*pdwValue = dwValue;
return TRUE;
}
static BOOL negotiate_get_config_from_auth_package_list(void* pAuthData, BOOL* kerberos, BOOL* ntlm,
BOOL* u2u)
{
BOOL rc = FALSE;
char* tok_ctx = nullptr;
char* PackageList = nullptr;
if (!sspi_CopyAuthPackageListA((const SEC_WINNT_AUTH_IDENTITY_INFO*)pAuthData, &PackageList))
return FALSE;
char* tok_ptr = strtok_s(PackageList, ",", &tok_ctx);
while (tok_ptr)
{
const char* PackageName = tok_ptr;
BOOL PackageInclude = TRUE;
if (PackageName[0] == '!')
{
PackageName = &PackageName[1];
PackageInclude = FALSE;
}
if (_stricmp(PackageName, PACKAGE_NAME_NTLM) == 0)
{
*ntlm = PackageInclude;
}
else if (_stricmp(PackageName, PACKAGE_NAME_KERBEROS) == 0)
{
*kerberos = PackageInclude;
}
else if (_stricmp(PackageName, PACKAGE_NAME_KERBEROS_U2U) == 0)
{
*u2u = PackageInclude;
}
else if (_stricmp(PackageName, PACKAGE_NAME_DISABLE_ALL) == 0)
{
*kerberos = FALSE;
*ntlm = FALSE;
*u2u = FALSE;
if (PackageName != PackageList)
{
WLog_WARN(TAG, "Special keyword '%s' not first in list, aborting", PackageName);
goto fail;
}
}
else
{
WLog_WARN(TAG, "Unknown authentication package name: %s, ignoring", PackageName);
}
tok_ptr = strtok_s(nullptr, ",", &tok_ctx);
}
rc = TRUE;
fail:
free(PackageList);
return rc;
}
static BOOL negotiate_get_config(void* pAuthData, BOOL* kerberos, BOOL* ntlm, BOOL* u2u)
{
HKEY hKey = nullptr;
WINPR_ASSERT(kerberos);
WINPR_ASSERT(ntlm);
WINPR_ASSERT(u2u);
#if !defined(WITH_KRB5_NO_NTLM_FALLBACK)
*ntlm = TRUE;
#else
*ntlm = FALSE;
#endif
#if defined(WITH_KRB5)
*kerberos = TRUE;
*u2u = TRUE;
#else
*kerberos = FALSE;
*u2u = FALSE;
#endif
if (negotiate_get_config_from_auth_package_list(pAuthData, kerberos, ntlm, u2u))
{
return TRUE; // use explicit authentication package list
}
{
char* key = winpr_getApplicatonDetailsRegKey(NEGO_REG_KEY);
if (key)
{
const LONG rc =
RegOpenKeyExA(HKEY_LOCAL_MACHINE, key, 0, KEY_READ | KEY_WOW64_64KEY, &hKey);
free(key);
if (rc == ERROR_SUCCESS)
{
DWORD dwValue = 0;
if (negotiate_get_dword(hKey, PACKAGE_NAME_KERBEROS, &dwValue))
*kerberos = (dwValue != 0);
if (negotiate_get_dword(hKey, PACKAGE_NAME_KERBEROS_U2U, &dwValue))
*u2u = (dwValue != 0);
#if !defined(WITH_KRB5_NO_NTLM_FALLBACK)
if (negotiate_get_dword(hKey, PACKAGE_NAME_NTLM, &dwValue))
*ntlm = (dwValue != 0);
#endif
RegCloseKey(hKey);
}
}
}
return TRUE;
}
static BOOL negotiate_write_neg_token(PSecBuffer output_buffer, NegToken* token)
{
WINPR_ASSERT(output_buffer);
WINPR_ASSERT(token);
BOOL ret = FALSE;
WinPrAsn1Encoder* enc = nullptr;
WinPrAsn1_MemoryChunk mechTypes = { token->mechTypes.cbBuffer, token->mechTypes.pvBuffer };
WinPrAsn1_OctetString mechToken = { token->mechToken.cbBuffer, token->mechToken.pvBuffer };
WinPrAsn1_OctetString mechListMic = { token->mic.cbBuffer, token->mic.pvBuffer };
wStream s;
size_t len = 0;
enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER);
if (!enc)
return FALSE;
/* For NegTokenInit wrap in an initialContextToken */
if (token->init)
{
/* InitialContextToken [APPLICATION 0] IMPLICIT SEQUENCE */
if (!WinPrAsn1EncAppContainer(enc, 0))
goto cleanup;
/* thisMech MechType OID */
if (!WinPrAsn1EncOID(enc, &spnego_OID))
goto cleanup;
}
/* innerContextToken [0] NegTokenInit or [1] NegTokenResp */
if (!WinPrAsn1EncContextualSeqContainer(enc, token->init ? 0 : 1))
goto cleanup;
WLog_DBG(TAG, token->init ? "Writing negTokenInit..." : "Writing negTokenResp...");
/* mechTypes [0] MechTypeList (mechTypes already contains the SEQUENCE tag) */
if (token->init)
{
if (!WinPrAsn1EncContextualRawContent(enc, 0, &mechTypes))
goto cleanup;
WLog_DBG(TAG, "\tmechTypes [0] (%" PRIu32 " bytes)", token->mechTypes.cbBuffer);
}
/* negState [0] ENUMERATED */
else if (token->negState != NOSTATE)
{
if (!WinPrAsn1EncContextualEnumerated(enc, 0, token->negState))
goto cleanup;
WLog_DBG(TAG, "\tnegState [0] (%d)", token->negState);
}
/* supportedMech [1] OID */
if (token->supportedMech.len)
{
if (!WinPrAsn1EncContextualOID(enc, 1, &token->supportedMech))
goto cleanup;
WLog_DBG(TAG, "\tsupportedMech [1] (%s)", negotiate_mech_name(&token->supportedMech));
}
/* mechToken [2] OCTET STRING */
if (token->mechToken.cbBuffer)
{
if (WinPrAsn1EncContextualOctetString(enc, 2, &mechToken) == 0)
goto cleanup;
WLog_DBG(TAG, "\tmechToken [2] (%" PRIu32 " bytes)", token->mechToken.cbBuffer);
}
/* mechListMIC [3] OCTET STRING */
if (token->mic.cbBuffer)
{
if (WinPrAsn1EncContextualOctetString(enc, 3, &mechListMic) == 0)
goto cleanup;
WLog_DBG(TAG, "\tmechListMIC [3] (%" PRIu32 " bytes)", token->mic.cbBuffer);
}
/* NegTokenInit or NegTokenResp */
if (!WinPrAsn1EncEndContainer(enc))
goto cleanup;
if (token->init)
{
/* initialContextToken */
if (!WinPrAsn1EncEndContainer(enc))
goto cleanup;
}
if (!WinPrAsn1EncStreamSize(enc, &len) || len > output_buffer->cbBuffer)
goto cleanup;
if (len > UINT32_MAX)
goto cleanup;
Stream_StaticInit(&s, output_buffer->pvBuffer, len);
if (WinPrAsn1EncToStream(enc, &s))
{
output_buffer->cbBuffer = (UINT32)len;
ret = TRUE;
}
cleanup:
WinPrAsn1Encoder_Free(&enc);
return ret;
}
static BOOL negotiate_read_neg_token(PSecBuffer input, NegToken* token)
{
WinPrAsn1Decoder dec = WinPrAsn1Decoder_init();
WinPrAsn1Decoder dec2 = WinPrAsn1Decoder_init();
WinPrAsn1_OID oid = WINPR_C_ARRAY_INIT;
WinPrAsn1_tagId contextual = 0;
WinPrAsn1_tag tag = 0;
size_t len = 0;
WinPrAsn1_OctetString octet_string = WINPR_C_ARRAY_INIT;
BOOL err = 0;
WINPR_ASSERT(input);
WINPR_ASSERT(token);
WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, input->pvBuffer, input->cbBuffer);
if (!WinPrAsn1DecPeekTag(&dec, &tag))
return FALSE;
if (tag == 0x60)
{
/* initialContextToken [APPLICATION 0] */
if (!WinPrAsn1DecReadApp(&dec, &tag, &dec2) || tag != 0)
return FALSE;
dec = dec2;
/* thisMech OID */
if (!WinPrAsn1DecReadOID(&dec, &oid, FALSE))
return FALSE;
if (!sspi_gss_oid_compare(&spnego_OID, &oid))
return FALSE;
/* [0] NegTokenInit */
if (!WinPrAsn1DecReadContextualSequence(&dec, 0, &err, &dec2))
return FALSE;
token->init = TRUE;
}
/* [1] NegTokenResp */
else if (!WinPrAsn1DecReadContextualSequence(&dec, 1, &err, &dec2))
return FALSE;
dec = dec2;
WLog_DBG(TAG, token->init ? "Reading negTokenInit..." : "Reading negTokenResp...");
/* Read NegTokenResp sequence members */
do
{
if (!WinPrAsn1DecReadContextualTag(&dec, &contextual, &dec2))
return FALSE;
switch (contextual)
{
case 0:
if (token->init)
{
/* mechTypes [0] MechTypeList */
wStream s = WinPrAsn1DecGetStream(&dec2);
token->mechTypes.BufferType = SECBUFFER_TOKEN;
const size_t mlen = Stream_Length(&s);
if (mlen > UINT32_MAX)
return FALSE;
token->mechTypes.cbBuffer = (UINT32)mlen;
token->mechTypes.pvBuffer = Stream_Buffer(&s);
WLog_DBG(TAG, "\tmechTypes [0] (%" PRIu32 " bytes)", token->mechTypes.cbBuffer);
}
else
{
/* negState [0] ENUMERATED */
WinPrAsn1_ENUMERATED rd = 0;
if (!WinPrAsn1DecReadEnumerated(&dec2, &rd))
return FALSE;
switch (rd)
{
case NOSTATE:
case ACCEPT_COMPLETED:
case ACCEPT_INCOMPLETE:
case REJECT:
case REQUEST_MIC:
break;
default:
WLog_ERR(TAG, "Invalid negState enumeration value %d", rd);
return FALSE;
}
token->negState = WINPR_ASSERTING_INT_CAST(enum NegState, rd);
WLog_DBG(TAG, "\tnegState [0] (%d)", rd);
}
break;
case 1:
if (token->init)
{
/* reqFlags [1] ContextFlags BIT STRING (ignored) */
if (!WinPrAsn1DecPeekTagAndLen(&dec2, &tag, &len) || (tag != ER_TAG_BIT_STRING))
return FALSE;
WLog_DBG(TAG, "\treqFlags [1] (%" PRIuz " bytes)", len);
}
else
{
/* supportedMech [1] MechType */
if (!WinPrAsn1DecReadOID(&dec2, &token->supportedMech, FALSE))
return FALSE;
WLog_DBG(TAG, "\tsupportedMech [1] (%s)",
negotiate_mech_name(&token->supportedMech));
}
break;
case 2:
/* mechToken [2] OCTET STRING */
if (!WinPrAsn1DecReadOctetString(&dec2, &octet_string, FALSE))
return FALSE;
if (octet_string.len > UINT32_MAX)
return FALSE;
token->mechToken.cbBuffer = (UINT32)octet_string.len;
token->mechToken.pvBuffer = octet_string.data;
token->mechToken.BufferType = SECBUFFER_TOKEN;
WLog_DBG(TAG, "\tmechToken [2] (%" PRIuz " bytes)", octet_string.len);
break;
case 3:
/* mechListMic [3] OCTET STRING */
if (!WinPrAsn1DecReadOctetString(&dec2, &octet_string, FALSE))
return FALSE;
if (octet_string.len > UINT32_MAX)
return FALSE;
token->mic.cbBuffer = (UINT32)octet_string.len;
token->mic.pvBuffer = octet_string.data;
token->mic.BufferType = SECBUFFER_TOKEN;
WLog_DBG(TAG, "\tmechListMIC [3] (%" PRIuz " bytes)", octet_string.len);
break;
default:
WLog_ERR(TAG, "unknown contextual item %d", contextual);
return FALSE;
}
} while (WinPrAsn1DecPeekTag(&dec, &tag));
return TRUE;
}
static SECURITY_STATUS negotiate_mic_exchange(NEGOTIATE_CONTEXT* context, NegToken* input_token,
NegToken* output_token, PSecBuffer output_buffer)
{
SecBuffer mic_buffers[2] = WINPR_C_ARRAY_INIT;
SecBufferDesc mic_buffer_desc = { SECBUFFER_VERSION, 2, mic_buffers };
SECURITY_STATUS status = 0;
WINPR_ASSERT(context);
WINPR_ASSERT(input_token);
WINPR_ASSERT(output_token);
WINPR_ASSERT(context->mech);
WINPR_ASSERT(context->mech->pkg);
const SecurityFunctionTableA* table = context->mech->pkg->table;
WINPR_ASSERT(table);
mic_buffers[0] = context->mechTypes;
/* Verify MIC if we received one */
if (input_token->mic.cbBuffer > 0)
{
mic_buffers[1] = input_token->mic;
status = table->VerifySignature(&context->sub_context, &mic_buffer_desc, 0, nullptr);
if (status != SEC_E_OK)
return status;
output_token->negState = ACCEPT_COMPLETED;
}
/* If peer expects a MIC then generate it */
if (input_token->negState != ACCEPT_COMPLETED)
{
/* Store the mic token after the mech token in the output buffer */
output_token->mic.BufferType = SECBUFFER_TOKEN;
if (output_buffer)
{
output_token->mic.cbBuffer = output_buffer->cbBuffer - output_token->mechToken.cbBuffer;
output_token->mic.pvBuffer =
(BYTE*)output_buffer->pvBuffer + output_token->mechToken.cbBuffer;
}
mic_buffers[1] = output_token->mic;
status = table->MakeSignature(&context->sub_context, 0, &mic_buffer_desc, 0);
if (status != SEC_E_OK)
return status;
output_token->mic = mic_buffers[1];
}
/* When using NTLM cipher states need to be reset after mic exchange */
const TCHAR* name = sspi_SecureHandleGetUpperPointer(&context->sub_context);
if (!name)
return SEC_E_INTERNAL_ERROR;
if (_tcsncmp(name, NTLM_SSP_NAME, ARRAYSIZE(NTLM_SSP_NAME)) == 0)
{
if (!ntlm_reset_cipher_state(&context->sub_context))
return SEC_E_INTERNAL_ERROR;
}
return SEC_E_OK;
}
static SECURITY_STATUS SEC_ENTRY negotiate_InitializeSecurityContextW(
PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq,
ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
{
NEGOTIATE_CONTEXT* context = nullptr;
NEGOTIATE_CONTEXT init_context = NEGOTIATE_CONTEXT_init();
MechCred* creds = nullptr;
PCtxtHandle sub_context = nullptr;
PCredHandle sub_cred = nullptr;
NegToken input_token = empty_neg_token;
NegToken output_token = empty_neg_token;
PSecBuffer input_buffer = nullptr;
PSecBuffer output_buffer = nullptr;
PSecBuffer bindings_buffer = nullptr;
SecBuffer mech_input_buffers[2] = WINPR_C_ARRAY_INIT;
SecBufferDesc mech_input = { SECBUFFER_VERSION, 2, mech_input_buffers };
SecBufferDesc mech_output = { SECBUFFER_VERSION, 1, &output_token.mechToken };
SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
SECURITY_STATUS sub_status = SEC_E_INTERNAL_ERROR;
WinPrAsn1Encoder* enc = nullptr;
wStream s;
const Mech* mech = nullptr;
if (!phCredential || !SecIsValidHandle(phCredential))
return SEC_E_NO_CREDENTIALS;
creds = sspi_SecureHandleGetLowerPointer(phCredential);
/* behave like windows SSPIs that don't want empty context */
if (phContext && !phContext->dwLower && !phContext->dwUpper)
return SEC_E_INVALID_HANDLE;
context = sspi_SecureHandleGetLowerPointer(phContext);
if (pInput)
{
input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
bindings_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_CHANNEL_BINDINGS);
}
if (pOutput)
output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
if (!context)
{
enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER);
if (!enc)
return SEC_E_INSUFFICIENT_MEMORY;
if (!WinPrAsn1EncSeqContainer(enc))
goto cleanup;
for (size_t i = 0; i < MECH_COUNT; i++)
{
MechCred* cred = &creds[i];
const SecPkg* pkg = MechTable[i].pkg;
WINPR_ASSERT(pkg);
WINPR_ASSERT(pkg->table_w);
if (!cred->valid)
{
WLog_DBG(TAG, "Unavailable mechanism: %s", negotiate_mech_name(cred->mech->oid));
continue;
}
/* Send an optimistic token for the first valid mechanism */
if (!init_context.mech)
{
/* Use the output buffer to store the optimistic token */
if (!output_buffer)
goto cleanup;
CopyMemory(&output_token.mechToken, output_buffer, sizeof(SecBuffer));
if (bindings_buffer)
mech_input_buffers[0] = *bindings_buffer;
WINPR_ASSERT(pkg->table_w->InitializeSecurityContextW);
sub_status = pkg->table_w->InitializeSecurityContextW(
&cred->cred, nullptr, pszTargetName, fContextReq | cred->mech->flags, Reserved1,
TargetDataRep, &mech_input, Reserved2, &init_context.sub_context, &mech_output,
pfContextAttr, ptsExpiry);
/* If the mechanism failed we can't use it; skip */
if (IsSecurityStatusError(sub_status))
{
if (SecIsValidHandle(&init_context.sub_context))
{
WINPR_ASSERT(pkg->table_w->DeleteSecurityContext);
pkg->table_w->DeleteSecurityContext(&init_context.sub_context);
}
cred->valid = FALSE;
continue;
}
init_context.mech = cred->mech;
}
if (!WinPrAsn1EncOID(enc, cred->mech->oid))
goto cleanup;
WLog_DBG(TAG, "Available mechanism: %s", negotiate_mech_name(cred->mech->oid));
}
/* No usable mechanisms were found */
if (!init_context.mech)
goto cleanup;
/* If the only available mech is NTLM use it directly otherwise use spnego */
if (init_context.mech->oid == &ntlm_OID)
{
init_context.spnego = FALSE;
output_buffer->cbBuffer = output_token.mechToken.cbBuffer;
WLog_DBG(TAG, "Using direct NTLM");
}
else
{
init_context.spnego = TRUE;
init_context.mechTypes.BufferType = SECBUFFER_DATA;
const size_t cb = WinPrAsn1EncEndContainer(enc);
WINPR_ASSERT(cb <= UINT32_MAX);
init_context.mechTypes.cbBuffer = (UINT32)cb;
}
/* Allocate memory for the new context */
context = negotiate_ContextNew(&init_context);
if (!context)
{
init_context.mech->pkg->table->DeleteSecurityContext(&init_context.sub_context);
WinPrAsn1Encoder_Free(&enc);
return SEC_E_INSUFFICIENT_MEMORY;
}
sspi_SecureHandleSetUpperPointer(phNewContext, NEGO_SSP_NAME);
sspi_SecureHandleSetLowerPointer(phNewContext, context);
if (!context->spnego)
{
status = sub_status;
goto cleanup;
}
/* Write mechTypesList */
Stream_StaticInit(&s, context->mechTypes.pvBuffer, context->mechTypes.cbBuffer);
if (!WinPrAsn1EncToStream(enc, &s))
goto cleanup;
output_token.mechTypes.cbBuffer = context->mechTypes.cbBuffer;
output_token.mechTypes.pvBuffer = context->mechTypes.pvBuffer;
output_token.init = TRUE;
if (sub_status == SEC_E_OK)
context->state = NEGOTIATE_STATE_FINAL_OPTIMISTIC;
}
else
{
if (!input_buffer)
return SEC_E_INVALID_TOKEN;
sub_context = &context->sub_context;
sub_cred = negotiate_FindCredential(creds, context->mech);
if (!context->spnego)
{
return context->mech->pkg->table_w->InitializeSecurityContextW(
sub_cred, sub_context, pszTargetName, fContextReq | context->mech->flags, Reserved1,
TargetDataRep, pInput, Reserved2, sub_context, pOutput, pfContextAttr, ptsExpiry);
}
if (!negotiate_read_neg_token(input_buffer, &input_token))
return SEC_E_INVALID_TOKEN;
/* On first response check if the server doesn't like out preferred mech */
if (context->state < NEGOTIATE_STATE_NEGORESP && input_token.supportedMech.len &&
!sspi_gss_oid_compare(&input_token.supportedMech, context->mech->oid))
{
mech = negotiate_GetMechByOID(&input_token.supportedMech);
if (!mech)
return SEC_E_INVALID_TOKEN;
/* Make sure the specified mech is supported and get the appropriate credential */
sub_cred = negotiate_FindCredential(creds, mech);
if (!sub_cred)
return SEC_E_INVALID_TOKEN;
/* Clean up the optimistic mech */
context->mech->pkg->table_w->DeleteSecurityContext(&context->sub_context);
sub_context = nullptr;
context->mech = mech;
context->mic = TRUE;
}
/* Check neg_state (required on first response) */
if (context->state < NEGOTIATE_STATE_NEGORESP)
{
switch (input_token.negState)
{
case NOSTATE:
return SEC_E_INVALID_TOKEN;
case REJECT:
return SEC_E_LOGON_DENIED;
case REQUEST_MIC:
context->mic = TRUE;
/* fallthrough */
WINPR_FALLTHROUGH
case ACCEPT_INCOMPLETE:
context->state = NEGOTIATE_STATE_NEGORESP;
break;
case ACCEPT_COMPLETED:
if (context->state == NEGOTIATE_STATE_INITIAL)
context->state = NEGOTIATE_STATE_NEGORESP;
else
context->state = NEGOTIATE_STATE_FINAL;
break;
default:
break;
}
WLog_DBG(TAG, "Negotiated mechanism: %s", negotiate_mech_name(context->mech->oid));
}
if (context->state == NEGOTIATE_STATE_NEGORESP)
{
/* Store the mech token in the output buffer */
if (!output_buffer)
goto cleanup;
CopyMemory(&output_token.mechToken, output_buffer, sizeof(SecBuffer));
mech_input_buffers[0] = input_token.mechToken;
if (bindings_buffer)
mech_input_buffers[1] = *bindings_buffer;
status = context->mech->pkg->table_w->InitializeSecurityContextW(
sub_cred, sub_context, pszTargetName, fContextReq | context->mech->flags, Reserved1,
TargetDataRep, input_token.mechToken.cbBuffer ? &mech_input : nullptr, Reserved2,
&context->sub_context, &mech_output, pfContextAttr, ptsExpiry);
if (IsSecurityStatusError(status))
return status;
}
if (status == SEC_E_OK)
{
if (output_token.mechToken.cbBuffer > 0)
context->state = NEGOTIATE_STATE_MIC;
else
context->state = NEGOTIATE_STATE_FINAL;
}
/* Check if the acceptor sent its final token without a mic */
if (context->state == NEGOTIATE_STATE_FINAL && input_token.mic.cbBuffer == 0)
{
if (context->mic || input_token.negState != ACCEPT_COMPLETED)
return SEC_E_INVALID_TOKEN;
if (output_buffer)
output_buffer->cbBuffer = 0;
return SEC_E_OK;
}
if ((context->state == NEGOTIATE_STATE_MIC && context->mic) ||
context->state == NEGOTIATE_STATE_FINAL)
{
status = negotiate_mic_exchange(context, &input_token, &output_token, output_buffer);
if (status != SEC_E_OK)
return status;
}
}
if (input_token.negState == ACCEPT_COMPLETED)
{
if (output_buffer)
output_buffer->cbBuffer = 0;
return SEC_E_OK;
}
if (output_token.negState == ACCEPT_COMPLETED)
status = SEC_E_OK;
else
status = SEC_I_CONTINUE_NEEDED;
if (!negotiate_write_neg_token(output_buffer, &output_token))
status = SEC_E_INTERNAL_ERROR;
cleanup:
WinPrAsn1Encoder_Free(&enc);
return status;
}
static SECURITY_STATUS SEC_ENTRY negotiate_InitializeSecurityContextA(
PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
{
SECURITY_STATUS status = 0;
SEC_WCHAR* pszTargetNameW = nullptr;
if (pszTargetName)
{
pszTargetNameW = ConvertUtf8ToWCharAlloc(pszTargetName, nullptr);
if (!pszTargetNameW)
return SEC_E_INTERNAL_ERROR;
}
status = negotiate_InitializeSecurityContextW(
phCredential, phContext, pszTargetNameW, fContextReq, Reserved1, TargetDataRep, pInput,
Reserved2, phNewContext, pOutput, pfContextAttr, ptsExpiry);
free(pszTargetNameW);
return status;
}
static const Mech* guessMech(PSecBuffer input_buffer, BOOL* spNego, WinPrAsn1_OID* oid)
{
WinPrAsn1Decoder decoder = WinPrAsn1Decoder_init();
WinPrAsn1Decoder appDecoder = WinPrAsn1Decoder_init();
WinPrAsn1_tagId tag = 0;
const char ssp[] = "NTLMSSP";
*spNego = FALSE;
/* Check for NTLM token */
if (input_buffer->cbBuffer >= 8 && strncmp(input_buffer->pvBuffer, ssp, sizeof(ssp)) == 0)
{
*oid = ntlm_OID;
return negotiate_GetMechByOID(&ntlm_OID);
}
/* Read initialContextToken or raw Kerberos token */
WinPrAsn1Decoder_InitMem(&decoder, WINPR_ASN1_DER, input_buffer->pvBuffer,
input_buffer->cbBuffer);
if (!WinPrAsn1DecReadApp(&decoder, &tag, &appDecoder) || tag != 0)
return nullptr;
if (!WinPrAsn1DecReadOID(&appDecoder, oid, FALSE))
return nullptr;
if (sspi_gss_oid_compare(oid, &spnego_OID))
{
*spNego = TRUE;
return nullptr;
}
return negotiate_GetMechByOID(oid);
}
static SECURITY_STATUS SEC_ENTRY negotiate_AcceptSecurityContext(
PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, ULONG fContextReq,
ULONG TargetDataRep, PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr,
PTimeStamp ptsTimeStamp)
{
NEGOTIATE_CONTEXT* context = nullptr;
NEGOTIATE_CONTEXT init_context = NEGOTIATE_CONTEXT_init();
MechCred* creds = nullptr;
PCredHandle sub_cred = nullptr;
NegToken input_token = empty_neg_token;
NegToken output_token = empty_neg_token;
PSecBuffer input_buffer = nullptr;
PSecBuffer output_buffer = nullptr;
SecBufferDesc mech_input = { SECBUFFER_VERSION, 1, &input_token.mechToken };
SecBufferDesc mech_output = { SECBUFFER_VERSION, 1, &output_token.mechToken };
SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
WinPrAsn1Decoder dec = WinPrAsn1Decoder_init();
WinPrAsn1Decoder dec2 = WinPrAsn1Decoder_init();
WinPrAsn1_tagId tag = 0;
WinPrAsn1_OID oid = WINPR_C_ARRAY_INIT;
const Mech* first_mech = nullptr;
if (!phCredential || !SecIsValidHandle(phCredential))
return SEC_E_NO_CREDENTIALS;
creds = sspi_SecureHandleGetLowerPointer(phCredential);
if (!pInput)
return SEC_E_INVALID_TOKEN;
/* behave like windows SSPIs that don't want empty context */
if (phContext && !phContext->dwLower && !phContext->dwUpper)
return SEC_E_INVALID_HANDLE;
context = sspi_SecureHandleGetLowerPointer(phContext);
input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
if (pOutput)
output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
if (!context)
{
init_context.mech = guessMech(input_buffer, &init_context.spnego, &oid);
if (!init_context.mech && !init_context.spnego)
return SEC_E_INVALID_TOKEN;
WLog_DBG(TAG, "Mechanism: %s", negotiate_mech_name(&oid));
if (init_context.spnego)
{
/* Process spnego token */
if (!negotiate_read_neg_token(input_buffer, &input_token))
return SEC_E_INVALID_TOKEN;
/* First token must be negoTokenInit and must contain a mechList */
if (!input_token.init || input_token.mechTypes.cbBuffer == 0)
return SEC_E_INVALID_TOKEN;
init_context.mechTypes.BufferType = SECBUFFER_DATA;
init_context.mechTypes.cbBuffer = input_token.mechTypes.cbBuffer;
/* Prepare to read mechList */
WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, input_token.mechTypes.pvBuffer,
input_token.mechTypes.cbBuffer);
if (!WinPrAsn1DecReadSequence(&dec, &dec2))
return SEC_E_INVALID_TOKEN;
dec = dec2;
/* If an optimistic token was provided pass it into the first mech */
if (input_token.mechToken.cbBuffer)
{
if (!WinPrAsn1DecReadOID(&dec, &oid, FALSE))
return SEC_E_INVALID_TOKEN;
init_context.mech = negotiate_GetMechByOID(&oid);
if (init_context.mech)
{
if (output_buffer)
output_token.mechToken = *output_buffer;
WLog_DBG(TAG, "Requested mechanism: %s",
negotiate_mech_name(init_context.mech->oid));
}
}
}
if (init_context.mech)
{
sub_cred = negotiate_FindCredential(creds, init_context.mech);
status = init_context.mech->pkg->table->AcceptSecurityContext(
sub_cred, nullptr, init_context.spnego ? &mech_input : pInput, fContextReq,
TargetDataRep, &init_context.sub_context,
init_context.spnego ? &mech_output : pOutput, pfContextAttr, ptsTimeStamp);
}
if (IsSecurityStatusError(status))
{
if (!init_context.spnego)
return status;
init_context.mic = TRUE;
first_mech = init_context.mech;
init_context.mech = nullptr;
output_token.mechToken.cbBuffer = 0;
}
while (!init_context.mech && WinPrAsn1DecPeekTag(&dec, &tag))
{
/* Read each mechanism */
if (!WinPrAsn1DecReadOID(&dec, &oid, FALSE))
return SEC_E_INVALID_TOKEN;
init_context.mech = negotiate_GetMechByOID(&oid);
WLog_DBG(TAG, "Requested mechanism: %s", negotiate_mech_name(&oid));
/* Microsoft may send two versions of the kerberos OID */
if (init_context.mech == first_mech)
init_context.mech = nullptr;
if (init_context.mech && !negotiate_FindCredential(creds, init_context.mech))
init_context.mech = nullptr;
}
if (!init_context.mech)
return SEC_E_INTERNAL_ERROR;
context = negotiate_ContextNew(&init_context);
if (!context)
{
if (!IsSecurityStatusError(status))
init_context.mech->pkg->table->DeleteSecurityContext(&init_context.sub_context);
return SEC_E_INSUFFICIENT_MEMORY;
}
sspi_SecureHandleSetUpperPointer(phNewContext, NEGO_SSP_NAME);
sspi_SecureHandleSetLowerPointer(phNewContext, context);
if (!init_context.spnego)
return status;
CopyMemory(init_context.mechTypes.pvBuffer, input_token.mechTypes.pvBuffer,
input_token.mechTypes.cbBuffer);
if (!context->mech->preferred)
{
output_token.negState = REQUEST_MIC;
context->mic = TRUE;
}
else
{
output_token.negState = ACCEPT_INCOMPLETE;
}
if (status == SEC_E_OK)
context->state = NEGOTIATE_STATE_FINAL;
else
context->state = NEGOTIATE_STATE_NEGORESP;
output_token.supportedMech = oid;
WLog_DBG(TAG, "Accepted mechanism: %s", negotiate_mech_name(&output_token.supportedMech));
}
else
{
sub_cred = negotiate_FindCredential(creds, context->mech);
if (!sub_cred)
return SEC_E_NO_CREDENTIALS;
if (!context->spnego)
{
return context->mech->pkg->table->AcceptSecurityContext(
sub_cred, &context->sub_context, pInput, fContextReq, TargetDataRep,
&context->sub_context, pOutput, pfContextAttr, ptsTimeStamp);
}
if (!negotiate_read_neg_token(input_buffer, &input_token))
return SEC_E_INVALID_TOKEN;
/* Process the mechanism token */
if (input_token.mechToken.cbBuffer > 0)
{
if (context->state != NEGOTIATE_STATE_NEGORESP)
return SEC_E_INVALID_TOKEN;
/* Use the output buffer to store the optimistic token */
if (output_buffer)
CopyMemory(&output_token.mechToken, output_buffer, sizeof(SecBuffer));
status = context->mech->pkg->table->AcceptSecurityContext(
sub_cred, &context->sub_context, &mech_input, fContextReq | context->mech->flags,
TargetDataRep, &context->sub_context, &mech_output, pfContextAttr, ptsTimeStamp);
if (IsSecurityStatusError(status))
return status;
if (status == SEC_E_OK)
context->state = NEGOTIATE_STATE_FINAL;
}
else if (context->state == NEGOTIATE_STATE_NEGORESP)
return SEC_E_INVALID_TOKEN;
}
if (context->state == NEGOTIATE_STATE_FINAL)
{
/* Check if initiator sent the last mech token without a mic and a mic was required */
if (context->mic && output_token.mechToken.cbBuffer == 0 && input_token.mic.cbBuffer == 0)
return SEC_E_INVALID_TOKEN;
if (context->mic || input_token.mic.cbBuffer > 0)
{
status = negotiate_mic_exchange(context, &input_token, &output_token, output_buffer);
if (status != SEC_E_OK)
return status;
}
else
output_token.negState = ACCEPT_COMPLETED;
}
if (input_token.negState == ACCEPT_COMPLETED)
{
if (output_buffer)
output_buffer->cbBuffer = 0;
return SEC_E_OK;
}
if (output_token.negState == ACCEPT_COMPLETED)
status = SEC_E_OK;
else
status = SEC_I_CONTINUE_NEEDED;
if (!negotiate_write_neg_token(output_buffer, &output_token))
return SEC_E_INTERNAL_ERROR;
return status;
}
static SECURITY_STATUS SEC_ENTRY negotiate_CompleteAuthToken(PCtxtHandle phContext,
PSecBufferDesc pToken)
{
NEGOTIATE_CONTEXT* context = nullptr;
SECURITY_STATUS status = SEC_E_OK;
context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
if (!context)
return SEC_E_INVALID_HANDLE;
WINPR_ASSERT(context->mech);
WINPR_ASSERT(context->mech->pkg);
WINPR_ASSERT(context->mech->pkg->table);
if (context->mech->pkg->table->CompleteAuthToken)
status = context->mech->pkg->table->CompleteAuthToken(&context->sub_context, pToken);
return status;
}
static SECURITY_STATUS SEC_ENTRY negotiate_DeleteSecurityContext(PCtxtHandle phContext)
{
NEGOTIATE_CONTEXT* context = nullptr;
SECURITY_STATUS status = SEC_E_OK;
context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
const SecPkg* pkg = nullptr;
if (!context)
return SEC_E_INVALID_HANDLE;
WINPR_ASSERT(context->mech);
WINPR_ASSERT(context->mech->pkg);
WINPR_ASSERT(context->mech->pkg->table);
pkg = context->mech->pkg;
if (pkg->table->DeleteSecurityContext)
status = pkg->table->DeleteSecurityContext(&context->sub_context);
negotiate_ContextFree(context);
return status;
}
static SECURITY_STATUS SEC_ENTRY
negotiate_ImpersonateSecurityContext(WINPR_ATTR_UNUSED PCtxtHandle phContext)
{
return SEC_E_OK;
}
static SECURITY_STATUS SEC_ENTRY
negotiate_RevertSecurityContext(WINPR_ATTR_UNUSED PCtxtHandle phContext)
{
return SEC_E_OK;
}
static SECURITY_STATUS SEC_ENTRY negotiate_QueryContextAttributesW(PCtxtHandle phContext,
ULONG ulAttribute, void* pBuffer)
{
NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
if (!context)
return SEC_E_INVALID_HANDLE;
WINPR_ASSERT(context->mech);
WINPR_ASSERT(context->mech->pkg);
WINPR_ASSERT(context->mech->pkg->table_w);
if (context->mech->pkg->table_w->QueryContextAttributesW)
return context->mech->pkg->table_w->QueryContextAttributesW(&context->sub_context,
ulAttribute, pBuffer);
return SEC_E_UNSUPPORTED_FUNCTION;
}
static SECURITY_STATUS SEC_ENTRY negotiate_QueryContextAttributesA(PCtxtHandle phContext,
ULONG ulAttribute, void* pBuffer)
{
NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
if (!context)
return SEC_E_INVALID_HANDLE;
WINPR_ASSERT(context->mech);
WINPR_ASSERT(context->mech->pkg);
WINPR_ASSERT(context->mech->pkg->table);
if (context->mech->pkg->table->QueryContextAttributesA)
return context->mech->pkg->table->QueryContextAttributesA(&context->sub_context,
ulAttribute, pBuffer);
return SEC_E_UNSUPPORTED_FUNCTION;
}
static SECURITY_STATUS SEC_ENTRY negotiate_SetContextAttributesW(PCtxtHandle phContext,
ULONG ulAttribute, void* pBuffer,
ULONG cbBuffer)
{
NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
if (!context)
return SEC_E_INVALID_HANDLE;
WINPR_ASSERT(context->mech);
WINPR_ASSERT(context->mech->pkg);
WINPR_ASSERT(context->mech->pkg->table_w);
if (context->mech->pkg->table_w->SetContextAttributesW)
return context->mech->pkg->table_w->SetContextAttributesW(&context->sub_context,
ulAttribute, pBuffer, cbBuffer);
return SEC_E_UNSUPPORTED_FUNCTION;
}
static SECURITY_STATUS SEC_ENTRY negotiate_SetContextAttributesA(PCtxtHandle phContext,
ULONG ulAttribute, void* pBuffer,
ULONG cbBuffer)
{
NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
if (!context)
return SEC_E_INVALID_HANDLE;
WINPR_ASSERT(context->mech);
WINPR_ASSERT(context->mech->pkg);
WINPR_ASSERT(context->mech->pkg->table);
if (context->mech->pkg->table->SetContextAttributesA)
return context->mech->pkg->table->SetContextAttributesA(&context->sub_context, ulAttribute,
pBuffer, cbBuffer);
return SEC_E_UNSUPPORTED_FUNCTION;
}
static SECURITY_STATUS SEC_ENTRY negotiate_SetCredentialsAttributesW(PCredHandle phCredential,
ULONG ulAttribute,
void* pBuffer, ULONG cbBuffer)
{
MechCred* creds = nullptr;
BOOL success = FALSE;
SECURITY_STATUS secStatus = 0;
creds = sspi_SecureHandleGetLowerPointer(phCredential);
if (!creds)
return SEC_E_INVALID_HANDLE;
for (size_t i = 0; i < MECH_COUNT; i++)
{
MechCred* cred = &creds[i];
WINPR_ASSERT(cred->mech);
WINPR_ASSERT(cred->mech->pkg);
WINPR_ASSERT(cred->mech->pkg->table);
WINPR_ASSERT(cred->mech->pkg->table_w->SetCredentialsAttributesW);
secStatus = cred->mech->pkg->table_w->SetCredentialsAttributesW(&cred->cred, ulAttribute,
pBuffer, cbBuffer);
if (secStatus == SEC_E_OK)
{
success = TRUE;
}
}
// return success if at least one submodule accepts the credential attribute
return (success ? SEC_E_OK : SEC_E_UNSUPPORTED_FUNCTION);
}
static SECURITY_STATUS SEC_ENTRY negotiate_SetCredentialsAttributesA(PCredHandle phCredential,
ULONG ulAttribute,
void* pBuffer, ULONG cbBuffer)
{
MechCred* creds = nullptr;
BOOL success = FALSE;
SECURITY_STATUS secStatus = 0;
creds = sspi_SecureHandleGetLowerPointer(phCredential);
if (!creds)
return SEC_E_INVALID_HANDLE;
for (size_t i = 0; i < MECH_COUNT; i++)
{
MechCred* cred = &creds[i];
if (!cred->valid)
continue;
WINPR_ASSERT(cred->mech);
WINPR_ASSERT(cred->mech->pkg);
WINPR_ASSERT(cred->mech->pkg->table);
WINPR_ASSERT(cred->mech->pkg->table->SetCredentialsAttributesA);
secStatus = cred->mech->pkg->table->SetCredentialsAttributesA(&cred->cred, ulAttribute,
pBuffer, cbBuffer);
if (secStatus == SEC_E_OK)
{
success = TRUE;
}
}
// return success if at least one submodule accepts the credential attribute
return (success ? SEC_E_OK : SEC_E_UNSUPPORTED_FUNCTION);
}
static SECURITY_STATUS SEC_ENTRY negotiate_AcquireCredentialsHandleW(
SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
PTimeStamp ptsExpiry)
{
BOOL kerberos = FALSE;
BOOL ntlm = FALSE;
BOOL u2u = FALSE;
if (!negotiate_get_config(pAuthData, &kerberos, &ntlm, &u2u))
return SEC_E_INTERNAL_ERROR;
MechCred* creds = calloc(MECH_COUNT, sizeof(MechCred));
if (!creds)
return SEC_E_INTERNAL_ERROR;
for (size_t i = 0; i < MECH_COUNT; i++)
{
MechCred* cred = &creds[i];
const SecPkg* pkg = MechTable[i].pkg;
cred->mech = &MechTable[i];
if (!kerberos && sspi_gss_oid_compare(MechTable[i].oid, &kerberos_OID))
continue;
if (!u2u && sspi_gss_oid_compare(MechTable[i].oid, &kerberos_u2u_OID))
continue;
if (!ntlm && _tcsncmp(SecPkgTable[i].name, NTLM_SSP_NAME, ARRAYSIZE(NTLM_SSP_NAME)) == 0)
continue;
WINPR_ASSERT(pkg->table_w);
WINPR_ASSERT(pkg->table_w->AcquireCredentialsHandleW);
if (pkg->table_w->AcquireCredentialsHandleW(
pszPrincipal, pszPackage, fCredentialUse, pvLogonID, pAuthData, pGetKeyFn,
pvGetKeyArgument, &cred->cred, ptsExpiry) != SEC_E_OK)
continue;
cred->valid = TRUE;
}
sspi_SecureHandleSetLowerPointer(phCredential, (void*)creds);
sspi_SecureHandleSetUpperPointer(phCredential, (void*)NEGO_SSP_NAME);
return SEC_E_OK;
}
static SECURITY_STATUS SEC_ENTRY negotiate_AcquireCredentialsHandleA(
SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
PTimeStamp ptsExpiry)
{
BOOL kerberos = FALSE;
BOOL ntlm = FALSE;
BOOL u2u = FALSE;
if (!negotiate_get_config(pAuthData, &kerberos, &ntlm, &u2u))
return SEC_E_INTERNAL_ERROR;
MechCred* creds = calloc(MECH_COUNT, sizeof(MechCred));
if (!creds)
return SEC_E_INTERNAL_ERROR;
for (size_t i = 0; i < MECH_COUNT; i++)
{
const SecPkg* pkg = MechTable[i].pkg;
MechCred* cred = &creds[i];
cred->mech = &MechTable[i];
if (!kerberos && sspi_gss_oid_compare(MechTable[i].oid, &kerberos_OID))
continue;
if (!u2u && sspi_gss_oid_compare(MechTable[i].oid, &kerberos_u2u_OID))
continue;
if (!ntlm && _tcsncmp(SecPkgTable[i].name, NTLM_SSP_NAME, ARRAYSIZE(NTLM_SSP_NAME)) == 0)
continue;
WINPR_ASSERT(pkg->table);
WINPR_ASSERT(pkg->table->AcquireCredentialsHandleA);
if (pkg->table->AcquireCredentialsHandleA(pszPrincipal, pszPackage, fCredentialUse,
pvLogonID, pAuthData, pGetKeyFn, pvGetKeyArgument,
&cred->cred, ptsExpiry) != SEC_E_OK)
continue;
cred->valid = TRUE;
}
sspi_SecureHandleSetLowerPointer(phCredential, (void*)creds);
sspi_SecureHandleSetUpperPointer(phCredential, (void*)NEGO_SSP_NAME);
return SEC_E_OK;
}
static SECURITY_STATUS SEC_ENTRY negotiate_QueryCredentialsAttributesW(
WINPR_ATTR_UNUSED PCredHandle phCredential, WINPR_ATTR_UNUSED ULONG ulAttribute,
WINPR_ATTR_UNUSED void* pBuffer)
{
WLog_ERR(TAG, "TODO: Implement");
return SEC_E_UNSUPPORTED_FUNCTION;
}
static SECURITY_STATUS SEC_ENTRY negotiate_QueryCredentialsAttributesA(
WINPR_ATTR_UNUSED PCredHandle phCredential, WINPR_ATTR_UNUSED ULONG ulAttribute,
WINPR_ATTR_UNUSED void* pBuffer)
{
WLog_ERR(TAG, "TODO: Implement");
return SEC_E_UNSUPPORTED_FUNCTION;
}
static SECURITY_STATUS SEC_ENTRY negotiate_FreeCredentialsHandle(PCredHandle phCredential)
{
MechCred* creds = sspi_SecureHandleGetLowerPointer(phCredential);
if (!creds)
return SEC_E_INVALID_HANDLE;
for (size_t i = 0; i < MECH_COUNT; i++)
{
MechCred* cred = &creds[i];
WINPR_ASSERT(cred->mech);
WINPR_ASSERT(cred->mech->pkg);
WINPR_ASSERT(cred->mech->pkg->table);
WINPR_ASSERT(cred->mech->pkg->table->FreeCredentialsHandle);
cred->mech->pkg->table->FreeCredentialsHandle(&cred->cred);
}
free(creds);
return SEC_E_OK;
}
static SECURITY_STATUS SEC_ENTRY negotiate_EncryptMessage(PCtxtHandle phContext, ULONG fQOP,
PSecBufferDesc pMessage,
ULONG MessageSeqNo)
{
NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
if (!context)
return SEC_E_INVALID_HANDLE;
if (context->mic)
MessageSeqNo++;
WINPR_ASSERT(context->mech);
WINPR_ASSERT(context->mech->pkg);
WINPR_ASSERT(context->mech->pkg->table);
if (context->mech->pkg->table->EncryptMessage)
return context->mech->pkg->table->EncryptMessage(&context->sub_context, fQOP, pMessage,
MessageSeqNo);
return SEC_E_UNSUPPORTED_FUNCTION;
}
static SECURITY_STATUS SEC_ENTRY negotiate_DecryptMessage(PCtxtHandle phContext,
PSecBufferDesc pMessage,
ULONG MessageSeqNo, ULONG* pfQOP)
{
NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
if (!context)
return SEC_E_INVALID_HANDLE;
if (context->mic)
MessageSeqNo++;
WINPR_ASSERT(context->mech);
WINPR_ASSERT(context->mech->pkg);
WINPR_ASSERT(context->mech->pkg->table);
if (context->mech->pkg->table->DecryptMessage)
return context->mech->pkg->table->DecryptMessage(&context->sub_context, pMessage,
MessageSeqNo, pfQOP);
return SEC_E_UNSUPPORTED_FUNCTION;
}
static SECURITY_STATUS SEC_ENTRY negotiate_MakeSignature(PCtxtHandle phContext, ULONG fQOP,
PSecBufferDesc pMessage,
ULONG MessageSeqNo)
{
NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
if (!context)
return SEC_E_INVALID_HANDLE;
if (context->mic)
MessageSeqNo++;
WINPR_ASSERT(context->mech);
WINPR_ASSERT(context->mech->pkg);
WINPR_ASSERT(context->mech->pkg->table);
if (context->mech->pkg->table->MakeSignature)
return context->mech->pkg->table->MakeSignature(&context->sub_context, fQOP, pMessage,
MessageSeqNo);
return SEC_E_UNSUPPORTED_FUNCTION;
}
static SECURITY_STATUS SEC_ENTRY negotiate_VerifySignature(PCtxtHandle phContext,
PSecBufferDesc pMessage,
ULONG MessageSeqNo, ULONG* pfQOP)
{
NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
if (!context)
return SEC_E_INVALID_HANDLE;
if (context->mic)
MessageSeqNo++;
WINPR_ASSERT(context->mech);
WINPR_ASSERT(context->mech->pkg);
WINPR_ASSERT(context->mech->pkg->table);
if (context->mech->pkg->table->VerifySignature)
return context->mech->pkg->table->VerifySignature(&context->sub_context, pMessage,
MessageSeqNo, pfQOP);
return SEC_E_UNSUPPORTED_FUNCTION;
}
const SecurityFunctionTableA NEGOTIATE_SecurityFunctionTableA = {
3, /* dwVersion */
nullptr, /* EnumerateSecurityPackages */
negotiate_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */
negotiate_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */
negotiate_FreeCredentialsHandle, /* FreeCredentialsHandle */
nullptr, /* Reserved2 */
negotiate_InitializeSecurityContextA, /* InitializeSecurityContext */
negotiate_AcceptSecurityContext, /* AcceptSecurityContext */
negotiate_CompleteAuthToken, /* CompleteAuthToken */
negotiate_DeleteSecurityContext, /* DeleteSecurityContext */
nullptr, /* ApplyControlToken */
negotiate_QueryContextAttributesA, /* QueryContextAttributes */
negotiate_ImpersonateSecurityContext, /* ImpersonateSecurityContext */
negotiate_RevertSecurityContext, /* RevertSecurityContext */
negotiate_MakeSignature, /* MakeSignature */
negotiate_VerifySignature, /* VerifySignature */
nullptr, /* FreeContextBuffer */
nullptr, /* QuerySecurityPackageInfo */
nullptr, /* Reserved3 */
nullptr, /* Reserved4 */
nullptr, /* ExportSecurityContext */
nullptr, /* ImportSecurityContext */
nullptr, /* AddCredentials */
nullptr, /* Reserved8 */
nullptr, /* QuerySecurityContextToken */
negotiate_EncryptMessage, /* EncryptMessage */
negotiate_DecryptMessage, /* DecryptMessage */
negotiate_SetContextAttributesA, /* SetContextAttributes */
negotiate_SetCredentialsAttributesA, /* SetCredentialsAttributes */
};
const SecurityFunctionTableW NEGOTIATE_SecurityFunctionTableW = {
3, /* dwVersion */
nullptr, /* EnumerateSecurityPackages */
negotiate_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */
negotiate_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */
negotiate_FreeCredentialsHandle, /* FreeCredentialsHandle */
nullptr, /* Reserved2 */
negotiate_InitializeSecurityContextW, /* InitializeSecurityContext */
negotiate_AcceptSecurityContext, /* AcceptSecurityContext */
negotiate_CompleteAuthToken, /* CompleteAuthToken */
negotiate_DeleteSecurityContext, /* DeleteSecurityContext */
nullptr, /* ApplyControlToken */
negotiate_QueryContextAttributesW, /* QueryContextAttributes */
negotiate_ImpersonateSecurityContext, /* ImpersonateSecurityContext */
negotiate_RevertSecurityContext, /* RevertSecurityContext */
negotiate_MakeSignature, /* MakeSignature */
negotiate_VerifySignature, /* VerifySignature */
nullptr, /* FreeContextBuffer */
nullptr, /* QuerySecurityPackageInfo */
nullptr, /* Reserved3 */
nullptr, /* Reserved4 */
nullptr, /* ExportSecurityContext */
nullptr, /* ImportSecurityContext */
nullptr, /* AddCredentials */
nullptr, /* Reserved8 */
nullptr, /* QuerySecurityContextToken */
negotiate_EncryptMessage, /* EncryptMessage */
negotiate_DecryptMessage, /* DecryptMessage */
negotiate_SetContextAttributesW, /* SetContextAttributes */
negotiate_SetCredentialsAttributesW, /* SetCredentialsAttributes */
};
BOOL NEGOTIATE_init(void)
{
InitializeConstWCharFromUtf8(NEGOTIATE_SecPkgInfoA.Name, NEGOTIATE_SecPkgInfoW_NameBuffer,
ARRAYSIZE(NEGOTIATE_SecPkgInfoW_NameBuffer));
InitializeConstWCharFromUtf8(NEGOTIATE_SecPkgInfoA.Comment, NEGOTIATE_SecPkgInfoW_CommentBuffer,
ARRAYSIZE(NEGOTIATE_SecPkgInfoW_CommentBuffer));
return TRUE;
}