Development notes about NegoEx security packages

How to test a NegoEx security package

Call mstsc (the Terminal Services client) and set a breakpoint in your security package to test it.

Alternatively, write an SSPI test program and use SspiExcludePackage to exclude NTLMSP_NAME, MICROSOFT_KERBEROS_NAME, and NEGOSSP_NAME.

Errors in documentation
SpAcquireCredentialsHandle
Documentation error: The docs claim that credentials are passed via SEC_WINNT_AUTH_IDENTITY. This is incorrect — the actual structure is SECPKG_CREDENTIAL.

Here is how to correctly read the username, password (and SHA1 of the certificate + PIN). PSEC_WINNT_AUTH_PACKED_CREDENTIALS is defined in sspi.h.

#define _SEC_WINNT_AUTH_TYPES
#include <sspi.h>

if (!MyLsaDispatchTable->GetCallInfo(&CallInfo)) {
    // handle error
}
if (CallInfo.Attributes & SECPKG_CALL_NEGO_EXTENDER && AuthorizationData != NULL) {
    PSECPKG_CREDENTIAL pGivenCredential = (PSECPKG_CREDENTIAL) AuthorizationData;
    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)) {
        // handle wrong credential type
    }
    // Read username
    szCredential = EIDAlloc((pSuppliedCredentials->UserName.ShortArrayCount + 1) * sizeof(WCHAR));
    memcpy(szCredential,
           (PBYTE)pSuppliedCredentials + pSuppliedCredentials->UserName.ShortArrayOffset,
           pSuppliedCredentials->UserName.ShortArrayCount * sizeof(WCHAR));
    ((WCHAR*)szCredential)[pSuppliedCredentials->UserName.ShortArrayCount] = 0;
    // Read password
    PBYTE pbData = ((PBYTE)pPackedCredentials + pAuthData->CredData.ByteArrayOffset);
    dwPasswordWSizeInBytes = pAuthData->CredData.ByteArrayLength + sizeof(WCHAR);
    szPasswordW = (PWSTR)EIDAlloc(dwPasswordWSizeInBytes);
    memcpy(szPasswordW, pbData, pAuthData->CredData.ByteArrayLength);
    szPasswordW[(pAuthData->CredData.ByteArrayLength / 2)] = 0;
}

SpQueryMetaData

The context handle returned by SpQueryMetaData is re-injected into SpExchangeMetaDataFn and SpInitLsaContext / SpAcceptLsaContext. It is NULL on the first call, and non-null after being set in the previous call. In practice, when metadata exchange occurs, the context injected into SpInitLsaContext can be non-null.


NegoKey

Before negotiation (SpInitLsaContext or SpAcceptLsaContext), a call to SpQuerySecurityContextAttribut(SECPKG_ATTR_NEGO_KEYS) is made. The handle is the one returned by SpQueryMetaData and SpExchangeMetaData.

  • The structure is PSecPkgContext_NegoKeys — must be zeroed on the first call
  • Memory must be allocated using the LSA private heap
  • Key type is a constant from RFC 3961 and NTSECAPI.h
  • aes128-cts-hmac-sha1-96 (17) with a 128-bit key size works
Tips & tricks
Unprotecting password / PIN

Credentials may be protected by CredProtect. Impersonate the client before calling CredUnProtect, then call RevertToSelf().

Implement ASC_REQ_ALLOCATE_MEMORY

Auto-allocation of buffers is required for NegoEx. Set ASC_REQ_ALLOCATE_MEMORY in ContextAttributes (output), then allocate via AllocateLsaHeap and copy the buffer. Set size and type correctly.

Terminal server

When reading smart cards, impersonate the client to access the remote reader.

NegoEx SSP and SMB (CIFS)

A NegoEx SSP used with SMB may fail with STATUS_NO_SUCH_PACKAGE (visible in Wireshark). This is a known limitation.