FreeRDP/winpr/libwinpr/ncrypt/ncrypt_pkcs11.c
David Fort f33c679552 smartcard: fix smartcard enumeration
There was a bug in smartcard listing that was leading to be unable to open a key
with slotId != 0. When any error or skipped item was happening during listing
the code was crashing.
2022-03-03 08:15:28 +01:00

969 lines
25 KiB
C

/**
* WinPR: Windows Portable Runtime
* NCrypt pkcs11 provider
*
* Copyright 2021 David Fort <contact@hardening-consulting.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef WITH_PKCS11
#include <pkcs11-helper-1.0/pkcs11.h>
#include <winpr/library.h>
#include <winpr/assert.h>
#include <winpr/spec.h>
#include "../log.h"
#include "ncrypt.h"
#define TAG WINPR_TAG("ncryptp11")
#define MAX_SLOTS 64
#define MAX_PRIVATE_KEYS 64
#define MAX_KEYS_PER_SLOT 64
/** @brief ncrypt provider handle */
typedef struct
{
NCryptBaseProvider baseProvider;
HANDLE library;
CK_FUNCTION_LIST_PTR p11;
} NCryptP11ProviderHandle;
/** @brief a handle returned by NCryptOpenKey */
typedef struct
{
NCryptBaseHandle base;
NCryptP11ProviderHandle* provider;
CK_SLOT_ID slotId;
CK_BYTE keyCertId[64];
CK_ULONG keyCertIdLen;
} NCryptP11KeyHandle;
/** @brief */
typedef struct
{
CK_SLOT_ID slotId;
CK_SLOT_INFO slotInfo;
CK_KEY_TYPE keyType;
CK_CHAR keyLabel[256];
CK_ULONG idLen;
CK_BYTE id[64];
} NCryptPrivateKeyEnum;
/** @brief */
typedef struct
{
CK_ULONG nslots;
CK_SLOT_ID slots[MAX_SLOTS];
CK_ULONG nprivateKeys;
NCryptPrivateKeyEnum privateKeys[MAX_PRIVATE_KEYS];
CK_ULONG privateKeyIndex;
} P11EnumKeysState;
static CK_OBJECT_CLASS object_class_private_key = CKO_PRIVATE_KEY;
static CK_BBOOL object_sign = CK_TRUE;
static CK_KEY_TYPE object_ktype_rsa = CKK_RSA;
static CK_ATTRIBUTE private_key_filter[] = {
{ CKA_CLASS, &object_class_private_key, sizeof(object_class_private_key) },
{ CKA_SIGN, &object_sign, sizeof(object_sign) },
{ CKA_KEY_TYPE, &object_ktype_rsa, sizeof(object_ktype_rsa) }
};
static SECURITY_STATUS NCryptP11StorageProvider_dtor(NCRYPT_HANDLE handle)
{
NCryptP11ProviderHandle* provider = (NCryptP11ProviderHandle*)handle;
CK_RV rv;
WINPR_ASSERT(provider);
rv = provider->p11->C_Finalize(NULL);
if (rv != CKR_OK)
{
}
if (provider->library)
FreeLibrary(provider->library);
return winpr_NCryptDefault_dtor(handle);
}
static void fix_padded_string(char *str, size_t maxlen)
{
char* ptr = str + maxlen - 1;
while (ptr > str && *ptr == ' ')
ptr--;
ptr++;
*ptr = 0;
}
static BOOL attributes_have_unallocated_buffers(CK_ATTRIBUTE_PTR attributes, CK_ULONG count)
{
CK_ULONG i;
for (i = 0; i < count; i++)
{
if (!attributes[i].pValue && (attributes[i].ulValueLen != CK_UNAVAILABLE_INFORMATION))
return TRUE;
}
return FALSE;
}
static BOOL attribute_allocate_attribute_array(CK_ATTRIBUTE_PTR attribute)
{
attribute->pValue = calloc(attribute->ulValueLen, sizeof(void*));
return !!attribute->pValue;
}
static BOOL attribute_allocate_ulong_array(CK_ATTRIBUTE_PTR attribute)
{
attribute->pValue = calloc(attribute->ulValueLen, sizeof(CK_ULONG));
return !!attribute->pValue;
}
static BOOL attribute_allocate_buffer(CK_ATTRIBUTE_PTR attribute)
{
attribute->pValue = calloc(attribute->ulValueLen, 1);
return !!attribute->pValue;
}
static BOOL attributes_allocate_buffers(CK_ATTRIBUTE_PTR attributes, CK_ULONG count)
{
CK_ULONG i;
BOOL ret = TRUE;
for (i = 0; i < count; i++)
{
if (attributes[i].pValue || (attributes[i].ulValueLen == CK_UNAVAILABLE_INFORMATION))
continue;
switch (attributes[i].type)
{
case CKA_WRAP_TEMPLATE:
case CKA_UNWRAP_TEMPLATE:
ret &= attribute_allocate_attribute_array(&attributes[i]);
break;
case CKA_ALLOWED_MECHANISMS:
ret &= attribute_allocate_ulong_array(&attributes[i]);
break;
default:
ret &= attribute_allocate_buffer(&attributes[i]);
break;
}
}
return ret;
}
static CK_RV object_load_attributes(NCryptP11ProviderHandle* provider, CK_SESSION_HANDLE session,
CK_OBJECT_HANDLE object, CK_ATTRIBUTE_PTR attributes,
CK_ULONG count)
{
CK_RV rv;
WINPR_ASSERT(provider);
WINPR_ASSERT(provider->p11);
WINPR_ASSERT(provider->p11->C_GetAttributeValue);
rv = provider->p11->C_GetAttributeValue(session, object, attributes, count);
switch (rv)
{
case CKR_OK:
if (!attributes_have_unallocated_buffers(attributes, count))
return rv;
/* fallthrought */
case CKR_ATTRIBUTE_SENSITIVE:
case CKR_ATTRIBUTE_TYPE_INVALID:
case CKR_BUFFER_TOO_SMALL:
/* attributes need some buffers for the result value */
if (!attributes_allocate_buffers(attributes, count))
return CKR_HOST_MEMORY;
rv = provider->p11->C_GetAttributeValue(session, object, attributes, count);
break;
default:
return rv;
}
switch (rv)
{
case CKR_ATTRIBUTE_SENSITIVE:
case CKR_ATTRIBUTE_TYPE_INVALID:
case CKR_BUFFER_TOO_SMALL:
WLog_ERR(TAG, "C_GetAttributeValue return %d even after buffer allocation", rv);
break;
}
return rv;
}
static SECURITY_STATUS collect_private_keys(NCryptP11ProviderHandle* provider,
P11EnumKeysState* state)
{
CK_RV rv;
CK_SESSION_HANDLE session = (CK_SESSION_HANDLE)NULL;
CK_ULONG i, j, nslotObjects;
CK_OBJECT_HANDLE slotObjects[MAX_KEYS_PER_SLOT] = { 0 };
const char* step = NULL;
CK_FUNCTION_LIST_PTR p11;
WINPR_ASSERT(provider);
p11 = provider->p11;
WINPR_ASSERT(p11);
state->nprivateKeys = 0;
for (i = 0; i < state->nslots; i++)
{
CK_SLOT_INFO slotInfo;
WINPR_ASSERT(p11->C_GetSlotInfo);
rv = p11->C_GetSlotInfo(state->slots[i], &slotInfo);
if (rv != CKR_OK)
{
WLog_ERR(TAG, "unable to retrieve information for slot #%d(%d)", i, state->slots[i]);
continue;
}
WINPR_ASSERT(p11->C_OpenSession);
rv = p11->C_OpenSession(state->slots[i], CKF_SERIAL_SESSION, NULL, NULL, &session);
if (rv != CKR_OK)
{
// TODO: shall it be fatal ?
WLog_ERR(TAG, "unable to openSession for slot #%d(%d)", i, state->slots[i]);
continue;
}
WINPR_ASSERT(p11->C_FindObjectsInit);
rv = p11->C_FindObjectsInit(session, private_key_filter, ARRAYSIZE(private_key_filter));
if (rv != CKR_OK)
{
// TODO: shall it be fatal ?
WLog_ERR(TAG, "unable to initiate search for slot #%d(%d)", i, state->slots[i]);
step = "C_FindObjectsInit";
goto cleanup_FindObjectsInit;
}
WINPR_ASSERT(p11->C_FindObjects);
rv = p11->C_FindObjects(session, &slotObjects[0], ARRAYSIZE(slotObjects), &nslotObjects);
if (rv != CKR_OK)
{
WLog_ERR(TAG, "unable to findObjects for slot #%d(%d)", i, state->slots[i]);
step = "C_FindObjects";
goto cleanup_FindObjects;
}
for (j = 0; j < nslotObjects; j++)
{
NCryptPrivateKeyEnum* privKey = &state->privateKeys[state->nprivateKeys];
CK_OBJECT_CLASS dataClass = CKO_PRIVATE_KEY;
CK_ATTRIBUTE key_or_certAttrs[] = {
{ CKA_ID, &privKey->id, sizeof(privKey->id) },
{ CKA_CLASS, &dataClass, sizeof(dataClass) },
{ CKA_LABEL, &privKey->keyLabel, sizeof(privKey->keyLabel) },
{ CKA_KEY_TYPE, &privKey->keyType, sizeof(privKey->keyType) }
};
rv = object_load_attributes(provider, session, slotObjects[j], key_or_certAttrs,
ARRAYSIZE(key_or_certAttrs));
if (rv != CKR_OK)
{
WLog_ERR(TAG, "error getting attributes");
continue;
}
privKey->idLen = key_or_certAttrs[0].ulValueLen;
privKey->slotId = state->slots[i];
privKey->slotInfo = slotInfo;
state->nprivateKeys++;
}
cleanup_FindObjects:
WINPR_ASSERT(p11->C_FindObjectsFinal);
rv = p11->C_FindObjectsFinal(session);
if (rv != CKR_OK)
{
WLog_ERR(TAG, "error during C_FindObjectsFinal for slot #%d(%d) (errorStep=%s)", i,
state->slots[i], step);
}
cleanup_FindObjectsInit:
WINPR_ASSERT(p11->C_CloseSession);
rv = p11->C_CloseSession(session);
if (rv != CKR_OK)
{
WLog_ERR(TAG, "error closing session for slot #%d(%d) (errorStep=%s)", i,
state->slots[i], step);
}
}
return ERROR_SUCCESS;
}
static BOOL convertKeyType(CK_KEY_TYPE k, LPWSTR dest, DWORD len, DWORD* outlen)
{
DWORD retLen;
const WCHAR* r = NULL;
#define ALGO_CASE(V, S) \
case V: \
r = S; \
break
switch (k)
{
ALGO_CASE(CKK_RSA, BCRYPT_RSA_ALGORITHM);
ALGO_CASE(CKK_DSA, BCRYPT_DSA_ALGORITHM);
ALGO_CASE(CKK_DH, BCRYPT_DH_ALGORITHM);
ALGO_CASE(CKK_ECDSA, BCRYPT_ECDSA_ALGORITHM);
ALGO_CASE(CKK_RC2, BCRYPT_RC2_ALGORITHM);
ALGO_CASE(CKK_RC4, BCRYPT_RC4_ALGORITHM);
ALGO_CASE(CKK_DES, BCRYPT_DES_ALGORITHM);
ALGO_CASE(CKK_DES3, BCRYPT_3DES_ALGORITHM);
case CKK_DES2:
case CKK_X9_42_DH:
case CKK_KEA:
case CKK_GENERIC_SECRET:
case CKK_CAST:
case CKK_CAST3:
case CKK_CAST128:
case CKK_RC5:
case CKK_IDEA:
case CKK_SKIPJACK:
case CKK_BATON:
case CKK_JUNIPER:
case CKK_CDMF:
case CKK_AES:
case CKK_BLOWFISH:
case CKK_TWOFISH:
default:
break;
}
#undef ALGO_CASE
retLen = _wcslen(r);
if (outlen)
*outlen = retLen;
if (!r)
{
if (dest && len > 0)
dest[0] = 0;
return FALSE;
}
else
{
if (retLen + 1 < len)
{
WLog_ERR(TAG, "target buffer is too small for algo name");
return FALSE;
}
if (dest)
{
memcpy(dest, r, retLen * 2);
dest[retLen] = 0;
}
}
return TRUE;
}
static void wprintKeyName(LPWSTR str, CK_SLOT_ID slotId, CK_BYTE* id, CK_ULONG idLen)
{
char asciiName[128];
char* ptr = asciiName;
const CK_BYTE* bytePtr;
CK_ULONG i;
*ptr = '\\'; ptr++;
bytePtr = ((CK_BYTE*)&slotId);
for (i = 0; i < sizeof(slotId); i++, bytePtr++, ptr += 2)
snprintf(ptr, 3, "%.2x", *bytePtr);
*ptr = '\\'; ptr++;
for (i = 0; i < idLen; i++, id++, ptr += 2)
snprintf(ptr, 3, "%.2x", *id);
MultiByteToWideChar(CP_UTF8, 0, asciiName, strlen(asciiName), str, (strlen(asciiName) + 1));
}
static size_t parseHex(const char* str, const char* end, CK_BYTE* target)
{
int ret = 0;
for (ret = 0; str != end && *str; str++, ret++, target++)
{
CK_BYTE v = 0;
if (*str <= '9' && *str >= '0')
{
v = (*str - '0');
}
else if (*str <= 'f' && *str >= 'a')
{
v = (10 + *str - 'a');
}
else if (*str <= 'F' && *str >= 'A')
{
v |= (10 + *str - 'A');
}
else
{
return 0;
}
v <<= 4;
str++;
if (!*str || str == end)
return 0;
if (*str <= '9' && *str >= '0')
{
v |= (*str - '0');
}
else if (*str <= 'f' && *str >= 'a')
{
v |= (10 + *str - 'a');
}
else if (*str <= 'F' && *str >= 'A')
{
v |= (10 + *str - 'A');
}
else
{
return 0;
}
*target = v;
}
return ret;
}
static SECURITY_STATUS parseKeyName(LPCWSTR pszKeyName, CK_SLOT_ID* slotId, CK_BYTE* id,
CK_ULONG* idLen)
{
char asciiKeyName[128] = { 0 };
char* pos;
if (WideCharToMultiByte(CP_UTF8, 0, pszKeyName, _wcslen(pszKeyName) + 1, asciiKeyName,
sizeof(asciiKeyName) - 1, "?", FALSE) <= 0)
return NTE_BAD_KEY;
if (*asciiKeyName != '\\')
return NTE_BAD_KEY;
pos = strchr(&asciiKeyName[1], '\\');
if (!pos)
return NTE_BAD_KEY;
if (pos - &asciiKeyName[1] > sizeof(CK_SLOT_ID) * 2)
return NTE_BAD_KEY;
*slotId = (CK_SLOT_ID)0;
if (parseHex(&asciiKeyName[1], pos, (CK_BYTE*)slotId) != sizeof(CK_SLOT_ID))
return NTE_BAD_KEY;
*idLen = parseHex(pos + 1, NULL, id);
if (!*idLen)
return NTE_BAD_KEY;
return ERROR_SUCCESS;
}
static SECURITY_STATUS NCryptP11EnumKeys(NCRYPT_PROV_HANDLE hProvider, LPCWSTR pszScope,
NCryptKeyName** ppKeyName, PVOID* ppEnumState,
DWORD dwFlags)
{
SECURITY_STATUS ret;
NCryptP11ProviderHandle* provider = (NCryptP11ProviderHandle*)hProvider;
P11EnumKeysState* state = (P11EnumKeysState*)*ppEnumState;
CK_RV rv = { 0 };
CK_SLOT_ID currentSlot = { 0 };
CK_SESSION_HANDLE currentSession = (CK_SESSION_HANDLE)NULL;
char slotFilterBuffer[65] = { 0 };
char* slotFilter = NULL;
size_t slotFilterLen = 0;
ret = checkNCryptHandle((NCRYPT_HANDLE)hProvider, WINPR_NCRYPT_PROVIDER);
if (ret != ERROR_SUCCESS)
return ret;
if (pszScope)
{
/*
* check whether pszScope is of the form \\.\<reader name>\ for filtering by
* card reader
*/
char asciiScope[128 + 6] = { 0 };
int asciiScopeLen;
if (WideCharToMultiByte(CP_UTF8, 0, pszScope, _wcslen(pszScope) + 1, asciiScope,
sizeof(asciiScope) - 1, "?", NULL) <= 0)
return NTE_INVALID_PARAMETER;
if (strstr(asciiScope, "\\\\.\\") != asciiScope)
return NTE_INVALID_PARAMETER;
asciiScopeLen = strnlen(asciiScope, sizeof(asciiScope));
if (asciiScope[asciiScopeLen - 1] != '\\')
return NTE_INVALID_PARAMETER;
asciiScope[asciiScopeLen - 1] = 0;
strncpy(slotFilterBuffer, &asciiScope[4], sizeof(slotFilterBuffer));
slotFilter = slotFilterBuffer;
slotFilterLen = asciiScopeLen - 5;
}
if (!state)
{
state = (P11EnumKeysState*)calloc(1, sizeof(*state));
if (!state)
return NTE_NO_MEMORY;
WINPR_ASSERT(provider->p11->C_GetSlotList);
rv = provider->p11->C_GetSlotList(CK_TRUE, NULL, &state->nslots);
if (rv != CKR_OK)
{
/* TODO: perhaps convert rv to NTE_*** errors */
return NTE_FAIL;
}
if (state->nslots > MAX_SLOTS)
state->nslots = MAX_SLOTS;
rv = provider->p11->C_GetSlotList(CK_TRUE, state->slots, &state->nslots);
if (rv != CKR_OK)
{
free(state);
/* TODO: perhaps convert rv to NTE_*** errors */
return NTE_FAIL;
}
ret = collect_private_keys(provider, state);
if (ret != ERROR_SUCCESS)
{
free(state);
return ret;
}
*ppEnumState = state;
}
for (; state->privateKeyIndex < state->nprivateKeys; state->privateKeyIndex++)
{
NCryptKeyName* keyName = NULL;
NCryptPrivateKeyEnum* privKey = &state->privateKeys[state->privateKeyIndex];
CK_OBJECT_CLASS oclass = CKO_CERTIFICATE;
CK_CERTIFICATE_TYPE ctype = CKC_X_509;
CK_ATTRIBUTE certificateFilter[] = { { CKA_CLASS, &oclass, sizeof(oclass) },
{ CKA_CERTIFICATE_TYPE, &ctype, sizeof(ctype) },
{ CKA_ID, privKey->id, privKey->idLen } };
CK_ULONG ncertObjects;
CK_OBJECT_HANDLE certObject;
/* check the reader filter if any */
if (slotFilter && memcmp(privKey->slotInfo.slotDescription, slotFilter, slotFilterLen) != 0)
continue;
if (!currentSession || (currentSlot != privKey->slotId))
{
/* if the current session doesn't match the current private key's slot, open a new one
*/
if (currentSession)
{
WINPR_ASSERT(provider->p11->C_CloseSession);
rv = provider->p11->C_CloseSession(currentSession);
currentSession = (CK_SESSION_HANDLE)NULL;
}
WINPR_ASSERT(provider->p11->C_OpenSession);
rv = provider->p11->C_OpenSession(privKey->slotId, CKF_SERIAL_SESSION, NULL, NULL,
&currentSession);
if (rv != CKR_OK)
{
WLog_ERR(TAG, "unable to openSession for slot %d", privKey->slotId);
continue;
}
currentSlot = privKey->slotId;
}
/* look if we can find a certificate that matches the private key's id */
WINPR_ASSERT(provider->p11->C_FindObjectsInit);
rv = provider->p11->C_FindObjectsInit(currentSession, certificateFilter,
ARRAYSIZE(certificateFilter));
if (rv != CKR_OK)
{
WLog_ERR(TAG, "unable to initiate search for slot %d", privKey->slotId);
continue;
}
WINPR_ASSERT(provider->p11->C_FindObjects);
rv = provider->p11->C_FindObjects(currentSession, &certObject, 1, &ncertObjects);
if (rv != CKR_OK)
{
WLog_ERR(TAG, "unable to findObjects for slot %d", currentSlot);
goto cleanup_FindObjects;
}
if (ncertObjects)
{
/* sizeof keyName struct + "\<slotId>\<certId>" + keyName->pszAlgid */
DWORD algoSz;
size_t KEYNAME_SZ = (1 + (sizeof(privKey->slotId) * 2) /*slotId*/ + 1 + (privKey->idLen * 2) + 1) * 2;
convertKeyType(privKey->keyType, NULL, 0, &algoSz);
KEYNAME_SZ += (algoSz + 1) * 2;
keyName = calloc(1, sizeof(*keyName) + KEYNAME_SZ);
if (!keyName)
{
WLog_ERR(TAG, "unable to allocate keyName");
goto cleanup_FindObjects;
}
keyName->dwLegacyKeySpec = AT_KEYEXCHANGE | AT_SIGNATURE;
keyName->dwFlags = NCRYPT_MACHINE_KEY_FLAG;
keyName->pszName = (LPWSTR)(keyName + 1);
wprintKeyName(keyName->pszName, privKey->slotId, privKey->id, privKey->idLen);
keyName->pszAlgid = keyName->pszName + _wcslen(keyName->pszName) + 1;
convertKeyType(privKey->keyType, keyName->pszAlgid, algoSz+1, NULL);
}
cleanup_FindObjects:
WINPR_ASSERT(provider->p11->C_FindObjectsFinal);
rv = provider->p11->C_FindObjectsFinal(currentSession);
if (keyName)
{
*ppKeyName = keyName;
state->privateKeyIndex++;
return ERROR_SUCCESS;
}
}
return NTE_NO_MORE_ITEMS;
}
static SECURITY_STATUS NCryptP11KeyGetProperties(NCryptP11KeyHandle* keyHandle,
NCryptKeyGetPropertyEnum property, PBYTE pbOutput,
DWORD cbOutput, DWORD* pcbResult, DWORD dwFlags)
{
SECURITY_STATUS ret = NTE_FAIL;
CK_RV rv;
CK_SESSION_HANDLE session;
CK_OBJECT_HANDLE objectHandle;
CK_ULONG objectCount;
NCryptP11ProviderHandle* provider;
CK_OBJECT_CLASS oclass = CKO_CERTIFICATE;
CK_CERTIFICATE_TYPE ctype = CKC_X_509;
CK_ATTRIBUTE certificateFilter[] = { { CKA_CLASS, &oclass, sizeof(oclass) },
{ CKA_CERTIFICATE_TYPE, &ctype, sizeof(ctype) },
{ CKA_ID, keyHandle->keyCertId,
keyHandle->keyCertIdLen } };
CK_ATTRIBUTE* objectFilter = certificateFilter;
CK_ULONG objectFilterLen = ARRAYSIZE(certificateFilter);
WINPR_ASSERT(keyHandle);
provider = keyHandle->provider;
WINPR_ASSERT(provider);
switch (property)
{
case NCRYPT_PROPERTY_CERTIFICATE:
break;
case NCRYPT_PROPERTY_READER:
{
CK_SLOT_INFO slotInfo;
WINPR_ASSERT(provider->p11->C_GetSlotInfo);
rv = provider->p11->C_GetSlotInfo(keyHandle->slotId, &slotInfo);
if (rv != CKR_OK)
return NTE_BAD_KEY;
#define SLOT_DESC_SZ sizeof(slotInfo.slotDescription)
fix_padded_string((char*)slotInfo.slotDescription, SLOT_DESC_SZ);
*pcbResult = 2 * (strnlen((char*)slotInfo.slotDescription, SLOT_DESC_SZ) + 1);
if (pbOutput)
{
if (cbOutput < *pcbResult)
return NTE_NO_MEMORY;
if (MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)slotInfo.slotDescription, -1,
(LPWSTR)pbOutput, cbOutput) <= 0)
return NTE_NO_MEMORY;
}
return ERROR_SUCCESS;
}
case NCRYPT_PROPERTY_SLOTID:
{
*pcbResult = 4;
if (pbOutput)
{
UINT32* ptr = (UINT32*)pbOutput;
if (cbOutput < 4)
return NTE_NO_MEMORY;
*ptr = keyHandle->slotId;
}
return ERROR_SUCCESS;
}
case NCRYPT_PROPERTY_UNKNOWN:
default:
return NTE_NOT_SUPPORTED;
}
WINPR_ASSERT(provider->p11->C_OpenSession);
rv = provider->p11->C_OpenSession(keyHandle->slotId, CKF_SERIAL_SESSION, NULL, NULL, &session);
if (rv != CKR_OK)
{
WLog_ERR(TAG, "error opening session on slot %d", keyHandle->slotId);
return NTE_FAIL;
}
WINPR_ASSERT(provider->p11->C_FindObjectsInit);
rv = provider->p11->C_FindObjectsInit(session, objectFilter, objectFilterLen);
if (rv != CKR_OK)
{
WLog_ERR(TAG, "unable to initiate search for slot %d", keyHandle->slotId);
goto out;
}
WINPR_ASSERT(provider->p11->C_FindObjects);
rv = provider->p11->C_FindObjects(session, &objectHandle, 1, &objectCount);
if (rv != CKR_OK)
{
WLog_ERR(TAG, "unable to findObjects for slot %d", keyHandle->slotId);
goto out_final;
}
if (!objectCount)
{
ret = NTE_NOT_FOUND;
goto out_final;
}
switch (property)
{
case NCRYPT_PROPERTY_CERTIFICATE:
{
CK_ATTRIBUTE certValue = { CKA_VALUE, pbOutput, cbOutput };
WINPR_ASSERT(provider->p11->C_GetAttributeValue);
rv = provider->p11->C_GetAttributeValue(session, objectHandle, &certValue, 1);
if (rv != CKR_OK)
{
// TODO: do a kind of translation from CKR_* to NTE_*
}
*pcbResult = certValue.ulValueLen;
ret = ERROR_SUCCESS;
break;
}
default:
ret = NTE_NOT_SUPPORTED;
break;
}
out_final:
WINPR_ASSERT(provider->p11->C_FindObjectsFinal);
rv = provider->p11->C_FindObjectsFinal(session);
if (rv != CKR_OK)
{
WLog_ERR(TAG, "error in C_FindObjectsFinal() for slot %d", keyHandle->slotId);
}
out:
WINPR_ASSERT(provider->p11->C_CloseSession);
rv = provider->p11->C_CloseSession(session);
if (rv != CKR_OK)
{
WLog_ERR(TAG, "error in C_CloseSession() for slot %d", keyHandle->slotId);
}
return ret;
}
static SECURITY_STATUS NCryptP11GetProperty(NCRYPT_HANDLE hObject, NCryptKeyGetPropertyEnum prop,
PBYTE pbOutput, DWORD cbOutput, DWORD* pcbResult,
DWORD dwFlags)
{
NCryptBaseHandle* base = (NCryptBaseHandle*)hObject;
WINPR_ASSERT(base);
switch (base->type)
{
case WINPR_NCRYPT_PROVIDER:
return ERROR_CALL_NOT_IMPLEMENTED;
case WINPR_NCRYPT_KEY:
return NCryptP11KeyGetProperties((NCryptP11KeyHandle*)hObject, prop, pbOutput, cbOutput,
pcbResult, dwFlags);
default:
return ERROR_INVALID_HANDLE;
}
return ERROR_SUCCESS;
}
static SECURITY_STATUS NCryptP11OpenKey(NCRYPT_PROV_HANDLE hProvider, NCRYPT_KEY_HANDLE* phKey,
LPCWSTR pszKeyName, DWORD dwLegacyKeySpec, DWORD dwFlags)
{
SECURITY_STATUS ret;
CK_SLOT_ID slotId;
CK_BYTE keyCertId[64] = { 0 };
CK_ULONG keyCertIdLen;
NCryptP11KeyHandle* keyHandle;
ret = parseKeyName(pszKeyName, &slotId, keyCertId, &keyCertIdLen);
if (ret != ERROR_SUCCESS)
return ret;
keyHandle = (NCryptP11KeyHandle*)ncrypt_new_handle(
WINPR_NCRYPT_KEY, sizeof(*keyHandle), NCryptP11GetProperty, winpr_NCryptDefault_dtor);
if (!keyHandle)
return NTE_NO_MEMORY;
keyHandle->provider = (NCryptP11ProviderHandle*)hProvider;
keyHandle->slotId = slotId;
memcpy(keyHandle->keyCertId, keyCertId, sizeof(keyCertId));
keyHandle->keyCertIdLen = keyCertIdLen;
*phKey = (NCRYPT_KEY_HANDLE)keyHandle;
return ERROR_SUCCESS;
}
static SECURITY_STATUS initialize_pkcs11(HANDLE handle,
CK_RV (*c_get_function_list)(CK_FUNCTION_LIST_PTR_PTR),
NCRYPT_PROV_HANDLE* phProvider)
{
SECURITY_STATUS status = ERROR_SUCCESS;
NCryptP11ProviderHandle* ret;
CK_RV rv;
WINPR_ASSERT(c_get_function_list);
WINPR_ASSERT(phProvider);
ret = (NCryptP11ProviderHandle*)ncrypt_new_handle(
WINPR_NCRYPT_PROVIDER, sizeof(*ret), NCryptP11GetProperty, NCryptP11StorageProvider_dtor);
if (!ret)
{
if (handle)
FreeLibrary(handle);
return NTE_NO_MEMORY;
}
ret->library = handle;
ret->baseProvider.enumKeysFn = NCryptP11EnumKeys;
ret->baseProvider.openKeyFn = NCryptP11OpenKey;
rv = c_get_function_list(&ret->p11);
if (rv != CKR_OK)
{
status = NTE_PROVIDER_DLL_FAIL;
goto fail;
}
WINPR_ASSERT(ret->p11->C_Initialize);
rv = ret->p11->C_Initialize(NULL);
if (rv != CKR_OK)
{
status = NTE_PROVIDER_DLL_FAIL;
goto fail;
}
*phProvider = (NCRYPT_PROV_HANDLE)ret;
fail:
if (status != ERROR_SUCCESS)
ret->baseProvider.baseHandle.releaseFn((NCRYPT_HANDLE)ret);
return status;
}
SECURITY_STATUS NCryptOpenP11StorageProviderEx(NCRYPT_PROV_HANDLE* phProvider,
LPCWSTR pszProviderName, DWORD dwFlags,
LPCSTR* modulePaths)
{
SECURITY_STATUS status = ERROR_INVALID_PARAMETER;
#if defined(__LP64__) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || \
defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)
#define LIBS64
#endif
LPCSTR openscPaths[] = { "opensc-pkcs11.so", /* In case winpr is installed in system paths */
#ifdef __APPLE__
"/usr/local/lib/pkcs11/opensc-pkcs11.so",
#else
/* linux and UNIXes */
#ifdef LIBS64
"/usr/lib/x86_64-linux-gnu/pkcs11/opensc-pkcs11.so", /* Ubuntu/debian
*/
"/lib64/pkcs11/opensc-pkcs11.so", /* Fedora */
#else
"/usr/lib/i386-linux-gnu/opensc-pkcs11.so", /* debian */
"/lib32/pkcs11/opensc-pkcs11.so", /* Fedora */
#endif
#endif
NULL };
if (!phProvider)
return ERROR_INVALID_PARAMETER;
#if defined(WITH_OPENSC_PKCS11_LINKED)
if (!modulePaths)
return initialize_pkcs11(NULL, C_GetFunctionList, phProvider);
#endif
if (!modulePaths)
modulePaths = openscPaths;
while (*modulePaths)
{
HANDLE library = LoadLibrary(*modulePaths);
CK_RV (*c_get_function_list)(CK_FUNCTION_LIST_PTR_PTR);
WLog_DBG(TAG, "Trying pkcs11-helper module '%s'", *modulePaths);
if (!library)
{
status = NTE_PROV_DLL_NOT_FOUND;
goto out_load_library;
}
c_get_function_list = GetProcAddress(library, "C_GetFunctionList");
if (!c_get_function_list)
{
status = NTE_PROV_TYPE_ENTRY_BAD;
goto out_load_library;
}
status = initialize_pkcs11(library, c_get_function_list, phProvider);
if (status != ERROR_SUCCESS)
{
status = NTE_PROVIDER_DLL_FAIL;
goto out_load_library;
}
WLog_DBG(TAG, "module '%s' loaded", *modulePaths);
return ERROR_SUCCESS;
out_load_library:
modulePaths++;
}
return status;
}
#endif