FreeRDP
Loading...
Searching...
No Matches
arm.c
1
21#include <freerdp/config.h>
22#include <freerdp/version.h>
23
24#include "../settings.h"
25
26#include <winpr/assert.h>
27
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>
35
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>
41#include <freerdp/utils/aad.h>
42
43#include "arm.h"
44#include "wst.h"
45#include "websocket.h"
46#include "http.h"
47#include "../credssp_auth.h"
48#include "../proxy.h"
49#include "../rdp.h"
50#include "../../crypto/crypto.h"
51#include "../../crypto/certificate.h"
52#include "../../crypto/opensslcompat.h"
53#include "rpc_fault.h"
54#include "../utils.h"
55#include "../redirection.h"
56
57#include <winpr/json.h>
58
59#include <string.h>
60
61struct rdp_arm
62{
63 rdpContext* context;
64 rdpTls* tls;
65 HttpContext* http;
66
67 UINT32 gateway_retry;
68 wLog* log;
69};
70
71typedef struct rdp_arm rdpArm;
72
73#define TAG FREERDP_TAG("core.gateway.arm")
74
75#ifdef WITH_AAD
76static BOOL arm_tls_connect(rdpArm* arm, rdpTls* tls, UINT32 timeout)
77{
78 WINPR_ASSERT(arm);
79 WINPR_ASSERT(tls);
80 int sockfd = 0;
81 long status = 0;
82 BIO* socketBio = NULL;
83 BIO* bufferedBio = NULL;
84 rdpSettings* settings = arm->context->settings;
85 if (!settings)
86 return FALSE;
87
88 const char* peerHostname = freerdp_settings_get_string(settings, FreeRDP_GatewayHostname);
89 if (!peerHostname)
90 return FALSE;
91
92 UINT16 peerPort = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort);
93 const char* proxyUsername = NULL;
94 const char* proxyPassword = NULL;
95 BOOL isProxyConnection =
96 proxy_prepare(settings, &peerHostname, &peerPort, &proxyUsername, &proxyPassword);
97
98 sockfd = freerdp_tcp_connect(arm->context, peerHostname, peerPort, timeout);
99
100 WLog_Print(arm->log, WLOG_DEBUG, "connecting to %s %d", peerHostname, peerPort);
101 if (sockfd < 0)
102 return FALSE;
103
104 socketBio = BIO_new(BIO_s_simple_socket());
105
106 if (!socketBio)
107 {
108 closesocket((SOCKET)sockfd);
109 return FALSE;
110 }
111
112 BIO_set_fd(socketBio, sockfd, BIO_CLOSE);
113 bufferedBio = BIO_new(BIO_s_buffered_socket());
114
115 if (!bufferedBio)
116 {
117 BIO_free_all(socketBio);
118 return FALSE;
119 }
120
121 bufferedBio = BIO_push(bufferedBio, socketBio);
122 if (!bufferedBio)
123 return FALSE;
124
125 status = BIO_set_nonblock(bufferedBio, TRUE);
126
127 if (isProxyConnection)
128 {
129 if (!proxy_connect(arm->context, bufferedBio, proxyUsername, proxyPassword,
130 freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
131 (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort)))
132 {
133 BIO_free_all(bufferedBio);
134 return FALSE;
135 }
136 }
137
138 if (!status)
139 {
140 BIO_free_all(bufferedBio);
141 return FALSE;
142 }
143
144 tls->hostname = freerdp_settings_get_string(settings, FreeRDP_GatewayHostname);
145 tls->port = MIN(UINT16_MAX, WINPR_ASSERTING_INT_CAST(int32_t, settings->GatewayPort));
146 tls->isGatewayTransport = TRUE;
147 status = freerdp_tls_connect(tls, bufferedBio);
148 if (status < 1)
149 {
150 rdpContext* context = arm->context;
151 if (status < 0)
152 freerdp_set_last_error_if_not(context, FREERDP_ERROR_TLS_CONNECT_FAILED);
153 else
154 freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
155
156 return FALSE;
157 }
158 return (status >= 1);
159}
160
161static BOOL arm_fetch_wellknown(rdpArm* arm)
162{
163 WINPR_ASSERT(arm);
164 WINPR_ASSERT(arm->context);
165 WINPR_ASSERT(arm->context->rdp);
166
167 rdpRdp* rdp = arm->context->rdp;
168 if (rdp->wellknown)
169 return TRUE;
170
171 const char* base =
172 freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayAzureActiveDirectory);
173 const BOOL useTenant =
174 freerdp_settings_get_bool(arm->context->settings, FreeRDP_GatewayAvdUseTenantid);
175 const char* tenantid = "common";
176 if (useTenant)
177 tenantid =
178 freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayAvdAadtenantid);
179
180 rdp->wellknown = freerdp_utils_aad_get_wellknown(arm->log, base, tenantid);
181 return rdp->wellknown ? TRUE : FALSE;
182}
183
184static wStream* arm_build_http_request(rdpArm* arm, const char* method,
185 TRANSFER_ENCODING transferEncoding, const char* content_type,
186 size_t content_length)
187{
188 wStream* s = NULL;
189
190 WINPR_ASSERT(arm);
191 WINPR_ASSERT(method);
192 WINPR_ASSERT(content_type);
193
194 WINPR_ASSERT(arm->context);
195 WINPR_ASSERT(arm->context->rdp);
196
197 const char* uri = http_context_get_uri(arm->http);
198 HttpRequest* request = http_request_new();
199
200 if (!request)
201 return NULL;
202
203 rdpSettings* settings = arm->context->settings;
204
205 if (!http_request_set_method(request, method) || !http_request_set_uri(request, uri))
206 goto out;
207
208 if (!freerdp_settings_get_string(settings, FreeRDP_GatewayHttpExtAuthBearer))
209 {
210 char* token = NULL;
211
212 pGetCommonAccessToken GetCommonAccessToken = freerdp_get_common_access_token(arm->context);
213 if (!GetCommonAccessToken)
214 {
215 WLog_Print(arm->log, WLOG_ERROR, "No authorization token provided");
216 goto out;
217 }
218
219 if (!arm_fetch_wellknown(arm))
220 goto out;
221
222 if (!GetCommonAccessToken(arm->context, ACCESS_TOKEN_TYPE_AVD, &token, 0))
223 {
224 WLog_Print(arm->log, WLOG_ERROR, "Unable to obtain access token");
225 goto out;
226 }
227
228 if (!freerdp_settings_set_string(settings, FreeRDP_GatewayHttpExtAuthBearer, token))
229 {
230 free(token);
231 goto out;
232 }
233 free(token);
234 }
235
236 if (!http_request_set_auth_scheme(request, "Bearer") ||
237 !http_request_set_auth_param(
238 request, freerdp_settings_get_string(settings, FreeRDP_GatewayHttpExtAuthBearer)))
239 goto out;
240
241 if (!http_request_set_transfer_encoding(request, transferEncoding) ||
242 !http_request_set_content_length(request, content_length) ||
243 !http_request_set_content_type(request, content_type))
244 goto out;
245
246 s = http_request_write(arm->http, request);
247 if (!s)
248 goto out;
249
250 const char* referer = freerdp_settings_get_string(settings, FreeRDP_GatewayHttpReferer);
251 if (referer && (strlen(referer) > 0))
252 {
253 if (!http_request_append_header(s, "Referer", "%s", referer))
254 goto out;
255 }
256
257 const char* tenantId = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdAadtenantid);
258 if (!http_request_append_header(s, "x-ms-tenant-id", "%s", tenantId))
259 goto out;
260
261 const char* wvd = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdWvdEndpointPool);
262 if (!wvd)
263 goto out;
264
265 if (!http_request_append_header(s, "MS-WVD-Activity-Hint", "ms-wvd-hp:%s", wvd))
266 goto out;
267
268 const char* armpath = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdArmpath);
269 if (!armpath)
270 goto out;
271
272 char* value = crypto_base64url_encode((const BYTE*)armpath, strlen(armpath));
273 if (!value)
274 goto out;
275 const BOOL rc = http_request_append_header(s, "x-ms-opsarmpath64", "%s", value);
276 free(value);
277
278 if (!rc)
279 goto out;
280out:
281 http_request_free(request);
282
283 if (s)
284 Stream_SealLength(s);
285
286 return s;
287}
288
289static BOOL arm_send_http_request(rdpArm* arm, rdpTls* tls, const char* method,
290 const char* content_type, const char* data, size_t content_length)
291{
292 int status = -1;
293 wStream* s =
294 arm_build_http_request(arm, method, TransferEncodingIdentity, content_type, content_length);
295
296 if (!s)
297 return FALSE;
298
299 const size_t sz = Stream_Length(s);
300 status = freerdp_tls_write_all(tls, Stream_Buffer(s), sz);
301
302 Stream_Free(s, TRUE);
303 if (status >= 0 && content_length > 0 && data)
304 status = freerdp_tls_write_all(tls, (const BYTE*)data, content_length);
305
306 return (status >= 0);
307}
308
309static void arm_free(rdpArm* arm)
310{
311 if (!arm)
312 return;
313
314 freerdp_tls_free(arm->tls);
315 http_context_free(arm->http);
316
317 free(arm);
318}
319
320static rdpArm* arm_new(rdpContext* context)
321{
322 WINPR_ASSERT(context);
323
324 rdpArm* arm = (rdpArm*)calloc(1, sizeof(rdpArm));
325 if (!arm)
326 goto fail;
327
328 arm->log = WLog_Get(TAG);
329 arm->context = context;
330 arm->tls = freerdp_tls_new(context);
331 if (!arm->tls)
332 goto fail;
333
334 arm->http = http_context_new();
335
336 if (!arm->http)
337 goto fail;
338
339 return arm;
340
341fail:
342 arm_free(arm);
343 return NULL;
344}
345
346static char* arm_create_request_json(rdpArm* arm)
347{
348 char* lbi = NULL;
349 char* message = NULL;
350
351 WINPR_ASSERT(arm);
352
353 WINPR_JSON* json = WINPR_JSON_CreateObject();
354 if (!json)
355 goto arm_create_cleanup;
357 json, "application",
358 freerdp_settings_get_string(arm->context->settings, FreeRDP_RemoteApplicationProgram));
359
360 lbi = calloc(
361 freerdp_settings_get_uint32(arm->context->settings, FreeRDP_LoadBalanceInfoLength) + 1,
362 sizeof(char));
363 if (!lbi)
364 goto arm_create_cleanup;
365
366 const size_t len =
367 freerdp_settings_get_uint32(arm->context->settings, FreeRDP_LoadBalanceInfoLength);
368 memcpy(lbi, freerdp_settings_get_pointer(arm->context->settings, FreeRDP_LoadBalanceInfo), len);
369
370 WINPR_JSON_AddStringToObject(json, "loadBalanceInfo", lbi);
371 WINPR_JSON_AddNullToObject(json, "LogonToken");
372 WINPR_JSON_AddNullToObject(json, "gatewayLoadBalancerToken");
373
374 message = WINPR_JSON_PrintUnformatted(json);
375arm_create_cleanup:
376 if (json)
377 WINPR_JSON_Delete(json);
378 free(lbi);
379 return message;
380}
381
396static WINPR_CIPHER_CTX* treatAuthBlob(wLog* log, const BYTE* pbInput, size_t cbInput,
397 size_t* pBlockSize)
398{
399 WINPR_CIPHER_CTX* ret = NULL;
400 char algoName[100] = { 0 };
401
402 WINPR_ASSERT(pBlockSize);
403 SSIZE_T algoSz = ConvertWCharNToUtf8((const WCHAR*)pbInput, cbInput / sizeof(WCHAR), algoName,
404 sizeof(algoName) - 1);
405 if (algoSz <= 0)
406 {
407 WLog_Print(log, WLOG_ERROR, "invalid algoName");
408 return NULL;
409 }
410
411 if (strcmp(algoName, "AES") != 0)
412 {
413 WLog_Print(log, WLOG_ERROR, "only AES is supported for now");
414 return NULL;
415 }
416
417 *pBlockSize = WINPR_AES_BLOCK_SIZE;
418 const size_t algoLen = WINPR_ASSERTING_INT_CAST(size_t, (algoSz + 1)) * sizeof(WCHAR);
419 if (cbInput < algoLen)
420 {
421 WLog_Print(log, WLOG_ERROR, "invalid AuthBlob size");
422 return NULL;
423 }
424
425 cbInput -= algoLen;
426
427 /* BCRYPT_KEY_DATA_BLOB_HEADER */
428 wStream staticStream = { 0 };
429 wStream* s = Stream_StaticConstInit(&staticStream, &pbInput[algoLen], cbInput);
430
431 if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 12))
432 return NULL;
433
434 const UINT32 dwMagic = Stream_Get_UINT32(s);
435 if (dwMagic != BCRYPT_KEY_DATA_BLOB_MAGIC)
436 {
437 WLog_Print(log, WLOG_ERROR, "unsupported authBlob type");
438 return NULL;
439 }
440
441 const UINT32 dwVersion = Stream_Get_UINT32(s);
442 if (dwVersion != BCRYPT_KEY_DATA_BLOB_VERSION1)
443 {
444 WLog_Print(log, WLOG_ERROR, "unsupported authBlob version %" PRIu32 ", expecting %d",
445 dwVersion, BCRYPT_KEY_DATA_BLOB_VERSION1);
446 return NULL;
447 }
448
449 const UINT32 cbKeyData = Stream_Get_UINT32(s);
450 if (!Stream_CheckAndLogRequiredLengthWLog(log, s, cbKeyData))
451 {
452 WLog_Print(log, WLOG_ERROR, "invalid authBlob size");
453 return NULL;
454 }
455
456 WINPR_CIPHER_TYPE cipherType = 0;
457 switch (cbKeyData)
458 {
459 case 16:
460 cipherType = WINPR_CIPHER_AES_128_CBC;
461 break;
462 case 24:
463 cipherType = WINPR_CIPHER_AES_192_CBC;
464 break;
465 case 32:
466 cipherType = WINPR_CIPHER_AES_256_CBC;
467 break;
468 default:
469 WLog_Print(log, WLOG_ERROR, "invalid authBlob cipher size");
470 return NULL;
471 }
472
473 ret = winpr_Cipher_NewEx(cipherType, WINPR_ENCRYPT, Stream_Pointer(s), cbKeyData, NULL, 0);
474 if (!ret)
475 {
476 WLog_Print(log, WLOG_ERROR, "error creating cipher");
477 return NULL;
478 }
479
480 if (!winpr_Cipher_SetPadding(ret, TRUE))
481 {
482 WLog_Print(log, WLOG_ERROR, "unable to enable padding on cipher");
483 winpr_Cipher_Free(ret);
484 return NULL;
485 }
486
487 return ret;
488}
489
490static BOOL arm_stringEncodeW(const BYTE* pin, size_t cbIn, BYTE** ppOut, size_t* pcbOut)
491{
492 *ppOut = NULL;
493 *pcbOut = 0;
494
495 /* encode to base64 with crlf */
496 char* b64encoded = crypto_base64_encode_ex(pin, cbIn, TRUE);
497 if (!b64encoded)
498 return FALSE;
499
500 /* and then convert to Unicode */
501 size_t outSz = 0;
502 *ppOut = (BYTE*)ConvertUtf8NToWCharAlloc(b64encoded, strlen(b64encoded), &outSz);
503 free(b64encoded);
504
505 if (!*ppOut)
506 return FALSE;
507
508 *pcbOut = (outSz + 1) * sizeof(WCHAR);
509 return TRUE;
510}
511
512static BOOL arm_encodeRedirectPasswd(wLog* log, rdpSettings* settings, const rdpCertificate* cert,
513 WINPR_CIPHER_CTX* cipher, size_t blockSize)
514{
515 BOOL ret = FALSE;
516 BYTE* output = NULL;
517 BYTE* finalOutput = NULL;
518
519 /* let's prepare the encrypted password, first we do a
520 * cipheredPass = AES(redirectedAuthBlob, toUtf16(passwd))
521 */
522
523 size_t wpasswdLen = 0;
524 WCHAR* wpasswd = freerdp_settings_get_string_as_utf16(settings, FreeRDP_Password, &wpasswdLen);
525 if (!wpasswd)
526 {
527 WLog_Print(log, WLOG_ERROR, "error when converting password to UTF16");
528 return FALSE;
529 }
530
531 const size_t wpasswdBytes = (wpasswdLen + 1) * sizeof(WCHAR);
532 BYTE* encryptedPass = calloc(1, wpasswdBytes + blockSize); /* 16: block size of AES (padding) */
533
534 if (!encryptedPass)
535 goto out;
536
537 size_t encryptedPassLen = 0;
538 if (!winpr_Cipher_Update(cipher, wpasswd, wpasswdBytes, encryptedPass, &encryptedPassLen))
539 goto out;
540
541 if (encryptedPassLen > wpasswdBytes)
542 goto out;
543
544 size_t finalLen = 0;
545 if (!winpr_Cipher_Final(cipher, &encryptedPass[encryptedPassLen], &finalLen))
546 {
547 WLog_Print(log, WLOG_ERROR, "error when ciphering password");
548 goto out;
549 }
550 encryptedPassLen += finalLen;
551
552 /* then encrypt(cipheredPass, publicKey(redirectedServerCert) */
553 size_t output_length = 0;
554 if (!freerdp_certificate_publickey_encrypt(cert, encryptedPass, encryptedPassLen, &output,
555 &output_length))
556 {
557 WLog_Print(log, WLOG_ERROR, "unable to encrypt with the server's public key");
558 goto out;
559 }
560
561 size_t finalOutputLen = 0;
562 if (!arm_stringEncodeW(output, output_length, &finalOutput, &finalOutputLen))
563 {
564 WLog_Print(log, WLOG_ERROR, "unable to base64+utf16 final blob");
565 goto out;
566 }
567
568 if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionPassword, finalOutput,
569 finalOutputLen))
570 {
571 WLog_Print(log, WLOG_ERROR, "unable to set the redirection password in settings");
572 goto out;
573 }
574
575 settings->RdstlsSecurity = TRUE;
576 settings->AadSecurity = FALSE;
577 settings->NlaSecurity = FALSE;
578 settings->RdpSecurity = FALSE;
579 settings->TlsSecurity = FALSE;
580 settings->RedirectionFlags = LB_PASSWORD_IS_PK_ENCRYPTED;
581 ret = TRUE;
582out:
583 free(finalOutput);
584 free(output);
585 free(encryptedPass);
586 free(wpasswd);
587 return ret;
588}
589
594static BOOL arm_pick_base64Utf16Field(wLog* log, const WINPR_JSON* json, const char* name,
595 BYTE** poutput, size_t* plen)
596{
597 *poutput = NULL;
598 *plen = 0;
599
600 WINPR_JSON* node = WINPR_JSON_GetObjectItemCaseSensitive(json, name);
601 if (!node || !WINPR_JSON_IsString(node))
602 return TRUE;
603
604 const char* nodeValue = WINPR_JSON_GetStringValue(node);
605 if (!nodeValue)
606 return TRUE;
607
608 BYTE* output1 = NULL;
609 size_t len1 = 0;
610 crypto_base64_decode(nodeValue, strlen(nodeValue), &output1, &len1);
611 if (!output1 || !len1)
612 {
613 WLog_Print(log, WLOG_ERROR, "error when first unbase64 for %s", name);
614 free(output1);
615 return FALSE;
616 }
617
618 size_t len2 = 0;
619 char* output2 = ConvertWCharNToUtf8Alloc((WCHAR*)output1, len1 / sizeof(WCHAR), &len2);
620 free(output1);
621 if (!output2 || !len2)
622 {
623 WLog_Print(log, WLOG_ERROR, "error when decode('utf-16') for %s", name);
624 free(output2);
625 return FALSE;
626 }
627
628 BYTE* output = NULL;
629 crypto_base64_decode(output2, len2, &output, plen);
630 free(output2);
631 if (!output || !*plen)
632 {
633 WLog_Print(log, WLOG_ERROR, "error when second unbase64 for %s", name);
634 free(output);
635 return FALSE;
636 }
637
638 *poutput = output;
639 return TRUE;
640}
641
662static size_t arm_parse_ipvx_count(WINPR_JSON* ipvX)
663{
664 WINPR_ASSERT(ipvX);
665 WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItemCaseSensitive(ipvX, "ipAddress");
666 if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
667 return 0;
668
669 /* Each entry might contain a public and a private address */
670 return WINPR_JSON_GetArraySize(ipAddress) * 2;
671}
672
673static BOOL arm_parse_ipv6(rdpSettings* settings, WINPR_JSON* ipv6, size_t* pAddressIdx)
674{
675 WINPR_ASSERT(settings);
676 WINPR_ASSERT(ipv6);
677 WINPR_ASSERT(pAddressIdx);
678
679 if (!freerdp_settings_get_bool(settings, FreeRDP_IPv6Enabled))
680 return TRUE;
681
682 WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItemCaseSensitive(ipv6, "ipAddress");
683 if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
684 return TRUE;
685 const size_t naddresses = WINPR_JSON_GetArraySize(ipAddress);
686 const uint32_t count = freerdp_settings_get_uint32(settings, FreeRDP_TargetNetAddressCount);
687 for (size_t j = 0; j < naddresses; j++)
688 {
689 WINPR_JSON* adressN = WINPR_JSON_GetArrayItem(ipAddress, j);
690 if (!adressN || !WINPR_JSON_IsString(adressN))
691 continue;
692
693 const char* addr = WINPR_JSON_GetStringValue(adressN);
694 if (utils_str_is_empty(addr))
695 continue;
696
697 if (*pAddressIdx >= count)
698 {
699 WLog_ERR(TAG, "Exceeded TargetNetAddresses, parsing failed");
700 return FALSE;
701 }
702
703 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
704 (*pAddressIdx)++, addr))
705 return FALSE;
706 }
707 return TRUE;
708}
709
710static BOOL arm_parse_ipv4(rdpSettings* settings, WINPR_JSON* ipv4, size_t* pAddressIdx)
711{
712 WINPR_ASSERT(settings);
713 WINPR_ASSERT(ipv4);
714 WINPR_ASSERT(pAddressIdx);
715
716 WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItemCaseSensitive(ipv4, "ipAddress");
717 if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
718 return TRUE;
719
720 const size_t naddresses = WINPR_JSON_GetArraySize(ipAddress);
721 const uint32_t count = freerdp_settings_get_uint32(settings, FreeRDP_TargetNetAddressCount);
722 for (size_t j = 0; j < naddresses; j++)
723 {
724 WINPR_JSON* adressN = WINPR_JSON_GetArrayItem(ipAddress, j);
725 if (!adressN)
726 continue;
727
728 WINPR_JSON* publicIpNode =
729 WINPR_JSON_GetObjectItemCaseSensitive(adressN, "publicIpAddress");
730 if (publicIpNode && WINPR_JSON_IsString(publicIpNode))
731 {
732 const char* publicIp = WINPR_JSON_GetStringValue(publicIpNode);
733 if (!utils_str_is_empty(publicIp))
734 {
735 if (*pAddressIdx >= count)
736 {
737 WLog_ERR(TAG, "Exceeded TargetNetAddresses, parsing failed");
738 return FALSE;
739 }
740 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
741 (*pAddressIdx)++, publicIp))
742 return FALSE;
743 }
744 }
745
746 WINPR_JSON* privateIpNode =
747 WINPR_JSON_GetObjectItemCaseSensitive(adressN, "privateIpAddress");
748 if (privateIpNode && WINPR_JSON_IsString(privateIpNode))
749 {
750 const char* privateIp = WINPR_JSON_GetStringValue(privateIpNode);
751 if (!utils_str_is_empty(privateIp))
752 {
753 if (*pAddressIdx >= count)
754 {
755 WLog_ERR(TAG, "Exceeded TargetNetAddresses, parsing failed");
756 return FALSE;
757 }
758 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
759 (*pAddressIdx)++, privateIp))
760 return FALSE;
761 }
762 }
763 }
764 return TRUE;
765}
766
767static BOOL arm_treat_azureInstanceNetworkMetadata(wLog* log, const char* metadata,
768 rdpSettings* settings)
769{
770 BOOL ret = FALSE;
771
772 WINPR_ASSERT(settings);
773
774 if (!freerdp_target_net_adresses_reset(settings, 0))
775 return FALSE;
776
777 WINPR_JSON* json = WINPR_JSON_Parse(metadata);
778 if (!json)
779 {
780 WLog_Print(log, WLOG_ERROR, "invalid azureInstanceNetworkMetadata");
781 return FALSE;
782 }
783
784 WINPR_JSON* iface = WINPR_JSON_GetObjectItemCaseSensitive(json, "interface");
785 if (!iface)
786 {
787 ret = TRUE;
788 goto out;
789 }
790
791 if (!WINPR_JSON_IsArray(iface))
792 {
793 WLog_Print(log, WLOG_ERROR, "expecting interface to be an Array");
794 goto out;
795 }
796
797 size_t interfaceSz = WINPR_JSON_GetArraySize(iface);
798 if (interfaceSz == 0)
799 {
800 WLog_WARN(TAG, "no addresses in azure instance metadata");
801 ret = TRUE;
802 goto out;
803 }
804
805 size_t count = 0;
806 for (size_t i = 0; i < interfaceSz; i++)
807 {
808 WINPR_JSON* interN = WINPR_JSON_GetArrayItem(iface, i);
809 if (!interN)
810 continue;
811
812 WINPR_JSON* ipv6 = WINPR_JSON_GetObjectItemCaseSensitive(interN, "ipv6");
813 if (ipv6)
814 count += arm_parse_ipvx_count(ipv6);
815
816 WINPR_JSON* ipv4 = WINPR_JSON_GetObjectItemCaseSensitive(interN, "ipv4");
817 if (ipv4)
818 count += arm_parse_ipvx_count(ipv4);
819 }
820
821 if (!freerdp_target_net_adresses_reset(settings, count))
822 return FALSE;
823
824 size_t addressIdx = 0;
825 for (size_t i = 0; i < interfaceSz; i++)
826 {
827 WINPR_JSON* interN = WINPR_JSON_GetArrayItem(iface, i);
828 if (!interN)
829 continue;
830
831 WINPR_JSON* ipv6 = WINPR_JSON_GetObjectItemCaseSensitive(interN, "ipv6");
832 if (ipv6)
833 {
834 if (!arm_parse_ipv6(settings, ipv6, &addressIdx))
835 goto out;
836 }
837
838 WINPR_JSON* ipv4 = WINPR_JSON_GetObjectItemCaseSensitive(interN, "ipv4");
839 if (ipv4)
840 {
841 if (!arm_parse_ipv4(settings, ipv4, &addressIdx))
842 goto out;
843 }
844 }
845 if (addressIdx > UINT32_MAX)
846 goto out;
847
848 if (!freerdp_settings_set_uint32(settings, FreeRDP_TargetNetAddressCount, (UINT32)addressIdx))
849 goto out;
850
851 ret = addressIdx > 0;
852
853out:
854 WINPR_JSON_Delete(json);
855 return ret;
856}
857
858static void zfree(char* str)
859{
860 if (str)
861 {
862 char* cur = str;
863 while (*cur != '\0')
864 *cur++ = '\0';
865 }
866 free(str);
867}
868
869static BOOL arm_fill_rdstls(rdpArm* arm, rdpSettings* settings, const WINPR_JSON* json,
870 const rdpCertificate* redirectedServerCert)
871{
872 WINPR_ASSERT(arm);
873 BOOL ret = FALSE;
874 BYTE* authBlob = NULL;
875 WCHAR* wGUID = NULL;
876
877 const char* redirUser = freerdp_settings_get_string(settings, FreeRDP_RedirectionUsername);
878 if (redirUser)
879 {
880 if (!freerdp_settings_set_string(settings, FreeRDP_Username, redirUser))
881 goto end;
882 }
883
884 /* Azure/Entra requires the domain field to be set to 'AzureAD' in most cases.
885 * Some setups have been reported to require a different one, so only supply the suggested
886 * default if there was no other domain provided.
887 */
888 const char* redirDomain = freerdp_settings_get_string(settings, FreeRDP_Domain);
889 if (!redirDomain)
890 {
891 if (!freerdp_settings_set_string(settings, FreeRDP_Domain, "AzureAD"))
892 goto end;
893 }
894
895 const char* duser = freerdp_settings_get_string(settings, FreeRDP_Username);
896 const char* ddomain = freerdp_settings_get_string(settings, FreeRDP_Domain);
897 const char* dpwd = freerdp_settings_get_string(settings, FreeRDP_Password);
898 if (!duser || !dpwd)
899 {
900 WINPR_ASSERT(arm->context);
901 WINPR_ASSERT(arm->context->instance);
902
903 char* username = NULL;
904 char* password = NULL;
905 char* domain = NULL;
906
907 if (ddomain)
908 domain = _strdup(ddomain);
909 if (duser)
910 username = _strdup(duser);
911
912 const BOOL rc =
913 IFCALLRESULT(FALSE, arm->context->instance->AuthenticateEx, arm->context->instance,
914 &username, &password, &domain, AUTH_RDSTLS);
915
916 const BOOL rc1 = freerdp_settings_set_string(settings, FreeRDP_Username, username);
917 const BOOL rc2 = freerdp_settings_set_string(settings, FreeRDP_Password, password);
918 const BOOL rc3 = freerdp_settings_set_string(settings, FreeRDP_Domain, domain);
919 zfree(username);
920 zfree(password);
921 zfree(domain);
922 if (!rc || !rc1 || !rc2 || !rc3)
923 goto end;
924 }
925
926 /* redirectedAuthGuid */
927 WINPR_JSON* redirectedAuthGuidNode =
928 WINPR_JSON_GetObjectItemCaseSensitive(json, "redirectedAuthGuid");
929 if (!redirectedAuthGuidNode || !WINPR_JSON_IsString(redirectedAuthGuidNode))
930 goto end;
931
932 const char* redirectedAuthGuid = WINPR_JSON_GetStringValue(redirectedAuthGuidNode);
933 if (!redirectedAuthGuid)
934 goto end;
935
936 size_t wGUID_len = 0;
937 wGUID = ConvertUtf8ToWCharAlloc(redirectedAuthGuid, &wGUID_len);
938 if (!wGUID || (wGUID_len == 0))
939 {
940 WLog_Print(arm->log, WLOG_ERROR, "unable to allocate space for redirectedAuthGuid");
941 goto end;
942 }
943
944 const BOOL status = freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionGuid, wGUID,
945 (wGUID_len + 1) * sizeof(WCHAR));
946
947 if (!status)
948 {
949 WLog_Print(arm->log, WLOG_ERROR, "unable to set RedirectionGuid");
950 goto end;
951 }
952
953 /* redirectedAuthBlob */
954 size_t authBlobLen = 0;
955 if (!arm_pick_base64Utf16Field(arm->log, json, "redirectedAuthBlob", &authBlob, &authBlobLen))
956 goto end;
957
958 size_t blockSize = 0;
959 WINPR_CIPHER_CTX* cipher = treatAuthBlob(arm->log, authBlob, authBlobLen, &blockSize);
960 if (!cipher)
961 goto end;
962
963 const BOOL rerp =
964 arm_encodeRedirectPasswd(arm->log, settings, redirectedServerCert, cipher, blockSize);
965 winpr_Cipher_Free(cipher);
966 if (!rerp)
967 goto end;
968
969 ret = TRUE;
970
971end:
972 free(wGUID);
973 free(authBlob);
974 return ret;
975}
976
977static BOOL arm_fill_gateway_parameters(rdpArm* arm, const char* message, size_t len)
978{
979 WINPR_ASSERT(arm);
980 WINPR_ASSERT(arm->context);
981 WINPR_ASSERT(message);
982
983 rdpCertificate* redirectedServerCert = NULL;
984 WINPR_JSON* json = WINPR_JSON_ParseWithLength(message, len);
985 BOOL status = FALSE;
986 if (!json)
987 return FALSE;
988
989 rdpSettings* settings = arm->context->settings;
990 WINPR_JSON* gwurl = WINPR_JSON_GetObjectItemCaseSensitive(json, "gatewayLocation");
991 const char* gwurlstr = WINPR_JSON_GetStringValue(gwurl);
992 if (gwurlstr != NULL)
993 {
994 WLog_Print(arm->log, WLOG_DEBUG, "extracted target url %s", gwurlstr);
995 if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUrl, gwurlstr))
996 goto fail;
997 }
998
999 WINPR_JSON* serverNameNode =
1000 WINPR_JSON_GetObjectItemCaseSensitive(json, "redirectedServerName");
1001 if (serverNameNode)
1002 {
1003 const char* serverName = WINPR_JSON_GetStringValue(serverNameNode);
1004 if (serverName)
1005 {
1006 if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, serverName))
1007 goto fail;
1008 }
1009 }
1010
1011 {
1012 const char key[] = "redirectedUsername";
1013 if (WINPR_JSON_HasObjectItem(json, key))
1014 {
1015 const char* userName = NULL;
1016 WINPR_JSON* userNameNode = WINPR_JSON_GetObjectItemCaseSensitive(json, key);
1017 if (userNameNode)
1018 userName = WINPR_JSON_GetStringValue(userNameNode);
1019 if (!freerdp_settings_set_string(settings, FreeRDP_RedirectionUsername, userName))
1020 goto fail;
1021 }
1022 }
1023
1024 WINPR_JSON* azureMeta =
1025 WINPR_JSON_GetObjectItemCaseSensitive(json, "azureInstanceNetworkMetadata");
1026 if (azureMeta && WINPR_JSON_IsString(azureMeta))
1027 {
1028 if (!arm_treat_azureInstanceNetworkMetadata(arm->log, WINPR_JSON_GetStringValue(azureMeta),
1029 settings))
1030 {
1031 WLog_Print(arm->log, WLOG_ERROR, "error when treating azureInstanceNetworkMetadata");
1032 goto fail;
1033 }
1034 }
1035
1036 /* redirectedServerCert */
1037 size_t certLen = 0;
1038 BYTE* cert = NULL;
1039 if (arm_pick_base64Utf16Field(arm->log, json, "redirectedServerCert", &cert, &certLen))
1040 {
1041 const BOOL rc = rdp_redirection_read_target_cert(&redirectedServerCert, cert, certLen);
1042 free(cert);
1043 if (!rc)
1044 goto fail;
1045 else if (!rdp_set_target_certificate(settings, redirectedServerCert))
1046 goto fail;
1047 }
1048
1049 if (freerdp_settings_get_bool(settings, FreeRDP_AadSecurity))
1050 status = TRUE;
1051 else
1052 status = arm_fill_rdstls(arm, settings, json, redirectedServerCert);
1053
1054fail:
1055 WINPR_JSON_Delete(json);
1056 freerdp_certificate_free(redirectedServerCert);
1057 return status;
1058}
1059
1060static BOOL arm_handle_request_ok(rdpArm* arm, const HttpResponse* response)
1061{
1062 const size_t len = http_response_get_body_length(response);
1063 const char* msg = (const char*)http_response_get_body(response);
1064 if (strnlen(msg, len + 1) > len)
1065 return FALSE;
1066
1067 WLog_Print(arm->log, WLOG_DEBUG, "Got HTTP Response data: %s", msg);
1068 return arm_fill_gateway_parameters(arm, msg, len);
1069}
1070
1071static BOOL arm_handle_bad_request(rdpArm* arm, const HttpResponse* response, BOOL* retry)
1072{
1073 WINPR_ASSERT(response);
1074 WINPR_ASSERT(retry);
1075
1076 *retry = FALSE;
1077
1078 BOOL rc = FALSE;
1079
1080 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
1081
1082 const size_t len = http_response_get_body_length(response);
1083 const char* msg = (const char*)http_response_get_body(response);
1084 if (msg && (strnlen(msg, len + 1) > len))
1085 {
1086 WLog_Print(arm->log, WLOG_ERROR, "Got HTTP Response data, but length is invalid");
1087
1088 return FALSE;
1089 }
1090
1091 WLog_Print(arm->log, WLOG_DEBUG, "Got HTTP Response data: %s", msg);
1092
1093 WINPR_JSON* json = WINPR_JSON_ParseWithLength(msg, len);
1094 if (json == NULL)
1095 {
1096 const char* error_ptr = WINPR_JSON_GetErrorPtr();
1097 if (error_ptr != NULL)
1098 WLog_Print(arm->log, WLOG_ERROR, "NullPoException: %s", error_ptr);
1099
1100 return FALSE;
1101 }
1102 else
1103 {
1104 WINPR_JSON* gateway_code_obj = WINPR_JSON_GetObjectItemCaseSensitive(json, "Code");
1105 const char* gw_code_str = WINPR_JSON_GetStringValue(gateway_code_obj);
1106 if (gw_code_str == NULL)
1107 {
1108 WLog_Print(arm->log, WLOG_ERROR, "Response has no \"Code\" property");
1109 goto fail;
1110 }
1111
1112 if (strcmp(gw_code_str, "E_PROXY_ORCHESTRATION_LB_SESSIONHOST_DEALLOCATED") == 0)
1113 {
1114 *retry = TRUE;
1115 WINPR_JSON* message = WINPR_JSON_GetObjectItemCaseSensitive(json, "Message");
1116 const char* msgstr = WINPR_JSON_GetStringValue(message);
1117 if (!msgstr)
1118 WLog_WARN(TAG, "Starting your VM. It may take up to 5 minutes");
1119 else
1120 WLog_WARN(TAG, "%s", msgstr);
1121 freerdp_set_last_error_if_not(arm->context, FREERDP_ERROR_CONNECT_TARGET_BOOTING);
1122 }
1123 else
1124 {
1125 goto fail;
1126 }
1127 }
1128
1129 rc = TRUE;
1130fail:
1131 WINPR_JSON_Delete(json);
1132 return rc;
1133}
1134
1135static BOOL arm_handle_request(rdpArm* arm, BOOL* retry, DWORD timeout)
1136{
1137 WINPR_ASSERT(retry);
1138
1139 if (!arm_fetch_wellknown(arm))
1140 {
1141 *retry = TRUE;
1142 return FALSE;
1143 }
1144
1145 *retry = FALSE;
1146
1147 char* message = NULL;
1148 BOOL rc = FALSE;
1149
1150 HttpResponse* response = NULL;
1151 long StatusCode = 0;
1152
1153 const char* useragent =
1154 freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpUserAgent);
1155 const char* msuseragent =
1156 freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpMsUserAgent);
1157 if (!http_context_set_uri(arm->http, "/api/arm/v2/connections") ||
1158 !http_context_set_accept(arm->http, "application/json") ||
1159 !http_context_set_cache_control(arm->http, "no-cache") ||
1160 !http_context_set_pragma(arm->http, "no-cache") ||
1161 !http_context_set_connection(arm->http, "Keep-Alive") ||
1162 !http_context_set_user_agent(arm->http, useragent) ||
1163 !http_context_set_x_ms_user_agent(arm->http, msuseragent) ||
1164 !http_context_set_host(arm->http, freerdp_settings_get_string(arm->context->settings,
1165 FreeRDP_GatewayHostname)))
1166 goto arm_error;
1167
1168 if (!arm_tls_connect(arm, arm->tls, timeout))
1169 goto arm_error;
1170
1171 message = arm_create_request_json(arm);
1172 if (!message)
1173 goto arm_error;
1174
1175 if (!arm_send_http_request(arm, arm->tls, "POST", "application/json", message, strlen(message)))
1176 goto arm_error;
1177
1178 response = http_response_recv(arm->tls, TRUE);
1179 if (!response)
1180 goto arm_error;
1181
1182 StatusCode = http_response_get_status_code(response);
1183 if (StatusCode == HTTP_STATUS_OK)
1184 {
1185 if (!arm_handle_request_ok(arm, response))
1186 goto arm_error;
1187 }
1188 else if (StatusCode == HTTP_STATUS_BAD_REQUEST)
1189 {
1190 if (!arm_handle_bad_request(arm, response, retry))
1191 goto arm_error;
1192 }
1193 else
1194 {
1195 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
1196 goto arm_error;
1197 }
1198
1199 rc = TRUE;
1200arm_error:
1201 http_response_free(response);
1202 free(message);
1203 return rc;
1204}
1205
1206#endif
1207
1208BOOL arm_resolve_endpoint(wLog* log, rdpContext* context, DWORD timeout)
1209{
1210#ifndef WITH_AAD
1211 WLog_Print(log, WLOG_ERROR, "arm gateway support not compiled in");
1212 return FALSE;
1213#else
1214
1215 if (!context)
1216 return FALSE;
1217
1218 if (!context->settings)
1219 return FALSE;
1220
1221 if ((freerdp_settings_get_uint32(context->settings, FreeRDP_LoadBalanceInfoLength) == 0) ||
1222 (freerdp_settings_get_string(context->settings, FreeRDP_RemoteApplicationProgram) == NULL))
1223 {
1224 WLog_Print(log, WLOG_ERROR, "loadBalanceInfo and RemoteApplicationProgram needed");
1225 return FALSE;
1226 }
1227
1228 rdpArm* arm = arm_new(context);
1229 if (!arm)
1230 return FALSE;
1231
1232 BOOL retry = FALSE;
1233 BOOL rc = FALSE;
1234 do
1235 {
1236 if (retry && rc)
1237 {
1238 freerdp* instance = context->instance;
1239 WINPR_ASSERT(instance);
1240 SSIZE_T delay = IFCALLRESULT(-1, instance->RetryDialog, instance, "arm-transport",
1241 arm->gateway_retry, arm);
1242 arm->gateway_retry++;
1243 if (delay <= 0)
1244 break; /* error or no retry desired, abort loop */
1245 else
1246 {
1247 WLog_Print(arm->log, WLOG_DEBUG, "Delay for %" PRIdz "ms before next attempt",
1248 delay);
1249 while (delay > 0)
1250 {
1251 DWORD slp = (UINT32)delay;
1252 if (delay > UINT32_MAX)
1253 slp = UINT32_MAX;
1254 Sleep(slp);
1255 delay -= slp;
1256 }
1257 }
1258 }
1259 rc = arm_handle_request(arm, &retry, timeout);
1260
1261 } while (retry && rc);
1262 arm_free(arm);
1263 return rc;
1264#endif
1265}
WINPR_API BOOL WINPR_JSON_HasObjectItem(const WINPR_JSON *object, const char *string)
Check if JSON has an object matching the name.
Definition c-json.c:132
WINPR_API BOOL WINPR_JSON_IsString(const WINPR_JSON *item)
Check if JSON item is of type String.
Definition c-json.c:182
WINPR_API WINPR_JSON * WINPR_JSON_CreateObject(void)
WINPR_JSON_CreateObject.
Definition c-json.c:232
WINPR_API WINPR_JSON * WINPR_JSON_GetArrayItem(const WINPR_JSON *array, size_t index)
Return a pointer to an item in the array.
Definition c-json.c:108
WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItemCaseSensitive(const WINPR_JSON *object, const char *string)
Same as WINPR_JSON_GetObjectItem but with case sensitive matching.
Definition c-json.c:127
WINPR_API WINPR_JSON * WINPR_JSON_AddStringToObject(WINPR_JSON *object, const char *name, const char *string)
WINPR_JSON_AddStringToObject.
Definition c-json.c:269
WINPR_API WINPR_JSON * WINPR_JSON_ParseWithLength(const char *value, size_t buffer_length)
Parse a JSON string.
Definition c-json.c:98
WINPR_API char * WINPR_JSON_PrintUnformatted(WINPR_JSON *item)
Serialize a JSON instance to string without formatting for human readable formatted output see WINPR_...
Definition c-json.c:301
WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
Definition c-json.c:142
WINPR_API WINPR_JSON * WINPR_JSON_AddNullToObject(WINPR_JSON *object, const char *name)
WINPR_JSON_AddNullToObject.
Definition c-json.c:237
WINPR_API void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
Definition c-json.c:103
WINPR_API size_t WINPR_JSON_GetArraySize(const WINPR_JSON *array)
Get the number of arrayitems from an array.
Definition c-json.c:114
WINPR_API BOOL WINPR_JSON_IsArray(const WINPR_JSON *item)
Check if JSON item is of type Array.
Definition c-json.c:187
WINPR_API const char * WINPR_JSON_GetErrorPtr(void)
Return an error string.
Definition c-json.c:137
WINPR_API WINPR_JSON * WINPR_JSON_Parse(const char *value)
Parse a '\0' terminated JSON string.
Definition c-json.c:93
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 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 const void * freerdp_settings_get_pointer(const rdpSettings *settings, FreeRDP_Settings_Keys_Pointer id)
Returns a immutable pointer settings value.
FREERDP_API BOOL freerdp_settings_set_uint32(rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 param)
Sets a UINT32 settings value.
FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.