crypt32: Support CRYPT_STRING_HEX in CryptStringToBinary().

This commit is contained in:
Paul Gofman 2022-09-20 15:47:54 -05:00 committed by Alexandre Julliard
parent a79ec1c584
commit 1120a1cb29
2 changed files with 505 additions and 4 deletions

View file

@ -943,6 +943,120 @@ static LONG DecodeAnyA(LPCSTR pszString, DWORD cchString,
return ret;
}
static BOOL is_hex_string_special_char(WCHAR c)
{
switch (c)
{
case '-':
case ',':
case ' ':
case '\t':
case '\r':
case '\n':
return TRUE;
default:
return FALSE;
}
}
static WCHAR wchar_from_str(BOOL wide, const void **str, DWORD *len)
{
WCHAR c;
if (!*len)
return 0;
--*len;
if (wide)
c = *(*(const WCHAR **)str)++;
else
c = *(*(const char **)str)++;
return c ? c : 0xffff;
}
static BYTE digit_from_char(WCHAR c)
{
if (c >= '0' && c <= '9')
return c - '0';
c = towlower(c);
if (c >= 'a' && c <= 'f')
return c - 'a' + 0xa;
return 0xff;
}
static LONG string_to_hex(const void* str, BOOL wide, DWORD len, BYTE *hex, DWORD *hex_len,
DWORD *skipped, DWORD *ret_flags)
{
unsigned int byte_idx = 0;
BYTE d1, d2;
WCHAR c;
if (!str || !hex_len)
return ERROR_INVALID_PARAMETER;
if (!len)
len = wide ? wcslen(str) : strlen(str);
if (wide && !len)
return ERROR_INVALID_PARAMETER;
if (skipped)
*skipped = 0;
if (ret_flags)
*ret_flags = 0;
while ((c = wchar_from_str(wide, &str, &len)) && is_hex_string_special_char(c))
;
while ((d1 = digit_from_char(c)) != 0xff)
{
if ((d2 = digit_from_char(wchar_from_str(wide, &str, &len))) == 0xff)
{
if (!hex)
*hex_len = 0;
return ERROR_INVALID_DATA;
}
if (hex && byte_idx < *hex_len)
hex[byte_idx] = (d1 << 4) | d2;
++byte_idx;
do
{
c = wchar_from_str(wide, &str, &len);
} while (c == '-' || c == ',');
}
while (c)
{
if (!is_hex_string_special_char(c))
{
if (!hex)
*hex_len = 0;
return ERROR_INVALID_DATA;
}
c = wchar_from_str(wide, &str, &len);
}
if (hex && byte_idx > *hex_len)
return ERROR_MORE_DATA;
if (ret_flags)
*ret_flags = CRYPT_STRING_HEX;
*hex_len = byte_idx;
return ERROR_SUCCESS;
}
static LONG string_to_hexA(const char *str, DWORD len, BYTE *hex, DWORD *hex_len, DWORD *skipped, DWORD *ret_flags)
{
return string_to_hex(str, FALSE, len, hex, hex_len, skipped, ret_flags);
}
BOOL WINAPI CryptStringToBinaryA(LPCSTR pszString,
DWORD cchString, DWORD dwFlags, BYTE *pbBinary, DWORD *pcbBinary,
DWORD *pdwSkip, DWORD *pdwFlags)
@ -988,6 +1102,8 @@ BOOL WINAPI CryptStringToBinaryA(LPCSTR pszString,
decoder = DecodeAnyA;
break;
case CRYPT_STRING_HEX:
decoder = string_to_hexA;
break;
case CRYPT_STRING_HEXASCII:
case CRYPT_STRING_HEXADDR:
case CRYPT_STRING_HEXASCIIADDR:
@ -1154,6 +1270,11 @@ static LONG DecodeAnyW(LPCWSTR pszString, DWORD cchString,
return ret;
}
static LONG string_to_hexW(const WCHAR *str, DWORD len, BYTE *hex, DWORD *hex_len, DWORD *skipped, DWORD *ret_flags)
{
return string_to_hex(str, TRUE, len, hex, hex_len, skipped, ret_flags);
}
BOOL WINAPI CryptStringToBinaryW(LPCWSTR pszString,
DWORD cchString, DWORD dwFlags, BYTE *pbBinary, DWORD *pcbBinary,
DWORD *pdwSkip, DWORD *pdwFlags)
@ -1199,6 +1320,8 @@ BOOL WINAPI CryptStringToBinaryW(LPCWSTR pszString,
decoder = DecodeAnyW;
break;
case CRYPT_STRING_HEX:
decoder = string_to_hexW;
break;
case CRYPT_STRING_HEXASCII:
case CRYPT_STRING_HEXADDR:
case CRYPT_STRING_HEXASCIIADDR:

View file

@ -775,11 +775,145 @@ static const struct BadString badStrings[] = {
{ "-----BEGIN X509 CRL-----\r\nAA==\r\n", CRYPT_STRING_BASE64X509CRLHEADER },
};
static void testStringToBinaryA(void)
static BOOL is_hex_string_special_char(WCHAR c)
{
BOOL ret;
switch (c)
{
case '-':
case ',':
case ' ':
case '\t':
case '\r':
case '\n':
return TRUE;
default:
return FALSE;
}
}
static WCHAR wchar_from_str(BOOL wide, const void **str, DWORD *len)
{
WCHAR c;
if (!*len)
return 0;
--*len;
if (wide)
c = *(*(const WCHAR **)str)++;
else
c = *(*(const char **)str)++;
return c ? c : 0xffff;
}
static BYTE digit_from_char(WCHAR c)
{
if (c >= '0' && c <= '9')
return c - '0';
c = towlower(c);
if (c >= 'a' && c <= 'f')
return c - 'a' + 0xa;
return 0xff;
}
static LONG string_to_hex(const void* str, BOOL wide, DWORD len, BYTE *hex, DWORD *hex_len,
DWORD *skipped, DWORD *ret_flags)
{
unsigned int byte_idx = 0;
BYTE d1, d2;
WCHAR c;
if (!str || !hex_len)
return ERROR_INVALID_PARAMETER;
if (!len)
len = wide ? wcslen(str) : strlen(str);
if (wide && !len)
return ERROR_INVALID_PARAMETER;
if (skipped)
*skipped = 0;
if (ret_flags)
*ret_flags = 0;
while ((c = wchar_from_str(wide, &str, &len)) && is_hex_string_special_char(c))
;
while ((d1 = digit_from_char(c)) != 0xff)
{
if ((d2 = digit_from_char(wchar_from_str(wide, &str, &len))) == 0xff)
{
if (!hex)
*hex_len = 0;
return ERROR_INVALID_DATA;
}
if (hex && byte_idx < *hex_len)
hex[byte_idx] = (d1 << 4) | d2;
++byte_idx;
do
{
c = wchar_from_str(wide, &str, &len);
} while (c == '-' || c == ',');
}
while (c)
{
if (!is_hex_string_special_char(c))
{
if (!hex)
*hex_len = 0;
return ERROR_INVALID_DATA;
}
c = wchar_from_str(wide, &str, &len);
}
if (hex && byte_idx > *hex_len)
return ERROR_MORE_DATA;
if (ret_flags)
*ret_flags = CRYPT_STRING_HEX;
*hex_len = byte_idx;
return ERROR_SUCCESS;
}
static void test_CryptStringToBinary(void)
{
static const char *string_hex_tests[] =
{
"",
"-",
",-",
"0",
"00",
"000",
"11220",
"1122q",
"q1122",
" aE\t\n\r\n",
"01-02",
"-,01-02",
"01-02-",
"aa,BB-ff,-,",
"1-2",
"010-02",
"aa,BBff,-,",
"aa,,-BB---ff,-,",
"010203040506070809q",
};
DWORD skipped, flags, expected_err, expected_len, expected_skipped, expected_flags;
BYTE buf[8], expected[8];
DWORD bufLen = 0, i;
BYTE buf[8];
WCHAR str_w[64];
BOOL ret, wide;
ret = CryptStringToBinaryA(NULL, 0, 0, NULL, NULL, NULL, NULL);
ok(!ret && GetLastError() == ERROR_INVALID_PARAMETER,
@ -959,10 +1093,254 @@ static void testStringToBinaryA(void)
CRYPT_STRING_ANY, CRYPT_STRING_BASE64HEADER, testsNoCR[i].toEncode,
testsNoCR[i].toEncodeLen);
}
/* CRYPT_STRING_HEX */
ret = CryptStringToBinaryW(L"01", 2, CRYPT_STRING_HEX, NULL, NULL, NULL, NULL);
ok(!ret && GetLastError() == ERROR_INVALID_PARAMETER, "got ret %d, error %lu.\n", ret, GetLastError());
if (0)
{
/* access violation on Windows. */
CryptStringToBinaryA("01", 2, CRYPT_STRING_HEX, NULL, NULL, NULL, NULL);
}
bufLen = 8;
ret = CryptStringToBinaryW(L"0102", 2, CRYPT_STRING_HEX, NULL, &bufLen, NULL, NULL);
ok(ret, "got error %lu.\n", GetLastError());
ok(bufLen == 1, "got length %lu.\n", bufLen);
bufLen = 8;
ret = CryptStringToBinaryW(NULL, 0, CRYPT_STRING_HEX, NULL, &bufLen, NULL, NULL);
ok(!ret && GetLastError() == ERROR_INVALID_PARAMETER, "got ret %d, error %lu.\n", ret, GetLastError());
ok(bufLen == 8, "got length %lu.\n", bufLen);
bufLen = 8;
ret = CryptStringToBinaryA(NULL, 0, CRYPT_STRING_HEX, NULL, &bufLen, NULL, NULL);
ok(!ret && GetLastError() == ERROR_INVALID_PARAMETER, "got ret %d, error %lu.\n", ret, GetLastError());
ok(bufLen == 8, "got length %lu.\n", bufLen);
bufLen = 8;
ret = CryptStringToBinaryW(L"0102", 3, CRYPT_STRING_HEX, NULL, &bufLen, NULL, NULL);
ok(!ret && GetLastError() == ERROR_INVALID_DATA, "got ret %d, error %lu.\n", ret, GetLastError());
ok(!bufLen, "got length %lu.\n", bufLen);
bufLen = 8;
buf[0] = 0xcc;
ret = CryptStringToBinaryW(L"0102", 3, CRYPT_STRING_HEX, buf, &bufLen, NULL, NULL);
ok(!ret && GetLastError() == ERROR_INVALID_DATA, "got ret %d, error %lu.\n", ret, GetLastError());
ok(bufLen == 8, "got length %lu.\n", bufLen);
ok(buf[0] == 1, "got buf[0] %#x.\n", buf[0]);
bufLen = 8;
buf[0] = 0xcc;
ret = CryptStringToBinaryW(L"0102", 2, CRYPT_STRING_HEX, buf, &bufLen, NULL, NULL);
ok(ret, "got error %lu.\n", GetLastError());
ok(bufLen == 1, "got length %lu.\n", bufLen);
ok(buf[0] == 1, "got buf[0] %#x.\n", buf[0]);
bufLen = 8;
buf[0] = buf[1] = 0xcc;
ret = CryptStringToBinaryA("01\0 02", 4, CRYPT_STRING_HEX, buf, &bufLen, NULL, NULL);
ok(!ret && GetLastError() == ERROR_INVALID_DATA, "got ret %d, error %lu.\n", ret, GetLastError());
ok(bufLen == 8, "got length %lu.\n", bufLen);
ok(buf[0] == 1, "got buf[0] %#x.\n", buf[0]);
ok(buf[1] == 0xcc, "got buf[1] %#x.\n", buf[1]);
bufLen = 8;
buf[0] = buf[1] = 0xcc;
ret = CryptStringToBinaryW(L"01\0 02", 4, CRYPT_STRING_HEX, buf, &bufLen, NULL, NULL);
ok(!ret && GetLastError() == ERROR_INVALID_DATA, "got ret %d, error %lu.\n", ret, GetLastError());
ok(bufLen == 8, "got length %lu.\n", bufLen);
ok(buf[0] == 1, "got buf[0] %#x.\n", buf[0]);
ok(buf[1] == 0xcc, "got buf[1] %#x.\n", buf[1]);
bufLen = 1;
buf[0] = 0xcc;
skipped = 0xdeadbeef;
flags = 0xdeadbeef;
ret = CryptStringToBinaryW(L"0102", 4, CRYPT_STRING_HEX, buf, &bufLen, &skipped, &flags);
ok(!ret && GetLastError() == ERROR_MORE_DATA, "got ret %d, error %lu.\n", ret, GetLastError());
ok(bufLen == 1, "got length %lu.\n", bufLen);
ok(buf[0] == 1, "got buf[0] %#x.\n", buf[0]);
ok(!flags, "got flags %lu.\n", flags);
ok(!skipped, "got skipped %lu.\n", skipped);
for (i = 0; i < ARRAY_SIZE(string_hex_tests); ++i)
{
for (wide = 0; wide < 2; ++wide)
{
if (wide)
{
unsigned int j = 0;
while ((str_w[j] = string_hex_tests[i][j]))
++j;
}
winetest_push_context("test %lu, %s", i, wide ? debugstr_w(str_w)
: debugstr_a(string_hex_tests[i]));
expected_len = 0xdeadbeef;
expected_skipped = 0xdeadbeef;
expected_flags = 0xdeadbeef;
expected_err = string_to_hex(wide ? (void *)str_w : (void *)string_hex_tests[i], wide, 0, NULL,
&expected_len, &expected_skipped, &expected_flags);
bufLen = 0xdeadbeef;
skipped = 0xdeadbeef;
flags = 0xdeadbeef;
SetLastError(0xdeadbeef);
if (wide)
ret = CryptStringToBinaryW(str_w, 0, CRYPT_STRING_HEX, NULL, &bufLen, &skipped, &flags);
else
ret = CryptStringToBinaryA(string_hex_tests[i], 0, CRYPT_STRING_HEX, NULL, &bufLen, &skipped, &flags);
ok(bufLen == expected_len, "got length %lu.\n", bufLen);
ok(skipped == expected_skipped, "got skipped %lu.\n", skipped);
ok(flags == expected_flags, "got flags %lu.\n", flags);
if (expected_err)
ok(!ret && GetLastError() == expected_err, "got ret %d, error %lu.\n", ret, GetLastError());
else
ok(ret, "got error %lu.\n", GetLastError());
memset(expected, 0xcc, sizeof(expected));
expected_len = 8;
expected_skipped = 0xdeadbeef;
expected_flags = 0xdeadbeef;
expected_err = string_to_hex(wide ? (void *)str_w : (void *)string_hex_tests[i], wide, 0, expected,
&expected_len, &expected_skipped, &expected_flags);
memset(buf, 0xcc, sizeof(buf));
bufLen = 8;
skipped = 0xdeadbeef;
flags = 0xdeadbeef;
SetLastError(0xdeadbeef);
if (wide)
ret = CryptStringToBinaryW(str_w, 0, CRYPT_STRING_HEX, buf, &bufLen, &skipped, &flags);
else
ret = CryptStringToBinaryA(string_hex_tests[i], 0, CRYPT_STRING_HEX, buf, &bufLen, &skipped, &flags);
ok(!memcmp(buf, expected, sizeof(buf)), "data does not match, buf[0] %#x, buf[1] %#x.\n", buf[0], buf[1]);
ok(bufLen == expected_len, "got length %lu.\n", bufLen);
if (expected_err)
ok(!ret && GetLastError() == expected_err, "got ret %d, error %lu.\n", ret, GetLastError());
else
ok(ret, "got error %lu.\n", GetLastError());
ok(bufLen == expected_len, "got length %lu.\n", bufLen);
ok(skipped == expected_skipped, "got skipped %lu.\n", skipped);
ok(flags == expected_flags, "got flags %lu.\n", flags);
winetest_pop_context();
}
}
bufLen = 1;
SetLastError(0xdeadbeef);
skipped = 0xdeadbeef;
flags = 0xdeadbeef;
memset(buf, 0xcc, sizeof(buf));
ret = CryptStringToBinaryA("0102", 0, CRYPT_STRING_HEX, buf, &bufLen, &skipped, &flags);
ok(!ret && GetLastError() == ERROR_MORE_DATA, "got ret %d, error %lu.\n", ret, GetLastError());
ok(bufLen == 1, "got length %lu.\n", bufLen);
ok(!skipped, "got skipped %lu.\n", skipped);
ok(!flags, "got flags %lu.\n", flags);
ok(buf[0] == 1, "got buf[0] %#x.\n", buf[0]);
ok(buf[1] == 0xcc, "got buf[1] %#x.\n", buf[1]);
bufLen = 1;
SetLastError(0xdeadbeef);
skipped = 0xdeadbeef;
flags = 0xdeadbeef;
memset(buf, 0xcc, sizeof(buf));
ret = CryptStringToBinaryA("0102q", 0, CRYPT_STRING_HEX, buf, &bufLen, &skipped, &flags);
ok(!ret && GetLastError() == ERROR_INVALID_DATA, "got ret %d, error %lu.\n", ret, GetLastError());
ok(bufLen == 1, "got length %lu.\n", bufLen);
ok(!skipped, "got skipped %lu.\n", skipped);
ok(!flags, "got flags %lu.\n", flags);
ok(buf[0] == 1, "got buf[0] %#x.\n", buf[0]);
ok(buf[1] == 0xcc, "got buf[1] %#x.\n", buf[1]);
bufLen = 1;
SetLastError(0xdeadbeef);
skipped = 0xdeadbeef;
flags = 0xdeadbeef;
memset(buf, 0xcc, sizeof(buf));
ret = CryptStringToBinaryW(L"0102q", 0, CRYPT_STRING_HEX, buf, &bufLen, &skipped, &flags);
ok(!ret && GetLastError() == ERROR_INVALID_DATA, "got ret %d, error %lu.\n", ret, GetLastError());
ok(bufLen == 1, "got length %lu.\n", bufLen);
ok(!skipped, "got skipped %lu.\n", skipped);
ok(!flags, "got flags %lu.\n", flags);
ok(buf[0] == 1, "got buf[0] %#x.\n", buf[0]);
ok(buf[1] == 0xcc, "got buf[1] %#x.\n", buf[1]);
bufLen = 1;
SetLastError(0xdeadbeef);
skipped = 0xdeadbeef;
flags = 0xdeadbeef;
memset(buf, 0xcc, sizeof(buf));
ret = CryptStringToBinaryW(L"0102", 0, CRYPT_STRING_HEX, buf, &bufLen, &skipped, &flags);
ok(bufLen == 1, "got length %lu.\n", bufLen);
ok(!ret && GetLastError() == ERROR_MORE_DATA, "got ret %d, error %lu.\n", ret, GetLastError());
ok(buf[0] == 1, "got buf[0] %#x.\n", buf[0]);
ok(buf[1] == 0xcc, "got buf[1] %#x.\n", buf[1]);
/* It looks like Windows is normalizing Unicode strings in some way which depending on locale may result in
* some invalid characters in 128-255 range being converted into sequences starting with valid hex numbers.
* Just avoiding characters in the 128-255 range in test. */
for (i = 1; i < 128; ++i)
{
char str_a[16];
for (wide = 0; wide < 2; ++wide)
{
if (wide)
{
str_w[0] = i;
wcscpy(str_w + 1, L"00");
}
else
{
str_a[0] = i;
strcpy(str_a + 1, "00");
}
winetest_push_context("char %#lx, %s", i, wide ? debugstr_w(str_w) : debugstr_a(str_a));
bufLen = 1;
buf[0] = buf[1] = 0xcc;
SetLastError(0xdeadbeef);
if (wide)
ret = CryptStringToBinaryW(str_w, 0, CRYPT_STRING_HEX, buf, &bufLen, &skipped, &flags);
else
ret = CryptStringToBinaryA(str_a, 0, CRYPT_STRING_HEX, buf, &bufLen, &skipped, &flags);
ok(bufLen == 1, "got length %lu.\n", bufLen);
if (is_hex_string_special_char(i))
{
ok(ret, "got error %lu.\n", GetLastError());
ok(!buf[0], "got buf[0] %#x.\n", buf[0]);
ok(buf[1] == 0xcc, "got buf[1] %#x.\n", buf[1]);
}
else
{
ok(!ret && GetLastError() == ERROR_INVALID_DATA, "got ret %d, error %lu.\n", ret, GetLastError());
if (isdigit(i) || (tolower(i) >= 'a' && tolower(i) <= 'f'))
{
ok(buf[0] == (digit_from_char(i) << 4), "got buf[0] %#x.\n", buf[0]);
ok(buf[1] == 0xcc, "got buf[0] %#x.\n", buf[1]);
}
else
{
ok(buf[0] == 0xcc, "got buf[0] %#x.\n", buf[0]);
}
}
winetest_pop_context();
}
}
}
START_TEST(base64)
{
test_CryptBinaryToString();
testStringToBinaryA();
test_CryptStringToBinary();
}