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.c1026
1 files changed, 0 insertions, 1026 deletions
diff --git a/src/reclaim/oidc_helper.c b/src/reclaim/oidc_helper.c
deleted file mode 100644
index a2da7312b..000000000
--- a/src/reclaim/oidc_helper.c
+++ /dev/null
@@ -1,1026 +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_CRYPTO_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_CRYPTO_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_CRYPTO_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_CRYPTO_PublicKey *aud_key,
363 const struct GNUNET_CRYPTO_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_TIME_Absolute exp_time;
371 struct GNUNET_TIME_Absolute time_now;
372 json_t *body;
373 char *audience;
374 char *subject;
375 char *body_str;
376
377 body = generate_userinfo_json (sub_key,
378 attrs,
379 presentations);
380 // iat REQUIRED time now
381 time_now = GNUNET_TIME_absolute_get ();
382 // exp REQUIRED time expired from config
383 exp_time = GNUNET_TIME_absolute_add (time_now, *expiration_time);
384 // auth_time only if max_age
385 // nonce only if nonce
386 // OPTIONAL acr,amr,azp
387 subject =
388 GNUNET_STRINGS_data_to_string_alloc (sub_key,
389 sizeof(struct
390 GNUNET_CRYPTO_PublicKey));
391 audience =
392 GNUNET_STRINGS_data_to_string_alloc (aud_key,
393 sizeof(struct
394 GNUNET_CRYPTO_PublicKey));
395
396 // aud REQUIRED public key client_id must be there
397 json_object_set_new (body, "aud", json_string (audience));
398 // iat
399 json_object_set_new (body,
400 "iat",
401 json_integer (time_now.abs_value_us / (1000 * 1000)));
402 // exp
403 json_object_set_new (body,
404 "exp",
405 json_integer (exp_time.abs_value_us / (1000 * 1000)));
406 // nbf
407 json_object_set_new (body,
408 "nbf",
409 json_integer (time_now.abs_value_us / (1000 * 1000)));
410 // nonce
411 if (NULL != nonce)
412 json_object_set_new (body, "nonce", json_string (nonce));
413
414 // Error checking
415 body_str = json_dumps (body, JSON_INDENT (2) | JSON_COMPACT);
416 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"ID-Token: %s\n", body_str);
417
418 json_decref (body);
419 GNUNET_free (subject);
420 GNUNET_free (audience);
421
422 return body_str;
423}
424
425
426char *
427OIDC_generate_id_token_rsa (const struct GNUNET_CRYPTO_PublicKey *aud_key,
428 const struct GNUNET_CRYPTO_PublicKey *sub_key,
429 const struct GNUNET_RECLAIM_AttributeList *attrs,
430 const struct
431 GNUNET_RECLAIM_PresentationList *presentations,
432 const struct GNUNET_TIME_Relative *expiration_time,
433 const char *nonce,
434 const json_t *secret_rsa_key)
435{
436 json_t *jws;
437 char *body_str;
438 char *result;
439
440 // Generate the body of the JSON Web Signature
441 body_str = generate_id_token_body (aud_key,
442 sub_key,
443 attrs,
444 presentations,
445 expiration_time,
446 nonce);
447
448 if (NULL == body_str)
449 {
450 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
451 "Body for the JWS could not be generated\n");
452 return NULL;
453 }
454
455 // Creating the JSON Web Signature.
456 jws = json_pack ("{s:o}", "payload",
457 jose_b64_enc (body_str, strlen (body_str)));
458 GNUNET_free (body_str);
459
460 if (! jose_jws_sig (NULL, jws, NULL, secret_rsa_key))
461 {
462 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
463 "Signature generation failed\n");
464 return NULL;
465 }
466
467 // Encoding JSON as compact JSON Web Signature
468 GNUNET_asprintf (&result, "%s.%s.%s",
469 json_string_value (json_object_get (jws, "protected")),
470 json_string_value (json_object_get (jws, "payload")),
471 json_string_value (json_object_get (jws, "signature")) );
472
473 json_decref (jws);
474 return result;
475}
476
477/**
478 * Create a JWT using HMAC (HS256) from attributes
479 *
480 * @param aud_key the public of the audience
481 * @param sub_key the public key of the subject
482 * @param attrs the attribute list
483 * @param presentations credential presentation list (may be empty)
484 * @param expiration_time the validity of the token
485 * @param secret_key the key used to sign the JWT
486 * @return a new base64-encoded JWT string.
487 */
488char *
489OIDC_generate_id_token_hmac (const struct GNUNET_CRYPTO_PublicKey *aud_key,
490 const struct GNUNET_CRYPTO_PublicKey *sub_key,
491 const struct GNUNET_RECLAIM_AttributeList *attrs,
492 const struct
493 GNUNET_RECLAIM_PresentationList *presentations,
494 const struct GNUNET_TIME_Relative *expiration_time,
495 const char *nonce,
496 const char *secret_key)
497{
498 struct GNUNET_HashCode signature;
499 char *header;
500 char *header_base64;
501 char *body_str;
502 char *body_base64;
503 char *signature_target;
504 char *signature_base64;
505 char *result;
506
507 // Generate and encode Header
508 header = create_jwt_hmac_header ();
509 if (NULL == header)
510 {
511 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
512 "Header for the JWS could not be generated\n");
513 return NULL;
514 }
515 GNUNET_STRINGS_base64url_encode (header, strlen (header), &header_base64);
516 GNUNET_free (header);
517 fix_base64 (header_base64);
518
519 // Generate and encode the body of the JSON Web Signature
520 body_str = generate_id_token_body (aud_key,
521 sub_key,
522 attrs,
523 presentations,
524 expiration_time,
525 nonce);
526
527 if (NULL == body_str)
528 {
529 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
530 "Body for the JWS could not be generated\n");
531 GNUNET_free (header_base64);
532 return NULL;
533 }
534
535 GNUNET_STRINGS_base64url_encode (body_str, strlen (body_str), &body_base64);
536 fix_base64 (body_base64);
537
538 /**
539 * Creating the JWT signature. This might not be
540 * standards compliant, check.
541 */
542 GNUNET_asprintf (&signature_target, "%s.%s", header_base64, body_base64);
543 GNUNET_CRYPTO_hmac_raw (secret_key,
544 strlen (secret_key),
545 signature_target,
546 strlen (signature_target),
547 &signature);
548 GNUNET_STRINGS_base64url_encode ((const char *) &signature,
549 sizeof(struct GNUNET_HashCode),
550 &signature_base64);
551 fix_base64 (signature_base64);
552
553 GNUNET_asprintf (&result,
554 "%s.%s.%s",
555 header_base64,
556 body_base64,
557 signature_base64);
558
559 GNUNET_free (header_base64);
560 GNUNET_free (body_str);
561 GNUNET_free (body_base64);
562 GNUNET_free (signature_target);
563 GNUNET_free (signature_base64);
564 return result;
565}
566
567
568/**
569 * Builds an OIDC authorization code including
570 * a reclaim ticket and nonce
571 *
572 * @param issuer the issuer of the ticket, used to sign the ticket and nonce
573 * @param ticket the ticket to include in the code
574 * @param attrs list of attributes which are shared
575 * @param presentations credential presentation list (may be empty)
576 * @param nonce the nonce to include in the code
577 * @param code_challenge PKCE code challenge
578 * @return a new authorization code (caller must free)
579 */
580char *
581OIDC_build_authz_code (const struct GNUNET_CRYPTO_PrivateKey *issuer,
582 const struct GNUNET_RECLAIM_Ticket *ticket,
583 const struct GNUNET_RECLAIM_AttributeList *attrs,
584 const struct
585 GNUNET_RECLAIM_PresentationList *presentations,
586 const char *nonce_str,
587 const char *code_challenge)
588{
589 struct OIDC_Parameters params;
590 char *code_payload;
591 char *payload;
592 char *tmp;
593 char *code_str;
594 char *buf_ptr = NULL;
595 size_t payload_len;
596 size_t code_payload_len;
597 size_t attr_list_len = 0;
598 size_t pres_list_len = 0;
599 size_t code_challenge_len = 0;
600 uint32_t nonce_len = 0;
601 struct GNUNET_CRYPTO_EccSignaturePurpose *purpose;
602
603 /** PLAINTEXT **/
604 // Assign ticket
605 memset (&params, 0, sizeof(params));
606 params.ticket = *ticket;
607 // Assign nonce
608 payload_len = sizeof(struct OIDC_Parameters);
609 if ((NULL != nonce_str) && (strcmp ("", nonce_str) != 0))
610 {
611 nonce_len = strlen (nonce_str);
612 payload_len += nonce_len;
613 }
614 params.nonce_len = htonl (nonce_len);
615 // Assign code challenge
616 if (NULL != code_challenge)
617 code_challenge_len = strlen (code_challenge);
618 payload_len += code_challenge_len;
619 params.code_challenge_len = htonl (code_challenge_len);
620 // Assign attributes
621 if (NULL != attrs)
622 {
623 // Get length
624 attr_list_len = GNUNET_RECLAIM_attribute_list_serialize_get_size (attrs);
625 params.attr_list_len = htonl (attr_list_len);
626 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
627 "Length of serialized attributes: %lu\n",
628 attr_list_len);
629 // Get serialized attributes
630 payload_len += attr_list_len;
631 }
632 if (NULL != presentations)
633 {
634 // Get length
635 // FIXME only add presentations relevant for attribute list!!!
636 // This is important because of the distinction between id_token and
637 // userinfo in OIDC
638 pres_list_len =
639 GNUNET_RECLAIM_presentation_list_serialize_get_size (presentations);
640 params.pres_list_len = htonl (pres_list_len);
641 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
642 "Length of serialized presentations: %lu\n",
643 pres_list_len);
644 // Get serialized attributes
645 payload_len += pres_list_len;
646 }
647
648 // Get plaintext length
649 payload = GNUNET_malloc (payload_len);
650 memcpy (payload, &params, sizeof(params));
651 tmp = payload + sizeof(params);
652 if (0 < code_challenge_len)
653 {
654 memcpy (tmp, code_challenge, code_challenge_len);
655 tmp += code_challenge_len;
656 }
657 if (0 < nonce_len)
658 {
659 memcpy (tmp, nonce_str, nonce_len);
660 tmp += nonce_len;
661 }
662 if (0 < attr_list_len)
663 GNUNET_RECLAIM_attribute_list_serialize (attrs, tmp);
664 tmp += attr_list_len;
665 if (0 < pres_list_len)
666 GNUNET_RECLAIM_presentation_list_serialize (presentations, tmp);
667 tmp += pres_list_len;
668
669 /** END **/
670
671 // Get length
672 code_payload_len = sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose)
673 + payload_len + sizeof(struct
674 GNUNET_CRYPTO_Signature);
675 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
676 "Length of data to encode: %lu\n",
677 code_payload_len);
678
679 // Initialize code payload
680 code_payload = GNUNET_malloc (code_payload_len);
681 GNUNET_assert (NULL != code_payload);
682 purpose = (struct GNUNET_CRYPTO_EccSignaturePurpose *) code_payload;
683 purpose->size = htonl (sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose)
684 + payload_len);
685 purpose->purpose = htonl (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN);
686 // Store pubkey
687 buf_ptr = (char *) &purpose[1];
688 memcpy (buf_ptr, payload, payload_len);
689 GNUNET_free (payload);
690 buf_ptr += payload_len;
691 // Sign and store signature
692 if (GNUNET_SYSERR ==
693 GNUNET_CRYPTO_sign_ (issuer,
694 purpose,
695 (struct GNUNET_CRYPTO_Signature *)
696 buf_ptr))
697 {
698 GNUNET_break (0);
699 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unable to sign code\n");
700 GNUNET_free (code_payload);
701 return NULL;
702 }
703 GNUNET_STRINGS_base64url_encode (code_payload, code_payload_len, &code_str);
704 GNUNET_free (code_payload);
705 return code_str;
706}
707
708
709enum GNUNET_GenericReturnValue
710check_code_challenge (const char *code_challenge,
711 uint32_t code_challenge_len,
712 const char *code_verifier)
713{
714 char *code_verifier_hash;
715 char *expected_code_challenge;
716
717 if (0 == code_challenge_len) /* Only check if this code requires a CV */
718 return GNUNET_OK;
719 if (NULL == code_verifier)
720 {
721 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
722 "Expected code verifier!\n");
723 return GNUNET_SYSERR;
724 }
725 code_verifier_hash = GNUNET_malloc (256 / 8);
726 // hash code verifier
727 gcry_md_hash_buffer (GCRY_MD_SHA256,
728 code_verifier_hash,
729 code_verifier,
730 strlen (code_verifier));
731 // encode code verifier
732 GNUNET_STRINGS_base64url_encode (code_verifier_hash, 256 / 8,
733 &expected_code_challenge);
734 GNUNET_free (code_verifier_hash);
735 if (0 !=
736 strncmp (expected_code_challenge, code_challenge, code_challenge_len))
737 {
738 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
739 "Invalid code verifier! Expected: %s, Got: %.*s\n",
740 expected_code_challenge,
741 code_challenge_len,
742 code_challenge);
743 GNUNET_free (expected_code_challenge);
744 return GNUNET_SYSERR;
745 }
746 GNUNET_free (expected_code_challenge);
747 return GNUNET_OK;
748}
749
750
751/**
752 * Parse reclaim ticket and nonce from
753 * authorization code.
754 * This also verifies the signature in the code.
755 *
756 * @param audience the expected audience of the code
757 * @param code the string representation of the code
758 * @param code_verfier PKCE code verifier. Optional, must be provided
759 * if used in request.
760 * @param ticket where to store the ticket
761 * @param attrs the attributes in the code
762 * @param presentations credential presentation list
763 * @param nonce_str where to store the nonce (if contained)
764 * @return GNUNET_OK if successful, else GNUNET_SYSERR
765 */
766int
767OIDC_parse_authz_code (const struct GNUNET_CRYPTO_PublicKey *audience,
768 const char *code,
769 const char *code_verifier,
770 struct GNUNET_RECLAIM_Ticket *ticket,
771 struct GNUNET_RECLAIM_AttributeList **attrs,
772 struct GNUNET_RECLAIM_PresentationList **presentations,
773 char **nonce_str,
774 enum OIDC_VerificationOptions opts)
775{
776 char *code_payload;
777 char *ptr;
778 char *plaintext;
779 char *attrs_ser;
780 char *presentations_ser;
781 char *code_challenge;
782 struct GNUNET_CRYPTO_EccSignaturePurpose *purpose;
783 struct GNUNET_CRYPTO_Signature *signature;
784 uint32_t code_challenge_len;
785 uint32_t attrs_ser_len;
786 uint32_t pres_ser_len;
787 size_t plaintext_len;
788 size_t code_payload_len;
789 uint32_t nonce_len = 0;
790 struct OIDC_Parameters *params;
791
792 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Trying to decode `%s'\n", code);
793 code_payload = NULL;
794 code_payload_len =
795 GNUNET_STRINGS_base64url_decode (code, strlen (code),
796 (void **) &code_payload);
797 if (code_payload_len < sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose)
798 + sizeof(struct OIDC_Parameters)
799 + sizeof(struct GNUNET_CRYPTO_Signature))
800 {
801 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Authorization code malformed\n");
802 GNUNET_free (code_payload);
803 return GNUNET_SYSERR;
804 }
805
806 purpose = (struct GNUNET_CRYPTO_EccSignaturePurpose *) code_payload;
807 plaintext_len = code_payload_len;
808 plaintext_len -= sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose);
809 ptr = (char *) &purpose[1];
810 plaintext_len -= sizeof(struct GNUNET_CRYPTO_Signature);
811 plaintext = ptr;
812 ptr += plaintext_len;
813 signature = (struct GNUNET_CRYPTO_Signature *) ptr;
814 params = (struct OIDC_Parameters *) plaintext;
815
816 // cmp code_challenge code_verifier
817 code_challenge_len = ntohl (params->code_challenge_len);
818 code_challenge = ((char *) &params[1]);
819 if (! (opts & OIDC_VERIFICATION_NO_CODE_VERIFIER))
820 {
821 if (GNUNET_OK != check_code_challenge (code_challenge,
822 code_challenge_len,
823 code_verifier))
824 {
825 GNUNET_free (code_payload);
826 return GNUNET_SYSERR;
827 }
828 }
829 nonce_len = ntohl (params->nonce_len);
830 if (0 != nonce_len)
831 {
832 *nonce_str = GNUNET_strndup (code_challenge + code_challenge_len,
833 nonce_len);
834 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got nonce: %s\n", *nonce_str);
835 }
836
837 // Ticket
838 memcpy (ticket, &params->ticket, sizeof(params->ticket));
839 // Signature
840 // GNUNET_CRYPTO_ecdsa_key_get_public (ecdsa_priv, &ecdsa_pub);
841 if (0 != GNUNET_memcmp (audience, &ticket->audience))
842 {
843 GNUNET_free (code_payload);
844 if (NULL != *nonce_str)
845 GNUNET_free (*nonce_str);
846 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
847 "Audience in ticket does not match client!\n");
848 return GNUNET_SYSERR;
849 }
850 if (GNUNET_OK !=
851 GNUNET_CRYPTO_signature_verify_ (
852 GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN,
853 purpose,
854 signature,
855 &(ticket->identity)))
856 {
857 GNUNET_free (code_payload);
858 if (NULL != *nonce_str)
859 GNUNET_free (*nonce_str);
860 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Signature of AuthZ code invalid!\n");
861 return GNUNET_SYSERR;
862 }
863 // Attributes
864 attrs_ser = ((char *) &params[1]) + code_challenge_len + nonce_len;
865 attrs_ser_len = ntohl (params->attr_list_len);
866 *attrs = GNUNET_RECLAIM_attribute_list_deserialize (attrs_ser, attrs_ser_len);
867 presentations_ser = ((char*) attrs_ser) + attrs_ser_len;
868 pres_ser_len = ntohl (params->pres_list_len);
869 *presentations =
870 GNUNET_RECLAIM_presentation_list_deserialize (presentations_ser,
871 pres_ser_len);
872
873 GNUNET_free (code_payload);
874 return GNUNET_OK;
875}
876
877
878/**
879 * Build a token response for a token request
880 * TODO: Maybe we should add the scope here?
881 *
882 * @param access_token the access token to include
883 * @param id_token the id_token to include
884 * @param expiration_time the expiration time of the token(s)
885 * @param token_response where to store the response
886 */
887void
888OIDC_build_token_response (const char *access_token,
889 const char *id_token,
890 const struct GNUNET_TIME_Relative *expiration_time,
891 char **token_response)
892{
893 json_t *root_json;
894
895 root_json = json_object ();
896
897 GNUNET_assert (NULL != access_token);
898 GNUNET_assert (NULL != id_token);
899 GNUNET_assert (NULL != expiration_time);
900 json_object_set_new (root_json, "access_token", json_string (access_token));
901 json_object_set_new (root_json, "token_type", json_string ("Bearer"));
902 json_object_set_new (root_json,
903 "expires_in",
904 json_integer (expiration_time->rel_value_us
905 / (1000 * 1000)));
906 json_object_set_new (root_json, "id_token", json_string (id_token));
907 *token_response = json_dumps (root_json, JSON_INDENT (0) | JSON_COMPACT);
908 json_decref (root_json);
909}
910
911
912/**
913 * Generate a new access token
914 */
915char *
916OIDC_access_token_new (const struct GNUNET_RECLAIM_Ticket *ticket)
917{
918 char *access_token;
919
920 GNUNET_STRINGS_base64_encode (ticket,
921 sizeof(*ticket),
922 &access_token);
923 return access_token;
924}
925
926
927/**
928 * Parse an access token
929 */
930int
931OIDC_access_token_parse (const char *token,
932 struct GNUNET_RECLAIM_Ticket **ticket)
933{
934 size_t sret;
935 char *decoded;
936 sret = GNUNET_STRINGS_base64_decode (token,
937 strlen (token),
938 (void**) &decoded);
939 if (sizeof (struct GNUNET_RECLAIM_Ticket) != sret)
940 {
941 GNUNET_free (decoded);
942 return GNUNET_SYSERR;
943 }
944 *ticket = (struct GNUNET_RECLAIM_Ticket *) decoded;
945 return GNUNET_OK;
946}
947
948
949/**
950 * Checks if a claim is implicitly requested through standard
951 * scope(s) or explicitly through non-standard scope.
952 *
953 * @param scopes the scopes which have been requested
954 * @param attr the attribute name to check
955 * @return GNUNET_YES if attribute is implcitly requested
956 */
957enum GNUNET_GenericReturnValue
958OIDC_check_scopes_for_claim_request (const char*scopes,
959 const char*attr)
960{
961 char *scope_variables;
962 char *scope_variable;
963 char delimiter[] = " ";
964 int i;
965
966 scope_variables = GNUNET_strdup (scopes);
967 scope_variable = strtok (scope_variables, delimiter);
968 while (NULL != scope_variable)
969 {
970 if (0 == strcmp ("profile", scope_variable))
971 {
972 for (i = 0; i < 14; i++)
973 {
974 if (0 == strcmp (attr, OIDC_profile_claims[i]))
975 {
976 GNUNET_free (scope_variables);
977 return GNUNET_YES;
978 }
979 }
980 }
981 else if (0 == strcmp ("address", scope_variable))
982 {
983 for (i = 0; i < 5; i++)
984 {
985 if (0 == strcmp (attr, OIDC_address_claims[i]))
986 {
987 GNUNET_free (scope_variables);
988 return GNUNET_YES;
989 }
990 }
991 }
992 else if (0 == strcmp ("email", scope_variable))
993 {
994 for (i = 0; i < 2; i++)
995 {
996 if (0 == strcmp (attr, OIDC_email_claims[i]))
997 {
998 GNUNET_free (scope_variables);
999 return GNUNET_YES;
1000 }
1001 }
1002 }
1003 else if (0 == strcmp ("phone", scope_variable))
1004 {
1005 for (i = 0; i < 2; i++)
1006 {
1007 if (0 == strcmp (attr, OIDC_phone_claims[i]))
1008 {
1009 GNUNET_free (scope_variables);
1010 return GNUNET_YES;
1011 }
1012 }
1013
1014 }
1015 else if (0 == strcmp (attr, scope_variable))
1016 {
1017 /** attribute matches requested scope **/
1018 GNUNET_free (scope_variables);
1019 return GNUNET_YES;
1020 }
1021 scope_variable = strtok (NULL, delimiter);
1022 }
1023 GNUNET_free (scope_variables);
1024 return GNUNET_NO;
1025
1026}