From 6371b64774428e83ff83ada88bda354356718aca Mon Sep 17 00:00:00 2001 From: "Schanzenbach, Martin" Date: Mon, 13 Aug 2018 08:51:19 +0200 Subject: fix build; move rest plugins to separate folder --- src/rest-plugins/oidc_helper.c | 440 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 440 insertions(+) create mode 100644 src/rest-plugins/oidc_helper.c (limited to 'src/rest-plugins/oidc_helper.c') diff --git a/src/rest-plugins/oidc_helper.c b/src/rest-plugins/oidc_helper.c new file mode 100644 index 000000000..1e9e64fec --- /dev/null +++ b/src/rest-plugins/oidc_helper.c @@ -0,0 +1,440 @@ +/* + This file is part of GNUnet + Copyright (C) 2010-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 . + */ + +/** + * @file reclaim/oidc_helper.c + * @brief helper library for OIDC related functions + * @author Martin Schanzenbach + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_signatures.h" +#include "gnunet_reclaim_service.h" +#include "gnunet_reclaim_attribute_lib.h" +#include +#include +#include "oidc_helper.h" + +static char* +create_jwt_header(void) +{ + json_t *root; + char *json_str; + + root = json_object (); + json_object_set_new (root, JWT_ALG, json_string (JWT_ALG_VALUE)); + json_object_set_new (root, JWT_TYP, json_string (JWT_TYP_VALUE)); + + json_str = json_dumps (root, JSON_INDENT(0) | JSON_COMPACT); + json_decref (root); + return json_str; +} + +static void +replace_char(char* str, char find, char replace){ + char *current_pos = strchr(str,find); + while (current_pos){ + *current_pos = replace; + current_pos = strchr(current_pos,find); + } +} + +//RFC4648 +static void +fix_base64(char* str) { + char *padding; + //First, remove trailing padding '=' + padding = strtok(str, "="); + while (NULL != padding) + padding = strtok(NULL, "="); + + //Replace + with - + replace_char (str, '+', '-'); + + //Replace / with _ + replace_char (str, '/', '_'); + +} + +/** + * Create a JWT from attributes + * + * @param aud_key the public of the audience + * @param sub_key the public key of the subject + * @param attrs the attribute list + * @param expiration_time the validity of the token + * @param secret_key the key used to sign the JWT + * @return a new base64-encoded JWT string. + */ +char* +OIDC_id_token_new (const struct GNUNET_CRYPTO_EcdsaPublicKey *aud_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *sub_key, + const struct GNUNET_RECLAIM_ATTRIBUTE_ClaimList *attrs, + const struct GNUNET_TIME_Relative *expiration_time, + const char *nonce, + const char *secret_key) +{ + struct GNUNET_RECLAIM_ATTRIBUTE_ClaimListEntry *le; + struct GNUNET_HashCode signature; + struct GNUNET_TIME_Absolute exp_time; + struct GNUNET_TIME_Absolute time_now; + char* audience; + char* subject; + char* header; + char* body_str; + char* result; + char* header_base64; + char* body_base64; + char* signature_target; + char* signature_base64; + char* attr_val_str; + json_t* body; + + //iat REQUIRED time now + time_now = GNUNET_TIME_absolute_get(); + //exp REQUIRED time expired from config + exp_time = GNUNET_TIME_absolute_add (time_now, *expiration_time); + //auth_time only if max_age + //nonce only if nonce + // OPTIONAL acr,amr,azp + subject = GNUNET_STRINGS_data_to_string_alloc (sub_key, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)); + audience = GNUNET_STRINGS_data_to_string_alloc (aud_key, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)); + header = create_jwt_header (); + body = json_object (); + + //iss REQUIRED case sensitive server uri with https + //The issuer is the local reclaim instance (e.g. https://reclaim.id/api/openid) + json_object_set_new (body, + "iss", json_string (SERVER_ADDRESS)); + //sub REQUIRED public key identity, not exceed 255 ASCII length + json_object_set_new (body, + "sub", json_string (subject)); + //aud REQUIRED public key client_id must be there + json_object_set_new (body, + "aud", json_string (audience)); + //iat + json_object_set_new (body, + "iat", json_integer (time_now.abs_value_us / (1000*1000))); + //exp + json_object_set_new (body, + "exp", json_integer (exp_time.abs_value_us / (1000*1000))); + //nbf + json_object_set_new (body, + "nbf", json_integer (time_now.abs_value_us / (1000*1000))); + //nonce + if (NULL != nonce) + json_object_set_new (body, + "nonce", json_string (nonce)); + + for (le = attrs->list_head; NULL != le; le = le->next) + { + attr_val_str = GNUNET_RECLAIM_ATTRIBUTE_value_to_string (le->claim->type, + le->claim->data, + le->claim->data_size); + json_object_set_new (body, + le->claim->name, + json_string (attr_val_str)); + GNUNET_free (attr_val_str); + } + body_str = json_dumps (body, JSON_INDENT(0) | JSON_COMPACT); + json_decref (body); + + GNUNET_STRINGS_base64_encode (header, + strlen (header), + &header_base64); + fix_base64(header_base64); + + GNUNET_STRINGS_base64_encode (body_str, + strlen (body_str), + &body_base64); + fix_base64(body_base64); + + GNUNET_free (subject); + GNUNET_free (audience); + + /** + * Creating the JWT signature. This might not be + * standards compliant, check. + */ + GNUNET_asprintf (&signature_target, "%s.%s", header_base64, body_base64); + GNUNET_CRYPTO_hmac_raw (secret_key, strlen (secret_key), signature_target, strlen (signature_target), &signature); + GNUNET_STRINGS_base64_encode ((const char*)&signature, + sizeof (struct GNUNET_HashCode), + &signature_base64); + fix_base64(signature_base64); + + GNUNET_asprintf (&result, "%s.%s.%s", + header_base64, body_base64, signature_base64); + + GNUNET_free (signature_target); + GNUNET_free (header); + GNUNET_free (body_str); + GNUNET_free (signature_base64); + GNUNET_free (body_base64); + GNUNET_free (header_base64); + return result; +} +/** + * Builds an OIDC authorization code including + * a reclaim ticket and nonce + * + * @param issuer the issuer of the ticket, used to sign the ticket and nonce + * @param ticket the ticket to include in the code + * @param nonce the nonce to include in the code + * @return a new authorization code (caller must free) + */ +char* +OIDC_build_authz_code (const struct GNUNET_CRYPTO_EcdsaPrivateKey *issuer, + const struct GNUNET_RECLAIM_Ticket *ticket, + const char* nonce) +{ + char *ticket_str; + json_t *code_json; + char *signature_payload; + char *signature_str; + char *authz_code; + size_t signature_payload_len; + struct GNUNET_CRYPTO_EcdsaSignature signature; + struct GNUNET_CRYPTO_EccSignaturePurpose *purpose; + + signature_payload_len = sizeof (struct GNUNET_RECLAIM_Ticket); + if (NULL != nonce) + signature_payload_len += strlen (nonce); + + signature_payload = GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) + signature_payload_len); + purpose = (struct GNUNET_CRYPTO_EccSignaturePurpose *)signature_payload; + purpose->size = htonl (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) + signature_payload_len); + purpose->purpose = htonl (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN); + memcpy (&purpose[1], + ticket, + sizeof (struct GNUNET_RECLAIM_Ticket)); + if (NULL != nonce) + memcpy (((char*)&purpose[1]) + sizeof (struct GNUNET_RECLAIM_Ticket), + nonce, + strlen (nonce)); + if (GNUNET_SYSERR == GNUNET_CRYPTO_ecdsa_sign (issuer, + purpose, + &signature)) + { + GNUNET_free (signature_payload); + return NULL; + } + signature_str = GNUNET_STRINGS_data_to_string_alloc (&signature, + sizeof (signature)); + ticket_str = GNUNET_STRINGS_data_to_string_alloc (ticket, + sizeof (struct GNUNET_RECLAIM_Ticket)); + + code_json = json_object (); + json_object_set_new (code_json, + "ticket", + json_string (ticket_str)); + if (NULL != nonce) + json_object_set_new (code_json, + "nonce", + json_string (nonce)); + json_object_set_new (code_json, + "signature", + json_string (signature_str)); + authz_code = json_dumps (code_json, + JSON_INDENT(0) | JSON_COMPACT); + GNUNET_free (signature_payload); + GNUNET_free (signature_str); + GNUNET_free (ticket_str); + json_decref (code_json); + return authz_code; +} + + + + +/** + * Parse reclaim ticket and nonce from + * authorization code. + * This also verifies the signature in the code. + * + * @param audience the expected audience of the code + * @param code the string representation of the code + * @param ticket where to store the ticket + * @param nonce where to store the nonce + * @return GNUNET_OK if successful, else GNUNET_SYSERR + */ +int +OIDC_parse_authz_code (const struct GNUNET_CRYPTO_EcdsaPublicKey *audience, + const char* code, + struct GNUNET_RECLAIM_Ticket **ticket, + char **nonce) +{ + json_error_t error; + json_t *code_json; + json_t *ticket_json; + json_t *nonce_json; + json_t *signature_json; + const char *ticket_str; + const char *signature_str; + const char *nonce_str; + char *code_output; + struct GNUNET_CRYPTO_EccSignaturePurpose *purpose; + struct GNUNET_CRYPTO_EcdsaSignature signature; + size_t signature_payload_len; + + code_output = NULL; + GNUNET_STRINGS_base64_decode (code, + strlen(code), + (void**)&code_output); + code_json = json_loads (code_output, 0 , &error); + GNUNET_free (code_output); + ticket_json = json_object_get (code_json, "ticket"); + nonce_json = json_object_get (code_json, "nonce"); + signature_json = json_object_get (code_json, "signature"); + *ticket = NULL; + *nonce = NULL; + + if ((NULL == ticket_json || !json_is_string (ticket_json)) || + (NULL == signature_json || !json_is_string (signature_json))) + { + json_decref (code_json); + return GNUNET_SYSERR; + } + ticket_str = json_string_value (ticket_json); + signature_str = json_string_value (signature_json); + nonce_str = NULL; + if (NULL != nonce_json) + nonce_str = json_string_value (nonce_json); + signature_payload_len = sizeof (struct GNUNET_RECLAIM_Ticket); + if (NULL != nonce_str) + signature_payload_len += strlen (nonce_str); + purpose = GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) + + signature_payload_len); + purpose->size = htonl (sizeof (struct GNUNET_CRYPTO_EccSignaturePurpose) + signature_payload_len); + purpose->purpose = htonl (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN); + if (GNUNET_OK != GNUNET_STRINGS_string_to_data (ticket_str, + strlen (ticket_str), + &purpose[1], + sizeof (struct GNUNET_RECLAIM_Ticket))) + { + GNUNET_free (purpose); + json_decref (code_json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Cannot parse ticket!\n"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != GNUNET_STRINGS_string_to_data (signature_str, + strlen (signature_str), + &signature, + sizeof (struct GNUNET_CRYPTO_EcdsaSignature))) + { + GNUNET_free (purpose); + json_decref (code_json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Cannot parse signature!\n"); + return GNUNET_SYSERR; + } + *ticket = GNUNET_new (struct GNUNET_RECLAIM_Ticket); + memcpy (*ticket, + &purpose[1], + sizeof (struct GNUNET_RECLAIM_Ticket)); + if (0 != memcmp (audience, + &(*ticket)->audience, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey))) + { + GNUNET_free (purpose); + GNUNET_free (*ticket); + json_decref (code_json); + *ticket = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Audience in ticket does not match client!\n"); + return GNUNET_SYSERR; + + } + if (NULL != nonce_str) + memcpy (((char*)&purpose[1]) + sizeof (struct GNUNET_RECLAIM_Ticket), + nonce_str, + strlen (nonce_str)); + if (GNUNET_OK != GNUNET_CRYPTO_ecdsa_verify (GNUNET_SIGNATURE_PURPOSE_RECLAIM_CODE_SIGN, + purpose, + &signature, + &(*ticket)->identity)) + { + GNUNET_free (purpose); + GNUNET_free (*ticket); + json_decref (code_json); + *ticket = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Signature of authZ code invalid!\n"); + return GNUNET_SYSERR; + } + *nonce = GNUNET_strdup (nonce_str); + return GNUNET_OK; +} + +/** + * Build a token response for a token request + * TODO: Maybe we should add the scope here? + * + * @param access_token the access token to include + * @param id_token the id_token to include + * @param expiration_time the expiration time of the token(s) + * @param token_response where to store the response + */ +void +OIDC_build_token_response (const char *access_token, + const char *id_token, + const struct GNUNET_TIME_Relative *expiration_time, + char **token_response) +{ + json_t *root_json; + + root_json = json_object (); + + GNUNET_assert (NULL != access_token); + GNUNET_assert (NULL != id_token); + GNUNET_assert (NULL != expiration_time); + json_object_set_new (root_json, + "access_token", + json_string (access_token)); + json_object_set_new (root_json, + "token_type", + json_string ("Bearer")); + json_object_set_new (root_json, + "expires_in", + json_integer (expiration_time->rel_value_us / (1000 * 1000))); + json_object_set_new (root_json, + "id_token", + json_string (id_token)); + *token_response = json_dumps (root_json, + JSON_INDENT(0) | JSON_COMPACT); + json_decref (root_json); +} + +/** + * Generate a new access token + */ +char* +OIDC_access_token_new () +{ + char* access_token_number; + char* access_token; + uint64_t random_number; + + random_number = GNUNET_CRYPTO_random_u64(GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); + GNUNET_asprintf (&access_token_number, "%" PRIu64, random_number); + GNUNET_STRINGS_base64_encode(access_token_number,strlen(access_token_number),&access_token); + return access_token; +} -- cgit v1.2.3