From e847f159a63f7106fc5a939933eda58f5348cbb1 Mon Sep 17 00:00:00 2001 From: fifthdegree Date: Sun, 16 Oct 2022 11:36:20 -0400 Subject: [PATCH] Try to use the smartcard key name Windows uses Windows expects the containerName field in TSSmartCardCreds to be what it would use for a smartcard key's name. Try to accomodate that (at least for PIV and GIDS cards). --- client/common/client.c | 5 +- client/common/smartcard_cli.c | 4 +- include/freerdp/utils/smartcardlogon.h | 3 +- libfreerdp/core/nla.c | 4 +- libfreerdp/core/smartcardlogon.c | 57 +++++--- winpr/include/winpr/ncrypt.h | 1 + winpr/libwinpr/ncrypt/ncrypt.c | 4 + winpr/libwinpr/ncrypt/ncrypt.h | 1 + winpr/libwinpr/ncrypt/ncrypt_pkcs11.c | 174 +++++++++++++++++++++++++ 9 files changed, 230 insertions(+), 23 deletions(-) diff --git a/client/common/client.c b/client/common/client.c index b9276e6a1..f0836121c 100644 --- a/client/common/client.c +++ b/client/common/client.c @@ -528,14 +528,17 @@ BOOL client_cli_choose_smartcard(SmartcardCertInfo** cert_list, DWORD count, DWO { const SmartcardCertInfo* cert = cert_list[i]; char* reader = NULL; + char* container_name = NULL; ConvertFromUnicode(CP_UTF8, 0, cert->reader, -1, &reader, 0, NULL, NULL); + ConvertFromUnicode(CP_UTF8, 0, cert->containerName, -1, &container_name, 0, NULL, NULL); printf("[%" PRIu32 "] %s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s\n", - i, cert->containerName, reader, cert->userHint, cert->domainHint, cert->subject, + i, container_name, reader, cert->userHint, cert->domainHint, cert->subject, cert->issuer, cert->upn); free(reader); + free(container_name); } while (1) diff --git a/client/common/smartcard_cli.c b/client/common/smartcard_cli.c index 3a55e6b0d..67ccdb38f 100644 --- a/client/common/smartcard_cli.c +++ b/client/common/smartcard_cli.c @@ -50,7 +50,9 @@ BOOL freerdp_smartcard_list(const rdpSettings* settings) printf("\t* slotId: %" PRIu32 "\n", info->slotId); printf("\t* pkinitArgs: %s\n", info->pkinitArgs); #endif - printf("\t* containerName: %s\n", info->containerName); + if (WideCharToMultiByte(CP_UTF8, 0, info->containerName, -1, asciiStr, sizeof(asciiStr), + NULL, NULL) > 0) + printf("\t* containerName: %s\n", asciiStr); if (info->upn) printf("\t* UPN: %s\n", info->upn); } diff --git a/include/freerdp/utils/smartcardlogon.h b/include/freerdp/utils/smartcardlogon.h index eaa76a26f..e9f7f6ee8 100644 --- a/include/freerdp/utils/smartcardlogon.h +++ b/include/freerdp/utils/smartcardlogon.h @@ -31,7 +31,8 @@ typedef struct SmartcardCertInfo_st CryptoCert certificate; char* pkinitArgs; UINT32 slotId; - char* containerName; + char* keyName; + WCHAR* containerName; char* upn; char* userHint; char* domainHint; diff --git a/libfreerdp/core/nla.c b/libfreerdp/core/nla.c index e95d23247..a0832b558 100644 --- a/libfreerdp/core/nla.c +++ b/libfreerdp/core/nla.c @@ -251,8 +251,8 @@ static BOOL nla_adjust_settings_from_smartcard(rdpNla* nla) if (!settings->ContainerName && nla->smartcardCert->containerName) { - if (!freerdp_settings_set_string(settings, FreeRDP_ContainerName, - nla->smartcardCert->containerName)) + if (ConvertFromUnicode(CP_UTF8, 0, nla->smartcardCert->containerName, -1, + &settings->ContainerName, 0, NULL, NULL) < 0) { WLog_ERR(TAG, "unable to copy container name"); goto out; diff --git a/libfreerdp/core/smartcardlogon.c b/libfreerdp/core/smartcardlogon.c index f1a497c3a..5193f8f4a 100644 --- a/libfreerdp/core/smartcardlogon.c +++ b/libfreerdp/core/smartcardlogon.c @@ -90,6 +90,7 @@ void smartcardCertInfo_Free(SmartcardCertInfo* scCert) free(scCert->reader); crypto_cert_free(scCert->certificate); free(scCert->pkinitArgs); + free(scCert->keyName); free(scCert->containerName); free(scCert->upn); free(scCert->userHint); @@ -117,7 +118,7 @@ static BOOL treat_sc_cert(SmartcardCertInfo* scCert) scCert->upn = crypto_cert_get_upn(scCert->certificate->px509); if (!scCert->upn) { - WLog_DBG(TAG, "%s has no UPN, trying emailAddress", scCert->containerName); + WLog_DBG(TAG, "%s has no UPN, trying emailAddress", scCert->keyName); scCert->upn = crypto_cert_get_email(scCert->certificate->px509); } @@ -128,7 +129,7 @@ static BOOL treat_sc_cert(SmartcardCertInfo* scCert) if (!atPos) { - WLog_ERR(TAG, "invalid UPN, for key %s (no @)", scCert->containerName); + WLog_ERR(TAG, "invalid UPN, for key %s (no @)", scCert->keyName); return FALSE; } @@ -138,8 +139,7 @@ static BOOL treat_sc_cert(SmartcardCertInfo* scCert) if (!scCert->userHint || !scCert->domainHint) { - WLog_ERR(TAG, "error allocating userHint or domainHint, for key %s", - scCert->containerName); + WLog_ERR(TAG, "error allocating userHint or domainHint, for key %s", scCert->keyName); return FALSE; } @@ -217,11 +217,11 @@ static BOOL list_provider_keys(const rdpSettings* settings, NCRYPT_PROV_HANDLE p if (!cert) goto out; - if (ConvertFromUnicode(CP_UTF8, 0, keyName->pszName, -1, &cert->containerName, 0, NULL, - NULL) <= 0) + if (ConvertFromUnicode(CP_UTF8, 0, keyName->pszName, -1, &cert->keyName, 0, NULL, NULL) <= + 0) goto endofloop; - WLog_DBG(TAG, "opening key %s", cert->containerName); + WLog_DBG(TAG, "opening key %s", cert->keyName); status = NCryptOpenKey(provider, &phKey, keyName->pszName, keyName->dwLegacyKeySpec, dwFlags); @@ -244,7 +244,7 @@ static BOOL list_provider_keys(const rdpSettings* settings, NCRYPT_PROV_HANDLE p dwFlags); if (status != ERROR_SUCCESS) { - WLog_ERR(TAG, "unable to retrieve slotId for key %s, status=%s", cert->containerName, + WLog_ERR(TAG, "unable to retrieve slotId for key %s, status=%s", cert->keyName, winpr_NCryptSecurityStatusError(status)); goto endofloop; } @@ -255,15 +255,14 @@ static BOOL list_provider_keys(const rdpSettings* settings, NCRYPT_PROV_HANDLE p status = NCryptGetProperty(phKey, NCRYPT_READER_PROPERTY, NULL, 0, &cbOutput, dwFlags); if (status != ERROR_SUCCESS) { - WLog_DBG(TAG, "unable to retrieve reader's name length for key %s", - cert->containerName); + WLog_DBG(TAG, "unable to retrieve reader's name length for key %s", cert->keyName); goto endofloop; } cert->reader = calloc(1, cbOutput + 2); if (!cert->reader) { - WLog_ERR(TAG, "unable to allocate reader's name for key %s", cert->containerName); + WLog_ERR(TAG, "unable to allocate reader's name for key %s", cert->keyName); goto endofloop; } @@ -271,7 +270,30 @@ static BOOL list_provider_keys(const rdpSettings* settings, NCRYPT_PROV_HANDLE p &cbOutput, dwFlags); if (status != ERROR_SUCCESS) { - WLog_ERR(TAG, "unable to retrieve reader's name for key %s", cert->containerName); + WLog_ERR(TAG, "unable to retrieve reader's name for key %s", cert->keyName); + goto endofloop; + } + + /* ====== retrieve key container name ====== */ + /* When using PKCS11, this will try to return what Windows would use for the key's name */ + cbOutput = 0; + status = NCryptGetProperty(phKey, NCRYPT_NAME_PROPERTY, NULL, 0, &cbOutput, dwFlags); + if (status == ERROR_SUCCESS) + { + cert->containerName = calloc(1, cbOutput + sizeof(WCHAR)); + if (!cert->containerName) + { + WLog_ERR(TAG, "unable to allocate key container name for key %s", cert->keyName); + goto endofloop; + } + + status = NCryptGetProperty(phKey, NCRYPT_NAME_PROPERTY, (BYTE*)cert->containerName, + cbOutput, &cbOutput, dwFlags); + } + + if (status != ERROR_SUCCESS) + { + WLog_ERR(TAG, "unable to retrieve key container name for key %s", cert->keyName); goto endofloop; } @@ -290,7 +312,7 @@ static BOOL list_provider_keys(const rdpSettings* settings, NCRYPT_PROV_HANDLE p if (!certBytes) { WLog_ERR(TAG, "unable to allocate %" PRIu32 " certBytes for key %s", cbOutput, - cert->containerName); + cert->keyName); goto endofloop; } @@ -298,14 +320,14 @@ static BOOL list_provider_keys(const rdpSettings* settings, NCRYPT_PROV_HANDLE p &cbOutput, dwFlags); if (status != ERROR_SUCCESS) { - WLog_ERR(TAG, "unable to retrieve certificate for key %s", cert->containerName); + WLog_ERR(TAG, "unable to retrieve certificate for key %s", cert->keyName); goto endofloop; } if (!winpr_Digest(WINPR_MD_SHA1, certBytes, cbOutput, cert->sha1Hash, sizeof(cert->sha1Hash))) { - WLog_ERR(TAG, "unable to compute certificate sha1 for key %s", cert->containerName); + WLog_ERR(TAG, "unable to compute certificate sha1 for key %s", cert->keyName); goto endofloop; } @@ -313,7 +335,7 @@ static BOOL list_provider_keys(const rdpSettings* settings, NCRYPT_PROV_HANDLE p if (!cert->certificate) { - WLog_ERR(TAG, "unable to parse X509 certificate for key %s", cert->containerName); + WLog_ERR(TAG, "unable to parse X509 certificate for key %s", cert->keyName); goto endofloop; } @@ -554,8 +576,7 @@ static BOOL smartcard_sw_enumerateCerts(const rdpSettings* settings, SmartcardCe if (ConvertToUnicode(CP_UTF8, 0, "FreeRDP Emulator", -1, &cert->reader, 0) < 0) goto out_error; - cert->containerName = _strdup("Private Key 00"); - if (!cert->containerName) + if (ConvertToUnicode(CP_UTF8, 0, "Private Key 00", -1, &cert->containerName, 0) < 0) goto out_error; /* compute PKINIT args FILE:, diff --git a/winpr/include/winpr/ncrypt.h b/winpr/include/winpr/ncrypt.h index 4b7e6c015..f29074af2 100644 --- a/winpr/include/winpr/ncrypt.h +++ b/winpr/include/winpr/ncrypt.h @@ -114,6 +114,7 @@ typedef ULONG_PTR NCRYPT_KEY_HANDLE; "c\x00" \ "a\x00t\x00" \ "e\x00\x00" +#define NCRYPT_NAME_PROPERTY (const WCHAR*)"N\x00a\x00m\x00e\x00\x00" #define NCRYPT_UNIQUE_NAME_PROPERTY \ (const WCHAR*)"U\x00n\x00i\x00q\x00u\x00" \ "e\x00 \x00N\x00" \ diff --git a/winpr/libwinpr/ncrypt/ncrypt.c b/winpr/libwinpr/ncrypt/ncrypt.c index 103787551..aab601763 100644 --- a/winpr/libwinpr/ncrypt/ncrypt.c +++ b/winpr/libwinpr/ncrypt/ncrypt.c @@ -197,6 +197,10 @@ static NCryptKeyGetPropertyEnum propertyStringToEnum(LPCWSTR pszProperty) { return NCRYPT_PROPERTY_SLOTID; } + else if (_wcscmp(pszProperty, NCRYPT_NAME_PROPERTY) == 0) + { + return NCRYPT_PROPERTY_NAME; + } return NCRYPT_PROPERTY_UNKNOWN; } diff --git a/winpr/libwinpr/ncrypt/ncrypt.h b/winpr/libwinpr/ncrypt/ncrypt.h index a4ecad3cf..c1b1de6ee 100644 --- a/winpr/libwinpr/ncrypt/ncrypt.h +++ b/winpr/libwinpr/ncrypt/ncrypt.h @@ -45,6 +45,7 @@ typedef enum NCRYPT_PROPERTY_CERTIFICATE, NCRYPT_PROPERTY_READER, NCRYPT_PROPERTY_SLOTID, + NCRYPT_PROPERTY_NAME, NCRYPT_PROPERTY_UNKNOWN } NCryptKeyGetPropertyEnum; diff --git a/winpr/libwinpr/ncrypt/ncrypt_pkcs11.c b/winpr/libwinpr/ncrypt/ncrypt_pkcs11.c index b028ec4c3..fa74f371c 100644 --- a/winpr/libwinpr/ncrypt/ncrypt_pkcs11.c +++ b/winpr/libwinpr/ncrypt/ncrypt_pkcs11.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include "../log.h" #include "ncrypt.h" @@ -73,6 +75,24 @@ typedef struct CK_ULONG keyIndex; } P11EnumKeysState; +struct +{ + const char* label; + BYTE tag[3]; +} piv_cert_tags[] = { + { "Certificate for PIV Authentication", "\x5F\xC1\x05" }, + { "Certificate for Digital Signature", "\x5F\xC1\x0A" }, + { "Certificate for Key Management", "\x5F\xC1\x0B" }, + { "Certificate for Card Authentication", "\x5F\xC1\x01" }, +}; + +const BYTE APDU_PIV_SELECT_AID[] = { 0x00, 0xA4, 0x04, 0x00, 0x09, 0xA0, 0x00, 0x00, + 0x03, 0x08, 0x00, 0x00, 0x10, 0x00, 0x00 }; +const BYTE APDU_PIV_GET_CHUID[] = { + 0x00, 0xCB, 0x3F, 0xFF, 0x05, 0x5C, 0x03, 0x5F, 0xC1, 0x02, 0x00 +}; +#define PIV_CONTAINER_NAME_LEN 36 + static CK_OBJECT_CLASS object_class_public_key = CKO_PUBLIC_KEY; static CK_BBOOL object_verify = CK_TRUE; static CK_KEY_TYPE object_ktype_rsa = CKK_RSA; @@ -793,6 +813,119 @@ static SECURITY_STATUS NCryptP11EnumKeys(NCRYPT_PROV_HANDLE hProvider, LPCWSTR p return NTE_NO_MORE_ITEMS; } +static SECURITY_STATUS get_piv_container_name(NCryptP11KeyHandle* key, BYTE* piv_tag, BYTE* output, + size_t output_len) +{ + CK_SLOT_INFO slot_info = { 0 }; + CK_FUNCTION_LIST_PTR p11 = NULL; + WCHAR* reader = NULL; + SCARDCONTEXT context = 0; + SCARDHANDLE card = 0; + DWORD proto = 0; + const SCARD_IO_REQUEST* pci = NULL; + BYTE buf[258] = { 0 }; + char container_name[PIV_CONTAINER_NAME_LEN + 1] = { 0 }; + DWORD buf_len = 0; + SECURITY_STATUS ret = NTE_BAD_KEY; + WinPrAsn1Decoder dec = { 0 }; + WinPrAsn1Decoder dec2 = { 0 }; + size_t len = 0; + BYTE tag = 0; + BYTE* p = NULL; + wStream s = { 0 }; + + WINPR_ASSERT(key); + WINPR_ASSERT(piv_tag); + + WINPR_ASSERT(key->provider); + p11 = key->provider->p11; + WINPR_ASSERT(p11); + + /* Get the reader the card is in */ + WINPR_ASSERT(p11->C_GetSlotInfo); + if (p11->C_GetSlotInfo(key->slotId, &slot_info) != CKR_OK) + return NTE_BAD_KEY; + + fix_padded_string((char*)slot_info.slotDescription, sizeof(slot_info.slotDescription)); + if (ConvertToUnicode( + CP_UTF8, 0, (char*)slot_info.slotDescription, + strnlen((char*)slot_info.slotDescription, sizeof(slot_info.slotDescription)), &reader, + 0) < 0) + return NTE_NO_MEMORY; + + if (SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &context) != SCARD_S_SUCCESS) + return NTE_BAD_KEY; + + if (SCardConnectW(context, reader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_Tx, &card, &proto) != + SCARD_S_SUCCESS) + goto out; + pci = (proto == SCARD_PROTOCOL_T0) ? SCARD_PCI_T0 : SCARD_PCI_T1; + + buf_len = sizeof(buf); + if (SCardTransmit(card, pci, APDU_PIV_SELECT_AID, sizeof(APDU_PIV_SELECT_AID), NULL, buf, + &buf_len) != SCARD_S_SUCCESS) + goto out; + if ((buf[buf_len - 2] != 0x90 || buf[buf_len - 1] != 0) && buf[buf_len - 2] != 0x61) + goto out; + + buf_len = sizeof(buf); + if (SCardTransmit(card, pci, APDU_PIV_GET_CHUID, sizeof(APDU_PIV_GET_CHUID), NULL, buf, + &buf_len) != SCARD_S_SUCCESS) + goto out; + if ((buf[buf_len - 2] != 0x90 || buf[buf_len - 1] != 0) && buf[buf_len - 2] != 0x61) + goto out; + + /* Find the GUID field in the CHUID data object */ + WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_BER, buf, buf_len); + if (!WinPrAsn1DecReadTagAndLen(&dec, &tag, &len) || tag != 0x53) + goto out; + while (WinPrAsn1DecReadTagLenValue(&dec, &tag, &len, &dec2) && tag != 0x34) + ; + if (tag != 0x34 || len != 16) + goto out; + + s = WinPrAsn1DecGetStream(&dec2); + p = Stream_Buffer(&s); + + /* Construct the value Windows would use for a PIV key's container name */ + snprintf(container_name, PIV_CONTAINER_NAME_LEN + 1, + "%.2x%.2x%.2x%.2x-%.2x%.2x-%.2x%.2x-%.2x%.2x-%.2x%.2x%.2x%.2x%.2x%.2x", p[3], p[2], + p[1], p[0], p[5], p[4], p[7], p[6], p[8], p[9], p[10], p[11], p[12], piv_tag[0], + piv_tag[1], piv_tag[2]); + + /* And convert it to UTF-16 */ + if (MultiByteToWideChar(CP_UTF8, 0, container_name, PIV_CONTAINER_NAME_LEN, (WCHAR*)output, + output_len) == PIV_CONTAINER_NAME_LEN) + ret = ERROR_SUCCESS; + +out: + if (card) + SCardDisconnect(card, SCARD_LEAVE_CARD); + if (context) + SCardReleaseContext(context); + return ret; +} + +static SECURITY_STATUS check_for_piv_container_name(NCryptP11KeyHandle* key, BYTE* pbOutput, + DWORD cbOutput, DWORD* pcbResult, char* label, + size_t label_len) +{ + for (int i = 0; i < ARRAYSIZE(piv_cert_tags); i++) + { + if (strncmp(label, piv_cert_tags[i].label, label_len) == 0) + { + *pcbResult = PIV_CONTAINER_NAME_LEN * sizeof(WCHAR); + if (!pbOutput) + return ERROR_SUCCESS; + else if (cbOutput < PIV_CONTAINER_NAME_LEN * sizeof(WCHAR)) + return NTE_NO_MEMORY; + else + return get_piv_container_name(key, piv_cert_tags[i].tag, pbOutput, cbOutput); + } + } + return NTE_NOT_FOUND; +} + static SECURITY_STATUS NCryptP11KeyGetProperties(NCryptP11KeyHandle* keyHandle, NCryptKeyGetPropertyEnum property, PBYTE pbOutput, DWORD cbOutput, DWORD* pcbResult, DWORD dwFlags) @@ -820,6 +953,7 @@ static SECURITY_STATUS NCryptP11KeyGetProperties(NCryptP11KeyHandle* keyHandle, { case NCRYPT_PROPERTY_CERTIFICATE: + case NCRYPT_PROPERTY_NAME: break; case NCRYPT_PROPERTY_READER: { @@ -909,6 +1043,46 @@ static SECURITY_STATUS NCryptP11KeyGetProperties(NCryptP11KeyHandle* keyHandle, ret = ERROR_SUCCESS; break; } + case NCRYPT_PROPERTY_NAME: + { + CK_ATTRIBUTE attr = { CKA_LABEL, NULL, 0 }; + char* label = NULL; + + WINPR_ASSERT(provider->p11->C_GetAttributeValue); + rv = provider->p11->C_GetAttributeValue(session, objectHandle, &attr, 1); + if (rv == CKR_OK) + { + label = calloc(1, attr.ulValueLen); + if (!label) + { + ret = NTE_NO_MEMORY; + break; + } + + attr.pValue = label; + rv = provider->p11->C_GetAttributeValue(session, objectHandle, &attr, 1); + } + + if (rv == CKR_OK) + { + /* Check if we have a PIV card */ + ret = check_for_piv_container_name(keyHandle, pbOutput, cbOutput, pcbResult, label, + attr.ulValueLen); + + /* Otherwise, at least for GIDS cards the label will be the correct value */ + if (ret == NTE_NOT_FOUND) + { + *pcbResult = + MultiByteToWideChar(CP_UTF8, 0, label, attr.ulValueLen, (LPWSTR)pbOutput, + pbOutput ? cbOutput / sizeof(WCHAR) : 0) * + sizeof(WCHAR); + ret = ERROR_SUCCESS; + } + } + + free(label); + break; + } default: ret = NTE_NOT_SUPPORTED; break;