Merge pull request #11790 from akallabeth/fix-cred-marshal

[winpr,credentials] prefer utf-8 over utf-16-LE
This commit is contained in:
akallabeth
2025-08-20 09:43:02 +02:00
committed by GitHub
3 changed files with 145 additions and 113 deletions

View File

@@ -34,7 +34,7 @@
#ifndef _WIN32
static BYTE wchar_decode(WCHAR c)
static BYTE char_decode(char c)
{
if (c >= 'A' && c <= 'Z')
return (BYTE)(c - 'A');
@@ -49,26 +49,26 @@ static BYTE wchar_decode(WCHAR c)
return 64;
}
static BOOL cred_decode(const WCHAR* cred, size_t len, BYTE* buf)
static BOOL cred_decode(const char* cred, size_t len, BYTE* buf)
{
size_t i = 0;
const WCHAR* p = cred;
const char* p = cred;
while (len >= 4)
{
BYTE c0 = wchar_decode(p[0]);
BYTE c0 = char_decode(p[0]);
if (c0 > 63)
return FALSE;
BYTE c1 = wchar_decode(p[1]);
BYTE c1 = char_decode(p[1]);
if (c1 > 63)
return FALSE;
BYTE c2 = wchar_decode(p[2]);
BYTE c2 = char_decode(p[2]);
if (c2 > 63)
return FALSE;
BYTE c3 = wchar_decode(p[3]);
BYTE c3 = char_decode(p[3]);
if (c3 > 63)
return FALSE;
@@ -82,15 +82,15 @@ static BOOL cred_decode(const WCHAR* cred, size_t len, BYTE* buf)
if (len == 3)
{
BYTE c0 = wchar_decode(p[0]);
BYTE c0 = char_decode(p[0]);
if (c0 > 63)
return FALSE;
BYTE c1 = wchar_decode(p[1]);
BYTE c1 = char_decode(p[1]);
if (c1 > 63)
return FALSE;
BYTE c2 = wchar_decode(p[2]);
BYTE c2 = char_decode(p[2]);
if (c2 > 63)
return FALSE;
@@ -99,11 +99,11 @@ static BOOL cred_decode(const WCHAR* cred, size_t len, BYTE* buf)
}
else if (len == 2)
{
BYTE c0 = wchar_decode(p[0]);
BYTE c0 = char_decode(p[0]);
if (c0 > 63)
return FALSE;
BYTE c1 = wchar_decode(p[1]);
BYTE c1 = char_decode(p[1]);
if (c1 > 63)
return FALSE;
@@ -117,54 +117,72 @@ static BOOL cred_decode(const WCHAR* cred, size_t len, BYTE* buf)
return TRUE;
}
static size_t cred_encode(const BYTE* bin, size_t len, WCHAR* cred)
static size_t cred_encode(const BYTE* bin, size_t len, char* cred, size_t credlen)
{
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
};
static const char encodingChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789"
"#-";
size_t n = 0;
while (len > 0)
size_t offset = 0;
while (offset < len)
{
cred[n++] = encodingChars[bin[0] & 0x3f];
BYTE x = (bin[0] & 0xc0) >> 6;
if (len == 1)
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;
}
cred[n++] = encodingChars[((bin[1] & 0xf) << 2) | x];
x = (bin[1] & 0xf0) >> 4;
if (len == 2)
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;
}
cred[n++] = encodingChars[((bin[2] & 0x3) << 4) | x];
cred[n++] = encodingChars[(bin[2] & 0xfc) >> 2];
bin += 3;
len -= 3;
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 cred, LPWSTR* MarshaledCredential)
BOOL CredMarshalCredentialW(CRED_MARSHAL_TYPE CredType, PVOID Credential,
LPWSTR* MarshaledCredential)
{
CERT_CREDENTIAL_INFO* cert = cred;
WCHAR* p = NULL;
char* b = NULL;
if (!CredMarshalCredentialA(CredType, Credential, &b) || !b)
return FALSE;
if (!cred || (CredType == CertCredential && cert->cbSize < sizeof(*cert)))
*MarshaledCredential = ConvertUtf8ToWCharAlloc(b, NULL);
free(b);
return (*MarshaledCredential != NULL);
}
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;
@@ -174,96 +192,86 @@ BOOL CredMarshalCredentialW(CRED_MARSHAL_TYPE CredType, PVOID cred, LPWSTR* Mars
{
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;
char buffer[3ULL + (sizeof(cert->rgbHashOfCert) * 4 / 3) +
1ULL /* rounding error */] = { 0 };
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;
}
*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] != '@')
char* str = NULL;
if (cred)
str = ConvertWCharToUtf8Alloc(cred, NULL);
const BOOL rc = CredUnmarshalCredentialA(str, pcredType, out);
free(str);
return rc;
}
BOOL CredUnmarshalCredentialA(LPCSTR cred, PCRED_MARSHAL_TYPE CredType, PVOID* Credential)
{
if (!cred)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
BYTE b = wchar_decode(cred[2]);
if (!b || b > BinaryBlobForSystem)
const size_t len = strlen(cred);
if ((len < 3) || !CredType || !Credential || (cred[0] != '@') || (cred[1] != '@'))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
*pcredType = (CRED_MARSHAL_TYPE)b;
BYTE b = char_decode(cred[2]);
if (!b || (b > BinaryBlobForSystem))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
size_t len = _wcslen(cred + 3);
switch (*pcredType)
*CredType = (CRED_MARSHAL_TYPE)b;
switch (*CredType)
{
case CertCredential:
{
BYTE hash[CERT_HASH_LENGTH];
BYTE hash[CERT_HASH_LENGTH] = { 0 };
if (len != 27 || !cred_decode(cred + 3, len, hash))
if ((len != 30) || !cred_decode(&cred[3], len - 3, hash))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
CERT_CREDENTIAL_INFO* cert = malloc(sizeof(*cert));
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));
cert->cbSize = sizeof(*cert);
*out = cert;
*Credential = cert;
break;
}
default:
WLog_ERR(TAG, "unhandled credType 0x%x", *pcredType);
WLog_ERR(TAG, "unhandled credType 0x%x", *CredType);
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;

View File

@@ -25,29 +25,32 @@ typedef struct
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 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(int argc, char** argv)
static int TestUnmarshal(WINPR_ATTR_UNUSED int argc, WINPR_ATTR_UNUSED char** argv)
{
for (int i = 0; i < ARRAYSIZE(testValues); i++)
for (size_t i = 0; i < ARRAYSIZE(testValues); i++)
{
CRED_MARSHAL_TYPE t = BinaryBlobForSystem;
CERT_CREDENTIAL_INFO* certInfo = NULL;
const TestItem* const val = &testValues[i];
if (!CredUnmarshalCredentialA(testValues[i].marshalled, &t, (void**)&certInfo) ||
!certInfo || t != CertCredential)
if (!CredUnmarshalCredentialA(val->marshalled, &t, (void**)&certInfo) || !certInfo ||
(t != CertCredential))
return -1;
BOOL ok = memcmp(testValues[i].source, certInfo->rgbHashOfCert,
sizeof(certInfo->rgbHashOfCert)) == 0;
const BOOL ok =
memcmp(val->source, certInfo->rgbHashOfCert, sizeof(certInfo->rgbHashOfCert)) == 0;
free(certInfo);
@@ -57,20 +60,21 @@ static int TestUnmarshal(int argc, char** argv)
return 0;
}
static int TestMarshal(int argc, char** argv)
static int TestMarshal(WINPR_ATTR_UNUSED int argc, WINPR_ATTR_UNUSED char** argv)
{
for (int i = 0; i < ARRAYSIZE(testValues); i++)
for (size_t 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));
const TestItem* const val = &testValues[i];
memcpy(certInfo.rgbHashOfCert, val->source, sizeof(certInfo.rgbHashOfCert));
LPSTR out = NULL;
if (!CredMarshalCredentialA(CertCredential, &certInfo, &out) || !out)
return -1;
BOOL ok = (strcmp(testValues[i].marshalled, out) == 0);
BOOL ok = (strcmp(val->marshalled, out) == 0);
free(out);

View File

@@ -124,6 +124,26 @@ static const uint32_t offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2
*/
static const uint8_t firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
/* We always need UTF-16LE, even on big endian systems! */
static WCHAR setWcharFrom(WCHAR w)
{
#if defined(__BIG_ENDIAN__)
union
{
WCHAR w;
char c[2];
} cnv;
cnv.w = w;
const char c = cnv.c[0];
cnv.c[0] = cnv.c[1];
cnv.c[1] = c;
return cnv.w;
#else
return w;
#endif
}
/* --------------------------------------------------------------------- */
/* The interface converts a whole buffer to avoid function-call overhead.
@@ -155,7 +175,7 @@ static ConversionResult winpr_ConvertUTF16toUTF8_Internal(const uint16_t** sourc
const uint16_t* oldSource =
source; /* In case we have to back up because of target overflow. */
ch = *source++;
ch = setWcharFrom(*source++);
/* If we have a surrogate pair, convert to UTF32 first. */
if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END)
@@ -163,7 +183,7 @@ static ConversionResult winpr_ConvertUTF16toUTF8_Internal(const uint16_t** sourc
/* If the 16 bits following the high surrogate are in the source buffer... */
if (source < sourceEnd)
{
uint32_t ch2 = *source;
uint32_t ch2 = setWcharFrom(*source);
/* If it's a low surrogate, convert to UTF32. */
if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END)
@@ -472,7 +492,7 @@ static ConversionResult winpr_ConvertUTF8toUTF16_Internal(const uint8_t** source
else
{
if (!computeLength)
*target++ = UNI_REPLACEMENT_CHAR;
*target++ = setWcharFrom(UNI_REPLACEMENT_CHAR);
else
target++;
}
@@ -480,7 +500,7 @@ static ConversionResult winpr_ConvertUTF8toUTF16_Internal(const uint8_t** source
else
{
if (!computeLength)
*target++ = (uint16_t)ch; /* normal case */
*target++ = setWcharFrom((WCHAR)ch); /* normal case */
else
target++;
}
@@ -496,7 +516,7 @@ static ConversionResult winpr_ConvertUTF8toUTF16_Internal(const uint8_t** source
else
{
if (!computeLength)
*target++ = UNI_REPLACEMENT_CHAR;
*target++ = setWcharFrom(UNI_REPLACEMENT_CHAR);
else
target++;
}
@@ -515,8 +535,8 @@ static ConversionResult winpr_ConvertUTF8toUTF16_Internal(const uint8_t** source
if (!computeLength)
{
*target++ = (uint16_t)((ch >> halfShift) + UNI_SUR_HIGH_START);
*target++ = (uint16_t)((ch & halfMask) + UNI_SUR_LOW_START);
*target++ = setWcharFrom((WCHAR)((ch >> halfShift) + UNI_SUR_HIGH_START));
*target++ = setWcharFrom((WCHAR)((ch & halfMask) + UNI_SUR_LOW_START));
}
else
{