Figure 1
Figure 1 SSPI Buffer Types
#define SECBUFFER_EMPTY 0
#define SECBUFFER_DATA 1
#define SECBUFFER_TOKEN 2
#define SECBUFFER_PKG_PARAMS 3
#define SECBUFFER_MISSING 4
#define SECBUFFER_EXTRA 5
#define SECBUFFER_STREAM_TRAILER 6
#define SECBUFFER_STREAM_HEADER 7
#define SECBUFFER_NEGOTIATION_INFO 8
#define SECBUFFER_PADDING 9
#define SECBUFFER_STREAM 10
#define SECBUFFER_MECHLIST 11
#define SECBUFFER_MECHLIST_SIGNATURE 12
#define SECBUFFER_READONLY 0x80000000
Figure 2 Encrypting GSSAPI Messages
Buffer |
Size |
SECBUFFER_TOKEN |
sizes.cbSecurityTrailer |
SECBUFFER_DATA |
Size of plaintext |
SECBUFFER_PADDING |
sizes.cbBlockSize |
Figure 3 Decrypting GSSAPI Messages
Buffer |
Description |
SECBUFFER_STREAM |
Encrypted data goes here |
SECBUFFER_DATA |
Decrypted data will go here |
Figure 4 Raw SSPI Encryption and Decryption Tokens
Encryption Buffer |
Size |
SECBUFFER_DATA |
Size of plaintext |
SECBUFFER_TOKEN |
sizes.cbSecurityTrailer |
Decryption Buffer |
Size |
SECBUFFER_DATA |
Size of ciphertext (same as plaintext) |
SECBUFFER_TOKEN |
Size of resulting security trailer |
Figure 5 Encrypting and Decrypting using SSPI
///////////////////////////////////////////////////////////
// excerpt from MODEL.H (SSPI workbench)
// encrypting data
bool Model::encryptMessage(const void* pvMessage,
DWORD cbMessage,
DWORD nSeqNo,
void** ppvSealedMessage,
DWORD* pcbSealedMessage) {
_ASSERT(msAuthnComplete == m_state);
// see how much extra space the SSP needs
SecPkgContext_Sizes sizes;
SECURITY_STATUS err =
m_pSSPI->QueryContextAttributes(&m_hctx,
SECPKG_ATTR_SIZES,
&sizes);
if (err) _err(L"QueryContextAttributes", false, err);
// allocate a buffer and copy plaintext into it
*ppvSealedMessage = malloc(cbMessage +
sizes.cbSecurityTrailer);
CopyMemory(*ppvSealedMessage, pvMessage, cbMessage);
// describe our buffer for SSPI
SecBuffer rgsb[] = {
{cbMessage, SECBUFFER_DATA, *ppvSealedMessage},
{sizes.cbSecurityTrailer, SECBUFFER_TOKEN,
static_cast<BYTE*>(*ppvSealedMessage) +
cbMessage},
};
SecBufferDesc sbd = {SECBUFFER_VERSION,
sizeof rgsb / sizeof *rgsb, rgsb};
// encrypt in place
err = m_pSSPI->EncryptMessage(&m_hctx, 0, &sbd, nSeqNo);
bool bOk = true;
if (err) {
_err(L"EncryptMessage", false, err);
bOk = false;
free(*ppvSealedMessage);
*ppvSealedMessage = 0;
}
*pcbSealedMessage = cbMessage + rgsb[1].cbBuffer;
return bOk;
}
// decrypting data
bool Model::decryptMessage(const void* pvSealedMessage,
DWORD cbSealedMessage,
DWORD cbMessage,
DWORD nSeqNo,
void** ppvMessage) {
_ASSERT(msAuthnComplete == m_state);
// allocate a buffer and copy ciphertext into it
*ppvMessage = malloc(cbSealedMessage);
CopyMemory(*ppvMessage,
pvSealedMessage,
cbSealedMessage);
const DWORD cbTrailer = cbSealedMessage - cbMessage;
// describe our buffer to SSPI
SecBuffer rgsb[] = {
{cbMessage, SECBUFFER_DATA, *ppvMessage},
{cbTrailer, SECBUFFER_TOKEN,
static_cast<BYTE*>(*ppvMessage) + cbMessage},
};
SecBufferDesc sbd = {SECBUFFER_VERSION,
sizeof rgsb / sizeof *rgsb,
rgsb};
// decrypt in place
SECURITY_STATUS err =
m_pSSPI->DecryptMessage(&m_hctx, &sbd, nSeqNo, 0);
if (err) {
_err(L"DecryptMessage", false, err);
free(*ppvMessage);
*ppvMessage = 0;
}
return 0 == err;
}
Figure 6 Creating and Verifying Signatures using SSPI
///////////////////////////////////////////////////////////
// excerpt from MODEL.H (SSPI workbench)
///////////////////////////////////////////////////////////
// creating a signature
bool Model::signMessage(const void* pvMessage,
DWORD cbMessage,
DWORD nSeqNo,
void** ppvSignedMessage,
DWORD* pcbSignedMessage) {
_ASSERT(msAuthnComplete == m_state);
// see how big the signature will be for this SSP
SecPkgContext_Sizes sizes;
SECURITY_STATUS err =
m_pSSPI->QueryContextAttributes(&m_hctx,
SECPKG_ATTR_SIZES,
&sizes);
if (err)
_err(L"QueryContextAttributes", false, err);
// allocate a buffer and copy in the raw message
*ppvSignedMessage = malloc(cbMessage +
sizes.cbMaxSignature);
CopyMemory(*ppvSignedMessage, pvMessage, cbMessage);
// describe our buffer for SSPI
SecBuffer rgsb[] = {
{cbMessage, SECBUFFER_DATA, *ppvSignedMessage},
{sizes.cbMaxSignature, SECBUFFER_TOKEN,
static_cast<BYTE*>(*ppvSignedMessage) +
cbMessage},
};
SecBufferDesc sbd = {SECBUFFER_VERSION,
sizeof rgsb / sizeof *rgsb,
rgsb};
// make the signature
err = m_pSSPI->MakeSignature(&m_hctx, 0, &sbd, nSeqNo);
bool bOk = true;
if (err) {
_err(L"MakeSignature", false, err);
bOk = false;
free(*ppvSignedMessage);
*ppvSignedMessage = 0;
}
*pcbSignedMessage = cbMessage + rgsb[1].cbBuffer;
return bOk;
}
///////////////////////////////////////////////////////////
// verifying a signature
bool Model::verifySignature(const void* pvSignedMessage,
DWORD cbSignedMessage,
DWORD cbMessage,
DWORD nSeqNo,
void** ppvMessage) {
_ASSERT(msAuthnComplete == m_state);
// allocate a buffer and copy in the signed message
*ppvMessage = malloc(cbSignedMessage);
CopyMemory(*ppvMessage,
pvSignedMessage,
cbSignedMessage);
const DWORD cbSig = cbSignedMessage - cbMessage;
// describe our buffer for SSPI
SecBuffer rgsb[] = {
{cbMessage, SECBUFFER_DATA, *ppvMessage},
{cbSig, SECBUFFER_TOKEN,
static_cast<BYTE*>(*ppvMessage) + cbMessage},
};
SecBufferDesc sbd = {SECBUFFER_VERSION,
sizeof rgsb / sizeof *rgsb,
rgsb};
// verify the signature
ULONG qop;
SECURITY_STATUS err =
m_pSSPI->VerifySignature(&m_hctx, &sbd, nSeqNo, &qop);
if (err) {
_err(L"VerifySignature", true, err);
free(*ppvMessage);
*ppvMessage = 0;
}
return 0 == err;
}
Figure 7 Validating Passwords using SSPI
#define _WIN32_WINNT 0x500
#define UNICODE
#include <windows.h>
#include <stdio.h>
#define SECURITY_WIN32
#include <security.h>
#pragma comment(lib, "secur32.lib")
// brain-dead error routine that dumps the last error and exits
void _err(const wchar_t* pszFcn, DWORD nErr = GetLastError())
{
wchar_t szErr[256];
if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, nErr, 0,
szErr, sizeof szErr / sizeof *szErr, 0))
wprintf(L"%s failed: %s", pszFcn, szErr);
else wprintf(L"%s failed: 0x%08X", nErr);
exit(1);
}
// Upon success, returns a handle to a NETWORK logon session
// for the specified principal (it will *not* have network
// credentials). Call CloseHandle to dispose of it when
// you are finished.
HANDLE _logonUserWithSSPI(wchar_t* pszSSP,
DWORD grfDesiredAccessToToken,
wchar_t* pszAuthority,
wchar_t* pszPrincipal,
wchar_t* pszPassword) {
// the following code loads the SSPI interface DLL
// and initializes it, getting a table of function ptrs
HINSTANCE hdll = LoadLibrary(L"security.dll");
if (!hdll)
_err(L"LoadLibrary");
INIT_SECURITY_INTERFACE_W initSecurityInterface =
(INIT_SECURITY_INTERFACE_W)GetProcAddress(hdll,
SECURITY_ENTRYPOINT_ANSIW);
if (!initSecurityInterface)
_err(L"GetProcAddress");
PSecurityFunctionTable pSSPI = initSecurityInterface();
// here's where we specify the credentials to verify
SEC_WINNT_AUTH_IDENTITY_EX authIdent = {
SEC_WINNT_AUTH_IDENTITY_VERSION,
sizeof authIdent,
pszPrincipal, lstrlenW(pszPrincipal),
pszAuthority, lstrlenW(pszAuthority),
pszPassword, lstrlenW(pszPassword),
SEC_WINNT_AUTH_IDENTITY_UNICODE,
0, 0
};
// get an SSPI handle for these credentials
CredHandle hcredClient;
TimeStamp expiryClient;
SECURITY_STATUS err =
pSSPI->AcquireCredentialsHandle(0, pszSSP, SECPKG_CRED_OUTBOUND,
0, &authIdent, 0, 0,
&hcredClient, &expiryClient);
if (err)
_err(L"AcquireCredentialsHandle for client", err);
// use the caller's credentials for the server
CredHandle hcredServer;
TimeStamp expiryServer;
err = pSSPI->AcquireCredentialsHandle(0, pszSSP, SECPKG_CRED_INBOUND,
0, 0, 0, 0, &hcredServer,
&expiryServer);
if (err)
_err(L"AcquireCredentialsHandle for server", err);
CtxtHandle hctxClient;
CtxtHandle hctxServer;
// create two buffers:
// one for the client sending tokens to the server,
// one for the server sending tokens to the client
// (buffer size chosen based on current Kerb SSP setting
// for cbMaxToken - you may need to adjust this)
BYTE bufC2S[8000];
BYTE bufS2C[8000];
SecBuffer sbufC2S = { sizeof bufC2S, SECBUFFER_TOKEN, bufC2S };
SecBuffer sbufS2C = { sizeof bufS2C, SECBUFFER_TOKEN, bufS2C };
SecBufferDesc bdC2S = { SECBUFFER_VERSION, 1, &sbufC2S };
SecBufferDesc bdS2C = { SECBUFFER_VERSION, 1, &sbufS2C };
// don't really need any special context attributes
DWORD grfRequiredCtxAttrsClient = ISC_REQ_CONNECTION;
DWORD grfRequiredCtxAttrsServer = ISC_REQ_CONNECTION;
// set up some aliases to make it obvious what's happening
PCtxtHandle pClientCtxHandleIn = 0;
PCtxtHandle pClientCtxHandleOut = &hctxClient;
PCtxtHandle pServerCtxHandleIn = 0;
PCtxtHandle pServerCtxHandleOut = &hctxServer;
SecBufferDesc* pClientInput = 0;
SecBufferDesc* pClientOutput = &bdC2S;
SecBufferDesc* pServerInput = &bdC2S;
SecBufferDesc* pServerOutput = &bdS2C;
DWORD grfCtxAttrsClient = 0;
DWORD grfCtxAttrsServer = 0;
TimeStamp expiryClientCtx;
TimeStamp expiryServerCtx;
// since the caller is acting as the server, we need
// a server principal name so that the client will
// be able to get a Kerb ticket (if Kerb is used)
wchar_t szSPN[256];
ULONG cchSPN = sizeof szSPN / sizeof *szSPN;
GetUserNameEx(NameSamCompatible, szSPN, &cchSPN);
// perform the authentication handshake, playing the
// role of both client *and* server.
bool bClientContinue = true;
bool bServerContinue = true;
while (bClientContinue || bServerContinue) {
if (bClientContinue) {
sbufC2S.cbBuffer = sizeof bufC2S;
err = pSSPI->InitializeSecurityContext(
&hcredClient, pClientCtxHandleIn,
szSPN,
grfRequiredCtxAttrsClient,
0, SECURITY_NATIVE_DREP,
pClientInput, 0,
pClientCtxHandleOut,
pClientOutput,
&grfCtxAttrsClient,
&expiryClientCtx);
switch (err) {
case 0:
bClientContinue = false;
break;
case SEC_I_CONTINUE_NEEDED:
pClientCtxHandleIn = pClientCtxHandleOut;
pClientInput = pServerOutput;
break;
default:
_err(L"InitializeSecurityContext", err);
}
}
if (bServerContinue) {
sbufS2C.cbBuffer = sizeof bufS2C;
err = pSSPI->AcceptSecurityContext(
&hcredServer, pServerCtxHandleIn,
pServerInput,
grfRequiredCtxAttrsServer,
SECURITY_NATIVE_DREP,
pServerCtxHandleOut,
pServerOutput,
&grfCtxAttrsServer,
&expiryServerCtx);
switch (err) {
case 0:
bServerContinue = false;
break;
case SEC_I_CONTINUE_NEEDED:
pServerCtxHandleIn = pServerCtxHandleOut;
break;
default:
_err(L"AcceptSecurityContext", err);
}
}
}
// if everything has gone smoothly, we've now got a logon
// session for the client - let's grab the token now
pSSPI->ImpersonateSecurityContext(pServerCtxHandleOut);
HANDLE htok;
if (!OpenThreadToken(GetCurrentThread(),
grfDesiredAccessToToken,
TRUE, &htok))
_err(L"OpenThreadToken");
// clean up
pSSPI->FreeCredentialsHandle(&hcredClient);
pSSPI->FreeCredentialsHandle(&hcredServer);
pSSPI->DeleteSecurityContext(pServerCtxHandleOut);
pSSPI->DeleteSecurityContext(pClientCtxHandleOut);
return htok;
}
// here's an example of usage
void main() {
// use "NTLM" or "Kerberos"
HANDLE htok = _logonUserWithSSPI(L"Kerberos",
TOKEN_QUERY,
L"bar.com",
L"alice",
L"alice");
CloseHandle(htok);
}