2167 lines
77 KiB
C++
2167 lines
77 KiB
C++
// Microsoft Windows
|
|
|
|
// Copyright (c) Microsoft Corporation 1992 - 1999
|
|
|
|
// File: userapi.cxx
|
|
|
|
// Contents: User-mode APIs to the NtLm security package
|
|
|
|
// Main user mode entry points into this dll:
|
|
// SpUserModeInitialize
|
|
// SpInstanceInit
|
|
// SpDeleteUserModeContext
|
|
// SpInitUserModeContext
|
|
// SpMakeSignature
|
|
// SpVerifySignature
|
|
// SpSealMessage
|
|
// SpUnsealMessage
|
|
// SpGetContextToken
|
|
// SpQueryContextAttributes
|
|
// SpCompleteAuthToken
|
|
// SpFormatCredentials
|
|
// SpMarshallSupplementalCreds
|
|
// SpExportSecurityContext
|
|
// SpImportSecurityContext
|
|
|
|
// Helper functions:
|
|
// ReferenceUserContext
|
|
// FreeUserContext
|
|
// DereferenceUserContext
|
|
// SspGenCheckSum
|
|
// SspEncryptBuffer
|
|
// NtLmMakePackedContext(this is called in the client's process)
|
|
// NtLmCreateUserModeContext
|
|
// SspGetTokenUser
|
|
// SspCreateTokenDacl
|
|
// SspMapContext (this is called in Lsa mode)
|
|
|
|
// History: ChandanS 26-Jul-1996 Stolen from kerberos\client2\userapi.cxx
|
|
|
|
#include <global.h> // Globals!
|
|
#include "crc32.h" // How to use crc32
|
|
|
|
extern "C"
|
|
{
|
|
#include <rc4.h> // How to use RC4 routine
|
|
#include <md5.h>
|
|
#include <hmac.h>
|
|
#include <nlp.h>
|
|
}
|
|
|
|
// Keep this is sync with NTLM_KERNEL_CONTEXT defined in security\msv_sspi\kernel\krnlapi.cxx
|
|
|
|
typedef struct _NTLM_CLIENT_CONTEXT{
|
|
union {
|
|
LIST_ENTRY Next;
|
|
KSEC_LIST_ENTRY KernelNext;
|
|
};
|
|
ULONG_PTR LsaContext;
|
|
ULONG NegotiateFlags;
|
|
HANDLE ClientTokenHandle;
|
|
PACCESS_TOKEN AccessToken;
|
|
PULONG pSendNonce; // ptr to nonce to use for send
|
|
PULONG pRecvNonce; // ptr to nonce to use for receive
|
|
struct RC4_KEYSTRUCT * pSealRc4Sched; // ptr to key sched used for Seal
|
|
struct RC4_KEYSTRUCT * pUnsealRc4Sched; // ptr to key sched used to Unseal
|
|
ULONG SendNonce;
|
|
ULONG RecvNonce;
|
|
LPWSTR ContextNames;
|
|
UCHAR SessionKey[MSV1_0_USER_SESSION_KEY_LENGTH];
|
|
ULONG ContextSignature;
|
|
ULONG References ;
|
|
TimeStamp PasswordExpiry;
|
|
ULONG UserFlags;
|
|
UCHAR SignSessionKey[MSV1_0_USER_SESSION_KEY_LENGTH];
|
|
UCHAR VerifySessionKey[MSV1_0_USER_SESSION_KEY_LENGTH];
|
|
UCHAR SealSessionKey[MSV1_0_USER_SESSION_KEY_LENGTH];
|
|
UCHAR UnsealSessionKey[MSV1_0_USER_SESSION_KEY_LENGTH];
|
|
|
|
ULONG64 Pad1; // pad keystructs to 64.
|
|
struct RC4_KEYSTRUCT SealRc4Sched; // key struct used for Seal
|
|
ULONG64 Pad2; // pad keystructs to 64.
|
|
struct RC4_KEYSTRUCT UnsealRc4Sched; // key struct used to Unseal
|
|
} NTLM_CLIENT_CONTEXT, * PNTLM_CLIENT_CONTEXT;
|
|
|
|
|
|
#define CSSEALMAGIC "session key to client-to-server sealing key magic constant"
|
|
#define SCSEALMAGIC "session key to server-to-client sealing key magic constant"
|
|
#define CSSIGNMAGIC "session key to client-to-server signing key magic constant"
|
|
#define SCSIGNMAGIC "session key to server-to-client signing key magic constant"
|
|
|
|
|
|
LIST_ENTRY NtLmUserContextList;
|
|
RTL_CRITICAL_SECTION NtLmUserContextLock;
|
|
|
|
// Counter for exported handles;never de-refed
|
|
// Should probably do a GetSystemInfo and get a space of handles that cannot
|
|
// be valid in the Lsa process
|
|
// BUGBUG check with user folks
|
|
ULONG_PTR ExportedContext = 0;
|
|
|
|
NTSTATUS SspCreateTokenDacl(HANDLE Token);
|
|
|
|
|
|
// Synopsis: Initialize an the MSV1_0 DLL in a client's address space
|
|
|
|
// Arguments: LsaVersion - Version of the security dll loading the package PackageVersion - Version of the MSV1_0 package
|
|
// UserFunctionTable - Receives a copy of Kerberos's user mode
|
|
// function table
|
|
// pcTables - Receives count of tables returned.
|
|
|
|
// Returns: STATUS_SUCCESS
|
|
|
|
// Notes: we do what was done in SspInitLocalContexts()
|
|
// from net\svcdlls\ntlmssp\client\sign.c and more.
|
|
NTSTATUS SEC_ENTRY SpUserModeInitialize(IN ULONG LsaVersion, OUT PULONG PackageVersion, OUT PSECPKG_USER_FUNCTION_TABLE * UserFunctionTable, OUT PULONG pcTables)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
#if DBG
|
|
SspGlobalDbflag = SSP_CRITICAL;
|
|
InitializeCriticalSection(&SspGlobalLogFileCritSect);
|
|
#endif
|
|
|
|
if (LsaVersion != SECPKG_INTERFACE_VERSION)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
*PackageVersion = SECPKG_INTERFACE_VERSION;
|
|
|
|
NtLmUserFunctionTable.InstanceInit = SpInstanceInit;
|
|
NtLmUserFunctionTable.MakeSignature = SpMakeSignature;
|
|
NtLmUserFunctionTable.VerifySignature = SpVerifySignature;
|
|
NtLmUserFunctionTable.SealMessage = SpSealMessage;
|
|
NtLmUserFunctionTable.UnsealMessage = SpUnsealMessage;
|
|
NtLmUserFunctionTable.GetContextToken = SpGetContextToken;
|
|
NtLmUserFunctionTable.QueryContextAttributes = SpQueryContextAttributes;
|
|
NtLmUserFunctionTable.CompleteAuthToken = SpCompleteAuthToken;
|
|
NtLmUserFunctionTable.InitUserModeContext = SpInitUserModeContext;
|
|
NtLmUserFunctionTable.DeleteUserModeContext = SpDeleteUserModeContext;
|
|
NtLmUserFunctionTable.FormatCredentials = SpFormatCredentials;
|
|
NtLmUserFunctionTable.MarshallSupplementalCreds = SpMarshallSupplementalCreds;
|
|
NtLmUserFunctionTable.ExportContext = SpExportSecurityContext;
|
|
NtLmUserFunctionTable.ImportContext = SpImportSecurityContext;
|
|
|
|
*UserFunctionTable = &NtLmUserFunctionTable;
|
|
*pcTables = 1;
|
|
|
|
Cleanup:
|
|
return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
|
|
}
|
|
|
|
|
|
// Synopsis: locates a user context in the list, refrences it
|
|
|
|
// Returns: the context, if it is found, else NULL
|
|
|
|
// Notes: This was SspContextReferenceContext() in
|
|
// net\svcdlls\ntlmssp\common\context.c
|
|
PNTLM_CLIENT_CONTEXT ReferenceUserContext(IN ULONG_PTR ContextHandle, IN BOOLEAN RemoveContext )
|
|
{
|
|
SspPrint(( SSP_API_MORE, "Entering ReferenceUserContext for 0x%x\n", ContextHandle ));
|
|
PLIST_ENTRY ListEntry;
|
|
PNTLM_CLIENT_CONTEXT pContext = NULL;
|
|
|
|
RtlEnterCriticalSection(&NtLmUserContextLock);
|
|
|
|
// Look for a match for the LsaContext, not user context
|
|
for (ListEntry = NtLmUserContextList.Flink; ListEntry != &NtLmUserContextList; ListEntry = ListEntry->Flink ) {
|
|
pContext = CONTAINING_RECORD(ListEntry, NTLM_CLIENT_CONTEXT, Next );
|
|
if (pContext == NULL)
|
|
{
|
|
RtlLeaveCriticalSection(&NtLmUserContextLock);
|
|
SspPrint(( SSP_API_MORE, "Leaving ReferenceUserContext for 0x%x\n", ContextHandle ));
|
|
return NULL;
|
|
}
|
|
|
|
if (pContext->LsaContext == ContextHandle)
|
|
{
|
|
// Found it!
|
|
if (!RemoveContext)
|
|
{
|
|
pContext->References += 1;
|
|
}
|
|
else
|
|
{
|
|
RemoveEntryList (&pContext->Next);
|
|
SspPrint(( SSP_API_MORE, "Delinked Context 0x%lx\n", pContext ));
|
|
}
|
|
|
|
RtlLeaveCriticalSection(&NtLmUserContextLock);
|
|
SspPrint(( SSP_API_MORE, "Leaving ReferenceUserContext for 0x%x\n", ContextHandle));
|
|
return pContext;
|
|
}
|
|
}
|
|
|
|
// No match found
|
|
|
|
RtlLeaveCriticalSection(&NtLmUserContextLock);
|
|
SspPrint(( SSP_API_MORE, "Leaving ReferenceUserContext for 0x%x\n", ContextHandle ));
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// Synopsis: frees alloced pointers in this context and then frees the context
|
|
// Arguments: lContext - the unlinked user context
|
|
// Returns: STATUS_SUCCESS on success
|
|
NTSTATUS FreeUserContext (PNTLM_CLIENT_CONTEXT UserContext)
|
|
{
|
|
SspPrint(( SSP_API_MORE, "Entering FreeUserContext for context 0x%x\n", UserContext ));
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
if (UserContext->ContextNames != NULL)
|
|
{
|
|
NtLmFree (UserContext->ContextNames);
|
|
}
|
|
|
|
if (UserContext->ClientTokenHandle != NULL)
|
|
{
|
|
NTSTATUS IgnoreStatus;
|
|
IgnoreStatus = NtClose(UserContext->ClientTokenHandle);
|
|
ASSERT (NT_SUCCESS (IgnoreStatus));
|
|
}
|
|
|
|
SspPrint(( SSP_API_MORE, "Deleting Context 0x%x\n", UserContext));
|
|
|
|
NtLmFree (UserContext);
|
|
|
|
SspPrint(( SSP_API_MORE, "Leaving FreeUserContext for context 0x%x, status = 0x%x\n", Status ));
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
// Synopsis: frees alloced elements in the context, frees context
|
|
|
|
// Returns: None
|
|
|
|
// Notes: This was SspContextDereferenceContext() in
|
|
// net\svcdlls\ntlmssp\common\context.c
|
|
NTSTATUS DereferenceUserContext (PNTLM_CLIENT_CONTEXT pContext)
|
|
{
|
|
SspPrint(( SSP_API_MORE, "Entering DereferenceUserContext 0x%lx\n", pContext ));
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
ULONG References;
|
|
|
|
// Decrement the reference count
|
|
RtlEnterCriticalSection(&NtLmUserContextLock);
|
|
ASSERT (pContext->References >= 1);
|
|
References = -- pContext->References;
|
|
RtlLeaveCriticalSection(&NtLmUserContextLock);
|
|
|
|
// If the count has dropped to zero, then free all alloced stuff
|
|
if (References == 0)
|
|
{
|
|
Status = FreeUserContext(pContext);
|
|
}
|
|
SspPrint(( SSP_API_MORE, "Leaving DereferenceUserContext\n" ));
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
// Synopsis: Initialize an instance of the NtLm package in a client's address space
|
|
|
|
// Arguments: Version - Version of the security dll loading the package
|
|
// FunctionTable - Contains helper routines for use by NtLm
|
|
// UserFunctions - Receives a copy of NtLm's user mode function table
|
|
|
|
// Returns: STATUS_SUCCESS
|
|
|
|
// Notes: we do what was done in SspInitLocalContexts()
|
|
// from net\svcdlls\ntlmssp\client\sign.c and more.
|
|
NTSTATUS NTAPI SpInstanceInit(IN ULONG Version, IN PSECPKG_DLL_FUNCTIONS DllFunctionTable, OUT PVOID * UserFunctionTable)
|
|
{
|
|
SspPrint(( SSP_API, "Entering SpInstanceInit\n" ));
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
// Save the Alloc/Free functions
|
|
NtLmState = NtLmUserMode;
|
|
UserFunctions = DllFunctionTable;
|
|
|
|
InitializeListHead (&NtLmUserContextList);
|
|
RtlInitializeCriticalSectionAndSpinCount (&NtLmUserContextLock, 5000);
|
|
|
|
SspPrint(( SSP_API, "Leaving SpInstanceInit: 0x%lx\n", Status ));
|
|
return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
|
|
}
|
|
|
|
|
|
|
|
// Synopsis: Deletes a user mode context by unlinking it and then dereferencing it.
|
|
|
|
// Arguments: ContextHandle - Lsa context handle of the context to delete
|
|
|
|
// Returns: STATUS_SUCCESS on success, STATUS_INVALID_HANDLE if the context can't be located
|
|
|
|
// Notes:
|
|
// If this is an exported context, send a flag back to the LSA so that
|
|
// Lsa does not call the SecpDeleteSecurityContext in the lsa process
|
|
NTSTATUS NTAPI SpDeleteUserModeContext(IN ULONG_PTR ContextHandle)
|
|
{
|
|
SspPrint(( SSP_API, "Entering SpDeleteUserModeContext 0x%lx\n", ContextHandle ));
|
|
PNTLM_CLIENT_CONTEXT pContext = NULL;
|
|
NTSTATUS Status = STATUS_SUCCESS, SaveStatus = STATUS_SUCCESS;
|
|
|
|
// Find the currently existing user context and delink it // so that another context cannot Reference it before we
|
|
// Dereference this one.
|
|
pContext = ReferenceUserContext(ContextHandle, TRUE);
|
|
if (pContext == NULL)
|
|
{
|
|
// pContext is legally NULL when we are dealing with an incomplete context. This can often be the case when the second call to InitializeSecurityContext() fails.
|
|
|
|
/// Status = STATUS_INVALID_HANDLE;
|
|
Status = STATUS_SUCCESS;
|
|
SspPrint(( SSP_API_MORE, "SpDeleteUserModeContext, local pContext is NULL\n" ));
|
|
goto CleanUp;
|
|
}
|
|
|
|
RtlEnterCriticalSection(&NtLmUserContextLock);
|
|
|
|
if ((pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_EXPORTED_CONTEXT) != 0)
|
|
{
|
|
// Ignore all other errors and pass back
|
|
SaveStatus = SEC_I_NO_LSA_CONTEXT;
|
|
}
|
|
|
|
RtlLeaveCriticalSection(&NtLmUserContextLock);
|
|
|
|
CleanUp:
|
|
if (pContext != NULL)
|
|
{
|
|
Status = DereferenceUserContext(pContext);
|
|
}
|
|
|
|
if (SaveStatus == SEC_I_NO_LSA_CONTEXT)
|
|
{
|
|
Status = SaveStatus;
|
|
}
|
|
|
|
SspPrint(( SSP_API, "Leaving SpDeleteUserModeContext: 0x%lx\n", Status ));
|
|
return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
|
|
}
|
|
|
|
|
|
VOID SspRc4Key(IN ULONG NegotiateFlags,OUT struct RC4_KEYSTRUCT *pRc4Key,IN PUCHAR pSessionKey)
|
|
/*++
|
|
RoutineDescription:
|
|
Create an RC4 key schedule, making sure key length is OK for export
|
|
Arguments:
|
|
NegotiateFlags negotiate feature flags; NTLM2 bit is only one looked at
|
|
pRc4Key pointer to RC4 key schedule structure; filled in by this routine
|
|
pSessionKey pointer to session key -- must be full 16 bytes
|
|
--*/
|
|
{
|
|
// For NTLM2, effective length was already cut down
|
|
if ((NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2) != 0) {
|
|
rc4_key(pRc4Key, MSV1_0_USER_SESSION_KEY_LENGTH, pSessionKey);
|
|
} else if( NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY ) {
|
|
UCHAR Key[MSV1_0_LANMAN_SESSION_KEY_LENGTH];
|
|
ULONG KeyLen;
|
|
|
|
ASSERT(MSV1_0_LANMAN_SESSION_KEY_LENGTH == 8);
|
|
|
|
// prior to Win2k, negotiated key strength had no bearing on key size.
|
|
// So, to allow proper interop to NT4, we don't worry about 128bit. 56bit and 40bit are the only supported options.
|
|
// 56bit is enabled because this was introduced in Win2k, and Win2k -> Win2k interops correctly.
|
|
|
|
#if 0
|
|
if( NegotiateFlags & NTLMSSP_NEGOTIATE_128 ) {
|
|
KeyLen = 8;
|
|
|
|
} else
|
|
#endif
|
|
if( NegotiateFlags & NTLMSSP_NEGOTIATE_56 ) {
|
|
KeyLen = 7;
|
|
|
|
// Put a well-known salt at the end of the key to limit the changing part to 56 bits.
|
|
Key[7] = 0xa0;
|
|
} else {
|
|
KeyLen = 5;
|
|
|
|
// Put a well-known salt at the end of the key to limit the changing part to 40 bits.
|
|
Key[5] = 0xe5;
|
|
Key[6] = 0x38;
|
|
Key[7] = 0xb0;
|
|
}
|
|
|
|
SspPrint(( SSP_SESSION_KEYS, "Non NTLMv2 LM_KEY session key size: %lu\n", KeyLen));
|
|
|
|
RtlCopyMemory(Key,pSessionKey,KeyLen);
|
|
|
|
rc4_key(pRc4Key, MSV1_0_LANMAN_SESSION_KEY_LENGTH, Key);
|
|
} else {
|
|
SspPrint(( SSP_SESSION_KEYS, "Non NTLMv2 (not LM_KEY) session key size: %lu\n", 16));
|
|
rc4_key(pRc4Key, MSV1_0_USER_SESSION_KEY_LENGTH, pSessionKey);
|
|
}
|
|
}
|
|
|
|
|
|
// Synopsis: Creates a user-mode context from a packed LSA mode context
|
|
|
|
// Arguments: ContextHandle - Lsa mode context handle for the context
|
|
// PackedContext - A marshalled buffer containing the LSA
|
|
// mode context.
|
|
|
|
// Returns: STATUS_SUCCESS or STATUS_INSUFFICIENT_RESOURCES
|
|
NTSTATUS NTAPI SpInitUserModeContext(IN ULONG_PTR ContextHandle,IN PSecBuffer PackedContext)
|
|
{
|
|
ASSERT(PackedContext);
|
|
|
|
SspPrint(( SSP_API, "Entering SpInitUserModeContext 0x%lx\n", ContextHandle ));
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PNTLM_CLIENT_CONTEXT pContext = NULL;
|
|
UINT Length = 0;
|
|
PNTLM_CLIENT_CONTEXT pTmpContext = (PNTLM_CLIENT_CONTEXT) PackedContext->pvBuffer;
|
|
|
|
if (PackedContext->cbBuffer < sizeof(NTLM_CLIENT_CONTEXT))
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
SspPrint(( SSP_CRITICAL, "SpInitUserModeContext, ContextData size < NTLM_CLIENT_CONTEXT\n" ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
pContext = (PNTLM_CLIENT_CONTEXT) NtLmAllocate( sizeof(NTLM_CLIENT_CONTEXT) );
|
|
if (!pContext)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
SspPrint(( SSP_CRITICAL, "SpInitUserModeContext, NtLmAllocate returns NULL\n" ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
// If ClientTokenHandle is NULL, we are being called as as an effect of InitializeSecurityContext, else we are being called because of AcceptSecurityContext
|
|
if (pTmpContext->ClientTokenHandle != NULL)
|
|
{
|
|
pContext->ClientTokenHandle = pTmpContext->ClientTokenHandle;
|
|
if (FAILED(SspCreateTokenDacl(pTmpContext->ClientTokenHandle)))
|
|
{
|
|
Status = STATUS_INVALID_HANDLE;
|
|
SspPrint(( SSP_CRITICAL, "SpInitUserModeContext, SspCreateTokenDacl failed\n" ));
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
// Copy contents of PackedContext->pvBuffer to pContext
|
|
pContext->LsaContext = ContextHandle;
|
|
pContext->NegotiateFlags = pTmpContext->NegotiateFlags;
|
|
pContext->References = 1;
|
|
|
|
// keep all 128 bits here, so signing can be strong even if encrypt can't be
|
|
RtlCopyMemory( pContext->SessionKey, pTmpContext->SessionKey, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
// if doing full duplex as part of NTLM2, generate different sign and seal keys for each direction
|
|
// all we do is MD5 the base session key with a different magic constant
|
|
if ( pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2 ) {
|
|
MD5_CTX Md5Context;
|
|
ULONG KeyLen;
|
|
|
|
ASSERT(MD5DIGESTLEN == MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
if( pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_128 )
|
|
KeyLen = 16;
|
|
else if( pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_56 )
|
|
KeyLen = 7;
|
|
else
|
|
KeyLen = 5;
|
|
|
|
SspPrint(( SSP_SESSION_KEYS, "NTLMv2 session key size: %lu\n", KeyLen));
|
|
|
|
// make client to server encryption key
|
|
MD5Init(&Md5Context);
|
|
MD5Update(&Md5Context, pContext->SessionKey, KeyLen);
|
|
MD5Update(&Md5Context, (unsigned char*)CSSEALMAGIC, sizeof(CSSEALMAGIC));
|
|
MD5Final(&Md5Context);
|
|
|
|
// if TokenHandle == NULL, this is the client side
|
|
// put key in the right place: for client it's seal, for server it's unseal
|
|
if (pContext->ClientTokenHandle == NULL)
|
|
RtlCopyMemory(pContext->SealSessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
else
|
|
RtlCopyMemory(pContext->UnsealSessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
// make server to client encryption key
|
|
MD5Init(&Md5Context);
|
|
MD5Update(&Md5Context, pContext->SessionKey, KeyLen);
|
|
MD5Update(&Md5Context, (unsigned char*)SCSEALMAGIC, sizeof(SCSEALMAGIC));
|
|
MD5Final(&Md5Context);
|
|
ASSERT(MD5DIGESTLEN == MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
if (pContext->ClientTokenHandle == NULL)
|
|
RtlCopyMemory(pContext->UnsealSessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
else
|
|
RtlCopyMemory(pContext->SealSessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
// make client to server signing key -- always 128 bits!
|
|
MD5Init(&Md5Context);
|
|
MD5Update(&Md5Context, pContext->SessionKey, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
MD5Update(&Md5Context, (unsigned char*)CSSIGNMAGIC, sizeof(CSSIGNMAGIC));
|
|
MD5Final(&Md5Context);
|
|
if (pContext->ClientTokenHandle == NULL)
|
|
RtlCopyMemory(pContext->SignSessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
else
|
|
RtlCopyMemory(pContext->VerifySessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
// make server to client signing key
|
|
MD5Init(&Md5Context);
|
|
MD5Update(&Md5Context, pContext->SessionKey, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
MD5Update(&Md5Context, (unsigned char*)SCSIGNMAGIC, sizeof(SCSIGNMAGIC));
|
|
MD5Final(&Md5Context);
|
|
if (pContext->ClientTokenHandle == NULL)
|
|
RtlCopyMemory(pContext->VerifySessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
else
|
|
RtlCopyMemory(pContext->SignSessionKey, Md5Context.digest, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
// set pointers to different key schedule and nonce for each direction key schedule will be filled in later...
|
|
pContext->pSealRc4Sched = &pContext->SealRc4Sched;
|
|
pContext->pUnsealRc4Sched = &pContext->UnsealRc4Sched;
|
|
pContext->pSendNonce = &pContext->SendNonce;
|
|
pContext->pRecvNonce = &pContext->RecvNonce;
|
|
} else {
|
|
// just copy session key to all four keys
|
|
// leave them 128 bits -- they get cut to 40 bits later
|
|
RtlCopyMemory( pContext->SealSessionKey,pContext->SessionKey,MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
RtlCopyMemory( pContext->UnsealSessionKey,pContext->SessionKey,MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
RtlCopyMemory( pContext->SignSessionKey,pContext->SessionKey,MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
RtlCopyMemory( pContext->VerifySessionKey,pContext->SessionKey,MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
// set pointers to share a key schedule and nonce for each direction
|
|
// (OK because half duplex!)
|
|
pContext->pSealRc4Sched = &pContext->SealRc4Sched;
|
|
pContext->pUnsealRc4Sched = &pContext->SealRc4Sched;
|
|
pContext->pSendNonce = &pContext->SendNonce;
|
|
pContext->pRecvNonce = &pContext->SendNonce;
|
|
}
|
|
|
|
Length = (UINT) (PackedContext->cbBuffer - sizeof(NTLM_CLIENT_CONTEXT));
|
|
if (Length == 0)
|
|
{
|
|
//There's no string after the NTLM_CLIENT_CONTEXT struct
|
|
pContext->ContextNames = NULL;
|
|
}
|
|
else
|
|
{
|
|
pContext->ContextNames = (LPWSTR) NtLmAllocate(Length + sizeof(WCHAR));
|
|
if (!pContext->ContextNames)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RtlCopyMemory(pContext->ContextNames, pTmpContext + 1, Length );
|
|
|
|
// null terminate the string
|
|
|
|
*(pContext->ContextNames + (Length/2)) = L'\0';
|
|
}
|
|
|
|
pContext->SendNonce = pTmpContext->SendNonce;
|
|
pContext->RecvNonce = pTmpContext->RecvNonce;
|
|
|
|
SspRc4Key(pContext->NegotiateFlags, &pContext->SealRc4Sched, pContext->SealSessionKey);
|
|
SspRc4Key(pContext->NegotiateFlags, &pContext->UnsealRc4Sched, pContext->UnsealSessionKey);
|
|
|
|
pContext->PasswordExpiry = pTmpContext->PasswordExpiry;
|
|
pContext->UserFlags = pTmpContext->UserFlags;
|
|
|
|
RtlEnterCriticalSection(&NtLmUserContextLock);
|
|
InsertHeadList ( &NtLmUserContextList, &pContext->Next );
|
|
RtlLeaveCriticalSection(&NtLmUserContextLock);
|
|
|
|
Cleanup:
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (pContext != NULL)
|
|
{
|
|
FreeUserContext(pContext);
|
|
}
|
|
}
|
|
|
|
// Let FreeContextBuffer handle freeing the virtual allocs
|
|
if (PackedContext->pvBuffer != NULL)
|
|
{
|
|
FreeContextBuffer(PackedContext->pvBuffer);
|
|
PackedContext->pvBuffer = NULL;
|
|
}
|
|
|
|
SspPrint(( SSP_API, "Leaving SpInitUserModeContext: 0x%lx\n", Status ));
|
|
return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
|
|
}
|
|
|
|
|
|
// Bogus add-shift check sum
|
|
|
|
void SspGenCheckSum(IN PSecBuffer pMessage, OUT PNTLMSSP_MESSAGE_SIGNATURE pSig)
|
|
/*++
|
|
RoutineDescription:
|
|
Generate a crc-32 checksum for a buffer
|
|
Notes: This was stolen from net\svcdlls\ntlmssp\client\sign.c ,
|
|
routine SspGenCheckSum. It's possible that bugs got copied too
|
|
--*/
|
|
{
|
|
Crc32(pSig->CheckSum,pMessage->cbBuffer,pMessage->pvBuffer,&pSig->CheckSum);
|
|
}
|
|
|
|
|
|
VOID SspEncryptBuffer(IN PNTLM_CLIENT_CONTEXT pContext,IN struct RC4_KEYSTRUCT * pRc4Key,IN ULONG BufferSize,IN OUT PVOID Buffer)
|
|
/*++
|
|
RoutineDescription:
|
|
Encrypts a buffer with the RC4 key in the context.
|
|
If the context is for a datagram session, then the key is copied before being used to encrypt the buffer.
|
|
Arguments:
|
|
pContext - Context containing the key to encrypt the data
|
|
BufferSize - Length of buffer in bytes
|
|
Buffer - Buffer to encrypt.
|
|
Notes: This was stolen from net\svcdlls\ntlmssp\client\sign.c ,
|
|
routine SspEncryptBuffer. It's possible that bugs got copied too
|
|
--*/
|
|
{
|
|
struct RC4_KEYSTRUCT TemporaryKey;
|
|
/// struct RC4_KEYSTRUCT * EncryptionKey = &pContext->Rc4Key;
|
|
struct RC4_KEYSTRUCT * EncryptionKey = pRc4Key;
|
|
|
|
if (BufferSize == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// For datagram (application supplied sequence numbers) before NTLM2 we used to copy the key before encrypting so we don't
|
|
// have a changing key; but that reused the key stream. Now we only do that when backwards compatibility is explicitly called for.
|
|
if (((pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) != 0) && ((pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2) == 0) ) {
|
|
RtlCopyMemory(&TemporaryKey,EncryptionKey,sizeof(struct RC4_KEYSTRUCT));
|
|
EncryptionKey = &TemporaryKey;
|
|
}
|
|
|
|
rc4(EncryptionKey,BufferSize,(PUCHAR) Buffer);
|
|
}
|
|
|
|
|
|
typedef enum _eSignSealOp {
|
|
eSign, // MakeSignature is calling
|
|
eVerify, // VerifySignature is calling
|
|
eSeal, // SealMessage is calling
|
|
eUnseal // UnsealMessage is calling
|
|
} eSignSealOp;
|
|
|
|
|
|
SECURITY_STATUS SspSignSealHelper(
|
|
IN PNTLM_CLIENT_CONTEXT pContext,
|
|
IN eSignSealOp Op,
|
|
IN OUT PSecBufferDesc pMessage,
|
|
IN ULONG MessageSeqNo,
|
|
OUT PNTLMSSP_MESSAGE_SIGNATURE pSig,
|
|
OUT PNTLMSSP_MESSAGE_SIGNATURE * ppSig
|
|
)
|
|
/*++
|
|
RoutineDescription:
|
|
Handle signing a message
|
|
--*/
|
|
{
|
|
HMACMD5_CTX HMACMD5Context;
|
|
UCHAR TempSig[MD5DIGESTLEN];
|
|
NTLMSSP_MESSAGE_SIGNATURE Sig;
|
|
int Signature;
|
|
ULONG i;
|
|
PUCHAR pKey; // ptr to key to use for encryption
|
|
PUCHAR pSignKey; // ptr to key to use for signing
|
|
PULONG pNonce; // ptr to nonce to use
|
|
struct RC4_KEYSTRUCT * pRc4Sched; // ptr to key schedule to use
|
|
|
|
Signature = -1;
|
|
for (i = 0; i < pMessage->cBuffers; i++)
|
|
{
|
|
if ((pMessage->pBuffers[i].BufferType & 0xFF) == SECBUFFER_TOKEN)
|
|
{
|
|
Signature = i;
|
|
break;
|
|
}
|
|
}
|
|
if (Signature == -1)
|
|
{
|
|
return(SEC_E_INVALID_TOKEN);
|
|
}
|
|
|
|
if (pMessage->pBuffers[Signature].cbBuffer < NTLMSSP_MESSAGE_SIGNATURE_SIZE)
|
|
{
|
|
return(SEC_E_INVALID_TOKEN);
|
|
}
|
|
|
|
*ppSig = (NTLMSSP_MESSAGE_SIGNATURE*)pMessage->pBuffers[Signature].pvBuffer;
|
|
|
|
// If sequence detect wasn't requested, put on an empty
|
|
// security token . Don't do the check if Seal/Unseal is called.
|
|
if (!(pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) && (Op == eSign || Op == eVerify))
|
|
{
|
|
RtlZeroMemory(pSig,NTLMSSP_MESSAGE_SIGNATURE_SIZE);
|
|
pSig->Version = NTLM_SIGN_VERSION;
|
|
return(SEC_E_OK);
|
|
}
|
|
|
|
// figure out which key, key schedule, and nonce to use
|
|
// depends on the op. SspAddLocalContext set up so that code on client and server just (un)seals with (un)seal key or key schedule, etc.
|
|
// and also sets pointers to share sending/receiving key schedule/nonce when in half duplex mode.
|
|
// Hence, this code gets to act as if it were always in full duplex mode.
|
|
switch (Op) {
|
|
case eSeal:
|
|
pSignKey = pContext->SignSessionKey; // if NTLM2
|
|
pKey = pContext->SealSessionKey;
|
|
pRc4Sched = pContext->pSealRc4Sched;
|
|
pNonce = pContext->pSendNonce;
|
|
break;
|
|
case eUnseal:
|
|
pSignKey = pContext->VerifySessionKey; // if NTLM2
|
|
pKey = pContext->UnsealSessionKey;
|
|
pRc4Sched = pContext->pUnsealRc4Sched;
|
|
pNonce = pContext->pRecvNonce;
|
|
break;
|
|
case eSign:
|
|
pSignKey = pContext->SignSessionKey; // if NTLM2
|
|
pKey = pContext->SealSessionKey; // might be used to encrypt the signature
|
|
pRc4Sched = pContext->pSealRc4Sched;
|
|
pNonce = pContext->pSendNonce;
|
|
break;
|
|
case eVerify:
|
|
pSignKey = pContext->VerifySessionKey; // if NTLM2
|
|
pKey = pContext->UnsealSessionKey; // might be used to decrypt the signature
|
|
pRc4Sched = pContext->pUnsealRc4Sched;
|
|
pNonce = pContext->pRecvNonce;
|
|
break;
|
|
}
|
|
|
|
// Either we can supply the sequence number, or the application can supply the message sequence number.
|
|
Sig.Version = NTLM_SIGN_VERSION;
|
|
|
|
// if we're doing the new NTLM2 version:
|
|
if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2) {
|
|
if ((pContext->NegotiateFlags & NTLMSSP_APP_SEQ) == 0)
|
|
{
|
|
Sig.Nonce = *pNonce; // use our sequence number
|
|
(*pNonce) += 1;
|
|
}
|
|
else {
|
|
if (Op == eSeal || Op == eSign || MessageSeqNo != 0)
|
|
Sig.Nonce = MessageSeqNo;
|
|
else
|
|
Sig.Nonce = (*ppSig)->Nonce;
|
|
|
|
// if using RC4, must rekey for each packet
|
|
// RC4 is used for seal, unseal; and for encrypting the HMAC hash if key exchange was negotiated (we use just HMAC if no key exchange, so that a good signing option exists with no RC4 encryption needed)
|
|
if (Op == eSeal || Op == eUnseal || pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
|
|
{
|
|
MD5_CTX Md5ContextReKey;
|
|
|
|
MD5Init(&Md5ContextReKey);
|
|
MD5Update(&Md5ContextReKey, pKey, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
MD5Update(&Md5ContextReKey, (unsigned char*)&Sig.Nonce, sizeof(Sig.Nonce));
|
|
MD5Final(&Md5ContextReKey);
|
|
ASSERT(MD5DIGESTLEN == MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
SspRc4Key(pContext->NegotiateFlags, pRc4Sched, Md5ContextReKey.digest);
|
|
}
|
|
}
|
|
|
|
// using HMAC hash, init it with the key
|
|
HMACMD5Init(&HMACMD5Context, pSignKey, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
// include the message sequence number
|
|
HMACMD5Update(&HMACMD5Context, (unsigned char*)&Sig.Nonce, sizeof(Sig.Nonce));
|
|
|
|
for (i = 0; i < pMessage->cBuffers ; i++ )
|
|
{
|
|
if (((pMessage->pBuffers[i].BufferType & 0xFF) == SECBUFFER_DATA) && (pMessage->pBuffers[i].cbBuffer != 0))
|
|
{
|
|
// decrypt (before checksum...) if it's not READ_ONLY
|
|
if ((Op==eUnseal) && !(pMessage->pBuffers[i].BufferType & SECBUFFER_READONLY) )
|
|
{
|
|
SspEncryptBuffer(pContext, pRc4Sched, pMessage->pBuffers[i].cbBuffer, pMessage->pBuffers[i].pvBuffer);
|
|
}
|
|
|
|
HMACMD5Update(&HMACMD5Context, (unsigned char*)pMessage->pBuffers[i].pvBuffer, pMessage->pBuffers[i].cbBuffer);
|
|
|
|
// Encrypt if its not READ_ONLY
|
|
if ((Op==eSeal) && !(pMessage->pBuffers[i].BufferType & SECBUFFER_READONLY) )
|
|
{
|
|
SspEncryptBuffer(pContext, pRc4Sched, pMessage->pBuffers[i].cbBuffer, pMessage->pBuffers[i].pvBuffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
HMACMD5Final(&HMACMD5Context, TempSig);
|
|
|
|
// use RandomPad and Checksum fields for 8 bytes of MD5 hash
|
|
RtlCopyMemory(&Sig.RandomPad, TempSig, 8);
|
|
|
|
// if we're using crypto for KEY_EXCH, may as well use it for signing too...
|
|
if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)
|
|
SspEncryptBuffer(pContext, pRc4Sched, 8, &Sig.RandomPad);
|
|
}
|
|
// pre-NTLM2 methods
|
|
else {
|
|
// required by CRC-32 algorithm
|
|
Sig.CheckSum = 0xffffffff;
|
|
|
|
for (i = 0; i < pMessage->cBuffers ; i++ )
|
|
{
|
|
if (((pMessage->pBuffers[i].BufferType & 0xFF) == SECBUFFER_DATA) &&
|
|
!(pMessage->pBuffers[i].BufferType & SECBUFFER_READONLY) &&
|
|
(pMessage->pBuffers[i].cbBuffer != 0))
|
|
{
|
|
// decrypt (before checksum...)
|
|
if (Op==eUnseal)
|
|
{
|
|
SspEncryptBuffer(pContext,pRc4Sched,pMessage->pBuffers[i].cbBuffer,pMessage->pBuffers[i].pvBuffer);
|
|
}
|
|
|
|
SspGenCheckSum(&pMessage->pBuffers[i], &Sig);
|
|
|
|
// Encrypt
|
|
if (Op==eSeal)
|
|
{
|
|
SspEncryptBuffer(pContext,pRc4Sched,pMessage->pBuffers[i].cbBuffer,pMessage->pBuffers[i].pvBuffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Required by CRC-32 algorithm
|
|
Sig.CheckSum ^= 0xffffffff;
|
|
|
|
// when we encrypt 0, we will get the cipher stream for the nonce!
|
|
Sig.Nonce = 0;
|
|
|
|
SspEncryptBuffer(pContext,pRc4Sched,sizeof(NTLMSSP_MESSAGE_SIGNATURE) - sizeof(ULONG),&Sig.RandomPad);
|
|
|
|
if ((pContext->NegotiateFlags & NTLMSSP_APP_SEQ) == 0)
|
|
{
|
|
Sig.Nonce ^= *pNonce; // use our sequence number and encrypt it
|
|
(*pNonce) += 1;
|
|
}
|
|
else if (Op == eSeal || Op == eSign || MessageSeqNo != 0)
|
|
Sig.Nonce ^= MessageSeqNo; // use caller's sequence number and encrypt it
|
|
else
|
|
Sig.Nonce = (*ppSig)->Nonce; // use sender's sequence number
|
|
|
|
// for SignMessage calling, does nothing (copies garbage)
|
|
// For VerifyMessage calling, allows it to compare sig block
|
|
// upon return to Verify without knowing whether its MD5 or CRC32
|
|
Sig.RandomPad = (*ppSig)->RandomPad;
|
|
}
|
|
|
|
pMessage->pBuffers[Signature].cbBuffer = sizeof(NTLMSSP_MESSAGE_SIGNATURE);
|
|
|
|
RtlCopyMemory(pSig,&Sig,NTLMSSP_MESSAGE_SIGNATURE_SIZE);
|
|
return(SEC_E_OK);
|
|
}
|
|
|
|
|
|
// Synopsis: Signs a message buffer by calculatinga checksum over all the non-read only data buffers and encrypting the checksum along with a nonce.
|
|
|
|
// Arguments: ContextHandle - Handle of the context to use to sign the message.
|
|
// QualityOfProtection - Unused flags.
|
|
// MessageBuffers - Contains an array of buffers to sign and to store the signature.
|
|
// MessageSequenceNumber - Sequence number for this message, only used in datagram cases.
|
|
|
|
// Requires: STATUS_INVALID_HANDLE - the context could not be found or was not configured for message integrity.
|
|
// STATUS_INVALID_PARAMETER - the signature buffer could not be found.
|
|
// STATUS_BUFFER_TOO_SMALL - the signature buffer is too small to hold the signature
|
|
|
|
// Notes: This was stolen from net\svcdlls\ntlmssp\client\sign.c ,
|
|
// routine SspHandleSignMessage. It's possible that bugs got copied too
|
|
NTSTATUS NTAPI SpMakeSignature(IN ULONG_PTR ContextHandle,IN ULONG fQOP,IN PSecBufferDesc pMessage,IN ULONG MessageSeqNo)
|
|
{
|
|
SspPrint(( SSP_API, "Entering SpMakeSignature\n" ));
|
|
NTSTATUS Status = S_OK;
|
|
NTSTATUS SubStatus = S_OK;
|
|
|
|
PNTLM_CLIENT_CONTEXT pContext;
|
|
NTLMSSP_MESSAGE_SIGNATURE Sig;
|
|
NTLMSSP_MESSAGE_SIGNATURE *pSig;
|
|
|
|
UNREFERENCED_PARAMETER(fQOP);
|
|
UNREFERENCED_PARAMETER(MessageSeqNo);
|
|
|
|
pContext = ReferenceUserContext(ContextHandle, FALSE);
|
|
|
|
if (pContext == NULL)
|
|
{
|
|
Status = STATUS_INVALID_HANDLE;
|
|
SspPrint(( SSP_CRITICAL, "SpMakeSignature, ReferenceUserContext returns NULL\n" ));
|
|
goto CleanUp;
|
|
}
|
|
|
|
|
|
Status = SspSignSealHelper(pContext,eSign,pMessage,MessageSeqNo,&Sig,&pSig);
|
|
if( !NT_SUCCESS(Status) ) {
|
|
SspPrint(( SSP_CRITICAL, "SpMakeSignature, SspSignSealHelper returns %lx\n", Status ));
|
|
goto CleanUp;
|
|
}
|
|
|
|
RtlCopyMemory(pSig,&Sig,NTLMSSP_MESSAGE_SIGNATURE_SIZE);
|
|
|
|
CleanUp:
|
|
if (pContext != NULL)
|
|
{
|
|
SubStatus = DereferenceUserContext(pContext);
|
|
|
|
// Don't destroy real status
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = SubStatus;
|
|
}
|
|
}
|
|
|
|
SspPrint(( SSP_API, "Leaving SpMakeSignature: 0x%lx\n", Status ));
|
|
return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
|
|
}
|
|
|
|
|
|
// Synopsis: Verifies a signed message buffer by calculating a checksum over all the non-read only data buffers and encrypting the checksum along with a nonce.
|
|
|
|
// Arguments: ContextHandle - Handle of the context to use to sign the message.
|
|
// MessageBuffers - Contains an array of signed buffers and a signature buffer.
|
|
// MessageSequenceNumber - Sequence number for this message, only used in datagram cases.
|
|
// QualityOfProtection - Unused flags.//
|
|
// Requires: STATUS_INVALID_HANDLE - the context could not be found or was not configured for message integrity.
|
|
// STATUS_INVALID_PARAMETER - the signature buffer could not be found or was too small.
|
|
// Notes: This was stolen from net\svcdlls\ntlmssp\client\sign.c ,
|
|
// routine SspHandleVerifyMessage. It's possible that bugs got copied too
|
|
NTSTATUS NTAPI SpVerifySignature(IN ULONG_PTR ContextHandle,IN PSecBufferDesc pMessage,IN ULONG MessageSeqNo,OUT PULONG pfQOP)
|
|
{
|
|
SspPrint(( SSP_API, "Entering SpVerifySignature\n" ));
|
|
NTSTATUS Status = S_OK;
|
|
NTSTATUS SubStatus = S_OK;
|
|
PNTLM_CLIENT_CONTEXT pContext;
|
|
NTLMSSP_MESSAGE_SIGNATURE Sig;
|
|
PNTLMSSP_MESSAGE_SIGNATURE pSig; // pointer to buffer with sig in it
|
|
|
|
UNREFERENCED_PARAMETER(pfQOP);
|
|
|
|
pContext = ReferenceUserContext(ContextHandle, FALSE);
|
|
if (!pContext)
|
|
{
|
|
Status = STATUS_INVALID_HANDLE;
|
|
SspPrint(( SSP_CRITICAL, "SpVerifySignature, ReferenceUserContext returns NULL\n" ));
|
|
goto CleanUp;
|
|
}
|
|
|
|
Status = SspSignSealHelper(pContext,eVerify,pMessage,MessageSeqNo,&Sig,&pSig);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
SspPrint(( SSP_CRITICAL, "SpVerifySignature, SspSignSealHelper returns %lx\n", Status ));
|
|
goto CleanUp;
|
|
}
|
|
|
|
if (pSig->Version != NTLM_SIGN_VERSION) {
|
|
Status = SEC_E_INVALID_TOKEN;
|
|
goto CleanUp;
|
|
}
|
|
|
|
// validate the signature...
|
|
if (pSig->CheckSum != Sig.CheckSum)
|
|
{
|
|
Status = SEC_E_MESSAGE_ALTERED;
|
|
goto CleanUp;
|
|
}
|
|
|
|
// with MD5 sig, this now matters!
|
|
if (pSig->RandomPad != Sig.RandomPad)
|
|
{
|
|
Status = SEC_E_MESSAGE_ALTERED;
|
|
goto CleanUp;
|
|
}
|
|
|
|
if (pSig->Nonce != Sig.Nonce)
|
|
{
|
|
Status = SEC_E_OUT_OF_SEQUENCE;
|
|
goto CleanUp;
|
|
}
|
|
|
|
CleanUp:
|
|
if (pContext != NULL)
|
|
{
|
|
SubStatus = DereferenceUserContext(pContext);
|
|
|
|
// Don't destroy real status
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = SubStatus;
|
|
}
|
|
}
|
|
|
|
SspPrint(( SSP_API, "Leaving SpVerifySignature: 0x%lx\n", Status ));
|
|
return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
|
|
}
|
|
|
|
|
|
|
|
// Synopsis: Verifies a signed message buffer by calculating a checksum over all the non-read only data buffers and encrypting the checksum along with a nonce.
|
|
|
|
// Arguments: ContextHandle - Handle of the context to use to sign the message.
|
|
// MessageBuffers - Contains an array of signed buffers and a signature buffer.
|
|
// MessageSequenceNumber - Sequence number for this message, only used in datagram cases.
|
|
// QualityOfProtection - Unused flags.//
|
|
// Requires: STATUS_INVALID_HANDLE - the context could not be found or was not configured for message integrity.
|
|
// STATUS_INVALID_PARAMETER - the signature buffer could not be found or was too small.
|
|
// Notes: This was stolen from net\svcdlls\ntlmssp\client\sign.c ,
|
|
// routine SspHandleSealMessage. It's possible that bugs got copied too
|
|
NTSTATUS NTAPI SpSealMessage(IN ULONG_PTR ContextHandle,IN ULONG fQOP,IN PSecBufferDesc pMessage,IN ULONG MessageSeqNo)
|
|
{
|
|
SspPrint(( SSP_API, "Entering SpSealMessage\n" ));
|
|
NTSTATUS Status = S_OK;
|
|
NTSTATUS SubStatus = S_OK;
|
|
PNTLM_CLIENT_CONTEXT pContext;
|
|
NTLMSSP_MESSAGE_SIGNATURE Sig;
|
|
PNTLMSSP_MESSAGE_SIGNATURE pSig; // pointer to buffer where sig goes
|
|
|
|
UNREFERENCED_PARAMETER(fQOP);
|
|
|
|
pContext = ReferenceUserContext(ContextHandle, FALSE);
|
|
if (!pContext)
|
|
{
|
|
Status = STATUS_INVALID_HANDLE;
|
|
SspPrint(( SSP_CRITICAL, "SpSealMessage, ReferenceUserContext returns NULL\n" ));
|
|
goto CleanUp;
|
|
}
|
|
|
|
Status = SspSignSealHelper(pContext,eSeal,pMessage,MessageSeqNo,&Sig,&pSig);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
SspPrint(( SSP_CRITICAL, "SpVerifySignature, SspSignSealHelper returns %lx\n", Status ));
|
|
goto CleanUp;
|
|
}
|
|
|
|
RtlCopyMemory(pSig,&Sig,NTLMSSP_MESSAGE_SIGNATURE_SIZE);
|
|
|
|
CleanUp:
|
|
if (pContext != NULL)
|
|
{
|
|
SubStatus = DereferenceUserContext(pContext);
|
|
|
|
// Don't destroy real status
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = SubStatus;
|
|
}
|
|
}
|
|
|
|
SspPrint(( SSP_API, "Leaving SpSealMessage: 0x%lx\n", Status ));
|
|
return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
|
|
}
|
|
|
|
|
|
// Synopsis: Verifies a signed message buffer by calculating a checksum over all the non-read only data buffers and encrypting the checksum along with a nonce.
|
|
// Arguments: ContextHandle - Handle of the context to use to sign the message.
|
|
// MessageBuffers - Contains an array of signed buffers and a signature buffer.
|
|
// MessageSequenceNumber - Sequence number for this message, only used in datagram cases.
|
|
// QualityOfProtection - Unused flags.//
|
|
// Requires: STATUS_INVALID_HANDLE - the context could not be found or was not configured for message integrity.
|
|
// STATUS_INVALID_PARAMETER - the signature buffer could not be found or was too small.
|
|
// Notes: This was stolen from net\svcdlls\ntlmssp\client\sign.c ,
|
|
// routine SspHandleUnsealMessage. It's possible that bugs got copied too
|
|
NTSTATUS NTAPI SpUnsealMessage(IN ULONG_PTR ContextHandle, IN PSecBufferDesc pMessage, IN ULONG MessageSeqNo, OUT PULONG pfQOP)
|
|
{
|
|
SspPrint(( SSP_API, "Entering SpUnsealMessage\n" ));
|
|
NTSTATUS Status = S_OK;
|
|
NTSTATUS SubStatus = S_OK;
|
|
PNTLM_CLIENT_CONTEXT pContext;
|
|
NTLMSSP_MESSAGE_SIGNATURE Sig;
|
|
PNTLMSSP_MESSAGE_SIGNATURE pSig; // pointer to buffer where sig goes
|
|
|
|
UNREFERENCED_PARAMETER(pfQOP);
|
|
|
|
pContext = ReferenceUserContext(ContextHandle, FALSE);
|
|
if (!pContext)
|
|
{
|
|
Status = STATUS_INVALID_HANDLE;
|
|
SspPrint(( SSP_CRITICAL, "SpUnsealMessage, ReferenceUserContext returns NULL\n" ));
|
|
goto CleanUp;
|
|
}
|
|
|
|
Status = SspSignSealHelper(pContext,eUnseal,pMessage,MessageSeqNo,&Sig,&pSig);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
SspPrint(( SSP_CRITICAL, "SpUnsealMessage, SspSignSealHelper returns %lx\n", Status ));
|
|
goto CleanUp;
|
|
}
|
|
|
|
if (pSig->Version != NTLM_SIGN_VERSION) {
|
|
Status = SEC_E_INVALID_TOKEN;
|
|
goto CleanUp;
|
|
}
|
|
|
|
// validate the signature...
|
|
if (pSig->CheckSum != Sig.CheckSum)
|
|
{
|
|
Status = SEC_E_MESSAGE_ALTERED;
|
|
goto CleanUp;
|
|
}
|
|
|
|
if (pSig->RandomPad != Sig.RandomPad)
|
|
{
|
|
Status = SEC_E_MESSAGE_ALTERED;
|
|
goto CleanUp;
|
|
}
|
|
|
|
if (pSig->Nonce != Sig.Nonce)
|
|
{
|
|
Status = SEC_E_OUT_OF_SEQUENCE;
|
|
goto CleanUp;
|
|
}
|
|
|
|
CleanUp:
|
|
if (pContext != NULL)
|
|
{
|
|
SubStatus = DereferenceUserContext(pContext);
|
|
|
|
// Don't destroy real status
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = SubStatus;
|
|
}
|
|
}
|
|
|
|
SspPrint(( SSP_API, "Leaving SpUnsealMessage: 0x%lx\n", Status ));
|
|
return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
|
|
}
|
|
|
|
|
|
|
|
// Synopsis: returns a pointer to the token for a server-side context
|
|
NTSTATUS NTAPI SpGetContextToken(IN ULONG_PTR ContextHandle,OUT PHANDLE ImpersonationToken)
|
|
{
|
|
SspPrint(( SSP_API, "Entering SpGetContextToken\n" ));
|
|
NTSTATUS Status = S_OK;
|
|
PNTLM_CLIENT_CONTEXT pContext;
|
|
|
|
pContext = ReferenceUserContext(ContextHandle, FALSE);
|
|
if (pContext && pContext->ClientTokenHandle)
|
|
{
|
|
*ImpersonationToken = pContext->ClientTokenHandle;
|
|
Status= S_OK;
|
|
goto CleanUp;
|
|
}
|
|
|
|
Status = STATUS_INVALID_HANDLE;
|
|
SspPrint(( SSP_CRITICAL, "SpGetContextToken, no token handle\n" ));
|
|
|
|
CleanUp:
|
|
if (pContext != NULL)
|
|
{
|
|
Status = DereferenceUserContext(pContext);
|
|
}
|
|
|
|
SspPrint(( SSP_API, "Leaving SpGetContextToken: 0x%lx\n", Status ));
|
|
return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
|
|
}
|
|
|
|
|
|
// Synopsis: Querys attributes of the specified context
|
|
// This API allows a customer of the security services to determine certain attributes of the context. These are: sizes, names, and lifespan.
|
|
|
|
// Arguments:
|
|
// ContextHandle - Handle to the context to query.
|
|
// Attribute - Attribute to query.
|
|
// #define SECPKG_ATTR_SIZES 0
|
|
// #define SECPKG_ATTR_NAMES 1
|
|
// #define SECPKG_ATTR_LIFESPAN 2
|
|
// Buffer - Buffer to copy the data into. The buffer must be large enough to fit the queried attribute.
|
|
// Returns:
|
|
// STATUS_SUCCESS - Call completed successfully
|
|
// STATUS_INVALID_HANDLE -- Credential/Context Handle is invalid
|
|
// STATUS_NOT_SUPPORTED -- Function code is not supported
|
|
NTSTATUS NTAPI SpQueryContextAttributes(IN ULONG_PTR ContextHandle,IN ULONG Attribute,IN OUT PVOID Buffer)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PNTLM_CLIENT_CONTEXT pContext = NULL;
|
|
PSecPkgContext_Sizes ContextSizes;
|
|
PSecPkgContext_Flags ContextFlags;
|
|
PSecPkgContext_DceInfo ContextDceInfo = NULL;
|
|
PSecPkgContext_Names ContextNames = NULL;
|
|
PSecPkgContext_PackageInfo PackageInfo;
|
|
PSecPkgContext_NegotiationInfo NegInfo ;
|
|
PSecPkgContext_PasswordExpiry PasswordExpires;
|
|
PSecPkgContext_UserFlags UserFlags;
|
|
ULONG PackageInfoSize = 0;
|
|
|
|
SspPrint(( SSP_API, "Entering SpQueryContextAttributes\n" ));
|
|
|
|
pContext = ReferenceUserContext(ContextHandle, FALSE);
|
|
if (pContext == NULL) {
|
|
Status = STATUS_INVALID_HANDLE;
|
|
SspPrint(( SSP_API_MORE, "SpQueryContextAttributes, ReferenceUserContext returns NULL (possible incomplete context)\n" ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Handle each of the various queried attributes
|
|
SspPrint(( SSP_API_MORE, "SpQueryContextAttributes : 0x%lx\n", Attribute ));
|
|
switch ( Attribute) {
|
|
case SECPKG_ATTR_SIZES:
|
|
ContextSizes = (PSecPkgContext_Sizes) Buffer;
|
|
ContextSizes->cbMaxToken = NTLMSP_MAX_TOKEN_SIZE;
|
|
|
|
if (pContext->NegotiateFlags & (NTLMSSP_NEGOTIATE_ALWAYS_SIGN | NTLMSSP_NEGOTIATE_SIGN | NTLMSSP_NEGOTIATE_SEAL) ) {
|
|
ContextSizes->cbMaxSignature = NTLMSSP_MESSAGE_SIGNATURE_SIZE;
|
|
} else {
|
|
ContextSizes->cbMaxSignature = 0;
|
|
}
|
|
|
|
if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_SEAL) {
|
|
ContextSizes->cbBlockSize = 1;
|
|
ContextSizes->cbSecurityTrailer = NTLMSSP_MESSAGE_SIGNATURE_SIZE;
|
|
}
|
|
else
|
|
{
|
|
ContextSizes->cbBlockSize = 0;
|
|
ContextSizes->cbSecurityTrailer = 0;
|
|
}
|
|
|
|
break;
|
|
case SECPKG_ATTR_DCE_INFO:
|
|
ContextDceInfo = (PSecPkgContext_DceInfo) Buffer;
|
|
if (pContext->ContextNames)
|
|
{
|
|
UINT Length = wcslen(pContext->ContextNames);
|
|
ContextDceInfo->pPac = (LPWSTR)UserFunctions->AllocateHeap((Length + 1) * sizeof(WCHAR));
|
|
if (ContextDceInfo->pPac == NULL) {
|
|
Status = STATUS_NO_MEMORY;
|
|
SspPrint(( SSP_CRITICAL, "SpQueryContextAttributes, NtLmAllocate returns NULL\n" ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
RtlCopyMemory((LPWSTR) ContextDceInfo->pPac,pContext->ContextNames,Length * sizeof(WCHAR));
|
|
*((LPWSTR)(ContextDceInfo->pPac) + Length) = L'\0';
|
|
}
|
|
else
|
|
{
|
|
SspPrint(( SSP_API_MORE, "SpQueryContextAttributes no ContextNames\n" ));
|
|
ContextDceInfo->pPac = (LPWSTR) UserFunctions->AllocateHeap(sizeof(WCHAR));
|
|
*((LPWSTR)(ContextDceInfo->pPac)) = L'\0';
|
|
}
|
|
|
|
ContextDceInfo->AuthzSvc = 0;
|
|
break;
|
|
case SECPKG_ATTR_NAMES:
|
|
ContextNames = (PSecPkgContext_Names) Buffer;
|
|
if (pContext->ContextNames)
|
|
{
|
|
UINT Length = wcslen(pContext->ContextNames);
|
|
ContextNames->sUserName = (LPWSTR) UserFunctions->AllocateHeap((Length+1) * sizeof(WCHAR));
|
|
if (ContextNames->sUserName == NULL) {
|
|
Status = STATUS_NO_MEMORY;
|
|
SspPrint(( SSP_CRITICAL, "SpQueryContextAttributes, NtLmAllocate returns NULL\n" ));
|
|
goto Cleanup;
|
|
}
|
|
RtlCopyMemory(ContextNames->sUserName,pContext->ContextNames,Length * sizeof(WCHAR));
|
|
*(ContextNames->sUserName + Length) = L'\0';
|
|
}
|
|
else
|
|
{
|
|
SspPrint(( SSP_API_MORE, "SpQueryContextAttributes no ContextNames\n" ));
|
|
ContextNames->sUserName = (LPWSTR) UserFunctions->AllocateHeap(sizeof(WCHAR));
|
|
*(ContextNames->sUserName) = L'\0';
|
|
}
|
|
|
|
break;
|
|
case SECPKG_ATTR_PACKAGE_INFO:
|
|
case SECPKG_ATTR_NEGOTIATION_INFO:
|
|
// Return the information about this package. This is useful for callers who used SPNEGO and don't know what package they got.
|
|
PackageInfo = (PSecPkgContext_PackageInfo) Buffer;
|
|
PackageInfoSize = sizeof(SecPkgInfoW) + sizeof(NTLMSP_NAME) + sizeof(NTLMSP_COMMENT);
|
|
PackageInfo->PackageInfo = (PSecPkgInfoW) UserFunctions->AllocateHeap(PackageInfoSize);
|
|
if (PackageInfo->PackageInfo == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
PackageInfo->PackageInfo->Name = (LPWSTR) (PackageInfo->PackageInfo + 1);
|
|
PackageInfo->PackageInfo->Comment = (LPWSTR) ((((PBYTE) PackageInfo->PackageInfo->Name)) + sizeof(NTLMSP_NAME));
|
|
wcscpy(PackageInfo->PackageInfo->Name,NTLMSP_NAME);
|
|
wcscpy(PackageInfo->PackageInfo->Comment,NTLMSP_COMMENT);
|
|
PackageInfo->PackageInfo->wVersion = SECURITY_SUPPORT_PROVIDER_INTERFACE_VERSION;
|
|
PackageInfo->PackageInfo->wRPCID = RPC_C_AUTHN_WINNT;
|
|
PackageInfo->PackageInfo->fCapabilities = NTLMSP_CAPS;
|
|
PackageInfo->PackageInfo->cbMaxToken = NTLMSP_MAX_TOKEN_SIZE;
|
|
|
|
if ( Attribute == SECPKG_ATTR_NEGOTIATION_INFO )
|
|
{
|
|
NegInfo = (PSecPkgContext_NegotiationInfo) PackageInfo ;
|
|
NegInfo->NegotiationState = SECPKG_NEGOTIATION_COMPLETE ;
|
|
}
|
|
break;
|
|
case SECPKG_ATTR_PASSWORD_EXPIRY:
|
|
PasswordExpires = (PSecPkgContext_PasswordExpiry) Buffer;
|
|
if(pContext->PasswordExpiry.QuadPart != 0) {
|
|
PasswordExpires->tsPasswordExpires = pContext->PasswordExpiry;
|
|
} else {
|
|
Status = SEC_E_UNSUPPORTED_FUNCTION;
|
|
}
|
|
break;
|
|
case SECPKG_ATTR_USER_FLAGS:
|
|
UserFlags = (PSecPkgContext_UserFlags) Buffer;
|
|
UserFlags->UserFlags = pContext->UserFlags;
|
|
break;
|
|
case SECPKG_ATTR_FLAGS:
|
|
{
|
|
BOOLEAN Client = (pContext->ClientTokenHandle == 0);
|
|
ULONG Flags = 0;
|
|
|
|
// BUGBUG: this doesn't return the complete flags
|
|
ContextFlags = (PSecPkgContext_Flags) Buffer;
|
|
ContextFlags->Flags = 0;
|
|
|
|
if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_SEAL) {
|
|
if( Client )
|
|
{
|
|
Flags |= ISC_RET_CONFIDENTIALITY;
|
|
} else {
|
|
Flags |= ASC_RET_CONFIDENTIALITY;
|
|
}
|
|
}
|
|
|
|
if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) {
|
|
if( Client )
|
|
{
|
|
Flags |= ISC_RET_SEQUENCE_DETECT | ISC_RET_REPLAY_DETECT | ISC_RET_INTEGRITY;
|
|
} else {
|
|
Flags |= ASC_RET_SEQUENCE_DETECT | ASC_RET_REPLAY_DETECT | ASC_RET_INTEGRITY;
|
|
}
|
|
}
|
|
|
|
if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_NULL_SESSION) {
|
|
if( Client )
|
|
{
|
|
Flags |= ISC_RET_NULL_SESSION;
|
|
} else {
|
|
Flags |= ASC_RET_NULL_SESSION;
|
|
}
|
|
}
|
|
|
|
if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) {
|
|
if( Client )
|
|
{
|
|
Flags |= ISC_RET_DATAGRAM;
|
|
} else {
|
|
Flags |= ASC_RET_DATAGRAM;
|
|
}
|
|
}
|
|
|
|
if (pContext->NegotiateFlags & NTLMSSP_NEGOTIATE_IDENTIFY) {
|
|
if( Client )
|
|
{
|
|
Flags |= ISC_RET_IDENTIFY;
|
|
} else {
|
|
Flags |= ASC_RET_IDENTIFY;
|
|
}
|
|
}
|
|
|
|
ContextFlags->Flags |= Flags;
|
|
break;
|
|
}
|
|
case SECPKG_ATTR_LIFESPAN:
|
|
default:
|
|
Status = STATUS_NOT_SUPPORTED;
|
|
break;
|
|
}
|
|
|
|
Cleanup:
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
switch (Attribute) {
|
|
case SECPKG_ATTR_NAMES:
|
|
if (ContextNames != NULL && ContextNames->sUserName )
|
|
{
|
|
NtLmFree(ContextNames->sUserName);
|
|
}
|
|
break;
|
|
case SECPKG_ATTR_DCE_INFO:
|
|
if (ContextDceInfo != NULL && ContextDceInfo->pPac)
|
|
{
|
|
NtLmFree(ContextDceInfo->pPac);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pContext != NULL)
|
|
{
|
|
(VOID) DereferenceUserContext(pContext);
|
|
}
|
|
|
|
SspPrint(( SSP_API, "Leaving SpQueryContextAttributes: 0x%lx\n", Status ));
|
|
return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
|
|
}
|
|
|
|
|
|
// Synopsis: Completes a context
|
|
NTSTATUS NTAPI SpCompleteAuthToken(IN ULONG_PTR ContextHandle, IN PSecBufferDesc InputBuffer)
|
|
{
|
|
UNREFERENCED_PARAMETER (ContextHandle);
|
|
UNREFERENCED_PARAMETER (InputBuffer);
|
|
SspPrint(( SSP_API, "Entering SpCompleteAuthToken\n" ));
|
|
SspPrint(( SSP_API, "Leaving SpCompleteAuthToken\n" ));
|
|
return(SEC_E_UNSUPPORTED_FUNCTION);
|
|
}
|
|
|
|
|
|
NTSTATUS NTAPI SpFormatCredentials(IN PSecBuffer Credentials, OUT PSecBuffer FormattedCredentials)
|
|
{
|
|
UNREFERENCED_PARAMETER (Credentials);
|
|
UNREFERENCED_PARAMETER (FormattedCredentials);
|
|
SspPrint(( SSP_API, "Entering SpFormatCredentials\n" ));
|
|
SspPrint(( SSP_API, "Leaving SpFormatCredentials\n" ));
|
|
return(SEC_E_UNSUPPORTED_FUNCTION);
|
|
}
|
|
|
|
|
|
NTSTATUS NTAPI SpMarshallSupplementalCreds(IN ULONG CredentialSize, IN PUCHAR Credentials, OUT PULONG MarshalledCredSize, OUT PVOID * MarshalledCreds)
|
|
{
|
|
UNREFERENCED_PARAMETER (CredentialSize);
|
|
UNREFERENCED_PARAMETER (Credentials);
|
|
UNREFERENCED_PARAMETER (MarshalledCredSize);
|
|
UNREFERENCED_PARAMETER (MarshalledCreds);
|
|
SspPrint(( SSP_API, "Entering SpMarshallSupplementalCreds\n" ));
|
|
SspPrint(( SSP_API, "Leaving SpMarshallSupplementalCreds\n" ));
|
|
return(SEC_E_UNSUPPORTED_FUNCTION);
|
|
}
|
|
|
|
|
|
// Synopsis: Maps a context to the caller's address space
|
|
// Arguments: Context - The context to map
|
|
// MappedContext - Set to TRUE on success
|
|
// ContextData - Receives a buffer in the caller's address space with the mapped context.
|
|
NTSTATUS NtLmMakePackedContext(IN PNTLM_CLIENT_CONTEXT Context,OUT PBOOLEAN MappedContext,OUT PSecBuffer ContextData,IN ULONG Flags)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PNTLM_CLIENT_CONTEXT PackedContext = NULL;
|
|
ULONG ContextSize, ContextNameSize = 0;
|
|
|
|
RtlEnterCriticalSection(&NtLmUserContextLock);
|
|
|
|
if (Context->ContextNames)
|
|
{
|
|
ContextNameSize = wcslen(Context->ContextNames);
|
|
}
|
|
|
|
ContextSize = sizeof(NTLM_CLIENT_CONTEXT) + ContextNameSize * sizeof(WCHAR);
|
|
PackedContext = (PNTLM_CLIENT_CONTEXT) NtLmAllocate(ContextSize);
|
|
if (PackedContext == NULL)
|
|
{
|
|
RtlLeaveCriticalSection(&NtLmUserContextLock);
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Copy all fields of the old context
|
|
RtlCopyMemory(PackedContext,Context,sizeof(NTLM_CLIENT_CONTEXT));
|
|
if (ContextNameSize > 0)
|
|
{
|
|
PackedContext->ContextNames = (LPWSTR) sizeof(NTLM_CLIENT_CONTEXT);
|
|
RtlCopyMemory(PackedContext+1,Context->ContextNames,ContextNameSize * sizeof(WCHAR));
|
|
}
|
|
else
|
|
{
|
|
PackedContext->ContextNames = NULL;
|
|
}
|
|
|
|
// Replace some fields
|
|
RtlLeaveCriticalSection(&NtLmUserContextLock);
|
|
|
|
// Token will be returned by the caller of this routine
|
|
PackedContext->ClientTokenHandle = NULL;
|
|
|
|
// Save the fact that it's exported
|
|
PackedContext->NegotiateFlags |= NTLMSSP_NEGOTIATE_EXPORTED_CONTEXT;
|
|
ContextData->pvBuffer = PackedContext;
|
|
ContextData->cbBuffer = ContextSize;
|
|
|
|
*MappedContext = TRUE;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
Cleanup:
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (PackedContext != NULL)
|
|
{
|
|
NtLmFree(PackedContext);
|
|
}
|
|
}
|
|
|
|
return(Status);
|
|
}
|
|
|
|
|
|
|
|
// Function: SpExportSecurityContext
|
|
|
|
// Synopsis: Exports a security context to another process
|
|
|
|
// Effects: Allocates memory for output
|
|
|
|
// Arguments: ContextHandle - handle to context to export
|
|
// Flags - Flags concerning duplication. Allowable flags:
|
|
// SECPKG_CONTEXT_EXPORT_DELETE_OLD - causes old context to be deleted.
|
|
// PackedContext - Receives serialized context to be freed with FreeContextBuffer
|
|
// TokenHandle - Optionally receives handle to context's token.
|
|
NTSTATUS SpExportSecurityContext(IN ULONG_PTR ContextHandle,IN ULONG Flags,OUT PSecBuffer PackedContext,OUT PHANDLE TokenHandle)
|
|
{
|
|
PNTLM_CLIENT_CONTEXT Context = NULL, pvContext = NULL;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
NTSTATUS SubStatus = STATUS_SUCCESS;
|
|
ULONG ContextSize = 0;
|
|
BOOLEAN MappedContext = FALSE;
|
|
|
|
SspPrint(( SSP_API,"Entering SpExportSecurityContext for context 0x%x\n", ContextHandle ));
|
|
|
|
if (ARGUMENT_PRESENT(TokenHandle))
|
|
{
|
|
*TokenHandle = NULL;
|
|
}
|
|
|
|
PackedContext->pvBuffer = NULL;
|
|
PackedContext->cbBuffer = 0;
|
|
PackedContext->BufferType = 0;
|
|
Context = ReferenceUserContext(
|
|
ContextHandle,
|
|
FALSE // don't unlink
|
|
);
|
|
if (Context == NULL)
|
|
{
|
|
SspPrint((SSP_CRITICAL, "SpExportSecurityContext: Invalid handle supplied (0x%x)\n", ContextHandle));
|
|
Status = STATUS_INVALID_HANDLE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
Status = NtLmMakePackedContext(Context,&MappedContext,PackedContext,Flags);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
ASSERT(MappedContext);
|
|
|
|
// Now either duplicate the token or copy it.
|
|
if (ARGUMENT_PRESENT(TokenHandle))
|
|
{
|
|
RtlEnterCriticalSection(&NtLmUserContextLock);
|
|
if ((Flags & SECPKG_CONTEXT_EXPORT_DELETE_OLD) != 0)
|
|
{
|
|
*TokenHandle = Context->ClientTokenHandle;
|
|
Context->ClientTokenHandle = NULL;
|
|
}
|
|
else
|
|
{
|
|
Status = NtDuplicateObject(
|
|
NtCurrentProcess(),
|
|
Context->ClientTokenHandle,
|
|
NULL,
|
|
TokenHandle,
|
|
0, // no new access
|
|
0, // no handle attributes
|
|
DUPLICATE_SAME_ACCESS
|
|
);
|
|
}
|
|
RtlLeaveCriticalSection(&NtLmUserContextLock);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
pvContext = (PNTLM_CLIENT_CONTEXT) PackedContext->pvBuffer;
|
|
|
|
// Semantics of this flag: Export from here, but reset the Nonce.
|
|
// We zero out the session key, since all we need is the rc4 key.
|
|
|
|
if ((Flags & SECPKG_CONTEXT_EXPORT_RESET_NEW) != 0)
|
|
{
|
|
/// pvContext->Nonce = (ULONG) -1;
|
|
pvContext->SendNonce = (ULONG) -1;
|
|
pvContext->RecvNonce = (ULONG) -1;
|
|
}
|
|
|
|
RtlZeroMemory(&pvContext->SessionKey, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
Cleanup:
|
|
if (Context != NULL)
|
|
{
|
|
SubStatus = DereferenceUserContext(Context);
|
|
// Don't destroy real status
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = SubStatus;
|
|
}
|
|
}
|
|
|
|
SspPrint(( SSP_API,"Leaving SpExportContext: 0x%lx\n", Status ));
|
|
return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
|
|
}
|
|
|
|
|
|
// Synopsis: Creates a user-mode context to support impersonation and message integrity and privacy
|
|
NTSTATUS NtLmCreateUserModeContext(IN ULONG_PTR ContextHandle,IN HANDLE Token,IN PSecBuffer MarshalledContext,OUT PNTLM_CLIENT_CONTEXT * NewContext)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PNTLM_CLIENT_CONTEXT Context = NULL;
|
|
PNTLM_CLIENT_CONTEXT PackedContext;
|
|
UINT Length = 0;
|
|
|
|
if (MarshalledContext->cbBuffer < sizeof(NTLM_CLIENT_CONTEXT))
|
|
{
|
|
SspPrint((SSP_CRITICAL,"NtLmCreateUserModeContext: Invalid buffer size for marshalled context: was 0x%x, needed 0x%x\n", MarshalledContext->cbBuffer, sizeof(NTLM_CLIENT_CONTEXT)));
|
|
return(STATUS_INVALID_PARAMETER);
|
|
}
|
|
|
|
PackedContext = (PNTLM_CLIENT_CONTEXT) MarshalledContext->pvBuffer;
|
|
|
|
Context = (PNTLM_CLIENT_CONTEXT)NtLmAllocate ( sizeof(NTLM_CLIENT_CONTEXT));
|
|
if (Context == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
|
|
*Context = *PackedContext;
|
|
|
|
// These need to be changed
|
|
|
|
Context->ClientTokenHandle = Token;
|
|
|
|
#if 0
|
|
if (Context->Nonce == (ULONG) -1)
|
|
{
|
|
// The context was exported with the reset flag
|
|
Context->Nonce = 0;
|
|
}
|
|
#endif
|
|
if (Context->SendNonce == (ULONG) -1)
|
|
{
|
|
Context->SendNonce = 0;
|
|
}
|
|
|
|
if (Context->RecvNonce == (ULONG) -1)
|
|
{
|
|
Context->RecvNonce = 0;
|
|
}
|
|
|
|
if ( Context->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2 )
|
|
{
|
|
Context->pSealRc4Sched = &Context->SealRc4Sched;
|
|
Context->pUnsealRc4Sched = &Context->UnsealRc4Sched;
|
|
Context->pSendNonce = &Context->SendNonce;
|
|
Context->pRecvNonce = &Context->RecvNonce;
|
|
} else {
|
|
Context->pSealRc4Sched = &Context->SealRc4Sched;
|
|
Context->pUnsealRc4Sched = &Context->SealRc4Sched;
|
|
Context->pSendNonce = &Context->SendNonce;
|
|
Context->pRecvNonce = &Context->SendNonce;
|
|
}
|
|
|
|
Context->References = 2;
|
|
Context->ContextNames = NULL;
|
|
|
|
// Modify the DACL on the token to grant access to the caller
|
|
if (Context->ClientTokenHandle != NULL)
|
|
{
|
|
Status = SspCreateTokenDacl(Context->ClientTokenHandle);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
Length = (UINT) (MarshalledContext->cbBuffer - sizeof(NTLM_CLIENT_CONTEXT));
|
|
if (Length > 0)
|
|
{
|
|
Context->ContextNames = (LPWSTR) NtLmAllocate(Length + sizeof(WCHAR));
|
|
if (!Context->ContextNames)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
RtlCopyMemory(Context->ContextNames, PackedContext + 1, Length);
|
|
// null termioate the string
|
|
*(Context->ContextNames + (Length/2)) = UNICODE_NULL;
|
|
}
|
|
|
|
Context->PasswordExpiry = PackedContext->PasswordExpiry;
|
|
Context->UserFlags = PackedContext->UserFlags;
|
|
|
|
RtlEnterCriticalSection(&NtLmUserContextLock);
|
|
InsertHeadList(&NtLmUserContextList, &Context->Next);
|
|
RtlLeaveCriticalSection(&NtLmUserContextLock);
|
|
|
|
*NewContext = Context;
|
|
|
|
Cleanup:
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (Context != NULL)
|
|
{
|
|
FreeUserContext(Context);
|
|
}
|
|
}
|
|
return(Status);
|
|
}
|
|
|
|
|
|
NTSTATUS SpImportSecurityContext(IN PSecBuffer PackedContext, IN HANDLE Token, OUT PULONG_PTR ContextHandle)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
NTSTATUS SubStatus = STATUS_SUCCESS;
|
|
PNTLM_CLIENT_CONTEXT Context = NULL;
|
|
|
|
SspPrint(( SSP_API,"Entering SpImportSecurityContext\n"));
|
|
|
|
Status = NtLmCreateUserModeContext(
|
|
0, // no lsa context
|
|
Token,
|
|
PackedContext,
|
|
&Context
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
SspPrint((SSP_CRITICAL,"SpImportSecurityContext: Failed to create user mode context: 0x%x\n", Status));
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Makeup a new LsaContext otherwise we can have only one ContextHandle for the original, exported all combined.
|
|
// Right this is a global counter, make this so we can never have a valid user-mode pointer.
|
|
// we never de-ref it, but let's say we end up having a valid user mode pointer and it collides with an existing context
|
|
RtlEnterCriticalSection(&NtLmUserContextLock);
|
|
Context->LsaContext = ++ExportedContext;
|
|
|
|
*ContextHandle = Context->LsaContext;
|
|
RtlLeaveCriticalSection(&NtLmUserContextLock);
|
|
|
|
Cleanup:
|
|
if (Context != NULL)
|
|
{
|
|
SubStatus = DereferenceUserContext(Context);
|
|
// Don't destroy real status
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = SubStatus;
|
|
}
|
|
}
|
|
|
|
SspPrint(( SSP_API,"Leaving SpImportSecurityContext: 0x%lx\n", Status));
|
|
return(SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR));
|
|
}
|
|
|
|
|
|
NTSTATUS SspGetTokenUser(HANDLE Token, PTOKEN_USER * pTokenUser)
|
|
/*++
|
|
RoutineDescription:
|
|
Gets the TOKEN_USER from an open token
|
|
Arguments:
|
|
Token - Handle to a token open for TOKEN_QUERY access
|
|
Return Value:
|
|
STATUS_INSUFFICIENT_RESOURCES - not enough memory to complete the function.
|
|
Errors from NtQueryInformationToken.
|
|
--*/
|
|
{
|
|
PTOKEN_USER LocalTokenUser = NULL;
|
|
NTSTATUS Status;
|
|
ULONG TokenUserSize = 0;
|
|
|
|
// Query the token user. First pass in NULL to get back the required size.
|
|
Status = NtQueryInformationToken(Token,TokenUser,NULL,0,&TokenUserSize);
|
|
if (Status != STATUS_BUFFER_TOO_SMALL)
|
|
{
|
|
ASSERT(Status != STATUS_SUCCESS);
|
|
SspPrint(( SSP_CRITICAL, "SspGetTokenUser, NtQueryInformationToken returns 0x%lx\n", Status ));
|
|
return(Status);
|
|
}
|
|
|
|
// Now allocate the required ammount of memory and try again.
|
|
LocalTokenUser = (PTOKEN_USER) NtLmAllocate(TokenUserSize);
|
|
if (LocalTokenUser == NULL)
|
|
{
|
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|
}
|
|
Status = NtQueryInformationToken(Token,TokenUser,LocalTokenUser,TokenUserSize,&TokenUserSize);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
*pTokenUser = LocalTokenUser;
|
|
}
|
|
else
|
|
{
|
|
NtLmFree(LocalTokenUser);
|
|
SspPrint(( SSP_CRITICAL, "SspGetTokenUser, NtQueryInformationToken returns 0x%lx\n", Status ));
|
|
}
|
|
|
|
return(Status);
|
|
}
|
|
|
|
|
|
NTSTATUS SspCreateTokenDacl(HANDLE Token)
|
|
/*++
|
|
RoutineDescription:
|
|
Creates a new DACL for the token granting the server and client all access to the token.
|
|
Arguments:
|
|
Token - Handle to an impersonation token open for TOKEN_QUERY and WRITE_DAC
|
|
Return Value:
|
|
STATUS_INSUFFICIENT_RESOURCES - insufficient memory to complete the function.
|
|
Errors from NtSetSecurityObject
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
PTOKEN_USER ProcessTokenUser = NULL;
|
|
PTOKEN_USER ThreadTokenUser = NULL;
|
|
PTOKEN_USER ImpersonationTokenUser = NULL;
|
|
HANDLE ProcessToken = NULL;
|
|
HANDLE ImpersonationToken = NULL;
|
|
BOOL fInsertImpersonatingUser = FALSE;
|
|
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
|
|
ULONG AclLength;
|
|
PACL NewDacl = NULL;
|
|
SECURITY_DESCRIPTOR SecurityDescriptor;
|
|
|
|
BOOL fReleaseContextLock = FALSE;
|
|
|
|
// Build the two well known sids we need.
|
|
if ( NtLmGlobalLocalSystemSid == NULL || NtLmGlobalAliasAdminsSid == NULL ) {
|
|
RtlEnterCriticalSection(&NtLmUserContextLock);
|
|
fReleaseContextLock = TRUE;
|
|
if (NtLmGlobalLocalSystemSid == NULL)
|
|
{
|
|
PSID pLocalSidSystem;
|
|
|
|
Status = RtlAllocateAndInitializeSid(&NtAuthority,1,SECURITY_LOCAL_SYSTEM_RID,0,0,0,0,0,0,0,&pLocalSidSystem);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
SspPrint(( SSP_CRITICAL, "SspCreateTokenDacl, RtlAllocateAndInitializeSid returns 0x%lx\n", Status ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
NtLmGlobalLocalSystemSid = pLocalSidSystem;
|
|
}
|
|
|
|
if (NtLmGlobalAliasAdminsSid == NULL)
|
|
{
|
|
PSID pLocalSidAdmins;
|
|
|
|
Status = RtlAllocateAndInitializeSid(&NtAuthority,2,SECURITY_BUILTIN_DOMAIN_RID,DOMAIN_ALIAS_RID_ADMINS,0,0,0,0,0,0,&pLocalSidAdmins);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
SspPrint(( SSP_CRITICAL, "SspCreateTokenDacl, RtlAllocateAndInitializeSid returns 0x%lx\n", Status ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
NtLmGlobalAliasAdminsSid = pLocalSidAdmins;
|
|
}
|
|
|
|
RtlLeaveCriticalSection(&NtLmUserContextLock);
|
|
fReleaseContextLock = FALSE;
|
|
}
|
|
|
|
// it's possible that the current thread is impersonating a user.
|
|
// if that's the case, get it's token user, and revert to insure we can open the process token.
|
|
Status = NtOpenThreadToken(NtCurrentThread(),TOKEN_QUERY | TOKEN_IMPERSONATE,TRUE,&ImpersonationToken);
|
|
if( NT_SUCCESS(Status) )
|
|
{
|
|
// stop impersonating.
|
|
RevertToSelf();
|
|
|
|
// get the token user for the impersonating user.
|
|
Status = SspGetTokenUser(ImpersonationToken,&ImpersonationTokenUser);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
SspPrint(( SSP_CRITICAL, "SspCreateTokenDacl, SspGetTokenUser returns 0x%lx\n", Status ));
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
// Open the process token to find out the user sid
|
|
Status = NtOpenProcessToken(NtCurrentProcess(),TOKEN_QUERY,&ProcessToken);
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
SspPrint(( SSP_CRITICAL, "SspCreateTokenDacl, NtOpenProcessToken returns 0x%lx\n", Status ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
// get the token user for the process token.
|
|
Status = SspGetTokenUser(ProcessToken,&ProcessTokenUser);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
SspPrint(( SSP_CRITICAL, "SspCreateTokenDacl, SspGetTokenUser returns 0x%lx\n", Status ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Now get the token user for the thread.
|
|
Status = SspGetTokenUser(Token,&ThreadTokenUser);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
SspPrint(( SSP_CRITICAL, "SspCreateTokenDacl, SspGetTokenUser returns 0x%lx\n", Status ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
AclLength = 4 * sizeof( ACCESS_ALLOWED_ACE ) - 4 * sizeof( ULONG ) +
|
|
RtlLengthSid( ProcessTokenUser->User.Sid ) +
|
|
RtlLengthSid( ThreadTokenUser->User.Sid ) +
|
|
RtlLengthSid( NtLmGlobalLocalSystemSid ) +
|
|
RtlLengthSid( NtLmGlobalAliasAdminsSid ) +
|
|
sizeof( ACL );
|
|
|
|
// determine if we need to add impersonation token sid onto the token Dacl.
|
|
if( ImpersonationTokenUser && !RtlEqualSid( ImpersonationTokenUser->User.Sid, ProcessTokenUser->User.Sid ) && !RtlEqualSid( ImpersonationTokenUser->User.Sid, ThreadTokenUser->User.Sid ))
|
|
{
|
|
AclLength += (sizeof(ACCESS_ALLOWED_ACE) - sizeof( ULONG )) + RtlLengthSid( ImpersonationTokenUser->User.Sid );
|
|
fInsertImpersonatingUser = TRUE;
|
|
}
|
|
|
|
NewDacl = (PACL)NtLmAllocate(AclLength );
|
|
if (NewDacl == NULL) {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
SspPrint(( SSP_CRITICAL, "SspCreateTokenDacl, NtLmallocate returns 0x%lx\n", NewDacl));
|
|
goto Cleanup;
|
|
}
|
|
|
|
Status = RtlCreateAcl( NewDacl, AclLength, ACL_REVISION2 );ASSERT(NT_SUCCESS( Status ));
|
|
Status = RtlAddAccessAllowedAce (NewDacl,ACL_REVISION2,TOKEN_ALL_ACCESS,ProcessTokenUser->User.Sid);ASSERT( NT_SUCCESS( Status ));
|
|
Status = RtlAddAccessAllowedAce (NewDacl,ACL_REVISION2,TOKEN_ALL_ACCESS,ThreadTokenUser->User.Sid);ASSERT( NT_SUCCESS( Status ));
|
|
|
|
if( fInsertImpersonatingUser )
|
|
{
|
|
Status = RtlAddAccessAllowedAce (NewDacl,ACL_REVISION2,TOKEN_ALL_ACCESS,ImpersonationTokenUser->User.Sid);
|
|
ASSERT( NT_SUCCESS( Status ));
|
|
}
|
|
|
|
Status = RtlAddAccessAllowedAce (NewDacl,ACL_REVISION2,TOKEN_ALL_ACCESS,NtLmGlobalAliasAdminsSid);ASSERT( NT_SUCCESS( Status ));
|
|
Status = RtlAddAccessAllowedAce (NewDacl,ACL_REVISION2,TOKEN_ALL_ACCESS,NtLmGlobalLocalSystemSid);ASSERT( NT_SUCCESS( Status ));
|
|
Status = RtlCreateSecurityDescriptor (&SecurityDescriptor,SECURITY_DESCRIPTOR_REVISION);ASSERT( NT_SUCCESS( Status ));
|
|
Status = RtlSetDaclSecurityDescriptor(&SecurityDescriptor,TRUE,NewDacl,FALSE);ASSERT( NT_SUCCESS( Status ));
|
|
Status = NtSetSecurityObject(Token,DACL_SECURITY_INFORMATION,&SecurityDescriptor);ASSERT( NT_SUCCESS( Status ));
|
|
|
|
Cleanup:
|
|
if( fReleaseContextLock )
|
|
RtlLeaveCriticalSection(&NtLmUserContextLock);
|
|
|
|
if (ImpersonationToken != NULL)
|
|
{
|
|
// put the thread token back if we were impersonating.
|
|
SetThreadToken( NULL, ImpersonationToken );
|
|
NtClose(ImpersonationToken);
|
|
}
|
|
|
|
if (ThreadTokenUser != NULL) {
|
|
NtLmFree( ThreadTokenUser );
|
|
}
|
|
|
|
if (ProcessTokenUser != NULL) {
|
|
NtLmFree( ProcessTokenUser );
|
|
}
|
|
|
|
if (ImpersonationTokenUser != NULL) {
|
|
|
|
NtLmFree( ImpersonationTokenUser );
|
|
}
|
|
|
|
if (NewDacl != NULL) {
|
|
NtLmFree( NewDacl );
|
|
}
|
|
|
|
if (ProcessToken != NULL)
|
|
{
|
|
NtClose(ProcessToken);
|
|
}
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
NTSTATUS SspMapContext(
|
|
IN PULONG_PTR phContext,
|
|
IN PUCHAR pSessionKey,
|
|
IN ULONG NegotiateFlags,
|
|
IN HANDLE TokenHandle,
|
|
IN PTimeStamp PasswordExpiry OPTIONAL,
|
|
IN ULONG UserFlags,
|
|
OUT PSecBuffer ContextData
|
|
)
|
|
/*++
|
|
RoutineDescription:
|
|
Create a local context for a real context
|
|
Don't link it to out list of local contexts.
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PNTLM_CLIENT_CONTEXT pContext = NULL;
|
|
ULONG cbContextData;
|
|
WCHAR ContextNames[(UNLEN + DNS_MAX_NAME_LENGTH + 2 + 1) *sizeof (WCHAR)];
|
|
PLUID pLogonId = NULL;
|
|
TOKEN_STATISTICS TokenStats;
|
|
PSSP_CONTEXT pTempContext = (PSSP_CONTEXT)*phContext;
|
|
PACTIVE_LOGON *ActiveLogon = NULL, Logon = NULL;
|
|
LPWSTR UserName = NULL;
|
|
UINT Length = 0;
|
|
BOOLEAN ActiveLogonsAreLocked = FALSE;
|
|
|
|
if (pTempContext == NULL)
|
|
{
|
|
Status = STATUS_INVALID_HANDLE;
|
|
SspPrint(( SSP_CRITICAL, "SspMapContext, pTempContext is 0x%lx\n", pTempContext));
|
|
goto Cleanup;
|
|
}
|
|
|
|
if( pTempContext->Credential != NULL ) {
|
|
pLogonId = &(pTempContext->Credential->LogonId);
|
|
} else {
|
|
// if it was a local call where the default creds were used, lookup the username and domain name based on the AuthenticationId of the access token.
|
|
// The local call gets a duplicated access token associated with the original client, so information on this logon should be found in the active logon list.
|
|
if( NegotiateFlags & NTLMSSP_NEGOTIATE_LOCAL_CALL && pTempContext->UserName.Length == 0 && pTempContext->UserName.Buffer == NULL &&
|
|
pTempContext->DomainName.Length == 0 && pTempContext->DomainName.Buffer == NULL && TokenHandle ) {
|
|
DWORD TokenInfoLength = sizeof( TokenStats );
|
|
|
|
if( GetTokenInformation(TokenHandle,TokenStatistics,&TokenStats,TokenInfoLength,&TokenInfoLength)) {
|
|
pLogonId = &(TokenStats.AuthenticationId);
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pLogonId )
|
|
{
|
|
NlpLockActiveLogons();
|
|
ActiveLogonsAreLocked = TRUE;
|
|
if (!NlpFindActiveLogon (pLogonId,&ActiveLogon))
|
|
{
|
|
// Status = STATUS_NO_SUCH_LOGON_SESSION;
|
|
SspPrint(( SSP_API_MORE, "SspMapContext, NlpFindActiveLogon returns FALSE\n"));
|
|
// SspPrint(( SSP_CRITICAL, "SspMapContext, NlpFindActiveLogon returns FALSE\n"));
|
|
// goto Cleanup;
|
|
}
|
|
}
|
|
|
|
if (ActiveLogon != NULL)
|
|
{
|
|
Logon = *ActiveLogon;
|
|
}
|
|
|
|
ContextNames[0] = L'\0';
|
|
|
|
if (Logon != NULL)
|
|
{
|
|
if ( (Logon->UserName.Length > 0) && (Logon->LogonDomainName.Length > 0))
|
|
{
|
|
RtlCopyMemory (ContextNames, Logon->LogonDomainName.Buffer, Logon->LogonDomainName.Length);
|
|
Length = (Logon->LogonDomainName.Length)/sizeof(WCHAR);
|
|
ContextNames[Length] = L'\\';
|
|
Length += 1;
|
|
UserName = &ContextNames[Length];
|
|
RtlCopyMemory (UserName,Logon->UserName.Buffer,Logon->UserName.Length);
|
|
Length += (Logon->UserName.Length)/sizeof(WCHAR);
|
|
ContextNames[Length] = L'\0';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We don't store network logons, but in the case of server side mapping, the client has sent us the Context Names, so use that.
|
|
// Also, handle the case where we don't have domainnames, just usernames Must handle the valid case where both domainname & username are NULL (rdr)
|
|
if ((pTempContext->DomainName.Length > 0) && (pTempContext->UserName.Length > 0))
|
|
{
|
|
RtlCopyMemory (ContextNames, pTempContext->DomainName.Buffer, pTempContext->DomainName.Length);
|
|
Length = (pTempContext->DomainName.Length)/sizeof(WCHAR);
|
|
ContextNames[Length] = L'\\';
|
|
Length += 1;
|
|
UserName = &ContextNames[Length];
|
|
RtlCopyMemory (UserName, pTempContext->UserName.Buffer, pTempContext->UserName.Length);
|
|
Length += (pTempContext->UserName.Length)/sizeof(WCHAR);
|
|
ContextNames[Length] = L'\0';
|
|
}
|
|
else if ((pTempContext->DomainName.Length == 0) && (pTempContext->UserName.Length >0))
|
|
{
|
|
RtlCopyMemory (ContextNames + Length, pTempContext->UserName.Buffer, pTempContext->UserName.Length);
|
|
Length = (pTempContext->UserName.Length)/sizeof(WCHAR);
|
|
ContextNames[Length] = L'\0';
|
|
}
|
|
}
|
|
// when domain is present, don't supply domain\UPN, supply domain\user
|
|
if( UserName ) {
|
|
DWORD cchUserName = wcslen(UserName);
|
|
DWORD i;
|
|
|
|
for( i = 0 ; i < cchUserName ; i++ ) {
|
|
if( UserName[ i ] == L'@' ) {
|
|
UserName[ i ] = L'\0';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
cbContextData = sizeof(NTLM_CLIENT_CONTEXT) + (wcslen(ContextNames) * sizeof(WCHAR)) ;
|
|
|
|
// the first sizeof (NTLM_CLIENT_CONTEXT) bytes can be casted to pContext anyway.
|
|
pContext = (PNTLM_CLIENT_CONTEXT)NtLmAllocate( cbContextData );
|
|
if (!pContext)
|
|
{
|
|
SspPrint(( SSP_CRITICAL, "SspMapContext, NtLmAllocate returns NULL\n"));
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
|
|
pContext->NegotiateFlags = NegotiateFlags;
|
|
|
|
/// if (NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY) {
|
|
|
|
/// RtlCopyMemory( pContext->SessionKey,
|
|
/// pSessionKey,
|
|
/// MSV1_0_LANMAN_SESSION_KEY_LENGTH);
|
|
/// } else {
|
|
|
|
RtlCopyMemory( pContext->SessionKey, pSessionKey, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
/// }
|
|
|
|
// Save away the LsaContextHandle
|
|
pContext->LsaContext = *phContext;
|
|
|
|
// dup token if it exists
|
|
if (TokenHandle != NULL)
|
|
{
|
|
Status = LsaFunctions->DuplicateHandle(TokenHandle, &pContext->ClientTokenHandle);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (pContext)
|
|
{
|
|
NtLmFree(pContext);
|
|
}
|
|
SspPrint(( SSP_CRITICAL, "SspMapContext, DuplicateHandle returns 0x%lx\n", Status));
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
if (cbContextData > sizeof(NTLM_CLIENT_CONTEXT) )
|
|
{
|
|
pContext->ContextNames = (WCHAR *)(pContext + 1);
|
|
RtlCopyMemory(pContext->ContextNames, ContextNames, cbContextData - sizeof(NTLM_CLIENT_CONTEXT));
|
|
}
|
|
else
|
|
{
|
|
pContext->ContextNames = NULL;
|
|
}
|
|
|
|
/// pContext->Nonce = 0;
|
|
pContext->SendNonce = 0;
|
|
pContext->RecvNonce = 0;
|
|
|
|
/// SspRc4Key(NegotiateFlags, &pContext->SealRc4Sched, pContext->SealSessionKey);
|
|
/// SspRc4Key(NegotiateFlags, &pContext->UnsealRc4Sched, pContext->UnsealSessionKey);
|
|
|
|
ContextData->pvBuffer = pContext;
|
|
ContextData->cbBuffer = cbContextData;
|
|
|
|
if(ARGUMENT_PRESENT(PasswordExpiry)) {
|
|
pContext->PasswordExpiry = *PasswordExpiry;
|
|
} else {
|
|
pContext->PasswordExpiry.QuadPart = 0;
|
|
}
|
|
|
|
pContext->UserFlags = UserFlags;
|
|
|
|
Cleanup:
|
|
if (ActiveLogonsAreLocked)
|
|
{
|
|
NlpUnlockActiveLogons();
|
|
}
|
|
return(Status);
|
|
} |