FreeRDP
Loading...
Searching...
No Matches
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(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);
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_GetObjectItemCaseSensitive(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 ensure_wellknown(rdpContext* context)
184{
185 if (context->rdp->wellknown)
186 return TRUE;
187
188 rdpAad* aad = context->rdp->aad;
189 if (!aad)
190 return FALSE;
191
192 if (!aad_fetch_wellknown(aad->log, context))
193 return FALSE;
194 return context->rdp->wellknown != NULL;
195}
196
197static BOOL aad_get_nonce(rdpAad* aad)
198{
199 BOOL ret = FALSE;
200 BYTE* response = NULL;
201 long resp_code = 0;
202 size_t response_length = 0;
203 WINPR_JSON* json = NULL;
204
205 WINPR_ASSERT(aad);
206 WINPR_ASSERT(aad->rdpcontext);
207
208 rdpRdp* rdp = aad->rdpcontext->rdp;
209 WINPR_ASSERT(rdp);
210
211 if (!ensure_wellknown(aad->rdpcontext))
212 return FALSE;
213
214 WINPR_JSON* obj = WINPR_JSON_GetObjectItemCaseSensitive(rdp->wellknown, "token_endpoint");
215 if (!obj)
216 {
217 WLog_Print(aad->log, WLOG_ERROR, "wellknown does not have 'token_endpoint', aborting");
218 return FALSE;
219 }
220 const char* url = WINPR_JSON_GetStringValue(obj);
221 if (!url)
222 {
223 WLog_Print(aad->log, WLOG_ERROR,
224 "wellknown does have 'token_endpoint=NULL' value, aborting");
225 return FALSE;
226 }
227
228 if (!freerdp_http_request(url, "grant_type=srv_challenge", &resp_code, &response,
229 &response_length))
230 {
231 WLog_Print(aad->log, WLOG_ERROR, "nonce request failed");
232 goto fail;
233 }
234
235 if (resp_code != HTTP_STATUS_OK)
236 {
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);
241 goto fail;
242 }
243
244 json = WINPR_JSON_ParseWithLength((const char*)response, response_length);
245 if (!json)
246 {
247 WLog_Print(aad->log, WLOG_ERROR, "Failed to parse nonce response: %s",
249 goto fail;
250 }
251
252 if (!json_get_string_alloc(aad->log, json, "Nonce", &aad->nonce))
253 goto fail;
254
255 ret = TRUE;
256
257fail:
258 free(response);
259 WINPR_JSON_Delete(json);
260 return ret;
261}
262
263int aad_client_begin(rdpAad* aad)
264{
265 size_t size = 0;
266
267 WINPR_ASSERT(aad);
268 WINPR_ASSERT(aad->rdpcontext);
269
270 rdpSettings* settings = aad->rdpcontext->settings;
271 WINPR_ASSERT(settings);
272
273 /* Get the host part of the hostname */
274 const char* hostname = freerdp_settings_get_string(settings, FreeRDP_AadServerHostname);
275 if (!hostname)
276 hostname = freerdp_settings_get_server_name(settings);
277 if (!hostname)
278 {
279 WLog_Print(aad->log, WLOG_ERROR, "hostname == NULL");
280 return -1;
281 }
282
283 aad->hostname = _strdup(hostname);
284 if (!aad->hostname)
285 {
286 WLog_Print(aad->log, WLOG_ERROR, "_strdup(hostname) == NULL");
287 return -1;
288 }
289
290 char* p = strchr(aad->hostname, '.');
291 if (p)
292 *p = '\0';
293
294 if (winpr_asprintf(&aad->scope, &size,
295 "ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fname%%2F%s%%"
296 "2Fuser_impersonation",
297 aad->hostname) <= 0)
298 return -1;
299
300 if (!generate_pop_key(aad))
301 return -1;
302
303 /* Obtain an oauth authorization code */
304 pGetCommonAccessToken GetCommonAccessToken = freerdp_get_common_access_token(aad->rdpcontext);
305 if (!GetCommonAccessToken)
306 {
307 WLog_Print(aad->log, WLOG_ERROR, "GetCommonAccessToken == NULL");
308 return -1;
309 }
310
311 if (!aad_fetch_wellknown(aad->log, aad->rdpcontext))
312 return -1;
313
314 const BOOL arc = GetCommonAccessToken(aad->rdpcontext, ACCESS_TOKEN_TYPE_AAD,
315 &aad->access_token, 2, aad->scope, aad->kid);
316 if (!arc)
317 {
318 WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain access token");
319 return -1;
320 }
321
322 /* Send the nonce request message */
323 if (!aad_get_nonce(aad))
324 {
325 WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain nonce");
326 return -1;
327 }
328
329 return 1;
330}
331
332static char* aad_create_jws_header(rdpAad* aad)
333{
334 WINPR_ASSERT(aad);
335
336 /* Construct the base64url encoded JWS header */
337 char* buffer = NULL;
338 size_t bufferlen = 0;
339 const int length =
340 winpr_asprintf(&buffer, &bufferlen, "{\"alg\":\"RS256\",\"kid\":\"%s\"}", aad->kid);
341 if (length < 0)
342 return NULL;
343
344 char* jws_header = crypto_base64url_encode((const BYTE*)buffer, bufferlen);
345 free(buffer);
346 return jws_header;
347}
348
349static char* aad_create_jws_payload(rdpAad* aad, const char* ts_nonce)
350{
351 const time_t ts = time(NULL);
352
353 WINPR_ASSERT(aad);
354
355 char* e = NULL;
356 char* n = NULL;
357 if (!get_encoded_rsa_params(aad->log, aad->key, &e, &n))
358 return NULL;
359
360 /* Construct the base64url encoded JWS payload */
361 char* buffer = NULL;
362 size_t bufferlen = 0;
363 const int length =
364 winpr_asprintf(&buffer, &bufferlen,
365 "{"
366 "\"ts\":\"%li\","
367 "\"at\":\"%s\","
368 "\"u\":\"ms-device-service://termsrv.wvd.microsoft.com/name/%s\","
369 "\"nonce\":\"%s\","
370 "\"cnf\":{\"jwk\":{\"kty\":\"RSA\",\"e\":\"%s\",\"n\":\"%s\"}},"
371 "\"client_claims\":\"{\\\"aad_nonce\\\":\\\"%s\\\"}\""
372 "}",
373 ts, aad->access_token, aad->hostname, ts_nonce, e, n, aad->nonce);
374 free(e);
375 free(n);
376
377 if (length < 0)
378 return NULL;
379
380 char* jws_payload = crypto_base64url_encode((BYTE*)buffer, bufferlen);
381 free(buffer);
382 return jws_payload;
383}
384
385static BOOL aad_update_digest(rdpAad* aad, WINPR_DIGEST_CTX* ctx, const char* what)
386{
387 WINPR_ASSERT(aad);
388 WINPR_ASSERT(ctx);
389 WINPR_ASSERT(what);
390
391 const BOOL dsu1 = winpr_DigestSign_Update(ctx, what, strlen(what));
392 if (!dsu1)
393 {
394 WLog_Print(aad->log, WLOG_ERROR, "winpr_DigestSign_Update [%s] failed", what);
395 return FALSE;
396 }
397 return TRUE;
398}
399
400static char* aad_final_digest(rdpAad* aad, WINPR_DIGEST_CTX* ctx)
401{
402 char* jws_signature = NULL;
403
404 WINPR_ASSERT(aad);
405 WINPR_ASSERT(ctx);
406
407 size_t siglen = 0;
408 const int dsf = winpr_DigestSign_Final(ctx, NULL, &siglen);
409 if (dsf <= 0)
410 {
411 WLog_Print(aad->log, WLOG_ERROR, "winpr_DigestSign_Final failed with %d", dsf);
412 return NULL;
413 }
414
415 char* buffer = calloc(siglen + 1, sizeof(char));
416 if (!buffer)
417 {
418 WLog_Print(aad->log, WLOG_ERROR, "calloc %" PRIuz " bytes failed", siglen + 1);
419 goto fail;
420 }
421
422 size_t fsiglen = siglen;
423 const int dsf2 = winpr_DigestSign_Final(ctx, (BYTE*)buffer, &fsiglen);
424 if (dsf2 <= 0)
425 {
426 WLog_Print(aad->log, WLOG_ERROR, "winpr_DigestSign_Final failed with %d", dsf2);
427 goto fail;
428 }
429
430 if (siglen != fsiglen)
431 {
432 WLog_Print(aad->log, WLOG_ERROR,
433 "winpr_DigestSignFinal returned different sizes, first %" PRIuz " then %" PRIuz,
434 siglen, fsiglen);
435 goto fail;
436 }
437 jws_signature = crypto_base64url_encode((const BYTE*)buffer, fsiglen);
438fail:
439 free(buffer);
440 return jws_signature;
441}
442
443static char* aad_create_jws_signature(rdpAad* aad, const char* jws_header, const char* jws_payload)
444{
445 char* jws_signature = NULL;
446
447 WINPR_ASSERT(aad);
448
449 WINPR_DIGEST_CTX* md_ctx = freerdp_key_digest_sign(aad->key, WINPR_MD_SHA256);
450 if (!md_ctx)
451 {
452 WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_New failed");
453 goto fail;
454 }
455
456 if (!aad_update_digest(aad, md_ctx, jws_header))
457 goto fail;
458 if (!aad_update_digest(aad, md_ctx, "."))
459 goto fail;
460 if (!aad_update_digest(aad, md_ctx, jws_payload))
461 goto fail;
462
463 jws_signature = aad_final_digest(aad, md_ctx);
464fail:
465 winpr_Digest_Free(md_ctx);
466 return jws_signature;
467}
468
469static int aad_send_auth_request(rdpAad* aad, const char* ts_nonce)
470{
471 int ret = -1;
472 char* jws_header = NULL;
473 char* jws_payload = NULL;
474 char* jws_signature = NULL;
475
476 WINPR_ASSERT(aad);
477 WINPR_ASSERT(ts_nonce);
478
479 wStream* s = Stream_New(NULL, 1024);
480 if (!s)
481 goto fail;
482
483 /* Construct the base64url encoded JWS header */
484 jws_header = aad_create_jws_header(aad);
485 if (!jws_header)
486 goto fail;
487
488 /* Construct the base64url encoded JWS payload */
489 jws_payload = aad_create_jws_payload(aad, ts_nonce);
490 if (!jws_payload)
491 goto fail;
492
493 /* Sign the JWS with the pop key */
494 jws_signature = aad_create_jws_signature(aad, jws_header, jws_payload);
495 if (!jws_signature)
496 goto fail;
497
498 /* Construct the Authentication Request PDU with the JWS as the RDP Assertion */
499 if (stream_sprintf(s, "{\"rdp_assertion\":\"%s.%s.%s\"}", jws_header, jws_payload,
500 jws_signature) < 0)
501 goto fail;
502
503 /* Include null terminator in PDU */
504 Stream_Write_UINT8(s, 0);
505
506 Stream_SealLength(s);
507
508 if (transport_write(aad->transport, s) < 0)
509 {
510 WLog_Print(aad->log, WLOG_ERROR, "transport_write [%" PRIuz " bytes] failed",
511 Stream_Length(s));
512 }
513 else
514 {
515 ret = 1;
516 aad->state = AAD_STATE_AUTH;
517 }
518fail:
519 Stream_Free(s, TRUE);
520 free(jws_header);
521 free(jws_payload);
522 free(jws_signature);
523
524 return ret;
525}
526
527static int aad_parse_state_initial(rdpAad* aad, wStream* s)
528{
529 const char* jstr = Stream_PointerAs(s, char);
530 const size_t jlen = Stream_GetRemainingLength(s);
531 const char* ts_nonce = NULL;
532 int ret = -1;
533 WINPR_JSON* json = NULL;
534
535 if (!Stream_SafeSeek(s, jlen))
536 goto fail;
537
538 json = WINPR_JSON_ParseWithLength(jstr, jlen);
539 if (!json)
540 {
541 WLog_Print(aad->log, WLOG_ERROR, "WINPR_JSON_ParseWithLength failed: %s",
543 goto fail;
544 }
545
546 if (!json_get_const_string(aad->log, json, "ts_nonce", &ts_nonce))
547 goto fail;
548
549 ret = aad_send_auth_request(aad, ts_nonce);
550fail:
551 WINPR_JSON_Delete(json);
552 return ret;
553}
554
555static int aad_parse_state_auth(rdpAad* aad, wStream* s)
556{
557 int rc = -1;
558 double result = 0;
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);
563
564 if (!Stream_SafeSeek(s, jlength))
565 goto fail;
566
567 json = WINPR_JSON_ParseWithLength(jstr, jlength);
568 if (!json)
569 {
570 WLog_Print(aad->log, WLOG_ERROR, "WINPR_JSON_ParseWithLength: %s",
572 goto fail;
573 }
574
575 if (!json_get_number(aad->log, json, "authentication_result", &result))
576 goto fail;
577 error_code = (DWORD)result;
578
579 if (error_code != S_OK)
580 {
581 WLog_Print(aad->log, WLOG_ERROR, "Authentication result: %s (0x%08" PRIx32 ")",
582 aad_auth_result_to_string(error_code), error_code);
583 goto fail;
584 }
585 aad->state = AAD_STATE_FINAL;
586 rc = 1;
587fail:
588 WINPR_JSON_Delete(json);
589 return rc;
590}
591
592int aad_recv(rdpAad* aad, wStream* s)
593{
594 WINPR_ASSERT(aad);
595 WINPR_ASSERT(s);
596
597 switch (aad->state)
598 {
599 case AAD_STATE_INITIAL:
600 return aad_parse_state_initial(aad, s);
601 case AAD_STATE_AUTH:
602 return aad_parse_state_auth(aad, s);
603 case AAD_STATE_FINAL:
604 default:
605 WLog_Print(aad->log, WLOG_ERROR, "Invalid AAD_STATE %u", aad->state);
606 return -1;
607 }
608}
609
610static BOOL generate_rsa_2048(rdpAad* aad)
611{
612 WINPR_ASSERT(aad);
613 return freerdp_key_generate(aad->key, "RSA", 1, 2048);
614}
615
616static char* generate_rsa_digest_base64_str(rdpAad* aad, const char* input, size_t ilen)
617{
618 char* b64 = NULL;
619 WINPR_DIGEST_CTX* digest = winpr_Digest_New();
620 if (!digest)
621 {
622 WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_New failed");
623 goto fail;
624 }
625
626 if (!winpr_Digest_Init(digest, WINPR_MD_SHA256))
627 {
628 WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_Init(WINPR_MD_SHA256) failed");
629 goto fail;
630 }
631
632 if (!winpr_Digest_Update(digest, (const BYTE*)input, ilen))
633 {
634 WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_Update(%" PRIuz ") failed", ilen);
635 goto fail;
636 }
637
638 BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = { 0 };
639 if (!winpr_Digest_Final(digest, hash, sizeof(hash)))
640 {
641 WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_Final(%" PRIuz ") failed", sizeof(hash));
642 goto fail;
643 }
644
645 /* Base64url encode the hash */
646 b64 = crypto_base64url_encode(hash, sizeof(hash));
647
648fail:
649 winpr_Digest_Free(digest);
650 return b64;
651}
652
653static BOOL generate_json_base64_str(rdpAad* aad, const char* b64_hash)
654{
655 WINPR_ASSERT(aad);
656
657 char* buffer = NULL;
658 size_t blen = 0;
659 const int length = winpr_asprintf(&buffer, &blen, "{\"kid\":\"%s\"}", b64_hash);
660 if (length < 0)
661 return FALSE;
662
663 /* Finally, base64url encode the JSON text to form the kid */
664 free(aad->kid);
665 aad->kid = crypto_base64url_encode((const BYTE*)buffer, (size_t)length);
666 free(buffer);
667
668 if (!aad->kid)
669 {
670 return FALSE;
671 }
672 return TRUE;
673}
674
675BOOL generate_pop_key(rdpAad* aad)
676{
677 BOOL ret = FALSE;
678 char* buffer = NULL;
679 char* b64_hash = NULL;
680 char* e = NULL;
681 char* n = NULL;
682
683 WINPR_ASSERT(aad);
684
685 /* Generate a 2048-bit RSA key pair */
686 if (!generate_rsa_2048(aad))
687 goto fail;
688
689 /* Encode the public key as a JWK */
690 if (!get_encoded_rsa_params(aad->log, aad->key, &e, &n))
691 goto fail;
692
693 size_t blen = 0;
694 const int alen =
695 winpr_asprintf(&buffer, &blen, "{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}", e, n);
696 if (alen < 0)
697 goto fail;
698
699 /* Hash the encoded public key */
700 b64_hash = generate_rsa_digest_base64_str(aad, buffer, blen);
701 if (!b64_hash)
702 goto fail;
703
704 /* Encode a JSON object with a single property "kid" whose value is the encoded hash */
705 ret = generate_json_base64_str(aad, b64_hash);
706
707fail:
708 free(b64_hash);
709 free(buffer);
710 free(e);
711 free(n);
712 return ret;
713}
714
715static char* bn_to_base64_url(wLog* wlog, rdpPrivateKey* key, enum FREERDP_KEY_PARAM param)
716{
717 WINPR_ASSERT(wlog);
718 WINPR_ASSERT(key);
719
720 size_t len = 0;
721 BYTE* bn = freerdp_key_get_param(key, param, &len);
722 if (!bn)
723 return NULL;
724
725 char* b64 = crypto_base64url_encode(bn, len);
726 free(bn);
727
728 if (!b64)
729 WLog_Print(wlog, WLOG_ERROR, "failed base64 url encode BIGNUM");
730
731 return b64;
732}
733
734BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key, char** pe, char** pn)
735{
736 BOOL rc = FALSE;
737 char* e = NULL;
738 char* n = NULL;
739
740 WINPR_ASSERT(wlog);
741 WINPR_ASSERT(key);
742 WINPR_ASSERT(pe);
743 WINPR_ASSERT(pn);
744
745 *pe = NULL;
746 *pn = NULL;
747
748 e = bn_to_base64_url(wlog, key, FREERDP_KEY_PARAM_RSA_E);
749 if (!e)
750 {
751 WLog_Print(wlog, WLOG_ERROR, "failed base64 url encode RSA E");
752 goto fail;
753 }
754
755 n = bn_to_base64_url(wlog, key, FREERDP_KEY_PARAM_RSA_N);
756 if (!n)
757 {
758 WLog_Print(wlog, WLOG_ERROR, "failed base64 url encode RSA N");
759 goto fail;
760 }
761
762 rc = TRUE;
763fail:
764 if (!rc)
765 {
766 free(e);
767 free(n);
768 }
769 else
770 {
771 *pe = e;
772 *pn = n;
773 }
774 return rc;
775}
776#else
777int aad_client_begin(rdpAad* aad)
778{
779 WINPR_ASSERT(aad);
780 WLog_Print(aad->log, WLOG_ERROR, "AAD security not compiled in, aborting!");
781 return -1;
782}
783
784int aad_recv(rdpAad* aad, wStream* s)
785{
786 WINPR_ASSERT(aad);
787 WLog_Print(aad->log, WLOG_ERROR, "AAD security not compiled in, aborting!");
788 return -1;
789}
790
791static BOOL ensure_wellknown(WINPR_ATTR_UNUSED rdpContext* context)
792{
793 return FALSE;
794}
795
796#endif
797
798rdpAad* aad_new(rdpContext* context, rdpTransport* transport)
799{
800 WINPR_ASSERT(transport);
801 WINPR_ASSERT(context);
802
803 rdpAad* aad = (rdpAad*)calloc(1, sizeof(rdpAad));
804
805 if (!aad)
806 return NULL;
807
808 aad->log = WLog_Get(FREERDP_TAG("aad"));
809 aad->key = freerdp_key_new();
810 if (!aad->key)
811 goto fail;
812 aad->rdpcontext = context;
813 aad->transport = transport;
814
815 return aad;
816fail:
817 WINPR_PRAGMA_DIAG_PUSH
818 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
819 aad_free(aad);
820 WINPR_PRAGMA_DIAG_POP
821 return NULL;
822}
823
824void aad_free(rdpAad* aad)
825{
826 if (!aad)
827 return;
828
829 free(aad->hostname);
830 free(aad->scope);
831 free(aad->nonce);
832 free(aad->access_token);
833 free(aad->kid);
834 freerdp_key_free(aad->key);
835
836 free(aad);
837}
838
839AAD_STATE aad_get_state(rdpAad* aad)
840{
841 WINPR_ASSERT(aad);
842 return aad->state;
843}
844
845BOOL aad_is_supported(void)
846{
847#ifdef WITH_AAD
848 return TRUE;
849#else
850 return FALSE;
851#endif
852}
853
854char* freerdp_utils_aad_get_access_token(wLog* log, const char* data, size_t length)
855{
856 char* token = NULL;
857 WINPR_JSON* access_token_prop = NULL;
858 const char* access_token_str = NULL;
859
860 WINPR_JSON* json = WINPR_JSON_ParseWithLength(data, length);
861 if (!json)
862 {
863 WLog_Print(log, WLOG_ERROR,
864 "Failed to parse access token response [got %" PRIuz " bytes: %s", length,
866 goto cleanup;
867 }
868
869 access_token_prop = WINPR_JSON_GetObjectItemCaseSensitive(json, "access_token");
870 if (!access_token_prop)
871 {
872 WLog_Print(log, WLOG_ERROR, "Response has no \"access_token\" property");
873 goto cleanup;
874 }
875
876 access_token_str = WINPR_JSON_GetStringValue(access_token_prop);
877 if (!access_token_str)
878 {
879 WLog_Print(log, WLOG_ERROR, "Invalid value for \"access_token\"");
880 goto cleanup;
881 }
882
883 token = _strdup(access_token_str);
884
885cleanup:
886 WINPR_JSON_Delete(json);
887 return token;
888}
889
890BOOL aad_fetch_wellknown(wLog* log, rdpContext* context)
891{
892 WINPR_ASSERT(context);
893
894 rdpRdp* rdp = context->rdp;
895 WINPR_ASSERT(rdp);
896
897 if (rdp->wellknown)
898 return TRUE;
899
900 const char* base =
901 freerdp_settings_get_string(context->settings, FreeRDP_GatewayAzureActiveDirectory);
902 const BOOL useTenant =
903 freerdp_settings_get_bool(context->settings, FreeRDP_GatewayAvdUseTenantid);
904 const char* tenantid = "common";
905 if (useTenant)
906 tenantid = freerdp_settings_get_string(context->settings, FreeRDP_GatewayAvdAadtenantid);
907 rdp->wellknown = freerdp_utils_aad_get_wellknown(log, base, tenantid);
908 return rdp->wellknown ? TRUE : FALSE;
909}
910
911const char* freerdp_utils_aad_get_wellknown_string(rdpContext* context, AAD_WELLKNOWN_VALUES which)
912{
913 return freerdp_utils_aad_get_wellknown_custom_string(
914 context, freerdp_utils_aad_wellknwon_value_name(which));
915}
916
917const char* freerdp_utils_aad_get_wellknown_custom_string(rdpContext* context, const char* which)
918{
919 WINPR_ASSERT(context);
920 WINPR_ASSERT(context->rdp);
921
922 if (!ensure_wellknown(context))
923 return NULL;
924
925 WINPR_JSON* obj = WINPR_JSON_GetObjectItemCaseSensitive(context->rdp->wellknown, which);
926 if (!obj)
927 return NULL;
928
929 return WINPR_JSON_GetStringValue(obj);
930}
931
932const char* freerdp_utils_aad_wellknwon_value_name(AAD_WELLKNOWN_VALUES which)
933{
934 switch (which)
935 {
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:
941 return "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:
953 return "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:
981 return "rbac_url";
982 default:
983 return "UNKNOWN";
984 }
985}
986
987WINPR_JSON* freerdp_utils_aad_get_wellknown_object(rdpContext* context, AAD_WELLKNOWN_VALUES which)
988{
989 return freerdp_utils_aad_get_wellknown_custom_object(
990 context, freerdp_utils_aad_wellknwon_value_name(which));
991}
992
993WINPR_JSON* freerdp_utils_aad_get_wellknown_custom_object(rdpContext* context, const char* which)
994{
995 WINPR_ASSERT(context);
996 WINPR_ASSERT(context->rdp);
997
998 if (!ensure_wellknown(context))
999 return NULL;
1000
1001 return WINPR_JSON_GetObjectItemCaseSensitive(context->rdp->wellknown, which);
1002}
1003
1004WINPR_ATTR_MALLOC(WINPR_JSON_Delete, 1)
1005WINPR_JSON* freerdp_utils_aad_get_wellknown(wLog* log, const char* base, const char* tenantid)
1006{
1007 WINPR_ASSERT(base);
1008 WINPR_ASSERT(tenantid);
1009
1010 char* str = NULL;
1011 size_t len = 0;
1012 winpr_asprintf(&str, &len, "https://%s/%s/v2.0/.well-known/openid-configuration", base,
1013 tenantid);
1014
1015 if (!str)
1016 {
1017 WLog_Print(log, WLOG_ERROR, "failed to create request URL for tenantid='%s'", tenantid);
1018 return NULL;
1019 }
1020
1021 BYTE* response = NULL;
1022 long resp_code = 0;
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))
1026 {
1027 WLog_Print(log, WLOG_ERROR, "request for '%s' failed with: %s", str,
1028 freerdp_http_status_string(resp_code));
1029 free(str);
1030 free(response);
1031 return NULL;
1032 }
1033 free(str);
1034
1035 WINPR_JSON* json = WINPR_JSON_ParseWithLength((const char*)response, response_length);
1036 free(response);
1037
1038 if (!json)
1039 WLog_Print(log, WLOG_ERROR, "failed to parse response as JSON: %s",
1041
1042 return json;
1043}
WINPR_API BOOL WINPR_JSON_HasObjectItem(const WINPR_JSON *object, const char *string)
Check if JSON has an object matching the name.
Definition c-json.c:132
WINPR_API BOOL WINPR_JSON_IsString(const WINPR_JSON *item)
Check if JSON item is of type String.
Definition c-json.c:182
WINPR_API double WINPR_JSON_GetNumberValue(const WINPR_JSON *item)
Return the Number value of a JSON item.
Definition c-json.c:147
WINPR_API BOOL WINPR_JSON_IsNumber(const WINPR_JSON *item)
Check if JSON item is of type Number.
Definition c-json.c:177
WINPR_API WINPR_JSON * WINPR_JSON_GetObjectItemCaseSensitive(const WINPR_JSON *object, const char *string)
Same as WINPR_JSON_GetObjectItem but with case sensitive matching.
Definition c-json.c:127
WINPR_API WINPR_JSON * WINPR_JSON_ParseWithLength(const char *value, size_t buffer_length)
Parse a JSON string.
Definition c-json.c:98
WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
Definition c-json.c:142
WINPR_API void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
Definition c-json.c:103
WINPR_API const char * WINPR_JSON_GetErrorPtr(void)
Return an error string.
Definition c-json.c:137
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.