Use popup browser for AAD auth in SDL client

Optionally build the SDL client with Qt WebEngine to create a popup
browser for authentication to AAD. Also change the URL output on the
command line to use the "nativeclient" redirect for easier copy/pasting
of the authorization code.
This commit is contained in:
fifthdegree 2023-05-17 13:25:37 -04:00 committed by akallabeth
parent ba7fdcb5f0
commit 449b96adb2
11 changed files with 225 additions and 45 deletions

View File

@ -98,10 +98,25 @@ set(LIBS
freerdp-client
)
option(WITH_WEBVIEW "Build with QtWebEngine support for AAD login popup browser" OFF)
if (WITH_WEBVIEW)
include(FindPkgConfig)
pkg_check_modules(QT REQUIRED Qt5WebEngineWidgets)
list(APPEND SRCS sdl_webview.cpp)
add_definitions(-DWITH_WEBVIEW)
include_directories(${QT_INCLUDE_DIRS})
list(APPEND LIBS ${QT_LIBRARIES})
endif()
add_executable(${PROJECT_NAME}
${SRCS}
)
if (QT_FOUND)
target_compile_options(${PROJECT_NAME} PRIVATE -fPIC)
endif()
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "sdl-freerdp")
target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBS})
set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Client/SDL")

View File

@ -54,6 +54,10 @@
#include "sdl_touch.hpp"
#include "sdl_pointer.hpp"
#ifdef WITH_WEBVIEW
#include "sdl_webview.hpp"
#endif
#define SDL_TAG CLIENT_TAG("SDL")
enum SDL_EXIT_CODE
@ -1171,6 +1175,11 @@ static BOOL sdl_client_new(freerdp* instance, rdpContext* context)
instance->VerifyCertificateEx = client_cli_verify_certificate_ex;
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;
#else
instance->GetAadAuthCode = client_cli_get_aad_auth_code;
#endif
/* TODO: Client display set up */
sdl->initialize = CreateEventA(nullptr, TRUE, FALSE, nullptr);

View File

@ -0,0 +1,96 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Popup browser for AAD authentication
*
* 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 <QApplication>
#include <QWebEngineView>
#include <QWebEngineProfile>
#include <QWebEngineUrlScheme>
#include <QWebEngineUrlSchemeHandler>
#include <QWebEngineUrlRequestJob>
#include <stdlib.h>
#include <winpr/string.h>
#include "sdl_webview.hpp"
class SchemeHandler : public QWebEngineUrlSchemeHandler
{
public:
SchemeHandler(char** code, QObject* parent = nullptr)
: QWebEngineUrlSchemeHandler(parent), codeptr(code){};
void requestStarted(QWebEngineUrlRequestJob* request)
{
QUrl url = request->requestUrl();
for (auto& param : url.query().split('&'))
{
QStringList pair = param.split('=');
if (pair.size() != 2 || pair[0] != "code")
continue;
QByteArray code = pair[1].toUtf8();
*codeptr = (char*)calloc(1, code.size() + 1);
strcpy(*codeptr, code.constData());
break;
}
qApp->exit();
}
private:
char** codeptr;
};
BOOL sdl_webview_get_aad_auth_code(freerdp* instance, const char* hostname, char** code,
const char** client_id, const char** redirect_uri)
{
int argc = 1;
char* name = "FreeRDP WebView";
size_t size = 0;
char* login_url = NULL;
*code = NULL;
*client_id = "5177bc73-fd99-4c77-a90c-76844c9b6999";
*redirect_uri =
"ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2f5177bc73-fd99-4c77-a90c-76844c9b6999";
winpr_asprintf(&login_url, &size,
"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",
*client_id, hostname, *redirect_uri);
QWebEngineUrlScheme::registerScheme(QWebEngineUrlScheme("ms-appx-web"));
QApplication app(argc, &name);
SchemeHandler handler(code);
QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("ms-appx-web", &handler);
QWebEngineView webview;
webview.load(QUrl(login_url));
webview.show();
app.exec();
free(login_url);
return (*code != NULL);
}

View File

@ -0,0 +1,34 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Popup browser for AAD authentication
*
* 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.
*/
#pragma once
#include <freerdp/freerdp.h>
#ifdef __cplusplus
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);
#ifdef __cplusplus
}
#endif

View File

@ -1879,6 +1879,7 @@ 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;
PubSub_SubscribeTerminate(context->pubSub, xf_TerminateEventHandler);
#ifdef WITH_XRENDER
PubSub_SubscribeZoomingChange(context->pubSub, xf_ZoomingChangeEventHandler);

View File

@ -61,8 +61,6 @@
#include <freerdp/log.h>
#define TAG CLIENT_TAG("common")
#define OAUTH2_CLIENT_ID "5177bc73-fd99-4c77-a90c-76844c9b6999"
static BOOL freerdp_client_common_new(freerdp* instance, rdpContext* context)
{
RDP_CLIENT_ENTRY_POINTS* pEntryPoints;
@ -77,7 +75,6 @@ static BOOL freerdp_client_common_new(freerdp* instance, rdpContext* context)
instance->VerifyChangedCertificateEx = client_cli_verify_changed_certificate_ex;
instance->PresentGatewayMessage = client_cli_present_gateway_message;
instance->LogonErrorInfo = client_cli_logon_error_info;
instance->GetAadAuthCode = client_cli_get_aad_auth_code;
pEntryPoints = instance->pClientEntryPoints;
WINPR_ASSERT(pEntryPoints);
@ -938,34 +935,57 @@ 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)
BOOL client_cli_get_aad_auth_code(freerdp* instance, const char* hostname, char** code,
const char** client_id, const char** redirect_uri)
{
size_t len = 0;
char* p = NULL;
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=" OAUTH2_CLIENT_ID
"&response_type=code"
"&scope=ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fname%%2F%s%%2Fuser_"
"impersonation"
"&redirect_uri=ms-appx-web%%3a%%2f%%2fMicrosoft.AAD.BrokerPlugin%%2f5177bc73-fd99-4c77-"
"a90c-76844c9b6999\n",
hostname);
printf("Paste authorization code here: ");
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, code, &len, stdin) < 0)
if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0)
return FALSE;
p = strpbrk(*code, "\r\n");
if (p)
*p = 0;
return TRUE;
for (char* p = strchr(url, '?'); p++ != NULL; p = strchr(p, '&'))
{
if (strncmp(p, "code=", 5) == 0)
{
char* end = NULL;
p += 5;
end = strchr(p, '&');
if (end)
*end = 0;
else
end = strchr(p, '\0');
*code = calloc(1, end - p);
if (!(*code))
break;
strcpy(*code, p);
break;
}
}
free(url);
return (*code != NULL);
}
BOOL client_auto_reconnect(freerdp* instance)

View File

@ -157,7 +157,8 @@ 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);
char** code, const char** client_id,
const char** redirect_uri);
FREERDP_API void
freerdp_client_OnChannelConnectedEventHandler(void* context,

View File

@ -126,7 +126,8 @@ 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);
typedef BOOL (*pGetAadAuthCode)(freerdp* instance, const char* hostname, char** code,
const char** client_id, const char** redirect_uri);
/** @brief Callback used if user interaction is required to accept
* an unknown certificate.

View File

@ -94,7 +94,6 @@ static int BIO_get_line(BIO* bio, char* buf, int size)
}
#endif
#define OAUTH2_CLIENT_ID "5177bc73-fd99-4c77-a90c-76844c9b6999"
static const char* auth_server = "login.microsoftonline.com";
static const char nonce_http_request[] = ""
@ -115,13 +114,13 @@ static const char token_http_request_header[] =
"\r\n";
static const char token_http_request_body[] =
""
"client_id=" OAUTH2_CLIENT_ID "&grant_type=authorization_code"
"client_id=%s&grant_type=authorization_code"
"&code=%s"
"&scope=ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fname%%2F%s%%2Fuser_"
"&scope=ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fid%%2F23444de0-61b7-42a2-"
"bef2-5512675a5f4d%%2Fuser_"
"impersonation"
"&req_cnf=%s"
"&redirect_uri=ms-appx-web%%3a%%2f%%2fMicrosoft.AAD.BrokerPlugin%%2f5177bc73-fd99-4c77-a90c-"
"76844c9b6999"
"&redirect_uri=%s"
"\r\n\r\n";
static BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key, char** e, char** n);
@ -408,7 +407,8 @@ fail:
return rc;
}
static BOOL aad_send_token_request(rdpAad* aad, BIO* bio, const char* auth_code)
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;
@ -416,8 +416,8 @@ static BOOL aad_send_token_request(rdpAad* aad, BIO* bio, const char* auth_code)
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, auth_code,
aad->hostname, aad->kid);
const int trc = winpr_asprintf(&req_body, &req_body_len, token_http_request_body, client_id,
auth_code, aad->kid, redirect_uri);
if (trc < 0)
goto fail;
const int trh = winpr_asprintf(&req_header, &req_header_len, token_http_request_header, trc);
@ -443,6 +443,8 @@ int aad_client_begin(rdpAad* aad)
SSL_CTX* ssl_ctx = NULL;
BIO* bio = NULL;
char* auth_code = NULL;
const char* client_id = NULL;
const char* redirect_uri = NULL;
WINPR_ASSERT(aad);
WINPR_ASSERT(aad->rdpcontext);
@ -481,7 +483,8 @@ int aad_client_begin(rdpAad* aad)
WLog_Print(aad->log, WLOG_ERROR, "instance->GetAadAuthCode == NULL");
goto fail;
}
const BOOL arc = instance->GetAadAuthCode(instance, aad->hostname, &auth_code);
const BOOL arc =
instance->GetAadAuthCode(instance, aad->hostname, &auth_code, &client_id, &redirect_uri);
if (!arc)
{
WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain authorization code");
@ -501,7 +504,7 @@ int aad_client_begin(rdpAad* aad)
goto fail;
/* Construct and send the token request message */
if (!aad_send_token_request(aad, bio, auth_code))
if (!aad_send_token_request(aad, bio, auth_code, client_id, redirect_uri))
goto fail;
/* Extract the access token from the JSON response */
@ -560,15 +563,15 @@ static char* aad_create_jws_payload(rdpAad* aad, const char* ts_nonce)
size_t bufferlen = 0;
const int length =
winpr_asprintf(&buffer, &bufferlen,
"{"
"\"ts\":\"%li\","
"\"at\":\"%s\","
"\"u\":\"ms-device-service://termsrv.wvd.microsoft.com/name/%s\","
"\"nonce\":\"%s\","
"\"cnf\":{\"jwk\":{\"kty\":\"RSA\",\"e\":\"%s\",\"n\":\"%s\"}},"
"\"client_claims\":\"{\\\"aad_nonce\\\":\\\"%s\\\"}\""
"}",
ts, aad->access_token, aad->hostname, ts_nonce, e, n, aad->nonce);
"{"
"\"ts\":\"%li\","
"\"at\":\"%s\","
"\"u\":\"ms-device-service://termsrv.wvd.microsoft.com/name/%s\","
"\"nonce\":\"%s\","
"\"cnf\":{\"jwk\":{\"kty\":\"RSA\",\"e\":\"%s\",\"n\":\"%s\"}},"
"\"client_claims\":\"{\\\"aad_nonce\\\":\\\"%s\\\"}\""
"}",
ts, aad->access_token, aad->hostname, ts_nonce, e, n, aad->nonce);
free(e);
free(n);

View File

@ -249,8 +249,8 @@ static BOOL build_pkinit_args(const rdpSettings* settings, SmartcardCertInfo* sc
const char* pkModule = Pkcs11Module ? Pkcs11Module : "opensc-pkcs11.so";
size_t size = 0;
if (winpr_asprintf(&scCert->pkinitArgs, &size, "PKCS11:module_name=%s:slotid=%" PRIu16, pkModule,
(UINT16)scCert->slotId) <= 0)
if (winpr_asprintf(&scCert->pkinitArgs, &size, "PKCS11:module_name=%s:slotid=%" PRIu16,
pkModule, (UINT16)scCert->slotId) <= 0)
return FALSE;
return TRUE;
}

View File

@ -38,7 +38,7 @@ extern "C"
WINPR_API BOOL winpr_str_append(const char* what, char* buffer, size_t size,
const char* separator);
WINPR_API int winpr_asprintf(char** s, size_t* slen, const char* templ, ...);
#ifndef _WIN32