aboutsummaryrefslogtreecommitdiff
path: root/src/service/rest/openid_plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/service/rest/openid_plugin.c')
-rw-r--r--src/service/rest/openid_plugin.c3169
1 files changed, 3169 insertions, 0 deletions
diff --git a/src/service/rest/openid_plugin.c b/src/service/rest/openid_plugin.c
new file mode 100644
index 000000000..a4f082d2a
--- /dev/null
+++ b/src/service/rest/openid_plugin.c
@@ -0,0 +1,3169 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2012-2018 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 * @author Martin Schanzenbach
22 * @author Philippe Buschmann
23 * @author Tristan Schwieren
24 * @file identity/plugin_rest_openid_connect.c
25 * @brief GNUnet Namestore REST plugin
26 *
27 */
28#include "platform.h"
29#include <inttypes.h>
30#include <jansson.h>
31#include <jose/jose.h>
32
33#include "gnunet_util_lib.h"
34#include "gnunet_gns_service.h"
35#include "gnunet_gnsrecord_lib.h"
36#include "gnunet_identity_service.h"
37#include "gnunet_namestore_service.h"
38#include "gnunet_reclaim_lib.h"
39#include "gnunet_reclaim_service.h"
40#include "gnunet_rest_lib.h"
41#include "gnunet_rest_plugin.h"
42#include "gnunet_signatures.h"
43#include "microhttpd.h"
44#include "oidc_helper.h"
45
46/**
47 * REST root namespace
48 */
49#define GNUNET_REST_API_NS_OIDC "/openid"
50
51/**
52 * OIDC config
53 */
54#define GNUNET_REST_API_NS_OIDC_CONFIG "/.well-known/openid-configuration"
55
56/**
57 * Authorize endpoint
58 */
59#define GNUNET_REST_API_NS_AUTHORIZE "/openid/authorize"
60
61/**
62 * Token endpoint
63 */
64#define GNUNET_REST_API_NS_TOKEN "/openid/token"
65
66/**
67 * JSON Web Keys endpoint
68 */
69#define GNUNET_REST_API_JWKS "/jwks.json"
70
71/**
72 * UserInfo endpoint
73 */
74#define GNUNET_REST_API_NS_USERINFO "/openid/userinfo"
75
76/**
77 * Login namespace
78 */
79#define GNUNET_REST_API_NS_LOGIN "/openid/login"
80
81/**
82 * State while collecting all egos
83 */
84#define ID_REST_STATE_INIT 0
85
86/**
87 * Done collecting egos
88 */
89#define ID_REST_STATE_POST_INIT 1
90
91/**
92 * OIDC grant_type key
93 */
94#define OIDC_GRANT_TYPE_KEY "grant_type"
95
96/**
97 * OIDC grant_type key
98 */
99#define OIDC_GRANT_TYPE_VALUE "authorization_code"
100
101/**
102 * OIDC code key
103 */
104#define OIDC_CODE_KEY "code"
105
106/**
107 * OIDC response_type key
108 */
109#define OIDC_RESPONSE_TYPE_KEY "response_type"
110
111/**
112 * OIDC client_id key
113 */
114#define OIDC_CLIENT_ID_KEY "client_id"
115
116/**
117 * OIDC scope key
118 */
119#define OIDC_SCOPE_KEY "scope"
120
121/**
122 * OIDC redirect_uri key
123 */
124#define OIDC_REDIRECT_URI_KEY "redirect_uri"
125
126/**
127 * OIDC state key
128 */
129#define OIDC_STATE_KEY "state"
130
131/**
132 * OIDC nonce key
133 */
134#define OIDC_NONCE_KEY "nonce"
135
136/**
137 * OIDC claims key
138 */
139#define OIDC_CLAIMS_KEY "claims"
140
141/**
142 * OIDC PKCE code challenge
143 */
144#define OIDC_CODE_CHALLENGE_KEY "code_challenge"
145
146/**
147 * OIDC PKCE code verifier
148 */
149#define OIDC_CODE_VERIFIER_KEY "code_verifier"
150
151/**
152 * OIDC cookie expiration (in seconds)
153 */
154#define OIDC_COOKIE_EXPIRATION 3
155
156/**
157 * OIDC cookie header key
158 */
159#define OIDC_COOKIE_HEADER_KEY "cookie"
160
161/**
162 * OIDC cookie header information key
163 */
164#define OIDC_AUTHORIZATION_HEADER_KEY "authorization"
165
166/**
167 * OIDC cookie header information key
168 */
169#define OIDC_COOKIE_HEADER_INFORMATION_KEY "Identity="
170
171/**
172 * OIDC cookie header if user cancelled
173 */
174#define OIDC_COOKIE_HEADER_ACCESS_DENIED "Identity=Denied"
175
176/**
177 * OIDC expected response_type while authorizing
178 */
179#define OIDC_EXPECTED_AUTHORIZATION_RESPONSE_TYPE "code"
180
181/**
182 * OIDC expected scope part while authorizing
183 */
184#define OIDC_EXPECTED_AUTHORIZATION_SCOPE "openid"
185
186/**
187 * OIDC error key for invalid client
188 */
189#define OIDC_ERROR_KEY_INVALID_CLIENT "invalid_client"
190
191/**
192 * OIDC error key for invalid scopes
193 */
194#define OIDC_ERROR_KEY_INVALID_SCOPE "invalid_scope"
195
196/**
197 * OIDC error key for invalid requests
198 */
199#define OIDC_ERROR_KEY_INVALID_REQUEST "invalid_request"
200
201/**
202 * OIDC error key for invalid tokens
203 */
204#define OIDC_ERROR_KEY_INVALID_TOKEN "invalid_token"
205
206/**
207 * OIDC error key for invalid cookies
208 */
209#define OIDC_ERROR_KEY_INVALID_COOKIE "invalid_cookie"
210
211/**
212 * OIDC error key for generic server errors
213 */
214#define OIDC_ERROR_KEY_SERVER_ERROR "server_error"
215
216/**
217 * OIDC error key for unsupported grants
218 */
219#define OIDC_ERROR_KEY_UNSUPPORTED_GRANT_TYPE "unsupported_grant_type"
220
221/**
222 * OIDC error key for unsupported response types
223 */
224#define OIDC_ERROR_KEY_UNSUPPORTED_RESPONSE_TYPE "unsupported_response_type"
225
226/**
227 * OIDC error key for unauthorized clients
228 */
229#define OIDC_ERROR_KEY_UNAUTHORIZED_CLIENT "unauthorized_client"
230
231/**
232 * OIDC error key for denied access
233 */
234#define OIDC_ERROR_KEY_ACCESS_DENIED "access_denied"
235
236/**
237 * OIDC key store file name
238 */
239#define OIDC_JWK_RSA_FILENAME "jwk_rsa.json"
240
241/**
242 * How long to wait for a consume in userinfo endpoint
243 */
244#define CONSUME_TIMEOUT GNUNET_TIME_relative_multiply ( \
245 GNUNET_TIME_UNIT_SECONDS,2)
246
247/**
248 * OIDC ignored parameter array
249 */
250static char *OIDC_ignored_parameter_array[] = { "display",
251 "prompt",
252 "ui_locales",
253 "response_mode",
254 "id_token_hint",
255 "login_hint",
256 "acr_values" };
257
258/**
259 * OIDC hashmap for cached access tokens and codes
260 */
261struct GNUNET_CONTAINER_MultiHashMap *oidc_code_cache;
262
263/**
264 * OIDC hashmap that keeps track of issued cookies
265 */
266struct GNUNET_CONTAINER_MultiHashMap *OIDC_cookie_jar_map;
267
268/**
269 * The configuration handle
270 */
271const struct GNUNET_CONFIGURATION_Handle *oid_cfg;
272
273/**
274 * HTTP methods allows for this plugin
275 */
276static char *allow_methods;
277
278/**
279 * Ego list
280 */
281static struct EgoEntry *ego_head;
282
283/**
284 * Ego list
285 */
286static struct EgoEntry *ego_tail;
287
288/**
289 * The processing state
290 */
291static int state;
292
293/**
294 * Handle to Identity service.
295 */
296static struct GNUNET_IDENTITY_Handle *identity_handle;
297
298/**
299 * GNS handle
300 */
301static struct GNUNET_GNS_Handle *gns_handle;
302
303/**
304 * Identity Provider
305 */
306static struct GNUNET_RECLAIM_Handle *idp;
307
308/**
309 * Timeout for consume call on userinfo
310 */
311static struct GNUNET_TIME_Relative consume_timeout;
312
313/**
314 * @brief struct returned by the initialization function of the plugin
315 */
316struct Plugin
317{
318 const struct GNUNET_CONFIGURATION_Handle *cfg;
319};
320
321/**
322 * @brief The RSA key used by the oidc enpoint
323 */
324json_t *oidc_jwk;
325
326/**
327 * OIDC needed variables
328 */
329struct OIDC_Variables
330{
331 /**
332 * The RP client public key
333 */
334 struct GNUNET_CRYPTO_PublicKey client_pkey;
335
336 /**
337 * The OIDC client id of the RP
338 */
339 char *client_id;
340
341 /**
342 * The OIDC redirect uri
343 */
344 char *redirect_uri;
345
346 /**
347 * The list of oidc scopes
348 */
349 char *scope;
350
351 /**
352 * The OIDC state
353 */
354 char *state;
355
356 /**
357 * The OIDC nonce
358 */
359 char *nonce;
360
361 /**
362 * The OIDC claims
363 */
364 char *claims;
365
366 /**
367 * The OIDC response type
368 */
369 char *response_type;
370
371 /**
372 * The identity chosen by the user to login
373 */
374 char *login_identity;
375
376 /**
377 * User cancelled authorization/login
378 */
379 int user_cancelled;
380
381 /**
382 * The PKCE code_challenge
383 */
384 char *code_challenge;
385
386 /**
387 * The PKCE code_verifier
388 */
389 char *code_verifier;
390
391};
392
393/**
394 * The ego list
395 */
396struct EgoEntry
397{
398 /**
399 * DLL
400 */
401 struct EgoEntry *next;
402
403 /**
404 * DLL
405 */
406 struct EgoEntry *prev;
407
408 /**
409 * Ego Identifier
410 */
411 char *identifier;
412
413 /**
414 * Public key string
415 */
416 char *keystring;
417
418 /**
419 * The Ego
420 */
421 struct GNUNET_IDENTITY_Ego *ego;
422};
423
424
425struct RequestHandle
426{
427 /**
428 * DLL
429 */
430 struct RequestHandle *next;
431
432 /**
433 * DLL
434 */
435 struct RequestHandle *prev;
436
437 /**
438 * Selected ego
439 */
440 struct EgoEntry *ego_entry;
441
442 /**
443 * Pointer to ego private key
444 */
445 struct GNUNET_CRYPTO_PrivateKey priv_key;
446
447 /**
448 * OIDC variables
449 */
450 struct OIDC_Variables *oidc;
451
452 /**
453 * GNS lookup op
454 */
455 struct GNUNET_GNS_LookupRequest *gns_op;
456
457 /**
458 * Rest connection
459 */
460 struct GNUNET_REST_RequestHandle *rest_handle;
461
462 /**
463 * Attribute claim list for id_token
464 */
465 struct GNUNET_RECLAIM_AttributeList *attr_idtoken_list;
466
467 /**
468 * Attribute claim list for userinfo
469 */
470 struct GNUNET_RECLAIM_AttributeList *attr_userinfo_list;
471
472 /**
473 * Credentials
474 */
475 struct GNUNET_RECLAIM_CredentialList *credentials;
476
477 /**
478 * Presentations
479 */
480 struct GNUNET_RECLAIM_PresentationList *presentations;
481
482 /**
483 * IDENTITY Operation
484 */
485 struct GNUNET_IDENTITY_Operation *op;
486
487
488 /**
489 * Idp Operation
490 */
491 struct GNUNET_RECLAIM_Operation *idp_op;
492
493 /**
494 * Timeout task for consume
495 */
496 struct GNUNET_SCHEDULER_Task *consume_timeout_op;
497
498 /**
499 * Attribute iterator
500 */
501 struct GNUNET_RECLAIM_AttributeIterator *attr_it;
502
503 /**
504 * Credential iterator
505 */
506 struct GNUNET_RECLAIM_CredentialIterator *cred_it;
507
508
509 /**
510 * Ticket iterator
511 */
512 struct GNUNET_RECLAIM_TicketIterator *ticket_it;
513
514 /**
515 * A ticket
516 */
517 struct GNUNET_RECLAIM_Ticket ticket;
518
519 /**
520 * Desired timeout for the lookup (default is no timeout).
521 */
522 struct GNUNET_TIME_Relative timeout;
523
524 /**
525 * ID of a task associated with the resolution process.
526 */
527 struct GNUNET_SCHEDULER_Task *timeout_task;
528
529 /**
530 * The plugin result processor
531 */
532 GNUNET_REST_ResultProcessor proc;
533
534 /**
535 * The closure of the result processor
536 */
537 void *proc_cls;
538
539 /**
540 * The url
541 */
542 char *url;
543
544 /**
545 * The passed access token
546 */
547 char *access_token;
548
549 /**
550 * The tld for redirect
551 */
552 char *tld;
553
554 /**
555 * The redirect prefix
556 */
557 char *redirect_prefix;
558
559 /**
560 * The redirect suffix
561 */
562 char *redirect_suffix;
563
564 /**
565 * Error response message
566 */
567 char *emsg;
568
569 /**
570 * Error response description
571 */
572 char *edesc;
573
574 /**
575 * Response code
576 */
577 int response_code;
578
579 /**
580 * Public client
581 */
582 int public_client;
583};
584
585/**
586 * DLL
587 */
588static struct RequestHandle *requests_head;
589
590/**
591 * DLL
592 */
593static struct RequestHandle *requests_tail;
594
595
596/**
597 * Cleanup lookup handle
598 * @param handle Handle to clean up
599 */
600static void
601cleanup_handle (struct RequestHandle *handle)
602{
603
604 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Cleaning up\n");
605 if (NULL != handle->timeout_task)
606 GNUNET_SCHEDULER_cancel (handle->timeout_task);
607 if (NULL != handle->attr_it)
608 GNUNET_RECLAIM_get_attributes_stop (handle->attr_it);
609 if (NULL != handle->cred_it)
610 GNUNET_RECLAIM_get_credentials_stop (handle->cred_it);
611 if (NULL != handle->ticket_it)
612 GNUNET_RECLAIM_ticket_iteration_stop (handle->ticket_it);
613 if (NULL != handle->idp_op)
614 GNUNET_RECLAIM_cancel (handle->idp_op);
615 if (NULL != handle->consume_timeout_op)
616 GNUNET_SCHEDULER_cancel (handle->consume_timeout_op);
617 GNUNET_free (handle->url);
618 GNUNET_free (handle->tld);
619 GNUNET_free (handle->redirect_prefix);
620 GNUNET_free (handle->redirect_suffix);
621 GNUNET_free (handle->emsg);
622 GNUNET_free (handle->edesc);
623 if (NULL != handle->gns_op)
624 GNUNET_GNS_lookup_cancel (handle->gns_op);
625 if (NULL != handle->oidc)
626 {
627 GNUNET_free (handle->oidc->client_id);
628 GNUNET_free (handle->oidc->login_identity);
629 GNUNET_free (handle->oidc->nonce);
630 GNUNET_free (handle->oidc->redirect_uri);
631 GNUNET_free (handle->oidc->response_type);
632 GNUNET_free (handle->oidc->scope);
633 GNUNET_free (handle->oidc->state);
634 if (NULL != handle->oidc->claims)
635 GNUNET_free (handle->oidc->claims);
636 if (NULL != handle->oidc->code_challenge)
637 GNUNET_free (handle->oidc->code_challenge);
638 GNUNET_free (handle->oidc);
639 }
640 if (NULL!=handle->attr_idtoken_list)
641 GNUNET_RECLAIM_attribute_list_destroy (handle->attr_idtoken_list);
642 if (NULL!=handle->attr_userinfo_list)
643 GNUNET_RECLAIM_attribute_list_destroy (handle->attr_userinfo_list);
644 if (NULL!=handle->credentials)
645 GNUNET_RECLAIM_credential_list_destroy (handle->credentials);
646 if (NULL!=handle->presentations)
647 GNUNET_RECLAIM_presentation_list_destroy (handle->presentations);
648 GNUNET_CONTAINER_DLL_remove (requests_head,
649 requests_tail,
650 handle);
651 if (NULL != handle->access_token)
652 GNUNET_free (handle->access_token);
653 GNUNET_free (handle);
654}
655
656
657/**
658 * Task run on error, sends error message. Cleans up everything.
659 *
660 * @param cls the `struct RequestHandle`
661 */
662static void
663do_error (void *cls)
664{
665 struct RequestHandle *handle = cls;
666 struct MHD_Response *resp;
667 char *json_error;
668
669 GNUNET_asprintf (&json_error,
670 "{ \"error\" : \"%s\", \"error_description\" : \"%s\"%s%s%s}",
671 handle->emsg,
672 (NULL != handle->edesc) ? handle->edesc : "",
673 (NULL != handle->oidc->state) ? ", \"state\":\"" : "",
674 (NULL != handle->oidc->state) ? handle->oidc->state : "",
675 (NULL != handle->oidc->state) ? "\"" : "");
676 if (0 == handle->response_code)
677 handle->response_code = MHD_HTTP_BAD_REQUEST;
678 resp = GNUNET_REST_create_response (json_error);
679 if (MHD_HTTP_UNAUTHORIZED == handle->response_code)
680 GNUNET_assert (MHD_NO !=
681 MHD_add_response_header (resp,
682 MHD_HTTP_HEADER_WWW_AUTHENTICATE,
683 "Basic"));
684 GNUNET_assert (MHD_NO != MHD_add_response_header (resp,
685 MHD_HTTP_HEADER_CONTENT_TYPE,
686 "application/json"));
687 handle->proc (handle->proc_cls, resp, handle->response_code);
688 cleanup_handle (handle);
689 GNUNET_free (json_error);
690}
691
692
693/**
694 * Task run on error in userinfo endpoint, sends error header. Cleans up
695 * everything
696 *
697 * @param cls the `struct RequestHandle`
698 */
699static void
700do_userinfo_error (void *cls)
701{
702 struct RequestHandle *handle = cls;
703 struct MHD_Response *resp;
704 char *error;
705
706 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
707 "Error: %s\n", handle->edesc);
708 GNUNET_asprintf (&error,
709 "error=\"%s\", error_description=\"%s\"",
710 handle->emsg,
711 (NULL != handle->edesc) ? handle->edesc : "");
712 resp = GNUNET_REST_create_response ("");
713 GNUNET_assert (MHD_NO !=
714 MHD_add_response_header (resp,
715 MHD_HTTP_HEADER_WWW_AUTHENTICATE,
716 "Bearer"));
717 handle->proc (handle->proc_cls, resp, handle->response_code);
718 cleanup_handle (handle);
719 GNUNET_free (error);
720}
721
722
723/**
724 * Task run on error, sends error message and redirects. Cleans up everything.
725 *
726 * @param cls the `struct RequestHandle`
727 */
728static void
729do_redirect_error (void *cls)
730{
731 struct RequestHandle *handle = cls;
732 struct MHD_Response *resp;
733 char *redirect;
734
735 GNUNET_asprintf (&redirect,
736 "%s?error=%s&error_description=%s%s%s",
737 handle->oidc->redirect_uri,
738 handle->emsg,
739 handle->edesc,
740 (NULL != handle->oidc->state) ? "&state=" : "",
741 (NULL != handle->oidc->state) ? handle->oidc->state : "");
742 resp = GNUNET_REST_create_response ("");
743 GNUNET_assert (MHD_NO != MHD_add_response_header (resp,
744 "Location", redirect));
745 handle->proc (handle->proc_cls, resp, MHD_HTTP_FOUND);
746 cleanup_handle (handle);
747 GNUNET_free (redirect);
748}
749
750
751/**
752 * Task run on timeout, sends error message. Cleans up everything.
753 *
754 * @param cls the `struct RequestHandle`
755 */
756static void
757do_timeout (void *cls)
758{
759 struct RequestHandle *handle = cls;
760
761 handle->timeout_task = NULL;
762 do_error (handle);
763}
764
765
766/**
767 * Respond to OPTIONS request
768 *
769 * @param con_handle the connection handle
770 * @param url the url
771 * @param cls the RequestHandle
772 */
773static void
774options_cont (struct GNUNET_REST_RequestHandle *con_handle,
775 const char *url,
776 void *cls)
777{
778 struct MHD_Response *resp;
779 struct RequestHandle *handle = cls;
780
781 // For now, independent of path return all options
782 resp = GNUNET_REST_create_response (NULL);
783 MHD_add_response_header (resp, "Access-Control-Allow-Methods", allow_methods);
784 handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
785 cleanup_handle (handle);
786 return;
787}
788
789
790/**
791 * Interprets cookie header and pass its identity keystring to handle
792 */
793static void
794cookie_identity_interpretation (struct RequestHandle *handle)
795{
796 struct GNUNET_HashCode cache_key;
797 char *cookies;
798 struct GNUNET_TIME_Absolute current_time, *relog_time;
799 char delimiter[] = "; ";
800 char *tmp_cookies;
801 char *token;
802 char *value;
803
804 // gets identity of login try with cookie
805 GNUNET_CRYPTO_hash (OIDC_COOKIE_HEADER_KEY,
806 strlen (OIDC_COOKIE_HEADER_KEY),
807 &cache_key);
808 if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle
809 ->header_param_map,
810 &cache_key))
811 {
812 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No cookie found\n");
813 return;
814 }
815 // splits cookies and find 'Identity' cookie
816 tmp_cookies =
817 GNUNET_CONTAINER_multihashmap_get (handle->rest_handle->header_param_map,
818 &cache_key);
819 cookies = GNUNET_strdup (tmp_cookies);
820 token = strtok (cookies, delimiter);
821 handle->oidc->user_cancelled = GNUNET_NO;
822 handle->oidc->login_identity = NULL;
823 if (NULL == token)
824 {
825 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
826 "Unable to parse cookie: %s\n",
827 cookies);
828 GNUNET_free (cookies);
829 return;
830 }
831
832 while (NULL != token)
833 {
834 if (0 == strcmp (token, OIDC_COOKIE_HEADER_ACCESS_DENIED))
835 {
836 handle->oidc->user_cancelled = GNUNET_YES;
837 GNUNET_free (cookies);
838 return;
839 }
840 if (NULL != strstr (token, OIDC_COOKIE_HEADER_INFORMATION_KEY))
841 break;
842 token = strtok (NULL, delimiter);
843 }
844 if (NULL == token)
845 {
846 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
847 "No cookie value to process: %s\n",
848 cookies);
849 GNUNET_free (cookies);
850 return;
851 }
852 GNUNET_CRYPTO_hash (token, strlen (token), &cache_key);
853 if (GNUNET_NO ==
854 GNUNET_CONTAINER_multihashmap_contains (OIDC_cookie_jar_map, &cache_key))
855 {
856 GNUNET_log (
857 GNUNET_ERROR_TYPE_WARNING,
858 "Found cookie `%s', but no corresponding expiration entry present...\n",
859 token);
860 GNUNET_free (cookies);
861 return;
862 }
863 relog_time =
864 GNUNET_CONTAINER_multihashmap_get (OIDC_cookie_jar_map, &cache_key);
865 current_time = GNUNET_TIME_absolute_get ();
866 // 30 min after old login -> redirect to login
867 if (current_time.abs_value_us > relog_time->abs_value_us)
868 {
869 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
870 "Found cookie `%s', but it is expired.\n",
871 token);
872 GNUNET_free (cookies);
873 return;
874 }
875 value = strtok (token, OIDC_COOKIE_HEADER_INFORMATION_KEY);
876 GNUNET_assert (NULL != value);
877 handle->oidc->login_identity = GNUNET_strdup (value);
878 GNUNET_free (cookies);
879}
880
881
882/**
883 * @brief Read the the JSON Web Key in the given file and return it.
884 * Return NULL and emit warning if JSON can not be decoded or the key is
885 * invalid
886 *
887 * @param filename the file to read the JWK from
888 * @return json_t* the reed JWK
889 */
890json_t *
891read_jwk_from_file (const char *filename)
892{
893 json_t *jwk;
894 json_error_t error;
895
896 jwk = json_load_file (filename, JSON_DECODE_ANY, &error);
897
898 if (! jwk)
899 {
900 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
901 ("Could not read OIDC RSA key from config file; %s\n"),
902 error.text);
903 }
904
905 return jwk;
906}
907
908
909/**
910 * @brief Write the JWK to file. If unsuccessful emit warning
911 *
912 * @param filename the name of the file the JWK is writen to
913 * @param jwk the JWK that is going to be written
914 * @return int Return GNUNET_OK if write is sucessfull
915 */
916static int
917write_jwk_to_file (const char *filename,
918 json_t *jwk)
919{
920 if (json_dump_file (jwk, filename, JSON_INDENT (2)))
921 {
922 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
923 ("Could not write OIDC RSA key to file %s\n"),
924 filename);
925 return GNUNET_ERROR_TYPE_WARNING;
926 }
927 else
928 return GNUNET_OK;
929}
930
931
932/**
933 * @brief Generate a new RSA JSON Web Key
934 *
935 * @return json_t* the generated JWK
936 */
937json_t *
938generate_jwk ()
939{
940 json_t *jwk;
941 jwk = json_pack ("{s:s,s:i}", "kty", "RSA", "bits", 2048);
942 jose_jwk_gen (NULL, jwk);
943 json_incref (jwk);
944 return jwk;
945}
946
947
948/**
949 * Return the path to the oidc directory path
950 *
951 * @param cls the RequestHandle
952 */
953char *
954get_oidc_dir_path (void *cls)
955{
956 char *oidc_directory;
957 struct RequestHandle *handle = cls;
958
959 // Read OIDC directory from config
960 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (oid_cfg,
961 "reclaim-rest-plugin",
962 "oidc_dir",
963 &oidc_directory))
964 {
965 // Could not read Config file
966 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_SERVER_ERROR);
967 handle->edesc = GNUNET_strdup ("gnunet configuration failed");
968 handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
969 GNUNET_SCHEDULER_add_now (&do_error, handle);
970 return NULL;
971 }
972
973 return oidc_directory;
974}
975
976
977/**
978 * Return the path to the RSA JWK key file
979 *
980 * @param cls the RequestHandle
981 */
982char *
983get_oidc_jwk_path (void *cls)
984{
985 char *oidc_directory;
986 char *oidc_jwk_path;
987
988 oidc_directory = get_oidc_dir_path (cls);
989
990 // Create path to file
991 GNUNET_asprintf (&oidc_jwk_path, "%s/%s", oidc_directory,
992 OIDC_JWK_RSA_FILENAME);
993
994 return oidc_jwk_path;
995}
996
997
998/**
999 * Redirects to login page stored in configuration file
1000 */
1001static void
1002login_redirect (void *cls)
1003{
1004 char *login_base_url;
1005 char *new_redirect;
1006 char *tmp;
1007 struct MHD_Response *resp;
1008 struct GNUNET_Buffer buf = { 0 };
1009 struct RequestHandle *handle = cls;
1010
1011 if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (oid_cfg,
1012 "reclaim-rest-plugin",
1013 "address",
1014 &login_base_url))
1015 {
1016 GNUNET_buffer_write_str (&buf, login_base_url);
1017 GNUNET_buffer_write_fstr (&buf,
1018 "?%s=%s",
1019 OIDC_RESPONSE_TYPE_KEY,
1020 handle->oidc->response_type);
1021 GNUNET_buffer_write_fstr (&buf,
1022 "&%s=%s",
1023 OIDC_CLIENT_ID_KEY,
1024 handle->oidc->client_id);
1025 GNUNET_STRINGS_urlencode (handle->oidc->redirect_uri,
1026 strlen (handle->oidc->redirect_uri),
1027 &tmp);
1028 GNUNET_buffer_write_fstr (&buf,
1029 "&%s=%s",
1030 OIDC_REDIRECT_URI_KEY,
1031 tmp);
1032 GNUNET_free (tmp);
1033 GNUNET_STRINGS_urlencode (handle->oidc->scope,
1034 strlen (handle->oidc->scope),
1035 &tmp);
1036 GNUNET_buffer_write_fstr (&buf,
1037 "&%s=%s",
1038 OIDC_SCOPE_KEY,
1039 tmp);
1040 GNUNET_free (tmp);
1041 if (NULL != handle->oidc->state)
1042 {
1043 GNUNET_STRINGS_urlencode (handle->oidc->state,
1044 strlen (handle->oidc->state),
1045 &tmp);
1046 GNUNET_buffer_write_fstr (&buf,
1047 "&%s=%s",
1048 OIDC_STATE_KEY,
1049 handle->oidc->state);
1050 GNUNET_free (tmp);
1051 }
1052 if (NULL != handle->oidc->code_challenge)
1053 {
1054 GNUNET_buffer_write_fstr (&buf,
1055 "&%s=%s",
1056 OIDC_CODE_CHALLENGE_KEY,
1057 handle->oidc->code_challenge);
1058 }
1059 if (NULL != handle->oidc->nonce)
1060 {
1061 GNUNET_buffer_write_fstr (&buf,
1062 "&%s=%s",
1063 OIDC_NONCE_KEY,
1064 handle->oidc->nonce);
1065 }
1066 if (NULL != handle->oidc->claims)
1067 {
1068 GNUNET_STRINGS_urlencode (handle->oidc->claims,
1069 strlen (handle->oidc->claims),
1070 &tmp);
1071 GNUNET_buffer_write_fstr (&buf,
1072 "&%s=%s",
1073 OIDC_CLAIMS_KEY,
1074 tmp);
1075 GNUNET_free (tmp);
1076 }
1077 new_redirect = GNUNET_buffer_reap_str (&buf);
1078 resp = GNUNET_REST_create_response ("");
1079 MHD_add_response_header (resp, "Location", new_redirect);
1080 GNUNET_free (login_base_url);
1081 }
1082 else
1083 {
1084 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_SERVER_ERROR);
1085 handle->edesc = GNUNET_strdup ("gnunet configuration failed");
1086 handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
1087 GNUNET_SCHEDULER_add_now (&do_error, handle);
1088 return;
1089 }
1090 handle->proc (handle->proc_cls, resp, MHD_HTTP_FOUND);
1091 GNUNET_free (new_redirect);
1092 cleanup_handle (handle);
1093}
1094
1095
1096/**
1097 * Does internal server error when iteration failed.
1098 */
1099static void
1100oidc_iteration_error (void *cls)
1101{
1102 struct RequestHandle *handle = cls;
1103
1104 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_SERVER_ERROR);
1105 handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
1106 GNUNET_SCHEDULER_add_now (&do_error, handle);
1107}
1108
1109
1110/**
1111 * Issues ticket and redirects to relying party with the authorization code as
1112 * parameter. Otherwise redirects with error
1113 */
1114static void
1115oidc_ticket_issue_cb (void *cls,
1116 const struct GNUNET_RECLAIM_Ticket *ticket,
1117 const struct
1118 GNUNET_RECLAIM_PresentationList *presentation)
1119{
1120 struct RequestHandle *handle = cls;
1121 struct MHD_Response *resp;
1122 char *ticket_str;
1123 char *redirect_uri;
1124 char *code_string;
1125
1126 handle->idp_op = NULL;
1127 if (NULL == ticket)
1128 {
1129 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_SERVER_ERROR);
1130 handle->edesc = GNUNET_strdup ("Server cannot generate ticket.");
1131 GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
1132 return;
1133 }
1134 handle->ticket = *ticket;
1135 ticket_str =
1136 GNUNET_STRINGS_data_to_string_alloc (&handle->ticket,
1137 sizeof(struct GNUNET_RECLAIM_Ticket));
1138 code_string = OIDC_build_authz_code (&handle->priv_key,
1139 &handle->ticket,
1140 handle->attr_idtoken_list,
1141 presentation,
1142 handle->oidc->nonce,
1143 handle->oidc->code_challenge);
1144 if ((NULL != handle->redirect_prefix) && (NULL != handle->redirect_suffix) &&
1145 (NULL != handle->tld))
1146 {
1147 GNUNET_asprintf (&redirect_uri,
1148 "%s.%s/%s%s%s=%s&state=%s",
1149 handle->redirect_prefix,
1150 handle->tld,
1151 handle->redirect_suffix,
1152 (NULL == strchr (handle->redirect_suffix, '?') ? "?" :
1153 "&"),
1154 handle->oidc->response_type,
1155 code_string,
1156 handle->oidc->state);
1157 }
1158 else
1159 {
1160 GNUNET_asprintf (&redirect_uri,
1161 "%s%s%s=%s&state=%s",
1162 handle->oidc->redirect_uri,
1163 (NULL == strchr (handle->oidc->redirect_uri, '?') ? "?" :
1164 "&"),
1165 handle->oidc->response_type,
1166 code_string,
1167 handle->oidc->state);
1168 }
1169 resp = GNUNET_REST_create_response ("");
1170 GNUNET_assert (MHD_NO != MHD_add_response_header (resp,
1171 "Location", redirect_uri));
1172 handle->proc (handle->proc_cls, resp, MHD_HTTP_FOUND);
1173 cleanup_handle (handle);
1174 GNUNET_free (redirect_uri);
1175 GNUNET_free (ticket_str);
1176 GNUNET_free (code_string);
1177}
1178
1179
1180static struct GNUNET_RECLAIM_AttributeList*
1181attribute_list_merge (struct GNUNET_RECLAIM_AttributeList *list_a,
1182 struct GNUNET_RECLAIM_AttributeList *list_b)
1183{
1184 struct GNUNET_RECLAIM_AttributeList *merged_list;
1185 struct GNUNET_RECLAIM_AttributeListEntry *le_a;
1186 struct GNUNET_RECLAIM_AttributeListEntry *le_b;
1187 struct GNUNET_RECLAIM_AttributeListEntry *le_m;
1188
1189 merged_list = GNUNET_new (struct GNUNET_RECLAIM_AttributeList);
1190 for (le_a = list_a->list_head; NULL != le_a; le_a = le_a->next)
1191 {
1192 le_m = GNUNET_new (struct GNUNET_RECLAIM_AttributeListEntry);
1193 le_m->attribute = GNUNET_RECLAIM_attribute_new (le_a->attribute->name,
1194 &le_a->attribute->
1195 credential,
1196 le_a->attribute->type,
1197 le_a->attribute->data,
1198 le_a->attribute->data_size);
1199 le_m->attribute->id = le_a->attribute->id;
1200 le_m->attribute->flag = le_a->attribute->flag;
1201 le_m->attribute->credential = le_a->attribute->credential;
1202 GNUNET_CONTAINER_DLL_insert (merged_list->list_head,
1203 merged_list->list_tail,
1204 le_m);
1205 }
1206 le_m = NULL;
1207 for (le_b = list_b->list_head; NULL != le_b; le_b = le_b->next)
1208 {
1209 for (le_m = merged_list->list_head; NULL != le_m; le_m = le_m->next)
1210 {
1211 if (GNUNET_YES == GNUNET_RECLAIM_id_is_equal (&le_m->attribute->id,
1212 &le_b->attribute->id))
1213 break; /** Attribute already in list **/
1214 }
1215 if (NULL != le_m)
1216 continue; /** Attribute already in list **/
1217 le_m = GNUNET_new (struct GNUNET_RECLAIM_AttributeListEntry);
1218 le_m->attribute = GNUNET_RECLAIM_attribute_new (le_b->attribute->name,
1219 &le_b->attribute->
1220 credential,
1221 le_b->attribute->type,
1222 le_b->attribute->data,
1223 le_b->attribute->data_size);
1224 le_m->attribute->id = le_b->attribute->id;
1225 le_m->attribute->flag = le_b->attribute->flag;
1226 le_m->attribute->credential = le_b->attribute->credential;
1227 GNUNET_CONTAINER_DLL_insert (merged_list->list_head,
1228 merged_list->list_tail,
1229 le_m);
1230 }
1231 return merged_list;
1232}
1233
1234
1235static void
1236oidc_cred_collect_finished_cb (void *cls)
1237{
1238 struct RequestHandle *handle = cls;
1239 struct GNUNET_RECLAIM_AttributeList *merged_list;
1240 struct GNUNET_RECLAIM_AttributeListEntry *le_m;
1241
1242 handle->cred_it = NULL;
1243 merged_list = attribute_list_merge (handle->attr_idtoken_list,
1244 handle->attr_userinfo_list);
1245 for (le_m = merged_list->list_head; NULL != le_m; le_m = le_m->next)
1246 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1247 "List Attribute in ticket to issue: %s\n",
1248 le_m->attribute->name);
1249 handle->idp_op = GNUNET_RECLAIM_ticket_issue (idp,
1250 &handle->priv_key,
1251 &handle->oidc->client_pkey,
1252 merged_list,
1253 &oidc_ticket_issue_cb,
1254 handle);
1255 GNUNET_RECLAIM_attribute_list_destroy (merged_list);
1256}
1257
1258
1259/**
1260 * Collects all attributes for an ego if in scope parameter
1261 */
1262static void
1263oidc_cred_collect (void *cls,
1264 const struct GNUNET_CRYPTO_PublicKey *identity,
1265 const struct GNUNET_RECLAIM_Credential *cred)
1266{
1267 struct RequestHandle *handle = cls;
1268 struct GNUNET_RECLAIM_AttributeListEntry *le;
1269 struct GNUNET_RECLAIM_CredentialListEntry *ale;
1270
1271 for (ale = handle->credentials->list_head; NULL != ale; ale = ale->next)
1272 {
1273 if (GNUNET_NO == GNUNET_RECLAIM_id_is_equal (&ale->credential->id,
1274 &cred->id))
1275 continue;
1276 /** Credential already in list **/
1277 GNUNET_RECLAIM_get_credentials_next (handle->cred_it);
1278 return;
1279 }
1280
1281 for (le = handle->attr_idtoken_list->list_head; NULL != le; le = le->next)
1282 {
1283 if (GNUNET_NO == GNUNET_RECLAIM_id_is_equal (&le->attribute->credential,
1284 &cred->id))
1285 continue;
1286 /** Credential matches for attribute, add **/
1287 ale = GNUNET_new (struct GNUNET_RECLAIM_CredentialListEntry);
1288 ale->credential = GNUNET_RECLAIM_credential_new (cred->name,
1289 cred->type,
1290 cred->data,
1291 cred->data_size);
1292 GNUNET_CONTAINER_DLL_insert (handle->credentials->list_head,
1293 handle->credentials->list_tail,
1294 ale);
1295 }
1296 GNUNET_RECLAIM_get_credentials_next (handle->cred_it);
1297}
1298
1299
1300static void
1301oidc_attr_collect_finished_cb (void *cls)
1302{
1303 struct RequestHandle *handle = cls;
1304
1305 handle->attr_it = NULL;
1306 handle->ticket_it = NULL;
1307 if (NULL == handle->attr_idtoken_list->list_head)
1308 {
1309 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_SCOPE);
1310 handle->edesc = GNUNET_strdup ("The requested scope is not available.");
1311 GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
1312 return;
1313 }
1314 handle->credentials = GNUNET_new (struct GNUNET_RECLAIM_CredentialList);
1315 handle->cred_it =
1316 GNUNET_RECLAIM_get_credentials_start (idp,
1317 &handle->priv_key,
1318 &oidc_iteration_error,
1319 handle,
1320 &oidc_cred_collect,
1321 handle,
1322 &oidc_cred_collect_finished_cb,
1323 handle);
1324
1325}
1326
1327
1328static int
1329attr_in_claims_request (struct RequestHandle *handle,
1330 const char *attr_name,
1331 const char *claims_parameter)
1332{
1333 int ret = GNUNET_NO;
1334 json_t *root;
1335 json_error_t error;
1336 json_t *claims_j;
1337 const char *key;
1338 json_t *value;
1339
1340 /** Check if attribute is requested through a scope **/
1341 if (GNUNET_YES == OIDC_check_scopes_for_claim_request (handle->oidc->scope,
1342 attr_name))
1343 return GNUNET_YES;
1344
1345 /** Try claims parameter if not in scope */
1346 if (NULL != handle->oidc->claims)
1347 {
1348 root = json_loads (handle->oidc->claims, JSON_DECODE_ANY, &error);
1349 claims_j = json_object_get (root, claims_parameter);
1350 /* obj is a JSON object */
1351 if (NULL != claims_j)
1352 {
1353 json_object_foreach (claims_j, key, value) {
1354 if (0 != strcmp (attr_name, key))
1355 continue;
1356 ret = GNUNET_YES;
1357 break;
1358 }
1359 }
1360 json_decref (root);
1361 }
1362 return ret;
1363}
1364
1365
1366static int
1367attr_in_idtoken_request (struct RequestHandle *handle,
1368 const char *attr_name)
1369{
1370 return attr_in_claims_request (handle, attr_name, "id_token");
1371}
1372
1373
1374static int
1375attr_in_userinfo_request (struct RequestHandle *handle,
1376 const char *attr_name)
1377{
1378 return attr_in_claims_request (handle, attr_name, "userinfo");
1379}
1380
1381
1382/**
1383 * Collects all attributes for an ego if in scope parameter
1384 */
1385static void
1386oidc_attr_collect (void *cls,
1387 const struct GNUNET_CRYPTO_PublicKey *identity,
1388 const struct GNUNET_RECLAIM_Attribute *attr)
1389{
1390 struct RequestHandle *handle = cls;
1391 struct GNUNET_RECLAIM_AttributeListEntry *le;
1392 if (GNUNET_YES == attr_in_idtoken_request (handle, attr->name))
1393 {
1394 le = GNUNET_new (struct GNUNET_RECLAIM_AttributeListEntry);
1395 le->attribute = GNUNET_RECLAIM_attribute_new (attr->name,
1396 &attr->credential,
1397 attr->type,
1398 attr->data,
1399 attr->data_size);
1400 le->attribute->id = attr->id;
1401 le->attribute->flag = attr->flag;
1402 le->attribute->credential = attr->credential;
1403 GNUNET_CONTAINER_DLL_insert (handle->attr_idtoken_list->list_head,
1404 handle->attr_idtoken_list->list_tail,
1405 le);
1406 }
1407 if (GNUNET_YES == attr_in_userinfo_request (handle, attr->name))
1408 {
1409 le = GNUNET_new (struct GNUNET_RECLAIM_AttributeListEntry);
1410 le->attribute = GNUNET_RECLAIM_attribute_new (attr->name,
1411 &attr->credential,
1412 attr->type,
1413 attr->data,
1414 attr->data_size);
1415 le->attribute->id = attr->id;
1416 le->attribute->flag = attr->flag;
1417 le->attribute->credential = attr->credential;
1418 GNUNET_CONTAINER_DLL_insert (handle->attr_userinfo_list->list_head,
1419 handle->attr_userinfo_list->list_tail,
1420 le);
1421 }
1422
1423 GNUNET_RECLAIM_get_attributes_next (handle->attr_it);
1424}
1425
1426
1427/**
1428 * Checks time and cookie and redirects accordingly
1429 */
1430static void
1431code_redirect (void *cls)
1432{
1433 struct RequestHandle *handle = cls;
1434 struct GNUNET_TIME_Absolute current_time;
1435 struct GNUNET_TIME_Absolute *relog_time;
1436 struct GNUNET_CRYPTO_PublicKey pubkey;
1437 struct GNUNET_CRYPTO_PublicKey ego_pkey;
1438 struct GNUNET_HashCode cache_key;
1439 char *identity_cookie;
1440
1441 GNUNET_asprintf (&identity_cookie,
1442 "Identity=%s",
1443 handle->oidc->login_identity);
1444 GNUNET_CRYPTO_hash (identity_cookie, strlen (identity_cookie), &cache_key);
1445 GNUNET_free (identity_cookie);
1446 // No login time for identity -> redirect to login
1447 if (GNUNET_YES ==
1448 GNUNET_CONTAINER_multihashmap_contains (OIDC_cookie_jar_map, &cache_key))
1449 {
1450 relog_time =
1451 GNUNET_CONTAINER_multihashmap_get (OIDC_cookie_jar_map, &cache_key);
1452 current_time = GNUNET_TIME_absolute_get ();
1453 // 30 min after old login -> redirect to login
1454 if (current_time.abs_value_us <= relog_time->abs_value_us)
1455 {
1456 if (GNUNET_OK !=
1457 GNUNET_CRYPTO_public_key_from_string (handle->oidc
1458 ->login_identity,
1459 &pubkey))
1460 {
1461 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_COOKIE);
1462 handle->edesc =
1463 GNUNET_strdup ("The cookie of a login identity is not valid");
1464 GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
1465 return;
1466 }
1467 // iterate over egos and compare their public key
1468 for (handle->ego_entry = ego_head; NULL != handle->ego_entry;
1469 handle->ego_entry = handle->ego_entry->next)
1470 {
1471 GNUNET_IDENTITY_ego_get_public_key (handle->ego_entry->ego, &ego_pkey);
1472 if (0 == GNUNET_memcmp (&ego_pkey, &pubkey))
1473 {
1474 handle->priv_key =
1475 *GNUNET_IDENTITY_ego_get_private_key (handle->ego_entry->ego);
1476 handle->attr_idtoken_list =
1477 GNUNET_new (struct GNUNET_RECLAIM_AttributeList);
1478 handle->attr_userinfo_list =
1479 GNUNET_new (struct GNUNET_RECLAIM_AttributeList);
1480 handle->attr_it =
1481 GNUNET_RECLAIM_get_attributes_start (idp,
1482 &handle->priv_key,
1483 &oidc_iteration_error,
1484 handle,
1485 &oidc_attr_collect,
1486 handle,
1487 &oidc_attr_collect_finished_cb,
1488 handle);
1489 return;
1490 }
1491 }
1492 GNUNET_SCHEDULER_add_now (&login_redirect, handle);
1493 return;
1494 }
1495 }
1496}
1497
1498
1499static void
1500build_redirect (void *cls)
1501{
1502 struct RequestHandle *handle = cls;
1503 struct MHD_Response *resp;
1504 char *redirect_uri;
1505
1506 if (GNUNET_YES == handle->oidc->user_cancelled)
1507 {
1508 if ((NULL != handle->redirect_prefix) &&
1509 (NULL != handle->redirect_suffix) && (NULL != handle->tld))
1510 {
1511 GNUNET_asprintf (&redirect_uri,
1512 "%s.%s/%s?error=%s&error_description=%s&state=%s",
1513 handle->redirect_prefix,
1514 handle->tld,
1515 handle->redirect_suffix,
1516 "access_denied",
1517 "User denied access",
1518 handle->oidc->state);
1519 }
1520 else
1521 {
1522 GNUNET_asprintf (&redirect_uri,
1523 "%s?error=%s&error_description=%s&state=%s",
1524 handle->oidc->redirect_uri,
1525 "access_denied",
1526 "User denied access",
1527 handle->oidc->state);
1528 }
1529 resp = GNUNET_REST_create_response ("");
1530 GNUNET_assert (MHD_NO != MHD_add_response_header (resp,
1531 "Location",
1532 redirect_uri));
1533 handle->proc (handle->proc_cls, resp, MHD_HTTP_FOUND);
1534 cleanup_handle (handle);
1535 GNUNET_free (redirect_uri);
1536 return;
1537 }
1538 GNUNET_SCHEDULER_add_now (&code_redirect, handle);
1539}
1540
1541
1542static void
1543lookup_redirect_uri_result (void *cls,
1544 uint32_t rd_count,
1545 const struct GNUNET_GNSRECORD_Data *rd)
1546{
1547 struct RequestHandle *handle = cls;
1548 char *tmp;
1549 char *tmp_key_str;
1550 char *pos;
1551 struct GNUNET_CRYPTO_PublicKey redirect_zone;
1552
1553 handle->gns_op = NULL;
1554 if (0 == rd_count)
1555 {
1556 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_SERVER_ERROR);
1557 handle->edesc =
1558 GNUNET_strdup ("Server cannot generate ticket, redirect uri not found.");
1559 GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
1560 return;
1561 }
1562 for (int i = 0; i < rd_count; i++)
1563 {
1564 if (GNUNET_GNSRECORD_TYPE_RECLAIM_OIDC_REDIRECT != rd[i].record_type)
1565 continue;
1566 if (0 != strncmp (rd[i].data, handle->oidc->redirect_uri, rd[i].data_size))
1567 continue;
1568 tmp = GNUNET_strndup (rd[i].data, rd[i].data_size);
1569 if (NULL == strstr (tmp, handle->oidc->client_id))
1570 {
1571 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1572 "Redirect uri %s does not contain client_id %s\n",
1573 tmp,
1574 handle->oidc->client_id);
1575 }
1576 else
1577 {
1578 pos = strrchr (tmp, (unsigned char) '.');
1579 if (NULL == pos)
1580 {
1581 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1582 "Redirect uri %s contains client_id but is malformed\n",
1583 tmp);
1584 GNUNET_free (tmp);
1585 continue;
1586 }
1587 *pos = '\0';
1588 handle->redirect_prefix = GNUNET_strdup (tmp);
1589 tmp_key_str = pos + 1;
1590 pos = strchr (tmp_key_str, (unsigned char) '/');
1591 if (NULL == pos)
1592 {
1593 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1594 "Redirect uri %s contains client_id but is malformed\n",
1595 tmp);
1596 GNUNET_free (tmp);
1597 continue;
1598 }
1599 *pos = '\0';
1600 handle->redirect_suffix = GNUNET_strdup (pos + 1);
1601
1602 GNUNET_STRINGS_string_to_data (tmp_key_str,
1603 strlen (tmp_key_str),
1604 &redirect_zone,
1605 sizeof(redirect_zone));
1606 }
1607 GNUNET_SCHEDULER_add_now (&build_redirect, handle);
1608 GNUNET_free (tmp);
1609 return;
1610 }
1611 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_SERVER_ERROR);
1612 handle->edesc =
1613 GNUNET_strdup ("Server cannot generate ticket, redirect uri not found.");
1614 GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
1615}
1616
1617
1618/**
1619 * Initiate redirect back to client.
1620 */
1621static void
1622client_redirect (void *cls)
1623{
1624 struct RequestHandle *handle = cls;
1625
1626 /* Lookup client redirect uri to verify request */
1627 handle->gns_op =
1628 GNUNET_GNS_lookup (gns_handle,
1629 GNUNET_GNS_EMPTY_LABEL_AT,
1630 &handle->oidc->client_pkey,
1631 GNUNET_GNSRECORD_TYPE_RECLAIM_OIDC_REDIRECT,
1632 GNUNET_GNS_LO_DEFAULT,
1633 &lookup_redirect_uri_result,
1634 handle);
1635}
1636
1637
1638static char *
1639get_url_parameter_copy (const struct RequestHandle *handle, const char *key)
1640{
1641 struct GNUNET_HashCode hc;
1642 char *value;
1643 char *res;
1644
1645 GNUNET_CRYPTO_hash (key, strlen (key), &hc);
1646 if (GNUNET_YES != GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle
1647 ->url_param_map,
1648 &hc))
1649 return NULL;
1650 value =
1651 GNUNET_CONTAINER_multihashmap_get (handle->rest_handle->url_param_map, &hc);
1652 if (NULL == value)
1653 return NULL;
1654 GNUNET_STRINGS_urldecode (value, strlen (value), &res);
1655 return res;
1656}
1657
1658
1659/**
1660 * Iteration over all results finished, build final
1661 * response.
1662 *
1663 * @param cls the `struct RequestHandle`
1664 */
1665static void
1666build_authz_response (void *cls)
1667{
1668 struct RequestHandle *handle = cls;
1669 struct GNUNET_HashCode cache_key;
1670
1671 char *expected_scope;
1672 char delimiter[] = " ";
1673 int number_of_ignored_parameter, iterator;
1674
1675
1676 // REQUIRED value: redirect_uri
1677 handle->oidc->redirect_uri =
1678 get_url_parameter_copy (handle, OIDC_REDIRECT_URI_KEY);
1679 if (NULL == handle->oidc->redirect_uri)
1680 {
1681 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST);
1682 handle->edesc = GNUNET_strdup ("missing parameter redirect_uri");
1683 GNUNET_SCHEDULER_add_now (&do_error, handle);
1684 return;
1685 }
1686
1687 // REQUIRED value: response_type
1688 handle->oidc->response_type =
1689 get_url_parameter_copy (handle, OIDC_RESPONSE_TYPE_KEY);
1690 if (NULL == handle->oidc->response_type)
1691 {
1692 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST);
1693 handle->edesc = GNUNET_strdup ("missing parameter response_type");
1694 GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
1695 return;
1696 }
1697
1698 // REQUIRED value: scope
1699 handle->oidc->scope = get_url_parameter_copy (handle, OIDC_SCOPE_KEY);
1700 if (NULL == handle->oidc->scope)
1701 {
1702 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_SCOPE);
1703 handle->edesc = GNUNET_strdup ("missing parameter scope");
1704 GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
1705 return;
1706 }
1707
1708 // OPTIONAL value: nonce
1709 handle->oidc->nonce = get_url_parameter_copy (handle, OIDC_NONCE_KEY);
1710
1711 // OPTIONAL value: claims
1712 handle->oidc->claims = get_url_parameter_copy (handle, OIDC_CLAIMS_KEY);
1713
1714 // TODO check other values if needed
1715 number_of_ignored_parameter =
1716 sizeof(OIDC_ignored_parameter_array) / sizeof(char *);
1717 for (iterator = 0; iterator < number_of_ignored_parameter; iterator++)
1718 {
1719 GNUNET_CRYPTO_hash (OIDC_ignored_parameter_array[iterator],
1720 strlen (OIDC_ignored_parameter_array[iterator]),
1721 &cache_key);
1722 if (GNUNET_YES ==
1723 GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle
1724 ->url_param_map,
1725 &cache_key))
1726 {
1727 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_ACCESS_DENIED);
1728 GNUNET_asprintf (&handle->edesc,
1729 "Server will not handle parameter: %s",
1730 OIDC_ignored_parameter_array[iterator]);
1731 GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
1732 return;
1733 }
1734 }
1735
1736 // We only support authorization code flows.
1737 if (0 != strcmp (handle->oidc->response_type,
1738 OIDC_EXPECTED_AUTHORIZATION_RESPONSE_TYPE))
1739 {
1740 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_UNSUPPORTED_RESPONSE_TYPE);
1741 handle->edesc = GNUNET_strdup ("The authorization server does not support "
1742 "obtaining this authorization code.");
1743 GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
1744 return;
1745 }
1746
1747 // Checks if scope contains 'openid'
1748 expected_scope = GNUNET_strdup (handle->oidc->scope);
1749 char *test;
1750 test = strtok (expected_scope, delimiter);
1751 while (NULL != test)
1752 {
1753 if (0 == strcmp (OIDC_EXPECTED_AUTHORIZATION_SCOPE, expected_scope))
1754 break;
1755 test = strtok (NULL, delimiter);
1756 }
1757 if (NULL == test)
1758 {
1759 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_SCOPE);
1760 handle->edesc =
1761 GNUNET_strdup ("The requested scope is invalid, unknown, or malformed.");
1762 GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
1763 GNUNET_free (expected_scope);
1764 return;
1765 }
1766
1767 GNUNET_free (expected_scope);
1768 if ((NULL == handle->oidc->login_identity) &&
1769 (GNUNET_NO == handle->oidc->user_cancelled))
1770 GNUNET_SCHEDULER_add_now (&login_redirect, handle);
1771 else
1772 GNUNET_SCHEDULER_add_now (&client_redirect, handle);
1773}
1774
1775
1776/**
1777 * Iterate over tlds in config
1778 */
1779static void
1780tld_iter (void *cls, const char *section, const char *option, const char *value)
1781{
1782 struct RequestHandle *handle = cls;
1783 struct GNUNET_CRYPTO_PublicKey pkey;
1784
1785 if (GNUNET_OK !=
1786 GNUNET_CRYPTO_public_key_from_string (value, &pkey))
1787 {
1788 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Skipping non key %s\n", value);
1789 return;
1790 }
1791 if (0 == GNUNET_memcmp (&pkey, &handle->oidc->client_pkey))
1792 handle->tld = GNUNET_strdup (option + 1);
1793}
1794
1795
1796/**
1797 * Responds to authorization GET and url-encoded POST request
1798 *
1799 * @param con_handle the connection handle
1800 * @param url the url
1801 * @param cls the RequestHandle
1802 */
1803static void
1804authorize_endpoint (struct GNUNET_REST_RequestHandle *con_handle,
1805 const char *url,
1806 void *cls)
1807{
1808 struct RequestHandle *handle = cls;
1809 struct EgoEntry *tmp_ego;
1810 const struct GNUNET_CRYPTO_PrivateKey *priv_key;
1811 struct GNUNET_CRYPTO_PublicKey pkey;
1812
1813 cookie_identity_interpretation (handle);
1814
1815 // RECOMMENDED value: state - REQUIRED for answers
1816 handle->oidc->state = get_url_parameter_copy (handle, OIDC_STATE_KEY);
1817
1818 // REQUIRED value: client_id
1819 handle->oidc->client_id = get_url_parameter_copy (handle, OIDC_CLIENT_ID_KEY);
1820 if (NULL == handle->oidc->client_id)
1821 {
1822 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST);
1823 handle->edesc = GNUNET_strdup ("missing parameter client_id");
1824 handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
1825 GNUNET_SCHEDULER_add_now (&do_error, handle);
1826 return;
1827 }
1828
1829 // OPTIONAL value: code_challenge
1830 handle->oidc->code_challenge = get_url_parameter_copy (handle,
1831 OIDC_CODE_CHALLENGE_KEY);
1832 if (NULL == handle->oidc->code_challenge)
1833 {
1834 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1835 "OAuth authorization request does not contain PKCE parameters!\n");
1836 }
1837
1838 if (GNUNET_OK !=
1839 GNUNET_CRYPTO_public_key_from_string (handle->oidc->client_id,
1840 &handle->oidc->client_pkey))
1841 {
1842 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_UNAUTHORIZED_CLIENT);
1843 handle->edesc = GNUNET_strdup ("The client is not authorized to request an "
1844 "authorization code using this method.");
1845 handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
1846 GNUNET_SCHEDULER_add_now (&do_error, handle);
1847 return;
1848 }
1849
1850 // If we know this identity, translated the corresponding TLD
1851 // TODO: We might want to have a reverse lookup functionality for TLDs?
1852 for (tmp_ego = ego_head; NULL != tmp_ego; tmp_ego = tmp_ego->next)
1853 {
1854 priv_key = GNUNET_IDENTITY_ego_get_private_key (tmp_ego->ego);
1855 GNUNET_CRYPTO_key_get_public (priv_key, &pkey);
1856 if (0 == GNUNET_memcmp (&pkey, &handle->oidc->client_pkey))
1857 {
1858 handle->tld = GNUNET_strdup (tmp_ego->identifier);
1859 handle->ego_entry = ego_tail;
1860 }
1861 }
1862 if (NULL == handle->tld)
1863 GNUNET_CONFIGURATION_iterate_section_values (oid_cfg, "gns", tld_iter,
1864 handle);
1865 if (NULL == handle->tld)
1866 handle->tld = GNUNET_strdup (handle->oidc->client_id);
1867 GNUNET_SCHEDULER_add_now (&build_authz_response, handle);
1868}
1869
1870
1871/**
1872 * Combines an identity with a login time and responds OK to login request
1873 *
1874 * @param con_handle the connection handle
1875 * @param url the url
1876 * @param cls the RequestHandle
1877 */
1878static void
1879login_cont (struct GNUNET_REST_RequestHandle *con_handle,
1880 const char *url,
1881 void *cls)
1882{
1883 struct MHD_Response *resp = GNUNET_REST_create_response ("");
1884 struct RequestHandle *handle = cls;
1885 struct GNUNET_HashCode cache_key;
1886 struct GNUNET_TIME_Absolute *current_time;
1887 struct GNUNET_TIME_Absolute *last_time;
1888 char *cookie;
1889 char *header_val;
1890 json_t *root;
1891 json_error_t error;
1892 json_t *identity;
1893 char term_data[handle->rest_handle->data_size + 1];
1894
1895 term_data[handle->rest_handle->data_size] = '\0';
1896 GNUNET_memcpy (term_data,
1897 handle->rest_handle->data,
1898 handle->rest_handle->data_size);
1899 root = json_loads (term_data, JSON_DECODE_ANY, &error);
1900 identity = json_object_get (root, "identity");
1901 if (! json_is_string (identity))
1902 {
1903 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1904 "Error parsing json string from %s\n",
1905 term_data);
1906 handle->proc (handle->proc_cls, resp, MHD_HTTP_BAD_REQUEST);
1907 json_decref (root);
1908 cleanup_handle (handle);
1909 return;
1910 }
1911 GNUNET_asprintf (&cookie, "Identity=%s", json_string_value (identity));
1912 GNUNET_asprintf (&header_val,
1913 "%s;Max-Age=%d",
1914 cookie,
1915 OIDC_COOKIE_EXPIRATION);
1916 GNUNET_assert (MHD_NO != MHD_add_response_header (resp,
1917 "Set-Cookie", header_val));
1918 GNUNET_assert (MHD_NO !=
1919 MHD_add_response_header (resp,
1920 "Access-Control-Allow-Methods",
1921 "POST"));
1922 GNUNET_CRYPTO_hash (cookie, strlen (cookie), &cache_key);
1923
1924 if (0 != strcmp (json_string_value (identity), "Denied"))
1925 {
1926 current_time = GNUNET_new (struct GNUNET_TIME_Absolute);
1927 *current_time = GNUNET_TIME_relative_to_absolute (
1928 GNUNET_TIME_relative_multiply (GNUNET_TIME_relative_get_second_ (),
1929 OIDC_COOKIE_EXPIRATION));
1930 last_time =
1931 GNUNET_CONTAINER_multihashmap_get (OIDC_cookie_jar_map, &cache_key);
1932 GNUNET_free (last_time);
1933 GNUNET_CONTAINER_multihashmap_put (OIDC_cookie_jar_map,
1934 &cache_key,
1935 current_time,
1936 GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE);
1937 }
1938 handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
1939 GNUNET_free (cookie);
1940 GNUNET_free (header_val);
1941 json_decref (root);
1942 cleanup_handle (handle);
1943}
1944
1945
1946static int
1947parse_credentials_basic_auth (struct RequestHandle *handle,
1948 char **client_id,
1949 char **client_secret)
1950{
1951 struct GNUNET_HashCode cache_key;
1952 char *authorization;
1953 char *credentials;
1954 char *basic_authorization;
1955 char *client_id_tmp;
1956 char *pass;
1957
1958 GNUNET_CRYPTO_hash (OIDC_AUTHORIZATION_HEADER_KEY,
1959 strlen (OIDC_AUTHORIZATION_HEADER_KEY),
1960 &cache_key);
1961 if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle
1962 ->header_param_map,
1963 &cache_key))
1964 return GNUNET_SYSERR;
1965 authorization =
1966 GNUNET_CONTAINER_multihashmap_get (handle->rest_handle->header_param_map,
1967 &cache_key);
1968
1969 // split header in "Basic" and [content]
1970 credentials = strtok (authorization, " ");
1971 if ((NULL == credentials) || (0 != strcmp ("Basic", credentials)))
1972 return GNUNET_SYSERR;
1973 credentials = strtok (NULL, " ");
1974 if (NULL == credentials)
1975 return GNUNET_SYSERR;
1976 GNUNET_STRINGS_base64_decode (credentials,
1977 strlen (credentials),
1978 (void **) &basic_authorization);
1979
1980 if (NULL == basic_authorization)
1981 return GNUNET_SYSERR;
1982 client_id_tmp = strtok (basic_authorization, ":");
1983 if (NULL == client_id_tmp)
1984 {
1985 GNUNET_free (basic_authorization);
1986 return GNUNET_SYSERR;
1987 }
1988 pass = strtok (NULL, ":");
1989 if (NULL == pass)
1990 {
1991 GNUNET_free (basic_authorization);
1992 return GNUNET_SYSERR;
1993 }
1994 *client_id = strdup (client_id_tmp);
1995 *client_secret = strdup (pass);
1996 GNUNET_free (basic_authorization);
1997 return GNUNET_OK;
1998}
1999
2000
2001static int
2002parse_credentials_post_body (struct RequestHandle *handle,
2003 char **client_id,
2004 char **client_secret)
2005{
2006 struct GNUNET_HashCode cache_key;
2007 char *client_id_tmp;
2008 char *pass;
2009
2010 GNUNET_CRYPTO_hash ("client_id",
2011 strlen ("client_id"),
2012 &cache_key);
2013 if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle
2014 ->url_param_map,
2015 &cache_key))
2016 return GNUNET_SYSERR;
2017 client_id_tmp = GNUNET_CONTAINER_multihashmap_get (
2018 handle->rest_handle->url_param_map,
2019 &cache_key);
2020 if (NULL == client_id_tmp)
2021 return GNUNET_SYSERR;
2022 *client_id = strdup (client_id_tmp);
2023 GNUNET_CRYPTO_hash ("client_secret",
2024 strlen ("client_secret"),
2025 &cache_key);
2026 if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle
2027 ->url_param_map,
2028 &cache_key))
2029 {
2030 GNUNET_free (*client_id);
2031 *client_id = NULL;
2032 return GNUNET_SYSERR;
2033 }
2034 pass = GNUNET_CONTAINER_multihashmap_get (handle->rest_handle->url_param_map,
2035 &cache_key);
2036 if (NULL == pass)
2037 {
2038 GNUNET_free (*client_id);
2039 *client_id = NULL;
2040 return GNUNET_SYSERR;
2041 }
2042 *client_secret = strdup (pass);
2043 return GNUNET_OK;
2044}
2045
2046
2047static int
2048check_authorization (struct RequestHandle *handle,
2049 struct GNUNET_CRYPTO_PublicKey *cid)
2050{
2051 char *expected_pass;
2052 char *received_cid;
2053 char *received_cpw;
2054 char *pkce_cv;
2055
2056 if (GNUNET_OK == parse_credentials_basic_auth (handle,
2057 &received_cid,
2058 &received_cpw))
2059 {
2060 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2061 "Received client credentials in HTTP AuthZ header\n");
2062 }
2063 else if (GNUNET_OK == parse_credentials_post_body (handle,
2064 &received_cid,
2065 &received_cpw))
2066 {
2067 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2068 "Received client credentials in POST body\n");
2069 }
2070 else
2071 {
2072 /** Allow public clients with PKCE **/
2073 pkce_cv = get_url_parameter_copy (handle, OIDC_CODE_VERIFIER_KEY);
2074 if (NULL == pkce_cv)
2075 {
2076 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_CLIENT);
2077 handle->response_code = MHD_HTTP_UNAUTHORIZED;
2078 return GNUNET_SYSERR;
2079 }
2080 handle->public_client = GNUNET_YES;
2081 GNUNET_free (pkce_cv);
2082 received_cid = get_url_parameter_copy (handle, OIDC_CLIENT_ID_KEY);
2083 GNUNET_STRINGS_string_to_data (received_cid,
2084 strlen (received_cid),
2085 cid,
2086 sizeof(struct GNUNET_CRYPTO_PublicKey));
2087 GNUNET_free (received_cid);
2088 return GNUNET_OK;
2089
2090 }
2091
2092 // check client password
2093 if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (oid_cfg,
2094 "reclaim-rest-plugin",
2095 "OIDC_CLIENT_HMAC_SECRET",
2096 &expected_pass))
2097 {
2098 if (0 != strcmp (expected_pass, received_cpw))
2099 {
2100 GNUNET_free (expected_pass);
2101 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_CLIENT);
2102 handle->response_code = MHD_HTTP_UNAUTHORIZED;
2103 GNUNET_free (received_cpw);
2104 GNUNET_free (received_cid);
2105 return GNUNET_SYSERR;
2106 }
2107 GNUNET_free (expected_pass);
2108 }
2109 else
2110 {
2111 GNUNET_free (received_cpw);
2112 GNUNET_free (received_cid);
2113 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_SERVER_ERROR);
2114 handle->edesc = GNUNET_strdup ("gnunet configuration failed");
2115 handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
2116 return GNUNET_SYSERR;
2117 }
2118 // check client_id
2119 for (handle->ego_entry = ego_head; NULL != handle->ego_entry;
2120 handle->ego_entry = handle->ego_entry->next)
2121 {
2122 if (0 == strcmp (handle->ego_entry->keystring, received_cid))
2123 break;
2124 }
2125 if (NULL == handle->ego_entry)
2126 {
2127 GNUNET_free (received_cpw);
2128 GNUNET_free (received_cid);
2129 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_CLIENT);
2130 handle->response_code = MHD_HTTP_UNAUTHORIZED;
2131 return GNUNET_SYSERR;
2132 }
2133 GNUNET_STRINGS_string_to_data (received_cid,
2134 strlen (received_cid),
2135 cid,
2136 sizeof(struct GNUNET_CRYPTO_PublicKey));
2137
2138 GNUNET_free (received_cpw);
2139 GNUNET_free (received_cid);
2140 return GNUNET_OK;
2141}
2142
2143
2144const struct EgoEntry *
2145find_ego (struct RequestHandle *handle,
2146 struct GNUNET_CRYPTO_PublicKey *test_key)
2147{
2148 struct EgoEntry *ego_entry;
2149 struct GNUNET_CRYPTO_PublicKey pub_key;
2150
2151 for (ego_entry = ego_head; NULL != ego_entry;
2152 ego_entry = ego_entry->next)
2153 {
2154 GNUNET_IDENTITY_ego_get_public_key (ego_entry->ego, &pub_key);
2155 if (0 == GNUNET_memcmp (&pub_key, test_key))
2156 return ego_entry;
2157 }
2158 return NULL;
2159}
2160
2161
2162/**
2163 * Responds to token url-encoded POST request
2164 *
2165 * @param con_handle the connection handle
2166 * @param url the url
2167 * @param cls the RequestHandle
2168 */
2169static void
2170token_endpoint (struct GNUNET_REST_RequestHandle *con_handle,
2171 const char *url,
2172 void *cls)
2173{
2174 struct RequestHandle *handle = cls;
2175 const struct EgoEntry *ego_entry = NULL;
2176 struct GNUNET_TIME_Relative expiration_time;
2177 struct GNUNET_RECLAIM_AttributeList *cl = NULL;
2178 struct GNUNET_RECLAIM_PresentationList *pl = NULL;
2179 struct GNUNET_RECLAIM_Ticket ticket;
2180 struct GNUNET_CRYPTO_PublicKey cid;
2181 struct GNUNET_HashCode cache_key;
2182 struct MHD_Response *resp = NULL;
2183 char *grant_type = NULL;
2184 char *code = NULL;
2185 char *json_response = NULL;
2186 char *id_token = NULL;
2187 char *access_token = NULL;
2188 char *jwa = NULL;
2189 char *jwt_secret = NULL;
2190 char *nonce = NULL;
2191 char *code_verifier = NULL;
2192 json_t *oidc_jwk = NULL;
2193 char *oidc_jwk_path = NULL;
2194 char *oidc_directory = NULL;
2195 char *tmp_at = NULL;
2196
2197 /*
2198 * Check Authorization
2199 */
2200 if (GNUNET_SYSERR == check_authorization (handle, &cid))
2201 {
2202 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2203 "OIDC authorization for token endpoint failed\n");
2204 GNUNET_SCHEDULER_add_now (&do_error, handle);
2205 return;
2206 }
2207
2208 /*
2209 * Check parameter
2210 */
2211
2212 // TODO Do not allow multiple equal parameter names
2213 // REQUIRED grant_type
2214 GNUNET_CRYPTO_hash (OIDC_GRANT_TYPE_KEY,
2215 strlen (OIDC_GRANT_TYPE_KEY),
2216 &cache_key);
2217 grant_type = get_url_parameter_copy (handle, OIDC_GRANT_TYPE_KEY);
2218 if (NULL == grant_type)
2219 {
2220 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST);
2221 handle->edesc = GNUNET_strdup ("missing parameter grant_type");
2222 handle->response_code = MHD_HTTP_BAD_REQUEST;
2223 GNUNET_SCHEDULER_add_now (&do_error, handle);
2224 return;
2225 }
2226
2227 // Check parameter grant_type == "authorization_code"
2228 if (0 != strcmp (OIDC_GRANT_TYPE_VALUE, grant_type))
2229 {
2230 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_UNSUPPORTED_GRANT_TYPE);
2231 handle->response_code = MHD_HTTP_BAD_REQUEST;
2232 GNUNET_free (grant_type);
2233 GNUNET_SCHEDULER_add_now (&do_error, handle);
2234 return;
2235 }
2236 GNUNET_free (grant_type);
2237 // REQUIRED code
2238 code = get_url_parameter_copy (handle, OIDC_CODE_KEY);
2239 if (NULL == code)
2240 {
2241 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST);
2242 handle->edesc = GNUNET_strdup ("missing parameter code");
2243 handle->response_code = MHD_HTTP_BAD_REQUEST;
2244 GNUNET_SCHEDULER_add_now (&do_error, handle);
2245 return;
2246 }
2247 ego_entry = find_ego (handle, &cid);
2248 if (NULL == ego_entry)
2249 {
2250 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST);
2251 handle->edesc = GNUNET_strdup ("Unknown client");
2252 handle->response_code = MHD_HTTP_BAD_REQUEST;
2253 GNUNET_free (code);
2254 GNUNET_SCHEDULER_add_now (&do_error, handle);
2255 return;
2256 }
2257
2258 // REQUIRED code verifier
2259 code_verifier = get_url_parameter_copy (handle, OIDC_CODE_VERIFIER_KEY);
2260 if (NULL == code_verifier)
2261 {
2262 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2263 "OAuth authorization request does not contain PKCE parameters!\n");
2264
2265 }
2266
2267 // decode code
2268 if (GNUNET_OK != OIDC_parse_authz_code (&cid, code, code_verifier, &ticket,
2269 &cl, &pl, &nonce,
2270 OIDC_VERIFICATION_DEFAULT))
2271 {
2272 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST);
2273 handle->edesc = GNUNET_strdup ("invalid code");
2274 handle->response_code = MHD_HTTP_BAD_REQUEST;
2275 GNUNET_free (code);
2276 if (NULL != code_verifier)
2277 GNUNET_free (code_verifier);
2278 GNUNET_SCHEDULER_add_now (&do_error, handle);
2279 return;
2280 }
2281 if (NULL != code_verifier)
2282 GNUNET_free (code_verifier);
2283
2284 // create jwt
2285 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (oid_cfg,
2286 "reclaim-rest-plugin",
2287 "expiration_time",
2288 &expiration_time))
2289 {
2290 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_SERVER_ERROR);
2291 handle->edesc = GNUNET_strdup ("gnunet configuration failed");
2292 handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
2293 GNUNET_free (code);
2294 if (NULL != nonce)
2295 GNUNET_free (nonce);
2296 GNUNET_RECLAIM_attribute_list_destroy (cl);
2297 GNUNET_RECLAIM_presentation_list_destroy (pl);
2298 GNUNET_SCHEDULER_add_now (&do_error, handle);
2299 return;
2300 }
2301
2302 // Check if HMAC or RSA should be used
2303 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (oid_cfg,
2304 "reclaim-rest-plugin",
2305 "oidc_json_web_algorithm",
2306 &jwa))
2307 {
2308 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2309 "Could not read OIDC JSON Web Algorithm config attribute."
2310 "Defaulting to RS256.");
2311 jwa = JWT_ALG_VALUE_RSA;
2312 }
2313
2314 if (! strcmp (jwa, JWT_ALG_VALUE_RSA))
2315 {
2316 // Replace for now
2317 oidc_jwk_path = get_oidc_jwk_path (cls);
2318 oidc_jwk = read_jwk_from_file (oidc_jwk_path);
2319
2320 // Check if secret JWK exists
2321 if (! oidc_jwk)
2322 {
2323 // Generate and save a new key
2324 oidc_jwk = generate_jwk ();
2325 oidc_directory = get_oidc_dir_path (cls);
2326
2327 // Create new oidc directory
2328 if (GNUNET_OK != GNUNET_DISK_directory_create (oidc_directory))
2329 {
2330 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2331 ("Failed to create directory `%s' for storing oidc data\n"),
2332 oidc_directory);
2333 }
2334 else
2335 {
2336 write_jwk_to_file (oidc_jwk_path, oidc_jwk);
2337 }
2338 }
2339
2340 // Generate oidc token
2341 id_token = OIDC_generate_id_token_rsa (&ticket.audience,
2342 &ticket.identity,
2343 cl,
2344 pl,
2345 &expiration_time,
2346 (NULL != nonce) ? nonce : NULL,
2347 oidc_jwk);
2348 }
2349 else if (! strcmp (jwa, JWT_ALG_VALUE_HMAC))
2350 {
2351 // TODO OPTIONAL acr,amr,azp
2352 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (oid_cfg,
2353 "reclaim-rest-plugin",
2354 "jwt_secret",
2355 &jwt_secret))
2356 {
2357 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST);
2358 handle->edesc = GNUNET_strdup ("No signing secret configured!");
2359 handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
2360 GNUNET_free (code);
2361 GNUNET_RECLAIM_attribute_list_destroy (cl);
2362 GNUNET_RECLAIM_presentation_list_destroy (pl);
2363 if (NULL != nonce)
2364 GNUNET_free (nonce);
2365 GNUNET_SCHEDULER_add_now (&do_error, handle);
2366 return;
2367 }
2368
2369 id_token = OIDC_generate_id_token_hmac (&ticket.audience,
2370 &ticket.identity,
2371 cl,
2372 pl,
2373 &expiration_time,
2374 (NULL != nonce) ? nonce : NULL,
2375 jwt_secret);
2376
2377 GNUNET_free (jwt_secret);
2378 }
2379 else
2380 {
2381 // TODO: OPTION NOT FOUND ERROR
2382 }
2383
2384 if (NULL != nonce)
2385 GNUNET_free (nonce);
2386 access_token = OIDC_access_token_new (&ticket);
2387 /**
2388 * Store mapping from access token to code so we can later
2389 * fall back on the provided attributes in userinfo one time.
2390 */
2391 GNUNET_CRYPTO_hash (access_token,
2392 strlen (access_token),
2393 &cache_key);
2394 /**
2395 * Note to future self: This cache has the following purpose:
2396 * Some OIDC plugins call the userendpoint right after receiving an
2397 * ID token and access token. There are reasons why this would make sense.
2398 * Others not so much.
2399 * In any case, in order to smoothen out the user experience upon login
2400 * (authorization), we speculatively cache the next
2401 * userinfo response in case the actual resolution through reclaim/GNS
2402 * takes too long.
2403 */
2404 tmp_at = GNUNET_CONTAINER_multihashmap_get (oidc_code_cache,
2405 &cache_key);
2406 GNUNET_CONTAINER_multihashmap_put (oidc_code_cache,
2407 &cache_key,
2408 code,
2409 GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE);
2410 /* If there was a previous code in there, free the old value */
2411 if (NULL != tmp_at)
2412 {
2413 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2414 "OIDC access token already issued. Cleanup.\n");
2415 GNUNET_free (tmp_at);
2416 }
2417
2418 OIDC_build_token_response (access_token,
2419 id_token,
2420 &expiration_time,
2421 &json_response);
2422
2423 resp = GNUNET_REST_create_response (json_response);
2424 GNUNET_assert (MHD_NO != MHD_add_response_header (resp,
2425 "Cache-Control",
2426 "no-store"));
2427 GNUNET_assert (MHD_NO != MHD_add_response_header (resp,
2428 "Pragma", "no-cache"));
2429 GNUNET_assert (MHD_NO != MHD_add_response_header (resp,
2430 "Content-Type",
2431 "application/json"));
2432 handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
2433 GNUNET_RECLAIM_attribute_list_destroy (cl);
2434 GNUNET_RECLAIM_presentation_list_destroy (pl);
2435 GNUNET_free (access_token);
2436 GNUNET_free (json_response);
2437 GNUNET_free (id_token);
2438 cleanup_handle (handle);
2439}
2440
2441
2442/**
2443 * Collects claims and stores them in handle
2444 */
2445static void
2446consume_ticket (void *cls,
2447 const struct GNUNET_CRYPTO_PublicKey *identity,
2448 const struct GNUNET_RECLAIM_Attribute *attr,
2449 const struct GNUNET_RECLAIM_Presentation *presentation)
2450{
2451 struct RequestHandle *handle = cls;
2452 struct GNUNET_RECLAIM_AttributeListEntry *ale;
2453 struct GNUNET_RECLAIM_PresentationListEntry *atle;
2454 struct MHD_Response *resp;
2455 struct GNUNET_HashCode cache_key;
2456 char *result_str;
2457 char *cached_code;
2458
2459 if (NULL != handle->consume_timeout_op)
2460 GNUNET_SCHEDULER_cancel (handle->consume_timeout_op);
2461 handle->consume_timeout_op = NULL;
2462 handle->idp_op = NULL;
2463
2464 /**
2465 * We received a reply. In any case clear the cache.
2466 */
2467 GNUNET_CRYPTO_hash (handle->access_token,
2468 strlen (handle->access_token),
2469 &cache_key);
2470 cached_code = GNUNET_CONTAINER_multihashmap_get (oidc_code_cache,
2471 &cache_key);
2472 if (NULL != cached_code)
2473 {
2474 GNUNET_assert (GNUNET_YES ==
2475 GNUNET_CONTAINER_multihashmap_remove (oidc_code_cache,
2476 &cache_key,
2477 cached_code));
2478 GNUNET_free (cached_code);
2479 }
2480
2481
2482 if (NULL == identity)
2483 {
2484 result_str = OIDC_generate_userinfo (&handle->ticket.identity,
2485 handle->attr_userinfo_list,
2486 handle->presentations);
2487 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Userinfo: %s\n", result_str);
2488 resp = GNUNET_REST_create_response (result_str);
2489 handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
2490 GNUNET_free (result_str);
2491 cleanup_handle (handle);
2492 return;
2493 }
2494 ale = GNUNET_new (struct GNUNET_RECLAIM_AttributeListEntry);
2495 ale->attribute = GNUNET_RECLAIM_attribute_new (attr->name,
2496 &attr->credential,
2497 attr->type,
2498 attr->data,
2499 attr->data_size);
2500 ale->attribute->id = attr->id;
2501 ale->attribute->flag = attr->flag;
2502 ale->attribute->credential = attr->credential;
2503 GNUNET_CONTAINER_DLL_insert (handle->attr_userinfo_list->list_head,
2504 handle->attr_userinfo_list->list_tail,
2505 ale);
2506 if (NULL == presentation)
2507 return;
2508 for (atle = handle->presentations->list_head;
2509 NULL != atle; atle = atle->next)
2510 {
2511 if (GNUNET_NO == GNUNET_RECLAIM_id_is_equal (
2512 &atle->presentation->credential_id,
2513 &presentation->credential_id))
2514 continue;
2515 break; /** already in list **/
2516 }
2517 if (NULL == atle)
2518 {
2519 /** Credential matches for attribute, add **/
2520 atle = GNUNET_new (struct GNUNET_RECLAIM_PresentationListEntry);
2521 atle->presentation = GNUNET_RECLAIM_presentation_new (presentation->type,
2522 presentation->data,
2523 presentation->
2524 data_size);
2525 atle->presentation->credential_id = presentation->credential_id;
2526 GNUNET_CONTAINER_DLL_insert (handle->presentations->list_head,
2527 handle->presentations->list_tail,
2528 atle);
2529 }
2530}
2531
2532
2533static void
2534consume_fail (void *cls)
2535{
2536 struct RequestHandle *handle = cls;
2537 struct GNUNET_HashCode cache_key;
2538 struct GNUNET_RECLAIM_AttributeList *cl = NULL;
2539 struct GNUNET_RECLAIM_PresentationList *pl = NULL;
2540 struct GNUNET_RECLAIM_Ticket ticket;
2541 struct MHD_Response *resp;
2542 char *nonce;
2543 char *cached_code;
2544 char *result_str;
2545
2546
2547 handle->consume_timeout_op = NULL;
2548 if (NULL != handle->idp_op)
2549 GNUNET_RECLAIM_cancel (handle->idp_op);
2550 handle->idp_op = NULL;
2551
2552 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2553 "Ticket consumptioned timed out. Using cache...\n");
2554 GNUNET_CRYPTO_hash (handle->access_token,
2555 strlen (handle->access_token),
2556 &cache_key);
2557 cached_code = GNUNET_CONTAINER_multihashmap_get (oidc_code_cache,
2558 &cache_key);
2559 if (NULL == cached_code)
2560 {
2561 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_TOKEN);
2562 handle->edesc = GNUNET_strdup ("No Access Token in cache!");
2563 handle->response_code = MHD_HTTP_UNAUTHORIZED;
2564 GNUNET_SCHEDULER_add_now (&do_userinfo_error, handle);
2565 return;
2566 }
2567 /**
2568 * Remove the cached item
2569 */
2570 GNUNET_assert (GNUNET_YES ==
2571 GNUNET_CONTAINER_multihashmap_remove (oidc_code_cache,
2572 &cache_key,
2573 cached_code));
2574
2575 // decode code
2576 if (GNUNET_OK != OIDC_parse_authz_code (&handle->ticket.audience,
2577 cached_code, NULL, &ticket,
2578 &cl, &pl, &nonce,
2579 OIDC_VERIFICATION_NO_CODE_VERIFIER))
2580 {
2581 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST);
2582 handle->edesc = GNUNET_strdup ("invalid code");
2583 handle->response_code = MHD_HTTP_BAD_REQUEST;
2584 GNUNET_free (cached_code);
2585 if (NULL != nonce)
2586 GNUNET_free (nonce);
2587 GNUNET_SCHEDULER_add_now (&do_error, handle);
2588 return;
2589 }
2590
2591 GNUNET_free (cached_code);
2592
2593 result_str = OIDC_generate_userinfo (&handle->ticket.identity,
2594 cl,
2595 pl);
2596 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Userinfo: %s\n", result_str);
2597 resp = GNUNET_REST_create_response (result_str);
2598 handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
2599 GNUNET_free (result_str);
2600 GNUNET_free (nonce);
2601 GNUNET_RECLAIM_attribute_list_destroy (cl);
2602 GNUNET_RECLAIM_presentation_list_destroy (pl);
2603 cleanup_handle (handle);
2604}
2605
2606
2607/**
2608 * Responds to userinfo GET and url-encoded POST request
2609 *
2610 * @param con_handle the connection handle
2611 * @param url the url
2612 * @param cls the RequestHandle
2613 */
2614static void
2615userinfo_endpoint (struct GNUNET_REST_RequestHandle *con_handle,
2616 const char *url,
2617 void *cls)
2618{
2619 // TODO expiration time
2620 struct RequestHandle *handle = cls;
2621 struct GNUNET_RECLAIM_Ticket *ticket;
2622 char delimiter[] = " ";
2623 struct GNUNET_HashCode cache_key;
2624 char *authorization;
2625 char *authorization_type;
2626 char *authorization_access_token;
2627 const struct EgoEntry *aud_ego;
2628 const struct GNUNET_CRYPTO_PrivateKey *privkey;
2629
2630 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Getting userinfo\n");
2631 GNUNET_CRYPTO_hash (OIDC_AUTHORIZATION_HEADER_KEY,
2632 strlen (OIDC_AUTHORIZATION_HEADER_KEY),
2633 &cache_key);
2634 if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle
2635 ->header_param_map,
2636 &cache_key))
2637 {
2638 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_TOKEN);
2639 handle->edesc = GNUNET_strdup ("No Access Token");
2640 handle->response_code = MHD_HTTP_UNAUTHORIZED;
2641 GNUNET_SCHEDULER_add_now (&do_userinfo_error, handle);
2642 return;
2643 }
2644 authorization =
2645 GNUNET_CONTAINER_multihashmap_get (handle->rest_handle->header_param_map,
2646 &cache_key);
2647
2648 // split header in "Bearer" and access_token
2649 authorization = GNUNET_strdup (authorization);
2650 authorization_type = strtok (authorization, delimiter);
2651 if ((NULL == authorization_type) ||
2652 (0 != strcmp ("Bearer", authorization_type)))
2653 {
2654 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_TOKEN);
2655 handle->edesc = GNUNET_strdup ("No Access Token");
2656 handle->response_code = MHD_HTTP_UNAUTHORIZED;
2657 GNUNET_SCHEDULER_add_now (&do_userinfo_error, handle);
2658 GNUNET_free (authorization);
2659 return;
2660 }
2661 authorization_access_token = strtok (NULL, delimiter);
2662 if (NULL == authorization_access_token)
2663 {
2664 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_TOKEN);
2665 handle->edesc = GNUNET_strdup ("Access token missing");
2666 handle->response_code = MHD_HTTP_UNAUTHORIZED;
2667 GNUNET_SCHEDULER_add_now (&do_userinfo_error, handle);
2668 GNUNET_free (authorization);
2669 return;
2670 }
2671
2672 if (GNUNET_OK != OIDC_access_token_parse (authorization_access_token,
2673 &ticket))
2674 {
2675 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_TOKEN);
2676 handle->edesc = GNUNET_strdup ("The access token is invalid");
2677 handle->response_code = MHD_HTTP_UNAUTHORIZED;
2678 GNUNET_SCHEDULER_add_now (&do_userinfo_error, handle);
2679 GNUNET_free (authorization);
2680 return;
2681
2682 }
2683 GNUNET_assert (NULL != ticket);
2684 handle->ticket = *ticket;
2685 GNUNET_free (ticket);
2686 aud_ego = find_ego (handle, &handle->ticket.audience);
2687 if (NULL == aud_ego)
2688 {
2689 handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_TOKEN);
2690 handle->edesc = GNUNET_strdup ("The access token expired");
2691 handle->response_code = MHD_HTTP_UNAUTHORIZED;
2692 GNUNET_SCHEDULER_add_now (&do_userinfo_error, handle);
2693 GNUNET_free (authorization);
2694 return;
2695 }
2696 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Consuming ticket\n");
2697 privkey = GNUNET_IDENTITY_ego_get_private_key (aud_ego->ego);
2698 handle->attr_userinfo_list =
2699 GNUNET_new (struct GNUNET_RECLAIM_AttributeList);
2700 handle->presentations =
2701 GNUNET_new (struct GNUNET_RECLAIM_PresentationList);
2702
2703 /* If the consume takes too long, we use values from the cache */
2704 handle->access_token = GNUNET_strdup (authorization_access_token);
2705 handle->consume_timeout_op = GNUNET_SCHEDULER_add_delayed (consume_timeout,
2706 &consume_fail,
2707 handle);
2708 handle->idp_op = GNUNET_RECLAIM_ticket_consume (idp,
2709 privkey,
2710 &handle->ticket,
2711 &consume_ticket,
2712 handle);
2713 GNUNET_free (authorization);
2714}
2715
2716
2717/**
2718 * Responds to /jwks.json
2719 *
2720 * @param con_handle the connection handle
2721 * @param url the url
2722 * @param cls the RequestHandle
2723 */
2724static void
2725jwks_endpoint (struct GNUNET_REST_RequestHandle *con_handle,
2726 const char *url,
2727 void *cls)
2728{
2729 char *oidc_directory;
2730 char *oidc_jwk_path;
2731 char *oidc_jwk_pub_str;
2732 json_t *oidc_jwk;
2733 struct MHD_Response *resp;
2734 struct RequestHandle *handle = cls;
2735
2736 oidc_jwk_path = get_oidc_jwk_path (cls);
2737 oidc_jwk = read_jwk_from_file (oidc_jwk_path);
2738
2739 // Check if secret JWK exists
2740 if (! oidc_jwk)
2741 {
2742 // Generate and save a new key
2743 oidc_jwk = generate_jwk ();
2744 oidc_directory = get_oidc_dir_path (cls);
2745
2746 // Create new oidc directory
2747 if (GNUNET_OK != GNUNET_DISK_directory_create (oidc_directory))
2748 {
2749 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2750 ("Failed to create directory `%s' for storing oidc data\n"),
2751 oidc_directory);
2752 }
2753 else
2754 {
2755 write_jwk_to_file (oidc_jwk_path, oidc_jwk);
2756 }
2757 }
2758
2759 // Convert secret JWK to public JWK
2760 jose_jwk_pub (NULL, oidc_jwk);
2761
2762 // Encode JWK as string and return to API endpoint
2763 oidc_jwk_pub_str = json_dumps (oidc_jwk, JSON_INDENT (1));
2764 resp = GNUNET_REST_create_response (oidc_jwk_pub_str);
2765 handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
2766 json_decref (oidc_jwk);
2767 GNUNET_free (oidc_jwk_pub_str);
2768 GNUNET_free (oidc_jwk_pub_str);
2769 cleanup_handle (handle);
2770}
2771
2772
2773/**
2774 * If listing is enabled, prints information about the egos.
2775 *
2776 * This function is initially called for all egos and then again
2777 * whenever a ego's identifier changes or if it is deleted. At the
2778 * end of the initial pass over all egos, the function is once called
2779 * with 'NULL' for 'ego'. That does NOT mean that the callback won't
2780 * be invoked in the future or that there was an error.
2781 *
2782 * When used with 'GNUNET_IDENTITY_create' or 'GNUNET_IDENTITY_get', this
2783 * function is only called ONCE, and 'NULL' being passed in 'ego' does
2784 * indicate an error (for example because name is taken or no default value is
2785 * known). If 'ego' is non-NULL and if '*ctx' is set in those callbacks, the
2786 * value WILL be passed to a subsequent call to the identity callback of
2787 * 'GNUNET_IDENTITY_connect' (if that one was not NULL).
2788 *
2789 * When an identity is renamed, this function is called with the
2790 * (known) ego but the NEW identifier.
2791 *
2792 * When an identity is deleted, this function is called with the
2793 * (known) ego and "NULL" for the 'identifier'. In this case,
2794 * the 'ego' is henceforth invalid (and the 'ctx' should also be
2795 * cleaned up).
2796 *
2797 * @param cls closure
2798 * @param ego ego handle
2799 * @param ctx context for application to store data for this ego
2800 * (during the lifetime of this process, initially NULL)
2801 * @param identifier identifier assigned by the user for this ego,
2802 * NULL if the user just deleted the ego and it
2803 * must thus no longer be used
2804 */
2805static void
2806list_ego (void *cls,
2807 struct GNUNET_IDENTITY_Ego *ego,
2808 void **ctx,
2809 const char *identifier)
2810{
2811 struct EgoEntry *ego_entry;
2812 struct GNUNET_CRYPTO_PublicKey pk;
2813
2814 if (NULL == ego)
2815 {
2816 state = ID_REST_STATE_POST_INIT;
2817 return;
2818 }
2819 if (ID_REST_STATE_INIT == state)
2820
2821 {
2822 ego_entry = GNUNET_new (struct EgoEntry);
2823 GNUNET_IDENTITY_ego_get_public_key (ego, &pk);
2824 ego_entry->keystring = GNUNET_CRYPTO_public_key_to_string (&pk);
2825 ego_entry->ego = ego;
2826 ego_entry->identifier = GNUNET_strdup (identifier);
2827 GNUNET_CONTAINER_DLL_insert_tail (ego_head,
2828 ego_tail,
2829 ego_entry);
2830 return;
2831 }
2832 /* Ego renamed or added */
2833 if (identifier != NULL)
2834 {
2835 for (ego_entry = ego_head; NULL != ego_entry;
2836 ego_entry = ego_entry->next)
2837 {
2838 if (ego_entry->ego == ego)
2839 {
2840 /* Rename */
2841 GNUNET_free (ego_entry->identifier);
2842 ego_entry->identifier = GNUNET_strdup (identifier);
2843 break;
2844 }
2845 }
2846 if (NULL == ego_entry)
2847 {
2848 /* Add */
2849 ego_entry = GNUNET_new (struct EgoEntry);
2850 GNUNET_IDENTITY_ego_get_public_key (ego, &pk);
2851 ego_entry->keystring = GNUNET_CRYPTO_public_key_to_string (&pk);
2852 ego_entry->ego = ego;
2853 ego_entry->identifier = GNUNET_strdup (identifier);
2854 GNUNET_CONTAINER_DLL_insert_tail (ego_head,
2855 ego_tail,
2856 ego_entry);
2857 }
2858 }
2859 else
2860 {
2861 /* Delete */
2862 for (ego_entry = ego_head; NULL != ego_entry;
2863 ego_entry = ego_entry->next)
2864 {
2865 if (ego_entry->ego == ego)
2866 break;
2867 }
2868 if (NULL == ego_entry)
2869 return; /* Not found */
2870
2871 GNUNET_CONTAINER_DLL_remove (ego_head,
2872 ego_tail,
2873 ego_entry);
2874 GNUNET_free (ego_entry->identifier);
2875 GNUNET_free (ego_entry->keystring);
2876 GNUNET_free (ego_entry);
2877 return;
2878 }
2879}
2880
2881
2882static void
2883oidc_config_endpoint (struct GNUNET_REST_RequestHandle *con_handle,
2884 const char *url,
2885 void *cls)
2886{
2887 json_t *oidc_config;
2888 json_t *auth_methods;
2889 json_t *sig_algs;
2890 json_t *scopes;
2891 json_t *response_types;
2892 json_t *sub_types;
2893 json_t *claim_types;
2894 char *oidc_config_str;
2895 struct MHD_Response *resp;
2896 struct RequestHandle *handle = cls;
2897
2898 oidc_config = json_object ();
2899 // FIXME get from config?
2900 json_object_set_new (oidc_config,
2901 "issuer", json_string ("http://localhost:7776"));
2902 json_object_set_new (oidc_config,
2903 "authorization_endpoint",
2904 json_string ("https://api.reclaim/openid/authorize"));
2905 json_object_set_new (oidc_config,
2906 "token_endpoint",
2907 json_string ("http://localhost:7776/openid/token"));
2908 auth_methods = json_array ();
2909 json_array_append_new (auth_methods,
2910 json_string ("client_secret_basic"));
2911 json_array_append_new (auth_methods,
2912 json_string ("client_secret_post"));
2913 json_object_set_new (oidc_config,
2914 "token_endpoint_auth_methods_supported",
2915 auth_methods);
2916 sig_algs = json_array ();
2917 json_array_append_new (sig_algs,
2918 json_string ("HS512"));
2919 json_array_append_new (sig_algs,
2920 json_string ("RS256"));
2921 json_object_set_new (oidc_config,
2922 "id_token_signing_alg_values_supported",
2923 sig_algs);
2924 json_object_set_new (oidc_config,
2925 "jwks_uri",
2926 json_string ("http://localhost:7776/jwks.json"));
2927 json_object_set_new (oidc_config,
2928 "userinfo_endpoint",
2929 json_string ("http://localhost:7776/openid/userinfo"));
2930 scopes = json_array ();
2931 json_array_append_new (scopes,
2932 json_string ("openid"));
2933 json_array_append_new (scopes,
2934 json_string ("profile"));
2935 json_array_append_new (scopes,
2936 json_string ("email"));
2937 json_array_append_new (scopes,
2938 json_string ("address"));
2939 json_array_append_new (scopes,
2940 json_string ("phone"));
2941 json_object_set_new (oidc_config,
2942 "scopes_supported",
2943 scopes);
2944 response_types = json_array ();
2945 json_array_append_new (response_types,
2946 json_string ("code"));
2947 json_object_set_new (oidc_config,
2948 "response_types_supported",
2949 response_types);
2950 sub_types = json_array ();
2951 json_array_append_new (sub_types,
2952 json_string ("public")); /* no pairwise support */
2953 json_object_set_new (oidc_config,
2954 "subject_types_supported",
2955 sub_types);
2956 claim_types = json_array ();
2957 json_array_append_new (claim_types,
2958 json_string ("normal"));
2959 json_array_append_new (claim_types,
2960 json_string ("aggregated"));
2961 json_object_set_new (oidc_config,
2962 "claim_types_supported",
2963 claim_types);
2964 json_object_set_new (oidc_config,
2965 "claims_parameter_supported",
2966 json_boolean (1));
2967 oidc_config_str = json_dumps (oidc_config, JSON_INDENT (1));
2968 resp = GNUNET_REST_create_response (oidc_config_str);
2969 handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
2970 json_decref (oidc_config);
2971 GNUNET_free (oidc_config_str);
2972 cleanup_handle (handle);
2973}
2974
2975
2976/**
2977 * Respond to OPTIONS request
2978 *
2979 * @param con_handle the connection handle
2980 * @param url the url
2981 * @param cls the RequestHandle
2982 */
2983static void
2984oidc_config_cors (struct GNUNET_REST_RequestHandle *con_handle,
2985 const char *url,
2986 void *cls)
2987{
2988 struct MHD_Response *resp;
2989 struct RequestHandle *handle = cls;
2990
2991 // For now, independent of path return all options
2992 resp = GNUNET_REST_create_response (NULL);
2993 GNUNET_assert (MHD_NO !=
2994 MHD_add_response_header (resp,
2995 "Access-Control-Allow-Methods",
2996 allow_methods));
2997 GNUNET_assert (MHD_NO !=
2998 MHD_add_response_header (resp,
2999 "Access-Control-Allow-Origin",
3000 "*"));
3001 handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
3002 cleanup_handle (handle);
3003 return;
3004}
3005
3006
3007enum GNUNET_GenericReturnValue
3008REST_openid_process_request (void *plugin,
3009 struct GNUNET_REST_RequestHandle *rest_handle,
3010 GNUNET_REST_ResultProcessor proc,
3011 void *proc_cls)
3012{
3013 struct RequestHandle *handle = GNUNET_new (struct RequestHandle);
3014 struct GNUNET_REST_RequestHandlerError err;
3015 static const struct GNUNET_REST_RequestHandler handlers[] =
3016 { { MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_AUTHORIZE, &authorize_endpoint },
3017 { MHD_HTTP_METHOD_POST,
3018 GNUNET_REST_API_NS_AUTHORIZE, &authorize_endpoint }, // url-encoded
3019 { MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_LOGIN, &login_cont },
3020 { MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_TOKEN, &token_endpoint },
3021 { MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_USERINFO, &userinfo_endpoint },
3022 { MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_USERINFO, &userinfo_endpoint },
3023 { MHD_HTTP_METHOD_GET, GNUNET_REST_API_JWKS, &jwks_endpoint },
3024 { MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_OIDC_CONFIG,
3025 &oidc_config_endpoint },
3026 { MHD_HTTP_METHOD_OPTIONS, GNUNET_REST_API_NS_OIDC_CONFIG,
3027 &oidc_config_cors },
3028 { MHD_HTTP_METHOD_OPTIONS, GNUNET_REST_API_NS_OIDC, &options_cont },
3029 GNUNET_REST_HANDLER_END };
3030
3031 handle->oidc = GNUNET_new (struct OIDC_Variables);
3032 if (NULL == OIDC_cookie_jar_map)
3033 OIDC_cookie_jar_map = GNUNET_CONTAINER_multihashmap_create (10,
3034 GNUNET_NO);
3035 if (NULL == oidc_code_cache)
3036 oidc_code_cache = GNUNET_CONTAINER_multihashmap_create (10,
3037 GNUNET_NO);
3038
3039 handle->response_code = 0;
3040 handle->timeout = GNUNET_TIME_UNIT_FOREVER_REL;
3041 handle->proc_cls = proc_cls;
3042 handle->proc = proc;
3043 handle->rest_handle = rest_handle;
3044 handle->url = GNUNET_strdup (rest_handle->url);
3045 handle->timeout_task =
3046 GNUNET_SCHEDULER_add_delayed (handle->timeout, &do_timeout, handle);
3047 GNUNET_CONTAINER_DLL_insert (requests_head,
3048 requests_tail,
3049 handle);
3050 if (handle->url[strlen (handle->url) - 1] == '/')
3051 handle->url[strlen (handle->url) - 1] = '\0';
3052 if (GNUNET_NO ==
3053 GNUNET_REST_handle_request (handle->rest_handle, handlers, &err, handle))
3054 return GNUNET_NO;
3055
3056 return GNUNET_YES;
3057}
3058
3059
3060/**
3061 * Entry point for the plugin.
3062 *
3063 * @param cls Config info
3064 * @return NULL on error, otherwise the plugin context
3065 */
3066void *
3067REST_openid_init (const struct GNUNET_CONFIGURATION_Handle *c)
3068{
3069 static struct Plugin plugin;
3070 struct GNUNET_REST_Plugin *api;
3071
3072 oid_cfg = c;
3073 if (NULL != plugin.cfg)
3074 return NULL; /* can only initialize once! */
3075 memset (&plugin, 0, sizeof(struct Plugin));
3076 plugin.cfg = oid_cfg;
3077 api = GNUNET_new (struct GNUNET_REST_Plugin);
3078 api->cls = &plugin;
3079 api->name = GNUNET_REST_API_NS_OIDC;
3080 identity_handle = GNUNET_IDENTITY_connect (oid_cfg, &list_ego, NULL);
3081 gns_handle = GNUNET_GNS_connect (oid_cfg);
3082 idp = GNUNET_RECLAIM_connect (oid_cfg);
3083 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (oid_cfg,
3084 "reclaim-rest-plugin",
3085 "OIDC_USERINFO_CONSUME_TIMEOUT",
3086 &consume_timeout))
3087 {
3088 consume_timeout = CONSUME_TIMEOUT;
3089 }
3090
3091
3092 state = ID_REST_STATE_INIT;
3093 GNUNET_asprintf (&allow_methods,
3094 "%s, %s, %s, %s, %s",
3095 MHD_HTTP_METHOD_GET,
3096 MHD_HTTP_METHOD_POST,
3097 MHD_HTTP_METHOD_PUT,
3098 MHD_HTTP_METHOD_DELETE,
3099 MHD_HTTP_METHOD_OPTIONS);
3100
3101 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
3102 _ ("OpenID Connect REST API initialized\n"));
3103 return api;
3104}
3105
3106
3107static int
3108cleanup_hashmap (void *cls, const struct GNUNET_HashCode *key, void *value)
3109{
3110 GNUNET_free (value);
3111 return GNUNET_YES;
3112}
3113
3114
3115/**
3116 * Exit point from the plugin.
3117 *
3118 * @param cls the plugin context (as returned by "init")
3119 * @return always NULL
3120 */
3121void *
3122REST_openid_done (void *cls)
3123{
3124 struct GNUNET_REST_Plugin *api = cls;
3125 struct Plugin *plugin = api->cls;
3126 struct EgoEntry *ego_entry;
3127
3128 plugin->cfg = NULL;
3129 while (NULL != requests_head)
3130 cleanup_handle (requests_head);
3131 if (NULL != OIDC_cookie_jar_map)
3132 {
3133 GNUNET_CONTAINER_multihashmap_iterate (OIDC_cookie_jar_map,
3134 &cleanup_hashmap,
3135 NULL);
3136 GNUNET_CONTAINER_multihashmap_destroy (OIDC_cookie_jar_map);
3137 }
3138 if (NULL != oidc_code_cache)
3139 {
3140 GNUNET_CONTAINER_multihashmap_iterate (oidc_code_cache,
3141 &cleanup_hashmap,
3142 NULL);
3143 GNUNET_CONTAINER_multihashmap_destroy (oidc_code_cache);
3144 }
3145
3146 GNUNET_free (allow_methods);
3147 if (NULL != gns_handle)
3148 GNUNET_GNS_disconnect (gns_handle);
3149 if (NULL != identity_handle)
3150 GNUNET_IDENTITY_disconnect (identity_handle);
3151 if (NULL != idp)
3152 GNUNET_RECLAIM_disconnect (idp);
3153 while (NULL != (ego_entry = ego_head))
3154 {
3155 GNUNET_CONTAINER_DLL_remove (ego_head,
3156 ego_tail,
3157 ego_entry);
3158 GNUNET_free (ego_entry->identifier);
3159 GNUNET_free (ego_entry->keystring);
3160 GNUNET_free (ego_entry);
3161 }
3162 GNUNET_free (api);
3163 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
3164 "OpenID Connect REST plugin is finished\n");
3165 return NULL;
3166}
3167
3168
3169/* end of plugin_rest_openid_connect.c */