You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
- SPDX-License-Identifier: AGPL3.0-or-later
- */
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
/**
* @author Martin Schanzenbach
* @author Philippe Buschmann
*/
#define OIDC_NONCE_KEY "nonce"
+/**
+ * OIDC PKCE code challenge
+ */
+#define OIDC_CODE_CHALLENGE_KEY "code_challenge"
+
+/**
+ * OIDC PKCE code verifier
+ */
+#define OIDC_CODE_VERIFIER_KEY "code_verifier"
+
/**
* OIDC cookie expiration (in seconds)
*/
/**
* OIDC ignored parameter array
*/
-static char *OIDC_ignored_parameter_array[] = {"display",
- "prompt",
- "ui_locales",
- "response_mode",
- "id_token_hint",
- "login_hint",
- "acr_values"};
+static char *OIDC_ignored_parameter_array[] = { "display",
+ "prompt",
+ "ui_locales",
+ "response_mode",
+ "id_token_hint",
+ "login_hint",
+ "acr_values" };
/**
* OIDC Hash map that keeps track of issued cookies
*/
int user_cancelled;
+ /**
+ * The PKCE code_challenge
+ */
+ char *code_challenge;
+
+ /**
+ * The PKCE code_verifier
+ */
+ char *code_verifier;
+
/**
* The response JSON
*/
struct GNUNET_RECLAIM_ATTRIBUTE_ClaimListEntry *claim_tmp;
struct EgoEntry *ego_entry;
struct EgoEntry *ego_tmp;
+
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Cleaning up\n");
if (NULL != handle->timeout_task)
GNUNET_SCHEDULER_cancel (handle->timeout_task);
GNUNET_free (ego_tmp->keystring);
GNUNET_free (ego_tmp);
}
- GNUNET_free_non_null (handle->attr_it);
GNUNET_free (handle);
}
struct RequestHandle *handle = cls;
struct MHD_Response *resp;
char *redirect;
+
GNUNET_asprintf (&redirect,
"%s?error=%s&error_description=%s%s%s",
handle->oidc->redirect_uri,
strlen (OIDC_COOKIE_HEADER_KEY),
&cache_key);
if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle
- ->header_param_map,
+ ->header_param_map,
&cache_key))
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No cookie found\n");
value = strtok (token, OIDC_COOKIE_HEADER_INFORMATION_KEY);
GNUNET_assert (NULL != value);
handle->oidc->login_identity = GNUNET_strdup (value);
+ GNUNET_free (cookies);
}
/**
&login_base_url))
{
GNUNET_asprintf (&new_redirect,
- "%s?%s=%s&%s=%s&%s=%s&%s=%s&%s=%s&%s=%s",
+ "%s?%s=%s&%s=%s&%s=%s&%s=%s&%s=%s&%s=%s&%s=%s",
login_base_url,
OIDC_RESPONSE_TYPE_KEY,
handle->oidc->response_type,
handle->oidc->scope,
OIDC_STATE_KEY,
(NULL != handle->oidc->state) ? handle->oidc->state : "",
+ OIDC_CODE_CHALLENGE_KEY,
+ (NULL != handle->oidc->code_challenge) ?
+ handle->oidc->code_challenge : "",
OIDC_NONCE_KEY,
(NULL != handle->oidc->nonce) ? handle->oidc->nonce : "");
resp = GNUNET_REST_create_response ("");
oidc_iteration_error (void *cls)
{
struct RequestHandle *handle = cls;
+
handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_SERVER_ERROR);
handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
GNUNET_SCHEDULER_add_now (&do_error, handle);
char *code_string;
handle->idp_op = NULL;
- handle->ticket = *ticket;
if (NULL == ticket)
{
handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_SERVER_ERROR);
GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
return;
}
+ handle->ticket = *ticket;
ticket_str =
GNUNET_STRINGS_data_to_string_alloc (&handle->ticket,
- sizeof (struct GNUNET_RECLAIM_Ticket));
+ sizeof(struct GNUNET_RECLAIM_Ticket));
// TODO change if more attributes are needed (see max_age)
code_string = OIDC_build_authz_code (&handle->priv_key,
&handle->ticket,
handle->attr_list,
- handle->oidc->nonce);
+ handle->oidc->nonce,
+ handle->oidc->code_challenge);
if ((NULL != handle->redirect_prefix) && (NULL != handle->redirect_suffix) &&
(NULL != handle->tld))
{
-
GNUNET_asprintf (&redirect_uri,
"%s.%s/%s?%s=%s&state=%s",
handle->redirect_prefix,
oidc_collect_finished_cb (void *cls)
{
struct RequestHandle *handle = cls;
+
handle->attr_it = NULL;
handle->ticket_it = NULL;
if (NULL == handle->attr_list->list_head)
{
if (GNUNET_OK !=
GNUNET_CRYPTO_ecdsa_public_key_from_string (handle->oidc
- ->login_identity,
+ ->login_identity,
strlen (
handle->oidc
- ->login_identity),
+ ->login_identity),
&pubkey))
{
handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_COOKIE);
if (NULL == strstr (tmp, handle->oidc->client_id))
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Redirect uri %s does not contain client_id %s",
+ "Redirect uri %s does not contain client_id %s\n",
tmp,
handle->oidc->client_id);
}
else
{
-
pos = strrchr (tmp, (unsigned char) '.');
+ if (NULL == pos)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Redirect uri %s contains client_id but is malformed\n",
+ tmp);
+ GNUNET_free (tmp);
+ continue;
+ }
*pos = '\0';
handle->redirect_prefix = GNUNET_strdup (tmp);
tmp_key_str = pos + 1;
pos = strchr (tmp_key_str, (unsigned char) '/');
+ if (NULL == pos)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Redirect uri %s contains client_id but is malformed\n",
+ tmp);
+ GNUNET_free (tmp);
+ continue;
+ }
*pos = '\0';
handle->redirect_suffix = GNUNET_strdup (pos + 1);
GNUNET_STRINGS_string_to_data (tmp_key_str,
strlen (tmp_key_str),
&redirect_zone,
- sizeof (redirect_zone));
+ sizeof(redirect_zone));
}
GNUNET_SCHEDULER_add_now (&build_redirect, handle);
GNUNET_free (tmp);
{
struct GNUNET_HashCode hc;
char *value;
+
GNUNET_CRYPTO_hash (key, strlen (key), &hc);
if (GNUNET_YES != GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle
- ->url_param_map,
+ ->url_param_map,
&hc))
return NULL;
value =
// TODO check other values if needed
number_of_ignored_parameter =
- sizeof (OIDC_ignored_parameter_array) / sizeof (char *);
+ sizeof(OIDC_ignored_parameter_array) / sizeof(char *);
for (iterator = 0; iterator < number_of_ignored_parameter; iterator++)
{
GNUNET_CRYPTO_hash (OIDC_ignored_parameter_array[iterator],
&cache_key);
if (GNUNET_YES ==
GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle
- ->url_param_map,
+ ->url_param_map,
&cache_key))
{
handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_ACCESS_DENIED);
return;
}
+ // OPTIONAL value: code_challenge
+ handle->oidc->code_challenge = get_url_parameter_copy (handle,
+ OIDC_CODE_CHALLENGE_KEY);
+ if (NULL == handle->oidc->code_challenge)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "OAuth authorization request does not contain PKCE parameters!\n");
+ }
+
if (GNUNET_OK !=
GNUNET_CRYPTO_ecdsa_public_key_from_string (handle->oidc->client_id,
strlen (
return;
}
- if (NULL == handle->ego_head)
- {
- handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_SERVER_ERROR);
- handle->edesc = GNUNET_strdup ("Egos are missing");
- handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- GNUNET_SCHEDULER_add_now (&do_error, handle);
- return;
- }
-
- handle->ego_entry = handle->ego_head;
- handle->priv_key =
- *GNUNET_IDENTITY_ego_get_private_key (handle->ego_head->ego);
// If we know this identity, translated the corresponding TLD
// TODO: We might want to have a reverse lookup functionality for TLDs?
for (tmp_ego = handle->ego_head; NULL != tmp_ego; tmp_ego = tmp_ego->next)
json_error_t error;
json_t *identity;
char term_data[handle->rest_handle->data_size + 1];
+
term_data[handle->rest_handle->data_size] = '\0';
GNUNET_memcpy (term_data,
handle->rest_handle->data,
strlen (OIDC_AUTHORIZATION_HEADER_KEY),
&cache_key);
if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle
- ->header_param_map,
+ ->header_param_map,
&cache_key))
{
handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_CLIENT);
// split header in "Basic" and [content]
credentials = strtok (authorization, " ");
- if (0 != strcmp ("Basic", credentials))
+ if ((NULL == credentials) || (0 != strcmp ("Basic", credentials)))
{
handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_CLIENT);
handle->response_code = MHD_HTTP_UNAUTHORIZED;
}
// check client_id
- for (handle->ego_entry = handle->ego_head;
- NULL != handle->ego_entry;
+ for (handle->ego_entry = handle->ego_head; NULL != handle->ego_entry;
handle->ego_entry = handle->ego_entry->next)
{
if (0 == strcmp (handle->ego_entry->keystring, client_id))
GNUNET_STRINGS_string_to_data (client_id,
strlen (client_id),
cid,
- sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
+ sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey));
GNUNET_free (basic_authorization);
return GNUNET_OK;
}
-static int
-ego_exists (struct RequestHandle *handle,
- struct GNUNET_CRYPTO_EcdsaPublicKey *test_key)
+const struct EgoEntry *
+find_ego (struct RequestHandle *handle,
+ struct GNUNET_CRYPTO_EcdsaPublicKey *test_key)
{
struct EgoEntry *ego_entry;
struct GNUNET_CRYPTO_EcdsaPublicKey pub_key;
{
GNUNET_IDENTITY_ego_get_public_key (ego_entry->ego, &pub_key);
if (0 == GNUNET_memcmp (&pub_key, test_key))
- break;
+ return ego_entry;
}
- if (NULL == ego_entry)
- return GNUNET_NO;
- return GNUNET_YES;
+ return NULL;
}
static void
GNUNET_CRYPTO_hash (access_token, strlen (access_token), &hc);
ticketbuf = GNUNET_new (struct GNUNET_RECLAIM_Ticket);
*ticketbuf = *ticket;
- GNUNET_CONTAINER_multihashmap_put (
- OIDC_access_token_map,
- &hc,
- ticketbuf,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
+ GNUNET_assert (GNUNET_SYSERR !=
+ GNUNET_CONTAINER_multihashmap_put (
+ OIDC_access_token_map,
+ &hc,
+ ticketbuf,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
}
/**
void *cls)
{
struct RequestHandle *handle = cls;
+ const struct EgoEntry *ego_entry;
struct GNUNET_TIME_Relative expiration_time;
struct GNUNET_RECLAIM_ATTRIBUTE_ClaimList *cl;
struct GNUNET_RECLAIM_Ticket ticket;
struct GNUNET_CRYPTO_EcdsaPublicKey cid;
+ const struct GNUNET_CRYPTO_EcdsaPrivateKey *privkey;
struct GNUNET_HashCode cache_key;
struct MHD_Response *resp;
char *grant_type;
char *access_token;
char *jwt_secret;
char *nonce;
+ char *code_verifier;
/*
* Check Authorization
{
handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_UNSUPPORTED_GRANT_TYPE);
handle->response_code = MHD_HTTP_BAD_REQUEST;
+ GNUNET_free (grant_type);
GNUNET_SCHEDULER_add_now (&do_error, handle);
return;
}
-
+ GNUNET_free (grant_type);
// REQUIRED code
code = get_url_parameter_copy (handle, OIDC_CODE_KEY);
if (NULL == code)
GNUNET_SCHEDULER_add_now (&do_error, handle);
return;
}
+ ego_entry = find_ego (handle, &cid);
+ if (NULL == ego_entry)
+ {
+ handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST);
+ handle->edesc = GNUNET_strdup ("Unknown client");
+ handle->response_code = MHD_HTTP_BAD_REQUEST;
+ GNUNET_free (code);
+ GNUNET_SCHEDULER_add_now (&do_error, handle);
+ return;
+ }
+ privkey = GNUNET_IDENTITY_ego_get_private_key (ego_entry->ego);
+
+ // REQUIRED code verifier
+ code_verifier = get_url_parameter_copy (handle, OIDC_CODE_VERIFIER_KEY);
+ if (NULL == code_verifier)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "OAuth authorization request does not contain PKCE parameters!\n");
+
+ }
// decode code
- if (GNUNET_OK != OIDC_parse_authz_code (&cid, code, &ticket, &cl, &nonce))
+ if (GNUNET_OK != OIDC_parse_authz_code (privkey, code, code_verifier, &ticket,
+ &cl, &nonce))
{
handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST);
handle->edesc = GNUNET_strdup ("invalid code");
handle->response_code = MHD_HTTP_BAD_REQUEST;
+ GNUNET_free (code);
GNUNET_SCHEDULER_add_now (&do_error, handle);
return;
}
+ GNUNET_free (code);
// create jwt
if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (cfg,
// TODO OPTIONAL acr,amr,azp
- if (GNUNET_NO == ego_exists (handle, &ticket.audience))
- {
- handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST);
- handle->edesc = GNUNET_strdup ("invalid code...");
- handle->response_code = MHD_HTTP_BAD_REQUEST;
- GNUNET_SCHEDULER_add_now (&do_error, handle);
- }
if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg,
"reclaim-rest-plugin",
"jwt_secret",
char *authorization_type;
char *authorization_access_token;
struct GNUNET_RECLAIM_Ticket *ticket;
+ const struct EgoEntry *ego_entry;
const struct GNUNET_CRYPTO_EcdsaPrivateKey *privkey;
- struct GNUNET_CRYPTO_EcdsaPublicKey pk;
-
GNUNET_CRYPTO_hash (OIDC_AUTHORIZATION_HEADER_KEY,
strlen (OIDC_AUTHORIZATION_HEADER_KEY),
&cache_key);
if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle
- ->header_param_map,
+ ->header_param_map,
&cache_key))
{
handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_TOKEN);
// split header in "Bearer" and access_token
authorization = GNUNET_strdup (authorization);
authorization_type = strtok (authorization, delimiter);
- if (0 != strcmp ("Bearer", authorization_type))
+ if ((NULL == authorization_type) ||
+ (0 != strcmp ("Bearer", authorization_type)))
{
handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_TOKEN);
handle->edesc = GNUNET_strdup ("No Access Token");
ticket =
GNUNET_CONTAINER_multihashmap_get (OIDC_access_token_map, &cache_key);
GNUNET_assert (NULL != ticket);
-
- for (handle->ego_entry = handle->ego_head; NULL != handle->ego_entry;
- handle->ego_entry = handle->ego_entry->next)
- {
- GNUNET_IDENTITY_ego_get_public_key (handle->ego_entry->ego, &pk);
- if (0 == GNUNET_memcmp (&pk, &ticket->audience))
- break; // Found
- }
- if (NULL == handle->ego_entry)
+ ego_entry = find_ego (handle, &ticket->audience);
+ if (NULL == ego_entry)
{
handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_TOKEN);
handle->edesc = GNUNET_strdup ("The access token expired");
handle->oidc->response = json_object ();
json_object_set_new (handle->oidc->response,
"sub",
- json_string (handle->ego_entry->keystring));
- privkey = GNUNET_IDENTITY_ego_get_private_key (handle->ego_entry->ego);
-
+ json_string (ego_entry->keystring));
+ privkey = GNUNET_IDENTITY_ego_get_private_key (ego_entry->ego);
handle->idp_op = GNUNET_RECLAIM_ticket_consume (handle->idp,
privkey,
ticket,
{
struct GNUNET_REST_RequestHandlerError err;
static const struct GNUNET_REST_RequestHandler handlers[] =
- {{MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_AUTHORIZE, &authorize_endpoint},
- {MHD_HTTP_METHOD_POST,
+ { { MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_AUTHORIZE, &authorize_endpoint },
+ { MHD_HTTP_METHOD_POST,
GNUNET_REST_API_NS_AUTHORIZE,
- &authorize_endpoint}, // url-encoded
- {MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_LOGIN, &login_cont},
- {MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_TOKEN, &token_endpoint},
- {MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_USERINFO, &userinfo_endpoint},
- {MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_USERINFO, &userinfo_endpoint},
- {MHD_HTTP_METHOD_OPTIONS, GNUNET_REST_API_NS_OIDC, &options_cont},
- GNUNET_REST_HANDLER_END};
+ &authorize_endpoint }, // url-encoded
+ { MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_LOGIN, &login_cont },
+ { MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_TOKEN, &token_endpoint },
+ { MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_USERINFO, &userinfo_endpoint },
+ { MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_USERINFO, &userinfo_endpoint },
+ { MHD_HTTP_METHOD_OPTIONS, GNUNET_REST_API_NS_OIDC, &options_cont },
+ GNUNET_REST_HANDLER_END };
if (GNUNET_NO ==
GNUNET_REST_handle_request (handle->rest_handle, handlers, &err, handle))
init_cont (handle);
return;
}
+ GNUNET_assert (NULL != ego);
if (ID_REST_STATE_INIT == handle->state)
+
{
ego_entry = GNUNET_new (struct EgoEntry);
GNUNET_IDENTITY_ego_get_public_key (ego, &pk);
void *proc_cls)
{
struct RequestHandle *handle = GNUNET_new (struct RequestHandle);
+
handle->oidc = GNUNET_new (struct OIDC_Variables);
if (NULL == OIDC_cookie_jar_map)
OIDC_cookie_jar_map = GNUNET_CONTAINER_multihashmap_create (10, GNUNET_NO);
cfg = cls;
if (NULL != plugin.cfg)
return NULL; /* can only initialize once! */
- memset (&plugin, 0, sizeof (struct Plugin));
+ memset (&plugin, 0, sizeof(struct Plugin));
plugin.cfg = cfg;
api = GNUNET_new (struct GNUNET_REST_Plugin);
api->cls = &plugin;
{
struct GNUNET_REST_Plugin *api = cls;
struct Plugin *plugin = api->cls;
+
plugin->cfg = NULL;
struct GNUNET_CONTAINER_MultiHashMapIterator *hashmap_it;
while (GNUNET_YES ==
GNUNET_CONTAINER_multihashmap_iterator_next (hashmap_it, NULL, value))
GNUNET_free_non_null (value);
+ GNUNET_CONTAINER_multihashmap_iterator_destroy (hashmap_it);
GNUNET_CONTAINER_multihashmap_destroy (OIDC_cookie_jar_map);
hashmap_it =