From 7c24da917efe1007b9c607f4f95a304ef292fff3 Mon Sep 17 00:00:00 2001 From: Joan Torres Date: Tue, 7 Mar 2023 11:58:28 +0100 Subject: [PATCH] Add RDSTLS security protocol The client tries to connect using RDSTLS only when it has received a server redirection PDU with LB_PASSWORD_IS_ENCRYPTED flag. The server exposes RDSTLS on negotiation if it has been configured on settings. Then authenticates a client using configured credentials from settings: RedirectionGuid, Username, Domain, Password. --- include/freerdp/settings.h | 4 +- libfreerdp/common/settings_getters.c | 7 + libfreerdp/common/settings_str.c | 1 + libfreerdp/core/CMakeLists.txt | 4 +- libfreerdp/core/connection.c | 28 +- libfreerdp/core/nego.c | 111 +++- libfreerdp/core/nego.h | 12 +- libfreerdp/core/peer.c | 1 + libfreerdp/core/rdstls.c | 785 +++++++++++++++++++++++++++ libfreerdp/core/rdstls.h | 50 ++ libfreerdp/core/settings.c | 1 + libfreerdp/core/transport.c | 147 +++++ libfreerdp/core/transport.h | 4 + 13 files changed, 1133 insertions(+), 22 deletions(-) create mode 100644 libfreerdp/core/rdstls.c create mode 100644 libfreerdp/core/rdstls.h diff --git a/include/freerdp/settings.h b/include/freerdp/settings.h index 326e85c8a..c2a79b5bb 100644 --- a/include/freerdp/settings.h +++ b/include/freerdp/settings.h @@ -631,6 +631,7 @@ typedef struct #define FreeRDP_TLSMaxVersion (1108) #define FreeRDP_TlsSecretsFile (1109) #define FreeRDP_AuthenticationPackageList (1110) +#define FreeRDP_RdstlsSecurity (1111) #define FreeRDP_MstscCookieMode (1152) #define FreeRDP_CookieMaxLength (1153) #define FreeRDP_PreconnectionId (1154) @@ -1142,7 +1143,8 @@ struct rdp_settings ALIGN64 UINT16 TLSMaxVersion; /* 1108 */ ALIGN64 char* TlsSecretsFile; /* 1109 */ ALIGN64 char* AuthenticationPackageList; /* 1110 */ - UINT64 padding1152[1152 - 1111]; /* 1111 */ + ALIGN64 BOOL RdstlsSecurity; /* 1111 */ + UINT64 padding1152[1152 - 1112]; /* 1112 */ /* Connection Cookie */ ALIGN64 BOOL MstscCookieMode; /* 1152 */ diff --git a/libfreerdp/common/settings_getters.c b/libfreerdp/common/settings_getters.c index e00431531..e32856081 100644 --- a/libfreerdp/common/settings_getters.c +++ b/libfreerdp/common/settings_getters.c @@ -399,6 +399,9 @@ BOOL freerdp_settings_get_bool(const rdpSettings* settings, size_t id) case FreeRDP_RdpSecurity: return settings->RdpSecurity; + case FreeRDP_RdstlsSecurity: + return settings->RdstlsSecurity; + case FreeRDP_RedirectClipboard: return settings->RedirectClipboard; @@ -1067,6 +1070,10 @@ BOOL freerdp_settings_set_bool(rdpSettings* settings, size_t id, BOOL val) settings->RdpSecurity = cnv.c; break; + case FreeRDP_RdstlsSecurity: + settings->RdstlsSecurity = cnv.c; + break; + case FreeRDP_RedirectClipboard: settings->RedirectClipboard = cnv.c; break; diff --git a/libfreerdp/common/settings_str.c b/libfreerdp/common/settings_str.c index e43f5a7ec..7e58a37f0 100644 --- a/libfreerdp/common/settings_str.c +++ b/libfreerdp/common/settings_str.c @@ -168,6 +168,7 @@ static const struct settings_str_entry settings_map[] = { { FreeRDP_PrintReconnectCookie, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_PrintReconnectCookie" }, { FreeRDP_PromptForCredentials, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_PromptForCredentials" }, { FreeRDP_RdpSecurity, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RdpSecurity" }, + { FreeRDP_RdstlsSecurity, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RdstlsSecurity" }, { FreeRDP_RedirectClipboard, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RedirectClipboard" }, { FreeRDP_RedirectDrives, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RedirectDrives" }, { FreeRDP_RedirectHomeDrive, FREERDP_SETTINGS_TYPE_BOOL, "FreeRDP_RedirectHomeDrive" }, diff --git a/libfreerdp/core/CMakeLists.txt b/libfreerdp/core/CMakeLists.txt index fe953a290..dee11a6cb 100644 --- a/libfreerdp/core/CMakeLists.txt +++ b/libfreerdp/core/CMakeLists.txt @@ -132,7 +132,9 @@ set(${MODULE_PREFIX}_SRCS display.c display.h credssp_auth.c - credssp_auth.h) + credssp_auth.h + rdstls.c + rdstls.h) set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${${MODULE_PREFIX}_GATEWAY_SRCS}) diff --git a/libfreerdp/core/connection.c b/libfreerdp/core/connection.c index e463f9753..627a32723 100644 --- a/libfreerdp/core/connection.c +++ b/libfreerdp/core/connection.c @@ -392,6 +392,7 @@ BOOL rdp_client_connect(rdpRdp* rdp) nego_enable_tls(rdp->nego, settings->TlsSecurity); nego_enable_nla(rdp->nego, settings->NlaSecurity); nego_enable_ext(rdp->nego, settings->ExtSecurity); + nego_enable_rdstls(rdp->nego, settings->RdstlsSecurity); if (settings->MstscCookieMode) settings->CookieMaxLength = MSTSC_COOKIE_MAX_LENGTH; @@ -424,7 +425,8 @@ BOOL rdp_client_connect(rdpRdp* rdp) SelectedProtocol = nego_get_selected_protocol(rdp->nego); - if ((SelectedProtocol & PROTOCOL_SSL) || (SelectedProtocol == PROTOCOL_RDP)) + if ((SelectedProtocol & PROTOCOL_SSL) || (SelectedProtocol == PROTOCOL_RDP) || + (SelectedProtocol == PROTOCOL_RDSTLS)) { wStream s = { 0 }; @@ -658,6 +660,8 @@ BOOL rdp_client_redirect(rdpRdp* rdp) return FALSE; } + settings->RdstlsSecurity = settings->RedirectionFlags & LB_PASSWORD_IS_PK_ENCRYPTED; + WINPR_ASSERT(rdp->context); WINPR_ASSERT(rdp->context->instance); if (!IFCALLRESULT(TRUE, rdp->context->instance->Redirect, rdp->context->instance)) @@ -1363,14 +1367,21 @@ BOOL rdp_server_accept_nego(rdpRdp* rdp, wStream* s) return FALSE; RequestedProtocols = nego_get_requested_protocols(nego); - WLog_INFO(TAG, "Client Security: NLA:%d TLS:%d RDP:%d", + WLog_INFO(TAG, "Client Security: RDSTLS:%d NLA:%d TLS:%d RDP:%d", + (RequestedProtocols & PROTOCOL_RDSTLS) ? 1 : 0, (RequestedProtocols & PROTOCOL_HYBRID) ? 1 : 0, (RequestedProtocols & PROTOCOL_SSL) ? 1 : 0, (RequestedProtocols == PROTOCOL_RDP) ? 1 : 0); - WLog_INFO(TAG, "Server Security: NLA:%" PRId32 " TLS:%" PRId32 " RDP:%" PRId32 "", - settings->NlaSecurity, settings->TlsSecurity, settings->RdpSecurity); + WLog_INFO(TAG, + "Server Security: RDSTLS:%" PRId32 " NLA:%" PRId32 " TLS:%" PRId32 " RDP:%" PRId32 "", + settings->RdstlsSecurity, settings->NlaSecurity, settings->TlsSecurity, + settings->RdpSecurity); - if ((settings->NlaSecurity) && (RequestedProtocols & PROTOCOL_HYBRID)) + if ((settings->RdstlsSecurity) && (RequestedProtocols & PROTOCOL_RDSTLS)) + { + SelectedProtocol = PROTOCOL_RDSTLS; + } + else if ((settings->NlaSecurity) && (RequestedProtocols & PROTOCOL_HYBRID)) { SelectedProtocol = PROTOCOL_HYBRID; } @@ -1414,7 +1425,8 @@ BOOL rdp_server_accept_nego(rdpRdp* rdp, wStream* s) if (!(SelectedProtocol & PROTOCOL_FAILED_NEGO)) { - WLog_INFO(TAG, "Negotiated Security: NLA:%d TLS:%d RDP:%d", + WLog_INFO(TAG, "Negotiated Security: RDSTLS:%d NLA:%d TLS:%d RDP:%d", + (SelectedProtocol & PROTOCOL_RDSTLS) ? 1 : 0, (SelectedProtocol & PROTOCOL_HYBRID) ? 1 : 0, (SelectedProtocol & PROTOCOL_SSL) ? 1 : 0, (SelectedProtocol == PROTOCOL_RDP) ? 1 : 0); @@ -1429,7 +1441,9 @@ BOOL rdp_server_accept_nego(rdpRdp* rdp, wStream* s) SelectedProtocol = nego_get_selected_protocol(nego); status = FALSE; - if (SelectedProtocol & PROTOCOL_HYBRID) + if (SelectedProtocol & PROTOCOL_RDSTLS) + status = transport_accept_rdstls(rdp->transport); + else if (SelectedProtocol & PROTOCOL_HYBRID) status = transport_accept_nla(rdp->transport); else if (SelectedProtocol & PROTOCOL_SSL) status = transport_accept_tls(rdp->transport); diff --git a/libfreerdp/core/nego.c b/libfreerdp/core/nego.c index 8ef665651..88f50deed 100644 --- a/libfreerdp/core/nego.c +++ b/libfreerdp/core/nego.c @@ -67,10 +67,11 @@ struct rdp_nego static const char* nego_state_string(NEGO_STATE state) { - static const char* const NEGO_STATE_STRINGS[] = { "NEGO_STATE_INITIAL", "NEGO_STATE_EXT", - "NEGO_STATE_NLA", "NEGO_STATE_TLS", - "NEGO_STATE_RDP", "NEGO_STATE_FAIL", - "NEGO_STATE_FINAL", "NEGO_STATE_INVALID" }; + static const char* const NEGO_STATE_STRINGS[] = { "NEGO_STATE_INITIAL", "NEGO_STATE_RDSTLS", + "NEGO_STATE_EXT", "NEGO_STATE_NLA", + "NEGO_STATE_TLS", "NEGO_STATE_RDP", + "NEGO_STATE_FAIL", "NEGO_STATE_FINAL", + "NEGO_STATE_INVALID" }; if (state >= ARRAYSIZE(NEGO_STATE_STRINGS)) return NEGO_STATE_STRINGS[ARRAYSIZE(NEGO_STATE_STRINGS) - 1]; return NEGO_STATE_STRINGS[state]; @@ -78,7 +79,7 @@ static const char* nego_state_string(NEGO_STATE state) static const char* protocol_security_string(UINT32 security) { - static const char* PROTOCOL_SECURITY_STRINGS[] = { "RDP", "TLS", "NLA", "UNK", "UNK", + static const char* PROTOCOL_SECURITY_STRINGS[] = { "RDP", "TLS", "NLA", "UNK", "RDSTLS", "UNK", "UNK", "UNK", "EXT", "UNK" }; if (security >= ARRAYSIZE(PROTOCOL_SECURITY_STRINGS)) return PROTOCOL_SECURITY_STRINGS[ARRAYSIZE(PROTOCOL_SECURITY_STRINGS) - 1]; @@ -115,7 +116,11 @@ BOOL nego_connect(rdpNego* nego) if (nego_get_state(nego) == NEGO_STATE_INITIAL) { - if (nego->EnabledProtocols[PROTOCOL_HYBRID_EX]) + if (nego->EnabledProtocols[PROTOCOL_RDSTLS]) + { + nego_set_state(nego, NEGO_STATE_RDSTLS); + } + else if (nego->EnabledProtocols[PROTOCOL_HYBRID_EX]) { nego_set_state(nego, NEGO_STATE_EXT); } @@ -146,9 +151,14 @@ BOOL nego_connect(rdpNego* nego) nego->EnabledProtocols[PROTOCOL_SSL] = FALSE; nego->EnabledProtocols[PROTOCOL_RDP] = FALSE; nego->EnabledProtocols[PROTOCOL_HYBRID_EX] = FALSE; + nego->EnabledProtocols[PROTOCOL_RDSTLS] = FALSE; switch (nego_get_state(nego)) { + case NEGO_STATE_RDSTLS: + nego->EnabledProtocols[PROTOCOL_RDSTLS] = TRUE; + nego->SelectedProtocol = PROTOCOL_RDSTLS; + break; case NEGO_STATE_EXT: nego->EnabledProtocols[PROTOCOL_HYBRID_EX] = TRUE; nego->EnabledProtocols[PROTOCOL_HYBRID] = TRUE; @@ -262,7 +272,12 @@ BOOL nego_security_connect(rdpNego* nego) } else if (!nego->SecurityConnected) { - if (nego->SelectedProtocol == PROTOCOL_HYBRID) + if (nego->SelectedProtocol == PROTOCOL_RDSTLS) + { + WLog_DBG(TAG, "nego_security_connect with PROTOCOL_RDSTLS"); + nego->SecurityConnected = transport_connect_rdstls(nego->transport); + } + else if (nego->SelectedProtocol == PROTOCOL_HYBRID) { WLog_DBG(TAG, "nego_security_connect with PROTOCOL_HYBRID"); nego->SecurityConnected = transport_connect_nla(nego->transport); @@ -442,6 +457,49 @@ BOOL nego_send_preconnection_pdu(rdpNego* nego) return TRUE; } +static void nego_attempt_rdstls(rdpNego* nego) +{ + WINPR_ASSERT(nego); + nego->RequestedProtocols = PROTOCOL_RDSTLS | PROTOCOL_SSL; + WLog_DBG(TAG, "Attempting RDSTLS security"); + + if (!nego_transport_connect(nego)) + { + nego_set_state(nego, NEGO_STATE_FAIL); + return; + } + + if (!nego_send_negotiation_request(nego)) + { + nego_set_state(nego, NEGO_STATE_FAIL); + return; + } + + if (!nego_recv_response(nego)) + { + nego_set_state(nego, NEGO_STATE_FAIL); + return; + } + + WLog_DBG(TAG, "state: %s", nego_state_string(nego_get_state(nego))); + + if (nego_get_state(nego) != NEGO_STATE_FINAL) + { + nego_transport_disconnect(nego); + + if (nego->EnabledProtocols[PROTOCOL_HYBRID_EX]) + nego_set_state(nego, NEGO_STATE_EXT); + else if (nego->EnabledProtocols[PROTOCOL_HYBRID]) + nego_set_state(nego, NEGO_STATE_NLA); + else if (nego->EnabledProtocols[PROTOCOL_SSL]) + nego_set_state(nego, NEGO_STATE_TLS); + else if (nego->EnabledProtocols[PROTOCOL_RDP]) + nego_set_state(nego, NEGO_STATE_RDP); + else + nego_set_state(nego, NEGO_STATE_FAIL); + } +} + static void nego_attempt_ext(rdpNego* nego) { WINPR_ASSERT(nego); @@ -863,6 +921,9 @@ void nego_send(rdpNego* nego) switch (nego_get_state(nego)) { + case NEGO_STATE_RDSTLS: + nego_attempt_rdstls(nego); + break; case NEGO_STATE_EXT: nego_attempt_ext(nego); break; @@ -1258,7 +1319,7 @@ BOOL nego_send_negotiation_response(rdpNego* nego) if (freerdp_settings_get_bool(settings, FreeRDP_SupportGraphicsPipeline)) flags |= DYNVC_GFX_PROTOCOL_SUPPORTED; - /* RDP_NEG_DATA must be present for TLS, NLA, and RDP */ + /* RDP_NEG_DATA must be present for TLS, NLA, RDP and RDSTLS */ Stream_Write_UINT8(s, TYPE_RDP_NEG_RSP); Stream_Write_UINT8(s, flags); /* flags */ Stream_Write_UINT16(s, 8); /* RDP_NEG_DATA length (8) */ @@ -1339,6 +1400,8 @@ BOOL nego_send_negotiation_response(rdpNego* nego) return FALSE; if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE)) return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_RdstlsSecurity, FALSE)) + return FALSE; if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE)) return FALSE; if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, FALSE)) @@ -1354,6 +1417,25 @@ BOOL nego_send_negotiation_response(rdpNego* nego) return FALSE; if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, TRUE)) return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_RdstlsSecurity, FALSE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, FALSE)) + return FALSE; + + if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionLevel, + ENCRYPTION_LEVEL_NONE)) + return FALSE; + } + else if (nego->SelectedProtocol == PROTOCOL_RDSTLS) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, FALSE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_RdstlsSecurity, TRUE)) + return FALSE; if (!freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, FALSE)) return FALSE; if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, FALSE)) @@ -1512,6 +1594,19 @@ void nego_enable_nla(rdpNego* nego, BOOL enable_nla) nego->EnabledProtocols[PROTOCOL_HYBRID] = enable_nla; } +/** + * Enable RDSTLS security protocol. + * @param nego A pointer to the NEGO struct pointer to the negotiation structure + * @param enable_rdstls whether to enable RDSTLS protocol (TRUE for enabled, + * FALSE for disabled) + */ + +void nego_enable_rdstls(rdpNego* nego, BOOL enable_rdstls) +{ + WLog_DBG(TAG, "Enabling RDSTLS security: %s", enable_rdstls ? "TRUE" : "FALSE"); + nego->EnabledProtocols[PROTOCOL_RDSTLS] = enable_rdstls; +} + /** * Enable NLA extended security protocol. * @param nego A pointer to the NEGO struct pointer to the negotiation structure diff --git a/libfreerdp/core/nego.h b/libfreerdp/core/nego.h index c30a80028..f39e1cc63 100644 --- a/libfreerdp/core/nego.h +++ b/libfreerdp/core/nego.h @@ -58,11 +58,12 @@ enum RDP_NEG_FAILURE_FAILURECODES typedef enum { NEGO_STATE_INITIAL, - NEGO_STATE_EXT, /* Extended NLA (NLA + TLS implicit) */ - NEGO_STATE_NLA, /* Network Level Authentication (TLS implicit) */ - NEGO_STATE_TLS, /* TLS Encryption without NLA */ - NEGO_STATE_RDP, /* Standard Legacy RDP Encryption */ - NEGO_STATE_FAIL, /* Negotiation failure */ + NEGO_STATE_RDSTLS, /* RDSTLS (TLS implicit) */ + NEGO_STATE_EXT, /* Extended NLA (NLA + TLS implicit) */ + NEGO_STATE_NLA, /* Network Level Authentication (TLS implicit) */ + NEGO_STATE_TLS, /* TLS Encryption without NLA */ + NEGO_STATE_RDP, /* Standard Legacy RDP Encryption */ + NEGO_STATE_FAIL, /* Negotiation failure */ NEGO_STATE_FINAL } NEGO_STATE; @@ -120,6 +121,7 @@ FREERDP_LOCAL void nego_set_gateway_bypass_local(rdpNego* nego, BOOL GatewayBypa FREERDP_LOCAL void nego_enable_rdp(rdpNego* nego, BOOL enable_rdp); FREERDP_LOCAL void nego_enable_tls(rdpNego* nego, BOOL enable_tls); FREERDP_LOCAL void nego_enable_nla(rdpNego* nego, BOOL enable_nla); +FREERDP_LOCAL void nego_enable_rdstls(rdpNego* nego, BOOL enable_rdstls); FREERDP_LOCAL void nego_enable_ext(rdpNego* nego, BOOL enable_ext); FREERDP_LOCAL const BYTE* nego_get_routing_token(rdpNego* nego, DWORD* RoutingTokenLength); FREERDP_LOCAL BOOL nego_set_routing_token(rdpNego* nego, const BYTE* RoutingToken, diff --git a/libfreerdp/core/peer.c b/libfreerdp/core/peer.c index 889330d50..986583631 100644 --- a/libfreerdp/core/peer.c +++ b/libfreerdp/core/peer.c @@ -822,6 +822,7 @@ static state_run_t peer_recv_callback_internal(rdpTransport* transport, wStream* else { SelectedProtocol = nego_get_selected_protocol(rdp->nego); + settings->RdstlsSecurity = (SelectedProtocol & PROTOCOL_RDSTLS) ? TRUE : FALSE; settings->NlaSecurity = (SelectedProtocol & PROTOCOL_HYBRID) ? TRUE : FALSE; settings->TlsSecurity = (SelectedProtocol & PROTOCOL_SSL) ? TRUE : FALSE; settings->RdpSecurity = (SelectedProtocol == PROTOCOL_RDP) ? TRUE : FALSE; diff --git a/libfreerdp/core/rdstls.c b/libfreerdp/core/rdstls.c new file mode 100644 index 000000000..cbd0ddea0 --- /dev/null +++ b/libfreerdp/core/rdstls.c @@ -0,0 +1,785 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDSTLS Security protocol + * + * Copyright 2023 Joan Torres + * + * 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 +#include + +#include "rdstls.h" +#include "transport.h" +#include "utils.h" + +#define TAG FREERDP_TAG("core.rdstls") + +struct rdp_rdstls +{ + BOOL server; + RDSTLS_STATE state; + rdpContext* context; + rdpTransport* transport; + + UINT32 resultCode; +}; + +/** + * Create new RDSTLS state machine. + * + * @param context A pointer to the rdp context to use + * + * @return new RDSTLS state machine. + */ + +rdpRdstls* rdstls_new(rdpContext* context, rdpTransport* transport) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(transport); + + rdpSettings* settings = context->settings; + WINPR_ASSERT(settings); + + rdpRdstls* rdstls = (rdpRdstls*)calloc(1, sizeof(rdpRdstls)); + + if (!rdstls) + return NULL; + + rdstls->context = context; + rdstls->transport = transport; + rdstls->server = settings->ServerMode; + + return rdstls; +} + +/** + * Free RDSTLS state machine. + * @param rdstls The RDSTLS instance to free + */ + +void rdstls_free(rdpRdstls* rdstls) +{ + if (!rdstls) + return; + + free(rdstls); +} + +static const char* rdstls_get_state_str(RDSTLS_STATE state) +{ + switch (state) + { + case RDSTLS_STATE_CAPABILITIES: + return "RDSTLS_STATE_CAPABILITIES"; + case RDSTLS_STATE_AUTH_REQ: + return "RDSTLS_STATE_AUTH_REQ"; + case RDSTLS_STATE_AUTH_RSP: + return "RDSTLS_STATE_AUTH_RSP"; + default: + return "UNKNOWN"; + } +} + +static RDSTLS_STATE rdstls_get_state(rdpRdstls* rdstls) +{ + WINPR_ASSERT(rdstls); + return rdstls->state; +} + +static BOOL rdstls_set_state(rdpRdstls* rdstls, RDSTLS_STATE state) +{ + WINPR_ASSERT(rdstls); + + WLog_DBG(TAG, "-- %s\t--> %s", rdstls_get_state_str(rdstls->state), + rdstls_get_state_str(state)); + rdstls->state = state; + return TRUE; +} + +static BOOL rdstls_write_capabilities(rdpRdstls* rdstls, wStream* s) +{ + if (!Stream_EnsureRemainingCapacity(s, 6)) + return FALSE; + + Stream_Write_UINT16(s, RDSTLS_TYPE_CAPABILITIES); + Stream_Write_UINT16(s, RDSTLS_DATA_CAPABILITIES); + Stream_Write_UINT16(s, RDSTLS_VERSION_1); + + return TRUE; +} + +static SSIZE_T rdstls_write_string(wStream* s, const char* str) +{ + const size_t pos = Stream_GetPosition(s); + + if (!Stream_EnsureRemainingCapacity(s, 2)) + return -1; + + if (!str) + { + /* Write unicode null */ + Stream_Write_UINT16(s, 2); + Stream_Write_UINT16(s, 0); + return (SSIZE_T)(Stream_GetPosition(s) - pos); + } + + const size_t length = (strlen(str) + 1); + + Stream_Write_UINT16(s, (UINT16)length * sizeof(WCHAR)); + + if (!Stream_EnsureRemainingCapacity(s, length * sizeof(WCHAR))) + return -1; + + if (Stream_Write_UTF16_String_From_UTF8(s, length, str, length, TRUE) < 0) + return -1; + + return (SSIZE_T)(Stream_GetPosition(s) - pos); +} + +static BOOL rdstls_write_data(wStream* s, UINT32 length, const BYTE* data) +{ + if (!Stream_EnsureRemainingCapacity(s, 2)) + return -1; + + if (!data) + { + /* Write unicode null */ + Stream_Write_UINT16(s, 2); + Stream_Write_UINT16(s, 0); + return TRUE; + } + + Stream_Write_UINT16(s, length); + + if (!Stream_EnsureRemainingCapacity(s, length)) + return FALSE; + + Stream_Write(s, data, length); + + return TRUE; +} + +static BOOL rdstls_write_authentication_request_with_password(rdpRdstls* rdstls, wStream* s) +{ + rdpSettings* settings = rdstls->context->settings; + WINPR_ASSERT(settings); + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return FALSE; + + Stream_Write_UINT16(s, RDSTLS_TYPE_AUTHREQ); + Stream_Write_UINT16(s, RDSTLS_DATA_PASSWORD_CREDS); + + if (!rdstls_write_data(s, settings->RedirectionGuidLength, settings->RedirectionGuid)) + return FALSE; + + if (rdstls_write_string(s, settings->Username) < 0) + return FALSE; + + if (rdstls_write_string(s, settings->Domain) < 0) + return FALSE; + + if (!rdstls_write_data(s, settings->RedirectionPasswordLength, settings->RedirectionPassword)) + return FALSE; + + return TRUE; +} + +static BOOL rdstls_write_authentication_request_with_cookie(rdpRdstls* rdstls, wStream* s) +{ + // TODO + return FALSE; +} + +static BOOL rdstls_write_authentication_response(rdpRdstls* rdstls, wStream* s) +{ + if (!Stream_EnsureRemainingCapacity(s, 8)) + return FALSE; + + Stream_Write_UINT16(s, RDSTLS_TYPE_AUTHRSP); + Stream_Write_UINT16(s, RDSTLS_DATA_RESULT_CODE); + Stream_Write_UINT32(s, rdstls->resultCode); + + return TRUE; +} + +static BOOL rdstls_process_capabilities(rdpRdstls* rdstls, wStream* s) +{ + UINT16 dataType; + UINT16 supportedVersions; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return FALSE; + + Stream_Read_UINT16(s, dataType); + if (dataType != RDSTLS_DATA_CAPABILITIES) + { + WLog_ERR(TAG, "received invalid DataType=0x%04" PRIX16 ", expected 0x%04" PRIX16, dataType, + RDSTLS_DATA_CAPABILITIES); + return FALSE; + } + + Stream_Read_UINT16(s, supportedVersions); + if ((supportedVersions & RDSTLS_VERSION_1) == 0) + { + WLog_ERR(TAG, "received invalid supportedVersions=0x%04" PRIX16 ", expected 0x%04" PRIX16, + supportedVersions, RDSTLS_VERSION_1); + return FALSE; + } + + return TRUE; +} + +static BOOL rdstls_read_unicode_string(wStream* s, char** str) +{ + UINT16 length = 0; + + WINPR_ASSERT(str); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return FALSE; + + Stream_Read_UINT16(s, length); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, length)) + return FALSE; + + if (length <= 2) + { + Stream_Seek(s, length); + return TRUE; + } + + *str = Stream_Read_UTF16_String_As_UTF8(s, length / sizeof(WCHAR), NULL); + if (!*str) + return FALSE; + + return TRUE; +} + +static BOOL rdstls_read_data(wStream* s, UINT16* pLength, BYTE** pData) +{ + UINT16 length = 0; + + WINPR_ASSERT(pLength); + WINPR_ASSERT(pData); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return FALSE; + + Stream_Read_UINT16(s, length); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, length)) + return FALSE; + + if (length <= 2) + { + Stream_Seek(s, length); + return TRUE; + } + + free(*pData); + *pData = (BYTE*)malloc(length); + if (!*pData) + return FALSE; + + Stream_Read(s, *pData, length); + *pLength = length; + + return TRUE; +} + +static BOOL rdstls_cmp_data(const char* field, const BYTE* serverData, + const UINT32 serverDataLength, const BYTE* clientData, + const UINT16 clientDataLength) +{ + if (serverDataLength > 0) + { + if (clientDataLength == 0) + { + WLog_ERR(TAG, "expected %s", field); + return FALSE; + } + + if (serverDataLength > UINT16_MAX || serverDataLength != clientDataLength || + memcmp(serverData, clientData, serverDataLength) != 0) + { + WLog_ERR(TAG, "%s verification failed", field); + return FALSE; + } + } + + return TRUE; +} + +static BOOL rdstls_cmp_str(const char* field, const char* serverStr, const char* clientStr) +{ + if (!utils_str_is_empty(serverStr)) + { + if (utils_str_is_empty(clientStr)) + { + WLog_ERR(TAG, "expected %s", field); + return FALSE; + } + + if (strcmp(serverStr, clientStr) != 0) + { + WLog_ERR(TAG, "%s verification failed", field); + return FALSE; + } + } + + return TRUE; +} + +static BOOL rdstls_process_authentication_request_with_password(rdpRdstls* rdstls, wStream* s) +{ + BOOL rc = FALSE; + + BYTE* clientRedirectionGuid = NULL; + UINT16 clientRedirectionGuidLength = 0; + char* clientPassword = NULL; + char* clientUsername = NULL; + char* clientDomain = NULL; + + const BYTE* serverRedirectionGuid = NULL; + UINT16 serverRedirectionGuidLength = 0; + const char* serverPassword = NULL; + const char* serverUsername = NULL; + const char* serverDomain = NULL; + + rdpSettings* settings = rdstls->context->settings; + WINPR_ASSERT(settings); + + if (!rdstls_read_data(s, &clientRedirectionGuidLength, &clientRedirectionGuid)) + goto fail; + + if (!rdstls_read_unicode_string(s, &clientUsername)) + goto fail; + + if (!rdstls_read_unicode_string(s, &clientDomain)) + goto fail; + + if (!rdstls_read_unicode_string(s, &clientPassword)) + goto fail; + + serverRedirectionGuid = freerdp_settings_get_pointer(settings, FreeRDP_RedirectionGuid); + serverRedirectionGuidLength = + freerdp_settings_get_uint32(settings, FreeRDP_RedirectionGuidLength); + serverUsername = freerdp_settings_get_string(settings, FreeRDP_Username); + serverDomain = freerdp_settings_get_string(settings, FreeRDP_Domain); + serverPassword = freerdp_settings_get_pointer(settings, FreeRDP_Password); + + rdstls->resultCode = ERROR_SUCCESS; + + if (!rdstls_cmp_data("RedirectionGuid", serverRedirectionGuid, serverRedirectionGuidLength, + clientRedirectionGuid, clientRedirectionGuidLength)) + rdstls->resultCode = ERROR_LOGON_FAILURE; + + if (!rdstls_cmp_str("UserName", serverUsername, clientUsername)) + rdstls->resultCode = ERROR_LOGON_FAILURE; + + if (!rdstls_cmp_str("Domain", serverDomain, clientDomain)) + rdstls->resultCode = ERROR_LOGON_FAILURE; + + if (!rdstls_cmp_str("Password", serverPassword, clientPassword)) + rdstls->resultCode = ERROR_LOGON_FAILURE; + + rc = TRUE; +fail: + free(clientRedirectionGuid); + return rc; +} + +static BOOL rdstls_process_authentication_request_with_cookie(rdpRdstls* rdstls, wStream* s) +{ + // TODO + return FALSE; +} + +static BOOL rdstls_process_authentication_request(rdpRdstls* rdstls, wStream* s) +{ + UINT16 dataType; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return FALSE; + + Stream_Read_UINT16(s, dataType); + switch (dataType) + { + case RDSTLS_DATA_PASSWORD_CREDS: + if (!rdstls_process_authentication_request_with_password(rdstls, s)) + return FALSE; + break; + case RDSTLS_DATA_AUTORECONNECT_COOKIE: + if (!rdstls_process_authentication_request_with_cookie(rdstls, s)) + return FALSE; + break; + default: + WLog_ERR(TAG, + "received invalid DataType=0x%04" PRIX16 ", expected 0x%04" PRIX16 + " or 0x%04" PRIX16, + dataType, RDSTLS_DATA_PASSWORD_CREDS, RDSTLS_DATA_AUTORECONNECT_COOKIE); + return FALSE; + } + + return TRUE; +} + +static BOOL rdstls_process_authentication_response(rdpRdstls* rdstls, wStream* s) +{ + UINT16 dataType; + UINT32 resultCode; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 6)) + return FALSE; + + Stream_Read_UINT16(s, dataType); + if (dataType != RDSTLS_DATA_RESULT_CODE) + { + WLog_ERR(TAG, "received invalid DataType=0x%04" PRIX16 ", expected 0x%04" PRIX16, dataType, + RDSTLS_DATA_RESULT_CODE); + return FALSE; + } + + Stream_Read_UINT32(s, resultCode); + if (resultCode != ERROR_SUCCESS) + { + WLog_ERR(TAG, "resultCode: %s [0x%08" PRIX32 "] %s", + freerdp_get_last_error_name(resultCode), resultCode, + freerdp_get_last_error_string(resultCode)); + return FALSE; + } + + return TRUE; +} + +static BOOL rdstls_send(rdpTransport* transport, wStream* s, void* extra) +{ + rdpRdstls* rdstls = (rdpRdstls*)extra; + rdpSettings* settings = NULL; + + WINPR_ASSERT(transport); + WINPR_ASSERT(s); + WINPR_ASSERT(rdstls); + + settings = rdstls->context->settings; + WINPR_ASSERT(settings); + + if (!Stream_EnsureRemainingCapacity(s, 2)) + return FALSE; + + Stream_Write_UINT16(s, RDSTLS_VERSION_1); + + switch (rdstls_get_state(rdstls)) + { + case RDSTLS_STATE_CAPABILITIES: + if (!rdstls_write_capabilities(rdstls, s)) + return FALSE; + break; + case RDSTLS_STATE_AUTH_REQ: + if (settings->RedirectionFlags & LB_PASSWORD_IS_PK_ENCRYPTED) + { + if (!rdstls_write_authentication_request_with_password(rdstls, s)) + return FALSE; + } + else if (settings->ServerAutoReconnectCookie != NULL) + { + if (!rdstls_write_authentication_request_with_cookie(rdstls, s)) + return FALSE; + } + else + { + WLog_ERR(TAG, "cannot authenticate with password or auto-reconnect cookie"); + return FALSE; + } + break; + case RDSTLS_STATE_AUTH_RSP: + if (!rdstls_write_authentication_response(rdstls, s)) + return FALSE; + break; + } + + if (transport_write(rdstls->transport, s) < 0) + return FALSE; + + return TRUE; +} + +static int rdstls_recv(rdpTransport* transport, wStream* s, void* extra) +{ + UINT16 length; + UINT16 version; + UINT16 pduType; + rdpRdstls* rdstls = (rdpRdstls*)extra; + + WINPR_ASSERT(transport); + WINPR_ASSERT(s); + WINPR_ASSERT(rdstls); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return FALSE; + + Stream_Read_UINT16(s, version); + if (version != RDSTLS_VERSION_1) + { + WLog_ERR(TAG, "received invalid RDSTLS Version=0x%04" PRIX16 ", expected 0x%04" PRIX16, + version, RDSTLS_VERSION_1); + return -1; + } + + Stream_Read_UINT16(s, pduType); + switch (pduType) + { + case RDSTLS_TYPE_CAPABILITIES: + if (!rdstls_process_capabilities(rdstls, s)) + return -1; + break; + case RDSTLS_TYPE_AUTHREQ: + if (!rdstls_process_authentication_request(rdstls, s)) + return -1; + break; + case RDSTLS_TYPE_AUTHRSP: + if (!rdstls_process_authentication_response(rdstls, s)) + return -1; + break; + default: + WLog_ERR(TAG, "unknown RDSTLS PDU type"); + return -1; + } + + return 1; +} + +static BOOL rdstls_send_capabilities(rdpRdstls* rdstls) +{ + BOOL rc = FALSE; + wStream* s; + + WINPR_ASSERT(rdstls); + + if (rdstls_get_state(rdstls) != RDSTLS_STATE_CAPABILITIES) + goto fail; + + s = Stream_New(NULL, 512); + if (!s) + goto fail; + + if (!rdstls_send(rdstls->transport, s, rdstls)) + goto fail; + + rc = rdstls_set_state(rdstls, RDSTLS_STATE_AUTH_REQ); +fail: + Stream_Free(s, TRUE); + return rc; +} + +static BOOL rdstls_recv_authentication_request(rdpRdstls* rdstls) +{ + BOOL rc = FALSE; + int status; + wStream* s; + + WINPR_ASSERT(rdstls); + + if (rdstls_get_state(rdstls) != RDSTLS_STATE_AUTH_REQ) + goto fail; + + s = Stream_New(NULL, 4096); + if (!s) + goto fail; + + status = transport_read_pdu(rdstls->transport, s); + + if (status < 0) + goto fail; + + status = rdstls_recv(rdstls->transport, s, rdstls); + + if (status < 0) + goto fail; + + rc = rdstls_set_state(rdstls, RDSTLS_STATE_AUTH_RSP); +fail: + Stream_Free(s, TRUE); + return rc; +} + +static BOOL rdstls_send_authentication_response(rdpRdstls* rdstls) +{ + BOOL rc = FALSE; + wStream* s; + + WINPR_ASSERT(rdstls); + + if (rdstls_get_state(rdstls) != RDSTLS_STATE_AUTH_RSP) + goto fail; + + s = Stream_New(NULL, 512); + if (!s) + goto fail; + + if (!rdstls_send(rdstls->transport, s, rdstls)) + goto fail; + + rc = TRUE; +fail: + Stream_Free(s, TRUE); + return rc; +} + +static BOOL rdstls_recv_capabilities(rdpRdstls* rdstls) +{ + BOOL rc = FALSE; + int status; + wStream* s; + + WINPR_ASSERT(rdstls); + + if (rdstls_get_state(rdstls) != RDSTLS_STATE_CAPABILITIES) + goto fail; + + s = Stream_New(NULL, 512); + if (!s) + goto fail; + + status = transport_read_pdu(rdstls->transport, s); + + if (status < 0) + goto fail; + + status = rdstls_recv(rdstls->transport, s, rdstls); + + if (status < 0) + goto fail; + + rc = rdstls_set_state(rdstls, RDSTLS_STATE_AUTH_REQ); +fail: + Stream_Free(s, TRUE); + return rc; +} + +static BOOL rdstls_send_authentication_request(rdpRdstls* rdstls) +{ + BOOL rc = FALSE; + wStream* s; + + WINPR_ASSERT(rdstls); + + if (rdstls_get_state(rdstls) != RDSTLS_STATE_AUTH_REQ) + goto fail; + + s = Stream_New(NULL, 4096); + if (!s) + goto fail; + + if (!rdstls_send(rdstls->transport, s, rdstls)) + goto fail; + + rc = rdstls_set_state(rdstls, RDSTLS_STATE_AUTH_RSP); +fail: + Stream_Free(s, TRUE); + return rc; +} + +static BOOL rdstls_recv_authentication_response(rdpRdstls* rdstls) +{ + BOOL rc = FALSE; + int status; + wStream* s; + + WINPR_ASSERT(rdstls); + + if (rdstls_get_state(rdstls) != RDSTLS_STATE_AUTH_RSP) + goto fail; + + s = Stream_New(NULL, 512); + if (!s) + goto fail; + + status = transport_read_pdu(rdstls->transport, s); + + if (status < 0) + goto fail; + + status = rdstls_recv(rdstls->transport, s, rdstls); + + if (status < 0) + goto fail; + + rc = TRUE; +fail: + Stream_Free(s, TRUE); + return rc; +} + +static int rdstls_server_authenticate(rdpRdstls* rdstls) +{ + rdstls_set_state(rdstls, RDSTLS_STATE_CAPABILITIES); + + if (!rdstls_send_capabilities(rdstls)) + return -1; + + if (!rdstls_recv_authentication_request(rdstls)) + return -1; + + if (!rdstls_send_authentication_response(rdstls)) + return -1; + + if (rdstls->resultCode != 0) + return -1; + + return 1; +} + +static int rdstls_client_authenticate(rdpRdstls* rdstls) +{ + rdstls_set_state(rdstls, RDSTLS_STATE_CAPABILITIES); + + if (!rdstls_recv_capabilities(rdstls)) + return -1; + + if (!rdstls_send_authentication_request(rdstls)) + return -1; + + if (!rdstls_recv_authentication_response(rdstls)) + return -1; + + return 1; +} + +/** + * Authenticate using RDSTLS. + * @param rdstls The RDSTLS instance to use + * + * @return 1 if authentication is successful + */ + +int rdstls_authenticate(rdpRdstls* rdstls) +{ + WINPR_ASSERT(rdstls); + + if (rdstls->server) + return rdstls_server_authenticate(rdstls); + else + return rdstls_client_authenticate(rdstls); +} diff --git a/libfreerdp/core/rdstls.h b/libfreerdp/core/rdstls.h new file mode 100644 index 000000000..b47d88fa7 --- /dev/null +++ b/libfreerdp/core/rdstls.h @@ -0,0 +1,50 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDSTLS Security protocol + * + * Copyright 2023 Joan Torres + * + * 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. + */ + +#ifndef FREERDP_LIB_CORE_RDSTLS_H +#define FREERDP_LIB_CORE_RDSTLS_H + +typedef struct rdp_rdstls rdpRdstls; + +#include + +#define RDSTLS_VERSION_1 0x01 + +#define RDSTLS_TYPE_CAPABILITIES 0x01 +#define RDSTLS_TYPE_AUTHREQ 0x02 +#define RDSTLS_TYPE_AUTHRSP 0x04 + +#define RDSTLS_DATA_CAPABILITIES 0x01 +#define RDSTLS_DATA_PASSWORD_CREDS 0x01 +#define RDSTLS_DATA_AUTORECONNECT_COOKIE 0x02 +#define RDSTLS_DATA_RESULT_CODE 0x01 + +typedef enum +{ + RDSTLS_STATE_CAPABILITIES, + RDSTLS_STATE_AUTH_REQ, + RDSTLS_STATE_AUTH_RSP, +} RDSTLS_STATE; + +FREERDP_LOCAL int rdstls_authenticate(rdpRdstls* rdstls); + +FREERDP_LOCAL rdpRdstls* rdstls_new(rdpContext* context, rdpTransport* transport); +FREERDP_LOCAL void rdstls_free(rdpRdstls* rdstls); + +#endif /* FREERDP_LIB_CORE_RDSTLS_H */ diff --git a/libfreerdp/core/settings.c b/libfreerdp/core/settings.c index c8ae61c5b..fe42b783f 100644 --- a/libfreerdp/core/settings.c +++ b/libfreerdp/core/settings.c @@ -388,6 +388,7 @@ rdpSettings* freerdp_settings_new(DWORD flags) !freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, TRUE) || !freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE) || !freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_RdstlsSecurity, FALSE) || !freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer, TRUE) || !freerdp_settings_set_bool(settings, FreeRDP_RestrictedAdminModeRequired, FALSE) || !freerdp_settings_set_bool(settings, FreeRDP_MstscCookieMode, FALSE) || diff --git a/libfreerdp/core/transport.c b/libfreerdp/core/transport.c index 8b41da1d2..6ba9bce0a 100644 --- a/libfreerdp/core/transport.c +++ b/libfreerdp/core/transport.c @@ -73,6 +73,7 @@ struct rdp_transport wStreamPool* ReceivePool; HANDLE connectedEvent; BOOL NlaMode; + BOOL RdstlsMode; BOOL blocking; BOOL GatewayEnabled; CRITICAL_SECTION ReadLock; @@ -367,6 +368,37 @@ BOOL transport_connect_nla(rdpTransport* transport) return rdp_client_transition_to_state(rdp, CONNECTION_STATE_NLA); } +BOOL transport_connect_rdstls(rdpTransport* transport) +{ + BOOL rc = FALSE; + rdpRdstls* rdstls = NULL; + rdpContext* context = NULL; + + WINPR_ASSERT(transport); + + context = transport_get_context(transport); + WINPR_ASSERT(context); + + if (!transport_connect_tls(transport)) + goto fail; + + rdstls = rdstls_new(context, transport); + transport_set_rdstls_mode(transport, TRUE); + + if (rdstls_authenticate(rdstls) < 0) + { + WLog_Print(transport->log, WLOG_ERROR, "RDSTLS authentication failed"); + freerdp_set_last_error_if_not(context, FREERDP_ERROR_AUTHENTICATION_FAILED); + goto fail; + } + + transport_set_rdstls_mode(transport, FALSE); + rc = TRUE; +fail: + rdstls_free(rdstls); + return rc; +} + BOOL transport_connect(rdpTransport* transport, const char* hostname, UINT16 port, DWORD timeout) { int sockfd; @@ -542,6 +574,39 @@ BOOL transport_accept_nla(rdpTransport* transport) return TRUE; } +BOOL transport_accept_rdstls(rdpTransport* transport) +{ + BOOL rc = FALSE; + rdpRdstls* rdstls = NULL; + rdpContext* context = NULL; + + WINPR_ASSERT(transport); + + context = transport_get_context(transport); + WINPR_ASSERT(context); + + if (!IFCALLRESULT(FALSE, transport->io.TLSAccept, transport)) + goto fail; + + rdstls = rdstls_new(context, transport); + transport_set_rdstls_mode(transport, TRUE); + + if (rdstls_authenticate(rdstls) < 0) + { + WLog_Print(transport->log, WLOG_ERROR, "client authentication failure"); + freerdp_tls_set_alert_code(transport->tls, TLS_ALERT_LEVEL_FATAL, + TLS_ALERT_DESCRIPTION_ACCESS_DENIED); + freerdp_tls_send_alert(transport->tls); + goto fail; + } + + transport_set_rdstls_mode(transport, FALSE); + rc = TRUE; +fail: + rdstls_free(rdstls); + return rc; +} + #define WLog_ERR_BIO(transport, biofunc, bio) \ transport_bio_error_log(transport, biofunc, bio, __FILE__, __FUNCTION__, __LINE__) @@ -773,6 +838,82 @@ SSIZE_T transport_parse_pdu(rdpTransport* transport, wStream* s, BOOL* incomplet } } } + else if (transport->RdstlsMode) + { + UINT16 version = (header[1] << 8) | header[0]; + UINT16 pduType; + UINT16 dataType; + + if (version != RDSTLS_VERSION_1) + { + WLog_Print(transport->log, WLOG_ERROR, "invalid RDSTLS version"); + return -1; + } + + if (position < 4) + return 0; + + pduType = (header[3] << 8) | header[2]; + switch (pduType) + { + case RDSTLS_TYPE_CAPABILITIES: + pduLength = 8; + break; + case RDSTLS_TYPE_AUTHREQ: + if (position < 6) + return 0; + + dataType = (header[5] << 8 | header[4]); + if (dataType == RDSTLS_DATA_PASSWORD_CREDS) + { + if (position < 8) + return 0; + + UINT16 redirGuidLength = (header[7] << 8 | header[6]); + size_t usernamePos = 8 + redirGuidLength; + + if (position < usernamePos + 2) + return 0; + + UINT16 usernameLength = (header[usernamePos + 1] << 8) | header[usernamePos]; + size_t domainPos = 8 + redirGuidLength + 2 + usernameLength; + + if (position < domainPos + 2) + return 0; + + UINT16 domainLength = (header[domainPos + 1] << 8) | header[domainPos]; + size_t passwordPos = + 8 + redirGuidLength + 2 + usernameLength + 2 + domainLength; + + if (position < passwordPos + 2) + return 0; + + UINT16 passwordLength = (header[passwordPos + 1] << 8) | header[passwordPos]; + + pduLength = 8 + redirGuidLength + 2 + usernameLength + 2 + domainLength + 2 + + passwordLength; + } + else if (dataType == RDSTLS_DATA_AUTORECONNECT_COOKIE) + { + if (position < 12) + return 0; + + pduLength = 12 + ((header[11] << 8) | header[10]); + } + else + { + WLog_Print(transport->log, WLOG_ERROR, "invalid RDSLTS dataType"); + return -1; + } + break; + case RDSTLS_TYPE_AUTHRSP: + pduLength = 10; + break; + default: + WLog_Print(transport->log, WLOG_ERROR, "invalid RDSTLS PDU type"); + return -1; + } + } else { if (header[0] == 0x03) @@ -1218,6 +1359,12 @@ void transport_set_nla_mode(rdpTransport* transport, BOOL NlaMode) transport->NlaMode = NlaMode; } +void transport_set_rdstls_mode(rdpTransport* transport, BOOL RdstlsMode) +{ + WINPR_ASSERT(transport); + transport->RdstlsMode = RdstlsMode; +} + BOOL transport_disconnect(rdpTransport* transport) { if (!transport) diff --git a/libfreerdp/core/transport.h b/libfreerdp/core/transport.h index cd27e7c1a..737c447a2 100644 --- a/libfreerdp/core/transport.h +++ b/libfreerdp/core/transport.h @@ -31,6 +31,7 @@ typedef enum #include "tcp.h" #include "nla.h" +#include "rdstls.h" #include "gateway/tsg.h" #include "gateway/rdg.h" @@ -61,9 +62,11 @@ FREERDP_LOCAL BOOL transport_disconnect(rdpTransport* transport); FREERDP_LOCAL BOOL transport_connect_rdp(rdpTransport* transport); FREERDP_LOCAL BOOL transport_connect_tls(rdpTransport* transport); FREERDP_LOCAL BOOL transport_connect_nla(rdpTransport* transport); +FREERDP_LOCAL BOOL transport_connect_rdstls(rdpTransport* transport); FREERDP_LOCAL BOOL transport_accept_rdp(rdpTransport* transport); FREERDP_LOCAL BOOL transport_accept_tls(rdpTransport* transport); FREERDP_LOCAL BOOL transport_accept_nla(rdpTransport* transport); +FREERDP_LOCAL BOOL transport_accept_rdstls(rdpTransport* transport); FREERDP_LOCAL int transport_read_pdu(rdpTransport* transport, wStream* s); FREERDP_LOCAL int transport_write(rdpTransport* transport, wStream* s); @@ -80,6 +83,7 @@ FREERDP_LOCAL HANDLE transport_get_front_bio(rdpTransport* transport); FREERDP_LOCAL BOOL transport_set_blocking_mode(rdpTransport* transport, BOOL blocking); FREERDP_LOCAL void transport_set_gateway_enabled(rdpTransport* transport, BOOL GatewayEnabled); FREERDP_LOCAL void transport_set_nla_mode(rdpTransport* transport, BOOL NlaMode); +FREERDP_LOCAL void transport_set_rdstls_mode(rdpTransport* transport, BOOL RdstlsMode); FREERDP_LOCAL BOOL transport_is_write_blocked(rdpTransport* transport); FREERDP_LOCAL int transport_drain_output_buffer(rdpTransport* transport);