FreeRDP
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 
42 #include "arm.h"
43 #include "wst.h"
44 #include "websocket.h"
45 #include "http.h"
46 #include "../credssp_auth.h"
47 #include "../proxy.h"
48 #include "../rdp.h"
49 #include "../../crypto/crypto.h"
50 #include "../../crypto/certificate.h"
51 #include "../../crypto/opensslcompat.h"
52 #include "rpc_fault.h"
53 #include "../utils.h"
54 #include "../redirection.h"
55 
56 #include <winpr/json.h>
57 
58 #include <string.h>
59 
60 struct rdp_arm
61 {
62  rdpContext* context;
63  rdpTls* tls;
64  HttpContext* http;
65 
66  UINT32 gateway_retry;
67 };
68 
69 typedef struct rdp_arm rdpArm;
70 
71 #define TAG FREERDP_TAG("core.gateway.arm")
72 
73 #ifdef WITH_AAD
74 static BOOL arm_tls_connect(rdpArm* arm, rdpTls* tls, int timeout)
75 {
76  WINPR_ASSERT(arm);
77  WINPR_ASSERT(tls);
78  int sockfd = 0;
79  long status = 0;
80  BIO* socketBio = NULL;
81  BIO* bufferedBio = NULL;
82  rdpSettings* settings = arm->context->settings;
83  if (!settings)
84  return FALSE;
85 
86  const char* peerHostname = freerdp_settings_get_string(settings, FreeRDP_GatewayHostname);
87  if (!peerHostname)
88  return FALSE;
89 
90  UINT16 peerPort = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort);
91  const char* proxyUsername = NULL;
92  const char* proxyPassword = NULL;
93  BOOL isProxyConnection =
94  proxy_prepare(settings, &peerHostname, &peerPort, &proxyUsername, &proxyPassword);
95 
96  sockfd = freerdp_tcp_connect(arm->context, peerHostname, peerPort, timeout);
97 
98  WLog_DBG(TAG, "connecting to %s %d", peerHostname, peerPort);
99  if (sockfd < 0)
100  return FALSE;
101 
102  socketBio = BIO_new(BIO_s_simple_socket());
103 
104  if (!socketBio)
105  {
106  closesocket((SOCKET)sockfd);
107  return FALSE;
108  }
109 
110  BIO_set_fd(socketBio, sockfd, BIO_CLOSE);
111  bufferedBio = BIO_new(BIO_s_buffered_socket());
112 
113  if (!bufferedBio)
114  {
115  BIO_free_all(socketBio);
116  return FALSE;
117  }
118 
119  bufferedBio = BIO_push(bufferedBio, socketBio);
120  if (!bufferedBio)
121  return FALSE;
122 
123  status = BIO_set_nonblock(bufferedBio, TRUE);
124 
125  if (isProxyConnection)
126  {
127  if (!proxy_connect(arm->context, bufferedBio, proxyUsername, proxyPassword,
128  freerdp_settings_get_string(settings, FreeRDP_GatewayHostname),
129  (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort)))
130  {
131  BIO_free_all(bufferedBio);
132  return FALSE;
133  }
134  }
135 
136  if (!status)
137  {
138  BIO_free_all(bufferedBio);
139  return FALSE;
140  }
141 
142  tls->hostname = freerdp_settings_get_string(settings, FreeRDP_GatewayHostname);
143  tls->port = MIN(UINT16_MAX, settings->GatewayPort);
144  tls->isGatewayTransport = TRUE;
145  status = freerdp_tls_connect(tls, bufferedBio);
146  if (status < 1)
147  {
148  rdpContext* context = arm->context;
149  if (status < 0)
150  freerdp_set_last_error_if_not(context, FREERDP_ERROR_TLS_CONNECT_FAILED);
151  else
152  freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
153 
154  return FALSE;
155  }
156  return (status >= 1);
157 }
158 
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)
162 {
163  wStream* s = NULL;
164  HttpRequest* request = NULL;
165  const char* uri = NULL;
166 
167  WINPR_ASSERT(arm);
168  WINPR_ASSERT(method);
169  WINPR_ASSERT(content_type);
170 
171  WINPR_ASSERT(arm->context);
172 
173  freerdp* instance = arm->context->instance;
174  WINPR_ASSERT(instance);
175 
176  uri = http_context_get_uri(arm->http);
177  request = http_request_new();
178 
179  if (!request)
180  return NULL;
181 
182  if (!http_request_set_method(request, method) || !http_request_set_uri(request, uri))
183  goto out;
184 
185  if (!freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer))
186  {
187  char* token = NULL;
188 
189  if (!instance->GetAccessToken)
190  {
191  WLog_ERR(TAG, "No authorization token provided");
192  goto out;
193  }
194 
195  if (!instance->GetAccessToken(instance, ACCESS_TOKEN_TYPE_AVD, &token, 0))
196  {
197  WLog_ERR(TAG, "Unable to obtain access token");
198  goto out;
199  }
200 
201  if (!freerdp_settings_set_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer,
202  token))
203  {
204  free(token);
205  goto out;
206  }
207  free(token);
208  }
209 
210  if (!http_request_set_auth_scheme(request, "Bearer") ||
211  !http_request_set_auth_param(
212  request,
213  freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer)))
214  goto out;
215 
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))
219  goto out;
220 
221  s = http_request_write(arm->http, request);
222 out:
223  http_request_free(request);
224 
225  if (s)
226  Stream_SealLength(s);
227 
228  return s;
229 }
230 
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)
233 {
234  int status = -1;
235  wStream* s =
236  arm_build_http_request(arm, method, TransferEncodingIdentity, content_type, content_length);
237 
238  if (!s)
239  return FALSE;
240 
241  const size_t sz = Stream_Length(s);
242  status = freerdp_tls_write_all(tls, Stream_Buffer(s), sz);
243 
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);
247 
248  return (status >= 0);
249 }
250 
251 static void arm_free(rdpArm* arm)
252 {
253  if (!arm)
254  return;
255 
256  freerdp_tls_free(arm->tls);
257  http_context_free(arm->http);
258 
259  free(arm);
260 }
261 
262 static rdpArm* arm_new(rdpContext* context)
263 {
264  WINPR_ASSERT(context);
265 
266  rdpArm* arm = (rdpArm*)calloc(1, sizeof(rdpArm));
267  if (!arm)
268  goto fail;
269 
270  arm->context = context;
271  arm->tls = freerdp_tls_new(context);
272  if (!arm->tls)
273  goto fail;
274 
275  arm->http = http_context_new();
276 
277  if (!arm->http)
278  goto fail;
279 
280  return arm;
281 
282 fail:
283  arm_free(arm);
284  return NULL;
285 }
286 
287 static char* arm_create_request_json(rdpArm* arm)
288 {
289  char* lbi = NULL;
290  char* message = NULL;
291 
292  WINPR_ASSERT(arm);
293 
294  WINPR_JSON* json = WINPR_JSON_CreateObject();
295  if (!json)
296  goto arm_create_cleanup;
298  json, "application",
299  freerdp_settings_get_string(arm->context->settings, FreeRDP_RemoteApplicationProgram));
300 
301  lbi = calloc(
302  freerdp_settings_get_uint32(arm->context->settings, FreeRDP_LoadBalanceInfoLength) + 1,
303  sizeof(char));
304  if (!lbi)
305  goto arm_create_cleanup;
306 
307  const size_t len =
308  freerdp_settings_get_uint32(arm->context->settings, FreeRDP_LoadBalanceInfoLength);
309  memcpy(lbi, freerdp_settings_get_pointer(arm->context->settings, FreeRDP_LoadBalanceInfo), len);
310 
311  WINPR_JSON_AddStringToObject(json, "loadBalanceInfo", lbi);
312  WINPR_JSON_AddNullToObject(json, "LogonToken");
313  WINPR_JSON_AddNullToObject(json, "gatewayLoadBalancerToken");
314 
315  message = WINPR_JSON_PrintUnformatted(json);
316 arm_create_cleanup:
317  if (json)
318  WINPR_JSON_Delete(json);
319  free(lbi);
320  return message;
321 }
322 
337 static WINPR_CIPHER_CTX* treatAuthBlob(const BYTE* pbInput, size_t cbInput)
338 {
339  WINPR_CIPHER_CTX* ret = NULL;
340  char algoName[100] = { 0 };
341 
342  SSIZE_T algoSz = ConvertWCharNToUtf8((const WCHAR*)pbInput, cbInput / sizeof(WCHAR), algoName,
343  sizeof(algoName));
344  if (algoSz <= 0)
345  {
346  WLog_ERR(TAG, "invalid algoName");
347  return NULL;
348  }
349 
350  algoName[algoSz] = 0;
351  if (strcmp(algoName, "AES") != 0)
352  {
353  WLog_ERR(TAG, "only AES is supported for now");
354  return NULL;
355  }
356 
357  cbInput -= (algoSz + 1) * sizeof(WCHAR);
358 
359  if (cbInput < 12)
360  {
361  WLog_ERR(TAG, "invalid AuthBlob size");
362  return NULL;
363  }
364 
365  /* BCRYPT_KEY_DATA_BLOB_HEADER */
366  wStream staticStream = { 0 };
367  wStream* s =
368  Stream_StaticConstInit(&staticStream, pbInput + (algoSz + 1) * sizeof(WCHAR), cbInput);
369 
370  UINT32 dwMagic = 0;
371  Stream_Read_UINT32(s, dwMagic);
372 
373  if (dwMagic != BCRYPT_KEY_DATA_BLOB_MAGIC)
374  {
375  WLog_ERR(TAG, "unsupported authBlob type");
376  return NULL;
377  }
378 
379  UINT32 dwVersion = 0;
380  Stream_Read_UINT32(s, dwVersion);
381  if (dwVersion != BCRYPT_KEY_DATA_BLOB_VERSION1)
382  {
383  WLog_ERR(TAG, "unsupported authBlob version %d, expecting %d", dwVersion,
384  BCRYPT_KEY_DATA_BLOB_VERSION1);
385  return NULL;
386  }
387 
388  UINT32 cbKeyData = 0;
389  Stream_Read_UINT32(s, cbKeyData);
390  cbInput -= 12;
391 
392  if (cbKeyData > cbInput)
393  {
394  WLog_ERR(TAG, "invalid authBlob size");
395  return NULL;
396  }
397 
398  int cipherType = 0;
399  switch (cbKeyData)
400  {
401  case 16:
402  cipherType = WINPR_CIPHER_AES_128_CBC;
403  break;
404  case 24:
405  cipherType = WINPR_CIPHER_AES_192_CBC;
406  break;
407  case 32:
408  cipherType = WINPR_CIPHER_AES_256_CBC;
409  break;
410  default:
411  WLog_ERR(TAG, "invalid authBlob cipher size");
412  return NULL;
413  }
414 
415  ret = winpr_Cipher_NewEx(cipherType, WINPR_ENCRYPT, Stream_Pointer(s), cbKeyData, NULL, 0);
416  if (!ret)
417  {
418  WLog_ERR(TAG, "error creating cipher");
419  return NULL;
420  }
421 
422  if (!winpr_Cipher_SetPadding(ret, TRUE))
423  {
424  WLog_ERR(TAG, "unable to enable padding on cipher");
425  winpr_Cipher_Free(ret);
426  return NULL;
427  }
428 
429  return ret;
430 }
431 
432 static BOOL arm_stringEncodeW(const BYTE* pin, size_t cbIn, BYTE** ppOut, size_t* pcbOut)
433 {
434  *ppOut = NULL;
435  *pcbOut = 0;
436 
437  /* encode to base64 with crlf */
438  char* b64encoded = crypto_base64_encode_ex(pin, cbIn, TRUE);
439  if (!b64encoded)
440  return FALSE;
441 
442  /* and then convert to Unicode */
443  size_t outSz = 0;
444  *ppOut = (BYTE*)ConvertUtf8NToWCharAlloc(b64encoded, strlen(b64encoded), &outSz);
445  free(b64encoded);
446 
447  if (!*ppOut)
448  return FALSE;
449 
450  *pcbOut = (outSz + 1) * sizeof(WCHAR);
451  return TRUE;
452 }
453 
454 static BOOL arm_encodeRedirectPasswd(rdpSettings* settings, const rdpCertificate* cert,
455  WINPR_CIPHER_CTX* cipher)
456 {
457  BOOL ret = FALSE;
458  BYTE* output = NULL;
459  BYTE* finalOutput = NULL;
460 
461  /* let's prepare the encrypted password, first we do a
462  * cipheredPass = AES(redirectedAuthBlob, toUtf16(passwd))
463  */
464 
465  size_t wpasswdLen = 0;
466  WCHAR* wpasswd = freerdp_settings_get_string_as_utf16(settings, FreeRDP_Password, &wpasswdLen);
467  if (!wpasswd)
468  {
469  WLog_ERR(TAG, "error when converting password to UTF16");
470  return FALSE;
471  }
472 
473  size_t wpasswdBytes = (wpasswdLen + 1) * sizeof(WCHAR);
474  BYTE* encryptedPass = calloc(1, wpasswdBytes + 16); /* 16: block size of AES (padding) */
475  size_t encryptedPassLen = 0;
476  size_t finalLen = 0;
477  if (!encryptedPass ||
478  !winpr_Cipher_Update(cipher, wpasswd, wpasswdBytes, encryptedPass, &encryptedPassLen) ||
479  !winpr_Cipher_Final(cipher, encryptedPass + encryptedPassLen, &finalLen))
480  {
481  WLog_ERR(TAG, "error when ciphering password");
482  goto out;
483  }
484  encryptedPassLen += finalLen;
485 
486  /* then encrypt(cipheredPass, publicKey(redirectedServerCert) */
487  size_t output_length = 0;
488 
489  if (!freerdp_certificate_publickey_encrypt(cert, encryptedPass, encryptedPassLen, &output,
490  &output_length))
491  {
492  WLog_ERR(TAG, "unable to encrypt with the server's public key");
493  goto out;
494  }
495 
496  size_t finalOutputLen = 0;
497  if (!arm_stringEncodeW(output, output_length, &finalOutput, &finalOutputLen))
498  {
499  WLog_ERR(TAG, "unable to base64+utf16 final blob");
500  goto out;
501  }
502 
503  if (!freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionPassword, finalOutput,
504  finalOutputLen))
505  {
506  WLog_ERR(TAG, "unable to set the redirection password in settings");
507  goto out;
508  }
509 
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;
516  ret = TRUE;
517 out:
518  free(finalOutput);
519  free(output);
520  free(encryptedPass);
521  free(wpasswd);
522  return ret;
523 }
524 
529 static BOOL arm_pick_base64Utf16Field(const WINPR_JSON* json, const char* name, BYTE** poutput,
530  size_t* plen)
531 {
532  *poutput = NULL;
533  *plen = 0;
534 
535  WINPR_JSON* node = WINPR_JSON_GetObjectItemCaseSensitive(json, name);
536  if (!node || !WINPR_JSON_IsString(node))
537  return TRUE;
538 
539  const char* nodeValue = WINPR_JSON_GetStringValue(node);
540  if (!nodeValue)
541  return TRUE;
542 
543  BYTE* output1 = NULL;
544  size_t len1 = 0;
545  crypto_base64_decode(nodeValue, strlen(nodeValue), &output1, &len1);
546  if (!output1 || !len1)
547  {
548  WLog_ERR(TAG, "error when first unbase64 for %s", name);
549  free(output1);
550  return FALSE;
551  }
552 
553  size_t len2 = 0;
554  char* output2 = ConvertWCharNToUtf8Alloc((WCHAR*)output1, len1 / sizeof(WCHAR), &len2);
555  free(output1);
556  if (!output2 || !len2)
557  {
558  WLog_ERR(TAG, "error when decode('utf-16') for %s", name);
559  free(output2);
560  return FALSE;
561  }
562 
563  BYTE* output = NULL;
564  crypto_base64_decode(output2, len2, &output, plen);
565  free(output2);
566  if (!output || !*plen)
567  {
568  WLog_ERR(TAG, "error when second unbase64 for %s", name);
569  free(output);
570  return FALSE;
571  }
572 
573  *poutput = output;
574  return TRUE;
575 }
576 
597 static size_t arm_parse_ipvx_count(WINPR_JSON* ipvX)
598 {
599  WINPR_ASSERT(ipvX);
600  WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItem(ipvX, "ipAddress");
601  if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
602  return 0;
603  return WINPR_JSON_GetArraySize(ipAddress);
604 }
605 
606 static BOOL arm_parse_ipv6(rdpSettings* settings, WINPR_JSON* ipv6, size_t* pAddressIdx)
607 {
608  WINPR_ASSERT(settings);
609  WINPR_ASSERT(ipv6);
610  WINPR_ASSERT(pAddressIdx);
611 
612  if (!freerdp_settings_get_bool(settings, FreeRDP_IPv6Enabled))
613  return TRUE;
614 
615  WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItem(ipv6, "ipAddress");
616  if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
617  return TRUE;
618  const size_t naddresses = WINPR_JSON_GetArraySize(ipAddress);
619  for (size_t j = 0; j < naddresses; j++)
620  {
621  WINPR_JSON* adressN = WINPR_JSON_GetArrayItem(ipAddress, j);
622  if (!adressN || !WINPR_JSON_IsString(adressN))
623  continue;
624 
625  const char* addr = WINPR_JSON_GetStringValue(adressN);
626  if (utils_str_is_empty(addr))
627  continue;
628  if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
629  (*pAddressIdx)++, addr))
630  return FALSE;
631  }
632  return TRUE;
633 }
634 
635 static BOOL arm_parse_ipv4(rdpSettings* settings, WINPR_JSON* ipv4, size_t* pAddressIdx)
636 {
637  WINPR_ASSERT(settings);
638  WINPR_ASSERT(ipv4);
639  WINPR_ASSERT(pAddressIdx);
640 
641  WINPR_JSON* ipAddress = WINPR_JSON_GetObjectItem(ipv4, "ipAddress");
642  if (!ipAddress || !WINPR_JSON_IsArray(ipAddress))
643  return TRUE;
644 
645  const size_t naddresses = WINPR_JSON_GetArraySize(ipAddress);
646  for (size_t j = 0; j < naddresses; j++)
647  {
648  WINPR_JSON* adressN = WINPR_JSON_GetArrayItem(ipAddress, j);
649  if (!adressN)
650  continue;
651 
652  WINPR_JSON* publicIpNode = WINPR_JSON_GetObjectItem(adressN, "publicIpAddress");
653  if (publicIpNode && WINPR_JSON_IsString(publicIpNode))
654  {
655  const char* publicIp = WINPR_JSON_GetStringValue(publicIpNode);
656  if (!utils_str_is_empty(publicIp))
657  {
658  if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
659  (*pAddressIdx)++, publicIp))
660  return FALSE;
661  }
662  }
663 
664  WINPR_JSON* privateIpNode = WINPR_JSON_GetObjectItem(adressN, "privateIpAddress");
665  if (privateIpNode && WINPR_JSON_IsString(privateIpNode))
666  {
667  const char* privateIp = WINPR_JSON_GetStringValue(privateIpNode);
668  if (!utils_str_is_empty(privateIp))
669  {
670  if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
671  (*pAddressIdx)++, privateIp))
672  return FALSE;
673  }
674  }
675  }
676  return TRUE;
677 }
678 
679 static BOOL arm_treat_azureInstanceNetworkMetadata(const char* metadata, rdpSettings* settings)
680 {
681  BOOL ret = FALSE;
682 
683  WINPR_ASSERT(settings);
684 
685  if (!freerdp_target_net_adresses_reset(settings, 0))
686  return FALSE;
687 
688  WINPR_JSON* json = WINPR_JSON_Parse(metadata);
689  if (!json)
690  {
691  WLog_ERR(TAG, "invalid azureInstanceNetworkMetadata");
692  return FALSE;
693  }
694 
695  WINPR_JSON* iface = WINPR_JSON_GetObjectItem(json, "interface");
696  if (!iface)
697  {
698  ret = TRUE;
699  goto out;
700  }
701 
702  if (!WINPR_JSON_IsArray(iface))
703  {
704  WLog_ERR(TAG, "expecting interface to be an Array");
705  goto out;
706  }
707 
708  size_t interfaceSz = WINPR_JSON_GetArraySize(iface);
709  if (interfaceSz == 0)
710  {
711  WLog_WARN(TAG, "no addresses in azure instance metadata");
712  ret = TRUE;
713  goto out;
714  }
715 
716  size_t count = 0;
717  for (size_t i = 0; i < interfaceSz; i++)
718  {
719  WINPR_JSON* interN = WINPR_JSON_GetArrayItem(iface, i);
720  if (!interN)
721  continue;
722 
723  WINPR_JSON* ipv6 = WINPR_JSON_GetObjectItem(interN, "ipv6");
724  if (ipv6)
725  count += arm_parse_ipvx_count(ipv6);
726 
727  WINPR_JSON* ipv4 = WINPR_JSON_GetObjectItem(interN, "ipv4");
728  if (ipv4)
729  count += arm_parse_ipvx_count(ipv4);
730  }
731 
732  if (!freerdp_target_net_adresses_reset(settings, count))
733  return FALSE;
734 
735  size_t addressIdx = 0;
736  for (size_t i = 0; i < interfaceSz; i++)
737  {
738  WINPR_JSON* interN = WINPR_JSON_GetArrayItem(iface, i);
739  if (!interN)
740  continue;
741 
742  WINPR_JSON* ipv6 = WINPR_JSON_GetObjectItem(interN, "ipv6");
743  if (ipv6)
744  {
745  if (!arm_parse_ipv6(settings, ipv6, &addressIdx))
746  goto out;
747  }
748 
749  WINPR_JSON* ipv4 = WINPR_JSON_GetObjectItem(interN, "ipv4");
750  if (ipv4)
751  {
752  if (!arm_parse_ipv4(settings, ipv4, &addressIdx))
753  goto out;
754  }
755  }
756  if (addressIdx > UINT32_MAX)
757  goto out;
758 
759  if (!freerdp_settings_set_uint32(settings, FreeRDP_TargetNetAddressCount, (UINT32)addressIdx))
760  goto out;
761 
762  ret = freerdp_settings_get_uint32(settings, FreeRDP_TargetNetAddressCount) > 0;
763 
764 out:
765  WINPR_JSON_Delete(json);
766  return ret;
767 }
768 
769 static BOOL arm_fill_rdstls(rdpArm* arm, rdpSettings* settings, const WINPR_JSON* json)
770 {
771  BOOL ret = TRUE;
772  BYTE* cert = NULL;
773  BYTE* authBlob = NULL;
774  rdpCertificate* redirectedServerCert = NULL;
775 
776  do
777  {
778  /* redirectedAuthGuid */
779  WINPR_JSON* redirectedAuthGuidNode =
780  WINPR_JSON_GetObjectItemCaseSensitive(json, "redirectedAuthGuid");
781  if (!redirectedAuthGuidNode || !WINPR_JSON_IsString(redirectedAuthGuidNode))
782  break;
783 
784  const char* redirectedAuthGuid = WINPR_JSON_GetStringValue(redirectedAuthGuidNode);
785  if (!redirectedAuthGuid)
786  break;
787 
788  WCHAR wGUID[72] = {
789  0
790  }; /* A GUID string is between 32 and 68 characters as string, depending on representation.
791  Add a few extra bytes for braces et al */
792  const SSIZE_T wGUID_len = ConvertUtf8ToWChar(redirectedAuthGuid, wGUID, ARRAYSIZE(wGUID));
793  if (wGUID_len < 0)
794  {
795  WLog_ERR(TAG, "unable to allocate space for redirectedAuthGuid");
796  ret = FALSE;
797  goto endOfFunction;
798  }
799 
800  BOOL status = freerdp_settings_set_pointer_len(settings, FreeRDP_RedirectionGuid, wGUID,
801  (wGUID_len + 1) * sizeof(WCHAR));
802  if (!status)
803  {
804  WLog_ERR(TAG, "unable to set RedirectionGuid");
805  ret = FALSE;
806  goto endOfFunction;
807  }
808 
809  /* redirectedServerCert */
810  size_t certLen = 0;
811  if (!arm_pick_base64Utf16Field(json, "redirectedServerCert", &cert, &certLen))
812  break;
813 
814  if (!rdp_redirection_read_target_cert(&redirectedServerCert, cert, certLen))
815  break;
816 
817  /* redirectedAuthBlob */
818  size_t authBlobLen = 0;
819  if (!arm_pick_base64Utf16Field(json, "redirectedAuthBlob", &authBlob, &authBlobLen))
820  break;
821 
822  WINPR_CIPHER_CTX* cipher = treatAuthBlob(authBlob, authBlobLen);
823  if (!cipher)
824  break;
825 
826  const BOOL rerp = arm_encodeRedirectPasswd(settings, redirectedServerCert, cipher);
827  winpr_Cipher_Free(cipher);
828  if (!rerp)
829  break;
830 
831  ret = TRUE;
832  } while (FALSE);
833 
834  free(cert);
835  freerdp_certificate_free(redirectedServerCert);
836  free(authBlob);
837 
838 endOfFunction:
839  return ret;
840 }
841 
842 static BOOL arm_fill_gateway_parameters(rdpArm* arm, const char* message, size_t len)
843 {
844  WINPR_ASSERT(arm);
845  WINPR_ASSERT(arm->context);
846  WINPR_ASSERT(message);
847 
848  WINPR_JSON* json = WINPR_JSON_ParseWithLength(message, len);
849  BOOL status = FALSE;
850  if (!json)
851  return FALSE;
852 
853  rdpSettings* settings = arm->context->settings;
854  WINPR_JSON* gwurl = WINPR_JSON_GetObjectItemCaseSensitive(json, "gatewayLocation");
855  const char* gwurlstr = WINPR_JSON_GetStringValue(gwurl);
856  if (gwurlstr != NULL)
857  {
858  WLog_DBG(TAG, "extracted target url %s", gwurlstr);
859  if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUrl, gwurlstr))
860  status = FALSE;
861  else
862  status = TRUE;
863  }
864 
865  WINPR_JSON* serverNameNode = WINPR_JSON_GetObjectItem(json, "redirectedServerName");
866  if (serverNameNode)
867  {
868  const char* serverName = WINPR_JSON_GetStringValue(serverNameNode);
869  if (serverName)
870  status = freerdp_settings_set_string(settings, FreeRDP_ServerHostname, serverName);
871  }
872 
873  WINPR_JSON* azureMeta = WINPR_JSON_GetObjectItem(json, "azureInstanceNetworkMetadata");
874  if (azureMeta && WINPR_JSON_IsString(azureMeta))
875  {
876  if (!arm_treat_azureInstanceNetworkMetadata(WINPR_JSON_GetStringValue(azureMeta), settings))
877  {
878  WLog_ERR(TAG, "error when treating azureInstanceNetworkMetadata");
879  }
880  }
881 
882  if (freerdp_settings_get_string(settings, FreeRDP_Password))
883  {
884  /* note: we retrieve some more fields for RDSTLS only if we have a password provided by the
885  * user, otherwise these are useless: we will not be able to do RDSTLS
886  */
887  status = arm_fill_rdstls(arm, settings, json);
888  }
889 
890  WINPR_JSON_Delete(json);
891  return status;
892 }
893 
894 static BOOL arm_handle_request_ok(rdpArm* arm, const HttpResponse* response)
895 {
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)
899  return FALSE;
900 
901  WLog_DBG(TAG, "Got HTTP Response data: %s", msg);
902  return arm_fill_gateway_parameters(arm, msg, len);
903 }
904 
905 static BOOL arm_handle_bad_request(rdpArm* arm, const HttpResponse* response, BOOL* retry)
906 {
907  WINPR_ASSERT(response);
908  WINPR_ASSERT(retry);
909 
910  *retry = FALSE;
911 
912  BOOL rc = FALSE;
913 
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)
917  return FALSE;
918 
919  WLog_DBG(TAG, "Got HTTP Response data: %s", msg);
920 
921  WINPR_JSON* json = WINPR_JSON_ParseWithLength(msg, len);
922  if (json == NULL)
923  {
924  const char* error_ptr = WINPR_JSON_GetErrorPtr();
925  if (error_ptr != NULL)
926  WLog_ERR(TAG, "NullPoException: %s", error_ptr);
927 
928  return FALSE;
929  }
930 
931  WINPR_JSON* gateway_code_obj = WINPR_JSON_GetObjectItemCaseSensitive(json, "Code");
932  const char* gw_code_str = WINPR_JSON_GetStringValue(gateway_code_obj);
933  if (gw_code_str == NULL)
934  {
935  WLog_ERR(TAG, "Response has no \"Code\" property");
936  http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
937  goto fail;
938  }
939 
940  if (strcmp(gw_code_str, "E_PROXY_ORCHESTRATION_LB_SESSIONHOST_DEALLOCATED") == 0)
941  {
942  *retry = TRUE;
943  WINPR_JSON* message = WINPR_JSON_GetObjectItemCaseSensitive(json, "Message");
944  const char* msgstr = WINPR_JSON_GetStringValue(message);
945  if (!msgstr)
946  WLog_WARN(TAG, "Starting your VM. It may take up to 5 minutes");
947  else
948  WLog_WARN(TAG, "%s", msgstr);
949  }
950  else
951  {
952  http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
953  goto fail;
954  }
955 
956  rc = TRUE;
957 fail:
958  WINPR_JSON_Delete(json);
959  return rc;
960 }
961 
962 static BOOL arm_handle_request(rdpArm* arm, BOOL* retry, DWORD timeout)
963 {
964  WINPR_ASSERT(retry);
965 
966  *retry = FALSE;
967 
968  char* message = NULL;
969  BOOL rc = FALSE;
970 
971  HttpResponse* response = NULL;
972  long StatusCode = 0;
973 
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) ||
981  !http_context_set_host(arm->http, freerdp_settings_get_string(arm->context->settings,
982  FreeRDP_GatewayHostname)))
983  goto arm_error;
984 
985  if (!arm_tls_connect(arm, arm->tls, timeout))
986  goto arm_error;
987 
988  message = arm_create_request_json(arm);
989  if (!message)
990  goto arm_error;
991 
992  if (!arm_send_http_request(arm, arm->tls, "POST", "application/json", message, strlen(message)))
993  goto arm_error;
994 
995  response = http_response_recv(arm->tls, TRUE);
996  if (!response)
997  goto arm_error;
998 
999  StatusCode = http_response_get_status_code(response);
1000  if (StatusCode == HTTP_STATUS_OK)
1001  {
1002  if (!arm_handle_request_ok(arm, response))
1003  goto arm_error;
1004  }
1005  else if (StatusCode == HTTP_STATUS_BAD_REQUEST)
1006  {
1007  if (!arm_handle_bad_request(arm, response, retry))
1008  goto arm_error;
1009  }
1010  else
1011  {
1012  http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
1013  goto arm_error;
1014  }
1015 
1016  rc = TRUE;
1017 arm_error:
1018  http_response_free(response);
1019  free(message);
1020  return rc;
1021 }
1022 
1023 #endif
1024 
1025 BOOL arm_resolve_endpoint(rdpContext* context, DWORD timeout)
1026 {
1027 #ifndef WITH_AAD
1028  WLog_ERR(TAG, "arm gateway support not compiled in");
1029  return FALSE;
1030 #else
1031 
1032  if (!context)
1033  return FALSE;
1034 
1035  if (!context->settings)
1036  return FALSE;
1037 
1038  if ((freerdp_settings_get_uint32(context->settings, FreeRDP_LoadBalanceInfoLength) == 0) ||
1039  (freerdp_settings_get_string(context->settings, FreeRDP_RemoteApplicationProgram) == NULL))
1040  {
1041  WLog_ERR(TAG, "loadBalanceInfo and RemoteApplicationProgram needed");
1042  return FALSE;
1043  }
1044 
1045  rdpArm* arm = arm_new(context);
1046  if (!arm)
1047  return FALSE;
1048 
1049  BOOL retry = FALSE;
1050  BOOL rc = FALSE;
1051  do
1052  {
1053  if (retry && rc)
1054  {
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++;
1060  if (delay <= 0)
1061  break; /* error or no retry desired, abort loop */
1062  else
1063  {
1064  WLog_DBG(TAG, "Delay for %" PRIdz "ms before next attempt", delay);
1065  while (delay > 0)
1066  {
1067  DWORD slp = (UINT32)delay;
1068  if (delay > UINT32_MAX)
1069  slp = UINT32_MAX;
1070  Sleep(slp);
1071  delay -= slp;
1072  }
1073  }
1074  }
1075  rc = arm_handle_request(arm, &retry, timeout);
1076 
1077  } while (retry && rc);
1078  arm_free(arm);
1079  return rc;
1080 #endif
1081 }
WINPR_API WINPR_JSON * WINPR_JSON_ParseWithLength(const char *value, size_t buffer_length)
Parse a JSON string.
Definition: json.c:125
WINPR_API WINPR_JSON * WINPR_JSON_AddStringToObject(WINPR_JSON *object, const char *name, const char *string)
WINPR_JSON_AddStringToObject.
Definition: json.c:573
WINPR_API BOOL WINPR_JSON_IsString(const WINPR_JSON *item)
Check if JSON item is of type String.
Definition: json.c:349
WINPR_API WINPR_JSON * WINPR_JSON_Parse(const char *value)
Parse a '\0' terminated JSON string.
Definition: json.c:113
WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItemCaseSensitive(const WINPR_JSON *object, const char *string)
Same as WINPR_JSON_GetObjectItem but with case insensitive matching.
Definition: json.c:197
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: json.c:662
WINPR_API WINPR_JSON * WINPR_JSON_AddNullToObject(WINPR_JSON *object, const char *name)
WINPR_JSON_AddNullToObject.
Definition: json.c:476
WINPR_API WINPR_JSON * WINPR_JSON_GetArrayItem(const WINPR_JSON *array, size_t index)
Return a pointer to an item in the array.
Definition: json.c:155
WINPR_API const char * WINPR_JSON_GetErrorPtr(void)
Return an error string.
Definition: json.c:223
WINPR_API void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
Definition: json.c:144
WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItem(const WINPR_JSON *object, const char *string)
Return a pointer to an JSON object item.
Definition: json.c:184
WINPR_API WINPR_JSON * WINPR_JSON_CreateObject(void)
WINPR_JSON_CreateObject.
Definition: json.c:465
WINPR_API size_t WINPR_JSON_GetArraySize(const WINPR_JSON *array)
Get the number of arrayitems from an array.
Definition: json.c:169
WINPR_API BOOL WINPR_JSON_IsArray(const WINPR_JSON *item)
Check if JSON item is of type Array.
Definition: json.c:361
WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
Definition: json.c:234
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.