|
|
/*
* PinCache.c */
#include <windows.h>
#include "pincache.h"
#if defined(DBG) || defined(DEBUG)
#define DebugPrint(a) (OutputDebugString(a))
#if TEST_DEBUG
#include <stdio.h>
#define CROW 8
void PCPrintBytes(LPSTR pszHdr, BYTE *pb, DWORD cbSize) { ULONG cb, i; CHAR rgsz[1024];
sprintf(rgsz, "\n %s, %d bytes ::\n", pszHdr, cbSize); DebugPrint(rgsz);
while (cbSize > 0) { // Start every row with an extra space
DebugPrint(" ");
cb = min(CROW, cbSize); cbSize -= cb; for (i = 0; i < cb; i++) sprintf(rgsz + (3*i), " %02x", pb[i]); DebugPrint(rgsz); for (i = cb; i < CROW; i++) DebugPrint(" "); DebugPrint(" '"); for (i = 0; i < cb; i++) { if (pb[i] >= 0x20 && pb[i] <= 0x7f) sprintf(rgsz+i, "%c", pb[i]); else sprintf(rgsz+i, "."); } sprintf(rgsz+i, "\n"); DebugPrint(rgsz); pb += cb; } }
BOOL MyGetTokenInformation( HANDLE TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, LPVOID TokenInformation, DWORD TokenInformationLength, PDWORD ReturnLength);
#define GetTokenInformation(A, B, C, D, E) MyGetTokenInformation(A, B, C, D, E)
#define TestDebugPrint(a) (OutputDebugString(a))
#else
#define TestDebugPrint(a)
#endif // TEST_DEBUG
#else
#define DebugPrint(a)
#define TestDebugPrint(a)
#endif // DBG || DEBUG
typedef struct _PINCACHEITEM { LUID luid; PBYTE pbPin; DWORD cbPin; } PINCACHEITEM, *PPINCACHEITEM;
#define CacheAlloc(X) (HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, X))
#define CacheFree(X) (HeapFree(GetProcessHeap(), 0, X))
#define INIT_PIN_ATTACK_SLEEP 3000 // milliseconds
#define MAX_PIN_ATTACK_SLEEP 24000 // milliseconds
#define MAX_FREE_BAD_TRIES 3
/**
* Function: PinCacheFlush */ void PinCacheFlush( IN OUT PINCACHE_HANDLE *phCache) { PPINCACHEITEM pCache = (PPINCACHEITEM) *phCache;
if (NULL == pCache) return;
TestDebugPrint(("PinCacheFlush: deleting cache\n"));
ZeroMemory(pCache->pbPin, pCache->cbPin); ZeroMemory(pCache, sizeof(PINCACHEITEM));
CacheFree(pCache->pbPin); CacheFree(pCache);
*phCache = NULL; }
/**
* Function: PinCacheAdd */ DWORD PinCacheAdd( IN PINCACHE_HANDLE *phCache, IN PPINCACHE_PINS pPins, IN PFN_VERIFYPIN_CALLBACK pfnVerifyPinCallback, IN PVOID pvCallbackCtx) { HANDLE hThreadToken = 0; TOKEN_STATISTICS stats; DWORD dwError = ERROR_SUCCESS; DWORD cb = 0; PPINCACHEITEM pCache = (PPINCACHEITEM) *phCache; DWORD cbPinToCache = 0; PBYTE pbPinToCache = NULL; BOOL fRefreshPin = FALSE; static DWORD dwSleep = INIT_PIN_ATTACK_SLEEP; static DWORD dwBadTries = 0; if (NULL != pCache && (0 != memcmp(pCache->pbPin, pPins->pbCurrentPin, pCache->cbPin) || pPins->cbCurrentPin != pCache->cbPin)) {
// The caller hasn't supplied the correct Pin, according to the current
// cache state. Perhaps the user accidently typed the wrong pin, in which
// case the caller's logon LUID should be the same as the cached LUID.
// If the LUID's don't match, this could still be an attack or a legitimate
// attempt from a different logon with a mis-typed pin.
if (! OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hThreadToken)) { if (! OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hThreadToken)) { dwError = GetLastError(); goto Ret; } }
if (! GetTokenInformation( hThreadToken, TokenStatistics, &stats, sizeof(stats), &cb)) { dwError = GetLastError(); goto Ret; }
if (0 != memcmp(&stats.AuthenticationId, &pCache->luid, sizeof(LUID)) && ++dwBadTries > MAX_FREE_BAD_TRIES) { // Current caller is a different luid from the cached one,
// and it's happened a few times already, so this call is suspicious.
// Start delaying.
DebugPrint(("PinCacheAdd: error - calling SleepEx(). Currently cached pin doesn't match\n")); SleepEx(dwSleep, FALSE);
if (dwSleep < MAX_PIN_ATTACK_SLEEP) dwSleep *= 2; }
dwError = SCARD_W_WRONG_CHV; goto Ret; } else if (0 != dwBadTries) { dwSleep = INIT_PIN_ATTACK_SLEEP; dwBadTries = 0; }
if (pPins->pbNewPin) { fRefreshPin = TRUE; cbPinToCache = pPins->cbNewPin; pbPinToCache = pPins->pbNewPin; } else { cbPinToCache = pPins->cbCurrentPin; pbPinToCache = pPins->pbCurrentPin; }
if (fRefreshPin || NULL == pCache) { // Check the pin
if (ERROR_SUCCESS != (dwError = pfnVerifyPinCallback(pPins, pvCallbackCtx))) { TestDebugPrint(("PinCacheAdd: pfnVerifyPinCallback failed\n")); return dwError; } }
if (! OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hThreadToken)) { if (! OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hThreadToken)) { TestDebugPrint(("PinCacheAdd: failed to open thread or process token\n")); dwError = GetLastError(); goto Ret; } }
if (! GetTokenInformation( hThreadToken, TokenStatistics, &stats, sizeof(stats), &cb)) { TestDebugPrint(("PinCacheAdd: GetTokenInformation failed\n")); dwError = GetLastError(); goto Ret; }
#if TEST_DEBUG
PCPrintBytes("PinCache LUID", (PBYTE) &stats.AuthenticationId, sizeof(LUID)); #endif
// Now the current ID is in stats.AuthenticationId
if (NULL == pCache) { TestDebugPrint(("PinCacheAdd: initializing new cache\n"));
// Initialize new cache
if (NULL == (pCache = (PPINCACHEITEM) CacheAlloc(sizeof(PINCACHEITEM)))) { dwError = ERROR_NOT_ENOUGH_MEMORY; goto Ret; }
CopyMemory(&pCache->luid, &stats.AuthenticationId, sizeof(LUID)); *phCache = (PINCACHE_HANDLE) pCache; fRefreshPin = TRUE; } else { // Compare ID's
if (0 != memcmp(&stats.AuthenticationId, &pCache->luid, sizeof(LUID))) { // PIN's are the same, so cache the new ID
TestDebugPrint(("PinCacheAdd: same Pin, different Logon as cached values\n")); CopyMemory(&pCache->luid, &stats.AuthenticationId, sizeof(LUID)); } }
if (fRefreshPin) { if (pCache->pbPin) CacheFree(pCache->pbPin);
pCache->cbPin = cbPinToCache; if (NULL == (pCache->pbPin = (PBYTE) CacheAlloc(cbPinToCache))) { dwError = ERROR_NOT_ENOUGH_MEMORY; goto Ret; } CopyMemory(pCache->pbPin, pbPinToCache, cbPinToCache); }
Ret: if (hThreadToken) CloseHandle(hThreadToken);
return dwError; }
/**
* Function: PinCacheQuery */ DWORD PinCacheQuery( IN PINCACHE_HANDLE hCache, IN OUT PBYTE pbPin, IN OUT PDWORD pcbPin) { HANDLE hThreadToken = 0; TOKEN_STATISTICS stats; DWORD dwError = ERROR_SUCCESS; DWORD cb = 0; PPINCACHEITEM pCache = (PPINCACHEITEM) hCache;
if (NULL == pCache) { *pcbPin = 0; return ERROR_EMPTY; }
if (! OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hThreadToken)) { if (! OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hThreadToken)) { TestDebugPrint(("PinCacheQuery: failed to open thread or process token\n")); dwError = GetLastError(); goto Ret; } }
if (! GetTokenInformation( hThreadToken, TokenStatistics, &stats, sizeof(stats), &cb)) { TestDebugPrint(("PinCacheQuery: GetTokenInformation failed\n")); dwError = GetLastError(); goto Ret; }
// Now the current ID is in stats.AuthenticationId
if (0 != memcmp(&stats.AuthenticationId, &pCache->luid, sizeof(LUID))) { // ID's are different, so ignore cache
TestDebugPrint(("PinCacheQuery: different Logon from cached value\n")); *pcbPin = 0; goto Ret; }
// ID's are the same, so return cached PIN
TestDebugPrint(("PinCacheQuery: same Logon as cached value\n")); if (NULL != pbPin) { if (*pcbPin >= pCache->cbPin) CopyMemory(pbPin, pCache->pbPin, pCache->cbPin); else dwError = ERROR_MORE_DATA; }
*pcbPin = pCache->cbPin; Ret: if (hThreadToken) CloseHandle(hThreadToken);
return dwError; }
/**
* Function: PinCachePresentPin */ DWORD PinCachePresentPin( IN PINCACHE_HANDLE hCache, IN PFN_VERIFYPIN_CALLBACK pfnVerifyPinCallback, IN PVOID pvCallbackCtx) { HANDLE hThreadToken = 0; TOKEN_STATISTICS stats; DWORD cb = 0; DWORD dwError = ERROR_SUCCESS; PPINCACHEITEM pCache = (PPINCACHEITEM) hCache; PINCACHE_PINS Pins;
if (NULL == pCache) return ERROR_EMPTY;
if (! OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hThreadToken)) { if (! OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hThreadToken)) { TestDebugPrint(("PinCachePresentPin: failed to open thread or process token\n")); dwError = GetLastError(); goto Ret; } }
if (! GetTokenInformation( hThreadToken, TokenStatistics, &stats, sizeof(stats), &cb)) { TestDebugPrint(("PinCachePresentPin: GetTokenInformation failed\n")); dwError = GetLastError(); goto Ret; }
// Now the current ID is in stats.AuthenticationId
if (0 != memcmp(&stats.AuthenticationId, &pCache->luid, sizeof(LUID))) { // ID's are different, so ignore cache
TestDebugPrint(("PinCachePresentPin: different Logon from cached value\n")); dwError = SCARD_W_CARD_NOT_AUTHENTICATED; goto Ret; }
// ID's are the same, so return cached PIN
TestDebugPrint(("PinCachePresentPin: same Logon as cached value\n"));
Pins.cbCurrentPin = pCache->cbPin; Pins.pbCurrentPin = pCache->pbPin; Pins.cbNewPin = 0; Pins.pbNewPin = NULL;
dwError = (*pfnVerifyPinCallback)(&Pins, pvCallbackCtx);
Ret: if (hThreadToken) CloseHandle(hThreadToken);
return dwError; }
|