/** * WinPR: Windows Portable Runtime * NCrypt pkcs11 provider * * Copyright 2021 David Fort * * 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 #include #include #include #include #include #include #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; WINPR_ASSERT(provider); rv = provider->p11->C_Finalize(NULL); if (rv != CKR_OK) { } if (provider->library) FreeLibrary(provider->library); return winpr_NCryptDefault_dtor(handle); } static void fix_padded_string(char* str, size_t maxlen) { char* ptr = str + maxlen - 1; while (ptr > str && *ptr == ' ') ptr--; ptr++; *ptr = 0; } static BOOL attributes_have_unallocated_buffers(CK_ATTRIBUTE_PTR attributes, CK_ULONG count) { CK_ULONG i; for (i = 0; i < count; i++) { if (!attributes[i].pValue && (attributes[i].ulValueLen != CK_UNAVAILABLE_INFORMATION)) return TRUE; } return FALSE; } static BOOL attribute_allocate_attribute_array(CK_ATTRIBUTE_PTR attribute) { attribute->pValue = calloc(attribute->ulValueLen, sizeof(void*)); return !!attribute->pValue; } static BOOL attribute_allocate_ulong_array(CK_ATTRIBUTE_PTR attribute) { attribute->pValue = calloc(attribute->ulValueLen, sizeof(CK_ULONG)); return !!attribute->pValue; } static BOOL attribute_allocate_buffer(CK_ATTRIBUTE_PTR attribute) { attribute->pValue = calloc(attribute->ulValueLen, 1); return !!attribute->pValue; } static BOOL attributes_allocate_buffers(CK_ATTRIBUTE_PTR attributes, CK_ULONG count) { CK_ULONG i; BOOL ret = TRUE; for (i = 0; i < count; i++) { if (attributes[i].pValue || (attributes[i].ulValueLen == CK_UNAVAILABLE_INFORMATION)) continue; switch (attributes[i].type) { case CKA_WRAP_TEMPLATE: case CKA_UNWRAP_TEMPLATE: ret &= attribute_allocate_attribute_array(&attributes[i]); break; case CKA_ALLOWED_MECHANISMS: ret &= attribute_allocate_ulong_array(&attributes[i]); break; default: ret &= attribute_allocate_buffer(&attributes[i]); break; } } return ret; } static CK_RV object_load_attributes(NCryptP11ProviderHandle* provider, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, CK_ATTRIBUTE_PTR attributes, CK_ULONG count) { CK_RV rv; WINPR_ASSERT(provider); WINPR_ASSERT(provider->p11); WINPR_ASSERT(provider->p11->C_GetAttributeValue); rv = provider->p11->C_GetAttributeValue(session, object, attributes, count); switch (rv) { case CKR_OK: if (!attributes_have_unallocated_buffers(attributes, count)) return rv; /* 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; 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; CK_ULONG i; *ptr = '\\'; ptr++; bytePtr = ((CK_BYTE*)&slotId); for (i = 0; i < sizeof(slotId); i++, bytePtr++, ptr += 2) snprintf(ptr, 3, "%.2x", *bytePtr); *ptr = '\\'; ptr++; for (i = 0; i < idLen; i++, id++, ptr += 2) snprintf(ptr, 3, "%.2x", *id); 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 (ret = 0; str != end && *str; str++, ret++, target++) { CK_BYTE v = 0; if (*str <= '9' && *str >= '0') { v = (*str - '0'); } else if (*str <= 'f' && *str >= 'a') { v = (10 + *str - 'a'); } else if (*str <= 'F' && *str >= 'A') { v |= (10 + *str - 'A'); } else { return 0; } v <<= 4; str++; if (!*str || str == end) return 0; if (*str <= '9' && *str >= '0') { v |= (*str - '0'); } else if (*str <= 'f' && *str >= 'a') { v |= (10 + *str - 'a'); } else if (*str <= 'F' && *str >= 'A') { v |= (10 + *str - 'A'); } else { return 0; } *target = v; } return ret; } static SECURITY_STATUS parseKeyName(LPCWSTR pszKeyName, CK_SLOT_ID* slotId, CK_BYTE* id, CK_ULONG* idLen) { char asciiKeyName[128] = { 0 }; char* pos; if (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 \\.\\ for filtering by * card reader */ char asciiScope[128 + 6 + 1] = { 0 }; size_t asciiScopeLen; 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; CK_OBJECT_HANDLE certObject; /* 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 + "\\" + keyName->pszAlgid */ DWORD algoSz; 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; CK_SESSION_HANDLE session; CK_OBJECT_HANDLE objectHandle; CK_ULONG objectCount; NCryptP11ProviderHandle* provider; CK_OBJECT_CLASS oclass = CKO_CERTIFICATE; CK_CERTIFICATE_TYPE ctype = CKC_X_509; CK_ATTRIBUTE certificateFilter[] = { { CKA_CLASS, &oclass, sizeof(oclass) }, { CKA_CERTIFICATE_TYPE, &ctype, sizeof(ctype) }, { CKA_ID, keyHandle->keyCertId, keyHandle->keyCertIdLen } }; CK_ATTRIBUTE* objectFilter = certificateFilter; CK_ULONG objectFilterLen = ARRAYSIZE(certificateFilter); WINPR_ASSERT(keyHandle); provider = keyHandle->provider; WINPR_ASSERT(provider); switch (property) { case NCRYPT_PROPERTY_CERTIFICATE: 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; CK_SLOT_ID slotId; CK_BYTE keyCertId[64] = { 0 }; CK_ULONG keyCertIdLen; NCryptP11KeyHandle* keyHandle; ret = parseKeyName(pszKeyName, &slotId, keyCertId, &keyCertIdLen); if (ret != ERROR_SUCCESS) return ret; keyHandle = (NCryptP11KeyHandle*)ncrypt_new_handle( WINPR_NCRYPT_KEY, sizeof(*keyHandle), NCryptP11GetProperty, winpr_NCryptDefault_dtor); if (!keyHandle) return NTE_NO_MEMORY; keyHandle->provider = (NCryptP11ProviderHandle*)hProvider; keyHandle->slotId = slotId; memcpy(keyHandle->keyCertId, keyCertId, sizeof(keyCertId)); keyHandle->keyCertIdLen = keyCertIdLen; *phKey = (NCRYPT_KEY_HANDLE)keyHandle; return ERROR_SUCCESS; } static SECURITY_STATUS initialize_pkcs11(HANDLE handle, CK_RV (*c_get_function_list)(CK_FUNCTION_LIST_PTR_PTR), NCRYPT_PROV_HANDLE* phProvider) { SECURITY_STATUS status = ERROR_SUCCESS; NCryptP11ProviderHandle* ret; CK_RV rv; WINPR_ASSERT(c_get_function_list); WINPR_ASSERT(phProvider); ret = (NCryptP11ProviderHandle*)ncrypt_new_handle( WINPR_NCRYPT_PROVIDER, sizeof(*ret), NCryptP11GetProperty, NCryptP11StorageProvider_dtor); if (!ret) { if (handle) FreeLibrary(handle); return NTE_NO_MEMORY; } ret->library = handle; ret->baseProvider.enumKeysFn = NCryptP11EnumKeys; ret->baseProvider.openKeyFn = NCryptP11OpenKey; rv = c_get_function_list(&ret->p11); if (rv != CKR_OK) { status = NTE_PROVIDER_DLL_FAIL; goto fail; } WINPR_ASSERT(ret->p11->C_Initialize); rv = ret->p11->C_Initialize(NULL); if (rv != CKR_OK) { status = NTE_PROVIDER_DLL_FAIL; goto fail; } *phProvider = (NCRYPT_PROV_HANDLE)ret; fail: if (status != ERROR_SUCCESS) ret->baseProvider.baseHandle.releaseFn((NCRYPT_HANDLE)ret); return status; } SECURITY_STATUS NCryptOpenP11StorageProviderEx(NCRYPT_PROV_HANDLE* phProvider, LPCWSTR pszProviderName, DWORD dwFlags, LPCSTR* modulePaths) { SECURITY_STATUS status = ERROR_INVALID_PARAMETER; #if defined(__LP64__) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || \ defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) #define LIBS64 #endif LPCSTR openscPaths[] = { "opensc-pkcs11.so", /* In case winpr is installed in system paths */ #ifdef __APPLE__ "/usr/local/lib/pkcs11/opensc-pkcs11.so", #else /* linux and UNIXes */ #ifdef LIBS64 "/usr/lib/x86_64-linux-gnu/pkcs11/opensc-pkcs11.so", /* Ubuntu/debian */ "/lib64/pkcs11/opensc-pkcs11.so", /* Fedora */ #else "/usr/lib/i386-linux-gnu/opensc-pkcs11.so", /* debian */ "/lib32/pkcs11/opensc-pkcs11.so", /* Fedora */ #endif #endif NULL }; if (!phProvider) return ERROR_INVALID_PARAMETER; #if defined(WITH_OPENSC_PKCS11_LINKED) if (!modulePaths) return initialize_pkcs11(NULL, C_GetFunctionList, phProvider); #endif if (!modulePaths) modulePaths = openscPaths; while (*modulePaths) { HANDLE library = LoadLibrary(*modulePaths); typedef CK_RV (*c_get_function_list_t)(CK_FUNCTION_LIST_PTR_PTR); c_get_function_list_t c_get_function_list; 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; }