FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
aad.c
1
20#include <freerdp/config.h>
21
22#include <stdio.h>
23#include <string.h>
24
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>
30
31#include <winpr/crypto.h>
32#include <winpr/json.h>
33
34#include "transport.h"
35#include "rdp.h"
36
37#include "aad.h"
38
39struct rdp_aad
40{
41 AAD_STATE state;
42 rdpContext* rdpcontext;
43 rdpTransport* transport;
44 char* access_token;
45 rdpPrivateKey* key;
46 char* kid;
47 char* nonce;
48 char* hostname;
49 char* scope;
50 wLog* log;
51};
52
53#ifdef WITH_AAD
54
55static BOOL aad_fetch_wellknown(rdpAad* aad);
56static BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key, char** e, char** n);
57static BOOL generate_pop_key(rdpAad* aad);
58
59WINPR_ATTR_FORMAT_ARG(2, 3)
60static SSIZE_T stream_sprintf(wStream* s, WINPR_FORMAT_ARG const char* fmt, ...)
61{
62 va_list ap = { 0 };
63 va_start(ap, fmt);
64 const int rc = vsnprintf(NULL, 0, fmt, ap);
65 va_end(ap);
66
67 if (rc < 0)
68 return rc;
69
70 if (!Stream_EnsureRemainingCapacity(s, (size_t)rc + 1))
71 return -1;
72
73 char* ptr = Stream_PointerAs(s, char);
74 va_start(ap, fmt);
75 const int rc2 = vsnprintf(ptr, WINPR_ASSERTING_INT_CAST(size_t, rc) + 1, fmt, ap);
76 va_end(ap);
77 if (rc != rc2)
78 return -23;
79 if (!Stream_SafeSeek(s, (size_t)rc2))
80 return -3;
81 return rc2;
82}
83
84static BOOL json_get_object(wLog* wlog, WINPR_JSON* json, const char* key, WINPR_JSON** obj)
85{
86 WINPR_ASSERT(json);
87 WINPR_ASSERT(key);
88
89 if (!WINPR_JSON_HasObjectItem(json, key))
90 {
91 WLog_Print(wlog, WLOG_ERROR, "[json] does not contain a key '%s'", key);
92 return FALSE;
93 }
94
95 WINPR_JSON* prop = WINPR_JSON_GetObjectItem(json, key);
96 if (!prop)
97 {
98 WLog_Print(wlog, WLOG_ERROR, "[json] object for key '%s' is NULL", key);
99 return FALSE;
100 }
101 *obj = prop;
102 return TRUE;
103}
104
105static BOOL json_get_number(wLog* wlog, WINPR_JSON* json, const char* key, double* result)
106{
107 BOOL rc = FALSE;
108 WINPR_JSON* prop = NULL;
109 if (!json_get_object(wlog, json, key, &prop))
110 return FALSE;
111
112 if (!WINPR_JSON_IsNumber(prop))
113 {
114 WLog_Print(wlog, WLOG_ERROR, "[json] object for key '%s' is NOT a NUMBER", key);
115 goto fail;
116 }
117
118 *result = WINPR_JSON_GetNumberValue(prop);
119
120 rc = TRUE;
121fail:
122 return rc;
123}
124
125static BOOL json_get_const_string(wLog* wlog, WINPR_JSON* json, const char* key,
126 const char** result)
127{
128 BOOL rc = FALSE;
129 WINPR_ASSERT(result);
130
131 *result = NULL;
132
133 WINPR_JSON* prop = NULL;
134 if (!json_get_object(wlog, json, key, &prop))
135 return FALSE;
136
137 if (!WINPR_JSON_IsString(prop))
138 {
139 WLog_Print(wlog, WLOG_ERROR, "[json] object for key '%s' is NOT a STRING", key);
140 goto fail;
141 }
142
143 const char* str = WINPR_JSON_GetStringValue(prop);
144 if (!str)
145 WLog_Print(wlog, WLOG_ERROR, "[json] object for key '%s' is NULL", key);
146 *result = str;
147 rc = str != NULL;
148
149fail:
150 return rc;
151}
152
153static BOOL json_get_string_alloc(wLog* wlog, WINPR_JSON* json, const char* key, char** result)
154{
155 const char* str = NULL;
156 if (!json_get_const_string(wlog, json, key, &str))
157 return FALSE;
158 free(*result);
159 *result = _strdup(str);
160 if (!*result)
161 WLog_Print(wlog, WLOG_ERROR, "[json] object for key '%s' strdup is NULL", key);
162 return *result != NULL;
163}
164
165static INLINE const char* aad_auth_result_to_string(DWORD code)
166{
167#define ERROR_CASE(cd, x) \
168 if ((cd) == (DWORD)(x)) \
169 return #x;
170
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";
181}
182
183static BOOL aad_get_nonce(rdpAad* aad)
184{
185 BOOL ret = FALSE;
186 BYTE* response = NULL;
187 long resp_code = 0;
188 size_t response_length = 0;
189 WINPR_JSON* json = NULL;
190
191 WINPR_ASSERT(aad);
192 WINPR_ASSERT(aad->rdpcontext);
193
194 rdpRdp* rdp = aad->rdpcontext->rdp;
195 WINPR_ASSERT(rdp);
196
197 WINPR_JSON* obj = WINPR_JSON_GetObjectItem(rdp->wellknown, "token_endpoint");
198 if (!obj)
199 {
200 WLog_Print(aad->log, WLOG_ERROR, "wellknown does not have 'token_endpoint', aborting");
201 return FALSE;
202 }
203 const char* url = WINPR_JSON_GetStringValue(obj);
204 if (!url)
205 {
206 WLog_Print(aad->log, WLOG_ERROR,
207 "wellknown does have 'token_endpoint=NULL' value, aborting");
208 return FALSE;
209 }
210
211 if (!freerdp_http_request(url, "grant_type=srv_challenge", &resp_code, &response,
212 &response_length))
213 {
214 WLog_Print(aad->log, WLOG_ERROR, "nonce request failed");
215 goto fail;
216 }
217
218 if (resp_code != HTTP_STATUS_OK)
219 {
220 WLog_Print(aad->log, WLOG_ERROR,
221 "Server unwilling to provide nonce; returned status code %li", resp_code);
222 if (response_length > 0)
223 WLog_Print(aad->log, WLOG_ERROR, "[status message] %s", response);
224 goto fail;
225 }
226
227 json = WINPR_JSON_ParseWithLength((const char*)response, response_length);
228 if (!json)
229 {
230 WLog_Print(aad->log, WLOG_ERROR, "Failed to parse nonce response");
231 goto fail;
232 }
233
234 if (!json_get_string_alloc(aad->log, json, "Nonce", &aad->nonce))
235 goto fail;
236
237 ret = TRUE;
238
239fail:
240 free(response);
241 WINPR_JSON_Delete(json);
242 return ret;
243}
244
245int aad_client_begin(rdpAad* aad)
246{
247 size_t size = 0;
248
249 WINPR_ASSERT(aad);
250 WINPR_ASSERT(aad->rdpcontext);
251
252 rdpSettings* settings = aad->rdpcontext->settings;
253 WINPR_ASSERT(settings);
254
255 freerdp* instance = aad->rdpcontext->instance;
256 WINPR_ASSERT(instance);
257
258 /* Get the host part of the hostname */
259 const char* hostname = freerdp_settings_get_string(settings, FreeRDP_AadServerHostname);
260 if (!hostname)
261 hostname = freerdp_settings_get_string(settings, FreeRDP_ServerHostname);
262 if (!hostname)
263 {
264 WLog_Print(aad->log, WLOG_ERROR, "FreeRDP_ServerHostname == NULL");
265 return -1;
266 }
267
268 aad->hostname = _strdup(hostname);
269 if (!aad->hostname)
270 {
271 WLog_Print(aad->log, WLOG_ERROR, "_strdup(FreeRDP_ServerHostname) == NULL");
272 return -1;
273 }
274
275 char* p = strchr(aad->hostname, '.');
276 if (p)
277 *p = '\0';
278
279 if (winpr_asprintf(&aad->scope, &size,
280 "ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fname%%2F%s%%"
281 "2Fuser_impersonation",
282 aad->hostname) <= 0)
283 return -1;
284
285 if (!generate_pop_key(aad))
286 return -1;
287
288 /* Obtain an oauth authorization code */
289 if (!instance->GetAccessToken)
290 {
291 WLog_Print(aad->log, WLOG_ERROR, "instance->GetAccessToken == NULL");
292 return -1;
293 }
294
295 if (!aad_fetch_wellknown(aad))
296 return -1;
297
298 const BOOL arc = instance->GetAccessToken(instance, ACCESS_TOKEN_TYPE_AAD, &aad->access_token,
299 2, aad->scope, aad->kid);
300 if (!arc)
301 {
302 WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain access token");
303 return -1;
304 }
305
306 /* Send the nonce request message */
307 if (!aad_get_nonce(aad))
308 {
309 WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain nonce");
310 return -1;
311 }
312
313 return 1;
314}
315
316static char* aad_create_jws_header(rdpAad* aad)
317{
318 WINPR_ASSERT(aad);
319
320 /* Construct the base64url encoded JWS header */
321 char* buffer = NULL;
322 size_t bufferlen = 0;
323 const int length =
324 winpr_asprintf(&buffer, &bufferlen, "{\"alg\":\"RS256\",\"kid\":\"%s\"}", aad->kid);
325 if (length < 0)
326 return NULL;
327
328 char* jws_header = crypto_base64url_encode((const BYTE*)buffer, bufferlen);
329 free(buffer);
330 return jws_header;
331}
332
333static char* aad_create_jws_payload(rdpAad* aad, const char* ts_nonce)
334{
335 const time_t ts = time(NULL);
336
337 WINPR_ASSERT(aad);
338
339 char* e = NULL;
340 char* n = NULL;
341 if (!get_encoded_rsa_params(aad->log, aad->key, &e, &n))
342 return NULL;
343
344 /* Construct the base64url encoded JWS payload */
345 char* buffer = NULL;
346 size_t bufferlen = 0;
347 const int length =
348 winpr_asprintf(&buffer, &bufferlen,
349 "{"
350 "\"ts\":\"%li\","
351 "\"at\":\"%s\","
352 "\"u\":\"ms-device-service://termsrv.wvd.microsoft.com/name/%s\","
353 "\"nonce\":\"%s\","
354 "\"cnf\":{\"jwk\":{\"kty\":\"RSA\",\"e\":\"%s\",\"n\":\"%s\"}},"
355 "\"client_claims\":\"{\\\"aad_nonce\\\":\\\"%s\\\"}\""
356 "}",
357 ts, aad->access_token, aad->hostname, ts_nonce, e, n, aad->nonce);
358 free(e);
359 free(n);
360
361 if (length < 0)
362 return NULL;
363
364 char* jws_payload = crypto_base64url_encode((BYTE*)buffer, bufferlen);
365 free(buffer);
366 return jws_payload;
367}
368
369static BOOL aad_update_digest(rdpAad* aad, WINPR_DIGEST_CTX* ctx, const char* what)
370{
371 WINPR_ASSERT(aad);
372 WINPR_ASSERT(ctx);
373 WINPR_ASSERT(what);
374
375 const BOOL dsu1 = winpr_DigestSign_Update(ctx, what, strlen(what));
376 if (!dsu1)
377 {
378 WLog_Print(aad->log, WLOG_ERROR, "winpr_DigestSign_Update [%s] failed", what);
379 return FALSE;
380 }
381 return TRUE;
382}
383
384static char* aad_final_digest(rdpAad* aad, WINPR_DIGEST_CTX* ctx)
385{
386 char* jws_signature = NULL;
387
388 WINPR_ASSERT(aad);
389 WINPR_ASSERT(ctx);
390
391 size_t siglen = 0;
392 const int dsf = winpr_DigestSign_Final(ctx, NULL, &siglen);
393 if (dsf <= 0)
394 {
395 WLog_Print(aad->log, WLOG_ERROR, "winpr_DigestSign_Final failed with %d", dsf);
396 return NULL;
397 }
398
399 char* buffer = calloc(siglen + 1, sizeof(char));
400 if (!buffer)
401 {
402 WLog_Print(aad->log, WLOG_ERROR, "calloc %" PRIuz " bytes failed", siglen + 1);
403 goto fail;
404 }
405
406 size_t fsiglen = siglen;
407 const int dsf2 = winpr_DigestSign_Final(ctx, (BYTE*)buffer, &fsiglen);
408 if (dsf2 <= 0)
409 {
410 WLog_Print(aad->log, WLOG_ERROR, "winpr_DigestSign_Final failed with %d", dsf2);
411 goto fail;
412 }
413
414 if (siglen != fsiglen)
415 {
416 WLog_Print(aad->log, WLOG_ERROR,
417 "winpr_DigestSignFinal returned different sizes, first %" PRIuz " then %" PRIuz,
418 siglen, fsiglen);
419 goto fail;
420 }
421 jws_signature = crypto_base64url_encode((const BYTE*)buffer, fsiglen);
422fail:
423 free(buffer);
424 return jws_signature;
425}
426
427static char* aad_create_jws_signature(rdpAad* aad, const char* jws_header, const char* jws_payload)
428{
429 char* jws_signature = NULL;
430
431 WINPR_ASSERT(aad);
432
433 WINPR_DIGEST_CTX* md_ctx = freerdp_key_digest_sign(aad->key, WINPR_MD_SHA256);
434 if (!md_ctx)
435 {
436 WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_New failed");
437 goto fail;
438 }
439
440 if (!aad_update_digest(aad, md_ctx, jws_header))
441 goto fail;
442 if (!aad_update_digest(aad, md_ctx, "."))
443 goto fail;
444 if (!aad_update_digest(aad, md_ctx, jws_payload))
445 goto fail;
446
447 jws_signature = aad_final_digest(aad, md_ctx);
448fail:
449 winpr_Digest_Free(md_ctx);
450 return jws_signature;
451}
452
453static int aad_send_auth_request(rdpAad* aad, const char* ts_nonce)
454{
455 int ret = -1;
456 char* jws_header = NULL;
457 char* jws_payload = NULL;
458 char* jws_signature = NULL;
459
460 WINPR_ASSERT(aad);
461 WINPR_ASSERT(ts_nonce);
462
463 wStream* s = Stream_New(NULL, 1024);
464 if (!s)
465 goto fail;
466
467 /* Construct the base64url encoded JWS header */
468 jws_header = aad_create_jws_header(aad);
469 if (!jws_header)
470 goto fail;
471
472 /* Construct the base64url encoded JWS payload */
473 jws_payload = aad_create_jws_payload(aad, ts_nonce);
474 if (!jws_payload)
475 goto fail;
476
477 /* Sign the JWS with the pop key */
478 jws_signature = aad_create_jws_signature(aad, jws_header, jws_payload);
479 if (!jws_signature)
480 goto fail;
481
482 /* Construct the Authentication Request PDU with the JWS as the RDP Assertion */
483 if (stream_sprintf(s, "{\"rdp_assertion\":\"%s.%s.%s\"}", jws_header, jws_payload,
484 jws_signature) < 0)
485 goto fail;
486
487 /* Include null terminator in PDU */
488 Stream_Write_UINT8(s, 0);
489
490 Stream_SealLength(s);
491
492 if (transport_write(aad->transport, s) < 0)
493 {
494 WLog_Print(aad->log, WLOG_ERROR, "transport_write [%" PRIdz " bytes] failed",
495 Stream_Length(s));
496 }
497 else
498 {
499 ret = 1;
500 aad->state = AAD_STATE_AUTH;
501 }
502fail:
503 Stream_Free(s, TRUE);
504 free(jws_header);
505 free(jws_payload);
506 free(jws_signature);
507
508 return ret;
509}
510
511static int aad_parse_state_initial(rdpAad* aad, wStream* s)
512{
513 const char* jstr = Stream_PointerAs(s, char);
514 const size_t jlen = Stream_GetRemainingLength(s);
515 const char* ts_nonce = NULL;
516 int ret = -1;
517 WINPR_JSON* json = NULL;
518
519 if (!Stream_SafeSeek(s, jlen))
520 goto fail;
521
522 json = WINPR_JSON_ParseWithLength(jstr, jlen);
523 if (!json)
524 goto fail;
525
526 if (!json_get_const_string(aad->log, json, "ts_nonce", &ts_nonce))
527 goto fail;
528
529 ret = aad_send_auth_request(aad, ts_nonce);
530fail:
531 WINPR_JSON_Delete(json);
532 return ret;
533}
534
535static int aad_parse_state_auth(rdpAad* aad, wStream* s)
536{
537 int rc = -1;
538 double result = 0;
539 DWORD error_code = 0;
540 WINPR_JSON* json = NULL;
541 const char* jstr = Stream_PointerAs(s, char);
542 const size_t jlength = Stream_GetRemainingLength(s);
543
544 if (!Stream_SafeSeek(s, jlength))
545 goto fail;
546
547 json = WINPR_JSON_ParseWithLength(jstr, jlength);
548 if (!json)
549 goto fail;
550
551 if (!json_get_number(aad->log, json, "authentication_result", &result))
552 goto fail;
553 error_code = (DWORD)result;
554
555 if (error_code != S_OK)
556 {
557 WLog_Print(aad->log, WLOG_ERROR, "Authentication result: %s (0x%08" PRIx32 ")",
558 aad_auth_result_to_string(error_code), error_code);
559 goto fail;
560 }
561 aad->state = AAD_STATE_FINAL;
562 rc = 1;
563fail:
564 WINPR_JSON_Delete(json);
565 return rc;
566}
567
568int aad_recv(rdpAad* aad, wStream* s)
569{
570 WINPR_ASSERT(aad);
571 WINPR_ASSERT(s);
572
573 switch (aad->state)
574 {
575 case AAD_STATE_INITIAL:
576 return aad_parse_state_initial(aad, s);
577 case AAD_STATE_AUTH:
578 return aad_parse_state_auth(aad, s);
579 case AAD_STATE_FINAL:
580 default:
581 WLog_Print(aad->log, WLOG_ERROR, "Invalid AAD_STATE %d", aad->state);
582 return -1;
583 }
584}
585
586static BOOL generate_rsa_2048(rdpAad* aad)
587{
588 WINPR_ASSERT(aad);
589 return freerdp_key_generate(aad->key, 2048);
590}
591
592static char* generate_rsa_digest_base64_str(rdpAad* aad, const char* input, size_t ilen)
593{
594 char* b64 = NULL;
595 WINPR_DIGEST_CTX* digest = winpr_Digest_New();
596 if (!digest)
597 {
598 WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_New failed");
599 goto fail;
600 }
601
602 if (!winpr_Digest_Init(digest, WINPR_MD_SHA256))
603 {
604 WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_Init(WINPR_MD_SHA256) failed");
605 goto fail;
606 }
607
608 if (!winpr_Digest_Update(digest, (const BYTE*)input, ilen))
609 {
610 WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_Update(%" PRIuz ") failed", ilen);
611 goto fail;
612 }
613
614 BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = { 0 };
615 if (!winpr_Digest_Final(digest, hash, sizeof(hash)))
616 {
617 WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_Final(%" PRIuz ") failed", sizeof(hash));
618 goto fail;
619 }
620
621 /* Base64url encode the hash */
622 b64 = crypto_base64url_encode(hash, sizeof(hash));
623
624fail:
625 winpr_Digest_Free(digest);
626 return b64;
627}
628
629static BOOL generate_json_base64_str(rdpAad* aad, const char* b64_hash)
630{
631 WINPR_ASSERT(aad);
632
633 char* buffer = NULL;
634 size_t blen = 0;
635 const int length = winpr_asprintf(&buffer, &blen, "{\"kid\":\"%s\"}", b64_hash);
636 if (length < 0)
637 return FALSE;
638
639 /* Finally, base64url encode the JSON text to form the kid */
640 free(aad->kid);
641 aad->kid = crypto_base64url_encode((const BYTE*)buffer, (size_t)length);
642 free(buffer);
643
644 if (!aad->kid)
645 {
646 return FALSE;
647 }
648 return TRUE;
649}
650
651BOOL generate_pop_key(rdpAad* aad)
652{
653 BOOL ret = FALSE;
654 char* buffer = NULL;
655 char* b64_hash = NULL;
656 char* e = NULL;
657 char* n = NULL;
658
659 WINPR_ASSERT(aad);
660
661 /* Generate a 2048-bit RSA key pair */
662 if (!generate_rsa_2048(aad))
663 goto fail;
664
665 /* Encode the public key as a JWK */
666 if (!get_encoded_rsa_params(aad->log, aad->key, &e, &n))
667 goto fail;
668
669 size_t blen = 0;
670 const int alen =
671 winpr_asprintf(&buffer, &blen, "{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}", e, n);
672 if (alen < 0)
673 goto fail;
674
675 /* Hash the encoded public key */
676 b64_hash = generate_rsa_digest_base64_str(aad, buffer, blen);
677 if (!b64_hash)
678 goto fail;
679
680 /* Encode a JSON object with a single property "kid" whose value is the encoded hash */
681 ret = generate_json_base64_str(aad, b64_hash);
682
683fail:
684 free(b64_hash);
685 free(buffer);
686 free(e);
687 free(n);
688 return ret;
689}
690
691static char* bn_to_base64_url(wLog* wlog, rdpPrivateKey* key, enum FREERDP_KEY_PARAM param)
692{
693 WINPR_ASSERT(wlog);
694 WINPR_ASSERT(key);
695
696 size_t len = 0;
697 BYTE* bn = freerdp_key_get_param(key, param, &len);
698 if (!bn)
699 return NULL;
700
701 char* b64 = crypto_base64url_encode(bn, len);
702 free(bn);
703
704 if (!b64)
705 WLog_Print(wlog, WLOG_ERROR, "failed base64 url encode BIGNUM");
706
707 return b64;
708}
709
710BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key, char** pe, char** pn)
711{
712 BOOL rc = FALSE;
713 char* e = NULL;
714 char* n = NULL;
715
716 WINPR_ASSERT(wlog);
717 WINPR_ASSERT(key);
718 WINPR_ASSERT(pe);
719 WINPR_ASSERT(pn);
720
721 *pe = NULL;
722 *pn = NULL;
723
724 e = bn_to_base64_url(wlog, key, FREERDP_KEY_PARAM_RSA_E);
725 if (!e)
726 {
727 WLog_Print(wlog, WLOG_ERROR, "failed base64 url encode RSA E");
728 goto fail;
729 }
730
731 n = bn_to_base64_url(wlog, key, FREERDP_KEY_PARAM_RSA_N);
732 if (!n)
733 {
734 WLog_Print(wlog, WLOG_ERROR, "failed base64 url encode RSA N");
735 goto fail;
736 }
737
738 rc = TRUE;
739fail:
740 if (!rc)
741 {
742 free(e);
743 free(n);
744 }
745 else
746 {
747 *pe = e;
748 *pn = n;
749 }
750 return rc;
751}
752#else
753int aad_client_begin(rdpAad* aad)
754{
755 WINPR_ASSERT(aad);
756 WLog_Print(aad->log, WLOG_ERROR, "AAD security not compiled in, aborting!");
757 return -1;
758}
759int aad_recv(rdpAad* aad, wStream* s)
760{
761 WINPR_ASSERT(aad);
762 WLog_Print(aad->log, WLOG_ERROR, "AAD security not compiled in, aborting!");
763 return -1;
764}
765#endif
766
767rdpAad* aad_new(rdpContext* context, rdpTransport* transport)
768{
769 WINPR_ASSERT(transport);
770 WINPR_ASSERT(context);
771
772 rdpAad* aad = (rdpAad*)calloc(1, sizeof(rdpAad));
773
774 if (!aad)
775 return NULL;
776
777 aad->log = WLog_Get(FREERDP_TAG("aad"));
778 aad->key = freerdp_key_new();
779 if (!aad->key)
780 goto fail;
781 aad->rdpcontext = context;
782 aad->transport = transport;
783
784 return aad;
785fail:
786 WINPR_PRAGMA_DIAG_PUSH
787 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
788 aad_free(aad);
789 WINPR_PRAGMA_DIAG_POP
790 return NULL;
791}
792
793void aad_free(rdpAad* aad)
794{
795 if (!aad)
796 return;
797
798 free(aad->hostname);
799 free(aad->scope);
800 free(aad->nonce);
801 free(aad->access_token);
802 free(aad->kid);
803 freerdp_key_free(aad->key);
804
805 free(aad);
806}
807
808AAD_STATE aad_get_state(rdpAad* aad)
809{
810 WINPR_ASSERT(aad);
811 return aad->state;
812}
813
814BOOL aad_is_supported(void)
815{
816#ifdef WITH_AAD
817 return TRUE;
818#else
819 return FALSE;
820#endif
821}
822
823char* freerdp_utils_aad_get_access_token(wLog* log, const char* data, size_t length)
824{
825 char* token = NULL;
826 WINPR_JSON* access_token_prop = NULL;
827 const char* access_token_str = NULL;
828
829 WINPR_JSON* json = WINPR_JSON_ParseWithLength(data, length);
830 if (!json)
831 {
832 WLog_Print(log, WLOG_ERROR, "Failed to parse access token response [got %" PRIuz " bytes",
833 length);
834 goto cleanup;
835 }
836
837 access_token_prop = WINPR_JSON_GetObjectItem(json, "access_token");
838 if (!access_token_prop)
839 {
840 WLog_Print(log, WLOG_ERROR, "Response has no \"access_token\" property");
841 goto cleanup;
842 }
843
844 access_token_str = WINPR_JSON_GetStringValue(access_token_prop);
845 if (!access_token_str)
846 {
847 WLog_Print(log, WLOG_ERROR, "Invalid value for \"access_token\"");
848 goto cleanup;
849 }
850
851 token = _strdup(access_token_str);
852
853cleanup:
854 WINPR_JSON_Delete(json);
855 return token;
856}
857
858BOOL aad_fetch_wellknown(rdpAad* aad)
859{
860 WINPR_ASSERT(aad);
861 WINPR_ASSERT(aad->rdpcontext);
862
863 rdpRdp* rdp = aad->rdpcontext->rdp;
864 WINPR_ASSERT(rdp);
865
866 if (rdp->wellknown)
867 return TRUE;
868
869 const char* base =
870 freerdp_settings_get_string(aad->rdpcontext->settings, FreeRDP_GatewayAzureActiveDirectory);
871 const BOOL useTenant =
872 freerdp_settings_get_bool(aad->rdpcontext->settings, FreeRDP_GatewayAvdUseTenantid);
873 const char* tenantid = "common";
874 if (useTenant)
875 tenantid =
876 freerdp_settings_get_string(aad->rdpcontext->settings, FreeRDP_GatewayAvdAadtenantid);
877 rdp->wellknown = freerdp_utils_aad_get_wellknown(aad->log, base, tenantid);
878 return rdp->wellknown ? TRUE : FALSE;
879}
880
881const char* freerdp_utils_aad_get_wellknown_string(rdpContext* context, AAD_WELLKNOWN_VALUES which)
882{
883 return freerdp_utils_aad_get_wellknown_custom_string(
884 context, freerdp_utils_aad_wellknwon_value_name(which));
885}
886
887const char* freerdp_utils_aad_get_wellknown_custom_string(rdpContext* context, const char* which)
888{
889 WINPR_ASSERT(context);
890 WINPR_ASSERT(context->rdp);
891
892 if (!context->rdp->wellknown)
893 return NULL;
894
895 WINPR_JSON* obj = WINPR_JSON_GetObjectItem(context->rdp->wellknown, which);
896 if (!obj)
897 return NULL;
898
899 return WINPR_JSON_GetStringValue(obj);
900}
901
902const char* freerdp_utils_aad_wellknwon_value_name(AAD_WELLKNOWN_VALUES which)
903{
904 switch (which)
905 {
906 case AAD_WELLKNOWN_token_endpoint:
907 return "token_endpoint";
908 case AAD_WELLKNOWN_token_endpoint_auth_methods_supported:
909 return "token_endpoint_auth_methods_supported";
910 case AAD_WELLKNOWN_jwks_uri:
911 return "jwks_uri";
912 case AAD_WELLKNOWN_response_modes_supported:
913 return "response_modes_supported";
914 case AAD_WELLKNOWN_subject_types_supported:
915 return "subject_types_supported";
916 case AAD_WELLKNOWN_id_token_signing_alg_values_supported:
917 return "id_token_signing_alg_values_supported";
918 case AAD_WELLKNOWN_response_types_supported:
919 return "response_types_supported";
920 case AAD_WELLKNOWN_scopes_supported:
921 return "scopes_supported";
922 case AAD_WELLKNOWN_issuer:
923 return "issuer";
924 case AAD_WELLKNOWN_request_uri_parameter_supported:
925 return "request_uri_parameter_supported";
926 case AAD_WELLKNOWN_userinfo_endpoint:
927 return "userinfo_endpoint";
928 case AAD_WELLKNOWN_authorization_endpoint:
929 return "authorization_endpoint";
930 case AAD_WELLKNOWN_device_authorization_endpoint:
931 return "device_authorization_endpoint";
932 case AAD_WELLKNOWN_http_logout_supported:
933 return "http_logout_supported";
934 case AAD_WELLKNOWN_frontchannel_logout_supported:
935 return "frontchannel_logout_supported";
936 case AAD_WELLKNOWN_end_session_endpoint:
937 return "end_session_endpoint";
938 case AAD_WELLKNOWN_claims_supported:
939 return "claims_supported";
940 case AAD_WELLKNOWN_kerberos_endpoint:
941 return "kerberos_endpoint";
942 case AAD_WELLKNOWN_tenant_region_scope:
943 return "tenant_region_scope";
944 case AAD_WELLKNOWN_cloud_instance_name:
945 return "cloud_instance_name";
946 case AAD_WELLKNOWN_cloud_graph_host_name:
947 return "cloud_graph_host_name";
948 case AAD_WELLKNOWN_msgraph_host:
949 return "msgraph_host";
950 case AAD_WELLKNOWN_rbac_url:
951 return "rbac_url";
952 default:
953 return "UNKNOWN";
954 }
955}
956
957WINPR_JSON* freerdp_utils_aad_get_wellknown_object(rdpContext* context, AAD_WELLKNOWN_VALUES which)
958{
959 return freerdp_utils_aad_get_wellknown_custom_object(
960 context, freerdp_utils_aad_wellknwon_value_name(which));
961}
962
963WINPR_JSON* freerdp_utils_aad_get_wellknown_custom_object(rdpContext* context, const char* which)
964{
965 WINPR_ASSERT(context);
966 WINPR_ASSERT(context->rdp);
967
968 if (!context->rdp->wellknown)
969 return NULL;
970
971 return WINPR_JSON_GetObjectItem(context->rdp->wellknown, which);
972}
973
974WINPR_ATTR_MALLOC(WINPR_JSON_Delete, 1)
975WINPR_JSON* freerdp_utils_aad_get_wellknown(wLog* log, const char* base, const char* tenantid)
976{
977 WINPR_ASSERT(base);
978 WINPR_ASSERT(tenantid);
979
980 char* str = NULL;
981 size_t len = 0;
982 winpr_asprintf(&str, &len, "https://%s/%s/v2.0/.well-known/openid-configuration", base,
983 tenantid);
984
985 if (!str)
986 {
987 WLog_Print(log, WLOG_ERROR, "failed to create request URL for tenantid='%s'", tenantid);
988 return NULL;
989 }
990
991 BYTE* response = NULL;
992 long resp_code = 0;
993 size_t response_length = 0;
994 const BOOL rc = freerdp_http_request(str, NULL, &resp_code, &response, &response_length);
995 if (!rc || (resp_code != HTTP_STATUS_OK))
996 {
997 WLog_Print(log, WLOG_ERROR, "request for '%s' failed with: %s", str,
998 freerdp_http_status_string(resp_code));
999 free(str);
1000 free(response);
1001 return NULL;
1002 }
1003 free(str);
1004
1005 WINPR_JSON* json = WINPR_JSON_ParseWithLength((const char*)response, response_length);
1006 free(response);
1007
1008 if (!json)
1009 WLog_Print(log, WLOG_ERROR, "failed to parse response as JSON");
1010
1011 return json;
1012}
WINPR_API BOOL WINPR_JSON_HasObjectItem(const WINPR_JSON *object, const char *string)
Check if JSON has an object matching the name.
Definition json.c:208
WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItem(const WINPR_JSON *object, const char *string)
Return a pointer to an JSON object item.
Definition json.c:182
WINPR_API BOOL WINPR_JSON_IsString(const WINPR_JSON *item)
Check if JSON item is of type String.
Definition json.c:347
WINPR_API double WINPR_JSON_GetNumberValue(const WINPR_JSON *item)
Return the Number value of a JSON item.
Definition json.c:244
WINPR_API BOOL WINPR_JSON_IsNumber(const WINPR_JSON *item)
Check if JSON item is of type Number.
Definition json.c:334
WINPR_API WINPR_JSON * WINPR_JSON_ParseWithLength(const char *value, size_t buffer_length)
Parse a JSON string.
Definition json.c:123
WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
Definition json.c:232
WINPR_API void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
Definition json.c:142
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_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.