Development notes about NegoEx security packages

How to test a NegoEx security package ?

You can call mstsc (the terminal server client program) and set a breakpoint in the security package to test.
As an alternative you can write a SSPI test program and use SspiExcludePackage to exclude NTLMSP_NAME, MICROSOFT_KERBEROS_NAME and NEGOSSP_NAME

Errors in documentation

SpAcquireCredentialsHandle

From the documentation “In the call to AcquireCredentialsHandle, the credentials are passed from the application to the SSP through the SEC_WINNT_AUTH_IDENTITY structure (by using the pAuthData parameter.)” “NegoEx calls the AcquireCredentialsHandle() function of each NegoEx compatible SSP, passing in the supplied or default credentials.”

This is not true. The structure is a SECPKG_CREDENTIAL. Here is a code that shows how to read the username password (also SHA1 of the certificate and the PIN).

PSEC_WINNT_AUTH_PACKED_CREDENTIALS is defined in sspi.h.

#define _SEC_WINNT_AUTH_TYPES
#include <sspi.h>
if (!MyLsaDispatchTable->GetCallInfo(&CallInfo))
{ 
	Trace(TRACE_LEVEL_WARNING,L"GetCallInfo not ok 0x%08x", Status); 
	Status = STATUS_INTERNAL_ERROR; 
	__leave;
}
if (CallInfo.Attributes & SECPKG_CALL_NEGO_EXTENDER && AuthorizationData != NULL) 
{
	Trace(TRACE_LEVEL_INFO,L"got a SECPKG_CALL_NEGO_EXTENDER call"); 
	PSECPKG_CREDENTIAL pGivenCredential = (PSECPKG_CREDENTIAL) AuthorizationData;
// MarshaledSuppliedCreds.ByteArrayLength is set to 0 if there is no credential
	PSECPKG_SUPPLIED_CREDENTIAL pSuppliedCredentials = (PSECPKG_SUPPLIED_CREDENTIAL) ((PBYTE)AuthorizationData + pGivenCredential->MarshaledSuppliedCreds.ByteArrayOffset);
	PSEC_WINNT_AUTH_PACKED_CREDENTIALS pPackedCredentials = (PSEC_WINNT_AUTH_PACKED_CREDENTIALS) ((PBYTE)pSuppliedCredentials + pSuppliedCredentials->PackedCredentials.ByteArrayOffset);
	MyLsaDispatchTable->LsaUnprotectMemory(pPackedCredentials, pSuppliedCredentials->PackedCredentials.ByteArrayLength);
	PSEC_WINNT_AUTH_DATA pAuthData = &(pPackedCredentials->AuthData);
	
	if (!IsEqualGUID(pAuthData->CredType, SEC_WINNT_AUTH_DATA_TYPE_PASSWORD))
	{
		GUID* guid = &(pAuthData->CredType);
		Trace(TRACE_LEVEL_WARNING,L"CredType not SEC_WINNT_AUTH_DATA_TYPE_PASSWORD = {%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
		guid->Data1, guid->Data2, guid->Data3,
		guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
		guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
		Status = STATUS_NOT_FOUND; 
		__leave;
	}
	Trace(TRACE_LEVEL_INFO,L"CredType is SEC_WINNT_AUTH_DATA_TYPE_PASSWORD");
	szCredential = EIDAlloc((pSuppliedCredentials->UserName.ShortArrayCount + 1) * sizeof(WCHAR));
	if (!szCredential)
	{
		Status = STATUS_INSUFFICIENT_RESOURCES; 
		Trace(TRACE_LEVEL_WARNING,L"EIDAlloc szCredential"); 
		__leave;
	}
	memcpy(szCredential, (PBYTE) pSuppliedCredentials + pSuppliedCredentials->UserName.ShortArrayOffset, pSuppliedCredentials->UserName.ShortArrayCount * sizeof(WCHAR));
	((WCHAR*)szCredential)[pSuppliedCredentials->UserName.ShortArrayCount] = 0;
	Trace(TRACE_LEVEL_INFO,L"szCredential %s", szCredential);

	PBYTE pbData = ((PBYTE)pPackedCredentials + pAuthData->CredData.ByteArrayOffset);
	dwPasswordWSizeInBytes = pAuthData->CredData.ByteArrayLength + sizeof(WCHAR);
	szPasswordW = (PWSTR) EIDAlloc(dwPasswordWSizeInBytes);
	if (!szPasswordW)
	{
		Status = STATUS_INSUFFICIENT_RESOURCES; 
		Trace(TRACE_LEVEL_WARNING,L"EIDAlloc szPasswordW"); 
		__leave;
	}
	memcpy(szPasswordW, pbData, pAuthData->CredData.ByteArrayLength);
	szPasswordW[(pAuthData->CredData.ByteArrayLength/2)] = 0;
#ifdef _DEBUG
	Trace(TRACE_LEVEL_INFO,L"szPasswordW %s", szPasswordW);
#endif 
}

SpQueryMetaData

The context handle returned by SpQueryMetaData (ContextHandle) is reinjected into SpExchangeMetaDataFn and SpInitLsaContext or SpAcceptLsaContext. This parameter is null at the first call, then the parameter is not null if set in the previous call. In pratice, when a metadata exchange happened, the context injected in SpInitLsaContext can be not null.

NegoKey

Before the negotiation (eg: SpInitLsaContext or SpAcceptLsaContext) a call to SpQuerySecurityContextAttribut(SECPKG_ATTR_NEGO_KEYS) is done. The handle is the handle returned by SpQueryMetaData and SpExchangeMetaData (NULL if not set). The structure is a PSecPkgContext_NegoKeys. It must be zeroized at the first call. At the second call (or last call), the structure can be set.

The memory must be allocated using the Lsa private heap. The key type is a constant defined in RFC3961. and also in NTSECAPI.h.

aes128-cts-hmac-sha1-96 (17) with a key size of 128 bits works.

tips & tricks

unprotecting password / pin

Sometimes, credentials can be protected by CredProtect.

Impersonate the client before calling CredUnProtect.

And don’t forget to call RevertToSelf() !

Implements ASC_REQ_ALLOCATE_MEMORY

Autoallocation of buffer is a requirement for NegoEx.

Set ASC_REQ_ALLOCATE_MEMORY in ContextAttributes (output) then when returning the buffer, check that there is a least one buffer (which pointer and size should be null), then allocate the memory using AllocateLsaHeap and copy the buffer in it. The size and type have to be set.

Terminal server

When reading smart card, don’t forget to impersonate the client to access the remote reader

kernel security package

When trying to use with SMB (CIFS) a NegoEX SSP can failed. The error code found in wireshark in the SMB packet is STATUS_NO_SUCH_PACKAGE.