Milestone 5: deliver embedded RDP sessions and lifecycle hardening

This commit is contained in:
Keith Smith
2026-03-03 18:59:26 -07:00
parent 230a401386
commit 36006bd4aa
2941 changed files with 724359 additions and 77 deletions

View File

@@ -0,0 +1,22 @@
# WinPR: Windows Portable Runtime
# libwinpr-credentials cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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.
winpr_module_add(credentials.c)
if(BUILD_TESTING_INTERNAL OR BUILD_TESTING)
add_subdirectory(test)
endif()

View File

@@ -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}"
)

View File

@@ -0,0 +1,303 @@
/**
* WinPR: Windows Portable Runtime
* Credentials Management
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2025 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/config.h>
#include <winpr/crt.h>
#include <winpr/cred.h>
#include <winpr/error.h>
#include <winpr/wlog.h>
#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 char_decode(char 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 char* cred, size_t len, BYTE* buf)
{
size_t i = 0;
const char* p = cred;
while (len >= 4)
{
BYTE c0 = char_decode(p[0]);
if (c0 > 63)
return FALSE;
BYTE c1 = char_decode(p[1]);
if (c1 > 63)
return FALSE;
BYTE c2 = char_decode(p[2]);
if (c2 > 63)
return FALSE;
BYTE c3 = char_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 = char_decode(p[0]);
if (c0 > 63)
return FALSE;
BYTE c1 = char_decode(p[1]);
if (c1 > 63)
return FALSE;
BYTE c2 = char_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 = char_decode(p[0]);
if (c0 > 63)
return FALSE;
BYTE c1 = char_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, char* cred, size_t credlen)
{
static const char encodingChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789"
"#-";
size_t n = 0;
size_t offset = 0;
while (offset < len)
{
if (n >= credlen)
break;
cred[n++] = encodingChars[bin[offset] & 0x3f];
BYTE x = (bin[offset] & 0xc0) >> 6;
offset++;
if (offset >= len)
{
cred[n++] = encodingChars[x];
break;
}
if (n >= credlen)
break;
cred[n++] = encodingChars[((bin[offset] & 0xf) << 2) | x];
x = (bin[offset] & 0xf0) >> 4;
offset++;
if (offset >= len)
{
cred[n++] = encodingChars[x];
break;
}
if (n >= credlen)
break;
cred[n++] = encodingChars[((bin[offset] & 0x3) << 4) | x];
if (n >= credlen)
break;
cred[n++] = encodingChars[(bin[offset] & 0xfc) >> 2];
offset++;
}
return n;
}
BOOL CredMarshalCredentialW(CRED_MARSHAL_TYPE CredType, PVOID Credential,
LPWSTR* MarshaledCredential)
{
char* b = nullptr;
if (!CredMarshalCredentialA(CredType, Credential, &b) || !b)
return FALSE;
*MarshaledCredential = ConvertUtf8ToWCharAlloc(b, nullptr);
free(b);
return (*MarshaledCredential != nullptr);
}
BOOL CredMarshalCredentialA(CRED_MARSHAL_TYPE CredType, PVOID Credential,
LPSTR* MarshaledCredential)
{
CERT_CREDENTIAL_INFO* cert = Credential;
if (!cert || ((CredType == CertCredential) && (cert->cbSize < sizeof(CERT_CREDENTIAL_INFO))) ||
!MarshaledCredential)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
switch (CredType)
{
case CertCredential:
{
char buffer[3ULL + (sizeof(cert->rgbHashOfCert) * 4 / 3) + 1ULL /* rounding error */] =
WINPR_C_ARRAY_INIT;
const char c = WINPR_ASSERTING_INT_CAST(char, 'A' + CredType);
(void)_snprintf(buffer, sizeof(buffer), "@@%c", c);
size_t len = cred_encode(cert->rgbHashOfCert, sizeof(cert->rgbHashOfCert), &buffer[3],
sizeof(buffer) - 3);
*MarshaledCredential = strndup(buffer, len + 3);
return TRUE;
}
default:
WLog_ERR(TAG, "unhandled type 0x%x", CredType);
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
}
BOOL CredUnmarshalCredentialW(LPCWSTR cred, PCRED_MARSHAL_TYPE CredType, PVOID* Credential)
{
char* str = nullptr;
if (cred)
str = ConvertWCharToUtf8Alloc(cred, nullptr);
const BOOL rc = CredUnmarshalCredentialA(str, CredType, Credential);
free(str);
return rc;
}
BOOL CredUnmarshalCredentialA(LPCSTR cred, PCRED_MARSHAL_TYPE CredType, PVOID* Credential)
{
if (!cred)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
const size_t len = strlen(cred);
if ((len < 3) || !CredType || !Credential || (cred[0] != '@') || (cred[1] != '@'))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
BYTE b = char_decode(cred[2]);
if (!b || (b > BinaryBlobForSystem))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
*CredType = (CRED_MARSHAL_TYPE)b;
switch (*CredType)
{
case CertCredential:
{
BYTE hash[CERT_HASH_LENGTH] = WINPR_C_ARRAY_INIT;
if ((len != 30) || !cred_decode(&cred[3], len - 3, hash))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
CERT_CREDENTIAL_INFO* cert = calloc(1, sizeof(CERT_CREDENTIAL_INFO));
if (!cert)
return FALSE;
cert->cbSize = sizeof(CERT_CREDENTIAL_INFO);
memcpy(cert->rgbHashOfCert, hash, sizeof(cert->rgbHashOfCert));
*Credential = cert;
break;
}
default:
WLog_ERR(TAG, "unhandled credType 0x%x", *CredType);
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
return TRUE;
}
BOOL CredIsMarshaledCredentialW(LPCWSTR MarshaledCredential)
{
CRED_MARSHAL_TYPE t = BinaryBlobForSystem;
void* out = nullptr;
BOOL ret = CredUnmarshalCredentialW(MarshaledCredential, &t, &out);
if (out)
CredFree(out);
return ret;
}
BOOL CredIsMarshaledCredentialA(LPCSTR MarshaledCredential)
{
CRED_MARSHAL_TYPE t = BinaryBlobForSystem;
void* out = nullptr;
BOOL ret = CredUnmarshalCredentialA(MarshaledCredential, &t, &out);
if (out)
CredFree(out);
return ret;
}
VOID CredFree(PVOID Buffer)
{
free(Buffer);
}
#endif /* _WIN32 */

View File

@@ -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")

View File

@@ -0,0 +1,95 @@
/**
* WinPR: Windows Portable Runtime
* Buffer Manipulation
*
* Copyright 2025 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 <string.h>
#include <winpr/cred.h>
typedef struct
{
LPCSTR marshalled;
BYTE source[CERT_HASH_LENGTH];
} TestItem;
static const 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(WINPR_ATTR_UNUSED int argc, WINPR_ATTR_UNUSED char** argv)
{
for (size_t i = 0; i < ARRAYSIZE(testValues); i++)
{
CRED_MARSHAL_TYPE t = BinaryBlobForSystem;
CERT_CREDENTIAL_INFO* certInfo = nullptr;
const TestItem* const val = &testValues[i];
if (!CredUnmarshalCredentialA(val->marshalled, &t, (void**)&certInfo) || !certInfo ||
(t != CertCredential))
return -1;
const BOOL ok =
memcmp(val->source, certInfo->rgbHashOfCert, sizeof(certInfo->rgbHashOfCert)) == 0;
free(certInfo);
if (!ok)
return -1;
}
return 0;
}
static int TestMarshal(WINPR_ATTR_UNUSED int argc, WINPR_ATTR_UNUSED char** argv)
{
for (size_t i = 0; i < ARRAYSIZE(testValues); i++)
{
CERT_CREDENTIAL_INFO certInfo = { sizeof(certInfo), { 0 } };
const TestItem* const val = &testValues[i];
memcpy(certInfo.rgbHashOfCert, val->source, sizeof(certInfo.rgbHashOfCert));
LPSTR out = nullptr;
if (!CredMarshalCredentialA(CertCredential, &certInfo, &out) || !out)
return -1;
BOOL ok = (strcmp(val->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;
}