/*
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"
#include "pabc_helper.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;
/**
* Response 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;
json_error_t json_err;
const char *key;
const char *jwt_body;
char *decoded_jwt;
char delim[] = ".";
char *jwt_string;
const char *pabc_key;
enum pabc_status status;
// FIXME parse JWT
jwt_string = GNUNET_strndup (id_token, strlen (id_token));
jwt_body = strtok (jwt_string, delim);
jwt_body = strtok (NULL, delim);
GNUNET_STRINGS_base64url_decode (jwt_body, strlen (jwt_body),
(void **) &decoded_jwt);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Decoded ID Token: %s\n", decoded_jwt);
payload_json = json_loads (decoded_jwt, JSON_DECODE_ANY, &json_err);
GNUNET_free (decoded_jwt);
json_object_foreach (payload_json, key, value)
{
pabc_key = key;
if (0 == strcmp ("iss", key))
pabc_key = "issuer"; // rename
if (0 == strcmp ("sub", key))
pabc_key = "subject"; // rename
if (0 == strcmp ("jti", key))
continue;
if (0 == strcmp ("exp", key))
pabc_key = "expiration"; // rename
if (0 == strcmp ("iat", key))
continue;
if (0 == strcmp ("nbf", key))
continue;
if (0 == strcmp ("aud", key))
continue;
char *tmp_val;
if (json_is_string (value))
tmp_val = GNUNET_strdup (json_string_value (value));
else
tmp_val = json_dumps (value, JSON_ENCODE_ANY);
if (NULL == tmp_val)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unable to encode JSON value for `%s'\n", key);
continue;
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Setting `%s' to `%s'\n", key, tmp_val);
status = pabc_set_attribute_value_by_name (ctx, pp, usr_ctx,
pabc_key,
tmp_val);
GNUNET_free (tmp_val);
if (PABC_OK != status)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to set attribute `%s'.\n", key);
}
}
GNUNET_free (jwt_string);
return PABC_OK;
}
static enum GNUNET_GenericReturnValue
setup_new_user_context (struct pabc_context *ctx,
struct pabc_public_parameters *pp,
struct pabc_user_context **usr_ctx)
{
if (PABC_OK != pabc_new_user_context (ctx, pp, usr_ctx))
return GNUNET_SYSERR;
if (PABC_OK != pabc_populate_user_context (ctx, *usr_ctx))
{
pabc_free_user_context (ctx, pp, usr_ctx);
return GNUNET_SYSERR;
}
return GNUNET_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_t *iss_json;
json_t *identity_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;
}
iss_json = json_object_get (data_json, "issuer");
if (NULL == iss_json)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unable to parse issuer\n");
json_decref (data_json);
GNUNET_SCHEDULER_add_now (&do_error, handle);
return;
}
identity_json = json_object_get (data_json, "identity");
if (NULL == identity_json)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unable to parse identity\n");
json_decref (data_json);
GNUNET_SCHEDULER_add_now (&do_error, handle);
return;
}
idtoken_json = json_object_get (data_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));
char *pp_str = json_dumps (pp_json, JSON_ENCODE_ANY);
status = pabc_decode_and_new_public_parameters (ctx,
&pp,
pp_str);
char *ppid;
GNUNET_assert (PABC_OK == pabc_cred_get_ppid_from_pp (pp_str, &ppid));
GNUNET_free (pp_str);
if (status != PABC_OK)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to read public parameters: %s\n",
pp_str);
json_decref (data_json);
GNUNET_SCHEDULER_add_now (&do_error, handle);
return;
}
// (Over)write parameters
status = PABC_write_public_parameters (json_string_value (iss_json),
pp);
if (status != PABC_OK)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to write public parameters.\n");
json_decref (data_json);
GNUNET_SCHEDULER_add_now (&do_error, handle);
return;
}
status = PABC_read_usr_ctx (json_string_value (identity_json),
json_string_value (iss_json),
ctx, pp, &usr_ctx);
if (PABC_OK != status)
{
if (GNUNET_OK != setup_new_user_context (ctx, pp, &usr_ctx))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to setup user context.\n");
pabc_free_public_parameters (ctx, &pp);
json_decref (data_json);
GNUNET_SCHEDULER_add_now (&do_error, handle);
return;
}
PABC_write_usr_ctx (json_string_value (identity_json),
json_string_value (iss_json),
ctx, pp, usr_ctx);
}
// 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;
}
char *nonce_str = json_dumps (nonce_json, JSON_ENCODE_ANY);
status = pabc_decode_nonce (ctx, nonce, nonce_str);
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 ();
GNUNET_assert (PABC_OK == pabc_cred_encode_cr (ctx, pp, cr,
json_string_value (
identity_json),
ppid, &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;
}
json_decref (handle->resp_object);
handle->resp_object = json_loads (response_str, JSON_DECODE_ANY, &err);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "%s\n", 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_pabc_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 */