diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /winpr/libwinpr/ncrypt | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'winpr/libwinpr/ncrypt')
-rw-r--r-- | winpr/libwinpr/ncrypt/CMakeLists.txt | 57 | ||||
-rw-r--r-- | winpr/libwinpr/ncrypt/ModuleOptions.cmake | 9 | ||||
-rw-r--r-- | winpr/libwinpr/ncrypt/ncrypt.c | 347 | ||||
-rw-r--r-- | winpr/libwinpr/ncrypt/ncrypt.h | 94 | ||||
-rw-r--r-- | winpr/libwinpr/ncrypt/ncrypt_pkcs11.c | 1297 | ||||
-rw-r--r-- | winpr/libwinpr/ncrypt/test/CMakeLists.txt | 29 | ||||
-rw-r--r-- | winpr/libwinpr/ncrypt/test/TestNCryptProviders.c | 51 | ||||
-rw-r--r-- | winpr/libwinpr/ncrypt/test/TestNCryptSmartcard.c | 156 |
8 files changed, 2040 insertions, 0 deletions
diff --git a/winpr/libwinpr/ncrypt/CMakeLists.txt b/winpr/libwinpr/ncrypt/CMakeLists.txt new file mode 100644 index 0000000..2989069 --- /dev/null +++ b/winpr/libwinpr/ncrypt/CMakeLists.txt @@ -0,0 +1,57 @@ +# WinPR: Windows Portable Runtime +# libwinpr-ncrypt cmake build script +# +# 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. + +if (PKCS11_FOUND) + winpr_include_directory_add(${PKCS11_INCLUDE_DIR}) + + option(WITH_OPENSC_PKCS11_LINKED "Directly link opensc-pkcs11" OFF) + if (WITH_OPENSC_PKCS11_LINKED) + + # opensc-pkcs11 is installed without any library prefix. + # Disable this when checking for this library. + set(backup ${CMAKE_FIND_LIBRARY_PREFIXES}) + set(CMAKE_FIND_LIBRARY_PREFIXES "") + find_library(OPENSC_PKCS11 + NAMES opensc-pkcs11 + PATH_SUFFIXES pkcs11 + REQUIRED) + set(CMAKE_FIND_LIBRARY_PREFIXES ${backup}) + + winpr_definition_add(-DWITH_OPENSC_PKCS11_LINKED) + winpr_library_add_private(${OPENSC_PKCS11}) + winpr_library_add_private(${PKCS11_LIBRARY}) + endif() +endif() + +if (PKCS11_FOUND) + winpr_module_add( + ncrypt_pkcs11.c + ) +endif() + +winpr_module_add( + ncrypt.c + ncrypt.h +) + +if (WIN32) + winpr_library_add_public(ncrypt) +endif() + +if(BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/winpr/libwinpr/ncrypt/ModuleOptions.cmake b/winpr/libwinpr/ncrypt/ModuleOptions.cmake new file mode 100644 index 0000000..80729fc --- /dev/null +++ b/winpr/libwinpr/ncrypt/ModuleOptions.cmake @@ -0,0 +1,9 @@ + +set(MINWIN_LAYER "1") +set(MINWIN_GROUP "core") +set(MINWIN_MAJOR_VERSION "1") +set(MINWIN_MINOR_VERSION "0") +set(MINWIN_SHORT_NAME "ncrypt") +set(MINWIN_LONG_NAME "NCrypt Functions") +set(MODULE_LIBRARY_NAME "ncrypt") + diff --git a/winpr/libwinpr/ncrypt/ncrypt.c b/winpr/libwinpr/ncrypt/ncrypt.c new file mode 100644 index 0000000..df9c5e1 --- /dev/null +++ b/winpr/libwinpr/ncrypt/ncrypt.c @@ -0,0 +1,347 @@ +/** + * WinPR: Windows Portable Runtime + * NCrypt library + * + * 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. + */ + +#include <winpr/assert.h> +#include <winpr/ncrypt.h> + +#ifndef _WIN32 + +#include <winpr/print.h> +#include "../log.h" + +#include "ncrypt.h" + +#define TAG WINPR_TAG("ncrypt") + +const static char NCRYPT_MAGIC[6] = { 'N', 'C', 'R', 'Y', 'P', 'T' }; + +SECURITY_STATUS checkNCryptHandle(NCRYPT_HANDLE handle, NCryptHandleType matchType) +{ + if (!handle) + { + WLog_VRB(TAG, "invalid handle '%p'", handle); + return ERROR_INVALID_PARAMETER; + } + + const NCryptBaseHandle* base = (NCryptBaseHandle*)handle; + if (memcmp(base->magic, NCRYPT_MAGIC, ARRAYSIZE(NCRYPT_MAGIC)) != 0) + { + char magic1[ARRAYSIZE(NCRYPT_MAGIC) + 1] = { 0 }; + char magic2[ARRAYSIZE(NCRYPT_MAGIC) + 1] = { 0 }; + + memcpy(magic1, base->magic, ARRAYSIZE(NCRYPT_MAGIC)); + memcpy(magic2, NCRYPT_MAGIC, ARRAYSIZE(NCRYPT_MAGIC)); + + WLog_VRB(TAG, "handle '%p' invalid magic '%s' instead of '%s'", base, magic1, magic2); + return ERROR_INVALID_PARAMETER; + } + + switch (base->type) + { + case WINPR_NCRYPT_PROVIDER: + case WINPR_NCRYPT_KEY: + break; + default: + WLog_VRB(TAG, "handle '%p' invalid type %d", base, base->type); + return ERROR_INVALID_PARAMETER; + } + + if ((matchType != WINPR_NCRYPT_INVALID) && (base->type != matchType)) + { + WLog_VRB(TAG, "handle '%p' invalid type %d, expected %d", base, base->type, matchType); + return ERROR_INVALID_PARAMETER; + } + return ERROR_SUCCESS; +} + +void* ncrypt_new_handle(NCryptHandleType kind, size_t len, NCryptGetPropertyFn getProp, + NCryptReleaseFn dtor) +{ + NCryptBaseHandle* ret = calloc(1, len); + if (!ret) + return NULL; + + memcpy(ret->magic, NCRYPT_MAGIC, sizeof(ret->magic)); + ret->type = kind; + ret->getPropertyFn = getProp; + ret->releaseFn = dtor; + return ret; +} + +SECURITY_STATUS winpr_NCryptDefault_dtor(NCRYPT_HANDLE handle) +{ + NCryptBaseHandle* h = (NCryptBaseHandle*)handle; + WINPR_ASSERT(h); + + memset(h->magic, 0, sizeof(h->magic)); + h->type = WINPR_NCRYPT_INVALID; + h->releaseFn = NULL; + free(h); + return ERROR_SUCCESS; +} + +SECURITY_STATUS NCryptEnumStorageProviders(DWORD* wProviderCount, + NCryptProviderName** ppProviderList, DWORD dwFlags) +{ + NCryptProviderName* ret = NULL; + size_t stringAllocSize = 0; +#ifdef WITH_PKCS11 + LPWSTR strPtr = NULL; + static const WCHAR emptyComment[] = { 0 }; + size_t copyAmount = 0; +#endif + + *wProviderCount = 0; + *ppProviderList = NULL; + +#ifdef WITH_PKCS11 + *wProviderCount += 1; + stringAllocSize += (_wcslen(MS_SCARD_PROV) + 1) * 2; + stringAllocSize += sizeof(emptyComment); +#endif + + if (!*wProviderCount) + return ERROR_SUCCESS; + + ret = malloc(*wProviderCount * sizeof(NCryptProviderName) + stringAllocSize); + if (!ret) + return NTE_NO_MEMORY; + +#ifdef WITH_PKCS11 + strPtr = (LPWSTR)(ret + *wProviderCount); + + ret->pszName = strPtr; + copyAmount = (_wcslen(MS_SCARD_PROV) + 1) * 2; + memcpy(strPtr, MS_SCARD_PROV, copyAmount); + strPtr += copyAmount / 2; + + ret->pszComment = strPtr; + copyAmount = sizeof(emptyComment); + memcpy(strPtr, emptyComment, copyAmount); + + *ppProviderList = ret; +#endif + + return ERROR_SUCCESS; +} + +SECURITY_STATUS NCryptOpenStorageProvider(NCRYPT_PROV_HANDLE* phProvider, LPCWSTR pszProviderName, + DWORD dwFlags) +{ + return winpr_NCryptOpenStorageProviderEx(phProvider, pszProviderName, dwFlags, NULL); +} + +SECURITY_STATUS winpr_NCryptOpenStorageProviderEx(NCRYPT_PROV_HANDLE* phProvider, + LPCWSTR pszProviderName, DWORD dwFlags, + LPCSTR* modulePaths) +{ +#if defined(WITH_PKCS11) + if (pszProviderName && ((_wcscmp(pszProviderName, MS_SMART_CARD_KEY_STORAGE_PROVIDER) == 0) || + (_wcscmp(pszProviderName, MS_SCARD_PROV) == 0))) + return NCryptOpenP11StorageProviderEx(phProvider, pszProviderName, dwFlags, modulePaths); + + char buffer[128] = { 0 }; + ConvertWCharToUtf8(pszProviderName, buffer, sizeof(buffer)); + WLog_WARN(TAG, "provider '%s' not supported", buffer); + return ERROR_NOT_SUPPORTED; +#else + WLog_WARN(TAG, "rebuild with -DWITH_PKCS11=ON to enable smartcard logon support"); + return ERROR_NOT_SUPPORTED; +#endif +} + +SECURITY_STATUS NCryptEnumKeys(NCRYPT_PROV_HANDLE hProvider, LPCWSTR pszScope, + NCryptKeyName** ppKeyName, PVOID* ppEnumState, DWORD dwFlags) +{ + SECURITY_STATUS ret = 0; + NCryptBaseProvider* provider = (NCryptBaseProvider*)hProvider; + + ret = checkNCryptHandle((NCRYPT_HANDLE)hProvider, WINPR_NCRYPT_PROVIDER); + if (ret != ERROR_SUCCESS) + return ret; + + return provider->enumKeysFn(hProvider, pszScope, ppKeyName, ppEnumState, dwFlags); +} + +SECURITY_STATUS NCryptOpenKey(NCRYPT_PROV_HANDLE hProvider, NCRYPT_KEY_HANDLE* phKey, + LPCWSTR pszKeyName, DWORD dwLegacyKeySpec, DWORD dwFlags) +{ + SECURITY_STATUS ret = 0; + NCryptBaseProvider* provider = (NCryptBaseProvider*)hProvider; + + ret = checkNCryptHandle((NCRYPT_HANDLE)hProvider, WINPR_NCRYPT_PROVIDER); + if (ret != ERROR_SUCCESS) + return ret; + if (!phKey || !pszKeyName) + return ERROR_INVALID_PARAMETER; + + return provider->openKeyFn(hProvider, phKey, pszKeyName, dwLegacyKeySpec, dwFlags); +} + +static NCryptKeyGetPropertyEnum propertyStringToEnum(LPCWSTR pszProperty) +{ + if (_wcscmp(pszProperty, NCRYPT_CERTIFICATE_PROPERTY) == 0) + { + return NCRYPT_PROPERTY_CERTIFICATE; + } + else if (_wcscmp(pszProperty, NCRYPT_READER_PROPERTY) == 0) + { + return NCRYPT_PROPERTY_READER; + } + else if (_wcscmp(pszProperty, NCRYPT_WINPR_SLOTID) == 0) + { + return NCRYPT_PROPERTY_SLOTID; + } + else if (_wcscmp(pszProperty, NCRYPT_NAME_PROPERTY) == 0) + { + return NCRYPT_PROPERTY_NAME; + } + + return NCRYPT_PROPERTY_UNKNOWN; +} + +SECURITY_STATUS NCryptGetProperty(NCRYPT_HANDLE hObject, LPCWSTR pszProperty, PBYTE pbOutput, + DWORD cbOutput, DWORD* pcbResult, DWORD dwFlags) +{ + NCryptKeyGetPropertyEnum property = NCRYPT_PROPERTY_UNKNOWN; + NCryptBaseHandle* base = NULL; + + if (!hObject) + return ERROR_INVALID_PARAMETER; + + base = (NCryptBaseHandle*)hObject; + if (memcmp(base->magic, NCRYPT_MAGIC, 6) != 0) + return ERROR_INVALID_HANDLE; + + property = propertyStringToEnum(pszProperty); + if (property == NCRYPT_PROPERTY_UNKNOWN) + return ERROR_NOT_SUPPORTED; + + return base->getPropertyFn(hObject, property, pbOutput, cbOutput, pcbResult, dwFlags); +} + +SECURITY_STATUS NCryptFreeObject(NCRYPT_HANDLE hObject) +{ + NCryptBaseHandle* base = NULL; + SECURITY_STATUS ret = checkNCryptHandle((NCRYPT_HANDLE)hObject, WINPR_NCRYPT_INVALID); + if (ret != ERROR_SUCCESS) + return ret; + + base = (NCryptBaseHandle*)hObject; + if (base->releaseFn) + ret = base->releaseFn(hObject); + + return ret; +} + +SECURITY_STATUS NCryptFreeBuffer(PVOID pvInput) +{ + if (!pvInput) + return ERROR_INVALID_PARAMETER; + + free(pvInput); + return ERROR_SUCCESS; +} + +#else +SECURITY_STATUS winpr_NCryptOpenStorageProviderEx(NCRYPT_PROV_HANDLE* phProvider, + LPCWSTR pszProviderName, DWORD dwFlags, + LPCSTR* modulePaths) +{ + typedef SECURITY_STATUS (*NCryptOpenStorageProviderFn)(NCRYPT_PROV_HANDLE * phProvider, + LPCWSTR pszProviderName, DWORD dwFlags); + NCryptOpenStorageProviderFn ncryptOpenStorageProviderFn; + SECURITY_STATUS ret; + HANDLE lib = LoadLibraryA("ncrypt.dll"); + if (!lib) + return NTE_PROV_DLL_NOT_FOUND; + + ncryptOpenStorageProviderFn = + (NCryptOpenStorageProviderFn)GetProcAddress(lib, "NCryptOpenStorageProvider"); + if (!ncryptOpenStorageProviderFn) + { + ret = NTE_PROV_DLL_NOT_FOUND; + goto out_free_lib; + } + + ret = ncryptOpenStorageProviderFn(phProvider, pszProviderName, dwFlags); + +out_free_lib: + FreeLibrary(lib); + return ret; +} +#endif /* _WIN32 */ + +const char* winpr_NCryptSecurityStatusError(SECURITY_STATUS status) +{ +#define NTE_CASE(S) \ + case (SECURITY_STATUS)S: \ + return #S + + switch (status) + { + NTE_CASE(ERROR_SUCCESS); + NTE_CASE(ERROR_INVALID_PARAMETER); + NTE_CASE(ERROR_INVALID_HANDLE); + NTE_CASE(ERROR_NOT_SUPPORTED); + + NTE_CASE(NTE_BAD_UID); + NTE_CASE(NTE_BAD_HASH); + NTE_CASE(NTE_BAD_KEY); + NTE_CASE(NTE_BAD_LEN); + NTE_CASE(NTE_BAD_DATA); + NTE_CASE(NTE_BAD_SIGNATURE); + NTE_CASE(NTE_BAD_VER); + NTE_CASE(NTE_BAD_ALGID); + NTE_CASE(NTE_BAD_FLAGS); + NTE_CASE(NTE_BAD_TYPE); + NTE_CASE(NTE_BAD_KEY_STATE); + NTE_CASE(NTE_BAD_HASH_STATE); + NTE_CASE(NTE_NO_KEY); + NTE_CASE(NTE_NO_MEMORY); + NTE_CASE(NTE_EXISTS); + NTE_CASE(NTE_PERM); + NTE_CASE(NTE_NOT_FOUND); + NTE_CASE(NTE_DOUBLE_ENCRYPT); + NTE_CASE(NTE_BAD_PROVIDER); + NTE_CASE(NTE_BAD_PROV_TYPE); + NTE_CASE(NTE_BAD_PUBLIC_KEY); + NTE_CASE(NTE_BAD_KEYSET); + NTE_CASE(NTE_PROV_TYPE_NOT_DEF); + NTE_CASE(NTE_PROV_TYPE_ENTRY_BAD); + NTE_CASE(NTE_KEYSET_NOT_DEF); + NTE_CASE(NTE_KEYSET_ENTRY_BAD); + NTE_CASE(NTE_PROV_TYPE_NO_MATCH); + NTE_CASE(NTE_SIGNATURE_FILE_BAD); + NTE_CASE(NTE_PROVIDER_DLL_FAIL); + NTE_CASE(NTE_PROV_DLL_NOT_FOUND); + NTE_CASE(NTE_BAD_KEYSET_PARAM); + NTE_CASE(NTE_FAIL); + NTE_CASE(NTE_SYS_ERR); + NTE_CASE(NTE_SILENT_CONTEXT); + NTE_CASE(NTE_TOKEN_KEYSET_STORAGE_FULL); + NTE_CASE(NTE_TEMPORARY_PROFILE); + NTE_CASE(NTE_FIXEDPARAMETER); + + default: + return "<unknown>"; + } + +#undef NTE_CASE +} diff --git a/winpr/libwinpr/ncrypt/ncrypt.h b/winpr/libwinpr/ncrypt/ncrypt.h new file mode 100644 index 0000000..222f655 --- /dev/null +++ b/winpr/libwinpr/ncrypt/ncrypt.h @@ -0,0 +1,94 @@ +/** + * WinPR: Windows Portable Runtime + * NCrypt library + * + * 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. + */ + +#ifndef WINPR_LIBWINPR_NCRYPT_NCRYPT_H_ +#define WINPR_LIBWINPR_NCRYPT_NCRYPT_H_ + +#include <winpr/config.h> + +#include <winpr/bcrypt.h> +#include <winpr/crypto.h> +#include <winpr/ncrypt.h> +#include <winpr/error.h> +#include <winpr/string.h> + +/** @brief type of ncrypt object */ +typedef enum +{ + WINPR_NCRYPT_INVALID, + WINPR_NCRYPT_PROVIDER, + WINPR_NCRYPT_KEY +} NCryptHandleType; + +/** @brief dtor function for ncrypt object */ +typedef SECURITY_STATUS (*NCryptReleaseFn)(NCRYPT_HANDLE handle); + +/** @brief an enum for the kind of property to retrieve */ +typedef enum +{ + NCRYPT_PROPERTY_CERTIFICATE, + NCRYPT_PROPERTY_READER, + NCRYPT_PROPERTY_SLOTID, + NCRYPT_PROPERTY_NAME, + NCRYPT_PROPERTY_UNKNOWN +} NCryptKeyGetPropertyEnum; + +typedef SECURITY_STATUS (*NCryptGetPropertyFn)(NCRYPT_HANDLE hObject, + NCryptKeyGetPropertyEnum property, PBYTE pbOutput, + DWORD cbOutput, DWORD* pcbResult, DWORD dwFlags); + +/** @brief common ncrypt handle items */ +typedef struct +{ + char magic[6]; + NCryptHandleType type; + NCryptGetPropertyFn getPropertyFn; + NCryptReleaseFn releaseFn; +} NCryptBaseHandle; + +typedef SECURITY_STATUS (*NCryptEnumKeysFn)(NCRYPT_PROV_HANDLE hProvider, LPCWSTR pszScope, + NCryptKeyName** ppKeyName, PVOID* ppEnumState, + DWORD dwFlags); +typedef SECURITY_STATUS (*NCryptOpenKeyFn)(NCRYPT_PROV_HANDLE hProvider, NCRYPT_KEY_HANDLE* phKey, + LPCWSTR pszKeyName, DWORD dwLegacyKeySpec, + DWORD dwFlags); + +/** @brief common ncrypt provider items */ +typedef struct +{ + NCryptBaseHandle baseHandle; + + NCryptEnumKeysFn enumKeysFn; + NCryptOpenKeyFn openKeyFn; +} NCryptBaseProvider; + +SECURITY_STATUS checkNCryptHandle(NCRYPT_HANDLE handle, NCryptHandleType matchType); + +SECURITY_STATUS winpr_NCryptDefault_dtor(NCRYPT_HANDLE handle); + +void* ncrypt_new_handle(NCryptHandleType kind, size_t len, NCryptGetPropertyFn getProp, + NCryptReleaseFn dtor); + +#if defined(WITH_PKCS11) +SECURITY_STATUS NCryptOpenP11StorageProviderEx(NCRYPT_PROV_HANDLE* phProvider, + LPCWSTR pszProviderName, DWORD dwFlags, + LPCSTR* modulePaths); +#endif + +#endif /* WINPR_LIBWINPR_NCRYPT_NCRYPT_H_ */ diff --git a/winpr/libwinpr/ncrypt/ncrypt_pkcs11.c b/winpr/libwinpr/ncrypt/ncrypt_pkcs11.c new file mode 100644 index 0000000..08e6274 --- /dev/null +++ b/winpr/libwinpr/ncrypt/ncrypt_pkcs11.c @@ -0,0 +1,1297 @@ +/** + * 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. + */ + +#include <stdlib.h> +#include <pkcs11-helper-1.0/pkcs11.h> + +#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" + +#define TAG WINPR_TAG("ncryptp11") + +#define MAX_SLOTS 64 +#define MAX_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; + +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]; +} NCryptKeyEnum; + +typedef struct +{ + CK_ULONG nslots; + CK_SLOT_ID slots[MAX_SLOTS]; + CK_ULONG nKeys; + NCryptKeyEnum keys[MAX_KEYS]; + CK_ULONG keyIndex; +} P11EnumKeysState; + +typedef struct +{ + const char* label; + BYTE tag[3]; +} piv_cert_tags_t; +static const piv_cert_tags_t 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" }, +}; + +static const BYTE APDU_PIV_SELECT_AID[] = { 0x00, 0xA4, 0x04, 0x00, 0x09, 0xA0, 0x00, 0x00, + 0x03, 0x08, 0x00, 0x00, 0x10, 0x00, 0x00 }; +static 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; + +static CK_ATTRIBUTE public_key_filter[] = { + { CKA_CLASS, &object_class_public_key, sizeof(object_class_public_key) }, + { CKA_VERIFY, &object_verify, sizeof(object_verify) }, + { 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 = 0; + + 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) +{ + for (CK_ULONG 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) +{ + BOOL ret = TRUE; + + for (CK_ULONG 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 = 0; + + 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; + /* fallthrough */ + WINPR_FALLTHROUGH + 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 const char* CK_RV_error_string(CK_RV rv) +{ + static char generic_buffer[200]; +#define ERR_ENTRY(X) \ + case X: \ + return #X + + switch (rv) + { + ERR_ENTRY(CKR_OK); + ERR_ENTRY(CKR_CANCEL); + ERR_ENTRY(CKR_HOST_MEMORY); + ERR_ENTRY(CKR_SLOT_ID_INVALID); + ERR_ENTRY(CKR_GENERAL_ERROR); + ERR_ENTRY(CKR_FUNCTION_FAILED); + ERR_ENTRY(CKR_ARGUMENTS_BAD); + ERR_ENTRY(CKR_NO_EVENT); + ERR_ENTRY(CKR_NEED_TO_CREATE_THREADS); + ERR_ENTRY(CKR_CANT_LOCK); + ERR_ENTRY(CKR_ATTRIBUTE_READ_ONLY); + ERR_ENTRY(CKR_ATTRIBUTE_SENSITIVE); + ERR_ENTRY(CKR_ATTRIBUTE_TYPE_INVALID); + ERR_ENTRY(CKR_ATTRIBUTE_VALUE_INVALID); + ERR_ENTRY(CKR_DATA_INVALID); + ERR_ENTRY(CKR_DATA_LEN_RANGE); + ERR_ENTRY(CKR_DEVICE_ERROR); + ERR_ENTRY(CKR_DEVICE_MEMORY); + ERR_ENTRY(CKR_DEVICE_REMOVED); + ERR_ENTRY(CKR_ENCRYPTED_DATA_INVALID); + ERR_ENTRY(CKR_ENCRYPTED_DATA_LEN_RANGE); + ERR_ENTRY(CKR_FUNCTION_CANCELED); + ERR_ENTRY(CKR_FUNCTION_NOT_PARALLEL); + ERR_ENTRY(CKR_FUNCTION_NOT_SUPPORTED); + ERR_ENTRY(CKR_KEY_HANDLE_INVALID); + ERR_ENTRY(CKR_KEY_SIZE_RANGE); + ERR_ENTRY(CKR_KEY_TYPE_INCONSISTENT); + ERR_ENTRY(CKR_KEY_NOT_NEEDED); + ERR_ENTRY(CKR_KEY_CHANGED); + ERR_ENTRY(CKR_KEY_NEEDED); + ERR_ENTRY(CKR_KEY_INDIGESTIBLE); + ERR_ENTRY(CKR_KEY_FUNCTION_NOT_PERMITTED); + ERR_ENTRY(CKR_KEY_NOT_WRAPPABLE); + ERR_ENTRY(CKR_KEY_UNEXTRACTABLE); + ERR_ENTRY(CKR_MECHANISM_INVALID); + ERR_ENTRY(CKR_MECHANISM_PARAM_INVALID); + ERR_ENTRY(CKR_OBJECT_HANDLE_INVALID); + ERR_ENTRY(CKR_OPERATION_ACTIVE); + ERR_ENTRY(CKR_OPERATION_NOT_INITIALIZED); + ERR_ENTRY(CKR_PIN_INCORRECT); + ERR_ENTRY(CKR_PIN_INVALID); + ERR_ENTRY(CKR_PIN_LEN_RANGE); + ERR_ENTRY(CKR_PIN_EXPIRED); + ERR_ENTRY(CKR_PIN_LOCKED); + ERR_ENTRY(CKR_SESSION_CLOSED); + ERR_ENTRY(CKR_SESSION_COUNT); + ERR_ENTRY(CKR_SESSION_HANDLE_INVALID); + ERR_ENTRY(CKR_SESSION_PARALLEL_NOT_SUPPORTED); + ERR_ENTRY(CKR_SESSION_READ_ONLY); + ERR_ENTRY(CKR_SESSION_EXISTS); + ERR_ENTRY(CKR_SESSION_READ_ONLY_EXISTS); + ERR_ENTRY(CKR_SESSION_READ_WRITE_SO_EXISTS); + ERR_ENTRY(CKR_SIGNATURE_INVALID); + ERR_ENTRY(CKR_SIGNATURE_LEN_RANGE); + ERR_ENTRY(CKR_TEMPLATE_INCOMPLETE); + ERR_ENTRY(CKR_TEMPLATE_INCONSISTENT); + ERR_ENTRY(CKR_TOKEN_NOT_PRESENT); + ERR_ENTRY(CKR_TOKEN_NOT_RECOGNIZED); + ERR_ENTRY(CKR_TOKEN_WRITE_PROTECTED); + ERR_ENTRY(CKR_UNWRAPPING_KEY_HANDLE_INVALID); + ERR_ENTRY(CKR_UNWRAPPING_KEY_SIZE_RANGE); + ERR_ENTRY(CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT); + ERR_ENTRY(CKR_USER_ALREADY_LOGGED_IN); + ERR_ENTRY(CKR_USER_NOT_LOGGED_IN); + ERR_ENTRY(CKR_USER_PIN_NOT_INITIALIZED); + ERR_ENTRY(CKR_USER_TYPE_INVALID); + ERR_ENTRY(CKR_USER_ANOTHER_ALREADY_LOGGED_IN); + ERR_ENTRY(CKR_USER_TOO_MANY_TYPES); + ERR_ENTRY(CKR_WRAPPED_KEY_INVALID); + ERR_ENTRY(CKR_WRAPPED_KEY_LEN_RANGE); + ERR_ENTRY(CKR_WRAPPING_KEY_HANDLE_INVALID); + ERR_ENTRY(CKR_WRAPPING_KEY_SIZE_RANGE); + ERR_ENTRY(CKR_WRAPPING_KEY_TYPE_INCONSISTENT); + ERR_ENTRY(CKR_RANDOM_SEED_NOT_SUPPORTED); + ERR_ENTRY(CKR_RANDOM_NO_RNG); + ERR_ENTRY(CKR_DOMAIN_PARAMS_INVALID); + ERR_ENTRY(CKR_BUFFER_TOO_SMALL); + ERR_ENTRY(CKR_SAVED_STATE_INVALID); + ERR_ENTRY(CKR_INFORMATION_SENSITIVE); + ERR_ENTRY(CKR_STATE_UNSAVEABLE); + ERR_ENTRY(CKR_CRYPTOKI_NOT_INITIALIZED); + ERR_ENTRY(CKR_CRYPTOKI_ALREADY_INITIALIZED); + ERR_ENTRY(CKR_MUTEX_BAD); + ERR_ENTRY(CKR_MUTEX_NOT_LOCKED); + ERR_ENTRY(CKR_FUNCTION_REJECTED); + default: + snprintf(generic_buffer, sizeof(generic_buffer), "unknown 0x%lx", rv); + return generic_buffer; + } +#undef ERR_ENTRY +} + +static SECURITY_STATUS collect_keys(NCryptP11ProviderHandle* provider, P11EnumKeysState* state) +{ + CK_OBJECT_HANDLE slotObjects[MAX_KEYS_PER_SLOT] = { 0 }; + const char* step = NULL; + + WINPR_ASSERT(provider); + + CK_FUNCTION_LIST_PTR p11 = provider->p11; + WINPR_ASSERT(p11); + + WLog_DBG(TAG, "checking %" PRIu32 " slots for valid keys...", state->nslots); + state->nKeys = 0; + for (CK_ULONG i = 0; i < state->nslots; i++) + { + CK_SESSION_HANDLE session = (CK_SESSION_HANDLE)NULL; + CK_SLOT_INFO slotInfo = { 0 }; + CK_TOKEN_INFO tokenInfo = { 0 }; + + WINPR_ASSERT(p11->C_GetSlotInfo); + CK_RV 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; + } + + fix_padded_string((char*)slotInfo.slotDescription, sizeof(slotInfo.slotDescription)); + WLog_DBG(TAG, "collecting keys for slot #%d(%lu) descr='%s' flags=0x%x", i, state->slots[i], + slotInfo.slotDescription, slotInfo.flags); + + /* this is a safety guard as we're supposed to have listed only readers with tokens in them + */ + if (!(slotInfo.flags & CKF_TOKEN_PRESENT)) + { + WLog_INFO(TAG, "token not present for slot #%d(%d)", i, state->slots[i]); + continue; + } + + WINPR_ASSERT(p11->C_GetTokenInfo); + rv = p11->C_GetTokenInfo(state->slots[i], &tokenInfo); + if (rv != CKR_OK) + { + WLog_INFO(TAG, "unable to retrieve token info for slot #%d(%d)", i, state->slots[i]); + } + else + { + fix_padded_string((char*)tokenInfo.label, sizeof(tokenInfo.label)); + WLog_DBG(TAG, "token, label='%s' flags=0x%x", tokenInfo.label, tokenInfo.flags); + } + + WINPR_ASSERT(p11->C_OpenSession); + rv = p11->C_OpenSession(state->slots[i], CKF_SERIAL_SESSION, NULL, NULL, &session); + if (rv != CKR_OK) + { + WLog_ERR(TAG, "unable to openSession for slot #%d(%d), session=%p rv=%s", i, + state->slots[i], session, CK_RV_error_string(rv)); + continue; + } + + WINPR_ASSERT(p11->C_FindObjectsInit); + rv = p11->C_FindObjectsInit(session, public_key_filter, ARRAYSIZE(public_key_filter)); + if (rv != CKR_OK) + { + // TODO: shall it be fatal ? + WLog_ERR(TAG, "unable to initiate search for slot #%d(%d), rv=%s", i, state->slots[i], + CK_RV_error_string(rv)); + step = "C_FindObjectsInit"; + goto cleanup_FindObjectsInit; + } + + CK_ULONG nslotObjects = 0; + 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), rv=%s", i, state->slots[i], + CK_RV_error_string(rv)); + step = "C_FindObjects"; + goto cleanup_FindObjects; + } + + WLog_DBG(TAG, "slot has %d objects", nslotObjects); + for (CK_ULONG j = 0; j < nslotObjects; j++) + { + NCryptKeyEnum* key = &state->keys[state->nKeys]; + CK_OBJECT_CLASS dataClass = CKO_PUBLIC_KEY; + CK_ATTRIBUTE key_or_certAttrs[] = { + { CKA_ID, &key->id, sizeof(key->id) }, + { CKA_CLASS, &dataClass, sizeof(dataClass) }, + { CKA_LABEL, &key->keyLabel, sizeof(key->keyLabel) }, + { CKA_KEY_TYPE, &key->keyType, sizeof(key->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, rv=%s", CK_RV_error_string(rv)); + continue; + } + + key->idLen = key_or_certAttrs[0].ulValueLen; + key->slotId = state->slots[i]; + key->slotInfo = slotInfo; + state->nKeys++; + } + + 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), rv=%s", + i, state->slots[i], step, CK_RV_error_string(rv)); + } + 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), rv=%s", i, + state->slots[i], step, CK_RV_error_string(rv)); + } + } + + return ERROR_SUCCESS; +} + +static BOOL convertKeyType(CK_KEY_TYPE k, LPWSTR dest, DWORD len, DWORD* outlen) +{ + DWORD retLen = 0; + 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] = { 0 }; + char* ptr = asciiName; + const CK_BYTE* bytePtr = NULL; + + *ptr = '\\'; + ptr++; + + bytePtr = ((CK_BYTE*)&slotId); + for (CK_ULONG i = 0; i < sizeof(slotId); i++, bytePtr++, ptr += 2) + snprintf(ptr, 3, "%.2x", *bytePtr); + + *ptr = '\\'; + ptr++; + + for (CK_ULONG i = 0; i < idLen; i++, id++, ptr += 2) + snprintf(ptr, 3, "%.2x", *id); + + ConvertUtf8NToWChar(asciiName, ARRAYSIZE(asciiName), str, + strnlen(asciiName, ARRAYSIZE(asciiName)) + 1); +} + +static size_t parseHex(const char* str, const char* end, CK_BYTE* target) +{ + int ret = 0; + + for (; 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 = NULL; + + if (ConvertWCharToUtf8(pszKeyName, asciiKeyName, ARRAYSIZE(asciiKeyName)) < 0) + return NTE_BAD_KEY; + + if (*asciiKeyName != '\\') + return NTE_BAD_KEY; + + pos = strchr(&asciiKeyName[1], '\\'); + if (!pos) + return NTE_BAD_KEY; + + if ((size_t)(pos - &asciiKeyName[1]) > sizeof(CK_SLOT_ID) * 2ull) + 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) +{ + 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; + + SECURITY_STATUS 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 + 1] = { 0 }; + size_t asciiScopeLen = 0; + + if (ConvertWCharToUtf8(pszScope, asciiScope, ARRAYSIZE(asciiScope) - 1) < 0) + { + WLog_WARN(TAG, "Invalid scope"); + return NTE_INVALID_PARAMETER; + } + + if (strstr(asciiScope, "\\\\.\\") != asciiScope) + { + WLog_WARN(TAG, "Invalid scope '%s'", asciiScope); + return NTE_INVALID_PARAMETER; + } + + asciiScopeLen = strnlen(asciiScope, ARRAYSIZE(asciiScope)); + if ((asciiScopeLen < 1) || (asciiScope[asciiScopeLen - 1] != '\\')) + { + WLog_WARN(TAG, "Invalid scope '%s'", asciiScope); + 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 */ + WLog_WARN(TAG, "C_GetSlotList failed with %u", rv); + 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 */ + WLog_WARN(TAG, "C_GetSlotList failed with %u", rv); + return NTE_FAIL; + } + + ret = collect_keys(provider, state); + if (ret != ERROR_SUCCESS) + { + free(state); + return ret; + } + + *ppEnumState = state; + } + + for (; state->keyIndex < state->nKeys; state->keyIndex++) + { + NCryptKeyName* keyName = NULL; + NCryptKeyEnum* key = &state->keys[state->keyIndex]; + 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, key->id, key->idLen } }; + CK_ULONG ncertObjects = 0; + CK_OBJECT_HANDLE certObject = 0; + + /* check the reader filter if any */ + if (slotFilter && memcmp(key->slotInfo.slotDescription, slotFilter, slotFilterLen) != 0) + continue; + + if (!currentSession || (currentSlot != key->slotId)) + { + /* if the current session doesn't match the current 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(key->slotId, CKF_SERIAL_SESSION, NULL, NULL, + ¤tSession); + if (rv != CKR_OK) + { + WLog_ERR(TAG, "unable to openSession for slot %d", key->slotId); + continue; + } + currentSlot = key->slotId; + } + + /* look if we can find a certificate that matches the 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", key->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 = 0; + size_t KEYNAME_SZ = + (1 + (sizeof(key->slotId) * 2) /*slotId*/ + 1 + (key->idLen * 2) + 1) * 2; + + convertKeyType(key->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, key->slotId, key->id, key->idLen); + + keyName->pszAlgid = keyName->pszName + _wcslen(keyName->pszName) + 1; + convertKeyType(key->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->keyIndex++; + return ERROR_SUCCESS; + } + } + + return NTE_NO_MORE_ITEMS; +} + +static SECURITY_STATUS get_piv_container_name(NCryptP11KeyHandle* key, const 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)); + reader = ConvertUtf8NToWCharAlloc((char*)slot_info.slotDescription, + ARRAYSIZE(slot_info.slotDescription), NULL); + ret = NTE_NO_MEMORY; + if (!reader) + goto out; + + ret = NTE_BAD_KEY; + if (SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &context) != SCARD_S_SUCCESS) + goto out; + + 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 */ + union + { + WCHAR* wc; + BYTE* b; + } cnv; + cnv.b = output; + if (ConvertUtf8NToWChar(container_name, ARRAYSIZE(container_name), cnv.wc, + output_len / sizeof(WCHAR)) > 0) + ret = ERROR_SUCCESS; + +out: + free(reader); + 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 (size_t i = 0; i < ARRAYSIZE(piv_cert_tags); i++) + { + const piv_cert_tags_t* cur = &piv_cert_tags[i]; + if (strncmp(label, cur->label, label_len) == 0) + { + *pcbResult = (PIV_CONTAINER_NAME_LEN + 1) * sizeof(WCHAR); + if (!pbOutput) + return ERROR_SUCCESS; + else if (cbOutput < (PIV_CONTAINER_NAME_LEN + 1) * sizeof(WCHAR)) + return NTE_NO_MEMORY; + else + return get_piv_container_name(key, cur->tag, pbOutput, cbOutput); + } + } + return NTE_NOT_FOUND; +} + +static SECURITY_STATUS NCryptP11KeyGetProperties(NCryptP11KeyHandle* keyHandle, + NCryptKeyGetPropertyEnum property, PBYTE pbOutput, + DWORD cbOutput, DWORD* pcbResult, DWORD dwFlags) +{ + SECURITY_STATUS ret = NTE_FAIL; + CK_RV rv = 0; + CK_SESSION_HANDLE session = 0; + CK_OBJECT_HANDLE objectHandle = 0; + CK_ULONG objectCount = 0; + NCryptP11ProviderHandle* provider = NULL; + 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: + case NCRYPT_PROPERTY_NAME: + 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) + { + union + { + WCHAR* wc; + BYTE* b; + } cnv; + cnv.b = pbOutput; + if (cbOutput < *pcbResult) + return NTE_NO_MEMORY; + + if (ConvertUtf8ToWChar((char*)slotInfo.slotDescription, cnv.wc, + cbOutput / sizeof(WCHAR)) < 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; + } + 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) + { + union + { + WCHAR* wc; + BYTE* b; + } cnv; + const size_t olen = pbOutput ? cbOutput / sizeof(WCHAR) : 0; + cnv.b = pbOutput; + SSIZE_T size = ConvertUtf8NToWChar(label, attr.ulValueLen, cnv.wc, olen); + if (size < 0) + ret = ERROR_CONVERT_TO_LARGE; + else + ret = ERROR_SUCCESS; + } + } + + free(label); + 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 = 0; + CK_SLOT_ID slotId = 0; + CK_BYTE keyCertId[64] = { 0 }; + CK_ULONG keyCertIdLen = 0; + NCryptP11KeyHandle* keyHandle = NULL; + + 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 = NULL; + CK_RV rv = 0; + + 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); + typedef CK_RV (*c_get_function_list_t)(CK_FUNCTION_LIST_PTR_PTR); + c_get_function_list_t c_get_function_list = NULL; + + 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 = (c_get_function_list_t)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; +} diff --git a/winpr/libwinpr/ncrypt/test/CMakeLists.txt b/winpr/libwinpr/ncrypt/test/CMakeLists.txt new file mode 100644 index 0000000..fbad47a --- /dev/null +++ b/winpr/libwinpr/ncrypt/test/CMakeLists.txt @@ -0,0 +1,29 @@ +set(MODULE_NAME "TestNCrypt") +set(MODULE_PREFIX "TEST_NCRYPT") + +set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c) + +set(${MODULE_PREFIX}_TESTS + TestNCryptSmartcard.c + TestNCryptProviders.c +) + +create_test_sourcelist(${MODULE_PREFIX}_SRCS + ${${MODULE_PREFIX}_DRIVER} + ${${MODULE_PREFIX}_TESTS}) + +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + +set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}") + +foreach(test ${${MODULE_PREFIX}_TESTS}) + get_filename_component(TestName ${test} NAME_WE) + add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName}) +endforeach() + +target_link_libraries(${MODULE_NAME} winpr ${OPENSSL_LIBRARIES}) +if(WIN32) + target_link_libraries(${MODULE_NAME} ncrypt) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test") diff --git a/winpr/libwinpr/ncrypt/test/TestNCryptProviders.c b/winpr/libwinpr/ncrypt/test/TestNCryptProviders.c new file mode 100644 index 0000000..8381e68 --- /dev/null +++ b/winpr/libwinpr/ncrypt/test/TestNCryptProviders.c @@ -0,0 +1,51 @@ +/** + * WinPR: Windows Portable Runtime + * Test for NCrypt library + * + * 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. + */ +#include <winpr/error.h> +#include <winpr/ncrypt.h> +#include <winpr/string.h> +#include <winpr/wlog.h> +#include <winpr/smartcard.h> + +#define TAG "testNCrypt" + +int TestNCryptProviders(int argc, char* argv[]) +{ + SECURITY_STATUS status = 0; + DWORD nproviders = 0; + NCryptProviderName* providers = NULL; + + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + status = NCryptEnumStorageProviders(&nproviders, &providers, NCRYPT_SILENT_FLAG); + if (status != ERROR_SUCCESS) + return -1; + + for (DWORD i = 0; i < nproviders; i++) + { + const NCryptProviderName* provider = &providers[i]; + char providerNameStr[256] = { 0 }; + + ConvertWCharToUtf8(provider->pszName, providerNameStr, ARRAYSIZE(providerNameStr)); + printf("%d: %s\n", i, providerNameStr); + } + + NCryptFreeBuffer(providers); + return 0; +} diff --git a/winpr/libwinpr/ncrypt/test/TestNCryptSmartcard.c b/winpr/libwinpr/ncrypt/test/TestNCryptSmartcard.c new file mode 100644 index 0000000..247a033 --- /dev/null +++ b/winpr/libwinpr/ncrypt/test/TestNCryptSmartcard.c @@ -0,0 +1,156 @@ +/** + * WinPR: Windows Portable Runtime + * Test for NCrypt library + * + * 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. + */ +#include <winpr/error.h> +#include <winpr/ncrypt.h> +#include <winpr/string.h> +#include <winpr/wlog.h> +#include <winpr/smartcard.h> + +#include <openssl/bio.h> +#include <openssl/x509.h> + +#define TAG "testNCrypt" + +static void crypto_print_name(const BYTE* b, DWORD sz) +{ + X509_NAME* name = NULL; + X509* x509 = NULL; + BIO* bio = NULL; + char* ret = NULL; + + bio = BIO_new_mem_buf(b, sz); + if (!bio) + return; + + x509 = d2i_X509_bio(bio, NULL); + if (!x509) + goto bio_release; + + name = X509_get_subject_name(x509); + if (!name) + goto x509_release; + + ret = calloc(1024, sizeof(char)); + if (!ret) + goto bio_release; + + char* ret2 = X509_NAME_oneline(name, ret, 1024); + + printf("\t%s\n", ret2); + free(ret); + +x509_release: + X509_free(x509); +bio_release: + BIO_free(bio); +} + +int TestNCryptSmartcard(int argc, char* argv[]) +{ + SECURITY_STATUS status = 0; + DWORD providerCount = 0; + NCryptProviderName* names = NULL; + + status = NCryptEnumStorageProviders(&providerCount, &names, NCRYPT_SILENT_FLAG); + if (status != ERROR_SUCCESS) + return -1; + + for (size_t j = 0; j < providerCount; j++) + { + const NCryptProviderName* name = &names[j]; + NCRYPT_PROV_HANDLE provider = 0; + char providerNameStr[256] = { 0 }; + PVOID enumState = NULL; + size_t i = 0; + NCryptKeyName* keyName = NULL; + + if (ConvertWCharToUtf8(name->pszName, providerNameStr, ARRAYSIZE(providerNameStr)) < 0) + continue; + printf("provider %" PRIuz ": %s\n", j, providerNameStr); + + status = NCryptOpenStorageProvider(&provider, name->pszName, 0); + if (status != ERROR_SUCCESS) + continue; + + while ((status = NCryptEnumKeys(provider, NULL, &keyName, &enumState, + NCRYPT_SILENT_FLAG)) == ERROR_SUCCESS) + { + NCRYPT_KEY_HANDLE phKey = 0; + DWORD dwFlags = 0; + DWORD cbOutput = 0; + char keyNameStr[256] = { 0 }; + WCHAR reader[1024] = { 0 }; + PBYTE certBytes = NULL; + + if (ConvertWCharToUtf8(keyName->pszName, keyNameStr, ARRAYSIZE(keyNameStr)) < 0) + continue; + + printf("\tkey %" PRIuz ": %s\n", i, keyNameStr); + status = NCryptOpenKey(provider, &phKey, keyName->pszName, keyName->dwLegacyKeySpec, + dwFlags); + if (status != ERROR_SUCCESS) + { + WLog_ERR(TAG, "unable to open key %s", keyNameStr); + continue; + } + + status = NCryptGetProperty(phKey, NCRYPT_READER_PROPERTY, (PBYTE)reader, sizeof(reader), + &cbOutput, dwFlags); + if (status == ERROR_SUCCESS) + { + char readerStr[1024] = { 0 }; + + ConvertWCharNToUtf8(reader, cbOutput, readerStr, ARRAYSIZE(readerStr)); + printf("\treader: %s\n", readerStr); + } + + cbOutput = 0; + status = + NCryptGetProperty(phKey, NCRYPT_CERTIFICATE_PROPERTY, NULL, 0, &cbOutput, dwFlags); + if (status != ERROR_SUCCESS) + { + WLog_ERR(TAG, "unable to retrieve certificate len for key '%s'", keyNameStr); + goto endofloop; + } + + certBytes = calloc(1, cbOutput); + status = NCryptGetProperty(phKey, NCRYPT_CERTIFICATE_PROPERTY, certBytes, cbOutput, + &cbOutput, dwFlags); + if (status != ERROR_SUCCESS) + { + WLog_ERR(TAG, "unable to retrieve certificate for key %s", keyNameStr); + goto endofloop; + } + + crypto_print_name(certBytes, cbOutput); + free(certBytes); + + endofloop: + NCryptFreeBuffer(keyName); + NCryptFreeObject((NCRYPT_HANDLE)phKey); + i++; + } + + NCryptFreeBuffer(enumState); + NCryptFreeObject((NCRYPT_HANDLE)provider); + } + + NCryptFreeBuffer(names); + return 0; +} |