21 #include <freerdp/config.h>
22 #include <freerdp/version.h>
24 #include "../settings.h"
26 #include <winpr/assert.h>
28 #include <winpr/crt.h>
29 #include <winpr/synch.h>
30 #include <winpr/print.h>
31 #include <winpr/stream.h>
32 #include <winpr/winsock.h>
33 #include <winpr/cred.h>
34 #include <winpr/bcrypt.h>
36 #include <freerdp/log.h>
37 #include <freerdp/error.h>
38 #include <freerdp/crypto/certificate.h>
39 #include <freerdp/utils/ringbuffer.h>
40 #include <freerdp/utils/smartcardlogon.h>
44 #include "websocket.h"
46 #include "../credssp_auth.h"
49 #include "../../crypto/crypto.h"
50 #include "../../crypto/certificate.h"
51 #include "../../crypto/opensslcompat.h"
52 #include "rpc_fault.h"
54 #include "../redirection.h"
56 #include <winpr/json.h>
69 typedef struct rdp_arm rdpArm;
71 #define TAG FREERDP_TAG("core.gateway.arm")
74 static BOOL arm_tls_connect(rdpArm* arm, rdpTls* tls,
int timeout)
80 BIO* socketBio = NULL;
81 BIO* bufferedBio = NULL;
82 rdpSettings* settings = arm->context->settings;
91 const char* proxyUsername = NULL;
92 const char* proxyPassword = NULL;
93 BOOL isProxyConnection =
94 proxy_prepare(settings, &peerHostname, &peerPort, &proxyUsername, &proxyPassword);
96 sockfd = freerdp_tcp_connect(arm->context, peerHostname, peerPort, timeout);
98 WLog_DBG(TAG,
"connecting to %s %d", peerHostname, peerPort);
102 socketBio = BIO_new(BIO_s_simple_socket());
106 closesocket((SOCKET)sockfd);
110 BIO_set_fd(socketBio, sockfd, BIO_CLOSE);
111 bufferedBio = BIO_new(BIO_s_buffered_socket());
115 BIO_free_all(socketBio);
119 bufferedBio = BIO_push(bufferedBio, socketBio);
123 status = BIO_set_nonblock(bufferedBio, TRUE);
125 if (isProxyConnection)
127 if (!proxy_connect(arm->context, bufferedBio, proxyUsername, proxyPassword,
131 BIO_free_all(bufferedBio);
138 BIO_free_all(bufferedBio);
143 tls->port = MIN(UINT16_MAX, settings->GatewayPort);
144 tls->isGatewayTransport = TRUE;
145 status = freerdp_tls_connect(tls, bufferedBio);
148 rdpContext* context = arm->context;
150 freerdp_set_last_error_if_not(context, FREERDP_ERROR_TLS_CONNECT_FAILED);
152 freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
156 return (status >= 1);
159 static wStream* arm_build_http_request(rdpArm* arm,
const char* method,
160 TRANSFER_ENCODING transferEncoding,
const char* content_type,
161 size_t content_length)
164 HttpRequest* request = NULL;
165 const char* uri = NULL;
168 WINPR_ASSERT(method);
169 WINPR_ASSERT(content_type);
171 WINPR_ASSERT(arm->context);
173 freerdp* instance = arm->context->instance;
174 WINPR_ASSERT(instance);
176 uri = http_context_get_uri(arm->http);
177 request = http_request_new();
182 if (!http_request_set_method(request, method) || !http_request_set_uri(request, uri))
189 if (!instance->GetAccessToken)
191 WLog_ERR(TAG,
"No authorization token provided");
195 if (!instance->GetAccessToken(instance, ACCESS_TOKEN_TYPE_AVD, &token, 0))
197 WLog_ERR(TAG,
"Unable to obtain access token");
210 if (!http_request_set_auth_scheme(request,
"Bearer") ||
211 !http_request_set_auth_param(
216 if (!http_request_set_transfer_encoding(request, transferEncoding) ||
217 !http_request_set_content_length(request, content_length) ||
218 !http_request_set_content_type(request, content_type))
221 s = http_request_write(arm->http, request);
223 http_request_free(request);
226 Stream_SealLength(s);
231 static BOOL arm_send_http_request(rdpArm* arm, rdpTls* tls,
const char* method,
232 const char* content_type,
const char* data,
size_t content_length)
236 arm_build_http_request(arm, method, TransferEncodingIdentity, content_type, content_length);
241 const size_t sz = Stream_Length(s);
242 status = freerdp_tls_write_all(tls, Stream_Buffer(s), sz);
244 Stream_Free(s, TRUE);
245 if (status >= 0 && content_length > 0 && data)
246 status = freerdp_tls_write_all(tls, (
const BYTE*)data, content_length);
248 return (status >= 0);
251 static void arm_free(rdpArm* arm)
256 freerdp_tls_free(arm->tls);
257 http_context_free(arm->http);
262 static rdpArm* arm_new(rdpContext* context)
264 WINPR_ASSERT(context);
266 rdpArm* arm = (rdpArm*)calloc(1,
sizeof(rdpArm));
270 arm->context = context;
271 arm->tls = freerdp_tls_new(context);
275 arm->http = http_context_new();
287 static char* arm_create_request_json(rdpArm* arm)
290 char* message = NULL;
296 goto arm_create_cleanup;
305 goto arm_create_cleanup;
337 static WINPR_CIPHER_CTX* treatAuthBlob(
const BYTE* pbInput,
size_t cbInput)
339 WINPR_CIPHER_CTX* ret = NULL;
340 char algoName[100] = { 0 };
342 SSIZE_T algoSz = ConvertWCharNToUtf8((
const WCHAR*)pbInput, cbInput /
sizeof(WCHAR), algoName,
346 WLog_ERR(TAG,
"invalid algoName");
350 algoName[algoSz] = 0;
351 if (strcmp(algoName,
"AES") != 0)
353 WLog_ERR(TAG,
"only AES is supported for now");
357 cbInput -= (algoSz + 1) *
sizeof(WCHAR);
361 WLog_ERR(TAG,
"invalid AuthBlob size");
368 Stream_StaticConstInit(&staticStream, pbInput + (algoSz + 1) *
sizeof(WCHAR), cbInput);
371 Stream_Read_UINT32(s, dwMagic);
373 if (dwMagic != BCRYPT_KEY_DATA_BLOB_MAGIC)
375 WLog_ERR(TAG,
"unsupported authBlob type");
379 UINT32 dwVersion = 0;
380 Stream_Read_UINT32(s, dwVersion);
381 if (dwVersion != BCRYPT_KEY_DATA_BLOB_VERSION1)
383 WLog_ERR(TAG,
"unsupported authBlob version %d, expecting %d", dwVersion,
384 BCRYPT_KEY_DATA_BLOB_VERSION1);
388 UINT32 cbKeyData = 0;
389 Stream_Read_UINT32(s, cbKeyData);
392 if (cbKeyData > cbInput)
394 WLog_ERR(TAG,
"invalid authBlob size");
402 cipherType = WINPR_CIPHER_AES_128_CBC;
405 cipherType = WINPR_CIPHER_AES_192_CBC;
408 cipherType = WINPR_CIPHER_AES_256_CBC;
411 WLog_ERR(TAG,
"invalid authBlob cipher size");
415 ret = winpr_Cipher_NewEx(cipherType, WINPR_ENCRYPT, Stream_Pointer(s), cbKeyData, NULL, 0);
418 WLog_ERR(TAG,
"error creating cipher");
422 if (!winpr_Cipher_SetPadding(ret, TRUE))
424 WLog_ERR(TAG,
"unable to enable padding on cipher");
425 winpr_Cipher_Free(ret);
432 static BOOL arm_stringEncodeW(
const BYTE* pin,
size_t cbIn, BYTE** ppOut,
size_t* pcbOut)
438 char* b64encoded = crypto_base64_encode_ex(pin, cbIn, TRUE);
444 *ppOut = (BYTE*)ConvertUtf8NToWCharAlloc(b64encoded, strlen(b64encoded), &outSz);
450 *pcbOut = (outSz + 1) *
sizeof(WCHAR);
454 static BOOL arm_encodeRedirectPasswd(rdpSettings* settings,
const rdpCertificate* cert,
455 WINPR_CIPHER_CTX* cipher)
459 BYTE* finalOutput = NULL;
465 size_t wpasswdLen = 0;
469 WLog_ERR(TAG,
"error when converting password to UTF16");
473 size_t wpasswdBytes = (wpasswdLen + 1) *
sizeof(WCHAR);
474 BYTE* encryptedPass = calloc(1, wpasswdBytes + 16);
475 size_t encryptedPassLen = 0;
477 if (!encryptedPass ||
478 !winpr_Cipher_Update(cipher, wpasswd, wpasswdBytes, encryptedPass, &encryptedPassLen) ||
479 !winpr_Cipher_Final(cipher, encryptedPass + encryptedPassLen, &finalLen))
481 WLog_ERR(TAG,
"error when ciphering password");
484 encryptedPassLen += finalLen;
487 size_t output_length = 0;
489 if (!freerdp_certificate_publickey_encrypt(cert, encryptedPass, encryptedPassLen, &output,
492 WLog_ERR(TAG,
"unable to encrypt with the server's public key");
496 size_t finalOutputLen = 0;
497 if (!arm_stringEncodeW(output, output_length, &finalOutput, &finalOutputLen))
499 WLog_ERR(TAG,
"unable to base64+utf16 final blob");
506 WLog_ERR(TAG,
"unable to set the redirection password in settings");
510 settings->RdstlsSecurity = TRUE;
511 settings->AadSecurity = FALSE;
512 settings->NlaSecurity = FALSE;
513 settings->RdpSecurity = FALSE;
514 settings->TlsSecurity = FALSE;
515 settings->RedirectionFlags = LB_PASSWORD_IS_PK_ENCRYPTED;
529 static BOOL arm_pick_base64Utf16Field(
const WINPR_JSON* json,
const char* name, BYTE** poutput,
543 BYTE* output1 = NULL;
545 crypto_base64_decode(nodeValue, strlen(nodeValue), &output1, &len1);
546 if (!output1 || !len1)
548 WLog_ERR(TAG,
"error when first unbase64 for %s", name);
554 char* output2 = ConvertWCharNToUtf8Alloc((WCHAR*)output1, len1 /
sizeof(WCHAR), &len2);
556 if (!output2 || !len2)
558 WLog_ERR(TAG,
"error when decode('utf-16') for %s", name);
564 crypto_base64_decode(output2, len2, &output, plen);
566 if (!output || !*plen)
568 WLog_ERR(TAG,
"error when second unbase64 for %s", name);
597 static size_t arm_parse_ipvx_count(WINPR_JSON* ipvX)
606 static BOOL arm_parse_ipv6(rdpSettings* settings, WINPR_JSON* ipv6,
size_t* pAddressIdx)
608 WINPR_ASSERT(settings);
610 WINPR_ASSERT(pAddressIdx);
619 for (
size_t j = 0; j < naddresses; j++)
626 if (utils_str_is_empty(addr))
628 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
629 (*pAddressIdx)++, addr))
635 static BOOL arm_parse_ipv4(rdpSettings* settings, WINPR_JSON* ipv4,
size_t* pAddressIdx)
637 WINPR_ASSERT(settings);
639 WINPR_ASSERT(pAddressIdx);
646 for (
size_t j = 0; j < naddresses; j++)
656 if (!utils_str_is_empty(publicIp))
658 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
659 (*pAddressIdx)++, publicIp))
668 if (!utils_str_is_empty(privateIp))
670 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
671 (*pAddressIdx)++, privateIp))
679 static BOOL arm_treat_azureInstanceNetworkMetadata(
const char* metadata, rdpSettings* settings)
683 WINPR_ASSERT(settings);
685 if (!freerdp_target_net_adresses_reset(settings, 0))
691 WLog_ERR(TAG,
"invalid azureInstanceNetworkMetadata");
704 WLog_ERR(TAG,
"expecting interface to be an Array");
709 if (interfaceSz == 0)
711 WLog_WARN(TAG,
"no addresses in azure instance metadata");
717 for (
size_t i = 0; i < interfaceSz; i++)
725 count += arm_parse_ipvx_count(ipv6);
729 count += arm_parse_ipvx_count(ipv4);
732 if (!freerdp_target_net_adresses_reset(settings, count))
735 size_t addressIdx = 0;
736 for (
size_t i = 0; i < interfaceSz; i++)
745 if (!arm_parse_ipv6(settings, ipv6, &addressIdx))
752 if (!arm_parse_ipv4(settings, ipv4, &addressIdx))
756 if (addressIdx > UINT32_MAX)
769 static BOOL arm_fill_rdstls(rdpArm* arm, rdpSettings* settings,
const WINPR_JSON* json)
773 BYTE* authBlob = NULL;
774 rdpCertificate* redirectedServerCert = NULL;
779 WINPR_JSON* redirectedAuthGuidNode =
785 if (!redirectedAuthGuid)
792 const SSIZE_T wGUID_len = ConvertUtf8ToWChar(redirectedAuthGuid, wGUID, ARRAYSIZE(wGUID));
795 WLog_ERR(TAG,
"unable to allocate space for redirectedAuthGuid");
801 (wGUID_len + 1) *
sizeof(WCHAR));
804 WLog_ERR(TAG,
"unable to set RedirectionGuid");
811 if (!arm_pick_base64Utf16Field(json,
"redirectedServerCert", &cert, &certLen))
814 if (!rdp_redirection_read_target_cert(&redirectedServerCert, cert, certLen))
818 size_t authBlobLen = 0;
819 if (!arm_pick_base64Utf16Field(json,
"redirectedAuthBlob", &authBlob, &authBlobLen))
822 WINPR_CIPHER_CTX* cipher = treatAuthBlob(authBlob, authBlobLen);
826 const BOOL rerp = arm_encodeRedirectPasswd(settings, redirectedServerCert, cipher);
827 winpr_Cipher_Free(cipher);
835 freerdp_certificate_free(redirectedServerCert);
842 static BOOL arm_fill_gateway_parameters(rdpArm* arm,
const char* message,
size_t len)
845 WINPR_ASSERT(arm->context);
846 WINPR_ASSERT(message);
853 rdpSettings* settings = arm->context->settings;
856 if (gwurlstr != NULL)
858 WLog_DBG(TAG,
"extracted target url %s", gwurlstr);
878 WLog_ERR(TAG,
"error when treating azureInstanceNetworkMetadata");
887 status = arm_fill_rdstls(arm, settings, json);
894 static BOOL arm_handle_request_ok(rdpArm* arm,
const HttpResponse* response)
896 const size_t len = http_response_get_body_length(response);
897 const char* msg = (
const char*)http_response_get_body(response);
898 if (strnlen(msg, len + 1) > len)
901 WLog_DBG(TAG,
"Got HTTP Response data: %s", msg);
902 return arm_fill_gateway_parameters(arm, msg, len);
905 static BOOL arm_handle_bad_request(rdpArm* arm,
const HttpResponse* response, BOOL* retry)
907 WINPR_ASSERT(response);
914 const size_t len = http_response_get_body_length(response);
915 const char* msg = (
const char*)http_response_get_body(response);
916 if (strnlen(msg, len + 1) > len)
919 WLog_DBG(TAG,
"Got HTTP Response data: %s", msg);
925 if (error_ptr != NULL)
926 WLog_ERR(TAG,
"NullPoException: %s", error_ptr);
933 if (gw_code_str == NULL)
935 WLog_ERR(TAG,
"Response has no \"Code\" property");
936 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
940 if (strcmp(gw_code_str,
"E_PROXY_ORCHESTRATION_LB_SESSIONHOST_DEALLOCATED") == 0)
946 WLog_WARN(TAG,
"Starting your VM. It may take up to 5 minutes");
948 WLog_WARN(TAG,
"%s", msgstr);
952 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
962 static BOOL arm_handle_request(rdpArm* arm, BOOL* retry, DWORD timeout)
968 char* message = NULL;
971 HttpResponse* response = NULL;
974 if (!http_context_set_uri(arm->http,
"/api/arm/v2/connections/") ||
975 !http_context_set_accept(arm->http,
"application/json") ||
976 !http_context_set_cache_control(arm->http,
"no-cache") ||
977 !http_context_set_pragma(arm->http,
"no-cache") ||
978 !http_context_set_connection(arm->http,
"Keep-Alive") ||
979 !http_context_set_user_agent(arm->http, FREERDP_USER_AGENT) ||
980 !http_context_set_x_ms_user_agent(arm->http, FREERDP_USER_AGENT) ||
982 FreeRDP_GatewayHostname)))
985 if (!arm_tls_connect(arm, arm->tls, timeout))
988 message = arm_create_request_json(arm);
992 if (!arm_send_http_request(arm, arm->tls,
"POST",
"application/json", message, strlen(message)))
995 response = http_response_recv(arm->tls, TRUE);
999 StatusCode = http_response_get_status_code(response);
1000 if (StatusCode == HTTP_STATUS_OK)
1002 if (!arm_handle_request_ok(arm, response))
1005 else if (StatusCode == HTTP_STATUS_BAD_REQUEST)
1007 if (!arm_handle_bad_request(arm, response, retry))
1012 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
1018 http_response_free(response);
1025 BOOL arm_resolve_endpoint(rdpContext* context, DWORD timeout)
1028 WLog_ERR(TAG,
"arm gateway support not compiled in");
1035 if (!context->settings)
1041 WLog_ERR(TAG,
"loadBalanceInfo and RemoteApplicationProgram needed");
1045 rdpArm* arm = arm_new(context);
1055 freerdp* instance = context->instance;
1056 WINPR_ASSERT(instance);
1057 SSIZE_T delay = IFCALLRESULT(-1, instance->RetryDialog, instance,
"arm-transport",
1058 arm->gateway_retry, arm);
1059 arm->gateway_retry++;
1064 WLog_DBG(TAG,
"Delay for %" PRIdz
"ms before next attempt", delay);
1067 DWORD slp = (UINT32)delay;
1068 if (delay > UINT32_MAX)
1075 rc = arm_handle_request(arm, &retry, timeout);
1077 }
while (retry && rc);
WINPR_API WINPR_JSON * WINPR_JSON_ParseWithLength(const char *value, size_t buffer_length)
Parse a JSON string.
WINPR_API WINPR_JSON * WINPR_JSON_AddStringToObject(WINPR_JSON *object, const char *name, const char *string)
WINPR_JSON_AddStringToObject.
WINPR_API BOOL WINPR_JSON_IsString(const WINPR_JSON *item)
Check if JSON item is of type String.
WINPR_API WINPR_JSON * WINPR_JSON_Parse(const char *value)
Parse a '\0' terminated JSON string.
WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItemCaseSensitive(const WINPR_JSON *object, const char *string)
Same as WINPR_JSON_GetObjectItem but with case insensitive matching.
WINPR_API char * WINPR_JSON_PrintUnformatted(WINPR_JSON *item)
Serialize a JSON instance to string without formatting for human readable formatted output see WINPR_...
WINPR_API WINPR_JSON * WINPR_JSON_AddNullToObject(WINPR_JSON *object, const char *name)
WINPR_JSON_AddNullToObject.
WINPR_API WINPR_JSON * WINPR_JSON_GetArrayItem(const WINPR_JSON *array, size_t index)
Return a pointer to an item in the array.
WINPR_API const char * WINPR_JSON_GetErrorPtr(void)
Return an error string.
WINPR_API void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItem(const WINPR_JSON *object, const char *string)
Return a pointer to an JSON object item.
WINPR_API WINPR_JSON * WINPR_JSON_CreateObject(void)
WINPR_JSON_CreateObject.
WINPR_API size_t WINPR_JSON_GetArraySize(const WINPR_JSON *array)
Get the number of arrayitems from an array.
WINPR_API BOOL WINPR_JSON_IsArray(const WINPR_JSON *item)
Check if JSON item is of type Array.
WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
FREERDP_API BOOL freerdp_settings_set_string(rdpSettings *settings, FreeRDP_Settings_Keys_String id, const char *param)
Sets a string settings value. The param is copied.
FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.
FREERDP_API const void * freerdp_settings_get_pointer(const rdpSettings *settings, FreeRDP_Settings_Keys_Pointer id)
Returns a immutable pointer settings value.
FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
FREERDP_API BOOL freerdp_settings_set_pointer_len(rdpSettings *settings, FreeRDP_Settings_Keys_Pointer id, const void *data, size_t len)
Set a pointer to value data.
FREERDP_API WCHAR * freerdp_settings_get_string_as_utf16(const rdpSettings *settings, FreeRDP_Settings_Keys_String id, size_t *pCharLen)
Return an allocated UTF16 string.
FREERDP_API BOOL freerdp_settings_set_uint32(rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 param)
Sets a UINT32 settings value.