[PATCH v6 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. -- v6: 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 | 254 +++++++++++++++++++++++++++++++- dlls/winscard/winscard.c | 232 ++++++++++++++++++++++++++++- dlls/winscard/winscard.spec | 2 +- include/winscard.h | 11 +- 6 files changed, 492 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..b67c55404ed 100644 --- a/dlls/winscard/tests/winscard.c +++ b/dlls/winscard/tests/winscard.c @@ -18,10 +18,14 @@ * 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 "minwindef.h" #include "wine/test.h" static void test_SCardEstablishContext(void) @@ -254,7 +258,255 @@ static void test_SCardEstablishContext(void) ok( ret == ERROR_INVALID_HANDLE, "got %lx\n", ret ); } +static const char *subkey_smartcards_database = "SOFTWARE\\Microsoft\\Cryptography\\Calais\\SmartCards"; +static const char *card_name_1 = "PKI-test-1"; +static const char *card_name_2 = "PKI-test-2"; +static const WCHAR *card_name_1W = L"PKI-test-1"; +static const WCHAR *card_name_2W = L"PKI-test-2"; +static const BYTE card_atr_1[] = { + 0x3b, 0xda, 0x13, 0xff, 0x81, 0x31, 0xfb, 0x46, 0x80, 0x12, 0x39, 0x2f, 0x31, 0xc1, 0x73, 0xc6, 0x01, 0xc0, 0x3b +}; +static 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 }; + + /* open the smartcard db, create it if needed */ + ret = RegCreateKeyExA(HKEY_LOCAL_MACHINE, subkey_smartcards_database, 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &key_db, NULL); + ok(ret == ERROR_SUCCESS, "failed to open HKLM\\%s: %ld\n", subkey_smartcards_database, ret); + if (ret) return ret; + + /* delete the test keys, in case they have not been deleted properly by the previous test run */ + RegDeleteTreeA(key_db, card_name_1); + RegDeleteTreeA(key_db, card_name_2); + + /* create the test keys */ + 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; + DWORD len = 0; + WCHAR *provider; + + /* test basic error conditions */ + provider = malloc(sizeof(WCHAR)); + ret = SCardGetCardTypeProviderNameW(0, NULL, SCARD_PROVIDER_CARD_MODULE, provider, &len); + ok(ret == SCARD_E_INVALID_PARAMETER, "should fail when card_type is null\n"); + + ret = SCardGetCardTypeProviderNameW(0, card_name_1W, 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(0, card_name_1W, 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(0, card_name_1W, 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(0, card_name_1W, 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(0, card_name_1W, 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(0, card_name_2W, 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(0, card_name_2W, 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(0, card_name_2W, 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_1W, 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_1W, 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(0, provider); + + len = SCARD_AUTOALLOCATE; + provider = NULL; + ret = SCardGetCardTypeProviderNameW(ctx, card_name_1W, 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(0, provider); + + ret = SCardReleaseContext(ctx); + ok(ret == ERROR_SUCCESS, "failed to release context: error %ld\n", ret); +} + +static void test_smartcard_db(void) +{ + LONG ret; + HKEY key; + + /* prepare the database */ + ret = populate_smartcard_db(); + ok(ret == ERROR_SUCCESS, "failed to populate database: error %ld\n", ret); + if (ret) return; + + /* run the tests */ + test_SCardGetCardTypeProviderName(); + + /* delete the test keys */ + ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, subkey_smartcards_database, 0, KEY_READ | KEY_WRITE, &key); + ok(ret == ERROR_SUCCESS, "failed to open the smartcard database: error %ld\n", ret); + ret = RegDeleteTreeA(key, card_name_1); + ok(ret == ERROR_SUCCESS, + "failed to delete HKLM\\%s\\%s: error %ld\n", + subkey_smartcards_database, + card_name_1, + ret); + ret = RegDeleteTreeA(key, card_name_2); + ok(ret == ERROR_SUCCESS, + "failed to delete HKLM\\%s\\%s: error %ld\n", + subkey_smartcards_database, + card_name_1, + ret); +} + START_TEST(winscard) { test_SCardEstablishContext(); + test_smartcard_db(); } diff --git a/dlls/winscard/winscard.c b/dlls/winscard/winscard.c index 5a92b5110c6..20a34529b08 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,232 @@ 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"; + +/* The ATR is between 2 and 33 bytes, but winscard pads it to 36 bytes (see struct SCARD_READERSTATEW) */ +#define ATR_N_BYTES 36 + +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 the requested type of provider (given by 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, including the trailing NUL char) */ + if (out_provider == NULL) + { + /* this is fine (even if it's not clear from msdn), 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; +} + +/** + * 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; +} + 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 | 134 +++++++++++++++++++++++++++++++++ dlls/winscard/winscard.c | 114 ++++++++++++++++++++++++++++ dlls/winscard/winscard.spec | 2 +- 3 files changed, 249 insertions(+), 1 deletion(-) diff --git a/dlls/winscard/tests/winscard.c b/dlls/winscard/tests/winscard.c index b67c55404ed..9f8a24a5537 100644 --- a/dlls/winscard/tests/winscard.c +++ b/dlls/winscard/tests/winscard.c @@ -475,6 +475,139 @@ 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; + BYTE atr_full[36]; + DWORD match_len = 0; + WCHAR *matching_cards; + + /* test basic error conditions */ + ret = SCardListCardsW(0, 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(0, 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(0, 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(0, 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(0, 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(0, 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(0, 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(0, 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(0, 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(0, 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(0, 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(0, 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; @@ -487,6 +620,7 @@ static void test_smartcard_db(void) /* run the tests */ test_SCardGetCardTypeProviderName(); + test_SCardListCardsW(); /* delete the test keys */ ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, subkey_smartcards_database, 0, KEY_READ | KEY_WRITE, &key); diff --git a/dlls/winscard/winscard.c b/dlls/winscard/winscard.c index 20a34529b08..cd2dc230191 100644 --- a/dlls/winscard/winscard.c +++ b/dlls/winscard/winscard.c @@ -1194,6 +1194,120 @@ static BOOL card_atr_matches(const HKEY db_key, const WCHAR *card_subkey_name, c return matches; } +/** Look up 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, including the NUL terminator */ + 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 | 133 +++++++++++++++++++++++++++++++++ dlls/winscard/winscard.c | 82 ++++++++++++++++++-- 2 files changed, 207 insertions(+), 8 deletions(-) diff --git a/dlls/winscard/tests/winscard.c b/dlls/winscard/tests/winscard.c index 9f8a24a5537..32097b3abfd 100644 --- a/dlls/winscard/tests/winscard.c +++ b/dlls/winscard/tests/winscard.c @@ -608,6 +608,138 @@ static void test_SCardListCardsW(void) free(matching_cards); } +static void test_SCardListCardsA(void) +{ + LONG ret; + const CHAR *expected; + BYTE atr_full[36]; + DWORD match_len = 0; + CHAR *matching_cards; + + /* test basic error conditions */ + ret = SCardListCardsA(0, 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(0, 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(0, 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(0, 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(0, 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(0, 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(0, 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(0, 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(0, 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(0, 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(0, 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(0, 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; @@ -621,6 +753,7 @@ static void test_smartcard_db(void) /* run the tests */ test_SCardGetCardTypeProviderName(); test_SCardListCardsW(); + test_SCardListCardsA(); /* delete the test keys */ ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, subkey_smartcards_database, 0, KEY_READ | KEY_WRITE, &key); diff --git a/dlls/winscard/winscard.c b/dlls/winscard/winscard.c index cd2dc230191..66924cd77fa 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; @@ -1308,6 +1300,80 @@ LONG WINAPI SCardListCardsW(SCARDCONTEXT context, const BYTE *atr, const GUID *i return SCARD_S_SUCCESS; } +/** Look up known cards in the smart card database. */ +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; /* includes trailing NUL */ + 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 | 125 ++++++++++++++++++++++++++++++++- dlls/winscard/winscard.c | 79 +++++++++++++++++++++ dlls/winscard/winscard.spec | 2 +- 3 files changed, 203 insertions(+), 3 deletions(-) diff --git a/dlls/winscard/tests/winscard.c b/dlls/winscard/tests/winscard.c index 32097b3abfd..54c34582dff 100644 --- a/dlls/winscard/tests/winscard.c +++ b/dlls/winscard/tests/winscard.c @@ -342,7 +342,7 @@ static LONG populate_smartcard_db(void) return 0; } -static void test_SCardGetCardTypeProviderName(void) +static void test_SCardGetCardTypeProviderNameW(void) { LONG ret; SCARDCONTEXT ctx; @@ -475,6 +475,126 @@ 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; @@ -751,7 +871,8 @@ static void test_smartcard_db(void) if (ret) return; /* run the tests */ - test_SCardGetCardTypeProviderName(); + test_SCardGetCardTypeProviderNameW(); + test_SCardGetCardTypeProviderNameA(); test_SCardListCardsW(); test_SCardListCardsA(); diff --git a/dlls/winscard/winscard.c b/dlls/winscard/winscard.c index 66924cd77fa..8569f86545f 100644 --- a/dlls/winscard/winscard.c +++ b/dlls/winscard/winscard.c @@ -1051,6 +1051,85 @@ LONG WINAPI SCardGetCardTypeProviderNameW(SCARDCONTEXT context, const WCHAR *car return SCARD_S_SUCCESS; } +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
On Sat May 16 21:47:53 2026 +0000, Guillaume Raffin wrote:
changed this line in [version 4 of the diff](/wine/wine/-/merge_requests/10751/diffs?diff_id=268180&start_sha=8c19dc39839cf01f1bbfa48b384e2b59ce03306a#5ad1f61589a4e0ae855d34da743c86d51ac32dd7_343_363) Good point.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10751#note_140245
On Sat May 16 21:47:53 2026 +0000, Guillaume Raffin wrote:
changed this line in [version 4 of the diff](/wine/wine/-/merge_requests/10751/diffs?diff_id=268180&start_sha=8c19dc39839cf01f1bbfa48b384e2b59ce03306a#5ad1f61589a4e0ae855d34da743c86d51ac32dd7_280_276) Done, thanks for the info!
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10751#note_140246
On Sat May 16 21:58:40 2026 +0000, Guillaume Raffin wrote:
changed this line in [version 6 of the diff](/wine/wine/-/merge_requests/10751/diffs?diff_id=268184&start_sha=8883722c404beb50b31d9fe3cc63fe6f69860b8a#5ad1f61589a4e0ae855d34da743c86d51ac32dd7_269_261) I've changed how the test keys are managed. I don't delete the smartcard db anymore, and `create_empty_key` no longer exists.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10751#note_140247
On Sat May 16 21:47:53 2026 +0000, Guillaume Raffin wrote:
changed this line in [version 4 of the diff](/wine/wine/-/merge_requests/10751/diffs?diff_id=268180&start_sha=8c19dc39839cf01f1bbfa48b384e2b59ce03306a#012f90c8c9d5df724d83b07a65cd5fec720907a8_973_969) Oh, okay. I've moved some details that I did not find in MSDN to comments in the body of the function.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10751#note_140248
Thanks for the comments! And sorry for my late reply. I've wrapped some long lines (I may have forgotten some), removed the extensive function docs, and reworked how the registry keys are created/deleted during the tests. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10751#note_140250
participants (2)
-
Guillaume Raffin -
Guillaume Raffin (@TheElectronWill)