[core,redirect] extract and check redirection cert

* extract the certificate from the redirection PDU
* if there is a certificate provided accept it if it matches the
  redirection target certificate without further user checks
This commit is contained in:
Armin Novak
2023-02-22 16:41:37 +01:00
committed by akallabeth
parent b2fa6da8d2
commit ae8f0106bd
7 changed files with 244 additions and 71 deletions

View File

@@ -657,7 +657,6 @@ typedef struct
#define FreeRDP_RedirectionGuid (1234)
#define FreeRDP_RedirectionGuidLength (1235)
#define FreeRDP_RedirectionTargetCertificate (1236)
#define FreeRDP_RedirectionTargetCertificateLength (1237)
#define FreeRDP_Password51 (1280)
#define FreeRDP_Password51Length (1281)
#define FreeRDP_SmartcardLogon (1282)
@@ -1174,9 +1173,8 @@ struct rdp_settings
ALIGN64 UINT32 RedirectionPreferType; /* 1233 */
ALIGN64 BYTE* RedirectionGuid; /* 1234 */
ALIGN64 UINT32 RedirectionGuidLength; /* 1235 */
ALIGN64 BYTE* RedirectionTargetCertificate; /* 1236 */
ALIGN64 UINT32 RedirectionTargetCertificateLength; /* 1237 */
UINT64 padding1280[1280 - 1238]; /* 1238 */
ALIGN64 rdpCertificate* RedirectionTargetCertificate; /* 1236 */
UINT64 padding1280[1280 - 1237]; /* 1237 */
/**
* Security

View File

@@ -1341,8 +1341,21 @@ BOOL freerdp_settings_set_pointer_len(rdpSettings* settings, size_t id, const vo
return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_RedirectionTsvUrlLength,
data, len, sizeof(char));
case FreeRDP_RedirectionTargetCertificate:
return freerdp_settings_set_pointer_len_(
settings, id, FreeRDP_RedirectionTargetCertificateLength, data, len, sizeof(BYTE));
freerdp_certificate_free(settings->RedirectionTargetCertificate);
if (len > 1)
{
WLog_ERR(TAG, "FreeRDP_RedirectionTargetCertificate::len must be 0 or 1");
return FALSE;
}
settings->RedirectionTargetCertificate = cnv.v;
if (!settings->RedirectionTargetCertificate && (len > 0))
{
settings->RedirectionTargetCertificate = freerdp_certificate_new();
if (!settings->RedirectionTargetCertificate)
return FALSE;
}
return TRUE;
case FreeRDP_RedirectionGuid:
return freerdp_settings_set_pointer_len_(settings, id, FreeRDP_RedirectionGuidLength,
data, len, sizeof(BYTE));

View File

@@ -1758,9 +1758,6 @@ UINT32 freerdp_settings_get_uint32(const rdpSettings* settings, size_t id)
case FreeRDP_RedirectionPreferType:
return settings->RedirectionPreferType;
case FreeRDP_RedirectionTargetCertificateLength:
return settings->RedirectionTargetCertificateLength;
case FreeRDP_RedirectionTsvUrlLength:
return settings->RedirectionTsvUrlLength;
@@ -2235,10 +2232,6 @@ BOOL freerdp_settings_set_uint32(rdpSettings* settings, size_t id, UINT32 val)
settings->RedirectionPreferType = cnv.c;
break;
case FreeRDP_RedirectionTargetCertificateLength:
settings->RedirectionTargetCertificateLength = cnv.c;
break;
case FreeRDP_RedirectionTsvUrlLength:
settings->RedirectionTsvUrlLength = cnv.c;
break;

View File

@@ -368,8 +368,6 @@ static const struct settings_str_entry settings_map[] = {
"FreeRDP_RedirectionPasswordLength" },
{ FreeRDP_RedirectionPreferType, FREERDP_SETTINGS_TYPE_UINT32,
"FreeRDP_RedirectionPreferType" },
{ FreeRDP_RedirectionTargetCertificateLength, FREERDP_SETTINGS_TYPE_UINT32,
"FreeRDP_RedirectionTargetCertificateLength" },
{ FreeRDP_RedirectionTsvUrlLength, FREERDP_SETTINGS_TYPE_UINT32,
"FreeRDP_RedirectionTsvUrlLength" },
{ FreeRDP_RemoteAppNumIconCacheEntries, FREERDP_SETTINGS_TYPE_UINT32,

View File

@@ -23,10 +23,11 @@
#include <winpr/crt.h>
#include <freerdp/log.h>
#include <freerdp/crypto/certificate.h>
#include <freerdp/redirection.h>
#include <freerdp/utils/string.h>
#include "connection.h"
#include "../crypto/certificate.h"
#include "redirection.h"
#include "utils.h"
@@ -51,8 +52,8 @@ struct rdp_redirection
char** TargetNetAddresses;
UINT32 RedirectionGuidLength;
BYTE* RedirectionGuid;
UINT32 TargetCertificateLength;
BYTE* TargetCertificate;
rdpCertificate* TargetCertificate;
};
static void redirection_free_array(char*** what, UINT32* count)
@@ -141,30 +142,38 @@ static BOOL redirection_copy_array(char*** dst, UINT32* plen, const char** str,
return *dst != NULL;
}
static BOOL rdp_redirection_get_data(wStream* s, UINT32* pLength, const BYTE** pData)
{
WINPR_ASSERT(pLength);
WINPR_ASSERT(pData);
if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
return FALSE;
Stream_Read_UINT32(s, *pLength);
if (!Stream_CheckAndLogRequiredLength(TAG, s, *pLength))
return FALSE;
*pData = Stream_Pointer(s);
Stream_Seek(s, *pLength);
return TRUE;
}
static BOOL rdp_redirection_read_unicode_string(wStream* s, char** str, size_t maxLength)
{
UINT32 length = 0;
const WCHAR* wstr = NULL;
if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
if (!rdp_redirection_get_data(s, &length, &wstr))
return FALSE;
Stream_Read_UINT32(s, length);
if ((length % 2) || length < 2 || length > maxLength)
{
WLog_ERR(TAG, "failure: invalid unicode string length: %" PRIu32 "", length);
return FALSE;
}
if (!Stream_CheckAndLogRequiredLength(TAG, s, length))
{
WLog_ERR(TAG, "failure: insufficient stream length (%" PRIu32 " bytes required)", length);
return FALSE;
}
wstr = (const WCHAR*)Stream_Pointer(s);
if (wstr[length / 2 - 1])
{
WLog_ERR(TAG, "failure: unterminated unicode string");
@@ -179,10 +188,171 @@ static BOOL rdp_redirection_read_unicode_string(wStream* s, char** str, size_t m
return FALSE;
}
Stream_Seek(s, length);
return TRUE;
}
static BOOL replace_char(char* utf8, size_t length, char what, char with)
{
for (size_t x = 0; x < length; x++)
{
char* cur = &utf8[x];
if (*cur == what)
*cur = with;
}
return TRUE;
}
static BOOL rdp_redirection_read_base64_wchar(UINT32 flag, wStream* s, UINT32* pLength,
BYTE** pData)
{
BOOL rc = FALSE;
char buffer[64] = { 0 };
const BYTE* ptr = NULL;
if (!rdp_redirection_get_data(s, pLength, &ptr))
return FALSE;
const WCHAR* wchar = ptr;
size_t utf8_len = 0;
char* utf8 = ConvertWCharNToUtf8Alloc(wchar, *pLength, &utf8_len);
if (!utf8)
return FALSE;
redirection_free_data(pData, NULL);
utf8_len = strnlen(utf8, utf8_len);
*pData = calloc(utf8_len, sizeof(BYTE));
if (!*pData)
goto fail;
size_t rlen = utf8_len;
size_t wpos = 0;
char* tok = strtok(utf8, "\r\n");
while (tok)
{
const size_t len = strnlen(tok, rlen);
rlen -= len;
size_t plen = 0;
BYTE* ptr = NULL;
crypto_base64_decode(tok, len, &ptr, &plen);
if (!ptr)
goto fail;
memcpy(&(*pData)[wpos], ptr, plen);
wpos += plen;
tok = strtok(NULL, "\r\n");
}
*pLength = wpos;
WLog_DBG(TAG, "%s:", rdp_redirection_flags_to_string(flag, buffer, sizeof(buffer)));
rc = TRUE;
fail:
free(utf8);
return rc;
}
static BOOL rdp_target_cert_get_element(wStream* s, UINT32* pType, UINT32* pEncoding,
const BYTE** ptr, size_t* pLength)
{
WINPR_ASSERT(pType);
WINPR_ASSERT(pEncoding);
WINPR_ASSERT(ptr);
WINPR_ASSERT(pLength);
if (!Stream_CheckAndLogRequiredLength(TAG, s, 12))
return FALSE;
UINT32 type = 0;
UINT32 encoding = 0;
UINT32 elementSize = 0;
Stream_Read_UINT32(s, type);
Stream_Read_UINT32(s, encoding);
Stream_Read_UINT32(s, elementSize);
if (!Stream_CheckAndLogRequiredLength(TAG, s, elementSize))
return FALSE;
*ptr = Stream_Pointer(s);
*pLength = elementSize;
Stream_Seek(s, elementSize);
*pType = type;
*pEncoding = encoding;
return TRUE;
}
static BOOL rdp_redirection_read_target_cert(rdpRedirection* redirection, const BYTE* data,
size_t length)
{
wStream sbuffer = { 0 };
wStream* s = Stream_StaticConstInit(&sbuffer, data, length);
freerdp_certificate_free(redirection->TargetCertificate);
redirection->TargetCertificate = NULL;
size_t plength = 0;
BYTE* ptr = NULL;
while (Stream_GetRemainingLength(s) > 0)
{
UINT32 type = 0;
UINT32 encoding = 0;
if (!rdp_target_cert_get_element(s, &type, &encoding, &ptr, &plength))
return FALSE;
switch (type)
{
case 32: /* ELEMENT_TYPE_CERTIFICATE */
if (encoding == 1 /* ENCODING_TYPE_ASN1_DER */)
{
if (redirection->TargetCertificate)
WLog_WARN(TAG, "Duplicate TargetCertificate in data detected!");
else
redirection->TargetCertificate =
freerdp_certificate_new_from_der(ptr, plength);
}
break;
default: /* ignore unknown fields */
WLog_WARN(TAG,
"Unknown TargetCertificate field type %" PRIu32 ", encoding %" PRIu32
" of length %" PRIu32,
type, encoding, plength);
break;
}
}
return redirection->TargetCertificate != NULL;
}
static BOOL rdp_redireciton_write_target_cert_stream(wStream* s, const rdpCertificate* cert)
{
#if 0
if (!Stream_EnsureRemainingCapacity(s, 4ull + length))
return FALSE;
Stream_Write_UINT32(s, length);
Stream_Write(s, data, length);
#endif
return FALSE;
}
static BOOL rdp_redirection_read_target_cert_stream(wStream* s, rdpRedirection* redirection)
{
UINT32 length = 0;
BYTE* ptr = NULL;
WINPR_ASSERT(redirection);
if (!rdp_redirection_read_base64_wchar(LB_TARGET_CERTIFICATE, s, &length, &ptr))
return FALSE;
const BOOL rc = rdp_redirection_read_target_cert(redirection, ptr, length);
free(ptr);
return rc;
}
int rdp_redirection_apply_settings(rdpRdp* rdp)
{
rdpSettings* settings = NULL;
@@ -310,9 +480,23 @@ int rdp_redirection_apply_settings(rdpRdp* rdp)
if (settings->RedirectionFlags & LB_TARGET_CERTIFICATE)
{
if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionTargetCertificate,
redirection->TargetCertificate,
redirection->TargetCertificateLength))
rdpCertificate* cert = freerdp_certificate_clone(redirection->TargetCertificate);
if (!freerdp_settings_set_pointer(settings, FreeRDP_RedirectionTargetCertificate, cert))
return -1;
BOOL pres = FALSE;
size_t length = 0;
char* pem = freerdp_certificate_get_pem(cert, &length);
if (pem)
{
pres = freerdp_settings_set_string_len(settings, FreeRDP_RedirectionAcceptedCert, pem,
length);
if (pres)
pres = freerdp_settings_set_uint32(settings, FreeRDP_RedirectionAcceptedCertLength,
length);
}
free(pem);
if (!pres)
return -1;
}
@@ -322,16 +506,9 @@ int rdp_redirection_apply_settings(rdpRdp* rdp)
static BOOL rdp_redirection_read_data(UINT32 flag, wStream* s, UINT32* pLength, BYTE** pData)
{
char buffer[64] = { 0 };
const BYTE* ptr = NULL;
WINPR_ASSERT(pLength);
WINPR_ASSERT(pData);
if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
return FALSE;
Stream_Read_UINT32(s, *pLength);
if (!Stream_CheckAndLogRequiredLength(TAG, s, *pLength))
if (!rdp_redirection_get_data(s, pLength, &ptr))
return FALSE;
redirection_free_data(pData, NULL);
@@ -339,10 +516,10 @@ static BOOL rdp_redirection_read_data(UINT32 flag, wStream* s, UINT32* pLength,
if (!*pData)
return FALSE;
memcpy(*pData, ptr, *pLength);
Stream_Read(s, *pData, *pLength);
WLog_DBG(TAG, "%s:", rdp_redirection_flags_to_string(flag, buffer, sizeof(buffer)));
winpr_HexDump(TAG, WLOG_DEBUG, *pData, *pLength);
return TRUE;
}
@@ -490,16 +667,15 @@ static state_run_t rdp_recv_server_redirection_pdu(rdpRdp* rdp, wStream* s)
if (redirection->flags & LB_REDIRECTION_GUID)
{
if (!rdp_redirection_read_data(LB_REDIRECTION_GUID, s, &redirection->RedirectionGuidLength,
&redirection->RedirectionGuid))
if (!rdp_redirection_read_base64_wchar(LB_REDIRECTION_GUID, s,
&redirection->RedirectionGuidLength,
&redirection->RedirectionGuid))
return STATE_RUN_FAILED;
}
if (redirection->flags & LB_TARGET_CERTIFICATE)
{
if (!rdp_redirection_read_data(LB_TARGET_CERTIFICATE, s,
&redirection->TargetCertificateLength,
&redirection->TargetCertificate))
if (!rdp_redirection_read_target_cert_stream(s, redirection))
return STATE_RUN_FAILED;
}
@@ -588,8 +764,7 @@ void redirection_free(rdpRedirection* redirection)
redirection_free_data(&redirection->LoadBalanceInfo, &redirection->LoadBalanceInfoLength);
redirection_free_data(&redirection->Password, &redirection->PasswordLength);
redirection_free_data(&redirection->RedirectionGuid, &redirection->RedirectionGuidLength);
redirection_free_data(&redirection->TargetCertificate,
&redirection->TargetCertificateLength);
freerdp_certificate_free(redirection->TargetCertificate);
redirection_free_array(&redirection->TargetNetAddresses,
&redirection->TargetNetAddressesCount);
@@ -708,8 +883,7 @@ BOOL rdp_write_enhanced_security_redirection_packet(wStream* s, const rdpRedirec
if (redirection->flags & LB_TARGET_CERTIFICATE)
{
if (!redir_write_data(LB_REDIRECTION_GUID, s, redirection->TargetCertificateLength,
redirection->TargetCertificate))
if (!rdp_redireciton_write_target_cert_stream(s, redirection->TargetCertificate))
goto fail;
}
@@ -836,7 +1010,7 @@ BOOL redirection_settings_are_valid(rdpRedirection* redirection, UINT32* pFlags)
if (redirection->flags & LB_TARGET_CERTIFICATE)
{
if (!redirection->TargetCertificate || (redirection->TargetCertificateLength == 0))
if (!redirection->TargetCertificate)
flags |= LB_TARGET_CERTIFICATE;
}
@@ -888,8 +1062,8 @@ BOOL redirection_set_byte_option(rdpRedirection* redirection, UINT32 flag, const
return redirection_copy_data(&redirection->RedirectionGuid,
&redirection->RedirectionGuidLength, data, length);
case LB_TARGET_CERTIFICATE:
return redirection_copy_data(&redirection->TargetCertificate,
&redirection->TargetCertificateLength, data, length);
freerdp_certificate_free(redirection->TargetCertificate);
return FALSE; // TODO rdp_redireciton_read_target_cert(redirection, data, length);
default:
return redirection_unsupported(__FUNCTION__, flag,
LB_CLIENT_TSV_URL | LB_PASSWORD | LB_LOAD_BALANCE_INFO |

View File

@@ -288,7 +288,6 @@ static const size_t uint32_list_indices[] = {
FreeRDP_RedirectionGuidLength,
FreeRDP_RedirectionPasswordLength,
FreeRDP_RedirectionPreferType,
FreeRDP_RedirectionTargetCertificateLength,
FreeRDP_RedirectionTsvUrlLength,
FreeRDP_RemoteAppNumIconCacheEntries,
FreeRDP_RemoteAppNumIconCaches,

View File

@@ -1349,29 +1349,27 @@ static BOOL is_accepted_fingerprint(const rdpCertificate* cert,
static BOOL accept_cert(rdpTls* tls, const BYTE* pem, UINT32 length)
{
rdpSettings* settings = tls->settings;
char* dupPem = _strdup((const char*)pem);
WINPR_ASSERT(tls);
size_t id = FreeRDP_AcceptedCert;
size_t lid = FreeRDP_AcceptedCertLength;
if (!dupPem)
return FALSE;
rdpSettings* settings = tls->settings;
if (tls->isGatewayTransport)
{
settings->GatewayAcceptedCert = dupPem;
settings->GatewayAcceptedCertLength = length;
id = FreeRDP_GatewayAcceptedCert;
lid = FreeRDP_GatewayAcceptedCertLength;
}
else if (is_redirected(tls))
{
settings->RedirectionAcceptedCert = dupPem;
settings->RedirectionAcceptedCertLength = length;
}
else
{
settings->AcceptedCert = dupPem;
settings->AcceptedCertLength = length;
id = FreeRDP_RedirectionAcceptedCert;
lid = FreeRDP_RedirectionAcceptedCertLength;
}
return TRUE;
if (!freerdp_settings_set_string_len(settings, id, pem, length))
return FALSE;
return freerdp_settings_set_uint32(settings, lid, length);
}
static BOOL tls_extract_pem(const rdpCertificate* cert, BYTE** PublicKey, size_t* PublicKeyLength)