aboutsummaryrefslogtreecommitdiff
path: root/src/reclaim/oidc_helper.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/reclaim/oidc_helper.c')
-rw-r--r--src/reclaim/oidc_helper.c1030
1 files changed, 0 insertions, 1030 deletions
diff --git a/src/reclaim/oidc_helper.c b/src/reclaim/oidc_helper.c
deleted file mode 100644
index cfa71b26c..000000000
--- a/src/reclaim/oidc_helper.c
+++ /dev/null
@@ -1,1030 +0,0 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2010-2015 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file reclaim/oidc_helper.c
23 * @brief helper library for OIDC related functions
24 * @author Martin Schanzenbach
25 * @author Tristan Schwieren
26 */
27#include "platform.h"
28#include <inttypes.h>
29#include <jansson.h>
30#include <jose/jose.h>
31#include "gnunet_util_lib.h"
32#include "gnunet_reclaim_lib.h"
33#include "gnunet_reclaim_service.h"
34#include "gnunet_signatures.h"
35#include "oidc_helper.h"
36// #include "benchmark.h"
37#include <gcrypt.h>
38
39GNUNET_NETWORK_STRUCT_BEGIN
40
41/**
42 * The signature used to generate the authorization code
43 */
44struct OIDC_Parameters
45{
46 /**
47 * The reclaim ticket
48 */
49 struct GNUNET_RECLAIM_Ticket ticket;
50
51 /**
52 * The nonce length
53 */
54 uint32_t nonce_len GNUNET_PACKED;
55
56 /**
57 * The length of the PKCE code_challenge
58 */
59 uint32_t code_challenge_len GNUNET_PACKED;
60
61 /**
62 * The length of the attributes list
63 */
64 uint32_t attr_list_len GNUNET_PACKED;
65
66 /**
67 * The length of the presentation list
68 */
69 uint32_t pres_list_len GNUNET_PACKED;
70};
71
72GNUNET_NETWORK_STRUCT_END
73
74/**
75 * Standard claims represented by the "profile" scope in OIDC
76 */
77static char OIDC_profile_claims[14][32] = {
78 "name", "family_name", "given_name", "middle_name", "nickname",
79 "preferred_username", "profile", "picture", "website", "gender", "birthdate",
80 "zoneinfo", "locale", "updated_at"
81};
82
83/**
84 * Standard claims represented by the "email" scope in OIDC
85 */
86static char OIDC_email_claims[2][16] = {
87 "email", "email_verified"
88};
89
90/**
91 * Standard claims represented by the "phone" scope in OIDC
92 */
93static char OIDC_phone_claims[2][32] = {
94 "phone_number", "phone_number_verified"
95};
96
97/**
98 * Standard claims represented by the "address" scope in OIDC
99 */
100static char OIDC_address_claims[5][32] = {
101 "street_address", "locality", "region", "postal_code", "country"
102};
103
104static enum GNUNET_GenericReturnValue
105is_claim_in_address_scope (const char *claim)
106{
107 int i;
108 for (i = 0; i < 5; i++)
109 {
110 if (0 == strcmp (claim, OIDC_address_claims[i]))
111 {
112 return GNUNET_YES;
113 }
114 }
115 return GNUNET_NO;
116}
117
118
119static char *
120create_jwt_hmac_header (void)
121{
122 json_t *root;
123 char *json_str;
124
125 root = json_object ();
126 json_object_set_new (root, JWT_ALG, json_string (JWT_ALG_VALUE_HMAC));
127 json_object_set_new (root, JWT_TYP, json_string (JWT_TYP_VALUE));
128
129 json_str = json_dumps (root, JSON_INDENT (0) | JSON_COMPACT);
130 json_decref (root);
131 return json_str;
132}
133
134
135static void
136replace_char (char *str, char find, char replace)
137{
138 char *current_pos = strchr (str, find);
139
140 while (current_pos)
141 {
142 *current_pos = replace;
143 current_pos = strchr (current_pos, find);
144 }
145}
146
147
148// RFC4648
149static void
150fix_base64 (char *str)
151{
152 // Replace + with -
153 replace_char (str, '+', '-');
154
155 // Replace / with _
156 replace_char (str, '/', '_');
157}
158
159
160static json_t*
161generate_userinfo_json (const struct GNUNET_IDENTITY_PublicKey *sub_key,
162 const struct GNUNET_RECLAIM_AttributeList *attrs,
163 const struct
164 GNUNET_RECLAIM_PresentationList *presentations)
165{
166 struct GNUNET_RECLAIM_AttributeListEntry *le;
167 struct GNUNET_RECLAIM_PresentationListEntry *ple;
168 char *subject;
169 char *source_name;
170 char *attr_val_str;
171 char *pres_val_str;
172 json_t *body;
173 json_t *aggr_names;
174 json_t *aggr_sources;
175 json_t *aggr_sources_jwt;
176 json_t *addr_claim = NULL;
177 int num_presentations = 0;
178 for (le = attrs->list_head; NULL != le; le = le->next)
179 {
180 if (GNUNET_NO == GNUNET_RECLAIM_id_is_zero (&le->attribute->credential))
181 num_presentations++;
182 }
183
184 subject =
185 GNUNET_STRINGS_data_to_string_alloc (sub_key,
186 sizeof(struct
187 GNUNET_IDENTITY_PublicKey));
188 body = json_object ();
189 aggr_names = json_object ();
190 aggr_sources = json_object ();
191
192 // iss REQUIRED case sensitive server uri with https
193 // The issuer is the local reclaim instance (e.g.
194 // https://reclaim.id/api/openid)
195 json_object_set_new (body, "iss", json_string (SERVER_ADDRESS));
196 // sub REQUIRED public key identity, not exceed 255 ASCII length
197 json_object_set_new (body, "sub", json_string (subject));
198 GNUNET_free (subject);
199 pres_val_str = NULL;
200 source_name = NULL;
201 int i = 0;
202 for (ple = presentations->list_head; NULL != ple; ple = ple->next)
203 {
204 // New presentation
205 GNUNET_asprintf (&source_name,
206 "src%d",
207 i);
208 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
209 "Adding new presentation source #%d\n", i);
210 aggr_sources_jwt = json_object ();
211 pres_val_str =
212 GNUNET_RECLAIM_presentation_value_to_string (ple->presentation->type,
213 ple->presentation->data,
214 ple->presentation->data_size);
215 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
216 "Presentation is: %s\n", pres_val_str);
217 json_object_set_new (aggr_sources_jwt,
218 GNUNET_RECLAIM_presentation_number_to_typename (
219 ple->presentation->type),
220 json_string (pres_val_str) );
221 json_object_set_new (aggr_sources, source_name, aggr_sources_jwt);
222 GNUNET_free (pres_val_str);
223 GNUNET_free (source_name);
224 source_name = NULL;
225 i++;
226 }
227
228 int addr_is_aggregated = GNUNET_NO;
229 int addr_is_normal = GNUNET_NO;
230 for (le = attrs->list_head; NULL != le; le = le->next)
231 {
232 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
233 "Processing %s for userinfo body\n",
234 le->attribute->name);
235 if (GNUNET_YES == GNUNET_RECLAIM_id_is_zero (&le->attribute->credential))
236 {
237 attr_val_str =
238 GNUNET_RECLAIM_attribute_value_to_string (le->attribute->type,
239 le->attribute->data,
240 le->attribute->data_size);
241 /**
242 * There is this weird quirk that the individual address claim(s) must be
243 * inside a JSON object of the "address" claim.
244 */
245 if (GNUNET_YES == is_claim_in_address_scope (le->attribute->name))
246 {
247 if (GNUNET_YES == addr_is_aggregated)
248 {
249 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
250 "Address is set as aggregated claim. Skipping self-issued value...\n");
251 GNUNET_free (attr_val_str);
252 continue;
253 }
254 addr_is_normal = GNUNET_YES;
255
256 if (NULL == addr_claim)
257 {
258 addr_claim = json_object ();
259 json_object_set_new (body, "address", addr_claim);
260 }
261 json_object_set_new (addr_claim, le->attribute->name,
262 json_string (attr_val_str));
263
264 }
265 else
266 {
267 json_object_set_new (body, le->attribute->name,
268 json_string (attr_val_str));
269 }
270 GNUNET_free (attr_val_str);
271 }
272 else
273 {
274 // Check if presentation is there
275 int j = 0;
276 for (ple = presentations->list_head; NULL != ple; ple = ple->next)
277 {
278 if (GNUNET_YES ==
279 GNUNET_RECLAIM_id_is_equal (&ple->presentation->credential_id,
280 &le->attribute->credential))
281 break;
282 j++;
283 }
284 if (NULL == ple)
285 {
286 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
287 "Presentation for `%s' missing...\n",
288 le->attribute->name);
289 continue;
290 }
291 /**
292 * There is this weird quirk that the individual address claim(s) must be
293 * inside a JSON object of the "address" claim.
294 */
295 if (GNUNET_YES == is_claim_in_address_scope (le->attribute->name))
296 {
297 if (GNUNET_YES == addr_is_normal)
298 {
299 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
300 "Address is already set as normal claim. Skipping attested value...\n");
301 continue;
302 }
303 addr_is_aggregated = GNUNET_YES;
304 /** This is/can only be set once! **/
305 if (NULL != addr_claim)
306 continue;
307 addr_claim = json_object ();
308 GNUNET_asprintf (&source_name,
309 "src%d",
310 j);
311 json_object_set_new (aggr_names, "address",
312 json_string (source_name));
313 GNUNET_free (source_name);
314 }
315 else
316 {
317 // Presentation exists, hence take the respective source str
318 GNUNET_asprintf (&source_name,
319 "src%d",
320 j);
321 json_object_set_new (aggr_names, le->attribute->name,
322 json_string (source_name));
323 GNUNET_free (source_name);
324 }
325 }
326 }
327 if (0 != i)
328 {
329 json_object_set_new (body, "_claim_names", aggr_names);
330 json_object_set_new (body, "_claim_sources", aggr_sources);
331 }
332
333 return body;
334}
335
336
337/**
338 * Generate userinfo JSON as string
339 *
340 * @param sub_key the subject (user)
341 * @param attrs user attribute list
342 * @param presentations credential presentation list (may be empty)
343 * @return Userinfo JSON
344 */
345char *
346OIDC_generate_userinfo (const struct GNUNET_IDENTITY_PublicKey *sub_key,
347 const struct GNUNET_RECLAIM_AttributeList *attrs,
348 const struct
349 GNUNET_RECLAIM_PresentationList *presentations)
350{
351 char *body_str;
352 json_t*body = generate_userinfo_json (sub_key,
353 attrs,
354 presentations);
355 body_str = json_dumps (body, JSON_INDENT (0) | JSON_COMPACT);
356 json_decref (body);
357 return body_str;
358}
359
360
361char *
362generate_id_token_body (const struct GNUNET_IDENTITY_PublicKey *aud_key,
363 const struct GNUNET_IDENTITY_PublicKey *sub_key,
364 const struct GNUNET_RECLAIM_AttributeList *attrs,
365 const struct
366 GNUNET_RECLAIM_PresentationList *presentations,
367 const struct GNUNET_TIME_Relative *expiration_time,
368 const char *nonce)
369{
370 struct GNUNET_HashCode signature;
371 struct GNUNET_TIME_Absolute exp_time;
372 struct GNUNET_TIME_Absolute time_now;
373 json_t *body;
374 char *audience;
375 char *subject;
376 char *body_str;
377
378 body = generate_userinfo_json (sub_key,
379 attrs,
380 presentations);
381 // iat REQUIRED time now
382 time_now = GNUNET_TIME_absolute_get ();
383 // exp REQUIRED time expired from config
384 exp_time = GNUNET_TIME_absolute_add (time_now, *expiration_time);
385 // auth_time only if max_age
386 // nonce only if nonce
387 // OPTIONAL acr,amr,azp
388 subject =
389 GNUNET_STRINGS_data_to_string_alloc (sub_key,
390 sizeof(struct
391 GNUNET_IDENTITY_PublicKey));
392 audience =
393 GNUNET_STRINGS_data_to_string_alloc (aud_key,
394 sizeof(struct
395 GNUNET_IDENTITY_PublicKey));
396
397 // aud REQUIRED public key client_id must be there
398 json_object_set_new (body, "aud", json_string (audience));
399 // iat
400 json_object_set_new (body,
401 "iat",
402 json_integer (time_now.abs_value_us / (1000 * 1000)));
403 // exp
404 json_object_set_new (body,
405 "exp",
406 json_integer (exp_time.abs_value_us / (1000 * 1000)));
407 // nbf
408 json_object_set_new (body,
409 "nbf",
410 json_integer (time_now.abs_value_us / (1000 * 1000)));
411 // nonce
412 if (NULL != nonce)
413 json_object_set_new (body, "nonce", json_string (nonce));
414
415 // Error checking
416 body_str = json_dumps (body, JSON_INDENT (2) | JSON_COMPACT);
417 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"ID-Token: %s\n", body_str);
418
419 json_decref (body);
420 GNUNET_free (subject);
421 GNUNET_free (audience);
422
423 return body_str;
424}
425
426
427/**
428 * Create a JWT using RSA256 algorithm from attributes
429 *
430 * @param aud_key the public of the audience
431 * @param sub_key the public key of the subject
432 * @param attrs the attribute list
433 * @param presentations credential presentation list (may be empty)
434 * @param expiration_time the validity of the token
435 * @param secret_rsa_key the key used to sign the JWT
436 * @return a new base64-encoded JWT string.
437 */
438char *
439OIDC_generate_id_token_rsa (const struct GNUNET_IDENTITY_PublicKey *aud_key,
440 const struct GNUNET_IDENTITY_PublicKey *sub_key,
441 const struct GNUNET_RECLAIM_AttributeList *attrs,
442 const struct
443 GNUNET_RECLAIM_PresentationList *presentations,
444 const struct GNUNET_TIME_Relative *expiration_time,
445 const char *nonce,
446 const json_t *secret_rsa_key)
447{
448 json_t *jws;
449 char *body_str;
450 char *result;
451
452 // Generate the body of the JSON Web Signature
453 body_str = generate_id_token_body (aud_key,
454 sub_key,
455 attrs,
456 presentations,
457 expiration_time,
458 nonce);
459
460 if (! body_str)
461 {
462 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
463 "Body for the JWS could not be generated\n");
464 }
465
466 // Creating the JSON Web Signature.
467 jws = json_pack ("{s:o}", "payload",
468 jose_b64_enc (body_str, strlen (body_str)));
469
470 if (! jose_jws_sig (NULL, jws, NULL, secret_rsa_key))
471 {
472 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
473 "Signature generation failed\n");
474 }
475
476 // Encoding JSON as compact JSON Web Signature
477 GNUNET_asprintf (&result, "%s.%s.%s",
478 json_string_value (json_object_get (jws, "protected")),
479 json_string_value (json_object_get (jws, "payload")),
480 json_string_value (json_object_get (jws, "signature")) );
481
482 json_decref(jws);
483 GNUNET_free(body_str);
484 return result;
485}
486
487/**
488 * Create a JWT using HMAC (HS256) from attributes
489 *
490 * @param aud_key the public of the audience
491 * @param sub_key the public key of the subject
492 * @param attrs the attribute list
493 * @param presentations credential presentation list (may be empty)
494 * @param expiration_time the validity of the token
495 * @param secret_key the key used to sign the JWT
496 * @return a new base64-encoded JWT string.
497 */
498char *
499OIDC_generate_id_token_hmac (const struct GNUNET_IDENTITY_PublicKey *aud_key,
500 const struct GNUNET_IDENTITY_PublicKey *sub_key,
501 const struct GNUNET_RECLAIM_AttributeList *attrs,
502 const struct
503 GNUNET_RECLAIM_PresentationList *presentations,
504 const struct GNUNET_TIME_Relative *expiration_time,
505 const char *nonce,
506 const char *secret_key)
507{
508 struct GNUNET_HashCode signature;
509 struct GNUNET_TIME_Absolute exp_time;
510 struct GNUNET_TIME_Absolute time_now;
511 char *header;
512 char *header_base64;
513 char *body_str;
514 char *body_base64;
515 char *signature_target;
516 char *signature_base64;
517 char *result;
518
519 // Generate and encode Header
520 header = create_jwt_hmac_header ();
521 GNUNET_STRINGS_base64url_encode (header, strlen (header), &header_base64);
522 fix_base64 (header_base64);
523
524 // Generate and encode the body of the JSON Web Signature
525 body_str = generate_id_token_body (aud_key,
526 sub_key,
527 attrs,
528 presentations,
529 expiration_time,
530 nonce);
531
532 if (! body_str)
533 {
534 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
535 "Body for the JWS could not be generated\n");
536 }
537
538 GNUNET_STRINGS_base64url_encode (body_str, strlen (body_str), &body_base64);
539 fix_base64 (body_base64);
540
541 /**
542 * Creating the JWT signature. This might not be
543 * standards compliant, check.
544 */
545 GNUNET_asprintf (&signature_target, "%s.%s", header_base64, body_base64);
546 GNUNET_CRYPTO_hmac_raw (secret_key,
547 strlen (secret_key),
548 signature_target,
549 strlen (signature_target),
550 &signature);
551 GNUNET_STRINGS_base64url_encode ((const char *) &signature,
552 sizeof(struct GNUNET_HashCode),
553 &signature_base64);
554 fix_base64 (signature_base64);
555
556 GNUNET_asprintf (&result,
557 "%s.%s.%s",
558 header_base64,
559 body_base64,
560 signature_base64);
561
562 GNUNET_free (header);
563 GNUNET_free (header_base64);
564 GNUNET_free (body_str);
565 GNUNET_free (body_base64);
566 GNUNET_free (signature_target);
567 GNUNET_free (signature_base64);
568 return result;
569}
570
571
572/**
573 * Builds an OIDC authorization code including
574 * a reclaim ticket and nonce
575 *
576 * @param issuer the issuer of the ticket, used to sign the ticket and nonce
577 * @param ticket the ticket to include in the code
578 * @param attrs list of attributes which are shared
579 * @param presentations credential presentation list (may be empty)
580 * @param nonce the nonce to include in the code
581 * @param code_challenge PKCE code challenge
582 * @return a new authorization code (caller must free)
583 */
584char *
585OIDC_build_authz_code (const struct GNUNET_IDENTITY_PrivateKey *issuer,
586 const struct GNUNET_RECLAIM_Ticket *ticket,
587 const struct GNUNET_RECLAIM_AttributeList *attrs,
588 const struct
589 GNUNET_RECLAIM_PresentationList *presentations,
590 const char *nonce_str,
591 const char *code_challenge)
592{
593 struct OIDC_Parameters params;
594 char *code_payload;
595 char *payload;
596 char *tmp;
597 char *code_str;
598 char *buf_ptr = NULL;
599 size_t payload_len;
600 size_t code_payload_len;
601 size_t attr_list_len = 0;
602 size_t pres_list_len = 0;
603 size_t code_challenge_len = 0;
604 uint32_t nonce_len = 0;
605 struct GNUNET_CRYPTO_EccSignaturePurpose *purpose;
606
607 /** PLAINTEXT **/
608 // Assign ticket
609 memset (&params, 0, sizeof(params));
610 params.ticket = *ticket;
611 // Assign nonce
612 payload_len = sizeof(struct OIDC_Parameters);
613 if ((NULL != nonce_str) && (strcmp ("", nonce_str) != 0))
614 {
615 nonce_len = strlen (nonce_str);
616 payload_len += nonce_len;
617 }
618 params.nonce_len = htonl (nonce_len);
619 // Assign code challenge
620 if (NULL != code_challenge)
621 code_challenge_len = strlen (code_challenge);
622 payload_len += code_challenge_len;
623 params.code_challenge_len = htonl (code_challenge_len);
624 // Assign attributes
625 if (NULL != attrs)
626 {
627 // Get length
628 attr_list_len = GNUNET_RECLAIM_attribute_list_serialize_get_size (attrs);
629 params.attr_list_len = htonl (attr_list_len);
630 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
631 "Length of serialized attributes: %lu\n",
632 attr_list_len);
633 // Get serialized attributes
634 payload_len += attr_list_len;
635 }
636 if (NULL != presentations)
637 {
638 // Get length
639 // FIXME only add presentations relevant for attribute list!!!
640 // This is important because of the distinction between id_token and
641 // userinfo in OIDC
642 pres_list_len =
643 GNUNET_RECLAIM_presentation_list_serialize_get_size (presentations);
644 params.pres_list_len = htonl (pres_list_len);
645 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
646 "Length of serialized presentations: %lu\n",
647 pres_list_len);
648 // Get serialized attributes
649 payload_len += pres_list_len;
650 }
651
652 // Get plaintext length
653 payload = GNUNET_malloc (payload_len);
654 memcpy (payload, &params, sizeof(params));
655 tmp = payload + sizeof(params);
656 if (0 < code_challenge_len)
657 {
658 memcpy (tmp, code_challenge, code_challenge_len);
659 tmp += code_challenge_len;
660 }
661 if (0 < nonce_len)
662 {
663 memcpy (tmp, nonce_str, nonce_len);
664 tmp += nonce_len;
665 }
666 if (0 < attr_list_len)
667 GNUNET_RECLAIM_attribute_list_serialize (attrs, tmp);
668 tmp += attr_list_len;
669 if (0 < pres_list_len)
670 GNUNET_RECLAIM_presentation_list_serialize (presentations, tmp);
671 tmp += pres_list_len;
672
673 /** END **/
674
675 // Get length
676 code_payload_len = sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose)
677 + payload_len + sizeof(struct
678 GNUNET_IDENTITY_Signature);
679 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
680 "Length of data to encode: %lu\n",
681 code_payload_len);
682
683 // Initialize code payload
684 code_payload = GNUNET_malloc (code_payload_len);
685 GNUNET_assert (NULL != code_payload);
686 purpose = (struct GNUNET_CRYPTO_EccSignaturePurpose *) code_payload;
687 purpose->size = htonl (sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose)
688 + payload_len);
689 purpose->purpose = htonl (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN);
690 // Store pubkey
691 buf_ptr = (char *) &purpose[1];
692 memcpy (buf_ptr, payload, payload_len);
693 GNUNET_free (payload);
694 buf_ptr += payload_len;
695 // Sign and store signature
696 if (GNUNET_SYSERR ==
697 GNUNET_IDENTITY_sign_ (issuer,
698 purpose,
699 (struct GNUNET_IDENTITY_Signature *)
700 buf_ptr))
701 {
702 GNUNET_break (0);
703 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unable to sign code\n");
704 GNUNET_free (code_payload);
705 return NULL;
706 }
707 GNUNET_STRINGS_base64url_encode (code_payload, code_payload_len, &code_str);
708 GNUNET_free (code_payload);
709 return code_str;
710}
711
712
713enum GNUNET_GenericReturnValue
714check_code_challenge (const char *code_challenge,
715 uint32_t code_challenge_len,
716 const char *code_verifier)
717{
718 char *code_verifier_hash;
719 char *expected_code_challenge;
720
721 if (0 == code_challenge_len) /* Only check if this code requires a CV */
722 return GNUNET_OK;
723 if (NULL == code_verifier)
724 {
725 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
726 "Expected code verifier!\n");
727 return GNUNET_SYSERR;
728 }
729 code_verifier_hash = GNUNET_malloc (256 / 8);
730 // hash code verifier
731 gcry_md_hash_buffer (GCRY_MD_SHA256,
732 code_verifier_hash,
733 code_verifier,
734 strlen (code_verifier));
735 // encode code verifier
736 GNUNET_STRINGS_base64url_encode (code_verifier_hash, 256 / 8,
737 &expected_code_challenge);
738 GNUNET_free (code_verifier_hash);
739 if (0 !=
740 strncmp (expected_code_challenge, code_challenge, code_challenge_len))
741 {
742 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
743 "Invalid code verifier! Expected: %s, Got: %.*s\n",
744 expected_code_challenge,
745 code_challenge_len,
746 code_challenge);
747 GNUNET_free (expected_code_challenge);
748 return GNUNET_SYSERR;
749 }
750 GNUNET_free (expected_code_challenge);
751 return GNUNET_OK;
752}
753
754
755/**
756 * Parse reclaim ticket and nonce from
757 * authorization code.
758 * This also verifies the signature in the code.
759 *
760 * @param audience the expected audience of the code
761 * @param code the string representation of the code
762 * @param code_verfier PKCE code verifier. Optional, must be provided
763 * if used in request.
764 * @param ticket where to store the ticket
765 * @param attrs the attributes in the code
766 * @param presentations credential presentation list
767 * @param nonce_str where to store the nonce (if contained)
768 * @return GNUNET_OK if successful, else GNUNET_SYSERR
769 */
770int
771OIDC_parse_authz_code (const struct GNUNET_IDENTITY_PublicKey *audience,
772 const char *code,
773 const char *code_verifier,
774 struct GNUNET_RECLAIM_Ticket *ticket,
775 struct GNUNET_RECLAIM_AttributeList **attrs,
776 struct GNUNET_RECLAIM_PresentationList **presentations,
777 char **nonce_str,
778 enum OIDC_VerificationOptions opts)
779{
780 char *code_payload;
781 char *ptr;
782 char *plaintext;
783 char *attrs_ser;
784 char *presentations_ser;
785 char *code_challenge;
786 struct GNUNET_CRYPTO_EccSignaturePurpose *purpose;
787 struct GNUNET_IDENTITY_Signature *signature;
788 uint32_t code_challenge_len;
789 uint32_t attrs_ser_len;
790 uint32_t pres_ser_len;
791 size_t plaintext_len;
792 size_t code_payload_len;
793 uint32_t nonce_len = 0;
794 struct OIDC_Parameters *params;
795
796 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Trying to decode `%s'\n", code);
797 code_payload = NULL;
798 code_payload_len =
799 GNUNET_STRINGS_base64url_decode (code, strlen (code),
800 (void **) &code_payload);
801 if (code_payload_len < sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose)
802 + sizeof(struct OIDC_Parameters)
803 + sizeof(struct GNUNET_IDENTITY_Signature))
804 {
805 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Authorization code malformed\n");
806 GNUNET_free (code_payload);
807 return GNUNET_SYSERR;
808 }
809
810 purpose = (struct GNUNET_CRYPTO_EccSignaturePurpose *) code_payload;
811 plaintext_len = code_payload_len;
812 plaintext_len -= sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose);
813 ptr = (char *) &purpose[1];
814 plaintext_len -= sizeof(struct GNUNET_IDENTITY_Signature);
815 plaintext = ptr;
816 ptr += plaintext_len;
817 signature = (struct GNUNET_IDENTITY_Signature *) ptr;
818 params = (struct OIDC_Parameters *) plaintext;
819
820 // cmp code_challenge code_verifier
821 code_challenge_len = ntohl (params->code_challenge_len);
822 code_challenge = ((char *) &params[1]);
823 if (! (opts & OIDC_VERIFICATION_NO_CODE_VERIFIER))
824 {
825 if (GNUNET_OK != check_code_challenge (code_challenge,
826 code_challenge_len,
827 code_verifier))
828 {
829 GNUNET_free (code_payload);
830 return GNUNET_SYSERR;
831 }
832 }
833 nonce_len = ntohl (params->nonce_len);
834 if (0 != nonce_len)
835 {
836 *nonce_str = GNUNET_strndup (code_challenge + code_challenge_len,
837 nonce_len);
838 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got nonce: %s\n", *nonce_str);
839 }
840
841 // Ticket
842 memcpy (ticket, &params->ticket, sizeof(params->ticket));
843 // Signature
844 // GNUNET_CRYPTO_ecdsa_key_get_public (ecdsa_priv, &ecdsa_pub);
845 if (0 != GNUNET_memcmp (audience, &ticket->audience))
846 {
847 GNUNET_free (code_payload);
848 if (NULL != *nonce_str)
849 GNUNET_free (*nonce_str);
850 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
851 "Audience in ticket does not match client!\n");
852 return GNUNET_SYSERR;
853 }
854 if (GNUNET_OK !=
855 GNUNET_IDENTITY_signature_verify_ (
856 GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN,
857 purpose,
858 signature,
859 &(ticket->identity)))
860 {
861 GNUNET_free (code_payload);
862 if (NULL != *nonce_str)
863 GNUNET_free (*nonce_str);
864 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Signature of AuthZ code invalid!\n");
865 return GNUNET_SYSERR;
866 }
867 // Attributes
868 attrs_ser = ((char *) &params[1]) + code_challenge_len + nonce_len;
869 attrs_ser_len = ntohl (params->attr_list_len);
870 *attrs = GNUNET_RECLAIM_attribute_list_deserialize (attrs_ser, attrs_ser_len);
871 presentations_ser = ((char*) attrs_ser) + attrs_ser_len;
872 pres_ser_len = ntohl (params->pres_list_len);
873 *presentations =
874 GNUNET_RECLAIM_presentation_list_deserialize (presentations_ser,
875 pres_ser_len);
876
877 GNUNET_free (code_payload);
878 return GNUNET_OK;
879}
880
881
882/**
883 * Build a token response for a token request
884 * TODO: Maybe we should add the scope here?
885 *
886 * @param access_token the access token to include
887 * @param id_token the id_token to include
888 * @param expiration_time the expiration time of the token(s)
889 * @param token_response where to store the response
890 */
891void
892OIDC_build_token_response (const char *access_token,
893 const char *id_token,
894 const struct GNUNET_TIME_Relative *expiration_time,
895 char **token_response)
896{
897 json_t *root_json;
898
899 root_json = json_object ();
900
901 GNUNET_assert (NULL != access_token);
902 GNUNET_assert (NULL != id_token);
903 GNUNET_assert (NULL != expiration_time);
904 json_object_set_new (root_json, "access_token", json_string (access_token));
905 json_object_set_new (root_json, "token_type", json_string ("Bearer"));
906 json_object_set_new (root_json,
907 "expires_in",
908 json_integer (expiration_time->rel_value_us
909 / (1000 * 1000)));
910 json_object_set_new (root_json, "id_token", json_string (id_token));
911 *token_response = json_dumps (root_json, JSON_INDENT (0) | JSON_COMPACT);
912 json_decref (root_json);
913}
914
915
916/**
917 * Generate a new access token
918 */
919char *
920OIDC_access_token_new (const struct GNUNET_RECLAIM_Ticket *ticket)
921{
922 char *access_token;
923
924 GNUNET_STRINGS_base64_encode (ticket,
925 sizeof(*ticket),
926 &access_token);
927 return access_token;
928}
929
930
931/**
932 * Parse an access token
933 */
934int
935OIDC_access_token_parse (const char *token,
936 struct GNUNET_RECLAIM_Ticket **ticket)
937{
938 size_t sret;
939 char *decoded;
940 sret = GNUNET_STRINGS_base64_decode (token,
941 strlen (token),
942 (void**) &decoded);
943 if (sizeof (struct GNUNET_RECLAIM_Ticket) != sret)
944 {
945 GNUNET_free (decoded);
946 return GNUNET_SYSERR;
947 }
948 *ticket = (struct GNUNET_RECLAIM_Ticket *) decoded;
949 return GNUNET_OK;
950}
951
952
953/**
954 * Checks if a claim is implicitly requested through standard
955 * scope(s) or explicitly through non-standard scope.
956 *
957 * @param scopes the scopes which have been requested
958 * @param attr the attribute name to check
959 * @return GNUNET_YES if attribute is implcitly requested
960 */
961enum GNUNET_GenericReturnValue
962OIDC_check_scopes_for_claim_request (const char*scopes,
963 const char*attr)
964{
965 char *scope_variables;
966 char *scope_variable;
967 char delimiter[] = " ";
968 int i;
969
970 scope_variables = GNUNET_strdup (scopes);
971 scope_variable = strtok (scope_variables, delimiter);
972 while (NULL != scope_variable)
973 {
974 if (0 == strcmp ("profile", scope_variable))
975 {
976 for (i = 0; i < 14; i++)
977 {
978 if (0 == strcmp (attr, OIDC_profile_claims[i]))
979 {
980 GNUNET_free (scope_variables);
981 return GNUNET_YES;
982 }
983 }
984 }
985 else if (0 == strcmp ("address", scope_variable))
986 {
987 for (i = 0; i < 5; i++)
988 {
989 if (0 == strcmp (attr, OIDC_address_claims[i]))
990 {
991 GNUNET_free (scope_variables);
992 return GNUNET_YES;
993 }
994 }
995 }
996 else if (0 == strcmp ("email", scope_variable))
997 {
998 for (i = 0; i < 2; i++)
999 {
1000 if (0 == strcmp (attr, OIDC_email_claims[i]))
1001 {
1002 GNUNET_free (scope_variables);
1003 return GNUNET_YES;
1004 }
1005 }
1006 }
1007 else if (0 == strcmp ("phone", scope_variable))
1008 {
1009 for (i = 0; i < 2; i++)
1010 {
1011 if (0 == strcmp (attr, OIDC_phone_claims[i]))
1012 {
1013 GNUNET_free (scope_variables);
1014 return GNUNET_YES;
1015 }
1016 }
1017
1018 }
1019 else if (0 == strcmp (attr, scope_variable))
1020 {
1021 /** attribute matches requested scope **/
1022 GNUNET_free (scope_variables);
1023 return GNUNET_YES;
1024 }
1025 scope_variable = strtok (NULL, delimiter);
1026 }
1027 GNUNET_free (scope_variables);
1028 return GNUNET_NO;
1029
1030}