summaryrefslogtreecommitdiffstats
path: root/winpr/libwinpr/ncrypt
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
commita9bcc81f821d7c66f623779fa5147e728eb3c388 (patch)
tree98676963bcdd537ae5908a067a8eb110b93486a6 /winpr/libwinpr/ncrypt
parentInitial commit. (diff)
downloadfreerdp3-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.txt57
-rw-r--r--winpr/libwinpr/ncrypt/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/ncrypt/ncrypt.c347
-rw-r--r--winpr/libwinpr/ncrypt/ncrypt.h94
-rw-r--r--winpr/libwinpr/ncrypt/ncrypt_pkcs11.c1297
-rw-r--r--winpr/libwinpr/ncrypt/test/CMakeLists.txt29
-rw-r--r--winpr/libwinpr/ncrypt/test/TestNCryptProviders.c51
-rw-r--r--winpr/libwinpr/ncrypt/test/TestNCryptSmartcard.c156
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,
+ &currentSession);
+ 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;
+}