Coverage Report

Created: 2025-07-01 06:46

/src/FreeRDP/libfreerdp/core/aad.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * Network Level Authentication (NLA)
4
 *
5
 * Copyright 2023 Isaac Klein <fifthdegree@protonmail.com>
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
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(wLog* log, rdpContext* context);
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 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
197
static 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_GetObjectItem(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");
248
    goto fail;
249
  }
250
251
  if (!json_get_string_alloc(aad->log, json, "Nonce", &aad->nonce))
252
    goto fail;
253
254
  ret = TRUE;
255
256
fail:
257
  free(response);
258
  WINPR_JSON_Delete(json);
259
  return ret;
260
}
261
262
int aad_client_begin(rdpAad* aad)
263
{
264
  size_t size = 0;
265
266
  WINPR_ASSERT(aad);
267
  WINPR_ASSERT(aad->rdpcontext);
268
269
  rdpSettings* settings = aad->rdpcontext->settings;
270
  WINPR_ASSERT(settings);
271
272
  /* Get the host part of the hostname */
273
  const char* hostname = freerdp_settings_get_string(settings, FreeRDP_AadServerHostname);
274
  if (!hostname)
275
    hostname = freerdp_settings_get_string(settings, FreeRDP_ServerHostname);
276
  if (!hostname)
277
  {
278
    WLog_Print(aad->log, WLOG_ERROR, "FreeRDP_ServerHostname == NULL");
279
    return -1;
280
  }
281
282
  aad->hostname = _strdup(hostname);
283
  if (!aad->hostname)
284
  {
285
    WLog_Print(aad->log, WLOG_ERROR, "_strdup(FreeRDP_ServerHostname) == NULL");
286
    return -1;
287
  }
288
289
  char* p = strchr(aad->hostname, '.');
290
  if (p)
291
    *p = '\0';
292
293
  if (winpr_asprintf(&aad->scope, &size,
294
                     "ms-device-service%%3A%%2F%%2Ftermsrv.wvd.microsoft.com%%2Fname%%2F%s%%"
295
                     "2Fuser_impersonation",
296
                     aad->hostname) <= 0)
297
    return -1;
298
299
  if (!generate_pop_key(aad))
300
    return -1;
301
302
  /* Obtain an oauth authorization code */
303
  pGetCommonAccessToken GetCommonAccessToken = freerdp_get_common_access_token(aad->rdpcontext);
304
  if (!GetCommonAccessToken)
305
  {
306
    WLog_Print(aad->log, WLOG_ERROR, "GetCommonAccessToken == NULL");
307
    return -1;
308
  }
309
310
  if (!aad_fetch_wellknown(aad->log, aad->rdpcontext))
311
    return -1;
312
313
  const BOOL arc = GetCommonAccessToken(aad->rdpcontext, ACCESS_TOKEN_TYPE_AAD,
314
                                        &aad->access_token, 2, aad->scope, aad->kid);
315
  if (!arc)
316
  {
317
    WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain access token");
318
    return -1;
319
  }
320
321
  /* Send the nonce request message */
322
  if (!aad_get_nonce(aad))
323
  {
324
    WLog_Print(aad->log, WLOG_ERROR, "Unable to obtain nonce");
325
    return -1;
326
  }
327
328
  return 1;
329
}
330
331
static char* aad_create_jws_header(rdpAad* aad)
332
{
333
  WINPR_ASSERT(aad);
334
335
  /* Construct the base64url encoded JWS header */
336
  char* buffer = NULL;
337
  size_t bufferlen = 0;
338
  const int length =
339
      winpr_asprintf(&buffer, &bufferlen, "{\"alg\":\"RS256\",\"kid\":\"%s\"}", aad->kid);
340
  if (length < 0)
341
    return NULL;
342
343
  char* jws_header = crypto_base64url_encode((const BYTE*)buffer, bufferlen);
344
  free(buffer);
345
  return jws_header;
346
}
347
348
static char* aad_create_jws_payload(rdpAad* aad, const char* ts_nonce)
349
{
350
  const time_t ts = time(NULL);
351
352
  WINPR_ASSERT(aad);
353
354
  char* e = NULL;
355
  char* n = NULL;
356
  if (!get_encoded_rsa_params(aad->log, aad->key, &e, &n))
357
    return NULL;
358
359
  /* Construct the base64url encoded JWS payload */
360
  char* buffer = NULL;
361
  size_t bufferlen = 0;
362
  const int length =
363
      winpr_asprintf(&buffer, &bufferlen,
364
                     "{"
365
                     "\"ts\":\"%li\","
366
                     "\"at\":\"%s\","
367
                     "\"u\":\"ms-device-service://termsrv.wvd.microsoft.com/name/%s\","
368
                     "\"nonce\":\"%s\","
369
                     "\"cnf\":{\"jwk\":{\"kty\":\"RSA\",\"e\":\"%s\",\"n\":\"%s\"}},"
370
                     "\"client_claims\":\"{\\\"aad_nonce\\\":\\\"%s\\\"}\""
371
                     "}",
372
                     ts, aad->access_token, aad->hostname, ts_nonce, e, n, aad->nonce);
373
  free(e);
374
  free(n);
375
376
  if (length < 0)
377
    return NULL;
378
379
  char* jws_payload = crypto_base64url_encode((BYTE*)buffer, bufferlen);
380
  free(buffer);
381
  return jws_payload;
382
}
383
384
static BOOL aad_update_digest(rdpAad* aad, WINPR_DIGEST_CTX* ctx, const char* what)
385
{
386
  WINPR_ASSERT(aad);
387
  WINPR_ASSERT(ctx);
388
  WINPR_ASSERT(what);
389
390
  const BOOL dsu1 = winpr_DigestSign_Update(ctx, what, strlen(what));
391
  if (!dsu1)
392
  {
393
    WLog_Print(aad->log, WLOG_ERROR, "winpr_DigestSign_Update [%s] failed", what);
394
    return FALSE;
395
  }
396
  return TRUE;
397
}
398
399
static char* aad_final_digest(rdpAad* aad, WINPR_DIGEST_CTX* ctx)
400
{
401
  char* jws_signature = NULL;
402
403
  WINPR_ASSERT(aad);
404
  WINPR_ASSERT(ctx);
405
406
  size_t siglen = 0;
407
  const int dsf = winpr_DigestSign_Final(ctx, NULL, &siglen);
408
  if (dsf <= 0)
409
  {
410
    WLog_Print(aad->log, WLOG_ERROR, "winpr_DigestSign_Final failed with %d", dsf);
411
    return NULL;
412
  }
413
414
  char* buffer = calloc(siglen + 1, sizeof(char));
415
  if (!buffer)
416
  {
417
    WLog_Print(aad->log, WLOG_ERROR, "calloc %" PRIuz " bytes failed", siglen + 1);
418
    goto fail;
419
  }
420
421
  size_t fsiglen = siglen;
422
  const int dsf2 = winpr_DigestSign_Final(ctx, (BYTE*)buffer, &fsiglen);
423
  if (dsf2 <= 0)
424
  {
425
    WLog_Print(aad->log, WLOG_ERROR, "winpr_DigestSign_Final failed with %d", dsf2);
426
    goto fail;
427
  }
428
429
  if (siglen != fsiglen)
430
  {
431
    WLog_Print(aad->log, WLOG_ERROR,
432
               "winpr_DigestSignFinal returned different sizes, first %" PRIuz " then %" PRIuz,
433
               siglen, fsiglen);
434
    goto fail;
435
  }
436
  jws_signature = crypto_base64url_encode((const BYTE*)buffer, fsiglen);
437
fail:
438
  free(buffer);
439
  return jws_signature;
440
}
441
442
static char* aad_create_jws_signature(rdpAad* aad, const char* jws_header, const char* jws_payload)
443
{
444
  char* jws_signature = NULL;
445
446
  WINPR_ASSERT(aad);
447
448
  WINPR_DIGEST_CTX* md_ctx = freerdp_key_digest_sign(aad->key, WINPR_MD_SHA256);
449
  if (!md_ctx)
450
  {
451
    WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_New failed");
452
    goto fail;
453
  }
454
455
  if (!aad_update_digest(aad, md_ctx, jws_header))
456
    goto fail;
457
  if (!aad_update_digest(aad, md_ctx, "."))
458
    goto fail;
459
  if (!aad_update_digest(aad, md_ctx, jws_payload))
460
    goto fail;
461
462
  jws_signature = aad_final_digest(aad, md_ctx);
463
fail:
464
  winpr_Digest_Free(md_ctx);
465
  return jws_signature;
466
}
467
468
static int aad_send_auth_request(rdpAad* aad, const char* ts_nonce)
469
{
470
  int ret = -1;
471
  char* jws_header = NULL;
472
  char* jws_payload = NULL;
473
  char* jws_signature = NULL;
474
475
  WINPR_ASSERT(aad);
476
  WINPR_ASSERT(ts_nonce);
477
478
  wStream* s = Stream_New(NULL, 1024);
479
  if (!s)
480
    goto fail;
481
482
  /* Construct the base64url encoded JWS header */
483
  jws_header = aad_create_jws_header(aad);
484
  if (!jws_header)
485
    goto fail;
486
487
  /* Construct the base64url encoded JWS payload */
488
  jws_payload = aad_create_jws_payload(aad, ts_nonce);
489
  if (!jws_payload)
490
    goto fail;
491
492
  /* Sign the JWS with the pop key */
493
  jws_signature = aad_create_jws_signature(aad, jws_header, jws_payload);
494
  if (!jws_signature)
495
    goto fail;
496
497
  /* Construct the Authentication Request PDU with the JWS as the RDP Assertion */
498
  if (stream_sprintf(s, "{\"rdp_assertion\":\"%s.%s.%s\"}", jws_header, jws_payload,
499
                     jws_signature) < 0)
500
    goto fail;
501
502
  /* Include null terminator in PDU */
503
  Stream_Write_UINT8(s, 0);
504
505
  Stream_SealLength(s);
506
507
  if (transport_write(aad->transport, s) < 0)
508
  {
509
    WLog_Print(aad->log, WLOG_ERROR, "transport_write [%" PRIdz " bytes] failed",
510
               Stream_Length(s));
511
  }
512
  else
513
  {
514
    ret = 1;
515
    aad->state = AAD_STATE_AUTH;
516
  }
517
fail:
518
  Stream_Free(s, TRUE);
519
  free(jws_header);
520
  free(jws_payload);
521
  free(jws_signature);
522
523
  return ret;
524
}
525
526
static int aad_parse_state_initial(rdpAad* aad, wStream* s)
527
{
528
  const char* jstr = Stream_PointerAs(s, char);
529
  const size_t jlen = Stream_GetRemainingLength(s);
530
  const char* ts_nonce = NULL;
531
  int ret = -1;
532
  WINPR_JSON* json = NULL;
533
534
  if (!Stream_SafeSeek(s, jlen))
535
    goto fail;
536
537
  json = WINPR_JSON_ParseWithLength(jstr, jlen);
538
  if (!json)
539
    goto fail;
540
541
  if (!json_get_const_string(aad->log, json, "ts_nonce", &ts_nonce))
542
    goto fail;
543
544
  ret = aad_send_auth_request(aad, ts_nonce);
545
fail:
546
  WINPR_JSON_Delete(json);
547
  return ret;
548
}
549
550
static int aad_parse_state_auth(rdpAad* aad, wStream* s)
551
{
552
  int rc = -1;
553
  double result = 0;
554
  DWORD error_code = 0;
555
  WINPR_JSON* json = NULL;
556
  const char* jstr = Stream_PointerAs(s, char);
557
  const size_t jlength = Stream_GetRemainingLength(s);
558
559
  if (!Stream_SafeSeek(s, jlength))
560
    goto fail;
561
562
  json = WINPR_JSON_ParseWithLength(jstr, jlength);
563
  if (!json)
564
    goto fail;
565
566
  if (!json_get_number(aad->log, json, "authentication_result", &result))
567
    goto fail;
568
  error_code = (DWORD)result;
569
570
  if (error_code != S_OK)
571
  {
572
    WLog_Print(aad->log, WLOG_ERROR, "Authentication result: %s (0x%08" PRIx32 ")",
573
               aad_auth_result_to_string(error_code), error_code);
574
    goto fail;
575
  }
576
  aad->state = AAD_STATE_FINAL;
577
  rc = 1;
578
fail:
579
  WINPR_JSON_Delete(json);
580
  return rc;
581
}
582
583
int aad_recv(rdpAad* aad, wStream* s)
584
{
585
  WINPR_ASSERT(aad);
586
  WINPR_ASSERT(s);
587
588
  switch (aad->state)
589
  {
590
    case AAD_STATE_INITIAL:
591
      return aad_parse_state_initial(aad, s);
592
    case AAD_STATE_AUTH:
593
      return aad_parse_state_auth(aad, s);
594
    case AAD_STATE_FINAL:
595
    default:
596
      WLog_Print(aad->log, WLOG_ERROR, "Invalid AAD_STATE %d", aad->state);
597
      return -1;
598
  }
599
}
600
601
static BOOL generate_rsa_2048(rdpAad* aad)
602
{
603
  WINPR_ASSERT(aad);
604
  return freerdp_key_generate(aad->key, "RSA", 1, 2048);
605
}
606
607
static char* generate_rsa_digest_base64_str(rdpAad* aad, const char* input, size_t ilen)
608
{
609
  char* b64 = NULL;
610
  WINPR_DIGEST_CTX* digest = winpr_Digest_New();
611
  if (!digest)
612
  {
613
    WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_New failed");
614
    goto fail;
615
  }
616
617
  if (!winpr_Digest_Init(digest, WINPR_MD_SHA256))
618
  {
619
    WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_Init(WINPR_MD_SHA256) failed");
620
    goto fail;
621
  }
622
623
  if (!winpr_Digest_Update(digest, (const BYTE*)input, ilen))
624
  {
625
    WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_Update(%" PRIuz ") failed", ilen);
626
    goto fail;
627
  }
628
629
  BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = { 0 };
630
  if (!winpr_Digest_Final(digest, hash, sizeof(hash)))
631
  {
632
    WLog_Print(aad->log, WLOG_ERROR, "winpr_Digest_Final(%" PRIuz ") failed", sizeof(hash));
633
    goto fail;
634
  }
635
636
  /* Base64url encode the hash */
637
  b64 = crypto_base64url_encode(hash, sizeof(hash));
638
639
fail:
640
  winpr_Digest_Free(digest);
641
  return b64;
642
}
643
644
static BOOL generate_json_base64_str(rdpAad* aad, const char* b64_hash)
645
{
646
  WINPR_ASSERT(aad);
647
648
  char* buffer = NULL;
649
  size_t blen = 0;
650
  const int length = winpr_asprintf(&buffer, &blen, "{\"kid\":\"%s\"}", b64_hash);
651
  if (length < 0)
652
    return FALSE;
653
654
  /* Finally, base64url encode the JSON text to form the kid */
655
  free(aad->kid);
656
  aad->kid = crypto_base64url_encode((const BYTE*)buffer, (size_t)length);
657
  free(buffer);
658
659
  if (!aad->kid)
660
  {
661
    return FALSE;
662
  }
663
  return TRUE;
664
}
665
666
BOOL generate_pop_key(rdpAad* aad)
667
{
668
  BOOL ret = FALSE;
669
  char* buffer = NULL;
670
  char* b64_hash = NULL;
671
  char* e = NULL;
672
  char* n = NULL;
673
674
  WINPR_ASSERT(aad);
675
676
  /* Generate a 2048-bit RSA key pair */
677
  if (!generate_rsa_2048(aad))
678
    goto fail;
679
680
  /* Encode the public key as a JWK */
681
  if (!get_encoded_rsa_params(aad->log, aad->key, &e, &n))
682
    goto fail;
683
684
  size_t blen = 0;
685
  const int alen =
686
      winpr_asprintf(&buffer, &blen, "{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}", e, n);
687
  if (alen < 0)
688
    goto fail;
689
690
  /* Hash the encoded public key */
691
  b64_hash = generate_rsa_digest_base64_str(aad, buffer, blen);
692
  if (!b64_hash)
693
    goto fail;
694
695
  /* Encode a JSON object with a single property "kid" whose value is the encoded hash */
696
  ret = generate_json_base64_str(aad, b64_hash);
697
698
fail:
699
  free(b64_hash);
700
  free(buffer);
701
  free(e);
702
  free(n);
703
  return ret;
704
}
705
706
static char* bn_to_base64_url(wLog* wlog, rdpPrivateKey* key, enum FREERDP_KEY_PARAM param)
707
{
708
  WINPR_ASSERT(wlog);
709
  WINPR_ASSERT(key);
710
711
  size_t len = 0;
712
  BYTE* bn = freerdp_key_get_param(key, param, &len);
713
  if (!bn)
714
    return NULL;
715
716
  char* b64 = crypto_base64url_encode(bn, len);
717
  free(bn);
718
719
  if (!b64)
720
    WLog_Print(wlog, WLOG_ERROR, "failed  base64 url encode BIGNUM");
721
722
  return b64;
723
}
724
725
BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key, char** pe, char** pn)
726
{
727
  BOOL rc = FALSE;
728
  char* e = NULL;
729
  char* n = NULL;
730
731
  WINPR_ASSERT(wlog);
732
  WINPR_ASSERT(key);
733
  WINPR_ASSERT(pe);
734
  WINPR_ASSERT(pn);
735
736
  *pe = NULL;
737
  *pn = NULL;
738
739
  e = bn_to_base64_url(wlog, key, FREERDP_KEY_PARAM_RSA_E);
740
  if (!e)
741
  {
742
    WLog_Print(wlog, WLOG_ERROR, "failed  base64 url encode RSA E");
743
    goto fail;
744
  }
745
746
  n = bn_to_base64_url(wlog, key, FREERDP_KEY_PARAM_RSA_N);
747
  if (!n)
748
  {
749
    WLog_Print(wlog, WLOG_ERROR, "failed  base64 url encode RSA N");
750
    goto fail;
751
  }
752
753
  rc = TRUE;
754
fail:
755
  if (!rc)
756
  {
757
    free(e);
758
    free(n);
759
  }
760
  else
761
  {
762
    *pe = e;
763
    *pn = n;
764
  }
765
  return rc;
766
}
767
#else
768
int aad_client_begin(rdpAad* aad)
769
0
{
770
0
  WINPR_ASSERT(aad);
771
0
  WLog_Print(aad->log, WLOG_ERROR, "AAD security not compiled in, aborting!");
772
0
  return -1;
773
0
}
774
775
int aad_recv(rdpAad* aad, wStream* s)
776
0
{
777
0
  WINPR_ASSERT(aad);
778
0
  WLog_Print(aad->log, WLOG_ERROR, "AAD security not compiled in, aborting!");
779
0
  return -1;
780
0
}
781
782
static BOOL ensure_wellknown(WINPR_ATTR_UNUSED rdpContext* context)
783
0
{
784
0
  return FALSE;
785
0
}
786
787
#endif
788
789
rdpAad* aad_new(rdpContext* context, rdpTransport* transport)
790
0
{
791
0
  WINPR_ASSERT(transport);
792
0
  WINPR_ASSERT(context);
793
794
0
  rdpAad* aad = (rdpAad*)calloc(1, sizeof(rdpAad));
795
796
0
  if (!aad)
797
0
    return NULL;
798
799
0
  aad->log = WLog_Get(FREERDP_TAG("aad"));
800
0
  aad->key = freerdp_key_new();
801
0
  if (!aad->key)
802
0
    goto fail;
803
0
  aad->rdpcontext = context;
804
0
  aad->transport = transport;
805
806
0
  return aad;
807
0
fail:
808
0
  WINPR_PRAGMA_DIAG_PUSH
809
0
  WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
810
0
  aad_free(aad);
811
0
  WINPR_PRAGMA_DIAG_POP
812
0
  return NULL;
813
0
}
814
815
void aad_free(rdpAad* aad)
816
0
{
817
0
  if (!aad)
818
0
    return;
819
820
0
  free(aad->hostname);
821
0
  free(aad->scope);
822
0
  free(aad->nonce);
823
0
  free(aad->access_token);
824
0
  free(aad->kid);
825
0
  freerdp_key_free(aad->key);
826
827
0
  free(aad);
828
0
}
829
830
AAD_STATE aad_get_state(rdpAad* aad)
831
0
{
832
0
  WINPR_ASSERT(aad);
833
0
  return aad->state;
834
0
}
835
836
BOOL aad_is_supported(void)
837
0
{
838
#ifdef WITH_AAD
839
  return TRUE;
840
#else
841
0
  return FALSE;
842
0
#endif
843
0
}
844
845
char* freerdp_utils_aad_get_access_token(wLog* log, const char* data, size_t length)
846
0
{
847
0
  char* token = NULL;
848
0
  WINPR_JSON* access_token_prop = NULL;
849
0
  const char* access_token_str = NULL;
850
851
0
  WINPR_JSON* json = WINPR_JSON_ParseWithLength(data, length);
852
0
  if (!json)
853
0
  {
854
0
    WLog_Print(log, WLOG_ERROR, "Failed to parse access token response [got %" PRIuz " bytes",
855
0
               length);
856
0
    goto cleanup;
857
0
  }
858
859
0
  access_token_prop = WINPR_JSON_GetObjectItem(json, "access_token");
860
0
  if (!access_token_prop)
861
0
  {
862
0
    WLog_Print(log, WLOG_ERROR, "Response has no \"access_token\" property");
863
0
    goto cleanup;
864
0
  }
865
866
0
  access_token_str = WINPR_JSON_GetStringValue(access_token_prop);
867
0
  if (!access_token_str)
868
0
  {
869
0
    WLog_Print(log, WLOG_ERROR, "Invalid value for \"access_token\"");
870
0
    goto cleanup;
871
0
  }
872
873
0
  token = _strdup(access_token_str);
874
875
0
cleanup:
876
0
  WINPR_JSON_Delete(json);
877
0
  return token;
878
0
}
879
880
BOOL aad_fetch_wellknown(wLog* log, rdpContext* context)
881
0
{
882
0
  WINPR_ASSERT(context);
883
884
0
  rdpRdp* rdp = context->rdp;
885
0
  WINPR_ASSERT(rdp);
886
887
0
  if (rdp->wellknown)
888
0
    return TRUE;
889
890
0
  const char* base =
891
0
      freerdp_settings_get_string(context->settings, FreeRDP_GatewayAzureActiveDirectory);
892
0
  const BOOL useTenant =
893
0
      freerdp_settings_get_bool(context->settings, FreeRDP_GatewayAvdUseTenantid);
894
0
  const char* tenantid = "common";
895
0
  if (useTenant)
896
0
    tenantid = freerdp_settings_get_string(context->settings, FreeRDP_GatewayAvdAadtenantid);
897
0
  rdp->wellknown = freerdp_utils_aad_get_wellknown(log, base, tenantid);
898
0
  return rdp->wellknown ? TRUE : FALSE;
899
0
}
900
901
const char* freerdp_utils_aad_get_wellknown_string(rdpContext* context, AAD_WELLKNOWN_VALUES which)
902
0
{
903
0
  return freerdp_utils_aad_get_wellknown_custom_string(
904
0
      context, freerdp_utils_aad_wellknwon_value_name(which));
905
0
}
906
907
const char* freerdp_utils_aad_get_wellknown_custom_string(rdpContext* context, const char* which)
908
0
{
909
0
  WINPR_ASSERT(context);
910
0
  WINPR_ASSERT(context->rdp);
911
912
0
  if (!ensure_wellknown(context))
913
0
    return NULL;
914
915
0
  WINPR_JSON* obj = WINPR_JSON_GetObjectItem(context->rdp->wellknown, which);
916
0
  if (!obj)
917
0
    return NULL;
918
919
0
  return WINPR_JSON_GetStringValue(obj);
920
0
}
921
922
const char* freerdp_utils_aad_wellknwon_value_name(AAD_WELLKNOWN_VALUES which)
923
0
{
924
0
  switch (which)
925
0
  {
926
0
    case AAD_WELLKNOWN_token_endpoint:
927
0
      return "token_endpoint";
928
0
    case AAD_WELLKNOWN_token_endpoint_auth_methods_supported:
929
0
      return "token_endpoint_auth_methods_supported";
930
0
    case AAD_WELLKNOWN_jwks_uri:
931
0
      return "jwks_uri";
932
0
    case AAD_WELLKNOWN_response_modes_supported:
933
0
      return "response_modes_supported";
934
0
    case AAD_WELLKNOWN_subject_types_supported:
935
0
      return "subject_types_supported";
936
0
    case AAD_WELLKNOWN_id_token_signing_alg_values_supported:
937
0
      return "id_token_signing_alg_values_supported";
938
0
    case AAD_WELLKNOWN_response_types_supported:
939
0
      return "response_types_supported";
940
0
    case AAD_WELLKNOWN_scopes_supported:
941
0
      return "scopes_supported";
942
0
    case AAD_WELLKNOWN_issuer:
943
0
      return "issuer";
944
0
    case AAD_WELLKNOWN_request_uri_parameter_supported:
945
0
      return "request_uri_parameter_supported";
946
0
    case AAD_WELLKNOWN_userinfo_endpoint:
947
0
      return "userinfo_endpoint";
948
0
    case AAD_WELLKNOWN_authorization_endpoint:
949
0
      return "authorization_endpoint";
950
0
    case AAD_WELLKNOWN_device_authorization_endpoint:
951
0
      return "device_authorization_endpoint";
952
0
    case AAD_WELLKNOWN_http_logout_supported:
953
0
      return "http_logout_supported";
954
0
    case AAD_WELLKNOWN_frontchannel_logout_supported:
955
0
      return "frontchannel_logout_supported";
956
0
    case AAD_WELLKNOWN_end_session_endpoint:
957
0
      return "end_session_endpoint";
958
0
    case AAD_WELLKNOWN_claims_supported:
959
0
      return "claims_supported";
960
0
    case AAD_WELLKNOWN_kerberos_endpoint:
961
0
      return "kerberos_endpoint";
962
0
    case AAD_WELLKNOWN_tenant_region_scope:
963
0
      return "tenant_region_scope";
964
0
    case AAD_WELLKNOWN_cloud_instance_name:
965
0
      return "cloud_instance_name";
966
0
    case AAD_WELLKNOWN_cloud_graph_host_name:
967
0
      return "cloud_graph_host_name";
968
0
    case AAD_WELLKNOWN_msgraph_host:
969
0
      return "msgraph_host";
970
0
    case AAD_WELLKNOWN_rbac_url:
971
0
      return "rbac_url";
972
0
    default:
973
0
      return "UNKNOWN";
974
0
  }
975
0
}
976
977
WINPR_JSON* freerdp_utils_aad_get_wellknown_object(rdpContext* context, AAD_WELLKNOWN_VALUES which)
978
0
{
979
0
  return freerdp_utils_aad_get_wellknown_custom_object(
980
0
      context, freerdp_utils_aad_wellknwon_value_name(which));
981
0
}
982
983
WINPR_JSON* freerdp_utils_aad_get_wellknown_custom_object(rdpContext* context, const char* which)
984
0
{
985
0
  WINPR_ASSERT(context);
986
0
  WINPR_ASSERT(context->rdp);
987
988
0
  if (!ensure_wellknown(context))
989
0
    return NULL;
990
991
0
  return WINPR_JSON_GetObjectItem(context->rdp->wellknown, which);
992
0
}
993
994
WINPR_ATTR_MALLOC(WINPR_JSON_Delete, 1)
995
WINPR_JSON* freerdp_utils_aad_get_wellknown(wLog* log, const char* base, const char* tenantid)
996
0
{
997
0
  WINPR_ASSERT(base);
998
0
  WINPR_ASSERT(tenantid);
999
1000
0
  char* str = NULL;
1001
0
  size_t len = 0;
1002
0
  winpr_asprintf(&str, &len, "https://%s/%s/v2.0/.well-known/openid-configuration", base,
1003
0
                 tenantid);
1004
1005
0
  if (!str)
1006
0
  {
1007
0
    WLog_Print(log, WLOG_ERROR, "failed to create request URL for tenantid='%s'", tenantid);
1008
0
    return NULL;
1009
0
  }
1010
1011
0
  BYTE* response = NULL;
1012
0
  long resp_code = 0;
1013
0
  size_t response_length = 0;
1014
0
  const BOOL rc = freerdp_http_request(str, NULL, &resp_code, &response, &response_length);
1015
0
  if (!rc || (resp_code != HTTP_STATUS_OK))
1016
0
  {
1017
0
    WLog_Print(log, WLOG_ERROR, "request for '%s' failed with: %s", str,
1018
0
               freerdp_http_status_string(resp_code));
1019
0
    free(str);
1020
0
    free(response);
1021
0
    return NULL;
1022
0
  }
1023
0
  free(str);
1024
1025
0
  WINPR_JSON* json = WINPR_JSON_ParseWithLength((const char*)response, response_length);
1026
0
  free(response);
1027
1028
0
  if (!json)
1029
0
    WLog_Print(log, WLOG_ERROR, "failed to parse response as JSON");
1030
1031
0
  return json;
1032
0
}