diff --git a/winpr/include/winpr/cred.h b/winpr/include/winpr/cred.h index 0c7ce8fee..225563af0 100644 --- a/winpr/include/winpr/cred.h +++ b/winpr/include/winpr/cred.h @@ -22,14 +22,17 @@ #include #ifdef _WIN32 +#include #include #else +#include + #define CERT_HASH_LENGTH 20 typedef enum { - CertCredential, + CertCredential = 1, UsernameTargetCredential, BinaryBlobCredential, UsernameForPackedCredentials, @@ -43,7 +46,91 @@ typedef struct UCHAR rgbHashOfCert[CERT_HASH_LENGTH]; } CERT_CREDENTIAL_INFO, *PCERT_CREDENTIAL_INFO; -#if 0 /* shall we implement these ? */ +typedef struct +{ + LPSTR Keyword; + DWORD Flags; + DWORD ValueSize; + LPBYTE Value; +} CREDENTIAL_ATTRIBUTEA, *PCREDENTIAL_ATTRIBUTEA; + +typedef struct +{ + LPWSTR Keyword; + DWORD Flags; + DWORD ValueSize; + LPBYTE Value; +} CREDENTIAL_ATTRIBUTEW, *PCREDENTIAL_ATTRIBUTEW; + +typedef struct +{ + DWORD Flags; + DWORD Type; + LPSTR TargetName; + LPSTR Comment; + FILETIME LastWritten; + DWORD CredentialBlobSize; + LPBYTE CredentialBlob; + DWORD Persist; + DWORD AttributeCount; + PCREDENTIAL_ATTRIBUTEA Attributes; + LPSTR TargetAlias; + LPSTR UserName; +} CREDENTIALA, *PCREDENTIALA; + +typedef struct +{ + DWORD Flags; + DWORD Type; + LPWSTR TargetName; + LPWSTR Comment; + FILETIME LastWritten; + DWORD CredentialBlobSize; + LPBYTE CredentialBlob; + DWORD Persist; + DWORD AttributeCount; + PCREDENTIAL_ATTRIBUTEA Attributes; + LPWSTR TargetAlias; + LPWSTR UserName; +} CREDENTIALW, *PCREDENTIALW; + +typedef struct +{ + LPSTR TargetName; + LPSTR NetbiosServerName; + LPSTR DnsServerName; + LPSTR NetbiosDomainName; + LPSTR DnsDomainName; + LPSTR DnsTreeName; + LPSTR PackageName; + ULONG Flags; + DWORD CredTypeCount; + LPDWORD CredTypes; +} CREDENTIAL_TARGET_INFORMATIONA, *PCREDENTIAL_TARGET_INFORMATIONA; + +typedef struct +{ + LPWSTR TargetName; + LPWSTR NetbiosServerName; + LPWSTR DnsServerName; + LPWSTR NetbiosDomainName; + LPWSTR DnsDomainName; + LPWSTR DnsTreeName; + LPWSTR PackageName; + ULONG Flags; + DWORD CredTypeCount; + LPDWORD CredTypes; +} CREDENTIAL_TARGET_INFORMATIONW, *PCREDENTIAL_TARGET_INFORMATIONW; + +typedef enum +{ + CredUnprotected, + CredUserProtection, + CredTrustedProtection, + CredForSystemProtection +} CRED_PROTECTION_TYPE, + *PCRED_PROTECTION_TYPE; + WINPR_API BOOL CredMarshalCredentialA(CRED_MARSHAL_TYPE CredType, PVOID Credential, LPSTR* MarshaledCredential); WINPR_API BOOL CredMarshalCredentialW(CRED_MARSHAL_TYPE CredType, PVOID Credential, @@ -55,7 +142,21 @@ WINPR_API BOOL CredMarshalCredentialW(CRED_MARSHAL_TYPE CredType, PVOID Credenti #define CredMarshalCredential CredMarshalCredentialA #endif -#endif /* 0 */ +WINPR_API BOOL CredUnmarshalCredentialW(LPCWSTR cred, PCRED_MARSHAL_TYPE CredType, + PVOID* Credential); + +WINPR_API BOOL CredUnmarshalCredentialA(LPCSTR cred, PCRED_MARSHAL_TYPE CredType, + PVOID* Credential); + +#ifdef UNICODE +#define CredUnmarshalCredential CredUnmarshalCredentialW +#else +#define CredUnmarshalCredential CredUnmarshalCredentialA +#endif + +WINPR_API BOOL CredIsMarshaledCredentialA(LPCSTR MarshaledCredential); +WINPR_API BOOL CredIsMarshaledCredentialW(LPCWSTR MarshaledCredential); +WINPR_API VOID CredFree(PVOID Buffer); #endif /* _WIN32 */ diff --git a/winpr/libwinpr/CMakeLists.txt b/winpr/libwinpr/CMakeLists.txt index dacb79d24..4864caf49 100644 --- a/winpr/libwinpr/CMakeLists.txt +++ b/winpr/libwinpr/CMakeLists.txt @@ -165,6 +165,7 @@ set(WINPR_CORE library file comm + credentials pipe interlocked security diff --git a/winpr/libwinpr/credentials/CMakeLists.txt b/winpr/libwinpr/credentials/CMakeLists.txt new file mode 100644 index 000000000..b71a3cf2c --- /dev/null +++ b/winpr/libwinpr/credentials/CMakeLists.txt @@ -0,0 +1,22 @@ +# WinPR: Windows Portable Runtime +# libwinpr-credentials cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +winpr_module_add(credentials.c) + +if(BUILD_TESTING_INTERNAL OR BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/winpr/libwinpr/credentials/ModuleOptions.cmake b/winpr/libwinpr/credentials/ModuleOptions.cmake new file mode 100644 index 000000000..f8c36d4ae --- /dev/null +++ b/winpr/libwinpr/credentials/ModuleOptions.cmake @@ -0,0 +1,9 @@ +set(MINWIN_LAYER "1") +set(MINWIN_GROUP "security") +set(MINWIN_MAJOR_VERSION "1") +set(MINWIN_MINOR_VERSION "0") +set(MINWIN_SHORT_NAME "credentials") +set(MINWIN_LONG_NAME "Credentials Management Functions") +set(MODULE_LIBRARY_NAME + "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}" +) diff --git a/winpr/libwinpr/credentials/credentials.c b/winpr/libwinpr/credentials/credentials.c new file mode 100644 index 000000000..f7e4810a0 --- /dev/null +++ b/winpr/libwinpr/credentials/credentials.c @@ -0,0 +1,295 @@ +/** + * WinPR: Windows Portable Runtime + * Credentials Management + * + * Copyright 2012 Marc-Andre Moreau + * Copyright 2025 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 "../log.h" + +#define TAG WINPR_TAG("Cred") + +/* + * Low-Level Credentials Management Functions: + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa374731(v=vs.85).aspx#low_level_credentials_management_functions + */ + +#ifndef _WIN32 + +static BYTE wchar_decode(WCHAR c) +{ + if (c >= 'A' && c <= 'Z') + return (BYTE)(c - 'A'); + if (c >= 'a' && c <= 'z') + return (BYTE)(c - 'a' + 26); + if (c >= '0' && c <= '9') + return (BYTE)(c - '0' + 52); + if (c == '#') + return 62; + if (c == '-') + return 63; + return 64; +} + +static BOOL cred_decode(const WCHAR* cred, size_t len, BYTE* buf) +{ + size_t i = 0; + const WCHAR* p = cred; + + while (len >= 4) + { + BYTE c0 = wchar_decode(p[0]); + if (c0 > 63) + return FALSE; + + BYTE c1 = wchar_decode(p[1]); + if (c1 > 63) + return FALSE; + + BYTE c2 = wchar_decode(p[2]); + if (c2 > 63) + return FALSE; + + BYTE c3 = wchar_decode(p[3]); + if (c3 > 63) + return FALSE; + + buf[i + 0] = (BYTE)((c1 << 6) | c0); + buf[i + 1] = (BYTE)((c2 << 4) | (c1 >> 2)); + buf[i + 2] = (BYTE)((c3 << 2) | (c2 >> 4)); + len -= 4; + i += 3; + p += 4; + } + + if (len == 3) + { + BYTE c0 = wchar_decode(p[0]); + if (c0 > 63) + return FALSE; + + BYTE c1 = wchar_decode(p[1]); + if (c1 > 63) + return FALSE; + + BYTE c2 = wchar_decode(p[2]); + if (c2 > 63) + return FALSE; + + buf[i + 0] = (BYTE)((c1 << 6) | c0); + buf[i + 1] = (BYTE)((c2 << 4) | (c1 >> 2)); + } + else if (len == 2) + { + BYTE c0 = wchar_decode(p[0]); + if (c0 > 63) + return FALSE; + + BYTE c1 = wchar_decode(p[1]); + if (c1 > 63) + return FALSE; + + buf[i + 0] = (BYTE)((c1 << 6) | c0); + } + else if (len == 1) + { + WLog_ERR(TAG, "invalid string length"); + return FALSE; + } + return TRUE; +} + +static size_t cred_encode(const BYTE* bin, size_t len, WCHAR* cred) +{ + static const WCHAR encodingChars[] = { + /* ABCDEFGHIJKLMNOPQRSTUVWXYZ */ + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, + /* abcdefghijklmnopqrstuvwxyz */ + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, + /* 0123456789 */ + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + /* #- */ + 0x23, 0x2d + }; + size_t n = 0; + + while (len > 0) + { + cred[n++] = encodingChars[bin[0] & 0x3f]; + BYTE x = (bin[0] & 0xc0) >> 6; + if (len == 1) + { + cred[n++] = encodingChars[x]; + break; + } + + cred[n++] = encodingChars[((bin[1] & 0xf) << 2) | x]; + x = (bin[1] & 0xf0) >> 4; + if (len == 2) + { + cred[n++] = encodingChars[x]; + break; + } + + cred[n++] = encodingChars[((bin[2] & 0x3) << 4) | x]; + cred[n++] = encodingChars[(bin[2] & 0xfc) >> 2]; + bin += 3; + len -= 3; + } + return n; +} + +BOOL CredMarshalCredentialW(CRED_MARSHAL_TYPE CredType, PVOID cred, LPWSTR* MarshaledCredential) +{ + CERT_CREDENTIAL_INFO* cert = cred; + WCHAR* p = NULL; + + if (!cred || (CredType == CertCredential && cert->cbSize < sizeof(*cert))) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + switch (CredType) + { + case CertCredential: + { + size_t size = (sizeof(cert->rgbHashOfCert) + 2) * 4 / 3; + if (!(p = malloc((size + 4) * sizeof(WCHAR)))) + return FALSE; + p[0] = '@'; + p[1] = '@'; + p[2] = (WCHAR)('A' + CredType); + size_t len = cred_encode(cert->rgbHashOfCert, sizeof(cert->rgbHashOfCert), p + 3); + p[len + 3] = 0; + break; + } + default: + WLog_ERR(TAG, "unhandled type 0x%x", CredType); + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + *MarshaledCredential = p; + return TRUE; +} + +BOOL CredMarshalCredentialA(CRED_MARSHAL_TYPE CredType, PVOID Credential, + LPSTR* MarshaledCredential) +{ + WCHAR* b = NULL; + if (!CredMarshalCredentialW(CredType, Credential, &b) || !b) + return FALSE; + + *MarshaledCredential = ConvertWCharNToUtf8Alloc(b, _wcslen(b), NULL); + free(b); + return (*MarshaledCredential != NULL); +} + +BOOL CredUnmarshalCredentialW(LPCWSTR cred, PCRED_MARSHAL_TYPE pcredType, PVOID* out) +{ + if (!cred || !pcredType || !out || cred[0] != '@' || cred[1] != '@') + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + BYTE b = wchar_decode(cred[2]); + if (!b || b > BinaryBlobForSystem) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + *pcredType = (CRED_MARSHAL_TYPE)b; + + size_t len = _wcslen(cred + 3); + switch (*pcredType) + { + case CertCredential: + { + BYTE hash[CERT_HASH_LENGTH]; + + if (len != 27 || !cred_decode(cred + 3, len, hash)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + CERT_CREDENTIAL_INFO* cert = malloc(sizeof(*cert)); + if (!cert) + return FALSE; + + memcpy(cert->rgbHashOfCert, hash, sizeof(cert->rgbHashOfCert)); + cert->cbSize = sizeof(*cert); + *out = cert; + break; + } + default: + WLog_ERR(TAG, "unhandled credType 0x%x", *pcredType); + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + return TRUE; +} + +BOOL CredUnmarshalCredentialA(LPCSTR cred, PCRED_MARSHAL_TYPE CredType, PVOID* Credential) +{ + WCHAR* b = ConvertUtf8NToWCharAlloc(cred, strlen(cred), NULL); + if (!b) + return FALSE; + + BOOL ret = CredUnmarshalCredentialW(b, CredType, Credential); + free(b); + return ret; +} + +BOOL CredIsMarshaledCredentialW(LPCWSTR MarshaledCredential) +{ + CRED_MARSHAL_TYPE t = BinaryBlobForSystem; + void* out = NULL; + + BOOL ret = CredUnmarshalCredentialW(MarshaledCredential, &t, &out); + if (out) + CredFree(out); + + return ret; +} + +BOOL CredIsMarshaledCredentialA(LPCSTR MarshaledCredential) +{ + CRED_MARSHAL_TYPE t = BinaryBlobForSystem; + void* out = NULL; + BOOL ret = CredUnmarshalCredentialA(MarshaledCredential, &t, &out); + if (out) + CredFree(out); + + return ret; +} + +VOID CredFree(PVOID Buffer) +{ + free(Buffer); +} + +#endif /* _WIN32 */ diff --git a/winpr/libwinpr/credentials/test/CMakeLists.txt b/winpr/libwinpr/credentials/test/CMakeLists.txt new file mode 100644 index 000000000..89bd53909 --- /dev/null +++ b/winpr/libwinpr/credentials/test/CMakeLists.txt @@ -0,0 +1,23 @@ +set(MODULE_NAME "TestCredentials") +set(MODULE_PREFIX "TEST_CREDENTIALS") + +disable_warnings_for_directory(${CMAKE_CURRENT_BINARY_DIR}) + +set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c) + +set(${MODULE_PREFIX}_TESTS TestMarshalUnmarshal.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}) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test") diff --git a/winpr/libwinpr/credentials/test/TestMarshalUnmarshal.c b/winpr/libwinpr/credentials/test/TestMarshalUnmarshal.c new file mode 100644 index 000000000..501342bca --- /dev/null +++ b/winpr/libwinpr/credentials/test/TestMarshalUnmarshal.c @@ -0,0 +1,91 @@ +/** + * WinPR: Windows Portable Runtime + * Buffer Manipulation + * + * Copyright 2025 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 + +typedef struct +{ + LPCSTR marshalled; + BYTE source[CERT_HASH_LENGTH]; +} TestItem; + +static TestItem testValues[] = { { "@@BQ9eNR0KWVU-CT8sPCp8z37POZHJ", + { 0x50, 0xef, 0x35, 0x11, 0xad, 0x58, 0x15, 0xf5, 0x0b, 0x13, + 0xcf, 0x3e, 0x42, 0xca, 0xcf, 0xf7, 0xfe, 0x38, 0xd9, 0x91 } }, + { "@@BKay-HwJsFZzclXAWZ#nO6Eluc7P", + { 0x8a, 0x26, 0xff, 0x07, 0x9c, 0xb0, 0x45, 0x36, 0x73, 0xe5, + 0x05, 0x58, 0x99, 0x7f, 0x3a, 0x3a, 0x51, 0xba, 0xdc, 0xfe } + + } }; + +static int TestUnmarshal(int argc, char** argv) +{ + + for (int i = 0; i < ARRAYSIZE(testValues); i++) + { + CRED_MARSHAL_TYPE t = BinaryBlobForSystem; + CERT_CREDENTIAL_INFO* certInfo = NULL; + + if (!CredUnmarshalCredentialA(testValues[i].marshalled, &t, &certInfo) || !certInfo || + t != CertCredential) + return -1; + + BOOL ok = memcmp(testValues[i].source, certInfo->rgbHashOfCert, + sizeof(certInfo->rgbHashOfCert)) == 0; + + free(certInfo); + + if (!ok) + return -1; + } + return 0; +} + +static int TestMarshal(int argc, char** argv) +{ + + for (int i = 0; i < ARRAYSIZE(testValues); i++) + { + CRED_MARSHAL_TYPE t = BinaryBlobForSystem; + CERT_CREDENTIAL_INFO certInfo = { sizeof(certInfo), { 0 } }; + memcpy(certInfo.rgbHashOfCert, testValues[i].source, sizeof(certInfo.rgbHashOfCert)); + LPSTR out = NULL; + + if (!CredMarshalCredentialA(CertCredential, &certInfo, &out) || !out) + return -1; + + BOOL ok = (strcmp(testValues[i].marshalled, out) == 0); + + free(out); + + if (!ok) + return -1; + } + return 0; +} + +int TestMarshalUnmarshal(int argc, char** argv) +{ + int ret = TestUnmarshal(argc, argv); + if (ret) + return ret; + + ret = TestMarshal(argc, argv); + return ret; +}