Merge remote-tracking branch 'origin/master' into identity_oidc
authorSchanzenbach, Martin <martin.schanzenbach@aisec.fraunhofer.de>
Mon, 8 Jan 2018 09:49:06 +0000 (10:49 +0100)
committerSchanzenbach, Martin <martin.schanzenbach@aisec.fraunhofer.de>
Mon, 8 Jan 2018 09:49:06 +0000 (10:49 +0100)
1  2 
src/identity-provider/plugin_rest_identity_provider.c
src/include/gnunet_rest_lib.h

index 68644777fecedd03c0da03e5c1bb46ed6ab6238c,6eb85643560958e402344409c93c493e0a3a5e71..1aa1f818deec9951bfa7e799f169e1c032595d08
   */
  #define GNUNET_REST_API_NS_IDENTITY_CONSUME "/idp/consume"
  
 +/**
 + * Authorize namespace
 + */
 +#define GNUNET_REST_API_NS_AUTHORIZE "/idp/authorize"
 +
 +/**
 + * Login namespace
 + */
 +#define GNUNET_REST_API_NS_LOGIN "/idp/login"
 +
  /**
   * Attribute key
   */
   */
  #define ID_REST_STATE_POST_INIT 1
  
 +/**
 + * OIDC response_type key
 + */
 +#define OIDC_RESPONSE_TYPE_KEY "response_type"
 +
 +/**
 + * OIDC client_id key
 + */
 +#define OIDC_CLIENT_ID_KEY "client_id"
 +
 +/**
 + * OIDC scope key
 + */
 +#define OIDC_SCOPE_KEY "scope"
 +
 +/**
 + * OIDC redirect_uri key
 + */
 +#define OIDC_REDIRECT_URI_KEY "redirect_uri"
 +
 +/**
 + * OIDC state key
 + */
 +#define OIDC_STATE_KEY "state"
 +
 +/**
 + * OIDC nonce key
 + */
 +#define OIDC_NONCE_KEY "nonce"
 +
 +/**
 + * OIDC cookie header key
 + */
 +#define OIDC_COOKIE_HEADER_KEY "Cookie"
 +
 +/**
 + * OIDC cookie header information key
 + */
 +#define OIDC_COOKIE_HEADER_INFORMATION_KEY "Identity="
 +
 +/**
 + * OIDC expected response_type while authorizing
 + */
 +#define OIDC_EXPECTED_AUTHORIZATION_RESPONSE_TYPE "code"
 +
 +/**
 + * OIDC expected scope part while authorizing
 + */
 +#define OIDC_EXPECTED_AUTHORIZATION_SCOPE "openid"
 +
 +
 +/**
 + * OIDC ignored parameter array
 + */
 +char* OIDC_ignored_parameter_array [] =
 +{
 +  "display",
 +  "prompt",
 +  "max_age",
 +  "ui_locales", 
 +  "response_mode",
 +  "id_token_hint",
 +  "login_hint", 
 +  "acr_values"
 +};
 +
 +/**
 + * OIDC authorized identities and times hashmap
 + */
 +struct GNUNET_CONTAINER_MultiHashMap *OIDC_authorized_identities;
  
  /**
   * The configuration handle
@@@ -315,16 -235,6 +315,16 @@@ struct RequestHandl
     */
    char *emsg;
  
 +  /**
 +   * Error response uri
 +   */
 +  char *eredirect;
 +
 +  /**
 +   * Error response description
 +   */
 +  char *edesc;
 +
    /**
     * Reponse code
     */
@@@ -398,7 -308,7 +398,7 @@@ do_error (void *cls
    char *json_error;
  
    GNUNET_asprintf (&json_error,
 -                   "{Error while processing request: %s}",
 +                   "{error : %s}",
                     handle->emsg);
    resp = GNUNET_REST_create_response (json_error);
    handle->proc (handle->proc_cls, resp, handle->response_code);
    GNUNET_free (json_error);
  }
  
 +/**
 + * Task run on error, sends error message.  Cleans up everything.
 + *
 + * @param cls the `struct RequestHandle`
 + */
 +static void
 +do_redirect_error (void *cls)
 +{
 +  struct RequestHandle *handle = cls;
 +  struct MHD_Response *resp;
 +  char* redirect;
 +  //TODO handle->url is wrong
 +  GNUNET_asprintf (&redirect,
 +                   "%s?error=%s&error_description=%s",
 +                 handle->eredirect, handle->emsg, handle->edesc );
 +  resp = GNUNET_REST_create_response ("");
 +  MHD_add_response_header (resp, "Location", redirect);
 +  handle->proc (handle->proc_cls, resp, MHD_HTTP_FOUND);
 +  cleanup_handle (handle);
 +  GNUNET_free (redirect);
 +}
 +
  /**
   * Task run on timeout, sends error message.  Cleans up everything.
   *
@@@ -905,10 -793,10 +905,10 @@@ revoke_ticket_cont (struct GNUNET_REST_
                                   strlen (rnd_str),
                                   &ticket.rnd,
                                   sizeof (uint64_t));
 -  GNUNET_STRINGS_string_to_data (identity_str,
 -                                 strlen (identity_str),
 -                                 &ticket.identity,
 -                                 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
 +//  GNUNET_STRINGS_string_to_data (identity_str,
 +//                                 strlen (identity_str),
 +//                                 &ticket.identity,type filter text
 +//                                 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
    GNUNET_STRINGS_string_to_data (audience_str,
                                   strlen (audience_str),
                                   &ticket.audience,
@@@ -1124,328 -1012,6 +1124,328 @@@ options_cont (struct GNUNET_REST_Reques
    return;
  }
  
 +/**
 + * Respond to OPTIONS request
 + *
 + * @param con_handle the connection handle
 + * @param url the url
 + * @param cls the RequestHandle
 + */
 +static void
 +authorize_cont (struct GNUNET_REST_RequestHandle *con_handle,
 +                const char* url,
 +                void *cls)
 +{
 +  struct MHD_Response *resp;
 +  struct RequestHandle *handle = cls;
 +  char *response_type;
 +  char *client_id;
 +  char *scope;
 +  char *redirect_uri;
 +  char *state = NULL;
 +  char *nonce = NULL;
 +  struct GNUNET_TIME_Absolute current_time, *relog_time;
 +  char *login_base_url, *new_redirect;
 +  struct GNUNET_HashCode cache_key;
 +
 +  //TODO clean up method
 +
 +  /** The Authorization Server MUST validate all the OAuth 2.0 parameters
 +   *  according to the OAuth 2.0 specification.
 +   */
 +  /**
 +   *  If the sub (subject) Claim is requested with a specific value for the
 +   *  ID Token, the Authorization Server MUST only send a positive response
 +   *  if the End-User identified by that sub value has an active session with
 +   *  the Authorization Server or has been Authenticated as a result of the
 +   *  request. The Authorization Server MUST NOT reply with an ID Token or
 +   *  Access Token for a different user, even if they have an active session
 +   *  with the Authorization Server. Such a request can be made either using
 +   *  an id_token_hint parameter or by requesting a specific Claim Value as
 +   *  described in Section 5.5.1, if the claims parameter is supported by
 +   *  the implementation.
 +   */
 +
 +
 +
 +  // REQUIRED value: client_id
 +  GNUNET_CRYPTO_hash (OIDC_CLIENT_ID_KEY, strlen (OIDC_CLIENT_ID_KEY),
 +                    &cache_key);
 +  if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle->url_param_map,
 +                                                         &cache_key))
 +  {
 +    handle->emsg=GNUNET_strdup("invalid_request");
 +    handle->edesc=GNUNET_strdup("Missing parameter: client_id");
 +    GNUNET_SCHEDULER_add_now (&do_error, handle);
 +    return;
 +  }
 +  client_id = GNUNET_CONTAINER_multihashmap_get(handle->rest_handle->url_param_map,
 +                                              &cache_key);
 +  struct GNUNET_CRYPTO_EcdsaPublicKey pubkey;
 +  GNUNET_CRYPTO_ecdsa_public_key_from_string(client_id,
 +                                           strlen (client_id),
 +                                           &pubkey);
 +//  GNUNET_NAMESTORE_zone_to_name();
 +  // Checks if client_id is valid:
 +  // TODO use GNUNET_NAMESTORE_zone_to_name() function to verify that a delegation to the client_id exists
 +  // TODO change check (lookup trusted public_key?)
 +//  if( strcmp( client_id, "localhost" ) != 0 )
 +//  {
 +//    handle->emsg=GNUNET_strdup("unauthorized_client");
 +//    handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
 +//    GNUNET_SCHEDULER_add_now (&do_error, handle);
 +//    return;
 +//  }
 +
 +  // REQUIRED value: redirect_uri
 +  // TODO verify the redirect uri matches https://<client_id>.zkey[/xyz]
 +  GNUNET_CRYPTO_hash (OIDC_REDIRECT_URI_KEY, strlen (OIDC_REDIRECT_URI_KEY),
 +                    &cache_key);
 +  if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle->url_param_map,
 +                                                         &cache_key))
 +  {
 +    handle->emsg=GNUNET_strdup("invalid_request");
 +    handle->edesc=GNUNET_strdup("Missing parameter: redirect_uri");
 +    GNUNET_SCHEDULER_add_now (&do_error, handle);
 +    return;
 +  }
 +  redirect_uri = GNUNET_CONTAINER_multihashmap_get(handle->rest_handle->url_param_map,
 +                                              &cache_key);
 +
 +  // verify the redirect uri matches https://<client_id>.zkey[/xyz]
 +  // TODO change check (check client_id->public key == address)
 +//  if( strcmp( redirect_uri, "https://localhost:8000" ) != 0 )
 +//  {
 +//    handle->emsg=GNUNET_strdup("invalid_request");
 +//    handle->edesc=GNUNET_strdup("Invalid or mismatching redirect_uri");
 +//    GNUNET_SCHEDULER_add_now (&do_error, handle);
 +//    return;
 +//  }
 +  handle->eredirect = GNUNET_strdup(redirect_uri);
 +
 +  // REQUIRED value: response_type
 +  GNUNET_CRYPTO_hash (OIDC_RESPONSE_TYPE_KEY, strlen (OIDC_RESPONSE_TYPE_KEY),
 +                    &cache_key);
 +  if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle->url_param_map,
 +                                                         &cache_key))
 +  {
 +    handle->emsg=GNUNET_strdup("invalid_request");
 +    handle->edesc=GNUNET_strdup("Missing parameter: response_type");
 +    GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
 +    return;
 +  }
 +  response_type = GNUNET_CONTAINER_multihashmap_get(handle->rest_handle->url_param_map,
 +                                                    &cache_key);
 +
 +  // REQUIRED value: scope
 +  GNUNET_CRYPTO_hash (OIDC_SCOPE_KEY, strlen (OIDC_SCOPE_KEY), &cache_key);
 +  if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle->url_param_map,
 +                                                         &cache_key))
 +  {
 +    handle->emsg=GNUNET_strdup("invalid_request");
 +    handle->edesc=GNUNET_strdup("Missing parameter: scope");
 +    GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
 +    return;
 +  }
 +  scope = GNUNET_CONTAINER_multihashmap_get(handle->rest_handle->url_param_map,
 +                                          &cache_key);
 +
 +  //RECOMMENDED value: state
 +  GNUNET_CRYPTO_hash (OIDC_STATE_KEY, strlen (OIDC_STATE_KEY), &cache_key);
 +  if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle->url_param_map,
 +                                                         &cache_key))
 +  {
 +    state = GNUNET_CONTAINER_multihashmap_get(handle->rest_handle->url_param_map,
 +                                            &cache_key);
 +  }
 +
 +  //OPTIONAL value: nonce
 +  GNUNET_CRYPTO_hash (OIDC_NONCE_KEY, strlen (OIDC_NONCE_KEY), &cache_key);
 +  if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle->url_param_map,
 +                                                         &cache_key))
 +  {
 +    nonce = GNUNET_CONTAINER_multihashmap_get(handle->rest_handle->url_param_map,
 +                                            &cache_key);
 +  }
 +
 +  int number_of_ignored_parameter = sizeof(OIDC_ignored_parameter_array) / sizeof(char *);
 +  int iterator;
 +  for( iterator = 0; iterator < number_of_ignored_parameter; iterator++ )
 +  {
 +    GNUNET_CRYPTO_hash (OIDC_ignored_parameter_array[iterator],
 +                      strlen(OIDC_ignored_parameter_array[iterator]),
 +                      &cache_key);
 +    if(GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains(handle->rest_handle->url_param_map,
 +                                                          &cache_key))
 +    {
 +      handle->emsg=GNUNET_strdup("access_denied");
 +      //TODO rewrite error description
 +      handle->edesc=GNUNET_strdup("Server will not handle parameter");
 +      GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
 +      return;
 +    }
 +  }
 +
 +  // Checks if response_type is 'code'
 +  if( strcmp( response_type, OIDC_EXPECTED_AUTHORIZATION_RESPONSE_TYPE ) != 0 )
 +  {
 +    handle->emsg=GNUNET_strdup("unsupported_response_type");
 +    handle->edesc=GNUNET_strdup("The authorization server does not support "
 +                              "obtaining this authorization code.");
 +    GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
 +    return;
 +  }
 +  // Checks if scope contains 'openid'
 +  if( strstr( scope, OIDC_EXPECTED_AUTHORIZATION_SCOPE ) == NULL )
 +  {
 +    handle->emsg=GNUNET_strdup("invalid_scope");
 +    handle->edesc=GNUNET_strdup("The requested scope is invalid, unknown, or "
 +                              "malformed.");
 +    GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
 +    return;
 +  }
 +
 +
 +  //TODO check other values and use them accordingly
 +
 +
 +  GNUNET_CRYPTO_hash (OIDC_COOKIE_HEADER_KEY, strlen (OIDC_COOKIE_HEADER_KEY),
 +                    &cache_key);
 +  //No identity-cookie -> redirect to login
 +  if ( GNUNET_YES
 +      == GNUNET_CONTAINER_multihashmap_contains (con_handle->header_param_map,
 +                                               &cache_key) )
 +  {
 +    //split cookies and find 'Identity' cookie
 +    char* cookies = GNUNET_CONTAINER_multihashmap_get (
 +      con_handle->header_param_map, &cache_key);
 +    char delimiter[] = "; ";
 +    char *identity_cookie;
 +    identity_cookie = strtok(cookies, delimiter);
 +
 +    while(identity_cookie != NULL)
 +    {
 +      if(strstr( identity_cookie, OIDC_COOKIE_HEADER_INFORMATION_KEY ) != NULL)
 +      {
 +      break;
 +      }
 +      identity_cookie = strtok(NULL, delimiter);
 +    }
 +    GNUNET_CRYPTO_hash (identity_cookie, strlen (identity_cookie), &cache_key);
 +
 +    //No login time for identity -> redirect to login
 +    if ( GNUNET_YES
 +      == GNUNET_CONTAINER_multihashmap_contains (OIDC_authorized_identities,
 +                                                 &cache_key) )
 +    {
 +      relog_time = GNUNET_CONTAINER_multihashmap_get (
 +            OIDC_authorized_identities, &cache_key);
 +
 +      current_time = GNUNET_TIME_absolute_get();
 +
 +      GNUNET_CONTAINER_multihashmap_remove_all(OIDC_authorized_identities, &cache_key);
 +      // 30 min after old login -> redirect to login
 +      if ( current_time.abs_value_us <= relog_time->abs_value_us )
 +      {
 +      resp = GNUNET_REST_create_response ("");
 +      // code = struct GNUNET_IDENTITY_PROVIDER_Ticket
 +      GNUNET_IDENTITY_PROVIDER_t
 +      MHD_add_response_header (resp, "Location", redirect_uri);
 +      handle->proc (handle->proc_cls, resp, MHD_HTTP_FOUND);
 +      cleanup_handle (handle);
 +      GNUNET_free(relog_time);
 +      return;
 +      }
 +      GNUNET_free(relog_time);
 +    }
 +  }
 +
 +
 +  // login redirection
 +  if ( GNUNET_OK
 +      == GNUNET_CONFIGURATION_get_value_string (cfg, "identity-rest-plugin",
 +                                              "address", &login_base_url) )
 +  {
 +    GNUNET_asprintf (&new_redirect, "%s?%s=%s&%s=%s&%s=%s&%s=%s&%s=%s&%s=%s",
 +                   login_base_url,
 +                   OIDC_RESPONSE_TYPE_KEY,
 +                   response_type,
 +                   OIDC_CLIENT_ID_KEY,
 +                   client_id,
 +                   OIDC_REDIRECT_URI_KEY,
 +                   redirect_uri,
 +                   OIDC_SCOPE_KEY,
 +                   scope,
 +                   OIDC_STATE_KEY,
 +                   (NULL == state) ? state : "",
 +                   OIDC_NONCE_KEY,
 +                   (NULL == nonce) ? nonce : "");
 +    resp = GNUNET_REST_create_response ("");
 +    MHD_add_response_header (resp, "Location", new_redirect);
 +  }
 +  else
 +  {
 +    handle->emsg = GNUNET_strdup("No server configuration");
 +    handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
 +    GNUNET_SCHEDULER_add_now (&do_error, handle);
 +    return;
 +  }
 +  handle->proc (handle->proc_cls, resp, MHD_HTTP_FOUND);
 +  cleanup_handle (handle);
 +  GNUNET_free(new_redirect);
 +  return;
 +}
 +
 +
 +/**
 + * Combines an identity with a login time and responds OK to login request
 + *
 + * @param con_handle the connection handle
 + * @param url the url
 + * @param cls the RequestHandle
 + */
 +static void
 +login_cont (struct GNUNET_REST_RequestHandle *con_handle,
 +            const char* url,
 +            void *cls)
 +{
 +
 +
 +  struct MHD_Response *resp = GNUNET_REST_create_response ("");
 +  struct RequestHandle *handle = cls;
 +  struct GNUNET_HashCode cache_key;
 +  struct GNUNET_TIME_Absolute *current_time;
 +  char* cookie;
 +  json_t *root;
 +  json_error_t error;
 +  json_t *identity;
 +  root = json_loads (handle->rest_handle->data, 0, &error);
 +  identity = json_object_get (root, "identity");
 +  if ( json_is_string(identity) )
 +  {
 +    GNUNET_asprintf (&cookie, "Identity=%s", json_string_value (identity));
 +
 +    GNUNET_CRYPTO_hash (cookie, strlen (cookie), &cache_key);
 +    current_time = GNUNET_new(struct GNUNET_TIME_Absolute);
 +    *current_time = GNUNET_TIME_relative_to_absolute (
 +      GNUNET_TIME_relative_multiply (GNUNET_TIME_relative_get_minute_ (),
 +                                     30));
 +    GNUNET_CONTAINER_multihashmap_put (
 +      OIDC_authorized_identities, &cache_key, current_time,
 +      GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE);
 +
 +    handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
 +  }
 +  else
 +  {
 +    handle->proc (handle->proc_cls, resp, MHD_HTTP_BAD_REQUEST);
 +  }
 +  GNUNET_free(cookie);
 +  json_decref (root);
 +  cleanup_handle (handle);
 +  return;
 +}
 +
  /**
   * Handle rest request
   *
@@@ -1459,9 -1025,6 +1459,9 @@@ init_cont (struct RequestHandle *handle
      {MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_IDENTITY_ATTRIBUTES, &list_attribute_cont},
      {MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_IDENTITY_ATTRIBUTES, &add_attribute_cont},
      {MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_IDENTITY_TICKETS, &list_tickets_cont},
 +    {MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_AUTHORIZE, &authorize_cont},
 +    {MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_LOGIN, &login_cont},
 +    {MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_AUTHORIZE, &authorize_cont},
      {MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_IDENTITY_REVOKE, &revoke_ticket_cont},
      {MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_IDENTITY_CONSUME, &consume_ticket_cont},
      {MHD_HTTP_METHOD_OPTIONS, GNUNET_REST_API_NS_IDENTITY_PROVIDER,
@@@ -1540,28 -1103,13 +1540,17 @@@ list_ego (void *cls
  
  }
  
- /**
-  * Function processing the REST call
-  *
-  * @param method HTTP method
-  * @param url URL of the HTTP request
-  * @param data body of the HTTP request (optional)
-  * @param data_size length of the body
-  * @param proc callback function for the result
-  * @param proc_cls closure for callback function
-  * @return GNUNET_OK if request accepted
-  */
  static void
  rest_identity_process_request(struct GNUNET_REST_RequestHandle *rest_handle,
                                GNUNET_REST_ResultProcessor proc,
                                void *proc_cls)
  {
    struct RequestHandle *handle = GNUNET_new (struct RequestHandle);
 -
 +  if ( NULL == OIDC_authorized_identities )
 +  {
 +    OIDC_authorized_identities = GNUNET_CONTAINER_multihashmap_create (10,
 +                                                                   GNUNET_NO);
 +  }
    handle->timeout = GNUNET_TIME_UNIT_FOREVER_REL;
    handle->proc_cls = proc_cls;
    handle->proc = proc;
index 5bf04636bb5e7b8eba7bd7308ee9e75e24d08981,e571eead344b36ce2aa259542d63444188d06579..41b85401d56704e73631218099c34a370c5ddcab
  #define GNUNET_REST_LIB_H
  
  #include "gnunet_util_lib.h"
- #include "microhttpd.h"
+ #include <microhttpd.h>
  
  #define GNUNET_REST_HANDLER_END {NULL, NULL, NULL}
  
  struct GNUNET_REST_RequestHandle
  {
 +  /**
 +   * Map of url parameters
 +   */
    struct GNUNET_CONTAINER_MultiHashMap *url_param_map;
 +
 +  /**
 +   * Map of headers
 +   */
 +  struct GNUNET_CONTAINER_MultiHashMap *header_param_map;
 +
 +  /**
 +   * The HTTP method as MHD value (see microhttpd.h)
 +   */
    const char *method;
 +
 +  /**
 +   * The url as string
 +   */
    const char *url;
 +
 +  /**
 +   * The POST data
 +   */
    const char *data;
 +
 +  /**
 +   * The POST data size
 +   */
    size_t data_size;
  };
  
@@@ -113,7 -89,7 +113,7 @@@ typedef void (*GNUNET_REST_ResultProces
   *
   * @param url URL to check
   * @param namespace namespace to check against
-  * @retun GNUNET_YES if namespace matches
+  * @return GNUNET_YES if namespace matches
   */
  int
  GNUNET_REST_namespace_match (const char *url, const char *namespace);
   * Create REST MHD response
   *
   * @param data result
-  * @retun MHD response
+  * @return MHD response
   */
   struct MHD_Response*
  GNUNET_REST_create_response (const char *data);