FreeRDP
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 
39 struct 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 
55 static BOOL aad_fetch_wellknown(rdpAad* aad);
56 static BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key, char** e, char** n);
57 static BOOL generate_pop_key(rdpAad* aad);
58 
59 WINPR_ATTR_FORMAT_ARG(2, 3)
60 static 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 
84 static 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 
105 static 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;
121 fail:
122  return rc;
123 }
124 
125 static 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 
149 fail:
150  return rc;
151 }
152 
153 static 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 
165 static 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 
183 static 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 
239 fail:
240  free(response);
241  WINPR_JSON_Delete(json);
242  return ret;
243 }
244 
245 int 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 
316 static 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 
333 static 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 
369 static 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 
384 static 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 FALSE;
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);
422 fail:
423  free(buffer);
424  return jws_signature;
425 }
426 
427 static 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);
448 fail:
449  winpr_Digest_Free(md_ctx);
450  return jws_signature;
451 }
452 
453 static 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  }
502 fail:
503  Stream_Free(s, TRUE);
504  free(jws_header);
505  free(jws_payload);
506  free(jws_signature);
507 
508  return ret;
509 }
510 
511 static 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);
530 fail:
531  WINPR_JSON_Delete(json);
532  return ret;
533 }
534 
535 static 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;
563 fail:
564  WINPR_JSON_Delete(json);
565  return rc;
566 }
567 
568 int 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 
586 static BOOL generate_rsa_2048(rdpAad* aad)
587 {
588  WINPR_ASSERT(aad);
589  return freerdp_key_generate(aad->key, 2048);
590 }
591 
592 static 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 
624 fail:
625  winpr_Digest_Free(digest);
626  return b64;
627 }
628 
629 static 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 
651 BOOL 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 
683 fail:
684  free(b64_hash);
685  free(buffer);
686  free(e);
687  free(n);
688  return ret;
689 }
690 
691 static 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 
710 BOOL 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;
739 fail:
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
753 int 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 }
759 int 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 
767 rdpAad* 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;
785 fail:
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 
793 void 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 
808 AAD_STATE aad_get_state(rdpAad* aad)
809 {
810  WINPR_ASSERT(aad);
811  return aad->state;
812 }
813 
814 BOOL aad_is_supported(void)
815 {
816 #ifdef WITH_AAD
817  return TRUE;
818 #else
819  return FALSE;
820 #endif
821 }
822 
823 char* 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 
853 cleanup:
854  WINPR_JSON_Delete(json);
855  return token;
856 }
857 
858 BOOL 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 
881 const 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 
887 const 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 
902 const 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 
957 WINPR_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 
963 WINPR_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 
974 WINPR_ATTR_MALLOC(WINPR_JSON_Delete, 1)
975 WINPR_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:210
WINPR_API WINPR_JSON * WINPR_JSON_ParseWithLength(const char *value, size_t buffer_length)
Parse a JSON string.
Definition: json.c:125
WINPR_API BOOL WINPR_JSON_IsString(const WINPR_JSON *item)
Check if JSON item is of type String.
Definition: json.c:349
WINPR_API double WINPR_JSON_GetNumberValue(const WINPR_JSON *item)
Return the Number value of a JSON item.
Definition: json.c:246
WINPR_API BOOL WINPR_JSON_IsNumber(const WINPR_JSON *item)
Check if JSON item is of type Number.
Definition: json.c:336
WINPR_API void WINPR_JSON_Delete(WINPR_JSON *item)
Delete a WinPR JSON wrapper object.
Definition: json.c:144
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:184
WINPR_API const char * WINPR_JSON_GetStringValue(WINPR_JSON *item)
Return the String value of a JSON item.
Definition: json.c:234
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.