Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

398 lines
11 KiB

  1. /*
  2. * PinCache.c
  3. */
  4. #include <windows.h>
  5. #include "pincache.h"
  6. #if defined(DBG) || defined(DEBUG)
  7. #define DebugPrint(a) (OutputDebugString(a))
  8. #if TEST_DEBUG
  9. #include <stdio.h>
  10. #define CROW 8
  11. void PCPrintBytes(LPSTR pszHdr, BYTE *pb, DWORD cbSize)
  12. {
  13. ULONG cb, i;
  14. CHAR rgsz[1024];
  15. sprintf(rgsz, "\n %s, %d bytes ::\n", pszHdr, cbSize);
  16. DebugPrint(rgsz);
  17. while (cbSize > 0)
  18. {
  19. // Start every row with an extra space
  20. DebugPrint(" ");
  21. cb = min(CROW, cbSize);
  22. cbSize -= cb;
  23. for (i = 0; i < cb; i++)
  24. sprintf(rgsz + (3*i), " %02x", pb[i]);
  25. DebugPrint(rgsz);
  26. for (i = cb; i < CROW; i++)
  27. DebugPrint(" ");
  28. DebugPrint(" '");
  29. for (i = 0; i < cb; i++)
  30. {
  31. if (pb[i] >= 0x20 && pb[i] <= 0x7f)
  32. sprintf(rgsz+i, "%c", pb[i]);
  33. else
  34. sprintf(rgsz+i, ".");
  35. }
  36. sprintf(rgsz+i, "\n");
  37. DebugPrint(rgsz);
  38. pb += cb;
  39. }
  40. }
  41. BOOL MyGetTokenInformation(
  42. HANDLE TokenHandle,
  43. TOKEN_INFORMATION_CLASS TokenInformationClass,
  44. LPVOID TokenInformation,
  45. DWORD TokenInformationLength,
  46. PDWORD ReturnLength);
  47. #define GetTokenInformation(A, B, C, D, E) MyGetTokenInformation(A, B, C, D, E)
  48. #define TestDebugPrint(a) (OutputDebugString(a))
  49. #else
  50. #define TestDebugPrint(a)
  51. #endif // TEST_DEBUG
  52. #else
  53. #define DebugPrint(a)
  54. #define TestDebugPrint(a)
  55. #endif // DBG || DEBUG
  56. typedef struct _PINCACHEITEM
  57. {
  58. LUID luid;
  59. PBYTE pbPin;
  60. DWORD cbPin;
  61. } PINCACHEITEM, *PPINCACHEITEM;
  62. #define CacheAlloc(X) (HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, X))
  63. #define CacheFree(X) (HeapFree(GetProcessHeap(), 0, X))
  64. #define INIT_PIN_ATTACK_SLEEP 3000 // milliseconds
  65. #define MAX_PIN_ATTACK_SLEEP 24000 // milliseconds
  66. #define MAX_FREE_BAD_TRIES 3
  67. /**
  68. * Function: PinCacheFlush
  69. */
  70. void PinCacheFlush(
  71. IN OUT PINCACHE_HANDLE *phCache)
  72. {
  73. PPINCACHEITEM pCache = (PPINCACHEITEM) *phCache;
  74. if (NULL == pCache)
  75. return;
  76. TestDebugPrint(("PinCacheFlush: deleting cache\n"));
  77. ZeroMemory(pCache->pbPin, pCache->cbPin);
  78. ZeroMemory(pCache, sizeof(PINCACHEITEM));
  79. CacheFree(pCache->pbPin);
  80. CacheFree(pCache);
  81. *phCache = NULL;
  82. }
  83. /**
  84. * Function: PinCacheAdd
  85. */
  86. DWORD PinCacheAdd(
  87. IN PINCACHE_HANDLE *phCache,
  88. IN PPINCACHE_PINS pPins,
  89. IN PFN_VERIFYPIN_CALLBACK pfnVerifyPinCallback,
  90. IN PVOID pvCallbackCtx)
  91. {
  92. HANDLE hThreadToken = 0;
  93. TOKEN_STATISTICS stats;
  94. DWORD dwError = ERROR_SUCCESS;
  95. DWORD cb = 0;
  96. PPINCACHEITEM pCache = (PPINCACHEITEM) *phCache;
  97. DWORD cbPinToCache = 0;
  98. PBYTE pbPinToCache = NULL;
  99. BOOL fRefreshPin = FALSE;
  100. static DWORD dwSleep = INIT_PIN_ATTACK_SLEEP;
  101. static DWORD dwBadTries = 0;
  102. if (NULL != pCache &&
  103. (0 != memcmp(pCache->pbPin, pPins->pbCurrentPin, pCache->cbPin)
  104. || pPins->cbCurrentPin != pCache->cbPin))
  105. {
  106. // The caller hasn't supplied the correct Pin, according to the current
  107. // cache state. Perhaps the user accidently typed the wrong pin, in which
  108. // case the caller's logon LUID should be the same as the cached LUID.
  109. // If the LUID's don't match, this could still be an attack or a legitimate
  110. // attempt from a different logon with a mis-typed pin.
  111. if (! OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hThreadToken))
  112. {
  113. if (! OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hThreadToken))
  114. {
  115. dwError = GetLastError();
  116. goto Ret;
  117. }
  118. }
  119. if (! GetTokenInformation(
  120. hThreadToken, TokenStatistics, &stats, sizeof(stats), &cb))
  121. {
  122. dwError = GetLastError();
  123. goto Ret;
  124. }
  125. if (0 != memcmp(&stats.AuthenticationId, &pCache->luid, sizeof(LUID)) &&
  126. ++dwBadTries > MAX_FREE_BAD_TRIES)
  127. {
  128. // Current caller is a different luid from the cached one,
  129. // and it's happened a few times already, so this call is suspicious.
  130. // Start delaying.
  131. DebugPrint(("PinCacheAdd: error - calling SleepEx(). Currently cached pin doesn't match\n"));
  132. SleepEx(dwSleep, FALSE);
  133. if (dwSleep < MAX_PIN_ATTACK_SLEEP)
  134. dwSleep *= 2;
  135. }
  136. dwError = SCARD_W_WRONG_CHV;
  137. goto Ret;
  138. }
  139. else if (0 != dwBadTries)
  140. {
  141. dwSleep = INIT_PIN_ATTACK_SLEEP;
  142. dwBadTries = 0;
  143. }
  144. if (pPins->pbNewPin)
  145. {
  146. fRefreshPin = TRUE;
  147. cbPinToCache = pPins->cbNewPin;
  148. pbPinToCache = pPins->pbNewPin;
  149. }
  150. else
  151. {
  152. cbPinToCache = pPins->cbCurrentPin;
  153. pbPinToCache = pPins->pbCurrentPin;
  154. }
  155. if (fRefreshPin || NULL == pCache)
  156. {
  157. // Check the pin
  158. if (ERROR_SUCCESS != (dwError =
  159. pfnVerifyPinCallback(pPins, pvCallbackCtx)))
  160. {
  161. TestDebugPrint(("PinCacheAdd: pfnVerifyPinCallback failed\n"));
  162. return dwError;
  163. }
  164. }
  165. if (! OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hThreadToken))
  166. {
  167. if (! OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hThreadToken))
  168. {
  169. TestDebugPrint(("PinCacheAdd: failed to open thread or process token\n"));
  170. dwError = GetLastError();
  171. goto Ret;
  172. }
  173. }
  174. if (! GetTokenInformation(
  175. hThreadToken, TokenStatistics, &stats, sizeof(stats), &cb))
  176. {
  177. TestDebugPrint(("PinCacheAdd: GetTokenInformation failed\n"));
  178. dwError = GetLastError();
  179. goto Ret;
  180. }
  181. #if TEST_DEBUG
  182. PCPrintBytes("PinCache LUID", (PBYTE) &stats.AuthenticationId, sizeof(LUID));
  183. #endif
  184. // Now the current ID is in stats.AuthenticationId
  185. if (NULL == pCache)
  186. {
  187. TestDebugPrint(("PinCacheAdd: initializing new cache\n"));
  188. // Initialize new cache
  189. if (NULL == (pCache = (PPINCACHEITEM) CacheAlloc(sizeof(PINCACHEITEM))))
  190. {
  191. dwError = ERROR_NOT_ENOUGH_MEMORY;
  192. goto Ret;
  193. }
  194. CopyMemory(&pCache->luid, &stats.AuthenticationId, sizeof(LUID));
  195. *phCache = (PINCACHE_HANDLE) pCache;
  196. fRefreshPin = TRUE;
  197. }
  198. else
  199. {
  200. // Compare ID's
  201. if (0 != memcmp(&stats.AuthenticationId, &pCache->luid, sizeof(LUID)))
  202. {
  203. // PIN's are the same, so cache the new ID
  204. TestDebugPrint(("PinCacheAdd: same Pin, different Logon as cached values\n"));
  205. CopyMemory(&pCache->luid, &stats.AuthenticationId, sizeof(LUID));
  206. }
  207. }
  208. if (fRefreshPin)
  209. {
  210. if (pCache->pbPin)
  211. CacheFree(pCache->pbPin);
  212. pCache->cbPin = cbPinToCache;
  213. if (NULL == (pCache->pbPin = (PBYTE) CacheAlloc(cbPinToCache)))
  214. {
  215. dwError = ERROR_NOT_ENOUGH_MEMORY;
  216. goto Ret;
  217. }
  218. CopyMemory(pCache->pbPin, pbPinToCache, cbPinToCache);
  219. }
  220. Ret:
  221. if (hThreadToken)
  222. CloseHandle(hThreadToken);
  223. return dwError;
  224. }
  225. /**
  226. * Function: PinCacheQuery
  227. */
  228. DWORD PinCacheQuery(
  229. IN PINCACHE_HANDLE hCache,
  230. IN OUT PBYTE pbPin,
  231. IN OUT PDWORD pcbPin)
  232. {
  233. HANDLE hThreadToken = 0;
  234. TOKEN_STATISTICS stats;
  235. DWORD dwError = ERROR_SUCCESS;
  236. DWORD cb = 0;
  237. PPINCACHEITEM pCache = (PPINCACHEITEM) hCache;
  238. if (NULL == pCache)
  239. {
  240. *pcbPin = 0;
  241. return ERROR_EMPTY;
  242. }
  243. if (! OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hThreadToken))
  244. {
  245. if (! OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hThreadToken))
  246. {
  247. TestDebugPrint(("PinCacheQuery: failed to open thread or process token\n"));
  248. dwError = GetLastError();
  249. goto Ret;
  250. }
  251. }
  252. if (! GetTokenInformation(
  253. hThreadToken, TokenStatistics, &stats, sizeof(stats), &cb))
  254. {
  255. TestDebugPrint(("PinCacheQuery: GetTokenInformation failed\n"));
  256. dwError = GetLastError();
  257. goto Ret;
  258. }
  259. // Now the current ID is in stats.AuthenticationId
  260. if (0 != memcmp(&stats.AuthenticationId, &pCache->luid, sizeof(LUID)))
  261. {
  262. // ID's are different, so ignore cache
  263. TestDebugPrint(("PinCacheQuery: different Logon from cached value\n"));
  264. *pcbPin = 0;
  265. goto Ret;
  266. }
  267. // ID's are the same, so return cached PIN
  268. TestDebugPrint(("PinCacheQuery: same Logon as cached value\n"));
  269. if (NULL != pbPin)
  270. {
  271. if (*pcbPin >= pCache->cbPin)
  272. CopyMemory(pbPin, pCache->pbPin, pCache->cbPin);
  273. else
  274. dwError = ERROR_MORE_DATA;
  275. }
  276. *pcbPin = pCache->cbPin;
  277. Ret:
  278. if (hThreadToken)
  279. CloseHandle(hThreadToken);
  280. return dwError;
  281. }
  282. /**
  283. * Function: PinCachePresentPin
  284. */
  285. DWORD PinCachePresentPin(
  286. IN PINCACHE_HANDLE hCache,
  287. IN PFN_VERIFYPIN_CALLBACK pfnVerifyPinCallback,
  288. IN PVOID pvCallbackCtx)
  289. {
  290. HANDLE hThreadToken = 0;
  291. TOKEN_STATISTICS stats;
  292. DWORD cb = 0;
  293. DWORD dwError = ERROR_SUCCESS;
  294. PPINCACHEITEM pCache = (PPINCACHEITEM) hCache;
  295. PINCACHE_PINS Pins;
  296. if (NULL == pCache)
  297. return ERROR_EMPTY;
  298. if (! OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hThreadToken))
  299. {
  300. if (! OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hThreadToken))
  301. {
  302. TestDebugPrint(("PinCachePresentPin: failed to open thread or process token\n"));
  303. dwError = GetLastError();
  304. goto Ret;
  305. }
  306. }
  307. if (! GetTokenInformation(
  308. hThreadToken, TokenStatistics, &stats, sizeof(stats), &cb))
  309. {
  310. TestDebugPrint(("PinCachePresentPin: GetTokenInformation failed\n"));
  311. dwError = GetLastError();
  312. goto Ret;
  313. }
  314. // Now the current ID is in stats.AuthenticationId
  315. if (0 != memcmp(&stats.AuthenticationId, &pCache->luid, sizeof(LUID)))
  316. {
  317. // ID's are different, so ignore cache
  318. TestDebugPrint(("PinCachePresentPin: different Logon from cached value\n"));
  319. dwError = SCARD_W_CARD_NOT_AUTHENTICATED;
  320. goto Ret;
  321. }
  322. // ID's are the same, so return cached PIN
  323. TestDebugPrint(("PinCachePresentPin: same Logon as cached value\n"));
  324. Pins.cbCurrentPin = pCache->cbPin;
  325. Pins.pbCurrentPin = pCache->pbPin;
  326. Pins.cbNewPin = 0;
  327. Pins.pbNewPin = NULL;
  328. dwError = (*pfnVerifyPinCallback)(&Pins, pvCallbackCtx);
  329. Ret:
  330. if (hThreadToken)
  331. CloseHandle(hThreadToken);
  332. return dwError;
  333. }