From 8562992bff092c27d6f589667c74659831da364c Mon Sep 17 00:00:00 2001 From: Martin Schanzenbach Date: Mon, 22 Feb 2021 15:21:15 +0100 Subject: -add pabc rest api for credential requests --- src/reclaim/Makefile.am | 20 ++ src/reclaim/plugin_rest_pabc.c | 560 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 580 insertions(+) create mode 100644 src/reclaim/plugin_rest_pabc.c diff --git a/src/reclaim/Makefile.am b/src/reclaim/Makefile.am index 66e185b5a..6ad842789 100644 --- a/src/reclaim/Makefile.am +++ b/src/reclaim/Makefile.am @@ -18,6 +18,7 @@ CREDENTIAL_PLUGIN = \ if HAVE_PABC CREDENTIAL_PLUGIN += libgnunet_plugin_reclaim_credential_pabc.la + REST_PLUGIN += libgnunet_plugin_rest_pabc.la endif EXTRA_DIST = \ @@ -91,6 +92,25 @@ libgnunet_plugin_rest_openid_connect_la_LDFLAGS = \ $(GN_PLUGIN_LDFLAGS) libgnunet_plugin_rest_openid_connect_la_CFLAGS = $(MHD_CFLAGS) $(AM_CFLAGS) +if HAVE_PABC +libgnunet_plugin_rest_pabc_la_SOURCES = \ + plugin_rest_pabc.c +libgnunet_plugin_rest_pabc_la_LIBADD = \ + libgnunetreclaim.la \ + $(top_builddir)/src/json/libgnunetjson.la \ + $(top_builddir)/src/rest/libgnunetrest.la \ + $(top_builddir)/src/util/libgnunetutil.la $(XLIBS) \ + $(LTLIBINTL) -ljansson -lpabc $(MHD_LIBS) +libgnunet_plugin_rest_pabc_la_DEPENDENCIES = \ + libgnunetreclaim.la \ + $(top_builddir)/src/json/libgnunetjson.la \ + $(top_builddir)/src/rest/libgnunetrest.la \ + $(top_builddir)/src/util/libgnunetutil.la +libgnunet_plugin_rest_pabc_la_LDFLAGS = \ + $(GN_PLUGIN_LDFLAGS) +libgnunet_plugin_rest_pabc_la_CFLAGS = $(MHD_CFLAGS) $(AM_CFLAGS) +endif + libgnunet_plugin_gnsrecord_reclaim_la_SOURCES = \ plugin_gnsrecord_reclaim.c diff --git a/src/reclaim/plugin_rest_pabc.c b/src/reclaim/plugin_rest_pabc.c new file mode 100644 index 000000000..c3bb8847f --- /dev/null +++ b/src/reclaim/plugin_rest_pabc.c @@ -0,0 +1,560 @@ +/* + This file is part of GNUnet. + Copyright (C) 2012-2015 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ +/** + * @author Martin Schanzenbach + * @file reclaim/plugin_rest_pabc.c + * @brief GNUnet pabc REST plugin + * + */ +#include "platform.h" +#include "microhttpd.h" +#include +#include +#include +#include "gnunet_reclaim_lib.h" +#include "gnunet_reclaim_service.h" +#include "gnunet_rest_lib.h" +#include "gnunet_rest_plugin.h" +#include "gnunet_signatures.h" + +/** + * REST root namespace + */ +#define GNUNET_REST_API_NS_PABC "/pabc" + +/** + * Credential request endpoint + */ +#define GNUNET_REST_API_NS_PABC_CR "/pabc/cr" + +/** + * The configuration handle + */ +const struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * HTTP methods allows for this plugin + */ +static char *allow_methods; + +/** + * @brief struct returned by the initialization function of the plugin + */ +struct Plugin +{ + const struct GNUNET_CONFIGURATION_Handle *cfg; +}; + + +struct RequestHandle +{ + /** + * DLL + */ + struct RequestHandle *next; + + /** + * DLL + */ + struct RequestHandle *prev; + + /** + * Rest connection + */ + struct GNUNET_REST_RequestHandle *rest_handle; + + /** + * Desired timeout for the lookup (default is no timeout). + */ + struct GNUNET_TIME_Relative timeout; + + /** + * ID of a task associated with the resolution process. + */ + struct GNUNET_SCHEDULER_Task *timeout_task; + + /** + * The plugin result processor + */ + GNUNET_REST_ResultProcessor proc; + + /** + * The closure of the result processor + */ + void *proc_cls; + + /** + * The url + */ + char *url; + + /** + * Error response message + */ + char *emsg; + + /** + * Reponse code + */ + int response_code; + + /** + * Response object + */ + json_t *resp_object; +}; + +/** + * DLL + */ +static struct RequestHandle *requests_head; + +/** + * DLL + */ +static struct RequestHandle *requests_tail; + + +/** + * Cleanup lookup handle + * @param handle Handle to clean up + */ +static void +cleanup_handle (void *cls) +{ + struct RequestHandle *handle = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Cleaning up\n"); + if (NULL != handle->resp_object) + json_decref (handle->resp_object); + if (NULL != handle->timeout_task) + GNUNET_SCHEDULER_cancel (handle->timeout_task); + if (NULL != handle->url) + GNUNET_free (handle->url); + if (NULL != handle->emsg) + GNUNET_free (handle->emsg); + GNUNET_CONTAINER_DLL_remove (requests_head, + requests_tail, + handle); + GNUNET_free (handle); +} + + +/** + * Task run on error, sends error message. Cleans up everything. + * + * @param cls the `struct RequestHandle` + */ +static void +do_error (void *cls) +{ + struct RequestHandle *handle = cls; + struct MHD_Response *resp; + char *json_error; + + GNUNET_asprintf (&json_error, "{ \"error\" : \"%s\" }", handle->emsg); + if (0 == handle->response_code) + { + handle->response_code = MHD_HTTP_BAD_REQUEST; + } + resp = GNUNET_REST_create_response (json_error); + MHD_add_response_header (resp, "Content-Type", "application/json"); + handle->proc (handle->proc_cls, resp, handle->response_code); + cleanup_handle (handle); + GNUNET_free (json_error); +} + + +/** + * Task run on timeout, sends error message. Cleans up everything. + * + * @param cls the `struct RequestHandle` + */ +static void +do_timeout (void *cls) +{ + struct RequestHandle *handle = cls; + + handle->timeout_task = NULL; + do_error (handle); +} + + +static void +return_response (void *cls) +{ + char *result_str; + struct RequestHandle *handle = cls; + struct MHD_Response *resp; + + result_str = json_dumps (handle->resp_object, 0); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Result %s\n", result_str); + resp = GNUNET_REST_create_response (result_str); + MHD_add_response_header (resp, "Access-Control-Allow-Methods", allow_methods); + handle->proc (handle->proc_cls, resp, MHD_HTTP_OK); + GNUNET_free (result_str); + cleanup_handle (handle); +} + +static enum pabc_status +set_attributes_from_idtoken (const struct pabc_context *ctx, + const struct pabc_public_parameters *pp, + struct pabc_user_context *usr_ctx, + const char *id_token) +{ + json_t *payload_json; + json_t *value; + const char *key; + enum pabc_status status; + + //FIXME parse JWT + json_object_foreach(payload_json, key, value) + { + //FIXME skip metadata in JWT, map attributes to PP + status = pabc_set_attribute_value_by_name (ctx, pp, usr_ctx, + key, + json_string_value (value)); + if (PABC_OK != status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to set attribute.\n"); + return PABC_FAILURE; + } + } + return PABC_OK; +} + + +static void +cr_cont (struct GNUNET_REST_RequestHandle *con_handle, + const char *url, + void *cls) +{ + struct RequestHandle *handle = cls; + char term_data[handle->rest_handle->data_size + 1]; + char *response_str; + json_t *data_json; + json_t *nonce_json; + json_t *pp_json; + json_t *idtoken_json; + json_error_t err; + struct pabc_public_parameters *pp = NULL; + struct pabc_context *ctx = NULL; + struct pabc_user_context *usr_ctx = NULL; + struct pabc_credential_request *cr = NULL; + struct pabc_nonce *nonce = NULL; + enum pabc_status status; + + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Credential request...\n"); + + if (0 >= handle->rest_handle->data_size) + { + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + + term_data[handle->rest_handle->data_size] = '\0'; + GNUNET_memcpy (term_data, + handle->rest_handle->data, + handle->rest_handle->data_size); + data_json = json_loads (term_data, JSON_DECODE_ANY, &err); + if (NULL == data_json) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unable to parse %s\n", term_data); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + if (!json_is_object (data_json)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unable to parse %s\n", term_data); + json_decref (data_json); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + + nonce_json = json_object_get (data_json, "nonce"); + if (NULL == nonce_json) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unable to parse nonce\n"); + json_decref (data_json); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + idtoken_json = json_object_get (idtoken_json, "id_token"); + if (NULL == idtoken_json) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unable to parse id_token\n"); + json_decref (data_json); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + pp_json = json_object_get (data_json, "public_params"); + if (NULL == pp_json) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unable to parse public parameters\n"); + json_decref (data_json); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + + PABC_ASSERT (pabc_new_ctx (&ctx)); + // load stuff FIXME: Needs helper + //status = load_public_parameters (ctx, pp_name, &pp); + if (status != PABC_OK) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to read public parameters.\n"); + json_decref (data_json); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + + /*FIXME: Needs helper + * status = read_usr_ctx (usr_name, pp_name, ctx, pp, &usr_ctx); + */ + if (PABC_OK != status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to read user context.\n"); + pabc_free_public_parameters (ctx, &pp); + json_decref (data_json); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + + // Set attributes from JWT to context + status = set_attributes_from_idtoken (ctx, + pp, + usr_ctx, + json_string_value (idtoken_json)); + if (status != PABC_OK) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to set attributes.\n"); + pabc_free_user_context (ctx, pp, &usr_ctx); + pabc_free_public_parameters (ctx, &pp); + json_decref (data_json); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + + + // nonce + status = pabc_new_nonce (ctx, &nonce); + if (status != PABC_OK) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to allocate nonce.\n"); + pabc_free_user_context (ctx, pp, &usr_ctx); + pabc_free_public_parameters (ctx, &pp); + json_decref (data_json); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + //FIXME: where does this come from??? + status = pabc_decode_nonce (ctx, nonce, json_string_value (nonce_json)); + if (status != PABC_OK) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to decode nonce.\n"); + pabc_free_nonce (ctx, &nonce); + pabc_free_user_context (ctx, pp, &usr_ctx); + pabc_free_public_parameters (ctx, &pp); + json_decref (data_json); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + + // cr + status = pabc_new_credential_request (ctx, pp, &cr); + if (PABC_OK != status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to allocate cr.\n"); + pabc_free_nonce (ctx, &nonce); + pabc_free_user_context (ctx, pp, &usr_ctx); + pabc_free_public_parameters (ctx, &pp); + json_decref (data_json); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + + status = pabc_gen_credential_request (ctx, pp, usr_ctx, nonce, cr); + if (PABC_OK != status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to generate cr.\n"); + pabc_free_nonce (ctx, &nonce); + pabc_free_credential_request (ctx, pp, &cr); + pabc_free_user_context (ctx, pp, &usr_ctx); + pabc_free_public_parameters (ctx, &pp); + json_decref (data_json); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + handle->resp_object = json_object (); + pabc_encode_credential_request (ctx, pp, cr, &response_str); + if (PABC_OK != status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to serialize cr.\n"); + pabc_free_nonce (ctx, &nonce); + pabc_free_credential_request (ctx, pp, &cr); + pabc_free_user_context (ctx, pp, &usr_ctx); + pabc_free_public_parameters (ctx, &pp); + json_decref (data_json); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "%s\n", response_str); + json_object_set_new (handle->resp_object, "cr", + json_string (response_str)); + GNUNET_free (response_str); + + // clean up + pabc_free_nonce (ctx, &nonce); + pabc_free_credential_request (ctx, pp, &cr); + pabc_free_user_context (ctx, pp, &usr_ctx); + pabc_free_public_parameters (ctx, &pp); + GNUNET_SCHEDULER_add_now (&return_response, handle); + json_decref (data_json); +} + + +/** + * Respond to OPTIONS request + * + * @param con_handle the connection handle + * @param url the url + * @param cls the RequestHandle + */ +static void +options_cont (struct GNUNET_REST_RequestHandle *con_handle, + const char *url, + void *cls) +{ + struct MHD_Response *resp; + struct RequestHandle *handle = cls; + + // For now, independent of path return all options + resp = GNUNET_REST_create_response (NULL); + MHD_add_response_header (resp, "Access-Control-Allow-Methods", allow_methods); + handle->proc (handle->proc_cls, resp, MHD_HTTP_OK); + cleanup_handle (handle); + return; +} + + +static enum GNUNET_GenericReturnValue +rest_identity_process_request (struct GNUNET_REST_RequestHandle *rest_handle, + GNUNET_REST_ResultProcessor proc, + void *proc_cls) +{ + struct RequestHandle *handle = GNUNET_new (struct RequestHandle); + struct GNUNET_REST_RequestHandlerError err; + static const struct GNUNET_REST_RequestHandler handlers[] = + { + {MHD_HTTP_METHOD_POST, + GNUNET_REST_API_NS_PABC_CR, &cr_cont }, + { MHD_HTTP_METHOD_OPTIONS, GNUNET_REST_API_NS_PABC, &options_cont }, + GNUNET_REST_HANDLER_END + }; + + handle->response_code = 0; + handle->timeout = GNUNET_TIME_UNIT_FOREVER_REL; + handle->proc_cls = proc_cls; + handle->proc = proc; + handle->rest_handle = rest_handle; + + handle->url = GNUNET_strdup (rest_handle->url); + if (handle->url[strlen (handle->url) - 1] == '/') + handle->url[strlen (handle->url) - 1] = '\0'; + handle->timeout_task = + GNUNET_SCHEDULER_add_delayed (handle->timeout, &do_timeout, handle); + GNUNET_CONTAINER_DLL_insert (requests_head, + requests_tail, + handle); + if (GNUNET_NO == + GNUNET_REST_handle_request (handle->rest_handle, handlers, &err, handle)) + { + cleanup_handle (handle); + return GNUNET_NO; + } + + return GNUNET_YES; +} + + +/** + * Entry point for the plugin. + * + * @param cls Config info + * @return NULL on error, otherwise the plugin context + */ +void * +libgnunet_plugin_rest_reclaim_init (void *cls) +{ + static struct Plugin plugin; + struct GNUNET_REST_Plugin *api; + + cfg = cls; + if (NULL != plugin.cfg) + return NULL; /* can only initialize once! */ + memset (&plugin, 0, sizeof(struct Plugin)); + plugin.cfg = cfg; + api = GNUNET_new (struct GNUNET_REST_Plugin); + api->cls = &plugin; + api->name = GNUNET_REST_API_NS_PABC; + api->process_request = &rest_identity_process_request; + GNUNET_asprintf (&allow_methods, + "%s, %s", + MHD_HTTP_METHOD_POST, + MHD_HTTP_METHOD_OPTIONS); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + _ ("Identity Provider REST API initialized\n")); + return api; +} + + +/** + * Exit point from the plugin. + * + * @param cls the plugin context (as returned by "init") + * @return always NULL + */ +void * +libgnunet_plugin_rest_reclaim_done (void *cls) +{ + struct GNUNET_REST_Plugin *api = cls; + struct Plugin *plugin = api->cls; + struct RequestHandle *request; + + plugin->cfg = NULL; + while (NULL != (request = requests_head)) + do_error (request); + + GNUNET_free (allow_methods); + GNUNET_free (api); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "PABC REST plugin is finished\n"); + return NULL; +} + + +/* end of plugin_rest_reclaim.c */ -- cgit v1.2.3