From 7ce054864a112f459a75ab542f844a8be0e6c47c Mon Sep 17 00:00:00 2001 From: Martin Schanzenbach Date: Tue, 8 Dec 2020 16:50:27 +0900 Subject: RECLAIM: Return userinfo claims from cache --- src/reclaim/oidc_helper.c | 78 ++++++++++------ src/reclaim/oidc_helper.h | 38 ++++++-- src/reclaim/plugin_rest_openid_connect.c | 151 +++++++++++++++++++++++++++++-- 3 files changed, 217 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/reclaim/oidc_helper.c b/src/reclaim/oidc_helper.c index c6d56e02d..1dde7b673 100644 --- a/src/reclaim/oidc_helper.c +++ b/src/reclaim/oidc_helper.c @@ -567,6 +567,48 @@ OIDC_build_authz_code (const struct GNUNET_IDENTITY_PrivateKey *issuer, } +enum GNUNET_GenericReturnValue +check_code_challenge (const char *code_challenge, + uint32_t code_challenge_len, + const char *code_verifier) +{ + char *code_verifier_hash; + char *expected_code_challenge; + + if (0 == code_challenge_len) /* Only check if this code requires a CV */ + return GNUNET_OK; + if (NULL == code_verifier) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected code verifier!\n"); + return GNUNET_SYSERR; + } + code_verifier_hash = GNUNET_malloc (256 / 8); + // hash code verifier + gcry_md_hash_buffer (GCRY_MD_SHA256, + code_verifier_hash, + code_verifier, + strlen (code_verifier)); + // encode code verifier + GNUNET_STRINGS_base64url_encode (code_verifier_hash, 256 / 8, + &expected_code_challenge); + GNUNET_free (code_verifier_hash); + if (0 != + strncmp (expected_code_challenge, code_challenge, code_challenge_len)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid code verifier! Expected: %s, Got: %.*s\n", + expected_code_challenge, + code_challenge_len, + code_challenge); + GNUNET_free (expected_code_challenge); + return GNUNET_SYSERR; + } + GNUNET_free (expected_code_challenge); + return GNUNET_OK; +} + + /** * Parse reclaim ticket and nonce from * authorization code. @@ -589,16 +631,15 @@ OIDC_parse_authz_code (const struct GNUNET_IDENTITY_PublicKey *audience, struct GNUNET_RECLAIM_Ticket *ticket, struct GNUNET_RECLAIM_AttributeList **attrs, struct GNUNET_RECLAIM_PresentationList **presentations, - char **nonce_str) + char **nonce_str, + enum OIDC_VerificationOptions opts) { char *code_payload; char *ptr; char *plaintext; char *attrs_ser; char *presentations_ser; - char *expected_code_challenge; char *code_challenge; - char *code_verifier_hash; struct GNUNET_CRYPTO_EccSignaturePurpose *purpose; struct GNUNET_IDENTITY_Signature *signature; uint32_t code_challenge_len; @@ -636,38 +677,15 @@ OIDC_parse_authz_code (const struct GNUNET_IDENTITY_PublicKey *audience, // cmp code_challenge code_verifier code_challenge_len = ntohl (params->code_challenge_len); code_challenge = ((char *) ¶ms[1]); - if (0 != code_challenge_len) /* Only check if this code requires a CV */ + if (!(opts & OIDC_VERIFICATION_NO_CODE_VERIFIER)) { - if (NULL == code_verifier) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Expected code verifier!\n"); - GNUNET_free (code_payload); - return GNUNET_SYSERR; - } - code_verifier_hash = GNUNET_malloc (256 / 8); - // hash code verifier - gcry_md_hash_buffer (GCRY_MD_SHA256, - code_verifier_hash, - code_verifier, - strlen (code_verifier)); - // encode code verifier - GNUNET_STRINGS_base64url_encode (code_verifier_hash, 256 / 8, - &expected_code_challenge); - GNUNET_free (code_verifier_hash); - if (0 != - strncmp (expected_code_challenge, code_challenge, code_challenge_len)) + if (GNUNET_OK != check_code_challenge (code_challenge, + code_challenge_len, + code_verifier)) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Invalid code verifier! Expected: %s, Got: %.*s\n", - expected_code_challenge, - code_challenge_len, - code_challenge); GNUNET_free (code_payload); - GNUNET_free (expected_code_challenge); return GNUNET_SYSERR; } - GNUNET_free (expected_code_challenge); } nonce_len = ntohl (params->nonce_len); if (0 != nonce_len) diff --git a/src/reclaim/oidc_helper.h b/src/reclaim/oidc_helper.h index eb1022423..2a8b7bbae 100644 --- a/src/reclaim/oidc_helper.h +++ b/src/reclaim/oidc_helper.h @@ -38,6 +38,19 @@ #define SERVER_ADDRESS "https://api.reclaim" +enum OIDC_VerificationOptions +{ + /** + * Strict verification + */ + OIDC_VERIFICATION_DEFAULT = 0, + + /** + * Do not check code verifier even if expected + */ + OIDC_VERIFICATION_NO_CODE_VERIFIER = 1 +}; + /** * Create a JWT from attributes * @@ -51,12 +64,13 @@ */ char* OIDC_generate_id_token (const struct GNUNET_IDENTITY_PublicKey *aud_key, - const struct GNUNET_IDENTITY_PublicKey *sub_key, - const struct GNUNET_RECLAIM_AttributeList *attrs, - const struct GNUNET_RECLAIM_PresentationList *presentations, - const struct GNUNET_TIME_Relative *expiration_time, - const char *nonce, - const char *secret_key); + const struct GNUNET_IDENTITY_PublicKey *sub_key, + const struct GNUNET_RECLAIM_AttributeList *attrs, + const struct + GNUNET_RECLAIM_PresentationList *presentations, + const struct GNUNET_TIME_Relative *expiration_time, + const char *nonce, + const char *secret_key); /** * Builds an OIDC authorization code including @@ -68,13 +82,15 @@ OIDC_generate_id_token (const struct GNUNET_IDENTITY_PublicKey *aud_key, * @param presentations credential presentation list * @param nonce the nonce to include in the code * @param code_challenge PKCE code challenge + * @param opts verification options * @return a new authorization code (caller must free) */ char* OIDC_build_authz_code (const struct GNUNET_IDENTITY_PrivateKey *issuer, const struct GNUNET_RECLAIM_Ticket *ticket, const struct GNUNET_RECLAIM_AttributeList *attrs, - const struct GNUNET_RECLAIM_PresentationList *presentations, + const struct + GNUNET_RECLAIM_PresentationList *presentations, const char *nonce, const char *code_challenge); @@ -99,7 +115,8 @@ OIDC_parse_authz_code (const struct GNUNET_IDENTITY_PublicKey *ecdsa_pub, struct GNUNET_RECLAIM_Ticket *ticket, struct GNUNET_RECLAIM_AttributeList **attrs, struct GNUNET_RECLAIM_PresentationList **presentations, - char **nonce); + char **nonce, + enum OIDC_VerificationOptions opts); /** * Build a token response for a token request @@ -126,7 +143,7 @@ OIDC_access_token_new (const struct GNUNET_RECLAIM_Ticket *ticket); * Parse an access token */ int -OIDC_access_token_parse (const char* token, +OIDC_access_token_parse (const char*token, struct GNUNET_RECLAIM_Ticket **ticket); @@ -154,6 +171,7 @@ OIDC_check_scopes_for_claim_request (const char *scopes, char * OIDC_generate_userinfo (const struct GNUNET_IDENTITY_PublicKey *sub_key, const struct GNUNET_RECLAIM_AttributeList *attrs, - const struct GNUNET_RECLAIM_PresentationList *presentations); + const struct + GNUNET_RECLAIM_PresentationList *presentations); #endif diff --git a/src/reclaim/plugin_rest_openid_connect.c b/src/reclaim/plugin_rest_openid_connect.c index 7a8a886bd..8d21a5c99 100644 --- a/src/reclaim/plugin_rest_openid_connect.c +++ b/src/reclaim/plugin_rest_openid_connect.c @@ -227,6 +227,11 @@ */ #define OIDC_ERROR_KEY_ACCESS_DENIED "access_denied" +/** + * How long to wait for a consume in userinfo endpoint + */ +#define CONSUME_TIMEOUT GNUNET_TIME_relative_multiply ( \ + GNUNET_TIME_UNIT_SECONDS,2) /** * OIDC ignored parameter array @@ -240,7 +245,12 @@ static char *OIDC_ignored_parameter_array[] = { "display", "acr_values" }; /** - * OIDC Hash map that keeps track of issued cookies + * OIDC hashmap for cached access tokens and codes + */ +struct GNUNET_CONTAINER_MultiHashMap *oidc_code_cache; + +/** + * OIDC hashmap that keeps track of issued cookies */ struct GNUNET_CONTAINER_MultiHashMap *OIDC_cookie_jar_map; @@ -459,6 +469,11 @@ struct RequestHandle */ struct GNUNET_RECLAIM_Operation *idp_op; + /** + * Timeout task for consume + */ + struct GNUNET_SCHEDULER_Task *consume_timeout_op; + /** * Attribute iterator */ @@ -505,6 +520,11 @@ struct RequestHandle */ char *url; + /** + * The passed access token + */ + char *access_token; + /** * The tld for redirect */ @@ -571,6 +591,8 @@ cleanup_handle (struct RequestHandle *handle) GNUNET_RECLAIM_ticket_iteration_stop (handle->ticket_it); if (NULL != handle->idp_op) GNUNET_RECLAIM_cancel (handle->idp_op); + if (NULL != handle->consume_timeout_op) + GNUNET_SCHEDULER_cancel (handle->consume_timeout_op); GNUNET_free (handle->url); GNUNET_free (handle->tld); GNUNET_free (handle->redirect_prefix); @@ -601,6 +623,8 @@ cleanup_handle (struct RequestHandle *handle) GNUNET_CONTAINER_DLL_remove (requests_head, requests_tail, handle); + if (NULL != handle->access_token) + GNUNET_free (handle->access_token); GNUNET_free (handle); } @@ -1282,8 +1306,8 @@ code_redirect (void *cls) { if (GNUNET_OK != GNUNET_IDENTITY_public_key_from_string (handle->oidc - ->login_identity, - &pubkey)) + ->login_identity, + &pubkey)) { handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_COOKIE); handle->edesc = @@ -1662,7 +1686,7 @@ authorize_endpoint (struct GNUNET_REST_RequestHandle *con_handle, if (GNUNET_OK != GNUNET_IDENTITY_public_key_from_string (handle->oidc->client_id, - &handle->oidc->client_pkey)) + &handle->oidc->client_pkey)) { handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_UNAUTHORIZED_CLIENT); handle->edesc = GNUNET_strdup ("The client is not authorized to request an " @@ -2071,7 +2095,8 @@ token_endpoint (struct GNUNET_REST_RequestHandle *con_handle, // decode code if (GNUNET_OK != OIDC_parse_authz_code (&cid, code, code_verifier, &ticket, - &cl, &pl, &nonce)) + &cl, &pl, &nonce, + OIDC_VERIFICATION_DEFAULT)) { handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST); handle->edesc = GNUNET_strdup ("invalid code"); @@ -2080,7 +2105,6 @@ token_endpoint (struct GNUNET_REST_RequestHandle *con_handle, GNUNET_SCHEDULER_add_now (&do_error, handle); return; } - GNUNET_free (code); // create jwt if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (cfg, @@ -2091,6 +2115,7 @@ token_endpoint (struct GNUNET_REST_RequestHandle *con_handle, handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_SERVER_ERROR); handle->edesc = GNUNET_strdup ("gnunet configuration failed"); handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; + GNUNET_free (code); GNUNET_SCHEDULER_add_now (&do_error, handle); return; } @@ -2105,6 +2130,7 @@ token_endpoint (struct GNUNET_REST_RequestHandle *con_handle, handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST); handle->edesc = GNUNET_strdup ("No signing secret configured!"); handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; + GNUNET_free (code); GNUNET_SCHEDULER_add_now (&do_error, handle); return; } @@ -2116,6 +2142,26 @@ token_endpoint (struct GNUNET_REST_RequestHandle *con_handle, (NULL != nonce) ? nonce : NULL, jwt_secret); access_token = OIDC_access_token_new (&ticket); + /* Store mapping from access token to code so we can later + * fall back on the provided attributes in userinfo + */ + GNUNET_CRYPTO_hash (access_token, + strlen (access_token), + &cache_key); + char *tmp_at = GNUNET_CONTAINER_multihashmap_get (oidc_code_cache, + &cache_key); + GNUNET_CONTAINER_multihashmap_put (oidc_code_cache, + &cache_key, + code, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE); + /* If there was a previus code in there, free the old value */ + if (NULL != tmp_at) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "OIDC access token already issued. Cleanup.\n"); + GNUNET_free (tmp_at); + } + OIDC_build_token_response (access_token, id_token, &expiration_time, @@ -2149,6 +2195,10 @@ consume_ticket (void *cls, struct GNUNET_RECLAIM_PresentationListEntry *atle; struct MHD_Response *resp; char *result_str; + + if (NULL != handle->consume_timeout_op) + GNUNET_SCHEDULER_cancel (handle->consume_timeout_op); + handle->consume_timeout_op = NULL; handle->idp_op = NULL; if (NULL == identity) @@ -2180,8 +2230,9 @@ consume_ticket (void *cls, for (atle = handle->presentations->list_head; NULL != atle; atle = atle->next) { - if (GNUNET_NO == GNUNET_RECLAIM_id_is_equal (&atle->presentation->credential_id, - &pres->credential_id)) + if (GNUNET_NO == GNUNET_RECLAIM_id_is_equal ( + &atle->presentation->credential_id, + &pres->credential_id)) continue; break; /** already in list **/ } @@ -2190,8 +2241,8 @@ consume_ticket (void *cls, /** Credential matches for attribute, add **/ atle = GNUNET_new (struct GNUNET_RECLAIM_PresentationListEntry); atle->presentation = GNUNET_RECLAIM_presentation_new (pres->type, - pres->data, - pres->data_size); + pres->data, + pres->data_size); GNUNET_CONTAINER_DLL_insert (handle->presentations->list_head, handle->presentations->list_tail, atle); @@ -2199,6 +2250,69 @@ consume_ticket (void *cls, } +static void +consume_timeout (void*cls) +{ + struct RequestHandle *handle = cls; + struct GNUNET_HashCode cache_key; + struct GNUNET_RECLAIM_AttributeList *cl = NULL; + struct GNUNET_RECLAIM_PresentationList *pl = NULL; + struct GNUNET_RECLAIM_Ticket ticket; + char *nonce; + char *cached_code; + + handle->consume_timeout_op = NULL; + if (NULL != handle->idp_op) + GNUNET_RECLAIM_cancel (handle->idp_op); + handle->idp_op = NULL; + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Ticket consumptioned timed out. Using cache...\n"); + GNUNET_CRYPTO_hash (handle->access_token, + strlen (handle->access_token), + &cache_key); + cached_code = GNUNET_CONTAINER_multihashmap_get (oidc_code_cache, + &cache_key); + if (NULL == cached_code) + { + handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_TOKEN); + handle->edesc = GNUNET_strdup ("No Access Token in cache!"); + handle->response_code = MHD_HTTP_UNAUTHORIZED; + GNUNET_SCHEDULER_add_now (&do_userinfo_error, handle); + return; + } + + // decode code + if (GNUNET_OK != OIDC_parse_authz_code (&handle->ticket.audience, + cached_code, NULL, &ticket, + &cl, &pl, &nonce, + OIDC_VERIFICATION_NO_CODE_VERIFIER)) + { + handle->emsg = GNUNET_strdup (OIDC_ERROR_KEY_INVALID_REQUEST); + handle->edesc = GNUNET_strdup ("invalid code"); + handle->response_code = MHD_HTTP_BAD_REQUEST; + GNUNET_free (cached_code); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + + struct MHD_Response *resp; + char *result_str; + + result_str = OIDC_generate_userinfo (&handle->ticket.identity, + cl, + pl); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Userinfo: %s\n", result_str); + resp = GNUNET_REST_create_response (result_str); + handle->proc (handle->proc_cls, resp, MHD_HTTP_OK); + GNUNET_free (result_str); + GNUNET_free (nonce); + GNUNET_RECLAIM_attribute_list_destroy (cl); + GNUNET_RECLAIM_presentation_list_destroy (pl); + cleanup_handle (handle); +} + + /** * Responds to userinfo GET and url-encoded POST request * @@ -2295,6 +2409,11 @@ userinfo_endpoint (struct GNUNET_REST_RequestHandle *con_handle, handle->presentations = GNUNET_new (struct GNUNET_RECLAIM_PresentationList); + /* If the consume takes too long, we use values from the cache */ + handle->access_token = GNUNET_strdup (authorization_access_token); + handle->consume_timeout_op = GNUNET_SCHEDULER_add_delayed (CONSUME_TIMEOUT, + &consume_timeout, + handle); handle->idp_op = GNUNET_RECLAIM_ticket_consume (idp, privkey, &handle->ticket, @@ -2554,6 +2673,10 @@ rest_identity_process_request (struct GNUNET_REST_RequestHandle *rest_handle, if (NULL == OIDC_cookie_jar_map) OIDC_cookie_jar_map = GNUNET_CONTAINER_multihashmap_create (10, GNUNET_NO); + if (NULL == oidc_code_cache) + oidc_code_cache = GNUNET_CONTAINER_multihashmap_create (10, + GNUNET_NO); + handle->response_code = 0; handle->timeout = GNUNET_TIME_UNIT_FOREVER_REL; handle->proc_cls = proc_cls; @@ -2646,6 +2769,14 @@ libgnunet_plugin_rest_openid_connect_done (void *cls) NULL); GNUNET_CONTAINER_multihashmap_destroy (OIDC_cookie_jar_map); } + if (NULL != oidc_code_cache) + { + GNUNET_CONTAINER_multihashmap_iterate (oidc_code_cache, + &cleanup_hashmap, + NULL); + GNUNET_CONTAINER_multihashmap_destroy (oidc_code_cache); + } + GNUNET_free (allow_methods); if (NULL != gns_handle) GNUNET_GNS_disconnect (gns_handle); -- cgit v1.2.3