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>
41 #include <freerdp/utils/aad.h>
45 #include "websocket.h"
47 #include "../credssp_auth.h"
50 #include "../../crypto/crypto.h"
51 #include "../../crypto/certificate.h"
52 #include "../../crypto/opensslcompat.h"
53 #include "rpc_fault.h"
55 #include "../redirection.h"
57 #include <winpr/json.h>
71 typedef struct rdp_arm rdpArm;
73 #define TAG FREERDP_TAG("core.gateway.arm")
76 static BOOL arm_tls_connect(rdpArm* arm, rdpTls* tls, UINT32 timeout)
82 BIO* socketBio = NULL;
83 BIO* bufferedBio = NULL;
84 rdpSettings* settings = arm->context->settings;
93 const char* proxyUsername = NULL;
94 const char* proxyPassword = NULL;
95 BOOL isProxyConnection =
96 proxy_prepare(settings, &peerHostname, &peerPort, &proxyUsername, &proxyPassword);
98 sockfd = freerdp_tcp_connect(arm->context, peerHostname, peerPort, timeout);
100 WLog_Print(arm->log, WLOG_DEBUG,
"connecting to %s %d", peerHostname, peerPort);
104 socketBio = BIO_new(BIO_s_simple_socket());
108 closesocket((SOCKET)sockfd);
112 BIO_set_fd(socketBio, sockfd, BIO_CLOSE);
113 bufferedBio = BIO_new(BIO_s_buffered_socket());
117 BIO_free_all(socketBio);
121 bufferedBio = BIO_push(bufferedBio, socketBio);
125 status = BIO_set_nonblock(bufferedBio, TRUE);
127 if (isProxyConnection)
129 if (!proxy_connect(arm->context, bufferedBio, proxyUsername, proxyPassword,
133 BIO_free_all(bufferedBio);
140 BIO_free_all(bufferedBio);
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);
150 rdpContext* context = arm->context;
152 freerdp_set_last_error_if_not(context, FREERDP_ERROR_TLS_CONNECT_FAILED);
154 freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
158 return (status >= 1);
161 static BOOL arm_fetch_wellknown(rdpArm* arm)
164 WINPR_ASSERT(arm->context);
165 WINPR_ASSERT(arm->context->rdp);
167 rdpRdp* rdp = arm->context->rdp;
173 const BOOL useTenant =
175 const char* tenantid =
"common";
180 rdp->wellknown = freerdp_utils_aad_get_wellknown(arm->log, base, tenantid);
181 return rdp->wellknown ? TRUE : FALSE;
184 static wStream* arm_build_http_request(rdpArm* arm,
const char* method,
185 TRANSFER_ENCODING transferEncoding,
const char* content_type,
186 size_t content_length)
189 HttpRequest* request = NULL;
190 const char* uri = NULL;
193 WINPR_ASSERT(method);
194 WINPR_ASSERT(content_type);
196 WINPR_ASSERT(arm->context);
198 freerdp* instance = arm->context->instance;
199 WINPR_ASSERT(instance);
201 uri = http_context_get_uri(arm->http);
202 request = http_request_new();
207 if (!http_request_set_method(request, method) || !http_request_set_uri(request, uri))
214 if (!instance->GetAccessToken)
216 WLog_Print(arm->log, WLOG_ERROR,
"No authorization token provided");
220 if (!arm_fetch_wellknown(arm))
223 if (!instance->GetAccessToken(instance, ACCESS_TOKEN_TYPE_AVD, &token, 0))
225 WLog_Print(arm->log, WLOG_ERROR,
"Unable to obtain access token");
238 if (!http_request_set_auth_scheme(request,
"Bearer") ||
239 !http_request_set_auth_param(
244 if (!http_request_set_transfer_encoding(request, transferEncoding) ||
245 !http_request_set_content_length(request, content_length) ||
246 !http_request_set_content_type(request, content_type))
249 s = http_request_write(arm->http, request);
251 http_request_free(request);
254 Stream_SealLength(s);
259 static BOOL arm_send_http_request(rdpArm* arm, rdpTls* tls,
const char* method,
260 const char* content_type,
const char* data,
size_t content_length)
264 arm_build_http_request(arm, method, TransferEncodingIdentity, content_type, content_length);
269 const size_t sz = Stream_Length(s);
270 status = freerdp_tls_write_all(tls, Stream_Buffer(s), sz);
272 Stream_Free(s, TRUE);
273 if (status >= 0 && content_length > 0 && data)
274 status = freerdp_tls_write_all(tls, (
const BYTE*)data, content_length);
276 return (status >= 0);
279 static void arm_free(rdpArm* arm)
284 freerdp_tls_free(arm->tls);
285 http_context_free(arm->http);
290 static rdpArm* arm_new(rdpContext* context)
292 WINPR_ASSERT(context);
294 rdpArm* arm = (rdpArm*)calloc(1,
sizeof(rdpArm));
298 arm->log = WLog_Get(TAG);
299 arm->context = context;
300 arm->tls = freerdp_tls_new(context);
304 arm->http = http_context_new();
316 static char* arm_create_request_json(rdpArm* arm)
319 char* message = NULL;
325 goto arm_create_cleanup;
334 goto arm_create_cleanup;
366 static WINPR_CIPHER_CTX* treatAuthBlob(wLog* log,
const BYTE* pbInput,
size_t cbInput)
368 WINPR_CIPHER_CTX* ret = NULL;
369 char algoName[100] = { 0 };
371 SSIZE_T algoSz = ConvertWCharNToUtf8((
const WCHAR*)pbInput, cbInput /
sizeof(WCHAR), algoName,
375 WLog_Print(log, WLOG_ERROR,
"invalid algoName");
379 algoName[algoSz] = 0;
380 if (strcmp(algoName,
"AES") != 0)
382 WLog_Print(log, WLOG_ERROR,
"only AES is supported for now");
386 cbInput -= WINPR_ASSERTING_INT_CAST(
size_t, (algoSz + 1)) *
sizeof(WCHAR);
390 WLog_Print(log, WLOG_ERROR,
"invalid AuthBlob size");
396 wStream* s = Stream_StaticConstInit(
397 &staticStream, pbInput + WINPR_ASSERTING_INT_CAST(
size_t, (algoSz + 1)) *
sizeof(WCHAR),
401 Stream_Read_UINT32(s, dwMagic);
403 if (dwMagic != BCRYPT_KEY_DATA_BLOB_MAGIC)
405 WLog_Print(log, WLOG_ERROR,
"unsupported authBlob type");
409 UINT32 dwVersion = 0;
410 Stream_Read_UINT32(s, dwVersion);
411 if (dwVersion != BCRYPT_KEY_DATA_BLOB_VERSION1)
413 WLog_Print(log, WLOG_ERROR,
"unsupported authBlob version %d, expecting %d", dwVersion,
414 BCRYPT_KEY_DATA_BLOB_VERSION1);
418 UINT32 cbKeyData = 0;
419 Stream_Read_UINT32(s, cbKeyData);
422 if (cbKeyData > cbInput)
424 WLog_Print(log, WLOG_ERROR,
"invalid authBlob size");
428 WINPR_CIPHER_TYPE cipherType = 0;
432 cipherType = WINPR_CIPHER_AES_128_CBC;
435 cipherType = WINPR_CIPHER_AES_192_CBC;
438 cipherType = WINPR_CIPHER_AES_256_CBC;
441 WLog_Print(log, WLOG_ERROR,
"invalid authBlob cipher size");
445 ret = winpr_Cipher_NewEx(cipherType, WINPR_ENCRYPT, Stream_Pointer(s), cbKeyData, NULL, 0);
448 WLog_Print(log, WLOG_ERROR,
"error creating cipher");
452 if (!winpr_Cipher_SetPadding(ret, TRUE))
454 WLog_Print(log, WLOG_ERROR,
"unable to enable padding on cipher");
455 winpr_Cipher_Free(ret);
462 static BOOL arm_stringEncodeW(
const BYTE* pin,
size_t cbIn, BYTE** ppOut,
size_t* pcbOut)
468 char* b64encoded = crypto_base64_encode_ex(pin, cbIn, TRUE);
474 *ppOut = (BYTE*)ConvertUtf8NToWCharAlloc(b64encoded, strlen(b64encoded), &outSz);
480 *pcbOut = (outSz + 1) *
sizeof(WCHAR);
484 static BOOL arm_encodeRedirectPasswd(wLog* log, rdpSettings* settings,
const rdpCertificate* cert,
485 WINPR_CIPHER_CTX* cipher)
489 BYTE* finalOutput = NULL;
495 size_t wpasswdLen = 0;
499 WLog_Print(log, WLOG_ERROR,
"error when converting password to UTF16");
503 size_t wpasswdBytes = (wpasswdLen + 1) *
sizeof(WCHAR);
504 BYTE* encryptedPass = calloc(1, wpasswdBytes + 16);
505 size_t encryptedPassLen = 0;
507 if (!encryptedPass ||
508 !winpr_Cipher_Update(cipher, wpasswd, wpasswdBytes, encryptedPass, &encryptedPassLen) ||
509 !winpr_Cipher_Final(cipher, encryptedPass + encryptedPassLen, &finalLen))
511 WLog_Print(log, WLOG_ERROR,
"error when ciphering password");
514 encryptedPassLen += finalLen;
517 size_t output_length = 0;
519 if (!freerdp_certificate_publickey_encrypt(cert, encryptedPass, encryptedPassLen, &output,
522 WLog_Print(log, WLOG_ERROR,
"unable to encrypt with the server's public key");
526 size_t finalOutputLen = 0;
527 if (!arm_stringEncodeW(output, output_length, &finalOutput, &finalOutputLen))
529 WLog_Print(log, WLOG_ERROR,
"unable to base64+utf16 final blob");
536 WLog_Print(log, WLOG_ERROR,
"unable to set the redirection password in settings");
540 settings->RdstlsSecurity = TRUE;
541 settings->AadSecurity = FALSE;
542 settings->NlaSecurity = FALSE;
543 settings->RdpSecurity = FALSE;
544 settings->TlsSecurity = FALSE;
545 settings->RedirectionFlags = LB_PASSWORD_IS_PK_ENCRYPTED;
559 static BOOL arm_pick_base64Utf16Field(wLog* log,
const WINPR_JSON* json,
const char* name,
560 BYTE** poutput,
size_t* plen)
573 BYTE* output1 = NULL;
575 crypto_base64_decode(nodeValue, strlen(nodeValue), &output1, &len1);
576 if (!output1 || !len1)
578 WLog_Print(log, WLOG_ERROR,
"error when first unbase64 for %s", name);
584 char* output2 = ConvertWCharNToUtf8Alloc((WCHAR*)output1, len1 /
sizeof(WCHAR), &len2);
586 if (!output2 || !len2)
588 WLog_Print(log, WLOG_ERROR,
"error when decode('utf-16') for %s", name);
594 crypto_base64_decode(output2, len2, &output, plen);
596 if (!output || !*plen)
598 WLog_Print(log, WLOG_ERROR,
"error when second unbase64 for %s", name);
627 static size_t arm_parse_ipvx_count(WINPR_JSON* ipvX)
636 static BOOL arm_parse_ipv6(rdpSettings* settings, WINPR_JSON* ipv6,
size_t* pAddressIdx)
638 WINPR_ASSERT(settings);
640 WINPR_ASSERT(pAddressIdx);
649 for (
size_t j = 0; j < naddresses; j++)
656 if (utils_str_is_empty(addr))
658 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
659 (*pAddressIdx)++, addr))
665 static BOOL arm_parse_ipv4(rdpSettings* settings, WINPR_JSON* ipv4,
size_t* pAddressIdx)
667 WINPR_ASSERT(settings);
669 WINPR_ASSERT(pAddressIdx);
676 for (
size_t j = 0; j < naddresses; j++)
686 if (!utils_str_is_empty(publicIp))
688 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
689 (*pAddressIdx)++, publicIp))
698 if (!utils_str_is_empty(privateIp))
700 if (!freerdp_settings_set_pointer_array(settings, FreeRDP_TargetNetAddresses,
701 (*pAddressIdx)++, privateIp))
709 static BOOL arm_treat_azureInstanceNetworkMetadata(wLog* log,
const char* metadata,
710 rdpSettings* settings)
714 WINPR_ASSERT(settings);
716 if (!freerdp_target_net_adresses_reset(settings, 0))
722 WLog_Print(log, WLOG_ERROR,
"invalid azureInstanceNetworkMetadata");
735 WLog_Print(log, WLOG_ERROR,
"expecting interface to be an Array");
740 if (interfaceSz == 0)
742 WLog_WARN(TAG,
"no addresses in azure instance metadata");
748 for (
size_t i = 0; i < interfaceSz; i++)
756 count += arm_parse_ipvx_count(ipv6);
760 count += arm_parse_ipvx_count(ipv4);
763 if (!freerdp_target_net_adresses_reset(settings, count))
766 size_t addressIdx = 0;
767 for (
size_t i = 0; i < interfaceSz; i++)
776 if (!arm_parse_ipv6(settings, ipv6, &addressIdx))
783 if (!arm_parse_ipv4(settings, ipv4, &addressIdx))
787 if (addressIdx > UINT32_MAX)
800 static BOOL arm_fill_rdstls(rdpArm* arm, rdpSettings* settings,
const WINPR_JSON* json)
804 BYTE* authBlob = NULL;
805 rdpCertificate* redirectedServerCert = NULL;
810 WINPR_JSON* redirectedAuthGuidNode =
816 if (!redirectedAuthGuid)
823 const SSIZE_T wGUID_len = ConvertUtf8ToWChar(redirectedAuthGuid, wGUID, ARRAYSIZE(wGUID));
826 WLog_Print(arm->log, WLOG_ERROR,
"unable to allocate space for redirectedAuthGuid");
832 settings, FreeRDP_RedirectionGuid, wGUID,
833 WINPR_ASSERTING_INT_CAST(
size_t, (wGUID_len + 1)) *
sizeof(WCHAR));
836 WLog_Print(arm->log, WLOG_ERROR,
"unable to set RedirectionGuid");
843 if (!arm_pick_base64Utf16Field(arm->log, json,
"redirectedServerCert", &cert, &certLen))
846 if (!rdp_redirection_read_target_cert(&redirectedServerCert, cert, certLen))
850 size_t authBlobLen = 0;
851 if (!arm_pick_base64Utf16Field(arm->log, json,
"redirectedAuthBlob", &authBlob,
855 WINPR_CIPHER_CTX* cipher = treatAuthBlob(arm->log, authBlob, authBlobLen);
860 arm_encodeRedirectPasswd(arm->log, settings, redirectedServerCert, cipher);
861 winpr_Cipher_Free(cipher);
869 freerdp_certificate_free(redirectedServerCert);
876 static BOOL arm_fill_gateway_parameters(rdpArm* arm,
const char* message,
size_t len)
879 WINPR_ASSERT(arm->context);
880 WINPR_ASSERT(message);
887 rdpSettings* settings = arm->context->settings;
890 if (gwurlstr != NULL)
892 WLog_Print(arm->log, WLOG_DEBUG,
"extracted target url %s", gwurlstr);
913 WLog_Print(arm->log, WLOG_ERROR,
"error when treating azureInstanceNetworkMetadata");
922 status = arm_fill_rdstls(arm, settings, json);
929 static BOOL arm_handle_request_ok(rdpArm* arm,
const HttpResponse* response)
931 const size_t len = http_response_get_body_length(response);
932 const char* msg = (
const char*)http_response_get_body(response);
933 if (strnlen(msg, len + 1) > len)
936 WLog_Print(arm->log, WLOG_DEBUG,
"Got HTTP Response data: %s", msg);
937 return arm_fill_gateway_parameters(arm, msg, len);
940 static BOOL arm_handle_bad_request(rdpArm* arm,
const HttpResponse* response, BOOL* retry)
942 WINPR_ASSERT(response);
949 const size_t len = http_response_get_body_length(response);
950 const char* msg = (
const char*)http_response_get_body(response);
951 if (strnlen(msg, len + 1) > len)
954 WLog_Print(arm->log, WLOG_DEBUG,
"Got HTTP Response data: %s", msg);
960 if (error_ptr != NULL)
961 WLog_Print(arm->log, WLOG_ERROR,
"NullPoException: %s", error_ptr);
968 if (gw_code_str == NULL)
970 WLog_Print(arm->log, WLOG_ERROR,
"Response has no \"Code\" property");
971 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
975 if (strcmp(gw_code_str,
"E_PROXY_ORCHESTRATION_LB_SESSIONHOST_DEALLOCATED") == 0)
981 WLog_WARN(TAG,
"Starting your VM. It may take up to 5 minutes");
983 WLog_WARN(TAG,
"%s", msgstr);
987 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
997 static BOOL arm_handle_request(rdpArm* arm, BOOL* retry, DWORD timeout)
1001 if (!arm_fetch_wellknown(arm))
1009 char* message = NULL;
1012 HttpResponse* response = NULL;
1013 long StatusCode = 0;
1015 if (!http_context_set_uri(arm->http,
"/api/arm/v2/connections/") ||
1016 !http_context_set_accept(arm->http,
"application/json") ||
1017 !http_context_set_cache_control(arm->http,
"no-cache") ||
1018 !http_context_set_pragma(arm->http,
"no-cache") ||
1019 !http_context_set_connection(arm->http,
"Keep-Alive") ||
1020 !http_context_set_user_agent(arm->http, FREERDP_USER_AGENT) ||
1021 !http_context_set_x_ms_user_agent(arm->http, FREERDP_USER_AGENT) ||
1023 FreeRDP_GatewayHostname)))
1026 if (!arm_tls_connect(arm, arm->tls, timeout))
1029 message = arm_create_request_json(arm);
1033 if (!arm_send_http_request(arm, arm->tls,
"POST",
"application/json", message, strlen(message)))
1036 response = http_response_recv(arm->tls, TRUE);
1040 StatusCode = http_response_get_status_code(response);
1041 if (StatusCode == HTTP_STATUS_OK)
1043 if (!arm_handle_request_ok(arm, response))
1046 else if (StatusCode == HTTP_STATUS_BAD_REQUEST)
1048 if (!arm_handle_bad_request(arm, response, retry))
1053 http_response_log_error_status(WLog_Get(TAG), WLOG_ERROR, response);
1059 http_response_free(response);
1066 BOOL arm_resolve_endpoint(wLog* log, rdpContext* context, DWORD timeout)
1069 WLog_Print(log, WLOG_ERROR,
"arm gateway support not compiled in");
1076 if (!context->settings)
1082 WLog_Print(log, WLOG_ERROR,
"loadBalanceInfo and RemoteApplicationProgram needed");
1086 rdpArm* arm = arm_new(context);
1096 freerdp* instance = context->instance;
1097 WINPR_ASSERT(instance);
1098 SSIZE_T delay = IFCALLRESULT(-1, instance->RetryDialog, instance,
"arm-transport",
1099 arm->gateway_retry, arm);
1100 arm->gateway_retry++;
1105 WLog_Print(arm->log, WLOG_DEBUG,
"Delay for %" PRIdz
"ms before next attempt",
1109 DWORD slp = (UINT32)delay;
1110 if (delay > UINT32_MAX)
1117 rc = arm_handle_request(arm, &retry, timeout);
1119 }
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.