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
This commit is contained in:
fifthdegree 2023-07-02 12:16:01 -04:00 committed by akallabeth
parent b8c2b6abd7
commit d309fcd6e8
20 changed files with 609 additions and 438 deletions

View File

@ -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" )

View File

@ -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 */

View File

@ -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);
}

View File

@ -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
}

View File

@ -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);

View File

@ -58,6 +58,11 @@
#include <freerdp/gdi/video.h>
#endif
#ifdef WITH_AAD
#include <cjson/cJSON.h>
#include <freerdp/utils/http.h>
#endif
#include <freerdp/log.h>
#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)
{
size_t size = 0;
char* url = NULL;
WINPR_ASSERT(instance);
WINPR_ASSERT(hostname);
WINPR_ASSERT(code);
WINPR_ASSERT(client_id);
WINPR_ASSERT(redirect_uri);
*code = NULL;
*client_id = "a85cf173-4192-42f8-81fa-777a763e6e2c";
*redirect_uri = "https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient";
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"
"\n",
*client_id, hostname, *redirect_uri);
printf("Paste redirect URL here: \n");
if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0)
return FALSE;
WINPR_ASSERT(url);
for (char* p = strchr(url, '?'); p++ != NULL; p = strchr(p, '&'))
{
if (strncmp(p, "code=", 5) == 0)
{
if (strncmp(p, "code=", 5) != 0)
continue;
char* end = NULL;
p += 5;
end = strchr(p, '&');
if (end)
*end = 0;
*code = strdup(p);
}
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(scope);
WINPR_ASSERT(req_cnf);
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&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 (*code != NULL);
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;
}
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)

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -0,0 +1,28 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Smartcard Device Service Virtual Channel
*
* Copyright 2023 Isaac Klein <fifthdegree@protonmail.com>
*
* 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.h>
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 */

View File

@ -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()

View File

@ -25,23 +25,19 @@
#include <freerdp/crypto/crypto.h>
#include <freerdp/crypto/privatekey.h>
#include "../crypto/privatekey.h"
#include <freerdp/utils/http.h>
#ifdef WITH_CJSON
#ifdef WITH_AAD
#include <cjson/cJSON.h>
#endif
#include <winpr/crypto.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#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 <openssl/core_names.h>
#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;

View File

@ -44,7 +44,7 @@
#include "../utils.h"
#include "../settings.h"
#ifdef WITH_CJSON
#ifdef WITH_AAD
#include <cjson/cJSON.h>
#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

View File

@ -33,6 +33,7 @@ set(${MODULE_PREFIX}_SRCS
smartcard_pack.c
smartcard_call.c
stopwatch.c
http.c
)
freerdp_module_add(${${MODULE_PREFIX}_SRCS})

231
libfreerdp/utils/http.c Normal file
View File

@ -0,0 +1,231 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Simple HTTP client request utility
*
* Copyright 2023 Isaac Klein <fifthdegree@protonmail.com>
*
* 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 <freerdp/config.h>
#include <freerdp/utils/http.h>
#include <winpr/assert.h>
#include <winpr/string.h>
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <freerdp/log.h>
#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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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