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>
34 #include "transport.h"
41 rdpContext* rdpcontext;
42 rdpTransport* transport;
54 static BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key,
char** e,
char** n);
55 static BOOL generate_pop_key(rdpAad* aad);
57 WINPR_ATTR_FORMAT_ARG(2, 3)
58 static SSIZE_T stream_sprintf(
wStream* s, WINPR_FORMAT_ARG const
char* fmt, ...)
62 const int rc = vsnprintf(NULL, 0, fmt, ap);
68 if (!Stream_EnsureRemainingCapacity(s, (
size_t)rc + 1))
71 char* ptr = Stream_PointerAs(s,
char);
73 const int rc2 = vsnprintf(ptr, rc + 1, fmt, ap);
77 if (!Stream_SafeSeek(s, (
size_t)rc2))
82 static BOOL json_get_object(wLog* wlog, WINPR_JSON* json,
const char* key, WINPR_JSON** obj)
89 WLog_Print(wlog, WLOG_ERROR,
"[json] does not contain a key '%s'", key);
96 WLog_Print(wlog, WLOG_ERROR,
"[json] object for key '%s' is NULL", key);
103 static BOOL json_get_number(wLog* wlog, WINPR_JSON* json,
const char* key,
double* result)
106 WINPR_JSON* prop = NULL;
107 if (!json_get_object(wlog, json, key, &prop))
112 WLog_Print(wlog, WLOG_ERROR,
"[json] object for key '%s' is NOT a NUMBER", key);
123 static BOOL json_get_const_string(wLog* wlog, WINPR_JSON* json,
const char* key,
127 WINPR_ASSERT(result);
131 WINPR_JSON* prop = NULL;
132 if (!json_get_object(wlog, json, key, &prop))
137 WLog_Print(wlog, WLOG_ERROR,
"[json] object for key '%s' is NOT a STRING", key);
143 WLog_Print(wlog, WLOG_ERROR,
"[json] object for key '%s' is NULL", key);
151 static BOOL json_get_string_alloc(wLog* wlog, WINPR_JSON* json,
const char* key,
char** result)
153 const char* str = NULL;
154 if (!json_get_const_string(wlog, json, key, &str))
157 *result = _strdup(str);
159 WLog_Print(wlog, WLOG_ERROR,
"[json] object for key '%s' strdup is NULL", key);
160 return *result != NULL;
163 static INLINE
const char* aad_auth_result_to_string(DWORD code)
165 #define ERROR_CASE(cd, x) \
166 if ((cd) == (DWORD)(x)) \
169 ERROR_CASE(code, S_OK)
170 ERROR_CASE(code, SEC_E_INVALID_TOKEN)
171 ERROR_CASE(code, E_ACCESSDENIED)
172 ERROR_CASE(code, STATUS_LOGON_FAILURE)
173 ERROR_CASE(code, STATUS_NO_LOGON_SERVERS)
174 ERROR_CASE(code, STATUS_INVALID_LOGON_HOURS)
175 ERROR_CASE(code, STATUS_INVALID_WORKSTATION)
176 ERROR_CASE(code, STATUS_PASSWORD_EXPIRED)
177 ERROR_CASE(code, STATUS_ACCOUNT_DISABLED)
178 return "Unknown error";
181 static BOOL aad_get_nonce(rdpAad* aad)
184 BYTE* response = NULL;
186 size_t response_length = 0;
187 WINPR_JSON* json = NULL;
189 if (!freerdp_http_request(
"https://login.microsoftonline.com/common/oauth2/v2.0/token",
190 "grant_type=srv_challenge", &resp_code, &response, &response_length))
192 WLog_Print(aad->log, WLOG_ERROR,
"nonce request failed");
196 if (resp_code != HTTP_STATUS_OK)
198 WLog_Print(aad->log, WLOG_ERROR,
199 "Server unwilling to provide nonce; returned status code %li", resp_code);
200 if (response_length > 0)
201 WLog_Print(aad->log, WLOG_ERROR,
"[status message] %s", response);
208 WLog_Print(aad->log, WLOG_ERROR,
"Failed to parse nonce response");
212 if (!json_get_string_alloc(aad->log, json,
"Nonce", &aad->nonce))
223 int aad_client_begin(rdpAad* aad)
228 WINPR_ASSERT(aad->rdpcontext);
230 rdpSettings* settings = aad->rdpcontext->settings;
231 WINPR_ASSERT(settings);
233 freerdp* instance = aad->rdpcontext->instance;
234 WINPR_ASSERT(instance);
242 WLog_Print(aad->log, WLOG_ERROR,
"FreeRDP_ServerHostname == NULL");
246 aad->hostname = _strdup(hostname);
249 WLog_Print(aad->log, WLOG_ERROR,
"_strdup(FreeRDP_ServerHostname) == NULL");
253 char* p = strchr(aad->hostname,
'.');
257 if (winpr_asprintf(&aad->scope, &size,
258 "ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fname%%2F%s%%"
259 "2Fuser_impersonation",
263 if (!generate_pop_key(aad))
267 if (!instance->GetAccessToken)
269 WLog_Print(aad->log, WLOG_ERROR,
"instance->GetAccessToken == NULL");
272 const BOOL arc = instance->GetAccessToken(instance, ACCESS_TOKEN_TYPE_AAD, &aad->access_token,
273 2, aad->scope, aad->kid);
276 WLog_Print(aad->log, WLOG_ERROR,
"Unable to obtain access token");
281 if (!aad_get_nonce(aad))
283 WLog_Print(aad->log, WLOG_ERROR,
"Unable to obtain nonce");
290 static char* aad_create_jws_header(rdpAad* aad)
296 size_t bufferlen = 0;
298 winpr_asprintf(&buffer, &bufferlen,
"{\"alg\":\"RS256\",\"kid\":\"%s\"}", aad->kid);
302 char* jws_header = crypto_base64url_encode((
const BYTE*)buffer, bufferlen);
307 static char* aad_create_jws_payload(rdpAad* aad,
const char* ts_nonce)
309 const time_t ts = time(NULL);
315 if (!get_encoded_rsa_params(aad->log, aad->key, &e, &n))
320 size_t bufferlen = 0;
322 winpr_asprintf(&buffer, &bufferlen,
326 "\"u\":\"ms-device-service://termsrv.wvd.microsoft.com/name/%s\","
328 "\"cnf\":{\"jwk\":{\"kty\":\"RSA\",\"e\":\"%s\",\"n\":\"%s\"}},"
329 "\"client_claims\":\"{\\\"aad_nonce\\\":\\\"%s\\\"}\""
331 ts, aad->access_token, aad->hostname, ts_nonce, e, n, aad->nonce);
338 char* jws_payload = crypto_base64url_encode((BYTE*)buffer, bufferlen);
343 static BOOL aad_update_digest(rdpAad* aad, WINPR_DIGEST_CTX* ctx,
const char* what)
349 const BOOL dsu1 = winpr_DigestSign_Update(ctx, what, strlen(what));
352 WLog_Print(aad->log, WLOG_ERROR,
"winpr_DigestSign_Update [%s] failed", what);
358 static char* aad_final_digest(rdpAad* aad, WINPR_DIGEST_CTX* ctx)
360 char* jws_signature = NULL;
366 const int dsf = winpr_DigestSign_Final(ctx, NULL, &siglen);
369 WLog_Print(aad->log, WLOG_ERROR,
"winpr_DigestSign_Final failed with %d", dsf);
373 char* buffer = calloc(siglen + 1,
sizeof(
char));
376 WLog_Print(aad->log, WLOG_ERROR,
"calloc %" PRIuz
" bytes failed", siglen + 1);
380 size_t fsiglen = siglen;
381 const int dsf2 = winpr_DigestSign_Final(ctx, (BYTE*)buffer, &fsiglen);
384 WLog_Print(aad->log, WLOG_ERROR,
"winpr_DigestSign_Final failed with %d", dsf2);
388 if (siglen != fsiglen)
390 WLog_Print(aad->log, WLOG_ERROR,
391 "winpr_DigestSignFinal returned different sizes, first %" PRIuz
" then %" PRIuz,
395 jws_signature = crypto_base64url_encode((
const BYTE*)buffer, fsiglen);
398 return jws_signature;
401 static char* aad_create_jws_signature(rdpAad* aad,
const char* jws_header,
const char* jws_payload)
403 char* jws_signature = NULL;
407 WINPR_DIGEST_CTX* md_ctx = freerdp_key_digest_sign(aad->key, WINPR_MD_SHA256);
410 WLog_Print(aad->log, WLOG_ERROR,
"winpr_Digest_New failed");
414 if (!aad_update_digest(aad, md_ctx, jws_header))
416 if (!aad_update_digest(aad, md_ctx,
"."))
418 if (!aad_update_digest(aad, md_ctx, jws_payload))
421 jws_signature = aad_final_digest(aad, md_ctx);
423 winpr_Digest_Free(md_ctx);
424 return jws_signature;
427 static int aad_send_auth_request(rdpAad* aad,
const char* ts_nonce)
430 char* jws_header = NULL;
431 char* jws_payload = NULL;
432 char* jws_signature = NULL;
435 WINPR_ASSERT(ts_nonce);
437 wStream* s = Stream_New(NULL, 1024);
442 jws_header = aad_create_jws_header(aad);
447 jws_payload = aad_create_jws_payload(aad, ts_nonce);
452 jws_signature = aad_create_jws_signature(aad, jws_header, jws_payload);
457 if (stream_sprintf(s,
"{\"rdp_assertion\":\"%s.%s.%s\"}", jws_header, jws_payload,
462 Stream_Write_UINT8(s, 0);
464 Stream_SealLength(s);
466 if (transport_write(aad->transport, s) < 0)
468 WLog_Print(aad->log, WLOG_ERROR,
"transport_write [%" PRIdz
" bytes] failed",
474 aad->state = AAD_STATE_AUTH;
477 Stream_Free(s, TRUE);
485 static int aad_parse_state_initial(rdpAad* aad,
wStream* s)
487 const char* jstr = Stream_PointerAs(s,
char);
488 const size_t jlen = Stream_GetRemainingLength(s);
489 const char* ts_nonce = NULL;
491 WINPR_JSON* json = NULL;
493 if (!Stream_SafeSeek(s, jlen))
500 if (!json_get_const_string(aad->log, json,
"ts_nonce", &ts_nonce))
503 ret = aad_send_auth_request(aad, ts_nonce);
509 static int aad_parse_state_auth(rdpAad* aad,
wStream* s)
513 DWORD error_code = 0;
514 WINPR_JSON* json = NULL;
515 const char* jstr = Stream_PointerAs(s,
char);
516 const size_t jlength = Stream_GetRemainingLength(s);
518 if (!Stream_SafeSeek(s, jlength))
525 if (!json_get_number(aad->log, json,
"authentication_result", &result))
527 error_code = (DWORD)result;
529 if (error_code != S_OK)
531 WLog_Print(aad->log, WLOG_ERROR,
"Authentication result: %s (0x%08" PRIx32
")",
532 aad_auth_result_to_string(error_code), error_code);
535 aad->state = AAD_STATE_FINAL;
542 int aad_recv(rdpAad* aad,
wStream* s)
549 case AAD_STATE_INITIAL:
550 return aad_parse_state_initial(aad, s);
552 return aad_parse_state_auth(aad, s);
553 case AAD_STATE_FINAL:
555 WLog_Print(aad->log, WLOG_ERROR,
"Invalid AAD_STATE %d", aad->state);
560 static BOOL generate_rsa_2048(rdpAad* aad)
563 return freerdp_key_generate(aad->key, 2048);
566 static char* generate_rsa_digest_base64_str(rdpAad* aad,
const char* input,
size_t ilen)
569 WINPR_DIGEST_CTX* digest = winpr_Digest_New();
572 WLog_Print(aad->log, WLOG_ERROR,
"winpr_Digest_New failed");
576 if (!winpr_Digest_Init(digest, WINPR_MD_SHA256))
578 WLog_Print(aad->log, WLOG_ERROR,
"winpr_Digest_Init(WINPR_MD_SHA256) failed");
582 if (!winpr_Digest_Update(digest, (
const BYTE*)input, ilen))
584 WLog_Print(aad->log, WLOG_ERROR,
"winpr_Digest_Update(%" PRIuz
") failed", ilen);
588 BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = { 0 };
589 if (!winpr_Digest_Final(digest, hash,
sizeof(hash)))
591 WLog_Print(aad->log, WLOG_ERROR,
"winpr_Digest_Final(%" PRIuz
") failed",
sizeof(hash));
596 b64 = crypto_base64url_encode(hash,
sizeof(hash));
599 winpr_Digest_Free(digest);
603 static BOOL generate_json_base64_str(rdpAad* aad,
const char* b64_hash)
609 const int length = winpr_asprintf(&buffer, &blen,
"{\"kid\":\"%s\"}", b64_hash);
615 aad->kid = crypto_base64url_encode((
const BYTE*)buffer, (
size_t)length);
625 BOOL generate_pop_key(rdpAad* aad)
629 char* b64_hash = NULL;
636 if (!generate_rsa_2048(aad))
640 if (!get_encoded_rsa_params(aad->log, aad->key, &e, &n))
645 winpr_asprintf(&buffer, &blen,
"{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}", e, n);
650 b64_hash = generate_rsa_digest_base64_str(aad, buffer, blen);
655 ret = generate_json_base64_str(aad, b64_hash);
665 static char* bn_to_base64_url(wLog* wlog, rdpPrivateKey* key,
enum FREERDP_KEY_PARAM param)
671 BYTE* bn = freerdp_key_get_param(key, param, &len);
675 char* b64 = crypto_base64url_encode(bn, len);
679 WLog_Print(wlog, WLOG_ERROR,
"failed base64 url encode BIGNUM");
684 BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key,
char** pe,
char** pn)
698 e = bn_to_base64_url(wlog, key, FREERDP_KEY_PARAM_RSA_E);
701 WLog_Print(wlog, WLOG_ERROR,
"failed base64 url encode RSA E");
705 n = bn_to_base64_url(wlog, key, FREERDP_KEY_PARAM_RSA_N);
708 WLog_Print(wlog, WLOG_ERROR,
"failed base64 url encode RSA N");
727 int aad_client_begin(rdpAad* aad)
730 WLog_Print(aad->log, WLOG_ERROR,
"AAD security not compiled in, aborting!");
733 int aad_recv(rdpAad* aad,
wStream* s)
736 WLog_Print(aad->log, WLOG_ERROR,
"AAD security not compiled in, aborting!");
741 rdpAad* aad_new(rdpContext* context, rdpTransport* transport)
743 WINPR_ASSERT(transport);
744 WINPR_ASSERT(context);
746 rdpAad* aad = (rdpAad*)calloc(1,
sizeof(rdpAad));
751 aad->log = WLog_Get(FREERDP_TAG(
"aad"));
752 aad->key = freerdp_key_new();
755 aad->rdpcontext = context;
756 aad->transport = transport;
760 WINPR_PRAGMA_DIAG_PUSH
761 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
763 WINPR_PRAGMA_DIAG_POP
767 void aad_free(rdpAad* aad)
775 free(aad->access_token);
777 freerdp_key_free(aad->key);
782 AAD_STATE aad_get_state(rdpAad* aad)
788 BOOL aad_is_supported(
void)
798 char* freerdp_utils_aad_get_access_token(wLog* log,
const char* data,
size_t length)
801 WINPR_JSON* access_token_prop = NULL;
802 const char* access_token_str = NULL;
807 WLog_Print(log, WLOG_ERROR,
"Failed to parse access token response [got %" PRIuz
" bytes",
813 if (!access_token_prop)
815 WLog_Print(log, WLOG_ERROR,
"Response has no \"access_token\" property");
820 if (!access_token_str)
822 WLog_Print(log, WLOG_ERROR,
"Invalid value for \"access_token\"");
826 token = _strdup(access_token_str);
WINPR_API BOOL WINPR_JSON_HasObjectItem(const WINPR_JSON *object, const char *string)
Check if JSON has an object matching the name.
WINPR_API WINPR_JSON * WINPR_JSON_ParseWithLength(const char *value, size_t buffer_length)
Parse a JSON string.
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 void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItem(const WINPR_JSON *object, const char *string)
Return a pointer to an JSON object item.
WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.