Windows2000/private/security/msv_sspi/userapi.cxx
2020-09-30 17:12:32 +02:00

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);
}