[PATCH v2 0/4] MR10751: winscard: Implement SCardGetCardTypeProviderName and SCardListCards (smartcard database)
These functions are necessary to make some smartcard-based programs work. They rely on the "SmartCard Database", which can be found in the windows registry, at \`HKLM\\SOFTWARE\\Microsoft\\Cryptography\\Calais\\SmartCards\`. I have added extensive unit tests and validated the implementation with a real application (an internal program based on Atos CardOS). As this is my first PR here, please don't hesitate to tell me if something's wrong! I was wondering if the database-related code should be moved to a dedicated file `winscard-database.c`? same for the tests?), let me know what you think. -- v2: winscard: Implement SCardGetCardTypeProviderNameA winscard: Implement SCardListCardsA winscard: Implement SCardListCardsW winscard: Implement SCardGetCardTypeProviderNameW https://gitlab.winehq.org/wine/wine/-/merge_requests/10751
From: Guillaume Raffin <theelectronwill@gmail.com> --- dlls/winscard/Makefile.in | 1 + dlls/winscard/tests/Makefile.in | 2 +- dlls/winscard/tests/winscard.c | 199 +++++++++++++++++++++++++++++++- dlls/winscard/winscard.c | 107 ++++++++++++++++- dlls/winscard/winscard.spec | 2 +- include/winscard.h | 11 +- 6 files changed, 312 insertions(+), 10 deletions(-) diff --git a/dlls/winscard/Makefile.in b/dlls/winscard/Makefile.in index c49b52626d6..5474a15e6ef 100644 --- a/dlls/winscard/Makefile.in +++ b/dlls/winscard/Makefile.in @@ -1,5 +1,6 @@ MODULE = winscard.dll IMPORTLIB = winscard +IMPORTS = kernelbase UNIXLIB = winscard.so UNIX_LIBS = $(PCSCLITE_LIBS) diff --git a/dlls/winscard/tests/Makefile.in b/dlls/winscard/tests/Makefile.in index efc5d6d6e25..faa68059404 100644 --- a/dlls/winscard/tests/Makefile.in +++ b/dlls/winscard/tests/Makefile.in @@ -1,5 +1,5 @@ TESTDLL = winscard.dll -IMPORTS = winscard +IMPORTS = winscard kernelbase SOURCES = \ winscard.c diff --git a/dlls/winscard/tests/winscard.c b/dlls/winscard/tests/winscard.c index 63a1d8f4faa..96f1f504f1f 100644 --- a/dlls/winscard/tests/winscard.c +++ b/dlls/winscard/tests/winscard.c @@ -18,10 +18,13 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -#include <stdio.h> +#include <wchar.h> #include <windows.h> #include <winscard.h> +#include <winnt.h> +#include <winreg.h> + #include "wine/test.h" static void test_SCardEstablishContext(void) @@ -254,7 +257,201 @@ static void test_SCardEstablishContext(void) ok( ret == ERROR_INVALID_HANDLE, "got %lx\n", ret ); } +static LSTATUS create_empty_key(HKEY hkey, LPCSTR name, PHKEY open_key) +{ + HKEY key; + LSTATUS ret; + + ret = RegOpenKeyExA(hkey, name, 0, KEY_READ|KEY_WRITE, &key); + if (ret == ERROR_SUCCESS) { + ret = RegDeleteTreeA(key, NULL); + if (ret != ERROR_SUCCESS) { + return ret; + } + } + ret = RegCreateKeyExA(hkey, name, 0, NULL, 0, KEY_READ|KEY_WRITE, NULL, open_key, NULL); + return ret; +} + +const char* SUBKEY_SMARTCARDS_DATABASE = "SOFTWARE\\Microsoft\\Cryptography\\Calais\\SmartCards"; +const char *CARD_NAME_1 = "PKI-test-1"; +const char *CARD_NAME_2 = "PKI-test-2"; +const WCHAR *CARD_NAME_1L = L"PKI-test-1"; +const WCHAR *CARD_NAME_2L = L"PKI-test-2"; +const BYTE CARD_ATR_1[] = {0x3b, 0xda, 0x13, 0xff, 0x81, 0x31, 0xfb, 0x46, 0x80, 0x12, + 0x39, 0x2f, 0x31, 0xc1, 0x73, 0xc6, 0x01, 0xc0, 0x3b}; +const BYTE CARD_ATR_2[] = {0x3b, 0xd2, 0x18, 0x00, 0x81, 0x31, 0xfe, 0x58, 0xC9, 0x03, 0x16}; + +static LONG populate_smartcard_db(void) +{ + HKEY key_db, key_card1, key_card2; + LONG ret; + + const char *crypto_provider_1 = "Microsoft Base Smart Card Crypto Provider"; + const char *crypto_provider_2 = "Test Crypto Provider"; + const char *storage_provider_1 = "Microsoft Smart Card Key Storage Provider"; + const char *storage_provider_2 = "Test Key Storage Provider"; + const char *dll_1 = "opensc-driver.dll"; + const char *dll_2 = "cardoscm64.dll"; + const BYTE atr_mask_2[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16}; + + ret = create_empty_key(HKEY_LOCAL_MACHINE, SUBKEY_SMARTCARDS_DATABASE, &key_db); + ok(ret == ERROR_SUCCESS, "failed to create an empty %s: %ld\n", SUBKEY_SMARTCARDS_DATABASE, ret); + + ret = RegCreateKeyExA(key_db, CARD_NAME_1, 0, NULL, 0, KEY_WRITE, NULL, &key_card1, NULL); + if (ret) return ret; + ret = RegCreateKeyExA(key_db, CARD_NAME_2, 0, NULL, 0, KEY_WRITE, NULL, &key_card2, NULL); + if (ret) return ret; + + ret = RegSetValueExA(key_card1, "Crypto Provider", 0, REG_SZ, (const BYTE*)crypto_provider_1, strlen(crypto_provider_1)); + if (ret) return ret; + ret = RegSetValueExA(key_card2, "Crypto Provider", 0, REG_SZ, (const BYTE*)crypto_provider_2, strlen(crypto_provider_2)); + if (ret) return ret; + + ret = RegSetValueExA(key_card1, "Smart Card Key Storage Provider", 0, REG_SZ, (const BYTE*)storage_provider_1, strlen(storage_provider_1)); + if (ret) return ret; + ret = RegSetValueExA(key_card2, "Smart Card Key Storage Provider", 0, REG_SZ, (const BYTE*)storage_provider_2, strlen(storage_provider_2)); + if (ret) return ret; + + ret = RegSetValueExA(key_card1, "80000001", 0, REG_SZ, (const BYTE*)dll_1, strlen(dll_1)); + if (ret) return ret; + ret = RegSetValueExA(key_card2, "80000001", 0, REG_SZ, (const BYTE*)dll_2, strlen(dll_2)); + if (ret) return ret; + + ret = RegSetValueExA(key_card1, "ATR", 0, REG_BINARY, (const BYTE*)CARD_ATR_1, sizeof(CARD_ATR_1)); + if (ret) return ret; + ret = RegSetValueExA(key_card2, "ATR", 0, REG_BINARY, (const BYTE*)CARD_ATR_2, sizeof(CARD_ATR_2)); + if (ret) return ret; + + /* don't create ATRMask for the first card, it will default to a mask full of ones */ + ret = RegSetValueExA(key_card2, "ATRMask", 0, REG_BINARY, (const BYTE*)atr_mask_2, sizeof(atr_mask_2)); + if (ret) return ret; + + ret = RegCloseKey(key_card1); + if (ret) return ret; + ret = RegCloseKey(key_card2); + if (ret) return ret; + ret = RegCloseKey(key_db); + if (ret) return ret; + + return 0; +} + +static void test_SCardGetCardTypeProviderName(void) +{ + LONG ret; + SCARDCONTEXT ctx = 0; /* SCardGetCardTypeProviderName does not need a context */ + DWORD len = 0; + WCHAR *provider; + + /* test basic error conditions */ + provider = malloc(sizeof(WCHAR)); + ret = SCardGetCardTypeProviderNameW(ctx, NULL, SCARD_PROVIDER_CARD_MODULE, provider, &len); + ok(ret == SCARD_E_INVALID_PARAMETER, "should fail when card_type is null\n"); + + ret = SCardGetCardTypeProviderNameW(ctx, CARD_NAME_1L, SCARD_PROVIDER_CARD_MODULE, provider, NULL); + ok(ret == SCARD_E_INVALID_PARAMETER, "should fail when length is null\n"); + free(provider); + + /* test get length and allocate */ + ret = SCardGetCardTypeProviderNameW(ctx, CARD_NAME_1L, SCARD_PROVIDER_CARD_MODULE, NULL, &len); + ok(len == 18, "invalid length: got %lu, expected %lu\n", len, 18L); + provider = calloc(len, sizeof(WCHAR)); + ret = SCardGetCardTypeProviderNameW(ctx, CARD_NAME_1L, SCARD_PROVIDER_CARD_MODULE, provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameW returned an error: %#lx\n", ret); + ok(len == 18, "invalid length: got %lu, expected %lu\n", len, 18L); + ok(wcscmp(provider, L"opensc-driver.dll") == 0, "bad output of SCardGetCardTypeProviderNameW: '%ls' (len %ld)\n", provider, len); + free(provider); + + /* test lookup with a pre-allocated space */ + len = 4; /* too small */ + provider = calloc(len, sizeof(WCHAR)); + ret = SCardGetCardTypeProviderNameW(ctx, CARD_NAME_1L, SCARD_PROVIDER_CARD_MODULE, provider, &len); + ok(ret == SCARD_E_INSUFFICIENT_BUFFER, "should have failed with SCARD_E_INSUFFICIENT_BUFFER but returned %#lx\n", ret); + ok(len == 4, "the length should not have been set, but was %ld\n", len); + ok(provider[0] == 0, "the provider should not have been set, but was %s\n", debugstr_w(provider)); + free(provider); + + len = 32; /* ok */ + provider = malloc(len * sizeof(WCHAR)); + for (int i = 0; i < 32; i++) { + provider[i] = 0xcafe; + } + ret = SCardGetCardTypeProviderNameW(ctx, CARD_NAME_1L, SCARD_PROVIDER_CARD_MODULE, provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameW returned an error: %#lx\n", ret); + ok(wcscmp(provider, L"opensc-driver.dll") == 0, "bad output of SCardGetCardTypeProviderNameW: '%ls' (len %ld)\n", provider, len); + for (int i = len; i < 32; i++) { + ok(provider[i] == 0xcafe, "memory corruption: provider[%d] = %u\n", i, provider[i]); + } + free(provider); + + len = 32; + provider = calloc(len, sizeof(WCHAR)); + ret = SCardGetCardTypeProviderNameW(ctx, CARD_NAME_2L, SCARD_PROVIDER_CARD_MODULE, provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameW returned an error: %#lx\n", ret); + ok(wcscmp(provider, L"cardoscm64.dll") == 0, "bad output of SCardGetCardTypeProviderNameW: '%ls' (len %ld)\n", provider, len); + free(provider); + + len = 32; + provider = calloc(len, sizeof(WCHAR)); + ret = SCardGetCardTypeProviderNameW(ctx, CARD_NAME_2L, SCARD_PROVIDER_KSP, provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameW returned an error: %#lx\n", ret); + ok(wcscmp(provider, L"Test Key Storage Provider") == 0, "bad output of SCardGetCardTypeProviderNameW: '%ls' (len %ld)\n", provider, len); + free(provider); + + len = 32; + provider = calloc(len, sizeof(WCHAR)); + ret = SCardGetCardTypeProviderNameW(ctx, CARD_NAME_2L, SCARD_PROVIDER_CSP, provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameW returned an error: %#lx\n", ret); + ok(wcscmp(provider, L"Test Crypto Provider") == 0, "bad output of SCardGetCardTypeProviderNameW: '%ls' (len %ld)\n", provider, len); + free(provider); + + /* test with a context */ + ret = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &ctx); + ok(ret == ERROR_SUCCESS, "failed to establish context: error %ld\n", ret); + if (ret) return; + + len = 32; + provider = calloc(len, sizeof(WCHAR)); + ret = SCardGetCardTypeProviderNameW(ctx, CARD_NAME_1L, SCARD_PROVIDER_CARD_MODULE, provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameW returned an error: %#lx\n", ret); + ok(wcscmp(provider, L"opensc-driver.dll") == 0, "bad output of SCardGetCardTypeProviderNameW: '%ls' (len %ld)\n", provider, len); + free(provider); + + /* test with auto alloc */ + len = SCARD_AUTOALLOCATE; + provider = NULL; + ret = SCardGetCardTypeProviderNameW(ctx, CARD_NAME_1L, SCARD_PROVIDER_CARD_MODULE, (LPWSTR)&provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameW returned an error: %#lx\n", ret); + ok(wcscmp(provider, L"opensc-driver.dll") == 0, "bad output of SCardGetCardTypeProviderNameW: '%ls' (len %ld)\n", provider, len); + ok(len == wcslen(provider)+1, "bad length from SCardGetCardTypeProviderNameW: got %lu, expected %Iu\n", len, wcslen(provider)+1); + SCardFreeMemory(ctx, provider); + + len = SCARD_AUTOALLOCATE; + provider = NULL; + ret = SCardGetCardTypeProviderNameW(ctx, CARD_NAME_1L, SCARD_PROVIDER_CSP, (LPWSTR)&provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameW returned an error: %#lx\n", ret); + ok(wcscmp(provider, L"Microsoft Base Smart Card Crypto Provider") == 0, "bad output of SCardGetCardTypeProviderNameW: '%ls' (len %ld)\n", provider, len); + ok(len == wcslen(provider)+1, "bad length from SCardGetCardTypeProviderNameW: got %lu, expected %Iu\n", len, wcslen(provider)+1); + SCardFreeMemory(ctx, provider); + + ret = SCardReleaseContext(ctx); + ok(ret == ERROR_SUCCESS, "failed to release context: error %ld\n", ret); +} + +static void test_smartcard_db(void) +{ + LONG ret; + + ret = populate_smartcard_db(); + ok(ret == ERROR_SUCCESS, "failed to populate database: error %ld\n", ret); + if (ret) return; + + test_SCardGetCardTypeProviderName(); +} + START_TEST(winscard) { test_SCardEstablishContext(); + test_smartcard_db(); } diff --git a/dlls/winscard/winscard.c b/dlls/winscard/winscard.c index 5a92b5110c6..ca826695f84 100644 --- a/dlls/winscard/winscard.c +++ b/dlls/winscard/winscard.c @@ -17,12 +17,14 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -#include <stdarg.h> #define WINSCARDAPI +#include "assert.h" +#include <stdarg.h> +#include "winerror.h" #include "windef.h" #include "winbase.h" +#include "winreg.h" #include "winscard.h" -#include "winternl.h" #include "wine/debug.h" #include "wine/unixlib.h" @@ -966,6 +968,107 @@ LONG WINAPI SCardFreeMemory( SCARDCONTEXT context, const void *mem ) return SCARD_S_SUCCESS; } +/* subkey of the db in HKLM */ +const WCHAR* SUBKEY_SMARTCARDS_DATABASE = L"SOFTWARE\\Microsoft\\Cryptography\\Calais\\SmartCards"; + +/******************************************************************************* + * SCardGetCardTypeProviderNameW (winscard.@) + * + * Return the name of the "provider" that handles the given type of card. + * Depending on the value of `provider_id`, this return either the name of the DLL, crypto provider, or key storage provider. + * + * PARAMS + * context [I] handle of scard context (can be null) + * card_type [I] name of the card type to search for + * provider_id [I] which provider to get (SCARD_PROVIDER_PRIMARY, SCARD_PROVIDER_CSP, SCARD_PROVIDER_KSP or SCARD_PROVIDER_CARD_MODULE) + * out_provider [O] provider name (can be null, even if msdn doesn't mention it) + * inout_provider_len [I/O] length of out_provider in characters, including '\0'. Set this to SCARD_AUTOALLOCATE to allocate out_provider automatically + */ +LONG WINAPI SCardGetCardTypeProviderNameW(SCARDCONTEXT context, const WCHAR *card_type, DWORD provider_id, WCHAR *out_provider, DWORD *inout_provider_len) +{ + struct handle *handle = (struct handle *)context; + HKEY key; + LONG ret; + DWORD value_len_wchars; + DWORD value_len_bytes = MAX_PATH * sizeof(WCHAR); + WCHAR value[MAX_PATH]; + BYTE **new_output; + + TRACE("%Ix, %s, %lu, %p, %p\n", context, debugstr_w(card_type), provider_id, out_provider, inout_provider_len); + + if (!card_type || !inout_provider_len) return SCARD_E_INVALID_PARAMETER; + + if (handle != NULL) + { + if (handle->magic != CONTEXT_MAGIC) + { + return ERROR_INVALID_HANDLE; + } + /* the handle should be used to restrict the visible cards, continue anyway */ + FIXME("card scopes not implemented\n"); + } + + ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, SUBKEY_SMARTCARDS_DATABASE, 0, KEY_READ, &key); + if (ret != ERROR_SUCCESS) + { + WARN("could not open the SmartCard database: error %ld\n", ret); + return SCARD_E_UNKNOWN_CARD; + } + + /* read the value that corresponds to provider_id */ + switch (provider_id) + { + case SCARD_PROVIDER_PRIMARY: + FIXME("SCARD_PROVIDER_PRIMARY not implemented\n"); + SetLastError(ERROR_NOT_SUPPORTED); + return SCARD_F_INTERNAL_ERROR; + case SCARD_PROVIDER_CSP: + ret = RegGetValueW(key, card_type, L"Crypto Provider", RRF_RT_REG_SZ, NULL, &value, &value_len_bytes); + break; + case SCARD_PROVIDER_KSP: + ret = RegGetValueW(key, card_type, L"Smart Card Key Storage Provider", RRF_RT_REG_SZ, NULL, &value, &value_len_bytes); + break; + case SCARD_PROVIDER_CARD_MODULE: + ret = RegGetValueW(key, card_type, L"80000001", RRF_RT_REG_SZ, NULL, &value, &value_len_bytes); + break; + default: + return SCARD_E_INVALID_PARAMETER; + } + RegCloseKey(key); + if (ret != ERROR_SUCCESS) return ret; + + /* note: the value includes a trailing zero thanks to RegGetValueW */ + assert(value_len_bytes % sizeof(WCHAR) == 0); + value_len_wchars = value_len_bytes / sizeof(WCHAR); + + /* store the value in out_provider (its length *in wchars* will go in inout_provider_len) */ + if (out_provider == NULL) + { + /* just return the length */ + } + else if (*inout_provider_len == SCARD_AUTOALLOCATE) + { + /* write the value to a new buffer */ + new_output = (BYTE**)out_provider; + *new_output = malloc(value_len_bytes); + if (*new_output == NULL) return ERROR_NOT_ENOUGH_MEMORY; + memcpy(*new_output, value, value_len_bytes); + TRACE("returning %s at %p, length %lu\n", debugstr_w((WCHAR*)*new_output), *new_output, value_len_wchars); + } + else if (*inout_provider_len < value_len_wchars) + { + return SCARD_E_INSUFFICIENT_BUFFER; + } + else + { + /* write the value to out_provider */ + memcpy(out_provider, value, value_len_bytes); + TRACE("returning %s, length %lu\n", debugstr_w(out_provider), value_len_wchars); + } + *inout_provider_len = value_len_wchars; + 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 34bf1c67950..f5f9deac283 100644 --- a/dlls/winscard/winscard.spec +++ b/dlls/winscard/winscard.spec @@ -22,7 +22,7 @@ @ stdcall SCardFreeMemory(long ptr) @ stdcall SCardGetAttrib(long long ptr ptr) @ stub SCardGetCardTypeProviderNameA -@ stub SCardGetCardTypeProviderNameW +@ stdcall SCardGetCardTypeProviderNameW(long wstr long wstr ptr) @ stub SCardGetProviderIdA @ stub SCardGetProviderIdW @ stdcall SCardGetStatusChangeA(long long ptr long) diff --git a/include/winscard.h b/include/winscard.h index 132972a4a9b..0c214450e1a 100644 --- a/include/winscard.h +++ b/include/winscard.h @@ -28,11 +28,6 @@ #define WINSCARDAPI DECLSPEC_IMPORT #endif -/* Valid scopes for contexts */ -#define SCARD_SCOPE_USER 0 -#define SCARD_SCOPE_TERMINAL 1 -#define SCARD_SCOPE_SYSTEM 2 - #ifndef _LPCBYTE_DEFINED #define _LPCBYTE_DEFINED typedef const BYTE *LPCBYTE; @@ -98,6 +93,12 @@ DECL_WINELIB_TYPE_AW(LPSCARD_READERSTATE) #define SCARD_UNPOWER_CARD 2 #define SCARD_EJECT_CARD 3 +/* constants for SCard{Get,Set}CardTypeProviderName */ +#define SCARD_PROVIDER_PRIMARY 1 +#define SCARD_PROVIDER_CSP 2 +#define SCARD_PROVIDER_KSP 3 +#define SCARD_PROVIDER_CARD_MODULE 0x80000001 + #ifdef __cplusplus extern "C" { #endif -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10751
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
From: Guillaume Raffin <theelectronwill@gmail.com> --- dlls/winscard/tests/winscard.c | 131 +++++++++++++++++++++++++++++++++ dlls/winscard/winscard.c | 87 ++++++++++++++++++++-- 2 files changed, 210 insertions(+), 8 deletions(-) diff --git a/dlls/winscard/tests/winscard.c b/dlls/winscard/tests/winscard.c index fa88186ca2d..f0b2231a146 100644 --- a/dlls/winscard/tests/winscard.c +++ b/dlls/winscard/tests/winscard.c @@ -568,6 +568,136 @@ static void test_SCardListCardsW(void) free(matching_cards); } +static void test_SCardListCardsA(void) +{ + LONG ret; + const CHAR *expected; + SCARDCONTEXT ctx = 0; /* SCardListCards does not need a context */ + BYTE atr_full[36]; + DWORD match_len = 0; + CHAR *matching_cards; + + /* test basic error conditions */ + ret = SCardListCardsA(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 = SCardListCardsA(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 of match */ + ret = SCardListCardsA(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"); + + /* allocate and call */ + matching_cards = calloc(match_len, sizeof(WCHAR)); + ret = SCardListCardsA(ctx, NULL, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list all cards: error %#lx\n", ret); + expected = "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 SCardListCardsA: %s\n", debugstr_an(matching_cards, match_len)); + free(matching_cards); + + /* test with pre-allocated */ + match_len = 32; + matching_cards = malloc(match_len); + memset(matching_cards, 0xca, match_len); + ret = SCardListCardsA(ctx, NULL, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list all cards: error %#lx\n", ret); + expected = "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 SCardListCardsA: %s\n", debugstr_an(matching_cards, match_len)); + for (int i = match_len+1; i < 32; i++) + { + ok((BYTE)matching_cards[i] == 0xca, "memory corruption\n"); + } + free(matching_cards); + + /* test with auto alloc */ + match_len = SCARD_AUTOALLOCATE; + matching_cards = NULL; + ret = SCardListCardsA(ctx, NULL, NULL, 0, (CHAR *)&matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list all cards: error %#lx\n", ret); + expected = "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 SCardListCardsA: %s\n", debugstr_an(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 = SCardListCardsA(ctx, CARD_ATR_1, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list card 1: error %#lx\n", ret); + expected = "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 SCardListCardsA: %s\n", debugstr_an(matching_cards, match_len)); + + /* test with ATRs that match exactly card 2 */ + match_len = 32; + ret = SCardListCardsA(ctx, CARD_ATR_2, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list card 2: error %#lx\n", ret); + expected = "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 SCardListCardsA: %s\n", debugstr_an(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 = SCardListCardsA(ctx, atr_full, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list card 2: error %#lx\n", ret); + expected = "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 SCardListCardsA: %s\n", debugstr_an(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 = SCardListCardsA(ctx, atr_full, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list card 2: error %#lx\n", ret); + expected = "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 SCardListCardsA: %s\n", debugstr_an(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 = SCardListCardsA(ctx, atr_full, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list no card: error %#lx\n", ret); + expected = ""; + 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 SCardListCardsA: %s\n", debugstr_an(matching_cards, match_len)); + + free(matching_cards); +} + static void test_smartcard_db(void) { LONG ret; @@ -578,6 +708,7 @@ static void test_smartcard_db(void) test_SCardGetCardTypeProviderName(); test_SCardListCardsW(); + test_SCardListCardsA(); } START_TEST(winscard) diff --git a/dlls/winscard/winscard.c b/dlls/winscard/winscard.c index f8c132ae7ec..908554c5b1a 100644 --- a/dlls/winscard/winscard.c +++ b/dlls/winscard/winscard.c @@ -188,14 +188,6 @@ LONG WINAPI SCardIsValidContext( SCARDCONTEXT context ) return ret; } -LONG WINAPI SCardListCardsA( SCARDCONTEXT context, const BYTE *atr, const GUID *interfaces, DWORD interface_count, - char *cards, DWORD *cards_len ) -{ - FIXME( "%Ix, %p, %p, %lu, %p, %p stub\n", context, atr, interfaces, interface_count, cards, cards_len ); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return SCARD_F_INTERNAL_ERROR; -} - LONG WINAPI SCardReleaseContext( SCARDCONTEXT context ) { struct handle *handle = (struct handle *)context; @@ -1334,6 +1326,85 @@ LONG WINAPI SCardListCardsW(SCARDCONTEXT context, const BYTE *atr, const GUID *i return SCARD_S_SUCCESS; } +/******************************************************************************* + * SCardListCardsA (winscard.@) + * + * Look up known cards in the smart card database. + * See SCardListCardsW + */ +LONG WINAPI SCardListCardsA(SCARDCONTEXT context, const BYTE *atr, const GUID *interfaces, DWORD interface_count, char *cards, DWORD *cards_len) +{ + WCHAR *cardsW; + DWORD cards_lenW; + LONG ret; + int converted_len; + + TRACE( "%Ix, %s, %p, %lu, %p, %p\n", context, debug_atr(atr), interfaces, interface_count, cards, cards_len ); + + if (!cards_len) return SCARD_E_INVALID_PARAMETER; + if (!cards) return SCardListCardsW(context, atr, interfaces, interface_count, NULL, cards_len); + + if (*cards_len == SCARD_AUTOALLOCATE) + { + char **new_output; + cards_lenW = SCARD_AUTOALLOCATE; + cardsW = NULL; + ret = SCardListCardsW(context, atr, interfaces, interface_count, (LPWSTR)&cardsW, &cards_lenW); + if (ret != ERROR_SUCCESS) return ret; + + /* determine the size that we need to allocate */ + converted_len = WideCharToMultiByte(CP_ACP, 0, cardsW, cards_lenW, NULL, 0, NULL, NULL); + if (converted_len == 0) + { + FIXME("can't convert %s to ANSI codepage\n", debugstr_w(cardsW)); + return SCARD_F_INTERNAL_ERROR; + } + new_output = (char**)cards; + *new_output = malloc(converted_len); + if (*new_output == NULL) return ERROR_NOT_ENOUGH_MEMORY; + + /* convert */ + WideCharToMultiByte(CP_ACP, 0, cardsW, cards_lenW, *new_output, converted_len, NULL, NULL); + *cards_len = converted_len; + SCardFreeMemory(context, cardsW); + + TRACE("returning %s at %p, length %lu\n", debugstr_an(*new_output, *cards_len), *new_output, *cards_len); + } + else + { + cards_lenW = *cards_len; + cardsW = calloc(cards_lenW, sizeof(WCHAR)); + + ret = SCardListCardsW(context, atr, interfaces, interface_count, cardsW, &cards_lenW); + if (ret != ERROR_SUCCESS) + { + free(cardsW); + return ret; + } + + /* determine the size after conversion and check it */ + converted_len = WideCharToMultiByte(CP_ACP, 0, cardsW, cards_lenW, NULL, 0, NULL, NULL); + if (converted_len == 0) + { + FIXME("can't convert %s to ANSI codepage\n", debugstr_w(cardsW)); + return SCARD_F_INTERNAL_ERROR; + } + + if (converted_len > *cards_len) + { + return SCARD_E_INSUFFICIENT_BUFFER; + } + + /* convert */ + WideCharToMultiByte(CP_ACP, 0, cardsW, cards_lenW, cards, converted_len, NULL, NULL); + *cards_len = converted_len; + free(cardsW); + + TRACE("returning %s, length %lu\n", debugstr_an(cards, *cards_len), *cards_len); + } + return ERROR_SUCCESS; +} + BOOL WINAPI DllMain( HINSTANCE hinst, DWORD reason, void *reserved ) { switch (reason) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10751
From: Guillaume Raffin <theelectronwill@gmail.com> --- dlls/winscard/tests/winscard.c | 99 +++++++++++++++++++++++++++++++++- dlls/winscard/winscard.c | 84 +++++++++++++++++++++++++++++ dlls/winscard/winscard.spec | 2 +- 3 files changed, 182 insertions(+), 3 deletions(-) diff --git a/dlls/winscard/tests/winscard.c b/dlls/winscard/tests/winscard.c index f0b2231a146..e4379c88ae9 100644 --- a/dlls/winscard/tests/winscard.c +++ b/dlls/winscard/tests/winscard.c @@ -337,7 +337,7 @@ static LONG populate_smartcard_db(void) return 0; } -static void test_SCardGetCardTypeProviderName(void) +static void test_SCardGetCardTypeProviderNameW(void) { LONG ret; SCARDCONTEXT ctx = 0; /* SCardGetCardTypeProviderName does not need a context */ @@ -439,6 +439,100 @@ static void test_SCardGetCardTypeProviderName(void) ok(ret == ERROR_SUCCESS, "failed to release context: error %ld\n", ret); } +static void test_SCardGetCardTypeProviderNameA(void) +{ + LONG ret; + SCARDCONTEXT ctx = 0; /* SCardGetCardTypeProviderName does not need a context */ + DWORD len = 0; + CHAR *provider; + + /* test basic error conditions */ + provider = malloc(1); + ret = SCardGetCardTypeProviderNameA(ctx, NULL, SCARD_PROVIDER_CARD_MODULE, provider, &len); + ok(ret == SCARD_E_INVALID_PARAMETER, "should fail when card_type is null\n"); + + ret = SCardGetCardTypeProviderNameA(ctx, CARD_NAME_1, SCARD_PROVIDER_CARD_MODULE, provider, NULL); + ok(ret == SCARD_E_INVALID_PARAMETER, "should fail when length is null\n"); + free(provider); + + ret = SCardGetCardTypeProviderNameA(ctx, CARD_NAME_1, SCARD_PROVIDER_CARD_MODULE, NULL, &len); + ok(ret == SCARD_E_INVALID_PARAMETER, "should fail when provider is null\n"); + + /* test lookup with a pre-allocated space */ + len = 4; /* too small */ + provider = calloc(len, 1); + ret = SCardGetCardTypeProviderNameA(ctx, CARD_NAME_1, SCARD_PROVIDER_CARD_MODULE, provider, &len); + ok(ret == SCARD_E_INSUFFICIENT_BUFFER, "should have failed with SCARD_E_INSUFFICIENT_BUFFER but returned %#lx\n", ret); + ok(len == 4, "the length should not have been set, but was %ld\n", len); + ok(provider[0] == 0, "the provider should not have been set, but was %s\n", debugstr_a(provider)); + free(provider); + + len = 32; /* ok */ + provider = malloc(len); + memset(provider, 0xca, len); + ret = SCardGetCardTypeProviderNameA(ctx, CARD_NAME_1, SCARD_PROVIDER_CARD_MODULE, provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameA returned an error: %#lx\n", ret); + ok(strcmp(provider, "opensc-driver.dll") == 0, "bad output of SCardGetCardTypeProviderNameA: '%s' (len %ld)\n", provider, len); + for (int i = len+1; i < 32; i++) + { + ok((BYTE)provider[i] == 0xca, "memory corruption\n"); + } + free(provider); + + len = 32; + provider = calloc(len, 1); + ret = SCardGetCardTypeProviderNameA(ctx, CARD_NAME_2, SCARD_PROVIDER_CARD_MODULE, provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameA returned an error: %#lx\n", ret); + ok(strcmp(provider, "cardoscm64.dll") == 0, "bad output of SCardGetCardTypeProviderNameA: '%s' (len %ld)\n", provider, len); + free(provider); + + len = 32; + provider = calloc(len, 1); + ret = SCardGetCardTypeProviderNameA(ctx, CARD_NAME_2, SCARD_PROVIDER_KSP, provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameA returned an error: %#lx\n", ret); + ok(strcmp(provider, "Test Key Storage Provider") == 0, "bad output of SCardGetCardTypeProviderNameA: '%s' (len %ld)\n", provider, len); + free(provider); + + len = 32; + provider = calloc(len, 1); + ret = SCardGetCardTypeProviderNameA(ctx, CARD_NAME_2, SCARD_PROVIDER_CSP, provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameA returned an error: %#lx\n", ret); + ok(strcmp(provider, "Test Crypto Provider") == 0, "bad output of SCardGetCardTypeProviderNameA: '%s' (len %ld)\n", provider, len); + free(provider); + + /* test with a context */ + ret = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &ctx); + ok(ret == ERROR_SUCCESS, "failed to establish context: error %ld\n", ret); + if (ret) return; + + len = 32; + provider = calloc(len, 1); + ret = SCardGetCardTypeProviderNameA(ctx, CARD_NAME_1, SCARD_PROVIDER_CARD_MODULE, provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameA returned an error: %#lx\n", ret); + ok(strcmp(provider, "opensc-driver.dll") == 0, "bad output of SCardGetCardTypeProviderNameA: '%s' (len %ld)\n", provider, len); + free(provider); + + /* test with auto alloc */ + len = SCARD_AUTOALLOCATE; + provider = NULL; + ret = SCardGetCardTypeProviderNameA(ctx, CARD_NAME_1, SCARD_PROVIDER_CARD_MODULE, (LPSTR)&provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameA returned an error: %#lx\n", ret); + ok(strcmp(provider, "opensc-driver.dll") == 0, "bad output of SCardGetCardTypeProviderNameA: '%s' (len %ld)\n", provider, len); + ok(len == strlen(provider)+1, "bad length from SCardGetCardTypeProviderNameA: got %lu, expected %Iu\n", len, strlen(provider)+1); + SCardFreeMemory(ctx, provider); + + len = SCARD_AUTOALLOCATE; + provider = NULL; + ret = SCardGetCardTypeProviderNameA(ctx, CARD_NAME_1, SCARD_PROVIDER_CSP, (LPSTR)&provider, &len); + ok(ret == ERROR_SUCCESS, "SCardGetCardTypeProviderNameA returned an error: %#lx\n", ret); + ok(strcmp(provider, "Microsoft Base Smart Card Crypto Provider") == 0, "bad output of SCardGetCardTypeProviderNameA: '%s' (len %ld)\n", provider, len); + ok(len == strlen(provider)+1, "bad length from SCardGetCardTypeProviderNameA: got %lu, expected %Iu\n", len, strlen(provider)+1); + SCardFreeMemory(ctx, provider); + + ret = SCardReleaseContext(ctx); + ok(ret == ERROR_SUCCESS, "failed to release context: error %ld\n", ret); +} + static void test_SCardListCardsW(void) { LONG ret; @@ -706,7 +800,8 @@ static void test_smartcard_db(void) ok(ret == ERROR_SUCCESS, "failed to populate database: error %ld\n", ret); if (ret) return; - test_SCardGetCardTypeProviderName(); + test_SCardGetCardTypeProviderNameW(); + test_SCardGetCardTypeProviderNameA(); test_SCardListCardsW(); test_SCardListCardsA(); } diff --git a/dlls/winscard/winscard.c b/dlls/winscard/winscard.c index 908554c5b1a..6b9c2331c8b 100644 --- a/dlls/winscard/winscard.c +++ b/dlls/winscard/winscard.c @@ -1064,6 +1064,90 @@ LONG WINAPI SCardGetCardTypeProviderNameW(SCARDCONTEXT context, const WCHAR *car return SCARD_S_SUCCESS; } +/******************************************************************************* + * SCardGetCardTypeProviderNameA (winscard.@) + * + * See SCardGetCardTypeProviderNameW + */ +LONG WINAPI SCardGetCardTypeProviderNameA(SCARDCONTEXT context, const CHAR *card_type, DWORD provider_id, char *out_provider, DWORD *inout_provider_len) +{ + LONG ret = SCARD_S_SUCCESS; + WCHAR *card_typeW; + WCHAR *providerW; + DWORD provider_lenW; + int converted_len; + + TRACE("%Ix, %s, %lu, %p, %p\n", context, debugstr_a(card_type), provider_id, out_provider, inout_provider_len); + + if (!card_type || !out_provider || !inout_provider_len) return SCARD_E_INVALID_PARAMETER; + if (ansi_to_utf16(card_type, &card_typeW) < 0) return ERROR_NOT_ENOUGH_MEMORY; + + if (*inout_provider_len == SCARD_AUTOALLOCATE) + { + char **new_output; + provider_lenW = SCARD_AUTOALLOCATE; + providerW = NULL; + ret = SCardGetCardTypeProviderNameW(context, card_typeW, provider_id, (LPWSTR)&providerW, &provider_lenW); + if (ret != ERROR_SUCCESS) goto end; + + /* determine the size that we need to allocate */ + converted_len = WideCharToMultiByte(CP_ACP, 0, providerW, provider_lenW, NULL, 0, NULL, NULL); + if (converted_len == 0) + { + FIXME("can't convert %s to ANSI codepage\n", debugstr_w(providerW)); + ret = SCARD_F_INTERNAL_ERROR; + goto end; + } + new_output = (char**)out_provider; + *new_output = malloc(converted_len); + if (*new_output == NULL) + { + ret = ERROR_NOT_ENOUGH_MEMORY; + goto end; + } + + /* convert */ + WideCharToMultiByte(CP_ACP, 0, providerW, provider_lenW, *new_output, converted_len, NULL, NULL); + *inout_provider_len = converted_len; + SCardFreeMemory(context, providerW); + } else { + provider_lenW = *inout_provider_len; + providerW = calloc(provider_lenW, sizeof(WCHAR)); + + ret = SCardGetCardTypeProviderNameW(context, card_typeW, provider_id, providerW, &provider_lenW); + if (ret != ERROR_SUCCESS) { + free(providerW); + goto end; + } + + /* determine the size after conversion and check it */ + converted_len = WideCharToMultiByte(CP_ACP, 0, providerW, provider_lenW, NULL, 0, NULL, NULL); + if (converted_len == 0) + { + FIXME("can't convert %s to ANSI codepage\n", debugstr_w(providerW)); + ret = SCARD_F_INTERNAL_ERROR; + goto end; + } + + if (converted_len > *inout_provider_len) + { + ret = SCARD_E_INSUFFICIENT_BUFFER; + goto end; + } + + /* convert */ + WideCharToMultiByte(CP_ACP, 0, providerW, provider_lenW, out_provider, converted_len, NULL, NULL); + *inout_provider_len = converted_len; + free(providerW); + } + + end: + free(card_typeW); + if (ret != SCARD_S_SUCCESS) TRACE("returning %#lx\n", ret); + else TRACE("returning %#lx: %s\n", ret, debugstr_an(out_provider, *inout_provider_len)); + return ret; +} + /** * Parses an ATR string and returns its length, or -1 if the ATR is invalid. * diff --git a/dlls/winscard/winscard.spec b/dlls/winscard/winscard.spec index a50f39d9f04..837c5a66657 100644 --- a/dlls/winscard/winscard.spec +++ b/dlls/winscard/winscard.spec @@ -21,7 +21,7 @@ @ stub SCardForgetReaderW @ stdcall SCardFreeMemory(long ptr) @ stdcall SCardGetAttrib(long long ptr ptr) -@ stub SCardGetCardTypeProviderNameA +@ stdcall SCardGetCardTypeProviderNameA(long str long str ptr) @ stdcall SCardGetCardTypeProviderNameW(long wstr long wstr ptr) @ stub SCardGetProviderIdA @ stub SCardGetProviderIdW -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10751
participants (2)
-
Guillaume Raffin -
Guillaume Raffin (@TheElectronWill)