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