20#include <freerdp/config.h>
25#include <freerdp/crypto/crypto.h>
26#include <freerdp/crypto/privatekey.h>
27#include "../crypto/privatekey.h"
28#include <freerdp/utils/http.h>
29#include <freerdp/utils/aad.h>
31#include <winpr/crypto.h>
32#include <winpr/json.h>
42 rdpContext* rdpcontext;
43 rdpTransport* transport;
55static BOOL aad_fetch_wellknown(wLog* log, rdpContext* context);
56static BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key,
char** e,
char** n);
57static BOOL generate_pop_key(rdpAad* aad);
59WINPR_ATTR_FORMAT_ARG(2, 3)
60static SSIZE_T stream_sprintf(
wStream* s, WINPR_FORMAT_ARG const
char* fmt, ...)
64 const int rc = vsnprintf(NULL, 0, fmt, ap);
70 if (!Stream_EnsureRemainingCapacity(s, (
size_t)rc + 1))
73 char* ptr = Stream_PointerAs(s,
char);
75 const int rc2 = vsnprintf(ptr, WINPR_ASSERTING_INT_CAST(
size_t, rc) + 1, fmt, ap);
79 if (!Stream_SafeSeek(s, (
size_t)rc2))
84static BOOL json_get_object(wLog* wlog, WINPR_JSON* json,
const char* key, WINPR_JSON** obj)
91 WLog_Print(wlog, WLOG_ERROR,
"[json] does not contain a key '%s'", key);
98 WLog_Print(wlog, WLOG_ERROR,
"[json] object for key '%s' is NULL", key);
105static BOOL json_get_number(wLog* wlog, WINPR_JSON* json,
const char* key,
double* result)
108 WINPR_JSON* prop = NULL;
109 if (!json_get_object(wlog, json, key, &prop))
114 WLog_Print(wlog, WLOG_ERROR,
"[json] object for key '%s' is NOT a NUMBER", key);
125static BOOL json_get_const_string(wLog* wlog, WINPR_JSON* json,
const char* key,
129 WINPR_ASSERT(result);
133 WINPR_JSON* prop = NULL;
134 if (!json_get_object(wlog, json, key, &prop))
139 WLog_Print(wlog, WLOG_ERROR,
"[json] object for key '%s' is NOT a STRING", key);
145 WLog_Print(wlog, WLOG_ERROR,
"[json] object for key '%s' is NULL", key);
153static BOOL json_get_string_alloc(wLog* wlog, WINPR_JSON* json,
const char* key,
char** result)
155 const char* str = NULL;
156 if (!json_get_const_string(wlog, json, key, &str))
159 *result = _strdup(str);
161 WLog_Print(wlog, WLOG_ERROR,
"[json] object for key '%s' strdup is NULL", key);
162 return *result != NULL;
165static inline const char* aad_auth_result_to_string(DWORD code)
167#define ERROR_CASE(cd, x) \
168 if ((cd) == (DWORD)(x)) \
171 ERROR_CASE(code, S_OK)
172 ERROR_CASE(code, SEC_E_INVALID_TOKEN)
173 ERROR_CASE(code, E_ACCESSDENIED)
174 ERROR_CASE(code, STATUS_LOGON_FAILURE)
175 ERROR_CASE(code, STATUS_NO_LOGON_SERVERS)
176 ERROR_CASE(code, STATUS_INVALID_LOGON_HOURS)
177 ERROR_CASE(code, STATUS_INVALID_WORKSTATION)
178 ERROR_CASE(code, STATUS_PASSWORD_EXPIRED)
179 ERROR_CASE(code, STATUS_ACCOUNT_DISABLED)
180 return "Unknown error";
183static BOOL ensure_wellknown(rdpContext* context)
185 if (context->rdp->wellknown)
188 rdpAad* aad = context->rdp->aad;
192 if (!aad_fetch_wellknown(aad->log, context))
194 return context->rdp->wellknown != NULL;
197static BOOL aad_get_nonce(rdpAad* aad)
200 BYTE* response = NULL;
202 size_t response_length = 0;
203 WINPR_JSON* json = NULL;
206 WINPR_ASSERT(aad->rdpcontext);
208 rdpRdp* rdp = aad->rdpcontext->rdp;
211 if (!ensure_wellknown(aad->rdpcontext))
217 WLog_Print(aad->log, WLOG_ERROR,
"wellknown does not have 'token_endpoint', aborting");
223 WLog_Print(aad->log, WLOG_ERROR,
224 "wellknown does have 'token_endpoint=NULL' value, aborting");
228 if (!freerdp_http_request(url,
"grant_type=srv_challenge", &resp_code, &response,
231 WLog_Print(aad->log, WLOG_ERROR,
"nonce request failed");
235 if (resp_code != HTTP_STATUS_OK)
237 WLog_Print(aad->log, WLOG_ERROR,
238 "Server unwilling to provide nonce; returned status code %li", resp_code);
239 if (response_length > 0)
240 WLog_Print(aad->log, WLOG_ERROR,
"[status message] %s", response);
247 WLog_Print(aad->log, WLOG_ERROR,
"Failed to parse nonce response: %s",
252 if (!json_get_string_alloc(aad->log, json,
"Nonce", &aad->nonce))
263int aad_client_begin(rdpAad* aad)
268 WINPR_ASSERT(aad->rdpcontext);
270 rdpSettings* settings = aad->rdpcontext->settings;
271 WINPR_ASSERT(settings);
279 WLog_Print(aad->log, WLOG_ERROR,
"hostname == NULL");
283 aad->hostname = _strdup(hostname);
286 WLog_Print(aad->log, WLOG_ERROR,
"_strdup(hostname) == NULL");
290 char* p = strchr(aad->hostname,
'.');
294 if (winpr_asprintf(&aad->scope, &size,
295 "ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fname%%2F%s%%"
296 "2Fuser_impersonation",
300 if (!generate_pop_key(aad))
304 pGetCommonAccessToken GetCommonAccessToken = freerdp_get_common_access_token(aad->rdpcontext);
305 if (!GetCommonAccessToken)
307 WLog_Print(aad->log, WLOG_ERROR,
"GetCommonAccessToken == NULL");
311 if (!aad_fetch_wellknown(aad->log, aad->rdpcontext))
314 const BOOL arc = GetCommonAccessToken(aad->rdpcontext, ACCESS_TOKEN_TYPE_AAD,
315 &aad->access_token, 2, aad->scope, aad->kid);
318 WLog_Print(aad->log, WLOG_ERROR,
"Unable to obtain access token");
323 if (!aad_get_nonce(aad))
325 WLog_Print(aad->log, WLOG_ERROR,
"Unable to obtain nonce");
332static char* aad_create_jws_header(rdpAad* aad)
338 size_t bufferlen = 0;
340 winpr_asprintf(&buffer, &bufferlen,
"{\"alg\":\"RS256\",\"kid\":\"%s\"}", aad->kid);
344 char* jws_header = crypto_base64url_encode((
const BYTE*)buffer, bufferlen);
349static char* aad_create_jws_payload(rdpAad* aad,
const char* ts_nonce)
351 const time_t ts = time(NULL);
357 if (!get_encoded_rsa_params(aad->log, aad->key, &e, &n))
362 size_t bufferlen = 0;
364 winpr_asprintf(&buffer, &bufferlen,
368 "\"u\":\"ms-device-service://termsrv.wvd.microsoft.com/name/%s\","
370 "\"cnf\":{\"jwk\":{\"kty\":\"RSA\",\"e\":\"%s\",\"n\":\"%s\"}},"
371 "\"client_claims\":\"{\\\"aad_nonce\\\":\\\"%s\\\"}\""
373 ts, aad->access_token, aad->hostname, ts_nonce, e, n, aad->nonce);
380 char* jws_payload = crypto_base64url_encode((BYTE*)buffer, bufferlen);
385static BOOL aad_update_digest(rdpAad* aad, WINPR_DIGEST_CTX* ctx,
const char* what)
391 const BOOL dsu1 = winpr_DigestSign_Update(ctx, what, strlen(what));
394 WLog_Print(aad->log, WLOG_ERROR,
"winpr_DigestSign_Update [%s] failed", what);
400static char* aad_final_digest(rdpAad* aad, WINPR_DIGEST_CTX* ctx)
402 char* jws_signature = NULL;
408 const int dsf = winpr_DigestSign_Final(ctx, NULL, &siglen);
411 WLog_Print(aad->log, WLOG_ERROR,
"winpr_DigestSign_Final failed with %d", dsf);
415 char* buffer = calloc(siglen + 1,
sizeof(
char));
418 WLog_Print(aad->log, WLOG_ERROR,
"calloc %" PRIuz
" bytes failed", siglen + 1);
422 size_t fsiglen = siglen;
423 const int dsf2 = winpr_DigestSign_Final(ctx, (BYTE*)buffer, &fsiglen);
426 WLog_Print(aad->log, WLOG_ERROR,
"winpr_DigestSign_Final failed with %d", dsf2);
430 if (siglen != fsiglen)
432 WLog_Print(aad->log, WLOG_ERROR,
433 "winpr_DigestSignFinal returned different sizes, first %" PRIuz
" then %" PRIuz,
437 jws_signature = crypto_base64url_encode((
const BYTE*)buffer, fsiglen);
440 return jws_signature;
443static char* aad_create_jws_signature(rdpAad* aad,
const char* jws_header,
const char* jws_payload)
445 char* jws_signature = NULL;
449 WINPR_DIGEST_CTX* md_ctx = freerdp_key_digest_sign(aad->key, WINPR_MD_SHA256);
452 WLog_Print(aad->log, WLOG_ERROR,
"winpr_Digest_New failed");
456 if (!aad_update_digest(aad, md_ctx, jws_header))
458 if (!aad_update_digest(aad, md_ctx,
"."))
460 if (!aad_update_digest(aad, md_ctx, jws_payload))
463 jws_signature = aad_final_digest(aad, md_ctx);
465 winpr_Digest_Free(md_ctx);
466 return jws_signature;
469static int aad_send_auth_request(rdpAad* aad,
const char* ts_nonce)
472 char* jws_header = NULL;
473 char* jws_payload = NULL;
474 char* jws_signature = NULL;
477 WINPR_ASSERT(ts_nonce);
479 wStream* s = Stream_New(NULL, 1024);
484 jws_header = aad_create_jws_header(aad);
489 jws_payload = aad_create_jws_payload(aad, ts_nonce);
494 jws_signature = aad_create_jws_signature(aad, jws_header, jws_payload);
499 if (stream_sprintf(s,
"{\"rdp_assertion\":\"%s.%s.%s\"}", jws_header, jws_payload,
504 Stream_Write_UINT8(s, 0);
506 Stream_SealLength(s);
508 if (transport_write(aad->transport, s) < 0)
510 WLog_Print(aad->log, WLOG_ERROR,
"transport_write [%" PRIuz
" bytes] failed",
516 aad->state = AAD_STATE_AUTH;
519 Stream_Free(s, TRUE);
527static int aad_parse_state_initial(rdpAad* aad,
wStream* s)
529 const char* jstr = Stream_PointerAs(s,
char);
530 const size_t jlen = Stream_GetRemainingLength(s);
531 const char* ts_nonce = NULL;
533 WINPR_JSON* json = NULL;
535 if (!Stream_SafeSeek(s, jlen))
541 WLog_Print(aad->log, WLOG_ERROR,
"WINPR_JSON_ParseWithLength failed: %s",
546 if (!json_get_const_string(aad->log, json,
"ts_nonce", &ts_nonce))
549 ret = aad_send_auth_request(aad, ts_nonce);
555static int aad_parse_state_auth(rdpAad* aad,
wStream* s)
559 DWORD error_code = 0;
560 WINPR_JSON* json = NULL;
561 const char* jstr = Stream_PointerAs(s,
char);
562 const size_t jlength = Stream_GetRemainingLength(s);
564 if (!Stream_SafeSeek(s, jlength))
570 WLog_Print(aad->log, WLOG_ERROR,
"WINPR_JSON_ParseWithLength: %s",
575 if (!json_get_number(aad->log, json,
"authentication_result", &result))
577 error_code = (DWORD)result;
579 if (error_code != S_OK)
581 WLog_Print(aad->log, WLOG_ERROR,
"Authentication result: %s (0x%08" PRIx32
")",
582 aad_auth_result_to_string(error_code), error_code);
585 aad->state = AAD_STATE_FINAL;
592int aad_recv(rdpAad* aad,
wStream* s)
599 case AAD_STATE_INITIAL:
600 return aad_parse_state_initial(aad, s);
602 return aad_parse_state_auth(aad, s);
603 case AAD_STATE_FINAL:
605 WLog_Print(aad->log, WLOG_ERROR,
"Invalid AAD_STATE %u", aad->state);
610static BOOL generate_rsa_2048(rdpAad* aad)
613 return freerdp_key_generate(aad->key,
"RSA", 1, 2048);
616static char* generate_rsa_digest_base64_str(rdpAad* aad,
const char* input,
size_t ilen)
619 WINPR_DIGEST_CTX* digest = winpr_Digest_New();
622 WLog_Print(aad->log, WLOG_ERROR,
"winpr_Digest_New failed");
626 if (!winpr_Digest_Init(digest, WINPR_MD_SHA256))
628 WLog_Print(aad->log, WLOG_ERROR,
"winpr_Digest_Init(WINPR_MD_SHA256) failed");
632 if (!winpr_Digest_Update(digest, (
const BYTE*)input, ilen))
634 WLog_Print(aad->log, WLOG_ERROR,
"winpr_Digest_Update(%" PRIuz
") failed", ilen);
638 BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = { 0 };
639 if (!winpr_Digest_Final(digest, hash,
sizeof(hash)))
641 WLog_Print(aad->log, WLOG_ERROR,
"winpr_Digest_Final(%" PRIuz
") failed",
sizeof(hash));
646 b64 = crypto_base64url_encode(hash,
sizeof(hash));
649 winpr_Digest_Free(digest);
653static BOOL generate_json_base64_str(rdpAad* aad,
const char* b64_hash)
659 const int length = winpr_asprintf(&buffer, &blen,
"{\"kid\":\"%s\"}", b64_hash);
665 aad->kid = crypto_base64url_encode((
const BYTE*)buffer, (
size_t)length);
675BOOL generate_pop_key(rdpAad* aad)
679 char* b64_hash = NULL;
686 if (!generate_rsa_2048(aad))
690 if (!get_encoded_rsa_params(aad->log, aad->key, &e, &n))
695 winpr_asprintf(&buffer, &blen,
"{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}", e, n);
700 b64_hash = generate_rsa_digest_base64_str(aad, buffer, blen);
705 ret = generate_json_base64_str(aad, b64_hash);
715static char* bn_to_base64_url(wLog* wlog, rdpPrivateKey* key,
enum FREERDP_KEY_PARAM param)
721 BYTE* bn = freerdp_key_get_param(key, param, &len);
725 char* b64 = crypto_base64url_encode(bn, len);
729 WLog_Print(wlog, WLOG_ERROR,
"failed base64 url encode BIGNUM");
734BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key,
char** pe,
char** pn)
748 e = bn_to_base64_url(wlog, key, FREERDP_KEY_PARAM_RSA_E);
751 WLog_Print(wlog, WLOG_ERROR,
"failed base64 url encode RSA E");
755 n = bn_to_base64_url(wlog, key, FREERDP_KEY_PARAM_RSA_N);
758 WLog_Print(wlog, WLOG_ERROR,
"failed base64 url encode RSA N");
777int aad_client_begin(rdpAad* aad)
780 WLog_Print(aad->log, WLOG_ERROR,
"AAD security not compiled in, aborting!");
784int aad_recv(rdpAad* aad,
wStream* s)
787 WLog_Print(aad->log, WLOG_ERROR,
"AAD security not compiled in, aborting!");
791static BOOL ensure_wellknown(WINPR_ATTR_UNUSED rdpContext* context)
798rdpAad* aad_new(rdpContext* context, rdpTransport* transport)
800 WINPR_ASSERT(transport);
801 WINPR_ASSERT(context);
803 rdpAad* aad = (rdpAad*)calloc(1,
sizeof(rdpAad));
808 aad->log = WLog_Get(FREERDP_TAG(
"aad"));
809 aad->key = freerdp_key_new();
812 aad->rdpcontext = context;
813 aad->transport = transport;
817 WINPR_PRAGMA_DIAG_PUSH
818 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
820 WINPR_PRAGMA_DIAG_POP
824void aad_free(rdpAad* aad)
832 free(aad->access_token);
834 freerdp_key_free(aad->key);
839AAD_STATE aad_get_state(rdpAad* aad)
845BOOL aad_is_supported(
void)
854char* freerdp_utils_aad_get_access_token(wLog* log,
const char* data,
size_t length)
857 WINPR_JSON* access_token_prop = NULL;
858 const char* access_token_str = NULL;
863 WLog_Print(log, WLOG_ERROR,
864 "Failed to parse access token response [got %" PRIuz
" bytes: %s", length,
870 if (!access_token_prop)
872 WLog_Print(log, WLOG_ERROR,
"Response has no \"access_token\" property");
877 if (!access_token_str)
879 WLog_Print(log, WLOG_ERROR,
"Invalid value for \"access_token\"");
883 token = _strdup(access_token_str);
890BOOL aad_fetch_wellknown(wLog* log, rdpContext* context)
892 WINPR_ASSERT(context);
894 rdpRdp* rdp = context->rdp;
902 const BOOL useTenant =
904 const char* tenantid =
"common";
907 rdp->wellknown = freerdp_utils_aad_get_wellknown(log, base, tenantid);
908 return rdp->wellknown ? TRUE : FALSE;
911const char* freerdp_utils_aad_get_wellknown_string(rdpContext* context, AAD_WELLKNOWN_VALUES which)
913 return freerdp_utils_aad_get_wellknown_custom_string(
914 context, freerdp_utils_aad_wellknwon_value_name(which));
917const char* freerdp_utils_aad_get_wellknown_custom_string(rdpContext* context,
const char* which)
919 WINPR_ASSERT(context);
920 WINPR_ASSERT(context->rdp);
922 if (!ensure_wellknown(context))
932const char* freerdp_utils_aad_wellknwon_value_name(AAD_WELLKNOWN_VALUES which)
936 case AAD_WELLKNOWN_token_endpoint:
937 return "token_endpoint";
938 case AAD_WELLKNOWN_token_endpoint_auth_methods_supported:
939 return "token_endpoint_auth_methods_supported";
940 case AAD_WELLKNOWN_jwks_uri:
942 case AAD_WELLKNOWN_response_modes_supported:
943 return "response_modes_supported";
944 case AAD_WELLKNOWN_subject_types_supported:
945 return "subject_types_supported";
946 case AAD_WELLKNOWN_id_token_signing_alg_values_supported:
947 return "id_token_signing_alg_values_supported";
948 case AAD_WELLKNOWN_response_types_supported:
949 return "response_types_supported";
950 case AAD_WELLKNOWN_scopes_supported:
951 return "scopes_supported";
952 case AAD_WELLKNOWN_issuer:
954 case AAD_WELLKNOWN_request_uri_parameter_supported:
955 return "request_uri_parameter_supported";
956 case AAD_WELLKNOWN_userinfo_endpoint:
957 return "userinfo_endpoint";
958 case AAD_WELLKNOWN_authorization_endpoint:
959 return "authorization_endpoint";
960 case AAD_WELLKNOWN_device_authorization_endpoint:
961 return "device_authorization_endpoint";
962 case AAD_WELLKNOWN_http_logout_supported:
963 return "http_logout_supported";
964 case AAD_WELLKNOWN_frontchannel_logout_supported:
965 return "frontchannel_logout_supported";
966 case AAD_WELLKNOWN_end_session_endpoint:
967 return "end_session_endpoint";
968 case AAD_WELLKNOWN_claims_supported:
969 return "claims_supported";
970 case AAD_WELLKNOWN_kerberos_endpoint:
971 return "kerberos_endpoint";
972 case AAD_WELLKNOWN_tenant_region_scope:
973 return "tenant_region_scope";
974 case AAD_WELLKNOWN_cloud_instance_name:
975 return "cloud_instance_name";
976 case AAD_WELLKNOWN_cloud_graph_host_name:
977 return "cloud_graph_host_name";
978 case AAD_WELLKNOWN_msgraph_host:
979 return "msgraph_host";
980 case AAD_WELLKNOWN_rbac_url:
987WINPR_JSON* freerdp_utils_aad_get_wellknown_object(rdpContext* context, AAD_WELLKNOWN_VALUES which)
989 return freerdp_utils_aad_get_wellknown_custom_object(
990 context, freerdp_utils_aad_wellknwon_value_name(which));
993WINPR_JSON* freerdp_utils_aad_get_wellknown_custom_object(rdpContext* context,
const char* which)
995 WINPR_ASSERT(context);
996 WINPR_ASSERT(context->rdp);
998 if (!ensure_wellknown(context))
1005WINPR_JSON* freerdp_utils_aad_get_wellknown(wLog* log, const
char* base, const
char* tenantid)
1008 WINPR_ASSERT(tenantid);
1012 winpr_asprintf(&str, &len,
"https://%s/%s/v2.0/.well-known/openid-configuration", base,
1017 WLog_Print(log, WLOG_ERROR,
"failed to create request URL for tenantid='%s'", tenantid);
1021 BYTE* response = NULL;
1023 size_t response_length = 0;
1024 const BOOL rc = freerdp_http_request(str, NULL, &resp_code, &response, &response_length);
1025 if (!rc || (resp_code != HTTP_STATUS_OK))
1027 WLog_Print(log, WLOG_ERROR,
"request for '%s' failed with: %s", str,
1028 freerdp_http_status_string(resp_code));
1039 WLog_Print(log, WLOG_ERROR,
"failed to parse response as JSON: %s",
WINPR_API BOOL WINPR_JSON_HasObjectItem(const WINPR_JSON *object, const char *string)
Check if JSON has an object matching the name.
WINPR_API BOOL WINPR_JSON_IsString(const WINPR_JSON *item)
Check if JSON item is of type String.
WINPR_API double WINPR_JSON_GetNumberValue(const WINPR_JSON *item)
Return the Number value of a JSON item.
WINPR_API BOOL WINPR_JSON_IsNumber(const WINPR_JSON *item)
Check if JSON item is of type Number.
WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItemCaseSensitive(const WINPR_JSON *object, const char *string)
Same as WINPR_JSON_GetObjectItem but with case sensitive matching.
WINPR_API WINPR_JSON * WINPR_JSON_ParseWithLength(const char *value, size_t buffer_length)
Parse a JSON string.
WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
WINPR_API void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
WINPR_API const char * WINPR_JSON_GetErrorPtr(void)
Return an error string.
FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.
FREERDP_API const char * freerdp_settings_get_server_name(const rdpSettings *settings)
A helper function to return the correct server name.
FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.