From d309fcd6e84349934e8a00b2b1b15a611d0639e3 Mon Sep 17 00:00:00 2001 From: fifthdegree Date: Sun, 2 Jul 2023 12:16:01 -0400 Subject: [PATCH] Restructure Azure AD related stuff - Move responsibility for obtaining access tokens to clients - Add function for getting access tokens for AVD - Get correct server hostname during AVD setup - Add utility function for doing http requests --- CMakeLists.txt | 5 +- client/SDL/sdl_freerdp.cpp | 6 +- client/SDL/sdl_webview.cpp | 87 ++++--- client/SDL/sdl_webview.hpp | 5 +- client/X11/xf_client.c | 3 +- client/common/client.c | 184 ++++++++++++-- include/config/config.h.in | 2 +- include/freerdp/client.h | 8 +- include/freerdp/freerdp.h | 16 +- include/freerdp/utils/http.h | 28 ++ libfreerdp/CMakeLists.txt | 2 +- libfreerdp/core/aad.c | 396 ++++------------------------- libfreerdp/core/gateway/arm.c | 59 ++++- libfreerdp/utils/CMakeLists.txt | 1 + libfreerdp/utils/http.c | 231 +++++++++++++++++ scripts/android-build-32.conf | 2 +- scripts/android-build-64.conf | 2 +- scripts/android-build-freerdp.sh | 6 +- scripts/android-build-release.conf | 2 +- scripts/android-build.conf | 2 +- 20 files changed, 609 insertions(+), 438 deletions(-) create mode 100644 include/freerdp/utils/http.h create mode 100644 libfreerdp/utils/http.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 084cdf114..fde3f8446 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -741,7 +741,10 @@ find_feature(Pulse ${PULSE_FEATURE_TYPE} ${PULSE_FEATURE_PURPOSE} ${PULSE_FEATUR find_feature(Cups ${CUPS_FEATURE_TYPE} ${CUPS_FEATURE_PURPOSE} ${CUPS_FEATURE_DESCRIPTION}) find_feature(PCSC ${PCSC_FEATURE_TYPE} ${PCSC_FEATURE_PURPOSE} ${PCSC_FEATURE_DESCRIPTION}) -find_feature(CJSON ${CJSON_FEATURE_TYPE} ${CJSON_FEATURE_PURPOSE} ${CJSON_FEATURE_DESCRIPTION}) +option(WITH_AAD "Compile with support for Azure AD authentication" ON) +if (WITH_AAD) + find_package(CJSON REQUIRED) +endif() if (WITH_DSP_FFMPEG OR WITH_VIDEO_FFMPEG OR WITH_FFMPEG) set(FFMPEG_FEATURE_TYPE "REQUIRED" ) diff --git a/client/SDL/sdl_freerdp.cpp b/client/SDL/sdl_freerdp.cpp index fb5d316dd..836db5093 100644 --- a/client/SDL/sdl_freerdp.cpp +++ b/client/SDL/sdl_freerdp.cpp @@ -1139,9 +1139,11 @@ static BOOL sdl_client_new(freerdp* instance, rdpContext* context) instance->VerifyChangedCertificateEx = client_cli_verify_changed_certificate_ex; instance->LogonErrorInfo = sdl_logon_error_info; #ifdef WITH_WEBVIEW - instance->GetAadAuthCode = sdl_webview_get_aad_auth_code; + instance->GetRDSAADAccessToken = sdl_webview_get_rdsaad_access_token; + instance->GetAVDAccessToken = sdl_webview_get_avd_access_token; #else - instance->GetAadAuthCode = client_cli_get_aad_auth_code; + instance->GetRDSAADAccessToken = client_cli_get_rdsaad_access_token; + instance->GetAVDAccessToken = client_cli_get_avd_access_token; #endif /* TODO: Client display set up */ diff --git a/client/SDL/sdl_webview.cpp b/client/SDL/sdl_webview.cpp index 52db32ece..4a840a9ce 100644 --- a/client/SDL/sdl_webview.cpp +++ b/client/SDL/sdl_webview.cpp @@ -66,35 +66,14 @@ class SchemeHandler : public QWebEngineUrlSchemeHandler std::string m_code; }; -BOOL sdl_webview_get_aad_auth_code(freerdp* instance, const char* hostname, char** code, - const char** client_id, const char** redirect_uri) +static std::string sdl_webview_get_auth_code(QString url) { int argc = 1; std::string name = "FreeRDP WebView"; - - WINPR_ASSERT(instance); - WINPR_ASSERT(hostname); - WINPR_ASSERT(code); - WINPR_ASSERT(client_id); - WINPR_ASSERT(redirect_uri); - - WINPR_UNUSED(instance); - - *code = nullptr; - *client_id = "5177bc73-fd99-4c77-a90c-76844c9b6999"; - *redirect_uri = - "ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2f5177bc73-fd99-4c77-a90c-76844c9b6999"; - - auto url = - QString("https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=") + - QString(*client_id) + - QString("&response_type=code&scope=ms-device-service%3A%2F%2Ftermsrv.wvd.microsoft.com%" - "2Fname%2F") + - QString(hostname) + QString("%2Fuser_impersonation&redirect_uri=") + QString(*redirect_uri); + char* argv[] = { name.data() }; QWebEngineUrlScheme::registerScheme(QWebEngineUrlScheme("ms-appx-web")); - char* argv[] = { name.data() }; QCoreApplication::setOrganizationName("QtExamples"); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication app(argc, argv); @@ -107,12 +86,60 @@ BOOL sdl_webview_get_aad_auth_code(freerdp* instance, const char* hostname, char webview.show(); if (app.exec() != 0) - return FALSE; + return ""; - auto val = handler.code(); - if (val.empty()) - return FALSE; - *code = _strdup(val.c_str()); - - return (*code != nullptr) ? TRUE : FALSE; + return handler.code(); +} + +BOOL sdl_webview_get_rdsaad_access_token(freerdp* instance, const char* scope, const char* req_cnf, + char** token) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(scope); + WINPR_ASSERT(req_cnf); + WINPR_ASSERT(token); + + WINPR_UNUSED(instance); + + std::string client_id = "5177bc73-fd99-4c77-a90c-76844c9b6999"; + std::string redirect_uri = + "ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2f5177bc73-fd99-4c77-a90c-76844c9b6999"; + + *token = nullptr; + + auto url = QString::fromStdString( + "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + client_id + + "&response_type=code&scope=" + scope + "&redirect_uri=" + redirect_uri); + + auto code = sdl_webview_get_auth_code(url); + if (code.empty()) + return FALSE; + + auto token_request = "grant_type=authorization_code&code=" + code + "&client_id=" + client_id + + "&scope=" + scope + "&redirect_uri=" + redirect_uri + + "&req_cnf=" + req_cnf; + return client_common_get_access_token(instance, token_request.c_str(), token); +} + +BOOL sdl_webview_get_avd_access_token(freerdp* instance, char** token) +{ + WINPR_ASSERT(token); + + std::string client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c"; + std::string redirect_uri = + "ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2fa85cf173-4192-42f8-81fa-777a763e6e2c"; + std::string scope = "https%3A%2F%2Fwww.wvd.microsoft.com%2F.default"; + + *token = nullptr; + + auto url = QString::fromStdString( + "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + client_id + + "&response_type=code&scope=" + scope + "&redirect_uri=" + redirect_uri); + auto code = sdl_webview_get_auth_code(url); + if (code.empty()) + return FALSE; + + auto token_request = "grant_type=authorization_code&code=" + code + "&client_id=" + client_id + + "&scope=" + scope + "&redirect_uri=" + redirect_uri; + return client_common_get_access_token(instance, token_request.c_str(), token); } diff --git a/client/SDL/sdl_webview.hpp b/client/SDL/sdl_webview.hpp index 3fd9441e0..93e229721 100644 --- a/client/SDL/sdl_webview.hpp +++ b/client/SDL/sdl_webview.hpp @@ -26,8 +26,9 @@ extern "C" { #endif - BOOL sdl_webview_get_aad_auth_code(freerdp* instance, const char* hostname, char** code, - const char** client_id, const char** redirect_uri); + BOOL sdl_webview_get_avd_access_token(freerdp* instance, char** token); + BOOL sdl_webview_get_rdsaad_access_token(freerdp* instance, const char* scope, + const char* req_cnf, char** token); #ifdef __cplusplus } diff --git a/client/X11/xf_client.c b/client/X11/xf_client.c index 1bb430559..8ca5df935 100644 --- a/client/X11/xf_client.c +++ b/client/X11/xf_client.c @@ -1976,7 +1976,8 @@ static BOOL xfreerdp_client_new(freerdp* instance, rdpContext* context) instance->PostDisconnect = xf_post_disconnect; instance->PostFinalDisconnect = xf_post_final_disconnect; instance->LogonErrorInfo = xf_logon_error_info; - instance->GetAadAuthCode = client_cli_get_aad_auth_code; + instance->GetRDSAADAccessToken = client_cli_get_rdsaad_access_token; + instance->GetAVDAccessToken = client_cli_get_avd_access_token; PubSub_SubscribeTerminate(context->pubSub, xf_TerminateEventHandler); #ifdef WITH_XRENDER PubSub_SubscribeZoomingChange(context->pubSub, xf_ZoomingChangeEventHandler); diff --git a/client/common/client.c b/client/common/client.c index b40df9d82..01d6f3682 100644 --- a/client/common/client.c +++ b/client/common/client.c @@ -58,6 +58,11 @@ #include #endif +#ifdef WITH_AAD +#include +#include +#endif + #include #define TAG CLIENT_TAG("common") @@ -935,49 +940,178 @@ BOOL client_cli_present_gateway_message(freerdp* instance, UINT32 type, BOOL isD return TRUE; } -BOOL client_cli_get_aad_auth_code(freerdp* instance, const char* hostname, char** code, - const char** client_id, const char** redirect_uri) +static char* extract_authorization_code(char* url) +{ + WINPR_ASSERT(url); + + for (char* p = strchr(url, '?'); p++ != NULL; p = strchr(p, '&')) + { + if (strncmp(p, "code=", 5) != 0) + continue; + + char* end = NULL; + p += 5; + + end = strchr(p, '&'); + if (end) + *end = 0; + else + end = strchr(p, '\0'); + + return p; + } + + return NULL; +} + +BOOL client_cli_get_rdsaad_access_token(freerdp* instance, const char* scope, const char* req_cnf, + char** token) { size_t size = 0; char* url = NULL; + char* token_request = NULL; + const char* client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c"; + const char* redirect_uri = + "https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient"; WINPR_ASSERT(instance); - WINPR_ASSERT(hostname); - WINPR_ASSERT(code); - WINPR_ASSERT(client_id); - WINPR_ASSERT(redirect_uri); + WINPR_ASSERT(scope); + WINPR_ASSERT(req_cnf); + WINPR_ASSERT(token); - *code = NULL; - *client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c"; - *redirect_uri = "https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient"; + *token = NULL; printf("Browse to: https://login.microsoftonline.com/common/oauth2/v2.0/" "authorize?client_id=%s&response_type=" - "code&scope=ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fname%%" - "2F%s%%2Fuser_impersonation&redirect_uri=%s" + "code&scope=%s&redirect_uri=%s" "\n", - *client_id, hostname, *redirect_uri); + client_id, scope, redirect_uri); printf("Paste redirect URL here: \n"); if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0) return FALSE; - for (char* p = strchr(url, '?'); p++ != NULL; p = strchr(p, '&')) - { - if (strncmp(p, "code=", 5) == 0) - { - char* end = NULL; - p += 5; + char* code = extract_authorization_code(url); + if (!code) + goto cleanup; - end = strchr(p, '&'); - if (end) - *end = 0; - *code = strdup(p); - } + if (winpr_asprintf(&token_request, &size, + "grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%" + "s&req_cnf=%s", + code, client_id, scope, redirect_uri, req_cnf) <= 0) + goto cleanup; + + client_common_get_access_token(instance, token_request, token); + +cleanup: + free(token_request); + free(url); + return (*token != NULL); +} + +BOOL client_cli_get_avd_access_token(freerdp* instance, char** token) +{ + size_t size = 0; + char* url = NULL; + char* token_request = NULL; + const char* client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c"; + const char* redirect_uri = + "https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient"; + const char* scope = "https%3A%2F%2Fwww.wvd.microsoft.com%2F.default"; + + WINPR_ASSERT(instance); + WINPR_ASSERT(token); + + *token = NULL; + + printf("Browse to: https://login.microsoftonline.com/common/oauth2/v2.0/" + "authorize?client_id=%s&response_type=" + "code&scope=%s&redirect_uri=%s" + "\n", + client_id, scope, redirect_uri); + printf("Paste redirect URL here: \n"); + + if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0) + return FALSE; + + char* code = extract_authorization_code(url); + if (!code) + goto cleanup; + + if (winpr_asprintf( + &token_request, &size, + "grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%s", code, + client_id, scope, redirect_uri) <= 0) + goto cleanup; + + client_common_get_access_token(instance, token_request, token); + +cleanup: + free(token_request); + free(url); + return (*token != NULL); +} + +BOOL client_common_get_access_token(freerdp* instance, const char* request, char** token) +{ +#ifdef WITH_AAD + WINPR_ASSERT(request); + WINPR_ASSERT(token); + + BOOL ret = FALSE; + long resp_code = 0; + BYTE* response = NULL; + size_t response_length = 0; + cJSON* json = NULL; + cJSON* access_token_prop = NULL; + const char* access_token_str = NULL; + + if (!freerdp_http_request("https://login.microsoftonline.com/common/oauth2/v2.0/token", request, + &resp_code, &response, &response_length)) + { + WLog_ERR(TAG, "access token request failed"); + return FALSE; } - free(url); - return (*code != NULL); + if (resp_code != 200) + { + WLog_ERR(TAG, "Server unwilling to provide access token; returned status code %li", + resp_code); + goto cleanup; + } + + json = cJSON_ParseWithLength((const char*)response, response_length); + if (!json) + { + WLog_ERR(TAG, "Failed to parse access token response"); + goto cleanup; + } + + access_token_prop = cJSON_GetObjectItem(json, "access_token"); + if (!access_token_prop) + { + WLog_ERR(TAG, "Response has no \"access_token\" property"); + goto cleanup; + } + + access_token_str = cJSON_GetStringValue(access_token_prop); + if (!access_token_str) + { + WLog_ERR(TAG, "Invalid value for \"access_token\""); + goto cleanup; + } + + *token = _strdup(access_token_str); + if (*token) + ret = TRUE; + +cleanup: + cJSON_Delete(json); + free(response); + return ret; +#else + return FALSE; +#endif } BOOL client_auto_reconnect(freerdp* instance) diff --git a/include/config/config.h.in b/include/config/config.h.in index 1d3c81565..3c84c2ff5 100644 --- a/include/config/config.h.in +++ b/include/config/config.h.in @@ -20,7 +20,7 @@ #cmakedefine WITH_CUPS #cmakedefine WITH_JPEG #cmakedefine WITH_WIN8 -#cmakedefine WITH_CJSON +#cmakedefine WITH_AAD #cmakedefine WITH_CAIRO #cmakedefine WITH_SWSCALE #cmakedefine WITH_RDPSND_DSOUND diff --git a/include/freerdp/client.h b/include/freerdp/client.h index a6612b756..76bc33b19 100644 --- a/include/freerdp/client.h +++ b/include/freerdp/client.h @@ -170,9 +170,11 @@ extern "C" FREERDP_API int client_cli_logon_error_info(freerdp* instance, UINT32 data, UINT32 type); - FREERDP_API BOOL client_cli_get_aad_auth_code(freerdp* instance, const char* hostname, - char** code, const char** client_id, - const char** redirect_uri); + FREERDP_API BOOL client_cli_get_rdsaad_access_token(freerdp* instance, const char* scope, + const char* req_cnf, char** token); + FREERDP_API BOOL client_cli_get_avd_access_token(freerdp* instance, char** token); + FREERDP_API BOOL client_common_get_access_token(freerdp* instance, const char* request, + char** token); FREERDP_API void freerdp_client_OnChannelConnectedEventHandler(void* context, diff --git a/include/freerdp/freerdp.h b/include/freerdp/freerdp.h index 3b1df52e0..4758287f6 100644 --- a/include/freerdp/freerdp.h +++ b/include/freerdp/freerdp.h @@ -126,8 +126,9 @@ extern "C" char** domain, rdp_auth_reason reason); typedef BOOL (*pChooseSmartcard)(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count, DWORD* choice, BOOL gateway); - typedef BOOL (*pGetAadAuthCode)(freerdp* instance, const char* hostname, char** code, - const char** client_id, const char** redirect_uri); + typedef BOOL (*pGetRDSAADAccessToken)(freerdp* instance, const char* scope, const char* req_cnf, + char** token); + typedef BOOL (*pGetAVDAccessToken)(freerdp* instance, char** token); /** @brief Callback used if user interaction is required to accept * an unknown certificate. @@ -521,10 +522,13 @@ owned by rdpRdp */ Callback for choosing a smartcard for logon. Used when multiple smartcards are available. Returns an index into a list of SmartcardCertInfo pointers */ - ALIGN64 pGetAadAuthCode GetAadAuthCode; /* (offset 71) - Callback for obtaining an oauth2 authorization - code for RDS AAD authentication */ - UINT64 paddingE[80 - 72]; /* 72 */ + ALIGN64 pGetRDSAADAccessToken GetRDSAADAccessToken; /* (offset 71) + Callback for obtaining an oauth2 access token + for RDS AAD authentication */ + ALIGN64 pGetAVDAccessToken GetAVDAccessToken; /* (offset 72) + Callback for obtaining an oauth2 access token + for Azure Virtual Desktop */ + UINT64 paddingE[80 - 73]; /* 73 */ }; struct rdp_channel_handles diff --git a/include/freerdp/utils/http.h b/include/freerdp/utils/http.h new file mode 100644 index 000000000..03b7561fb --- /dev/null +++ b/include/freerdp/utils/http.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Smartcard Device Service Virtual Channel + * + * Copyright 2023 Isaac Klein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_UTILS_HTTP_H +#define FREERDP_UTILS_HTTP_H + +#include + +FREERDP_API BOOL freerdp_http_request(const char* url, const char* body, long* status_code, + BYTE** response, size_t* response_length); + +#endif /* FREERDP_UTILS_HTTP_H */ diff --git a/libfreerdp/CMakeLists.txt b/libfreerdp/CMakeLists.txt index 7adb3ed5d..5fa21f567 100644 --- a/libfreerdp/CMakeLists.txt +++ b/libfreerdp/CMakeLists.txt @@ -219,7 +219,7 @@ if(FAAC_FOUND) include_directories(${FAAC_INCLUDE_DIRS}) endif() -if(WITH_CJSON) +if(WITH_AAD) freerdp_library_add(${CJSON_LIBRARIES}) include_directories(${CJSON_INCLUDE_DIRS}) endif() diff --git a/libfreerdp/core/aad.c b/libfreerdp/core/aad.c index e451a9060..8837730da 100644 --- a/libfreerdp/core/aad.c +++ b/libfreerdp/core/aad.c @@ -25,23 +25,19 @@ #include #include #include "../crypto/privatekey.h" +#include -#ifdef WITH_CJSON +#ifdef WITH_AAD #include #endif #include -#include -#include -#include -#include - #include "transport.h" #include "aad.h" -#ifdef WITH_CJSON +#ifdef WITH_AAD #if CJSON_VERSION_MAJOR == 1 #if CJSON_VERSION_MINOR <= 7 #if CJSON_VERSION_PATCH < 13 @@ -61,71 +57,14 @@ struct rdp_aad char* kid; char* nonce; char* hostname; + char* scope; wLog* log; }; -#ifdef WITH_CJSON -#if OPENSSL_VERSION_NUMBER >= 0x30000000L -#include -#else -static int BIO_get_line(BIO* bio, char* buf, int size) -{ - int pos = 0; - - if (size <= 1) - return 0; - - while (pos < size - 1) - { - char c = '\0'; - const int rc = BIO_read(bio, &c, sizeof(c)); - if (rc < 0) - return rc; - if (rc > 0) - { - buf[pos++] = c; - if (c == '\n') - break; - } - } - - buf[pos] = '\0'; - return pos; -} -#endif - -static char* auth_server = "login.microsoftonline.com"; - -static const char nonce_http_request[] = "" - "POST /common/oauth2/token HTTP/1.1\r\n" - "Host: login.microsoftonline.com\r\n" - "Content-Type: application/x-www-form-urlencoded\r\n" - "Content-Length: 24\r\n" - "\r\n" - "grant_type=srv_challenge" - "\r\n\r\n"; - -static const char token_http_request_header[] = - "" - "POST /common/oauth2/v2.0/token HTTP/1.1\r\n" - "Host: login.microsoftonline.com\r\n" - "Content-Type: application/x-www-form-urlencoded\r\n" - "Content-Length: %lu\r\n" - "\r\n"; -static const char token_http_request_body[] = - "" - "client_id=%s&grant_type=authorization_code" - "&code=%s" - "&scope=ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fname%%2F%s%%2Fuser_" - "impersonation" - "&req_cnf=%s" - "&redirect_uri=%s" - "\r\n\r\n"; +#ifdef WITH_AAD static BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key, char** e, char** n); static BOOL generate_pop_key(rdpAad* aad); -static BOOL read_http_message(rdpAad* aad, BIO* bio, long* status_code, char** content, - size_t* content_length); static SSIZE_T stream_sprintf(wStream* s, const char* fmt, ...) { @@ -151,13 +90,6 @@ static SSIZE_T stream_sprintf(wStream* s, const char* fmt, ...) return rc2; } -static int print_error(const char* str, size_t len, void* u) -{ - wLog* wlog = (wLog*)u; - WLog_Print(wlog, WLOG_ERROR, "%s [%" PRIuz "]", str, len); - return 1; -} - static BOOL json_get_object(wLog* wlog, cJSON* json, const char* key, cJSON** obj) { WINPR_ASSERT(json); @@ -255,88 +187,6 @@ static BOOL json_get_string_alloc(wLog* wlog, cJSON* json, const char* key, char return *result != NULL; } -static BIO* aad_connect_https(rdpAad* aad, SSL_CTX* ssl_ctx) -{ - WINPR_ASSERT(aad); - WINPR_ASSERT(ssl_ctx); - - const int vprc = SSL_CTX_set_default_verify_paths(ssl_ctx); - if (vprc != 1) - { - WLog_Print(aad->log, WLOG_ERROR, "Error setting verify paths"); - return NULL; - } - const long mrc = SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); - if ((mrc & SSL_MODE_AUTO_RETRY) == 0) - WLog_Print(aad->log, WLOG_WARN, "Failed to set SSL_MODE_AUTO_RETRY"); - - BIO* bio = BIO_new_ssl_connect(ssl_ctx); - if (!bio) - { - WLog_Print(aad->log, WLOG_ERROR, "Error setting up connection"); - return NULL; - } - const long chrc = BIO_set_conn_hostname(bio, auth_server); - if (chrc != 1) - { - WLog_Print(aad->log, WLOG_ERROR, "Error setting BIO hostname"); - BIO_free(bio); - return NULL; - } - const long cprc = BIO_set_conn_port(bio, "https"); - if (cprc != 1) - { - WLog_Print(aad->log, WLOG_ERROR, "Error setting BIO port"); - BIO_free(bio); - return NULL; - } - return bio; -} - -static BOOL aad_logging_bio_write(rdpAad* aad, BIO* bio, const char* str) -{ - WINPR_ASSERT(aad); - WINPR_ASSERT(bio); - WINPR_ASSERT(str); - - const size_t size = strlen(str); - if (size > INT_MAX) - return FALSE; - ERR_clear_error(); - if (BIO_write(bio, str, (int)size) < 0) - { - ERR_print_errors_cb(print_error, aad->log); - return FALSE; - } - return TRUE; -} - -static char* aad_read_response(rdpAad* aad, BIO* bio, size_t* plen, const char* what) -{ - WINPR_ASSERT(plen); - - long status_code; - char* buffer = NULL; - size_t length = 0; - - *plen = 0; - if (!read_http_message(aad, bio, &status_code, &buffer, &length)) - { - WLog_Print(aad->log, WLOG_ERROR, "Unable to read %s HTTP response", what); - return NULL; - } - WLog_Print(aad->log, WLOG_DEBUG, "%s HTTP response: %s", what, buffer); - - if (status_code != 200) - { - WLog_Print(aad->log, WLOG_ERROR, "%s HTTP status code: %li", what, status_code); - free(buffer); - return NULL; - } - *plen = length; - return buffer; -} - static cJSON* compat_cJSON_ParseWithLength(const char* value, size_t buffer_length) { #if defined(USE_CJSON_COMPAT) @@ -349,101 +199,50 @@ static cJSON* compat_cJSON_ParseWithLength(const char* value, size_t buffer_leng return cJSON_ParseWithLength(value, buffer_length); #endif } -static BOOL aad_read_and_extract_token_from_json(rdpAad* aad, BIO* bio) -{ - BOOL rc = FALSE; - size_t blen = 0; - char* buffer = aad_read_response(aad, bio, &blen, "access token"); - if (!buffer) - return FALSE; - cJSON* json = compat_cJSON_ParseWithLength(buffer, blen); - if (!json) +static BOOL aad_get_nonce(rdpAad* aad) +{ + BOOL ret = FALSE; + BYTE* response = NULL; + long resp_code = 0; + size_t response_length = 0; + cJSON* json = NULL; + + if (!freerdp_http_request("https://login.microsoftonline.com/common/oauth2/v2.0/token", + "grant_type=srv_challenge", &resp_code, &response, &response_length)) { - WLog_Print(aad->log, WLOG_ERROR, "Failed to parse JSON response"); + WLog_Print(aad->log, WLOG_ERROR, "nonce request failed"); goto fail; } - if (!json_get_string_alloc(aad->log, json, "access_token", &aad->access_token)) + if (resp_code != 200) { WLog_Print(aad->log, WLOG_ERROR, - "Could not find \"access_token\" property in JSON response"); + "Server unwilling to provide nonce; returned status code %li", resp_code); goto fail; } - rc = TRUE; -fail: - free(buffer); - cJSON_Delete(json); - return rc; -} - -static BOOL aad_read_and_extrace_nonce_from_json(rdpAad* aad, BIO* bio) -{ - BOOL rc = FALSE; - size_t blen = 0; - char* buffer = aad_read_response(aad, bio, &blen, "Nonce"); - if (!buffer) - return FALSE; - - /* Extract the nonce from the response */ - cJSON* json = compat_cJSON_ParseWithLength(buffer, blen); + json = cJSON_ParseWithLength((const char*)response, response_length); if (!json) { - WLog_Print(aad->log, WLOG_ERROR, "Failed to parse JSON response"); + WLog_Print(aad->log, WLOG_ERROR, "Failed to parse nonce response"); goto fail; } if (!json_get_string_alloc(aad->log, json, "Nonce", &aad->nonce)) - { - WLog_Print(aad->log, WLOG_ERROR, "Could not find \"Nonce\" property in JSON response"); goto fail; - } - rc = TRUE; + + ret = TRUE; + fail: - free(buffer); + free(response); cJSON_Delete(json); - return rc; -} - -static BOOL aad_send_token_request(rdpAad* aad, BIO* bio, const char* auth_code, - const char* client_id, const char* redirect_uri) -{ - BOOL rc = FALSE; - - char* req_body = NULL; - char* req_header = NULL; - size_t req_body_len = 0; - size_t req_header_len = 0; - const int trc = winpr_asprintf(&req_body, &req_body_len, token_http_request_body, client_id, - auth_code, aad->hostname, aad->kid, redirect_uri); - if (trc < 0) - goto fail; - const int trh = winpr_asprintf(&req_header, &req_header_len, token_http_request_header, trc); - if (trh < 0) - goto fail; - - WLog_Print(aad->log, WLOG_DEBUG, "HTTP access token request: %s%s", req_header, req_body); - - if (!aad_logging_bio_write(aad, bio, req_header)) - goto fail; - if (!aad_logging_bio_write(aad, bio, req_body)) - goto fail; - rc = TRUE; -fail: - free(req_body); - free(req_header); - return rc; + return ret; } int aad_client_begin(rdpAad* aad) { - int ret = -1; - SSL_CTX* ssl_ctx = NULL; - BIO* bio = NULL; - char* auth_code = NULL; - const char* client_id = NULL; - const char* redirect_uri = NULL; + size_t size = 0; WINPR_ASSERT(aad); WINPR_ASSERT(aad->rdpcontext); @@ -473,60 +272,37 @@ int aad_client_begin(rdpAad* aad) if (p) *p = '\0'; + if (winpr_asprintf(&aad->scope, &size, + "ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fname%%2F%s%%" + "2Fuser_impersonation", + aad->hostname) <= 0) + return -1; + if (!generate_pop_key(aad)) - goto fail; + return -1; /* Obtain an oauth authorization code */ - if (!instance->GetAadAuthCode) + if (!instance->GetRDSAADAccessToken) { - WLog_Print(aad->log, WLOG_ERROR, "instance->GetAadAuthCode == NULL"); - goto fail; + WLog_Print(aad->log, WLOG_ERROR, "instance->GetRDSAADAccessToken == NULL"); + return -1; } const BOOL arc = - instance->GetAadAuthCode(instance, aad->hostname, &auth_code, &client_id, &redirect_uri); + instance->GetRDSAADAccessToken(instance, aad->scope, aad->kid, &aad->access_token); if (!arc) { - WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain authorization code"); - goto fail; + WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain access token"); + return -1; } - /* Set up an ssl connection to the authorization server */ - ssl_ctx = SSL_CTX_new(TLS_client_method()); - if (!ssl_ctx) - { - WLog_Print(aad->log, WLOG_ERROR, "Error setting up SSL context"); - goto fail; - } - - bio = aad_connect_https(aad, ssl_ctx); - if (!bio) - goto fail; - - /* Construct and send the token request message */ - if (!aad_send_token_request(aad, bio, auth_code, client_id, redirect_uri)) - goto fail; - - /* Extract the access token from the JSON response */ - if (!aad_read_and_extract_token_from_json(aad, bio)) - goto fail; - /* Send the nonce request message */ - WLog_Print(aad->log, WLOG_DEBUG, "HTTP nonce request: %s", nonce_http_request); + if (!aad_get_nonce(aad)) + { + WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain nonce"); + return -1; + } - if (!aad_logging_bio_write(aad, bio, nonce_http_request)) - goto fail; - - /* Read in the response */ - if (!aad_read_and_extrace_nonce_from_json(aad, bio)) - goto fail; - - ret = 1; - -fail: - BIO_free_all(bio); - SSL_CTX_free(ssl_ctx); - free(auth_code); - return ret; + return 1; } static char* aad_create_jws_header(rdpAad* aad) @@ -792,85 +568,6 @@ int aad_recv(rdpAad* aad, wStream* s) } } -static BOOL read_http_message(rdpAad* aad, BIO* bio, long* status_code, char** content, - size_t* content_length) -{ - char buffer[1024] = { 0 }; - - WINPR_ASSERT(aad); - WINPR_ASSERT(status_code); - WINPR_ASSERT(content); - WINPR_ASSERT(content_length); - - *content_length = 0; - const int rb = BIO_get_line(bio, buffer, sizeof(buffer)); - if (rb <= 0) - { - WLog_Print(aad->log, WLOG_ERROR, "Error reading HTTP response [BIO_get_line %d]", rb); - return FALSE; - } - - if (sscanf(buffer, "HTTP/%*u.%*u %li %*[^\r\n]\r\n", status_code) < 1) - { - WLog_Print(aad->log, WLOG_ERROR, "Invalid HTTP response status line"); - return FALSE; - } - - do - { - const int rbb = BIO_get_line(bio, buffer, sizeof(buffer)); - if (rbb <= 0) - { - WLog_Print(aad->log, WLOG_ERROR, "Error reading HTTP response [BIO_get_line %d]", rbb); - return FALSE; - } - - char* val = NULL; - char* name = strtok_s(buffer, ":", &val); - if (name && (_stricmp(name, "content-length") == 0)) - { - errno = 0; - *content_length = strtoul(val, NULL, 10); - switch (errno) - { - case 0: - break; - default: - WLog_Print(aad->log, WLOG_ERROR, "strtoul(%s) returned %s [%d]", val, - strerror(errno), errno); - return FALSE; - } - } - } while (strcmp(buffer, "\r\n") != 0); - - if (*content_length == 0) - return TRUE; - - *content = calloc(*content_length + 1, sizeof(char)); - if (!*content) - return FALSE; - - size_t offset = 0; - while (offset < *content_length) - { - const size_t diff = *content_length - offset; - int len = (int)diff; - if (diff > INT_MAX) - len = INT_MAX; - const int brc = BIO_read(bio, &(*content)[offset], len); - if (brc <= 0) - { - free(*content); - WLog_Print(aad->log, WLOG_ERROR, - "Error reading HTTP response body (BIO_read returned %d)", brc); - return FALSE; - } - offset += (size_t)brc; - } - - return TRUE; -} - static BOOL generate_rsa_2048(rdpAad* aad) { WINPR_ASSERT(aad); @@ -1080,6 +777,7 @@ void aad_free(rdpAad* aad) return; free(aad->hostname); + free(aad->scope); free(aad->nonce); free(aad->access_token); free(aad->kid); @@ -1096,7 +794,7 @@ AAD_STATE aad_get_state(rdpAad* aad) BOOL aad_is_supported(void) { -#ifdef WITH_CJSON +#ifdef WITH_AAD return TRUE; #else return FALSE; diff --git a/libfreerdp/core/gateway/arm.c b/libfreerdp/core/gateway/arm.c index 2c305d055..a1ac5d799 100644 --- a/libfreerdp/core/gateway/arm.c +++ b/libfreerdp/core/gateway/arm.c @@ -44,7 +44,7 @@ #include "../utils.h" #include "../settings.h" -#ifdef WITH_CJSON +#ifdef WITH_AAD #include #endif @@ -59,7 +59,7 @@ typedef struct rdp_arm rdpArm; #define TAG FREERDP_TAG("core.gateway.arm") -#ifdef WITH_CJSON +#ifdef WITH_AAD static BOOL arm_tls_connect(rdpArm* arm, rdpTls* tls, int timeout) { WINPR_ASSERT(arm); @@ -152,8 +152,14 @@ static wStream* arm_build_http_request(rdpArm* arm, const char* method, HttpRequest* request = NULL; const char* uri; - if (!arm || !method || !content_type) - return NULL; + WINPR_ASSERT(arm); + WINPR_ASSERT(method); + WINPR_ASSERT(content_type); + + WINPR_ASSERT(arm->context); + + freerdp* instance = arm->context->instance; + WINPR_ASSERT(instance); uri = http_context_get_uri(arm->http); request = http_request_new(); @@ -164,15 +170,37 @@ static wStream* arm_build_http_request(rdpArm* arm, const char* method, if (!http_request_set_method(request, method) || !http_request_set_uri(request, uri)) goto out; - else if (freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer)) + if (!freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer)) { - if (!http_request_set_auth_scheme(request, "Bearer") || - !http_request_set_auth_param( - request, freerdp_settings_get_string(arm->context->settings, - FreeRDP_GatewayHttpExtAuthBearer))) + char* token = NULL; + + if (!instance->GetAVDAccessToken) + { + WLog_ERR(TAG, "No authorization token provided"); goto out; + } + + if (!instance->GetAVDAccessToken(instance, &token)) + { + WLog_ERR(TAG, "Unable to obtain access token"); + goto out; + } + + if (!freerdp_settings_set_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer, + token)) + { + free(token); + goto out; + } + free(token); } + if (!http_request_set_auth_scheme(request, "Bearer") || + !http_request_set_auth_param( + request, + freerdp_settings_get_string(arm->context->settings, FreeRDP_GatewayHttpExtAuthBearer))) + goto out; + if (!http_request_set_transfer_encoding(request, transferEncoding) || !http_request_set_content_length(request, content_length) || !http_request_set_content_type(request, content_type)) @@ -304,6 +332,17 @@ static BOOL arm_fill_gateway_parameters(rdpArm* arm, const char* message) else status = TRUE; } + + const cJSON* hostname = cJSON_GetObjectItem(json, "redirectedServerName"); + if (cJSON_GetStringValue(hostname)) + { + if (!freerdp_settings_set_string(arm->context->settings, FreeRDP_ServerHostname, + cJSON_GetStringValue(hostname))) + status = FALSE; + else + status = TRUE; + } + cJSON_Delete(json); } return status; @@ -313,7 +352,7 @@ static BOOL arm_fill_gateway_parameters(rdpArm* arm, const char* message) BOOL arm_resolve_endpoint(rdpContext* context, DWORD timeout) { -#ifndef WITH_CJSON +#ifndef WITH_AAD WLog_ERR(TAG, "arm gateway support not compiled in"); return FALSE; #else diff --git a/libfreerdp/utils/CMakeLists.txt b/libfreerdp/utils/CMakeLists.txt index 980b1b6a7..f0e91a33e 100644 --- a/libfreerdp/utils/CMakeLists.txt +++ b/libfreerdp/utils/CMakeLists.txt @@ -33,6 +33,7 @@ set(${MODULE_PREFIX}_SRCS smartcard_pack.c smartcard_call.c stopwatch.c + http.c ) freerdp_module_add(${${MODULE_PREFIX}_SRCS}) diff --git a/libfreerdp/utils/http.c b/libfreerdp/utils/http.c new file mode 100644 index 000000000..1245ef79a --- /dev/null +++ b/libfreerdp/utils/http.c @@ -0,0 +1,231 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Simple HTTP client request utility + * + * Copyright 2023 Isaac Klein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include + +#include +#include +#include + +#include +#define TAG FREERDP_TAG("utils.http") + +static const char get_header_fmt[] = "GET %s HTTP/1.1\r\n" + "Host: %s\r\n" + "\r\n"; + +static const char post_header_fmt[] = "POST %s HTTP/1.1\r\n" + "Host: %s\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: %lu\r\n" + "\r\n"; + +static void log_errors(const char* msg) +{ + unsigned long ec = 0; + while ((ec = ERR_get_error())) + WLog_ERR(TAG, "%s: %s", msg, ERR_error_string(ec, NULL)); +} + +BOOL freerdp_http_request(const char* url, const char* body, long* status_code, BYTE** response, + size_t* response_length) +{ + BOOL ret = FALSE; + char* hostname = NULL; + const char* path = NULL; + char* headers = NULL; + size_t size = 0; + int status = 0; + char buffer[1024] = { 0 }; + BIO* bio = NULL; + SSL_CTX* ssl_ctx = NULL; + + WINPR_ASSERT(status_code); + WINPR_ASSERT(response); + WINPR_ASSERT(response_length); + + *response = NULL; + + if (!url || strlen(url) < 8 || strncmp(url, "https://", 8) != 0 || + !(path = strchr(url + 8, '/'))) + { + WLog_ERR(TAG, "invalid url provided"); + goto out; + } + + hostname = calloc(1, path - (url + 8) + 1); + if (!hostname) + return FALSE; + strncpy(hostname, url + 8, path - (url + 8)); + + if (body) + { + if (winpr_asprintf(&headers, &size, post_header_fmt, path, hostname, strlen(body)) < 0) + return FALSE; + } + else + { + if (winpr_asprintf(&headers, &size, get_header_fmt, path, hostname) < 0) + return FALSE; + } + + ssl_ctx = SSL_CTX_new(TLS_client_method()); + + if (!ssl_ctx) + { + log_errors("could not set up ssl context"); + goto out; + } + + if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) + { + log_errors("could not set ssl context verify paths"); + goto out; + } + + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + + bio = BIO_new_ssl_connect(ssl_ctx); + if (!bio) + { + log_errors("could not set up connection"); + goto out; + } + + if (BIO_set_conn_port(bio, "https") <= 0) + { + log_errors("could not set port"); + goto out; + } + + if (!BIO_set_conn_hostname(bio, hostname)) + { + log_errors("could not set hostname"); + goto out; + } + + WLog_DBG(TAG, "headers:\n%s", headers); + ERR_clear_error(); + if (BIO_write(bio, headers, strlen(headers)) < 0) + { + log_errors("could not write headers"); + goto out; + } + + if (body) + { + WLog_DBG(TAG, "body:\n%s", body); + + if (strlen(body) > INT_MAX) + { + WLog_ERR(TAG, "body too long!"); + goto out; + } + + ERR_clear_error(); + if (BIO_write(bio, body, strlen(body)) < 0) + { + log_errors("could not write body"); + goto out; + } + } + + status = BIO_get_line(bio, buffer, sizeof(buffer)); + if (status <= 0) + { + log_errors("could not read response"); + goto out; + } + + if (sscanf(buffer, "HTTP/1.1 %li %*[^\r\n]\r\n", status_code) < 1) + { + WLog_ERR(TAG, "invalid HTTP status line"); + goto out; + } + + do + { + status = BIO_get_line(bio, buffer, sizeof(buffer)); + if (status <= 0) + { + log_errors("could not read response"); + goto out; + } + + char* val = NULL; + char* name = strtok_s(buffer, ":", &val); + if (name && (_stricmp(name, "content-length") == 0)) + { + errno = 0; + *response_length = strtoul(val, NULL, 10); + if (errno) + { + WLog_ERR(TAG, "could not parse content length (%s): %s [%d]", val, strerror(errno), + errno); + goto out; + } + } + } while (strcmp(buffer, "\r\n") != 0); + + if (*response_length > 0) + { + *response = calloc(1, *response_length + 1); + if (!*response) + goto out; + + if (*response_length > INT_MAX) + { + WLog_ERR(TAG, "response too long!"); + goto out; + } + + BYTE* p = *response; + int left = *response_length; + while (left > 0) + { + status = BIO_read(bio, p, left); + if (status <= 0) + { + log_errors("could not read response"); + goto out; + } + p += status; + left -= status; + } + } + + ret = TRUE; + +out: + if (!ret) + { + free(*response); + *response = NULL; + *response_length = 0; + } + free(hostname); + free(headers); + BIO_free_all(bio); + SSL_CTX_free(ssl_ctx); + return ret; +} diff --git a/scripts/android-build-32.conf b/scripts/android-build-32.conf index 22042d9ff..2ed965a11 100644 --- a/scripts/android-build-32.conf +++ b/scripts/android-build-32.conf @@ -12,7 +12,7 @@ WITH_OPENH264=0 WITH_OPENSSL=1 WITH_FFMPEG=1 -WITH_CJSON=1 +WITH_AAD=1 BUILD_DEPS=1 DEPS_ONLY=0 NDK_TARGET=21 diff --git a/scripts/android-build-64.conf b/scripts/android-build-64.conf index 4a42ae283..9d001c5d0 100644 --- a/scripts/android-build-64.conf +++ b/scripts/android-build-64.conf @@ -12,7 +12,7 @@ WITH_OPENH264=1 WITH_OPENSSL=1 WITH_FFMPEG=1 -WITH_CJSON=1 +WITH_AAD=1 BUILD_DEPS=1 DEPS_ONLY=0 NDK_TARGET=21 diff --git a/scripts/android-build-freerdp.sh b/scripts/android-build-freerdp.sh index de8f7004b..d2e324e33 100755 --- a/scripts/android-build-freerdp.sh +++ b/scripts/android-build-freerdp.sh @@ -12,7 +12,7 @@ CJSON_HASH=d348621ca93571343a56862df7de4ff3bc9b5667 WITH_OPENH264=0 WITH_OPENSSL=0 WITH_FFMPEG=0 -WITH_CJSON=0 +WITH_AAD=0 SRC_DIR=$(dirname "${BASH_SOURCE[0]}") SRC_DIR=$(realpath "$SRC_DIR") @@ -50,7 +50,7 @@ do shift ;; --cjson) - WITH_CJSON=1 + WITH_AAD=1 shift ;; --openssl) @@ -162,7 +162,7 @@ do else CMAKE_CMD_ARGS="$CMAKE_CMD_ARGS -DWITH_FFMPEG=OFF" fi - if [ $WITH_CJSON -ne 0 ]; + if [ $WITH_AAD -ne 0 ]; then if [ $BUILD_DEPS -ne 0 ]; then diff --git a/scripts/android-build-release.conf b/scripts/android-build-release.conf index 211c7b850..db9f04592 100644 --- a/scripts/android-build-release.conf +++ b/scripts/android-build-release.conf @@ -12,7 +12,7 @@ WITH_OPENH264=0 WITH_OPENSSL=1 WITH_FFMPEG=1 -WITH_CJSON=1 +WITH_AAD=1 BUILD_DEPS=1 DEPS_ONLY=0 NDK_TARGET=23 diff --git a/scripts/android-build.conf b/scripts/android-build.conf index 4185cf752..c9ee50d6c 100644 --- a/scripts/android-build.conf +++ b/scripts/android-build.conf @@ -12,7 +12,7 @@ WITH_OPENH264=0 WITH_OPENSSL=1 WITH_FFMPEG=0 -WITH_CJSON=1 +WITH_AAD=1 BUILD_DEPS=1 DEPS_ONLY=0 NDK_TARGET=21