mirror of
https://github.com/morgan9e/FreeRDP
synced 2026-04-14 00:14:11 +09:00
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).
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:<cert file>,<key file>
|
||||
|
||||
@@ -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" \
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ typedef enum
|
||||
NCRYPT_PROPERTY_CERTIFICATE,
|
||||
NCRYPT_PROPERTY_READER,
|
||||
NCRYPT_PROPERTY_SLOTID,
|
||||
NCRYPT_PROPERTY_NAME,
|
||||
NCRYPT_PROPERTY_UNKNOWN
|
||||
} NCryptKeyGetPropertyEnum;
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <winpr/library.h>
|
||||
#include <winpr/assert.h>
|
||||
#include <winpr/spec.h>
|
||||
#include <winpr/smartcard.h>
|
||||
#include <winpr/asn1.h>
|
||||
|
||||
#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;
|
||||
|
||||
Reference in New Issue
Block a user