From: Guillaume Raffin <theelectronwill@gmail.com> --- dlls/winscard/tests/winscard.c | 130 ++++++++++++++++ dlls/winscard/winscard.c | 265 +++++++++++++++++++++++++++++++++ dlls/winscard/winscard.spec | 2 +- 3 files changed, 396 insertions(+), 1 deletion(-) diff --git a/dlls/winscard/tests/winscard.c b/dlls/winscard/tests/winscard.c index 96f1f504f1f..fa88186ca2d 100644 --- a/dlls/winscard/tests/winscard.c +++ b/dlls/winscard/tests/winscard.c @@ -439,6 +439,135 @@ static void test_SCardGetCardTypeProviderName(void) ok(ret == ERROR_SUCCESS, "failed to release context: error %ld\n", ret); } +static void test_SCardListCardsW(void) +{ + LONG ret; + const WCHAR *expected; + SCARDCONTEXT ctx = 0; /* SCardListCards does not need a context */ + BYTE atr_full[36]; + DWORD match_len = 0; + WCHAR *matching_cards; + + /* test basic error conditions */ + ret = SCardListCardsW(ctx, NULL, NULL, 0, NULL, NULL); + ok(ret == SCARD_E_INVALID_PARAMETER, "should fail when inout_cards_len is null\n"); + + match_len = 2; + matching_cards = calloc(match_len, sizeof(WCHAR)); + ret = SCardListCardsW(ctx, NULL, NULL, 0, matching_cards, &match_len); + ok(ret == SCARD_E_INSUFFICIENT_BUFFER, "should fail when the output buffer is too small\n"); + free(matching_cards); + + /* get length and allocate */ + ret = SCardListCardsW(ctx, NULL, NULL, 0, NULL, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list all cards: error %#lx\n", ret); + ok(match_len > 0, "match size should not be empty"); + matching_cards = calloc(match_len, sizeof(WCHAR)); + ret = SCardListCardsW(ctx, NULL, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list all cards: error %#lx\n", ret); + expected = L"PKI-test-1\0PKI-test-2\0"; + ok(match_len == 23, "invalid length: expected %ld, got %ld\n", 23L, match_len); + ok(memcmp(matching_cards, expected, match_len) == 0, "bad output of SCardListCardsW: %s\n", debugstr_wn(matching_cards, match_len)); + free(matching_cards); + + /* test with pre-allocated */ + match_len = 32; + matching_cards = malloc(match_len * sizeof(WCHAR)); + for (int i = 0; i < 32; i++) { + matching_cards[i] = 0xcafe; + } + ret = SCardListCardsW(ctx, NULL, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list all cards: error %#lx\n", ret); + expected = L"PKI-test-1\0PKI-test-2\0"; + ok(match_len == 23, "invalid length: expected %ld, got %ld\n", 23L, match_len); + ok(memcmp(matching_cards, expected, match_len) == 0, "bad output of SCardListCardsW: %s\n", debugstr_wn(matching_cards, match_len)); + for (int i = match_len; i < 32; i++) { + ok(matching_cards[i] == 0xcafe, "memory corruption: matching_cards[%d] = %u\n", i, matching_cards[i]); + } + free(matching_cards); + + /* test with auto alloc */ + match_len = SCARD_AUTOALLOCATE; + matching_cards = NULL; + ret = SCardListCardsW(ctx, NULL, NULL, 0, (LPWSTR)&matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list all cards: error %#lx\n", ret); + expected = L"PKI-test-1\0PKI-test-2\0\0"; + ok(match_len == 23, "invalid length: expected %ld, got %ld\n", 23L, match_len); + ok(matching_cards != NULL, "the buffer should have been allocated, is NULL\n"); + ok(memcmp(matching_cards, expected, match_len) == 0, "bad output of SCardListCardsW: %s\n", debugstr_wn(matching_cards, match_len)); + + ret = SCardFreeMemory(ctx, matching_cards); + ok(ret == ERROR_SUCCESS, "failed to free auto-allocated memory\n"); + + /* test with ATRs that match exactly card 1 */ + match_len = 32; + matching_cards = calloc(match_len, sizeof(WCHAR)); + ret = SCardListCardsW(ctx, CARD_ATR_1, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list card 1: error %#lx\n", ret); + expected = L"PKI-test-1\0"; + ok(match_len == 12, "invalid length: expected %ld, got %ld\n", 12L, match_len); + ok(matching_cards != NULL, "the buffer should have been allocated, is NULL\n"); + ok(memcmp(matching_cards, expected, match_len) == 0, "bad output of SCardListCardsW: %s\n", debugstr_wn(matching_cards, match_len)); + + /* test with ATRs that match exactly card 2 */ + match_len = 32; + ret = SCardListCardsW(ctx, CARD_ATR_2, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list card 2: error %#lx\n", ret); + expected = L"PKI-test-2\0"; + ok(match_len == 12, "invalid length: expected %ld, got %ld\n", 12L, match_len); + ok(matching_cards != NULL, "the buffer should have been allocated, is NULL\n"); + ok(memcmp(matching_cards, expected, match_len) == 0, "bad output of SCardListCardsW: %s\n", debugstr_wn(matching_cards, match_len)); + + /* test with zero-padded ATR that match */ + for (int i = 0; i < sizeof(CARD_ATR_2); i++) { + atr_full[i] = CARD_ATR_2[i]; + } + for (int i = sizeof(CARD_ATR_2); i < sizeof(atr_full); i++) { + atr_full[i] = 0; + } + match_len = 32; + ret = SCardListCardsW(ctx, atr_full, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list card 2: error %#lx\n", ret); + expected = L"PKI-test-2\0"; + ok(match_len == 12, "invalid length: expected %ld, got %ld\n", 12L, match_len); + ok(matching_cards != NULL, "the buffer should have been allocated, is NULL\n"); + ok(memcmp(matching_cards, expected, match_len) == 0, "bad output of SCardListCardsW: %s\n", debugstr_wn(matching_cards, match_len)); + + /* test with an ATR that matches thanks to the mask */ + for (int i = 0; i < sizeof(CARD_ATR_2)-1; i++) { + atr_full[i] = CARD_ATR_2[i]; + } + atr_full[sizeof(CARD_ATR_2)-1] = 0xff; + for (int i = sizeof(CARD_ATR_2); i < sizeof(atr_full); i++) { + atr_full[i] = 0; + } + match_len = 32; + ret = SCardListCardsW(ctx, atr_full, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list card 2: error %#lx\n", ret); + expected = L"PKI-test-2\0"; + ok(match_len == 12, "invalid length: expected %ld, got %ld\n", 12L, match_len); + ok(matching_cards != NULL, "the buffer should have been allocated, is NULL\n"); + ok(memcmp(matching_cards, expected, match_len) == 0, "bad output of SCardListCardsW: %s\n", debugstr_wn(matching_cards, match_len)); + + /* test with an ATR that doesn't match */ + atr_full[0] = 0x3B; + atr_full[1] = 0x02; + atr_full[2] = 0x14; + atr_full[3] = 0x50; + for (int i = 4; i < sizeof(atr_full); i++) { + atr_full[i] = 0; + } + match_len = 32; + ret = SCardListCardsW(ctx, atr_full, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list no card: error %#lx\n", ret); + expected = L""; + ok(match_len == 1, "invalid length: expected %ld, got %ld\n", 1L, match_len); + ok(matching_cards != NULL, "the buffer should have been allocated, is NULL\n"); + ok(memcmp(matching_cards, expected, match_len) == 0, "bad output of SCardListCardsW: %s\n", debugstr_wn(matching_cards, match_len)); + + free(matching_cards); +} + static void test_smartcard_db(void) { LONG ret; @@ -448,6 +577,7 @@ static void test_smartcard_db(void) if (ret) return; test_SCardGetCardTypeProviderName(); + test_SCardListCardsW(); } START_TEST(winscard) diff --git a/dlls/winscard/winscard.c b/dlls/winscard/winscard.c index ca826695f84..f8c132ae7ec 100644 --- a/dlls/winscard/winscard.c +++ b/dlls/winscard/winscard.c @@ -971,6 +971,9 @@ LONG WINAPI SCardFreeMemory( SCARDCONTEXT context, const void *mem ) /* subkey of the db in HKLM */ const WCHAR* SUBKEY_SMARTCARDS_DATABASE = L"SOFTWARE\\Microsoft\\Cryptography\\Calais\\SmartCards"; +/* The ATR is between 2 and 33 bytes, but winscard pads it to 36 bytes (see struct SCARD_READERSTATEW) */ +#define ATR_N_BYTES 36 + /******************************************************************************* * SCardGetCardTypeProviderNameW (winscard.@) * @@ -1069,6 +1072,268 @@ LONG WINAPI SCardGetCardTypeProviderNameW(SCARDCONTEXT context, const WCHAR *car return SCARD_S_SUCCESS; } +/** + * Parses an ATR string and returns its length, or -1 if the ATR is invalid. + * + * See https://en.wikipedia.org/wiki/Answer_to_reset. + */ +static int parse_atr_length(const BYTE *atr) +{ + int length = 2; /* TS and T0 are always present */ + BYTE ts = atr[0]; + BYTE t0 = atr[1]; + BYTE k = t0 & 0x0f; /* number of historical bytes */ + BOOL has_tck = FALSE; + BYTE presence; + + if (ts != 0x3b && ts != 0x3f) + { + /* invalid TS */ + return -1; + } + + /* read T{A,B,C,D}i */ + presence = (t0 & 0xf0) >> 4; /* presence of T{A,B,C,D}(1) */ + while (presence != 0) + { + BYTE td_i; + if (presence & 0b0001) length++; /* TAi */ + if (presence & 0b0010) length++; /* TBi */ + if (presence & 0b0100) length++; /* TCi */ + if (presence & 0b1000) + { + /* TDi is present, use it to determine whether T{A,B,C,D}(i+1) are present */ + td_i = atr[length++]; + presence = (td_i & 0xf0) >> 4; + has_tck |= (td_i & 0x0f) != 0; /* TCK is present if any T is non-zero */ + } else { + presence = 0; + } + + if (length > ATR_N_BYTES) return -1; + } + + length += k; + if (has_tck) length++; + if (length > ATR_N_BYTES) return -1; + return length; +} + +static const char *debug_atr_n(const BYTE *atr, int n) +{ + static const char hex[16] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; + char buffer[ATR_N_BYTES*3]; + + if (!atr) return "(null)"; + if (n < 0 || n > ATR_N_BYTES || IsBadReadPtr(atr, n)) return "(invalid)"; + for (int i = 0; i < n; i++) + { + BYTE b = atr[i]; + buffer[i*3] = hex[(b >> 4) & 0x0f]; + buffer[i*3 + 1] = hex[b & 0x0f]; + buffer[i*3 + 2] = (i < n-1) ? ' ' : '\0'; + } + return strdup(buffer); +} + +static const char *debug_atr(const BYTE *atr) +{ + if (!atr) return "(null)"; + return debug_atr_n(atr, parse_atr_length(atr)); +} + +static BOOL card_atr_matches(const HKEY db_key, const WCHAR *card_subkey_name, const BYTE *atr, LONG atr_len) +{ + HKEY card_subkey; + BYTE search_atr[ATR_N_BYTES] = {0}; + BYTE card_atr[ATR_N_BYTES] = {0}; + BYTE card_atr_mask[ATR_N_BYTES]; + DWORD card_atr_size = ATR_N_BYTES; + DWORD card_atr_mask_size = ATR_N_BYTES; + LONG ret; + BOOL matches = TRUE; + + /* pad the given ATR to ATR_N_BYTES */ + for (int i = 0; i < atr_len; i++) + { + search_atr[i] = atr[i]; + } + + /* fill the default mask */ + for (int i = 0; i < ATR_N_BYTES; i++) + { + card_atr_mask[i] = 0xff; + } + + if ((ret=RegOpenKeyExW(db_key, card_subkey_name, 0, KEY_READ, &card_subkey)) != ERROR_SUCCESS) + { + /* ignore this sub-key, others may work */ + WARN("failed to open registry key HKLM\\%S\\%S: %#lx\n", SUBKEY_SMARTCARDS_DATABASE, card_subkey_name, ret); + return FALSE; + } + + if ((ret=RegGetValueW(card_subkey, NULL, L"ATR", RRF_RT_REG_BINARY, NULL, card_atr, &card_atr_size)) != ERROR_SUCCESS) + { + /* ignore this sub-key, others may work */ + WARN("failed to read registry value HKLM\\%S\\%S\\ATR: %#lx\n", SUBKEY_SMARTCARDS_DATABASE, card_subkey_name, ret); + RegCloseKey(card_subkey); + return FALSE; + } + switch (ret=RegGetValueW(card_subkey, NULL, L"ATRMask", RRF_RT_REG_BINARY, NULL, card_atr_mask, &card_atr_mask_size)) + { + case ERROR_SUCCESS: + break; + case ERROR_FILE_NOT_FOUND: + /* the mask is optional in the db, use the default */ + break; + default: + WARN("failed to read registry value HKLM\\%S\\%S\\ATRMask: %#lx\n", SUBKEY_SMARTCARDS_DATABASE, card_subkey_name, ret); + RegCloseKey(card_subkey); + return FALSE; + } + TRACE("got from db: ATR=%s, ATRMask=%s\n", debug_atr_n(card_atr, card_atr_size), debug_atr_n(card_atr_mask, card_atr_size)); + + /* use the ATR and ATR mask to check whether this card matches the caller's request */ + for (DWORD i = 0; i < ATR_N_BYTES; i++) + { + if ((search_atr[i] & card_atr_mask[i]) != card_atr[i]) + { + matches = FALSE; + break; + } + } + RegCloseKey(card_subkey); + TRACE("returning %d\n", matches); + return matches; +} + +/******************************************************************************* + * SCardListCardsW (winscard.@) + * + * Look up known cards in the smart card database. + * + * PARAMS + * context [I] handle of scard context (can be null) + * atr [I] "Answer To Reset" of the card (can be null). If supplied, only the cards that match the ATR are returned. + * interfaces [I] array of card interfaces GUIDs (can be null). If supplied, only the cards that support these interfaecs are returned. + * interface_count [I] length of the interfaces array + * out_cards [O] cards found (multi-string) + * inout_cards_len [I/O] length of out_cards in characters, including '\0'. Set this to SCARD_AUTOALLOCATE to allocate a buffer automatically + */ +/** Look up for known cards in the smart card database. */ +LONG WINAPI SCardListCardsW(SCARDCONTEXT context, const BYTE *atr, const GUID *interfaces, DWORD interface_count, WCHAR *out_cards, DWORD *inout_cards_len) +{ + struct handle *handle = (struct handle *)context; + HKEY db_key; + LSTATUS ret; + BYTE **new_output; + DWORD res_len_wchars = 0; + + DWORD i_subkey = 0; + WCHAR card_subkey_name[256]; + DWORD card_subkey_name_len_wchars = 256; + DWORD new_len = 0; + int atr_len = 0; + + TRACE("%Ix, %s, %p, %lu, %p, %p\n", context, debug_atr(atr), interfaces, interface_count, out_cards, inout_cards_len); + + if (!inout_cards_len) return SCARD_E_INVALID_PARAMETER; + + if (handle != NULL) + { + if (handle->magic != CONTEXT_MAGIC) + { + return ERROR_INVALID_HANDLE; + } + FIXME("card scopes not implemented\n"); + /* continue anyway */ + } + + if (interfaces != NULL) + { + FIXME("card services identifiers not implemented\n"); + /* continue anyway, it's usually better to try to return at least one card */ + } + + if (atr != NULL) + { + atr_len = parse_atr_length(atr); + if (atr_len < 0) return SCARD_E_INVALID_ATR; + } + + /* + According to the docs, we have 3 cases for the result: + - out_cards == null => return (in inout_cards_len) the length of the buffer that would have been returned if it existed + - out_cards != null && *inout_cards_len == SCARD_AUTOALLOCATE => allocate a buffer ourselves + - out_cards != null && *inout_cards_len != SCARD_AUTOALLOCATE => fill the provided buffer (out_cards) + */ + + /* handle the auto-allocate flag in two passes */ + if (out_cards != NULL && *inout_cards_len == SCARD_AUTOALLOCATE) + { + /* get the buffer size */ + SCardListCardsW(context, atr, interfaces, interface_count, NULL, &res_len_wchars); + + /* allocate and fill */ + new_output = (BYTE**)out_cards; + *new_output = calloc(res_len_wchars, sizeof(WCHAR)); + if (*new_output == NULL) return ERROR_NOT_ENOUGH_MEMORY; + *inout_cards_len = res_len_wchars; + return SCardListCardsW(context, atr, interfaces, interface_count, (WCHAR*)*new_output, inout_cards_len); + } + + ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, SUBKEY_SMARTCARDS_DATABASE, 0, KEY_READ, &db_key); + + if (ret == ERROR_FILE_NOT_FOUND) { + WARN("the smartcard db does not exist: HKLM\\%S not found\n", SUBKEY_SMARTCARDS_DATABASE); + /* return an empty list of cards */ + goto end; + } + else if (ret != ERROR_SUCCESS) + { + return SCARD_F_INTERNAL_ERROR; + } + + /* look at each subkey and try to find a matching card type */ + while ((ret = RegEnumKeyExW(db_key, i_subkey, card_subkey_name, &card_subkey_name_len_wchars, NULL, NULL, NULL, NULL)) == ERROR_SUCCESS) + { + TRACE("found key HKLM\\%S\\%S\n", SUBKEY_SMARTCARDS_DATABASE, card_subkey_name); + + if (atr == NULL || card_atr_matches(db_key, card_subkey_name, atr, atr_len)) + { + /* match found => append to the multi-string (or just increase the length if out_cards is null) */ + card_subkey_name_len_wchars++; /* +1 for the trailing \0, which is not included in the count by RegEnumKeyExW */ + new_len = res_len_wchars + card_subkey_name_len_wchars; + if (new_len < res_len_wchars) + { + /* overflow */ + return ERROR_NOT_ENOUGH_MEMORY; + } + if (out_cards != NULL) + { + if (*inout_cards_len < new_len) return SCARD_E_INSUFFICIENT_BUFFER; + lstrcpynW(&out_cards[res_len_wchars], card_subkey_name, card_subkey_name_len_wchars); + } + res_len_wchars = new_len; + } + + /* prepare for the next call of RegEnumKeyExW */ + i_subkey++; + card_subkey_name_len_wchars = 256; + } + + end: + /* terminate the multi-string */ + if (out_cards != NULL) + { + if (*inout_cards_len < res_len_wchars + 1) return SCARD_E_INSUFFICIENT_BUFFER; + out_cards[res_len_wchars] = '\0'; + } + *inout_cards_len = res_len_wchars + 1; + TRACE("returning %s, length %ld\n", debugstr_wn(out_cards, *inout_cards_len), *inout_cards_len); + return SCARD_S_SUCCESS; +} + BOOL WINAPI DllMain( HINSTANCE hinst, DWORD reason, void *reserved ) { switch (reason) diff --git a/dlls/winscard/winscard.spec b/dlls/winscard/winscard.spec index f5f9deac283..a50f39d9f04 100644 --- a/dlls/winscard/winscard.spec +++ b/dlls/winscard/winscard.spec @@ -35,7 +35,7 @@ @ stub SCardIntroduceReaderW @ stdcall SCardIsValidContext(long) @ stdcall SCardListCardsA(long ptr ptr long str ptr) -@ stub SCardListCardsW +@ stdcall SCardListCardsW(long ptr ptr long wstr ptr) @ stub SCardListInterfacesA @ stub SCardListInterfacesW @ stdcall SCardListReaderGroupsA(long ptr ptr) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10751