Skip to content

Writing a NegoEx security package: development notes

How to test a NegoEx security package

Run mstsc (the Remote Desktop client) with a breakpoint in the security package.

Or write an SSPI test program that calls SspiExcludePackage to exclude NTLMSP_NAME, MICROSOFT_KERBEROS_NAME, and NEGOSSP_NAME.

Documentation errors
SpAcquireCredentialsHandle
Documentation error: The documentation says the credentials come as a SEC_WINNT_AUTH_IDENTITY. They don't: the structure is SECPKG_CREDENTIAL.

This code reads the user name and the password (or the certificate SHA1 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)) {
    // 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

LSA passes the context handle returned by SpQueryMetaData back to SpExchangeMetaDataFn and to SpInitLsaContext / SpAcceptLsaContext. It is NULL on the first call, then keeps the value set by the previous call. So after a metadata exchange, SpInitLsaContext can get a non-null context.


NegoKey

Before the negotiation (SpInitLsaContext or SpAcceptLsaContext), LSA calls SpQueryContextAttributes(SECPKG_ATTR_NEGO_KEYS) with the handle returned by SpQueryMetaData and SpExchangeMetaData.

  • The buffer is a PSecPkgContext_NegoKeys. Zero it on the first call.
  • Allocate the memory from the LSA private heap.
  • The key type is a constant from RFC 3961 (also in NTSECAPI.h).
  • aes128-cts-hmac-sha1-96 (17) with a 128-bit key works.
Tips
Unprotecting the password or PIN

The credentials can be protected with CredProtect. Impersonate the client before calling CredUnProtect, then call RevertToSelf().

Implement ASC_REQ_ALLOCATE_MEMORY

NegoEx needs the package to allocate its output buffers. Set ASC_REQ_ALLOCATE_MEMORY in the output ContextAttributes, allocate with AllocateLsaHeap, and copy the token. Set the buffer size and type.

Terminal server

Impersonate the client to reach its redirected smart card reader.

NegoEx SSP and SMB (CIFS)

A NegoEx SSP can fail over SMB with STATUS_NO_SUCH_PACKAGE (visible in Wireshark). For the attributes SMB2 queries, see what SMB2 needs from a security package.