FreeRDP/libfreerdp/core/nego.c

1344 lines
29 KiB
C
Raw Normal View History

/**
2012-10-09 07:02:04 +04:00
* FreeRDP: A Remote Desktop Protocol Implementation
* RDP Protocol Security Negotiation
*
* Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
Standard RDP Security Layer Levels/Method Overhaul [MS-RDPBCGR] Section 5.3 describes the encryption level and method values for standard RDP security. Looking at the current usage of these values in the FreeRDP code gives me reason to believe that there is a certain lack of understanding of how these values should be handled. The encryption level is only configured on the server side in the "Encryption Level" setting found in the Remote Desktop Session Host Configuration RDP-Tcp properties dialog and this value is never transferred from the client to the server over the wire. The possible options are "None", "Low", "Client Compatible", "High" and "FIPS Compliant". The client receices this value in the Server Security Data block (TS_UD_SC_SEC1), probably only for informational purposes and maybe to give the client the possibility to verify if the server's decision for the encryption method confirms to the server's encryption level. The possible encryption methods are "NONE", "40BIT", "56BIT", "128BIT" and "FIPS" and the RDP client advertises the ones it supports to the server in the Client Security Data block (TS_UD_CS_SEC). The server's configured encryption level value restricts the possible final encryption method. Something that I was not able to find in the documentation is the priority level of the individual encryption methods based on which the server makes its final method decision if there are several options. My analysis with Windows Servers reveiled that the order is 128, 56, 40, FIPS. The server only chooses FIPS if the level is "FIPS Comliant" or if it is the only method advertised by the client. Bottom line: * FreeRDP's client side does not need to set settings->EncryptionLevel (which was done quite frequently). * FreeRDP's server side does not have to set the supported encryption methods list in settings->EncryptionMethods Changes in this commit: Removed unnecessary/confusing changes of EncryptionLevel/Methods settings Refactor settings->DisableEncryption * This value actually means "Advanced RDP Encryption (NLA/TLS) is NOT used" * The old name caused lots of confusion among developers * Renamed it to "UseRdpSecurityLayer" (the compare logic stays untouched) Any client's setting of settings->EncryptionMethods were annihilated * All clients "want" to set all supported methods * Some clients forgot 56bit because 56bit was not supported at the time the code was written * settings->EncryptionMethods was overwritten anyways in nego_connect() * Removed all client side settings of settings->EncryptionMethods The default is "None" (0) * Changed nego_connect() to advertise all supported methods if settings->EncryptionMethods is 0 (None) * Added a commandline option /encryption-methods:comma separated list of the values "40", "56", "128", "FIPS". E.g. /encryption-methods:56,128 * Print warning if server chooses non-advertised method Verify received level and method in client's gcc_read_server_security_data * Only accept valid/known encryption methods * Verify encryption level/method combinations according to MS-RDPBCGR 5.3.2 Server implementations can now set settings->EncryptionLevel * The default for settings->EncryptionLevel is 0 (None) * nego_send_negotiation_response() changes it to ClientCompatible in that case * default to ClientCompatible if the server implementation set an invalid level Fix server's gcc_write_server_security_data * Verify server encryption level value set by server implementations * Choose rdp encryption method based on level and supported client methods * Moved FIPS to the lowest priority (only used if other methods are possible) Updated sample server * Support RDP Security (RdpKeyFile was not set) * Added commented sample code for setting the security level
2014-12-12 04:17:12 +03:00
* Copyright 2014 Norbert Federa <norbert.federa@thincast.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <winpr/crt.h>
#include <freerdp/log.h>
#include "tpkt.h"
#include "nego.h"
#include "transport.h"
#define TAG FREERDP_TAG("core.nego")
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"
};
2013-10-29 00:54:00 +04:00
static const char PROTOCOL_SECURITY_STRINGS[9][4] =
{
"RDP",
"TLS",
"NLA",
2013-10-29 00:54:00 +04:00
"UNK",
"UNK",
"UNK",
"UNK",
"UNK",
"EXT"
};
static BOOL nego_transport_connect(rdpNego* nego);
static BOOL nego_transport_disconnect(rdpNego* nego);
2015-01-12 15:44:04 +03:00
static BOOL nego_security_connect(rdpNego* nego);
/**
* Negotiate protocol security and connect.
* @param nego
* @return
*/
BOOL nego_connect(rdpNego* nego)
{
rdpSettings* settings = nego->transport->settings;
if (nego->state == NEGO_STATE_INITIAL)
{
2015-02-03 01:16:32 +03:00
if (nego->EnabledProtocols[PROTOCOL_EXT])
{
nego->state = NEGO_STATE_EXT;
}
2015-02-03 01:16:32 +03:00
else if (nego->EnabledProtocols[PROTOCOL_NLA])
{
nego->state = NEGO_STATE_NLA;
}
2015-02-03 01:16:32 +03:00
else if (nego->EnabledProtocols[PROTOCOL_TLS])
{
nego->state = NEGO_STATE_TLS;
}
2015-02-03 01:16:32 +03:00
else if (nego->EnabledProtocols[PROTOCOL_RDP])
{
nego->state = NEGO_STATE_RDP;
}
else
{
2015-02-02 19:50:56 +03:00
WLog_ERR(TAG, "No security protocol is enabled");
nego->state = NEGO_STATE_FAIL;
2014-04-10 23:07:53 +04:00
return FALSE;
}
if (!nego->NegotiateSecurityLayer)
{
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "Security Layer Negotiation is disabled");
2012-08-01 00:58:41 +04:00
/* attempt only the highest enabled protocol (see nego_attempt_*) */
2015-02-03 01:16:32 +03:00
nego->EnabledProtocols[PROTOCOL_NLA] = FALSE;
nego->EnabledProtocols[PROTOCOL_TLS] = FALSE;
nego->EnabledProtocols[PROTOCOL_RDP] = FALSE;
nego->EnabledProtocols[PROTOCOL_EXT] = FALSE;
if (nego->state == NEGO_STATE_EXT)
2012-08-01 00:58:41 +04:00
{
2015-02-03 01:16:32 +03:00
nego->EnabledProtocols[PROTOCOL_EXT] = TRUE;
nego->EnabledProtocols[PROTOCOL_NLA] = TRUE;
nego->SelectedProtocol = PROTOCOL_EXT;
}
else if (nego->state == NEGO_STATE_NLA)
{
2015-02-03 01:16:32 +03:00
nego->EnabledProtocols[PROTOCOL_NLA] = TRUE;
nego->SelectedProtocol = PROTOCOL_NLA;
2012-08-01 00:58:41 +04:00
}
else if (nego->state == NEGO_STATE_TLS)
2012-08-01 00:58:41 +04:00
{
2015-02-03 01:16:32 +03:00
nego->EnabledProtocols[PROTOCOL_TLS] = TRUE;
nego->SelectedProtocol = PROTOCOL_TLS;
2012-08-01 00:58:41 +04:00
}
else if (nego->state == NEGO_STATE_RDP)
2012-08-01 00:58:41 +04:00
{
2015-02-03 01:16:32 +03:00
nego->EnabledProtocols[PROTOCOL_RDP] = TRUE;
nego->SelectedProtocol = PROTOCOL_RDP;
2012-08-01 00:58:41 +04:00
}
}
2015-02-03 01:16:32 +03:00
if (nego->SendPreconnectionPdu)
{
2015-02-03 01:16:32 +03:00
if (!nego_send_preconnection_pdu(nego))
{
WLog_ERR(TAG, "Failed to send preconnection pdu");
nego->state = NEGO_STATE_FINAL;
return FALSE;
}
}
}
do
{
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "state: %s", NEGO_STATE_STRINGS[nego->state]);
nego_send(nego);
if (nego->state == NEGO_STATE_FAIL)
{
2015-02-02 19:50:56 +03:00
WLog_ERR(TAG, "Protocol Security Negotiation Failure");
nego->state = NEGO_STATE_FINAL;
return FALSE;
}
}
while (nego->state != NEGO_STATE_FINAL);
2015-02-03 01:16:32 +03:00
WLog_DBG(TAG, "Negotiated %s security", PROTOCOL_SECURITY_STRINGS[nego->SelectedProtocol]);
/* update settings with negotiated protocol security */
2015-02-03 01:16:32 +03:00
settings->RequestedProtocols = nego->RequestedProtocols;
settings->SelectedProtocol = nego->SelectedProtocol;
settings->NegotiationFlags = nego->flags;
2015-02-03 01:16:32 +03:00
if (nego->SelectedProtocol == PROTOCOL_RDP)
{
Standard RDP Security Layer Levels/Method Overhaul [MS-RDPBCGR] Section 5.3 describes the encryption level and method values for standard RDP security. Looking at the current usage of these values in the FreeRDP code gives me reason to believe that there is a certain lack of understanding of how these values should be handled. The encryption level is only configured on the server side in the "Encryption Level" setting found in the Remote Desktop Session Host Configuration RDP-Tcp properties dialog and this value is never transferred from the client to the server over the wire. The possible options are "None", "Low", "Client Compatible", "High" and "FIPS Compliant". The client receices this value in the Server Security Data block (TS_UD_SC_SEC1), probably only for informational purposes and maybe to give the client the possibility to verify if the server's decision for the encryption method confirms to the server's encryption level. The possible encryption methods are "NONE", "40BIT", "56BIT", "128BIT" and "FIPS" and the RDP client advertises the ones it supports to the server in the Client Security Data block (TS_UD_CS_SEC). The server's configured encryption level value restricts the possible final encryption method. Something that I was not able to find in the documentation is the priority level of the individual encryption methods based on which the server makes its final method decision if there are several options. My analysis with Windows Servers reveiled that the order is 128, 56, 40, FIPS. The server only chooses FIPS if the level is "FIPS Comliant" or if it is the only method advertised by the client. Bottom line: * FreeRDP's client side does not need to set settings->EncryptionLevel (which was done quite frequently). * FreeRDP's server side does not have to set the supported encryption methods list in settings->EncryptionMethods Changes in this commit: Removed unnecessary/confusing changes of EncryptionLevel/Methods settings Refactor settings->DisableEncryption * This value actually means "Advanced RDP Encryption (NLA/TLS) is NOT used" * The old name caused lots of confusion among developers * Renamed it to "UseRdpSecurityLayer" (the compare logic stays untouched) Any client's setting of settings->EncryptionMethods were annihilated * All clients "want" to set all supported methods * Some clients forgot 56bit because 56bit was not supported at the time the code was written * settings->EncryptionMethods was overwritten anyways in nego_connect() * Removed all client side settings of settings->EncryptionMethods The default is "None" (0) * Changed nego_connect() to advertise all supported methods if settings->EncryptionMethods is 0 (None) * Added a commandline option /encryption-methods:comma separated list of the values "40", "56", "128", "FIPS". E.g. /encryption-methods:56,128 * Print warning if server chooses non-advertised method Verify received level and method in client's gcc_read_server_security_data * Only accept valid/known encryption methods * Verify encryption level/method combinations according to MS-RDPBCGR 5.3.2 Server implementations can now set settings->EncryptionLevel * The default for settings->EncryptionLevel is 0 (None) * nego_send_negotiation_response() changes it to ClientCompatible in that case * default to ClientCompatible if the server implementation set an invalid level Fix server's gcc_write_server_security_data * Verify server encryption level value set by server implementations * Choose rdp encryption method based on level and supported client methods * Moved FIPS to the lowest priority (only used if other methods are possible) Updated sample server * Support RDP Security (RdpKeyFile was not set) * Added commented sample code for setting the security level
2014-12-12 04:17:12 +03:00
settings->UseRdpSecurityLayer = TRUE;
if (!settings->EncryptionMethods)
{
/**
* Advertise all supported encryption methods if the client
* implementation did not set any security methods
*/
settings->EncryptionMethods = ENCRYPTION_METHOD_40BIT | ENCRYPTION_METHOD_56BIT | ENCRYPTION_METHOD_128BIT | ENCRYPTION_METHOD_FIPS;
}
}
/* finally connect security layer (if not already done) */
if (!nego_security_connect(nego))
{
2015-02-03 01:16:32 +03:00
WLog_DBG(TAG, "Failed to connect with %s security", PROTOCOL_SECURITY_STRINGS[nego->SelectedProtocol]);
return FALSE;
}
return TRUE;
}
2015-01-12 15:44:04 +03:00
BOOL nego_disconnect(rdpNego* nego)
{
nego->state = NEGO_STATE_INITIAL;
return nego_transport_disconnect(nego);
}
/* connect to selected security layer */
BOOL nego_security_connect(rdpNego* nego)
{
2015-02-03 01:16:32 +03:00
if (!nego->TcpConnected)
{
2015-02-03 01:16:32 +03:00
nego->SecurityConnected = FALSE;
}
2015-02-03 01:16:32 +03:00
else if (!nego->SecurityConnected)
{
2015-02-03 01:16:32 +03:00
if (nego->SelectedProtocol == PROTOCOL_NLA)
{
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "nego_security_connect with PROTOCOL_NLA");
2015-02-03 01:16:32 +03:00
nego->SecurityConnected = transport_connect_nla(nego->transport);
}
2015-02-03 01:16:32 +03:00
else if (nego->SelectedProtocol == PROTOCOL_TLS)
{
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "nego_security_connect with PROTOCOL_TLS");
2015-02-03 01:16:32 +03:00
nego->SecurityConnected = transport_connect_tls(nego->transport);
}
2015-02-03 01:16:32 +03:00
else if (nego->SelectedProtocol == PROTOCOL_RDP)
{
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "nego_security_connect with PROTOCOL_RDP");
2015-02-03 01:16:32 +03:00
nego->SecurityConnected = transport_connect_rdp(nego->transport);
}
else
{
2015-02-02 19:50:56 +03:00
WLog_ERR(TAG, "cannot connect security layer because no protocol has been selected yet.");
}
}
2015-02-03 01:16:32 +03:00
return nego->SecurityConnected;
}
/**
* Connect TCP layer.
* @param nego
* @return
*/
BOOL nego_tcp_connect(rdpNego* nego)
{
2015-02-03 01:16:32 +03:00
if (!nego->TcpConnected)
{
if (nego->GatewayEnabled)
{
if (nego->GatewayBypassLocal)
{
/* Attempt a direct connection first, and then fallback to using the gateway */
WLog_INFO(TAG, "Detecting if host can be reached locally. - This might take some time.");
WLog_INFO(TAG, "To disable auto detection use /gateway-usage-method:direct");
transport_set_gateway_enabled(nego->transport, FALSE);
2015-02-03 01:16:32 +03:00
nego->TcpConnected = transport_connect(nego->transport, nego->hostname, nego->port, 1);
}
2015-02-03 01:16:32 +03:00
if (!nego->TcpConnected)
{
transport_set_gateway_enabled(nego->transport, TRUE);
2015-02-03 01:16:32 +03:00
nego->TcpConnected = transport_connect(nego->transport, nego->hostname, nego->port, 15);
}
}
else
{
2015-02-03 01:16:32 +03:00
nego->TcpConnected = transport_connect(nego->transport, nego->hostname, nego->port, 15);
}
}
2015-02-03 01:16:32 +03:00
return nego->TcpConnected;
}
/**
* Connect TCP layer. For direct approach, connect security layer as well.
* @param nego
* @return
*/
BOOL nego_transport_connect(rdpNego* nego)
{
nego_tcp_connect(nego);
2015-02-03 01:16:32 +03:00
if (nego->TcpConnected && !nego->NegotiateSecurityLayer)
return nego_security_connect(nego);
2015-02-03 01:16:32 +03:00
return nego->TcpConnected;
}
/**
* Disconnect TCP layer.
* @param nego
* @return
*/
BOOL nego_transport_disconnect(rdpNego* nego)
{
2015-02-03 01:16:32 +03:00
if (nego->TcpConnected)
transport_disconnect(nego->transport);
2015-02-03 01:16:32 +03:00
nego->TcpConnected = FALSE;
nego->SecurityConnected = FALSE;
return TRUE;
}
/**
* Send preconnection information if enabled.
* @param nego
* @return
*/
BOOL nego_send_preconnection_pdu(rdpNego* nego)
{
wStream* s;
2012-10-09 11:26:39 +04:00
UINT32 cbSize;
UINT16 cchPCB = 0;
WCHAR* wszPCB = NULL;
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "Sending preconnection PDU");
if (!nego_tcp_connect(nego))
return FALSE;
/* it's easier to always send the version 2 PDU, and it's just 2 bytes overhead */
cbSize = PRECONNECTION_PDU_V2_MIN_SIZE;
2015-02-03 01:16:32 +03:00
if (nego->PreconnectionBlob)
{
2015-02-03 01:16:32 +03:00
cchPCB = (UINT16) ConvertToUnicode(CP_UTF8, 0, nego->PreconnectionBlob, -1, &wszPCB, 0);
cchPCB += 1; /* zero-termination */
cbSize += cchPCB * 2;
}
s = Stream_New(NULL, cbSize);
if (!s)
{
WLog_ERR(TAG, "Stream_New failed!");
return FALSE;
}
2013-05-09 00:09:16 +04:00
Stream_Write_UINT32(s, cbSize); /* cbSize */
Stream_Write_UINT32(s, 0); /* Flags */
Stream_Write_UINT32(s, PRECONNECTION_PDU_V2); /* Version */
2015-02-03 01:16:32 +03:00
Stream_Write_UINT32(s, nego->PreconnectionId); /* Id */
2013-05-09 00:09:16 +04:00
Stream_Write_UINT16(s, cchPCB); /* cchPCB */
if (wszPCB)
{
2013-05-09 00:09:16 +04:00
Stream_Write(s, wszPCB, cchPCB * 2); /* wszPCB */
free(wszPCB);
}
Stream_SealLength(s);
if (transport_write(nego->transport, s) < 0)
2013-09-06 13:07:33 +04:00
{
Stream_Free(s, TRUE);
return FALSE;
2013-09-06 13:07:33 +04:00
}
Stream_Free(s, TRUE);
return TRUE;
}
/**
* Attempt negotiating NLA + TLS extended security.
* @param nego
*/
void nego_attempt_ext(rdpNego* nego)
{
2015-02-03 01:16:32 +03:00
nego->RequestedProtocols = PROTOCOL_NLA | PROTOCOL_TLS | PROTOCOL_EXT;
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "Attempting NLA extended security");
if (!nego_transport_connect(nego))
{
nego->state = NEGO_STATE_FAIL;
return;
}
if (!nego_send_negotiation_request(nego))
{
nego->state = NEGO_STATE_FAIL;
return;
}
if (!nego_recv_response(nego))
{
nego->state = NEGO_STATE_FAIL;
return;
}
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "state: %s", NEGO_STATE_STRINGS[nego->state]);
if (nego->state != NEGO_STATE_FINAL)
{
nego_transport_disconnect(nego);
2015-02-03 01:16:32 +03:00
if (nego->EnabledProtocols[PROTOCOL_NLA])
nego->state = NEGO_STATE_NLA;
2015-02-03 01:16:32 +03:00
else if (nego->EnabledProtocols[PROTOCOL_TLS])
nego->state = NEGO_STATE_TLS;
2015-02-03 01:16:32 +03:00
else if (nego->EnabledProtocols[PROTOCOL_RDP])
nego->state = NEGO_STATE_RDP;
else
nego->state = NEGO_STATE_FAIL;
}
}
/**
* Attempt negotiating NLA + TLS security.
* @param nego
*/
void nego_attempt_nla(rdpNego* nego)
{
2015-02-03 01:16:32 +03:00
nego->RequestedProtocols = PROTOCOL_NLA | PROTOCOL_TLS;
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "Attempting NLA security");
if (!nego_transport_connect(nego))
{
nego->state = NEGO_STATE_FAIL;
return;
}
if (!nego_send_negotiation_request(nego))
{
nego->state = NEGO_STATE_FAIL;
return;
}
if (!nego_recv_response(nego))
{
nego->state = NEGO_STATE_FAIL;
return;
}
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "state: %s", NEGO_STATE_STRINGS[nego->state]);
if (nego->state != NEGO_STATE_FINAL)
{
nego_transport_disconnect(nego);
2015-02-03 01:16:32 +03:00
if (nego->EnabledProtocols[PROTOCOL_TLS])
nego->state = NEGO_STATE_TLS;
2015-02-03 01:16:32 +03:00
else if (nego->EnabledProtocols[PROTOCOL_RDP])
nego->state = NEGO_STATE_RDP;
else
nego->state = NEGO_STATE_FAIL;
}
}
/**
* Attempt negotiating TLS security.
* @param nego
*/
void nego_attempt_tls(rdpNego* nego)
{
2015-02-03 01:16:32 +03:00
nego->RequestedProtocols = PROTOCOL_TLS;
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "Attempting TLS security");
if (!nego_transport_connect(nego))
{
nego->state = NEGO_STATE_FAIL;
return;
}
if (!nego_send_negotiation_request(nego))
{
nego->state = NEGO_STATE_FAIL;
return;
}
if (!nego_recv_response(nego))
{
nego->state = NEGO_STATE_FAIL;
return;
}
if (nego->state != NEGO_STATE_FINAL)
{
nego_transport_disconnect(nego);
2015-02-03 01:16:32 +03:00
if (nego->EnabledProtocols[PROTOCOL_RDP])
nego->state = NEGO_STATE_RDP;
else
nego->state = NEGO_STATE_FAIL;
}
}
/**
* Attempt negotiating standard RDP security.
* @param nego
*/
void nego_attempt_rdp(rdpNego* nego)
{
2015-02-03 01:16:32 +03:00
nego->RequestedProtocols = PROTOCOL_RDP;
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "Attempting RDP security");
if (!nego_transport_connect(nego))
{
nego->state = NEGO_STATE_FAIL;
return;
}
if (!nego_send_negotiation_request(nego))
{
nego->state = NEGO_STATE_FAIL;
return;
}
if (!nego_recv_response(nego))
{
nego->state = NEGO_STATE_FAIL;
return;
}
}
/**
* Wait to receive a negotiation response
* @param nego
*/
BOOL nego_recv_response(rdpNego* nego)
{
int status;
wStream* s;
s = Stream_New(NULL, 1024);
if (!s)
{
WLog_ERR(TAG, "Stream_New failed!");
return FALSE;
}
status = transport_read_pdu(nego->transport, s);
if (status < 0)
{
Stream_Free(s, TRUE);
2013-01-14 02:37:50 +04:00
return FALSE;
}
status = nego_recv(nego->transport, s, nego);
2013-08-28 18:42:23 +04:00
Stream_Free(s, TRUE);
2013-11-08 02:37:58 +04:00
if (status < 0)
return FALSE;
return TRUE;
}
/**
* Receive protocol security negotiation message.\n
* @msdn{cc240501}
* @param transport transport
* @param s stream
* @param extra nego pointer
*/
int nego_recv(rdpTransport* transport, wStream* s, void* extra)
{
BYTE li;
BYTE type;
UINT16 length;
rdpNego* nego = (rdpNego*) extra;
length = tpkt_read_header(s);
if (length == 0)
return -1;
2011-09-25 22:54:07 +04:00
if (!tpdu_read_connection_confirm(s, &li))
return -1;
if (li > 6)
{
/* rdpNegData (optional) */
2013-05-09 00:09:16 +04:00
Stream_Read_UINT8(s, type); /* Type */
switch (type)
{
case TYPE_RDP_NEG_RSP:
nego_process_negotiation_response(nego, s);
2015-02-03 01:16:32 +03:00
WLog_DBG(TAG, "selected_protocol: %d", nego->SelectedProtocol);
2012-03-31 18:09:19 +04:00
/* enhanced security selected ? */
2015-02-03 01:16:32 +03:00
if (nego->SelectedProtocol)
{
2015-02-03 01:16:32 +03:00
if ((nego->SelectedProtocol == PROTOCOL_NLA) &&
(!nego->EnabledProtocols[PROTOCOL_NLA]))
{
2012-03-31 18:09:19 +04:00
nego->state = NEGO_STATE_FAIL;
}
2015-02-03 01:16:32 +03:00
if ((nego->SelectedProtocol == PROTOCOL_TLS) &&
(!nego->EnabledProtocols[PROTOCOL_TLS]))
{
2012-03-31 18:09:19 +04:00
nego->state = NEGO_STATE_FAIL;
}
}
2015-02-03 01:16:32 +03:00
else if (!nego->EnabledProtocols[PROTOCOL_RDP])
{
nego->state = NEGO_STATE_FAIL;
}
break;
case TYPE_RDP_NEG_FAILURE:
nego_process_negotiation_failure(nego, s);
break;
}
}
else if (li == 6)
{
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "no rdpNegData");
2015-02-03 01:16:32 +03:00
if (!nego->EnabledProtocols[PROTOCOL_RDP])
nego->state = NEGO_STATE_FAIL;
else
nego->state = NEGO_STATE_FINAL;
}
else
{
2015-02-02 19:50:56 +03:00
WLog_ERR(TAG, "invalid negotiation response");
nego->state = NEGO_STATE_FAIL;
}
return 0;
}
/**
* Read optional routing token or cookie of X.224 Connection Request PDU.
* @msdn{cc240470}
* @param nego
* @param s stream
*/
static BOOL nego_read_request_token_or_cookie(rdpNego* nego, wStream* s)
{
/* routingToken and cookie are optional and mutually exclusive!
*
* routingToken (variable): An optional and variable-length routing
* token (used for load balancing) terminated by a 0x0D0A two-byte
* sequence: (check [MSFT-SDLBTS] for details!)
* Cookie:[space]msts=[ip address].[port].[reserved][\x0D\x0A]
*
* cookie (variable): An optional and variable-length ANSI character
* string terminated by a 0x0D0A two-byte sequence:
* Cookie:[space]mstshash=[ANSISTRING][\x0D\x0A]
*/
BYTE *str = NULL;
UINT16 crlf = 0;
int pos, len;
BOOL result = FALSE;
BOOL isToken = FALSE;
str = Stream_Pointer(s);
pos = Stream_GetPosition(s);
/* minimum length for cookie is 15 */
if (Stream_GetRemainingLength(s) < 15)
return TRUE;
if (!memcmp(Stream_Pointer(s), "Cookie: msts=", 13))
{
isToken = TRUE;
Stream_Seek(s, 13);
}
else
{
/* not a cookie, minimum length for token is 19 */
if (Stream_GetRemainingLength(s) < 19)
return TRUE;
if (memcmp(Stream_Pointer(s), "Cookie: mstshash=", 17))
return TRUE;
Stream_Seek(s, 17);
}
while (Stream_GetRemainingLength(s) >= 2)
{
Stream_Read_UINT16(s, crlf);
if (crlf == 0x0A0D)
break;
Stream_Rewind(s, 1);
}
if (crlf == 0x0A0D)
{
Stream_Rewind(s, 2);
len = Stream_GetPosition(s) - pos;
Stream_Write_UINT16(s, 0);
if (strlen((char*)str) == len)
{
if (isToken)
result = nego_set_routing_token(nego, str, len);
else
result = nego_set_cookie(nego, (char*)str);
}
}
if (!result)
{
Stream_SetPosition(s, pos);
WLog_ERR(TAG, "invalid %s received",
isToken ? "routing token" : "cookie");
}
else
{
WLog_DBG(TAG, "received %s [%s]",
isToken ? "routing token" : "cookie", str);
}
return result;
}
/**
* Read protocol security negotiation request message.\n
* @param nego
* @param s stream
*/
BOOL nego_read_request(rdpNego* nego, wStream* s)
{
BYTE li;
BYTE type;
tpkt_read_header(s);
if (!tpdu_read_connection_request(s, &li))
return FALSE;
if (li != Stream_GetRemainingLength(s) + 6)
{
2015-02-03 01:16:32 +03:00
WLog_ERR(TAG, "Incorrect TPDU length indicator.");
return FALSE;
}
if (!nego_read_request_token_or_cookie(nego, s))
{
WLog_ERR(TAG, "Failed to parse routing token or cookie.");
return FALSE;
}
if (Stream_GetRemainingLength(s) >= 8)
{
/* rdpNegData (optional) */
2013-05-09 00:09:16 +04:00
Stream_Read_UINT8(s, type); /* Type */
if (type != TYPE_RDP_NEG_REQ)
{
2015-02-03 01:16:32 +03:00
WLog_ERR(TAG, "Incorrect negotiation request type %d", type);
return FALSE;
}
nego_process_negotiation_request(nego, s);
}
return TRUE;
}
/**
* Send protocol security negotiation message.
* @param nego
*/
void nego_send(rdpNego* nego)
{
if (nego->state == NEGO_STATE_EXT)
nego_attempt_ext(nego);
else if (nego->state == NEGO_STATE_NLA)
nego_attempt_nla(nego);
else if (nego->state == NEGO_STATE_TLS)
nego_attempt_tls(nego);
else if (nego->state == NEGO_STATE_RDP)
nego_attempt_rdp(nego);
else
2015-02-02 19:50:56 +03:00
WLog_ERR(TAG, "invalid negotiation state for sending");
}
/**
* Send RDP Negotiation Request (RDP_NEG_REQ).\n
* @msdn{cc240500}\n
* @msdn{cc240470}
* @param nego
*/
BOOL nego_send_negotiation_request(rdpNego* nego)
{
wStream* s;
int length;
int bm, em;
2013-11-06 10:51:55 +04:00
BYTE flags = 0;
int cookie_length;
s = Stream_New(NULL, 512);
if (!s)
{
WLog_ERR(TAG, "Stream_New failed!");
return FALSE;
}
length = TPDU_CONNECTION_REQUEST_LENGTH;
bm = Stream_GetPosition(s);
Stream_Seek(s, length);
if (nego->RoutingToken)
{
2013-05-09 00:09:16 +04:00
Stream_Write(s, nego->RoutingToken, nego->RoutingTokenLength);
2015-02-03 01:16:32 +03:00
/* Ensure Routing Token is correctly terminated - may already be present in string */
2015-02-03 01:16:32 +03:00
if ((nego->RoutingTokenLength > 2) &&
(nego->RoutingToken[nego->RoutingTokenLength - 2] == 0x0D) &&
(nego->RoutingToken[nego->RoutingTokenLength - 1] == 0x0A))
{
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "Routing token looks correctly terminated - use verbatim");
length +=nego->RoutingTokenLength;
}
else
{
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "Adding terminating CRLF to routing token");
Stream_Write_UINT8(s, 0x0D); /* CR */
Stream_Write_UINT8(s, 0x0A); /* LF */
length += nego->RoutingTokenLength + 2;
}
}
else if (nego->cookie)
{
cookie_length = strlen(nego->cookie);
2015-02-03 01:16:32 +03:00
if (cookie_length > (int) nego->CookieMaxLength)
cookie_length = nego->CookieMaxLength;
2013-05-09 00:09:16 +04:00
Stream_Write(s, "Cookie: mstshash=", 17);
Stream_Write(s, (BYTE*) nego->cookie, cookie_length);
Stream_Write_UINT8(s, 0x0D); /* CR */
Stream_Write_UINT8(s, 0x0A); /* LF */
length += cookie_length + 19;
}
2015-02-03 01:16:32 +03:00
WLog_DBG(TAG, "RequestedProtocols: %d", nego->RequestedProtocols);
2015-02-03 01:16:32 +03:00
if ((nego->RequestedProtocols > PROTOCOL_RDP) || (nego->sendNegoData))
{
/* RDP_NEG_DATA must be present for TLS and NLA */
2013-11-06 10:51:55 +04:00
if (nego->RestrictedAdminModeRequired)
flags |= RESTRICTED_ADMIN_MODE_REQUIRED;
2013-05-09 00:09:16 +04:00
Stream_Write_UINT8(s, TYPE_RDP_NEG_REQ);
2013-11-06 10:51:55 +04:00
Stream_Write_UINT8(s, flags);
2013-05-09 00:09:16 +04:00
Stream_Write_UINT16(s, 8); /* RDP_NEG_DATA length (8) */
2015-02-03 01:16:32 +03:00
Stream_Write_UINT32(s, nego->RequestedProtocols); /* requestedProtocols */
length += 8;
}
em = Stream_GetPosition(s);
Stream_SetPosition(s, bm);
tpkt_write_header(s, length);
tpdu_write_connection_request(s, length - 5);
Stream_SetPosition(s, em);
Stream_SealLength(s);
if (transport_write(nego->transport, s) < 0)
2013-09-06 13:07:33 +04:00
{
Stream_Free(s, TRUE);
return FALSE;
2013-09-06 13:07:33 +04:00
}
Stream_Free(s, TRUE);
return TRUE;
}
/**
* Process Negotiation Request from Connection Request message.
* @param nego
* @param s
*/
void nego_process_negotiation_request(rdpNego* nego, wStream* s)
{
BYTE flags;
UINT16 length;
2013-05-09 00:09:16 +04:00
Stream_Read_UINT8(s, flags);
Stream_Read_UINT16(s, length);
2015-02-03 01:16:32 +03:00
Stream_Read_UINT32(s, nego->RequestedProtocols);
2015-02-03 01:16:32 +03:00
WLog_DBG(TAG, "RDP_NEG_REQ: RequestedProtocol: 0x%04X", nego->RequestedProtocols);
nego->state = NEGO_STATE_FINAL;
}
/**
* Process Negotiation Response from Connection Confirm message.
* @param nego
* @param s
*/
void nego_process_negotiation_response(rdpNego* nego, wStream* s)
{
UINT16 length;
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "RDP_NEG_RSP");
if (Stream_GetRemainingLength(s) < 7)
{
2015-02-02 19:50:56 +03:00
WLog_ERR(TAG, "Invalid RDP_NEG_RSP");
nego->state = NEGO_STATE_FAIL;
return;
}
2013-05-09 00:09:16 +04:00
Stream_Read_UINT8(s, nego->flags);
Stream_Read_UINT16(s, length);
2015-02-03 01:16:32 +03:00
Stream_Read_UINT32(s, nego->SelectedProtocol);
nego->state = NEGO_STATE_FINAL;
}
/**
* Process Negotiation Failure from Connection Confirm message.
* @param nego
* @param s
*/
void nego_process_negotiation_failure(rdpNego* nego, wStream* s)
{
BYTE flags;
UINT16 length;
2012-10-09 11:26:39 +04:00
UINT32 failureCode;
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "RDP_NEG_FAILURE");
2013-05-09 00:09:16 +04:00
Stream_Read_UINT8(s, flags);
Stream_Read_UINT16(s, length);
Stream_Read_UINT32(s, failureCode);
switch (failureCode)
{
case SSL_REQUIRED_BY_SERVER:
2015-02-18 23:36:57 +03:00
WLog_WARN(TAG, "Error: SSL_REQUIRED_BY_SERVER");
break;
case SSL_NOT_ALLOWED_BY_SERVER:
2015-02-18 23:36:57 +03:00
WLog_WARN(TAG, "Error: SSL_NOT_ALLOWED_BY_SERVER");
nego->sendNegoData = TRUE;
break;
case SSL_CERT_NOT_ON_SERVER:
2015-02-02 19:50:56 +03:00
WLog_ERR(TAG, "Error: SSL_CERT_NOT_ON_SERVER");
nego->sendNegoData = TRUE;
break;
case INCONSISTENT_FLAGS:
2015-02-02 19:50:56 +03:00
WLog_ERR(TAG, "Error: INCONSISTENT_FLAGS");
break;
case HYBRID_REQUIRED_BY_SERVER:
2015-02-18 23:36:57 +03:00
WLog_WARN(TAG, "Error: HYBRID_REQUIRED_BY_SERVER");
break;
default:
2015-02-02 19:50:56 +03:00
WLog_ERR(TAG, "Error: Unknown protocol security error %d", failureCode);
break;
}
nego->state = NEGO_STATE_FAIL;
}
/**
* Send RDP Negotiation Response (RDP_NEG_RSP).\n
* @param nego
*/
BOOL nego_send_negotiation_response(rdpNego* nego)
{
int length;
int bm, em;
BOOL status;
wStream* s;
BYTE flags;
rdpSettings* settings;
status = TRUE;
settings = nego->transport->settings;
s = Stream_New(NULL, 512);
if (!s)
{
WLog_ERR(TAG, "Stream_New failed!");
return FALSE;
}
length = TPDU_CONNECTION_CONFIRM_LENGTH;
bm = Stream_GetPosition(s);
Stream_Seek(s, length);
if (nego->SelectedProtocol & PROTOCOL_FAILED_NEGO)
{
UINT32 errorCode = (nego->SelectedProtocol & ~PROTOCOL_FAILED_NEGO);
flags = 0;
2013-05-09 00:09:16 +04:00
Stream_Write_UINT8(s, TYPE_RDP_NEG_FAILURE);
Stream_Write_UINT8(s, flags); /* flags */
2013-05-09 00:09:16 +04:00
Stream_Write_UINT16(s, 8); /* RDP_NEG_DATA length (8) */
Stream_Write_UINT32(s, errorCode);
length += 8;
status = FALSE;
}
else
{
flags = EXTENDED_CLIENT_DATA_SUPPORTED;
if (settings->SupportGraphicsPipeline)
flags |= DYNVC_GFX_PROTOCOL_SUPPORTED;
/* RDP_NEG_DATA must be present for TLS, NLA, and RDP */
Stream_Write_UINT8(s, TYPE_RDP_NEG_RSP);
Stream_Write_UINT8(s, flags); /* flags */
Stream_Write_UINT16(s, 8); /* RDP_NEG_DATA length (8) */
2015-02-03 01:16:32 +03:00
Stream_Write_UINT32(s, nego->SelectedProtocol); /* selectedProtocol */
length += 8;
}
em = Stream_GetPosition(s);
Stream_SetPosition(s, bm);
tpkt_write_header(s, length);
tpdu_write_connection_confirm(s, length - 5);
Stream_SetPosition(s, em);
Stream_SealLength(s);
if (transport_write(nego->transport, s) < 0)
2013-09-06 13:07:33 +04:00
{
Stream_Free(s, TRUE);
return FALSE;
2013-09-06 13:07:33 +04:00
}
Stream_Free(s, TRUE);
if (status)
{
/* update settings with negotiated protocol security */
2015-02-03 01:16:32 +03:00
settings->RequestedProtocols = nego->RequestedProtocols;
settings->SelectedProtocol = nego->SelectedProtocol;
if (settings->SelectedProtocol == PROTOCOL_RDP)
{
settings->TlsSecurity = FALSE;
settings->NlaSecurity = FALSE;
settings->RdpSecurity = TRUE;
Standard RDP Security Layer Levels/Method Overhaul [MS-RDPBCGR] Section 5.3 describes the encryption level and method values for standard RDP security. Looking at the current usage of these values in the FreeRDP code gives me reason to believe that there is a certain lack of understanding of how these values should be handled. The encryption level is only configured on the server side in the "Encryption Level" setting found in the Remote Desktop Session Host Configuration RDP-Tcp properties dialog and this value is never transferred from the client to the server over the wire. The possible options are "None", "Low", "Client Compatible", "High" and "FIPS Compliant". The client receices this value in the Server Security Data block (TS_UD_SC_SEC1), probably only for informational purposes and maybe to give the client the possibility to verify if the server's decision for the encryption method confirms to the server's encryption level. The possible encryption methods are "NONE", "40BIT", "56BIT", "128BIT" and "FIPS" and the RDP client advertises the ones it supports to the server in the Client Security Data block (TS_UD_CS_SEC). The server's configured encryption level value restricts the possible final encryption method. Something that I was not able to find in the documentation is the priority level of the individual encryption methods based on which the server makes its final method decision if there are several options. My analysis with Windows Servers reveiled that the order is 128, 56, 40, FIPS. The server only chooses FIPS if the level is "FIPS Comliant" or if it is the only method advertised by the client. Bottom line: * FreeRDP's client side does not need to set settings->EncryptionLevel (which was done quite frequently). * FreeRDP's server side does not have to set the supported encryption methods list in settings->EncryptionMethods Changes in this commit: Removed unnecessary/confusing changes of EncryptionLevel/Methods settings Refactor settings->DisableEncryption * This value actually means "Advanced RDP Encryption (NLA/TLS) is NOT used" * The old name caused lots of confusion among developers * Renamed it to "UseRdpSecurityLayer" (the compare logic stays untouched) Any client's setting of settings->EncryptionMethods were annihilated * All clients "want" to set all supported methods * Some clients forgot 56bit because 56bit was not supported at the time the code was written * settings->EncryptionMethods was overwritten anyways in nego_connect() * Removed all client side settings of settings->EncryptionMethods The default is "None" (0) * Changed nego_connect() to advertise all supported methods if settings->EncryptionMethods is 0 (None) * Added a commandline option /encryption-methods:comma separated list of the values "40", "56", "128", "FIPS". E.g. /encryption-methods:56,128 * Print warning if server chooses non-advertised method Verify received level and method in client's gcc_read_server_security_data * Only accept valid/known encryption methods * Verify encryption level/method combinations according to MS-RDPBCGR 5.3.2 Server implementations can now set settings->EncryptionLevel * The default for settings->EncryptionLevel is 0 (None) * nego_send_negotiation_response() changes it to ClientCompatible in that case * default to ClientCompatible if the server implementation set an invalid level Fix server's gcc_write_server_security_data * Verify server encryption level value set by server implementations * Choose rdp encryption method based on level and supported client methods * Moved FIPS to the lowest priority (only used if other methods are possible) Updated sample server * Support RDP Security (RdpKeyFile was not set) * Added commented sample code for setting the security level
2014-12-12 04:17:12 +03:00
settings->UseRdpSecurityLayer = TRUE;
Standard RDP Security Layer Levels/Method Overhaul [MS-RDPBCGR] Section 5.3 describes the encryption level and method values for standard RDP security. Looking at the current usage of these values in the FreeRDP code gives me reason to believe that there is a certain lack of understanding of how these values should be handled. The encryption level is only configured on the server side in the "Encryption Level" setting found in the Remote Desktop Session Host Configuration RDP-Tcp properties dialog and this value is never transferred from the client to the server over the wire. The possible options are "None", "Low", "Client Compatible", "High" and "FIPS Compliant". The client receices this value in the Server Security Data block (TS_UD_SC_SEC1), probably only for informational purposes and maybe to give the client the possibility to verify if the server's decision for the encryption method confirms to the server's encryption level. The possible encryption methods are "NONE", "40BIT", "56BIT", "128BIT" and "FIPS" and the RDP client advertises the ones it supports to the server in the Client Security Data block (TS_UD_CS_SEC). The server's configured encryption level value restricts the possible final encryption method. Something that I was not able to find in the documentation is the priority level of the individual encryption methods based on which the server makes its final method decision if there are several options. My analysis with Windows Servers reveiled that the order is 128, 56, 40, FIPS. The server only chooses FIPS if the level is "FIPS Comliant" or if it is the only method advertised by the client. Bottom line: * FreeRDP's client side does not need to set settings->EncryptionLevel (which was done quite frequently). * FreeRDP's server side does not have to set the supported encryption methods list in settings->EncryptionMethods Changes in this commit: Removed unnecessary/confusing changes of EncryptionLevel/Methods settings Refactor settings->DisableEncryption * This value actually means "Advanced RDP Encryption (NLA/TLS) is NOT used" * The old name caused lots of confusion among developers * Renamed it to "UseRdpSecurityLayer" (the compare logic stays untouched) Any client's setting of settings->EncryptionMethods were annihilated * All clients "want" to set all supported methods * Some clients forgot 56bit because 56bit was not supported at the time the code was written * settings->EncryptionMethods was overwritten anyways in nego_connect() * Removed all client side settings of settings->EncryptionMethods The default is "None" (0) * Changed nego_connect() to advertise all supported methods if settings->EncryptionMethods is 0 (None) * Added a commandline option /encryption-methods:comma separated list of the values "40", "56", "128", "FIPS". E.g. /encryption-methods:56,128 * Print warning if server chooses non-advertised method Verify received level and method in client's gcc_read_server_security_data * Only accept valid/known encryption methods * Verify encryption level/method combinations according to MS-RDPBCGR 5.3.2 Server implementations can now set settings->EncryptionLevel * The default for settings->EncryptionLevel is 0 (None) * nego_send_negotiation_response() changes it to ClientCompatible in that case * default to ClientCompatible if the server implementation set an invalid level Fix server's gcc_write_server_security_data * Verify server encryption level value set by server implementations * Choose rdp encryption method based on level and supported client methods * Moved FIPS to the lowest priority (only used if other methods are possible) Updated sample server * Support RDP Security (RdpKeyFile was not set) * Added commented sample code for setting the security level
2014-12-12 04:17:12 +03:00
if (settings->EncryptionLevel == ENCRYPTION_LEVEL_NONE)
{
Standard RDP Security Layer Levels/Method Overhaul [MS-RDPBCGR] Section 5.3 describes the encryption level and method values for standard RDP security. Looking at the current usage of these values in the FreeRDP code gives me reason to believe that there is a certain lack of understanding of how these values should be handled. The encryption level is only configured on the server side in the "Encryption Level" setting found in the Remote Desktop Session Host Configuration RDP-Tcp properties dialog and this value is never transferred from the client to the server over the wire. The possible options are "None", "Low", "Client Compatible", "High" and "FIPS Compliant". The client receices this value in the Server Security Data block (TS_UD_SC_SEC1), probably only for informational purposes and maybe to give the client the possibility to verify if the server's decision for the encryption method confirms to the server's encryption level. The possible encryption methods are "NONE", "40BIT", "56BIT", "128BIT" and "FIPS" and the RDP client advertises the ones it supports to the server in the Client Security Data block (TS_UD_CS_SEC). The server's configured encryption level value restricts the possible final encryption method. Something that I was not able to find in the documentation is the priority level of the individual encryption methods based on which the server makes its final method decision if there are several options. My analysis with Windows Servers reveiled that the order is 128, 56, 40, FIPS. The server only chooses FIPS if the level is "FIPS Comliant" or if it is the only method advertised by the client. Bottom line: * FreeRDP's client side does not need to set settings->EncryptionLevel (which was done quite frequently). * FreeRDP's server side does not have to set the supported encryption methods list in settings->EncryptionMethods Changes in this commit: Removed unnecessary/confusing changes of EncryptionLevel/Methods settings Refactor settings->DisableEncryption * This value actually means "Advanced RDP Encryption (NLA/TLS) is NOT used" * The old name caused lots of confusion among developers * Renamed it to "UseRdpSecurityLayer" (the compare logic stays untouched) Any client's setting of settings->EncryptionMethods were annihilated * All clients "want" to set all supported methods * Some clients forgot 56bit because 56bit was not supported at the time the code was written * settings->EncryptionMethods was overwritten anyways in nego_connect() * Removed all client side settings of settings->EncryptionMethods The default is "None" (0) * Changed nego_connect() to advertise all supported methods if settings->EncryptionMethods is 0 (None) * Added a commandline option /encryption-methods:comma separated list of the values "40", "56", "128", "FIPS". E.g. /encryption-methods:56,128 * Print warning if server chooses non-advertised method Verify received level and method in client's gcc_read_server_security_data * Only accept valid/known encryption methods * Verify encryption level/method combinations according to MS-RDPBCGR 5.3.2 Server implementations can now set settings->EncryptionLevel * The default for settings->EncryptionLevel is 0 (None) * nego_send_negotiation_response() changes it to ClientCompatible in that case * default to ClientCompatible if the server implementation set an invalid level Fix server's gcc_write_server_security_data * Verify server encryption level value set by server implementations * Choose rdp encryption method based on level and supported client methods * Moved FIPS to the lowest priority (only used if other methods are possible) Updated sample server * Support RDP Security (RdpKeyFile was not set) * Added commented sample code for setting the security level
2014-12-12 04:17:12 +03:00
/**
* If the server implementation did not explicitely set a
* encryption level we default to client compatible
*/
settings->EncryptionLevel = ENCRYPTION_LEVEL_CLIENT_COMPATIBLE;
}
Standard RDP Security Layer Levels/Method Overhaul [MS-RDPBCGR] Section 5.3 describes the encryption level and method values for standard RDP security. Looking at the current usage of these values in the FreeRDP code gives me reason to believe that there is a certain lack of understanding of how these values should be handled. The encryption level is only configured on the server side in the "Encryption Level" setting found in the Remote Desktop Session Host Configuration RDP-Tcp properties dialog and this value is never transferred from the client to the server over the wire. The possible options are "None", "Low", "Client Compatible", "High" and "FIPS Compliant". The client receices this value in the Server Security Data block (TS_UD_SC_SEC1), probably only for informational purposes and maybe to give the client the possibility to verify if the server's decision for the encryption method confirms to the server's encryption level. The possible encryption methods are "NONE", "40BIT", "56BIT", "128BIT" and "FIPS" and the RDP client advertises the ones it supports to the server in the Client Security Data block (TS_UD_CS_SEC). The server's configured encryption level value restricts the possible final encryption method. Something that I was not able to find in the documentation is the priority level of the individual encryption methods based on which the server makes its final method decision if there are several options. My analysis with Windows Servers reveiled that the order is 128, 56, 40, FIPS. The server only chooses FIPS if the level is "FIPS Comliant" or if it is the only method advertised by the client. Bottom line: * FreeRDP's client side does not need to set settings->EncryptionLevel (which was done quite frequently). * FreeRDP's server side does not have to set the supported encryption methods list in settings->EncryptionMethods Changes in this commit: Removed unnecessary/confusing changes of EncryptionLevel/Methods settings Refactor settings->DisableEncryption * This value actually means "Advanced RDP Encryption (NLA/TLS) is NOT used" * The old name caused lots of confusion among developers * Renamed it to "UseRdpSecurityLayer" (the compare logic stays untouched) Any client's setting of settings->EncryptionMethods were annihilated * All clients "want" to set all supported methods * Some clients forgot 56bit because 56bit was not supported at the time the code was written * settings->EncryptionMethods was overwritten anyways in nego_connect() * Removed all client side settings of settings->EncryptionMethods The default is "None" (0) * Changed nego_connect() to advertise all supported methods if settings->EncryptionMethods is 0 (None) * Added a commandline option /encryption-methods:comma separated list of the values "40", "56", "128", "FIPS". E.g. /encryption-methods:56,128 * Print warning if server chooses non-advertised method Verify received level and method in client's gcc_read_server_security_data * Only accept valid/known encryption methods * Verify encryption level/method combinations according to MS-RDPBCGR 5.3.2 Server implementations can now set settings->EncryptionLevel * The default for settings->EncryptionLevel is 0 (None) * nego_send_negotiation_response() changes it to ClientCompatible in that case * default to ClientCompatible if the server implementation set an invalid level Fix server's gcc_write_server_security_data * Verify server encryption level value set by server implementations * Choose rdp encryption method based on level and supported client methods * Moved FIPS to the lowest priority (only used if other methods are possible) Updated sample server * Support RDP Security (RdpKeyFile was not set) * Added commented sample code for setting the security level
2014-12-12 04:17:12 +03:00
if (settings->LocalConnection)
{
/**
* Note: This hack was firstly introduced in commit 95f5e115 to
* disable the unnecessary encryption with peers connecting to
* 127.0.0.1 or local unix sockets.
* This also affects connections via port tunnels! (e.g. ssh -L)
*/
WLog_INFO(TAG, "Turning off encryption for local peer with standard rdp security");
settings->UseRdpSecurityLayer = FALSE;
settings->EncryptionLevel = ENCRYPTION_LEVEL_NONE;
}
if (!settings->RdpServerRsaKey && !settings->RdpKeyFile)
{
WLog_ERR(TAG, "Missing server certificate");
return FALSE;
}
}
else if (settings->SelectedProtocol == PROTOCOL_TLS)
{
settings->TlsSecurity = TRUE;
settings->NlaSecurity = FALSE;
settings->RdpSecurity = FALSE;
Standard RDP Security Layer Levels/Method Overhaul [MS-RDPBCGR] Section 5.3 describes the encryption level and method values for standard RDP security. Looking at the current usage of these values in the FreeRDP code gives me reason to believe that there is a certain lack of understanding of how these values should be handled. The encryption level is only configured on the server side in the "Encryption Level" setting found in the Remote Desktop Session Host Configuration RDP-Tcp properties dialog and this value is never transferred from the client to the server over the wire. The possible options are "None", "Low", "Client Compatible", "High" and "FIPS Compliant". The client receices this value in the Server Security Data block (TS_UD_SC_SEC1), probably only for informational purposes and maybe to give the client the possibility to verify if the server's decision for the encryption method confirms to the server's encryption level. The possible encryption methods are "NONE", "40BIT", "56BIT", "128BIT" and "FIPS" and the RDP client advertises the ones it supports to the server in the Client Security Data block (TS_UD_CS_SEC). The server's configured encryption level value restricts the possible final encryption method. Something that I was not able to find in the documentation is the priority level of the individual encryption methods based on which the server makes its final method decision if there are several options. My analysis with Windows Servers reveiled that the order is 128, 56, 40, FIPS. The server only chooses FIPS if the level is "FIPS Comliant" or if it is the only method advertised by the client. Bottom line: * FreeRDP's client side does not need to set settings->EncryptionLevel (which was done quite frequently). * FreeRDP's server side does not have to set the supported encryption methods list in settings->EncryptionMethods Changes in this commit: Removed unnecessary/confusing changes of EncryptionLevel/Methods settings Refactor settings->DisableEncryption * This value actually means "Advanced RDP Encryption (NLA/TLS) is NOT used" * The old name caused lots of confusion among developers * Renamed it to "UseRdpSecurityLayer" (the compare logic stays untouched) Any client's setting of settings->EncryptionMethods were annihilated * All clients "want" to set all supported methods * Some clients forgot 56bit because 56bit was not supported at the time the code was written * settings->EncryptionMethods was overwritten anyways in nego_connect() * Removed all client side settings of settings->EncryptionMethods The default is "None" (0) * Changed nego_connect() to advertise all supported methods if settings->EncryptionMethods is 0 (None) * Added a commandline option /encryption-methods:comma separated list of the values "40", "56", "128", "FIPS". E.g. /encryption-methods:56,128 * Print warning if server chooses non-advertised method Verify received level and method in client's gcc_read_server_security_data * Only accept valid/known encryption methods * Verify encryption level/method combinations according to MS-RDPBCGR 5.3.2 Server implementations can now set settings->EncryptionLevel * The default for settings->EncryptionLevel is 0 (None) * nego_send_negotiation_response() changes it to ClientCompatible in that case * default to ClientCompatible if the server implementation set an invalid level Fix server's gcc_write_server_security_data * Verify server encryption level value set by server implementations * Choose rdp encryption method based on level and supported client methods * Moved FIPS to the lowest priority (only used if other methods are possible) Updated sample server * Support RDP Security (RdpKeyFile was not set) * Added commented sample code for setting the security level
2014-12-12 04:17:12 +03:00
settings->UseRdpSecurityLayer = FALSE;
settings->EncryptionLevel = ENCRYPTION_LEVEL_NONE;
}
else if (settings->SelectedProtocol == PROTOCOL_NLA)
{
settings->TlsSecurity = TRUE;
settings->NlaSecurity = TRUE;
settings->RdpSecurity = FALSE;
Standard RDP Security Layer Levels/Method Overhaul [MS-RDPBCGR] Section 5.3 describes the encryption level and method values for standard RDP security. Looking at the current usage of these values in the FreeRDP code gives me reason to believe that there is a certain lack of understanding of how these values should be handled. The encryption level is only configured on the server side in the "Encryption Level" setting found in the Remote Desktop Session Host Configuration RDP-Tcp properties dialog and this value is never transferred from the client to the server over the wire. The possible options are "None", "Low", "Client Compatible", "High" and "FIPS Compliant". The client receices this value in the Server Security Data block (TS_UD_SC_SEC1), probably only for informational purposes and maybe to give the client the possibility to verify if the server's decision for the encryption method confirms to the server's encryption level. The possible encryption methods are "NONE", "40BIT", "56BIT", "128BIT" and "FIPS" and the RDP client advertises the ones it supports to the server in the Client Security Data block (TS_UD_CS_SEC). The server's configured encryption level value restricts the possible final encryption method. Something that I was not able to find in the documentation is the priority level of the individual encryption methods based on which the server makes its final method decision if there are several options. My analysis with Windows Servers reveiled that the order is 128, 56, 40, FIPS. The server only chooses FIPS if the level is "FIPS Comliant" or if it is the only method advertised by the client. Bottom line: * FreeRDP's client side does not need to set settings->EncryptionLevel (which was done quite frequently). * FreeRDP's server side does not have to set the supported encryption methods list in settings->EncryptionMethods Changes in this commit: Removed unnecessary/confusing changes of EncryptionLevel/Methods settings Refactor settings->DisableEncryption * This value actually means "Advanced RDP Encryption (NLA/TLS) is NOT used" * The old name caused lots of confusion among developers * Renamed it to "UseRdpSecurityLayer" (the compare logic stays untouched) Any client's setting of settings->EncryptionMethods were annihilated * All clients "want" to set all supported methods * Some clients forgot 56bit because 56bit was not supported at the time the code was written * settings->EncryptionMethods was overwritten anyways in nego_connect() * Removed all client side settings of settings->EncryptionMethods The default is "None" (0) * Changed nego_connect() to advertise all supported methods if settings->EncryptionMethods is 0 (None) * Added a commandline option /encryption-methods:comma separated list of the values "40", "56", "128", "FIPS". E.g. /encryption-methods:56,128 * Print warning if server chooses non-advertised method Verify received level and method in client's gcc_read_server_security_data * Only accept valid/known encryption methods * Verify encryption level/method combinations according to MS-RDPBCGR 5.3.2 Server implementations can now set settings->EncryptionLevel * The default for settings->EncryptionLevel is 0 (None) * nego_send_negotiation_response() changes it to ClientCompatible in that case * default to ClientCompatible if the server implementation set an invalid level Fix server's gcc_write_server_security_data * Verify server encryption level value set by server implementations * Choose rdp encryption method based on level and supported client methods * Moved FIPS to the lowest priority (only used if other methods are possible) Updated sample server * Support RDP Security (RdpKeyFile was not set) * Added commented sample code for setting the security level
2014-12-12 04:17:12 +03:00
settings->UseRdpSecurityLayer = FALSE;
settings->EncryptionLevel = ENCRYPTION_LEVEL_NONE;
}
}
return status;
}
/**
* Initialize NEGO state machine.
* @param nego
*/
void nego_init(rdpNego* nego)
{
nego->state = NEGO_STATE_INITIAL;
2015-02-03 01:16:32 +03:00
nego->RequestedProtocols = PROTOCOL_RDP;
nego->CookieMaxLength = DEFAULT_COOKIE_MAX_LENGTH;
nego->sendNegoData = FALSE;
nego->flags = 0;
}
/**
* Create a new NEGO state machine instance.
* @param transport
* @return
*/
rdpNego* nego_new(rdpTransport* transport)
{
rdpNego* nego = (rdpNego*) calloc(1, sizeof(rdpNego));
if (!nego)
return NULL;
nego->transport = transport;
nego_init(nego);
return nego;
}
/**
* Free NEGO state machine.
* @param nego
*/
void nego_free(rdpNego* nego)
{
2015-05-11 10:07:39 +03:00
if (nego)
{
free(nego->RoutingToken);
free(nego->cookie);
free(nego);
}
}
/**
* Set target hostname and port.
* @param nego
* @param hostname
* @param port
*/
void nego_set_target(rdpNego* nego, char* hostname, int port)
{
nego->hostname = hostname;
nego->port = port;
}
/**
* Enable security layer negotiation.
* @param nego pointer to the negotiation structure
* @param enable_rdp whether to enable security layer negotiation (TRUE for enabled, FALSE for disabled)
*/
void nego_set_negotiation_enabled(rdpNego* nego, BOOL NegotiateSecurityLayer)
{
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "Enabling security layer negotiation: %s", NegotiateSecurityLayer ? "TRUE" : "FALSE");
nego->NegotiateSecurityLayer = NegotiateSecurityLayer;
}
2013-11-06 10:51:55 +04:00
/**
* Enable restricted admin mode.
* @param nego pointer to the negotiation structure
* @param enable_restricted whether to enable security layer negotiation (TRUE for enabled, FALSE for disabled)
*/
void nego_set_restricted_admin_mode_required(rdpNego* nego, BOOL RestrictedAdminModeRequired)
{
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "Enabling restricted admin mode: %s", RestrictedAdminModeRequired ? "TRUE" : "FALSE");
2013-11-06 10:51:55 +04:00
nego->RestrictedAdminModeRequired = RestrictedAdminModeRequired;
}
void nego_set_gateway_enabled(rdpNego* nego, BOOL GatewayEnabled)
{
nego->GatewayEnabled = GatewayEnabled;
}
void nego_set_gateway_bypass_local(rdpNego* nego, BOOL GatewayBypassLocal)
{
nego->GatewayBypassLocal = GatewayBypassLocal;
}
/**
* Enable RDP security protocol.
* @param nego pointer to the negotiation structure
* @param enable_rdp whether to enable normal RDP protocol (TRUE for enabled, FALSE for disabled)
*/
void nego_enable_rdp(rdpNego* nego, BOOL enable_rdp)
{
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "Enabling RDP security: %s", enable_rdp ? "TRUE" : "FALSE");
2015-02-03 01:16:32 +03:00
nego->EnabledProtocols[PROTOCOL_RDP] = enable_rdp;
}
/**
* Enable TLS security protocol.
* @param nego pointer to the negotiation structure
* @param enable_tls whether to enable TLS + RDP protocol (TRUE for enabled, FALSE for disabled)
*/
2013-11-06 10:51:55 +04:00
void nego_enable_tls(rdpNego* nego, BOOL enable_tls)
{
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "Enabling TLS security: %s", enable_tls ? "TRUE" : "FALSE");
2015-02-03 01:16:32 +03:00
nego->EnabledProtocols[PROTOCOL_TLS] = enable_tls;
}
/**
* Enable NLA security protocol.
* @param nego pointer to the negotiation structure
* @param enable_nla whether to enable network level authentication protocol (TRUE for enabled, FALSE for disabled)
*/
void nego_enable_nla(rdpNego* nego, BOOL enable_nla)
{
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "Enabling NLA security: %s", enable_nla ? "TRUE" : "FALSE");
2015-02-03 01:16:32 +03:00
nego->EnabledProtocols[PROTOCOL_NLA] = enable_nla;
}
/**
* Enable NLA extended security protocol.
* @param nego pointer to the negotiation structure
* @param enable_ext whether to enable network level authentication extended protocol (TRUE for enabled, FALSE for disabled)
*/
void nego_enable_ext(rdpNego* nego, BOOL enable_ext)
{
2015-02-02 19:50:56 +03:00
WLog_DBG(TAG, "Enabling NLA extended security: %s", enable_ext ? "TRUE" : "FALSE");
2015-02-03 01:16:32 +03:00
nego->EnabledProtocols[PROTOCOL_EXT] = enable_ext;
}
/**
* Set routing token.
* @param nego
2012-09-24 12:40:32 +04:00
* @param RoutingToken
* @param RoutingTokenLength
*/
2014-04-10 23:07:53 +04:00
BOOL nego_set_routing_token(rdpNego* nego, BYTE* RoutingToken, DWORD RoutingTokenLength)
{
free(nego->RoutingToken);
2012-09-24 12:40:32 +04:00
nego->RoutingTokenLength = RoutingTokenLength;
nego->RoutingToken = (BYTE*) malloc(nego->RoutingTokenLength);
2014-04-10 23:07:53 +04:00
if (!nego->RoutingToken)
return FALSE;
CopyMemory(nego->RoutingToken, RoutingToken, nego->RoutingTokenLength);
2014-04-10 23:07:53 +04:00
return TRUE;
}
/**
* Set cookie.
* @param nego
* @param cookie
*/
2014-04-10 23:07:53 +04:00
BOOL nego_set_cookie(rdpNego* nego, char* cookie)
{
if (nego->cookie)
{
free(nego->cookie);
2015-05-11 10:07:39 +03:00
nego->cookie = NULL;
}
if (!cookie)
return TRUE;
nego->cookie = _strdup(cookie);
2014-04-10 23:07:53 +04:00
if (!nego->cookie)
return FALSE;
return TRUE;
}
/**
* Set cookie maximum length
* @param nego
2015-02-03 01:16:32 +03:00
* @param CookieMaxLength
*/
2015-02-03 01:16:32 +03:00
void nego_set_cookie_max_length(rdpNego* nego, UINT32 CookieMaxLength)
{
2015-02-03 01:16:32 +03:00
nego->CookieMaxLength = CookieMaxLength;
}
/**
* Enable / disable preconnection PDU.
* @param nego
* @param send_pcpdu
*/
2015-02-03 01:16:32 +03:00
void nego_set_send_preconnection_pdu(rdpNego* nego, BOOL SendPreconnectionPdu)
{
2015-02-03 01:16:32 +03:00
nego->SendPreconnectionPdu = SendPreconnectionPdu;
}
/**
* Set preconnection id.
* @param nego
* @param id
*/
2015-02-03 01:16:32 +03:00
void nego_set_preconnection_id(rdpNego* nego, UINT32 PreconnectionId)
{
2015-02-03 01:16:32 +03:00
nego->PreconnectionId = PreconnectionId;
}
/**
* Set preconnection blob.
* @param nego
* @param blob
*/
2015-02-03 01:16:32 +03:00
void nego_set_preconnection_blob(rdpNego* nego, char* PreconnectionBlob)
{
2015-02-03 01:16:32 +03:00
nego->PreconnectionBlob = PreconnectionBlob;
}