challenger

OAuth 2.0-based authentication service that validates user can receive messages at a certain address
Log | Files | Refs | Submodules | README | LICENSE

commit 71cc5f78b24a83c50217cbde85a19a7db441235c
parent ce1b718f85191548b732929d19a6c36a56dcb7c1
Author: Bohdan Potuzhnyi <potub1@bfh.ch>
Date:   Thu,  1 Aug 2024 17:53:30 +0200

in theory done, but needs live testing and adjustments

Diffstat:
Msrc/challenger/Makefile.am | 1+
Msrc/challenger/challenger-httpd_authorize.c | 44+++++++++++++++++++++++++++++++++++++++++++-
Msrc/challenger/challenger-httpd_token.c | 151+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 193 insertions(+), 3 deletions(-)

diff --git a/src/challenger/Makefile.am b/src/challenger/Makefile.am @@ -60,6 +60,7 @@ challenger_httpd_LDADD = \ -lgnunetcurl \ -lgnunetjson \ -lgnunetutil \ + -lgcrypt \ $(XLIB) EXTRA_DIST = \ diff --git a/src/challenger/challenger-httpd_authorize.c b/src/challenger/challenger-httpd_authorize.c @@ -62,6 +62,8 @@ CH_handler_authorize (struct CH_HandlerContext *hc, const char *redirect_uri; const char *state; const char *scope; + const char *code_challenge; + const char *code_challenge_method; struct CHALLENGER_ValidationNonceP nonce; (void) upload_data; @@ -142,11 +144,26 @@ CH_handler_authorize (struct CH_HandlerContext *hc, = MHD_lookup_connection_value (hc->connection, MHD_GET_ARGUMENT_KIND, "redirect_uri"); + + code_challenge = MHD_lookup_connection_value(hc->connection, + MHD_GET_ARGUMENT_KIND, + "code_challenge"); + + code_challenge_method = MHD_lookup_connection_value(hc->connection, + MHD_GET_ARGUMENT_KIND, + "code_challenge_method"); + if (NULL != code_challenge) + { + if (NULL == code_challenge_method) + code_challenge_method = "plain"; + } + /* Note: this is a somewhat arbitrary restriction, as the rest of this code would support other schemas just fine. However, #7838 (RFC 7636) should be implemented before lifting this restriction, as otherwise the service might be accidentally used with public clients which would then be insecure. */ + /* if ( (NULL != redirect_uri) && (0 != strncmp (redirect_uri, "http://", @@ -163,6 +180,29 @@ CH_handler_authorize (struct CH_HandlerContext *hc, TALER_EC_GENERIC_PARAMETER_MALFORMED, "redirect_uri (has to start with 'http://' or 'https://')"); } + */ + + /** + * Replacement of previous safe check to not allow public without s256 code_challenge + */ + if ( (NULL != redirect_uri) && + (0 != strncmp (redirect_uri, + "http://", + strlen ("http://"))) && + (0 != strncmp (redirect_uri, + "https://", + strlen ("https://"))) && + ( (0 == strcmp(code_challenge_method, "plain")) || code_challenge_method == NULL) ) + { + GNUNET_break_op (0); + return reply_error ( + hc, + "invalid-request", + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "redirect_uri (has to start with 'http://' or 'https://' or not use 'plain'/NULL as code_challenge)"); + } + state = MHD_lookup_connection_value (hc->connection, MHD_GET_ARGUMENT_KIND, @@ -186,12 +226,14 @@ CH_handler_authorize (struct CH_HandlerContext *hc, /* authorize_start will return 0 if a 'redirect_uri' was configured for the client and this one differs. */ - qs = CH_db->authorize_start (CH_db->cls, + qs = CH_db->authorize_start_pkce (CH_db->cls, &nonce, client_id, scope, state, redirect_uri, + code_challenge, + code_challenge_method, &last_address, &address_attempts_left, &pin_transmissions_left, diff --git a/src/challenger/challenger-httpd_token.c b/src/challenger/challenger-httpd_token.c @@ -25,7 +25,61 @@ #include "challenger-httpd_common.h" #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> +#include <gcrypt.h> +// TODO: Until final merge, find out how it opperates normally + +// Base64 character set for standard encoding +static const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +// Function to base64 encode +char *base64_encode(const unsigned char *data, size_t input_length, size_t *output_length) { + *output_length = 4 * ((input_length + 2) / 3); + char *encoded_data = (char *)malloc(*output_length + 1); + if (encoded_data == NULL) return NULL; + + for (size_t i = 0, j = 0; i < input_length;) { + uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0; + uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0; + uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0; + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + encoded_data[j++] = base64_table[(triple >> 3 * 6) & 0x3F]; + encoded_data[j++] = base64_table[(triple >> 2 * 6) & 0x3F]; + encoded_data[j++] = base64_table[(triple >> 1 * 6) & 0x3F]; + encoded_data[j++] = base64_table[(triple >> 0 * 6) & 0x3F]; + } + + static const int mod_table[] = {0, 2, 1}; + for (size_t i = 0; i < mod_table[input_length % 3]; ++i) + encoded_data[*output_length - 1 - i] = '='; + + encoded_data[*output_length] = '\0'; + return encoded_data; +} + +// Function to base64url encode (modifies the base64 encoding output) +char *base64url_encode(const unsigned char *input, size_t len, size_t *out_len) { + char *encoded_data = base64_encode(input, len, out_len); + if (encoded_data == NULL) return NULL; + + for (size_t i = 0; i < *out_len; i++) { + if (encoded_data[i] == '+') { + encoded_data[i] = '-'; + } else if (encoded_data[i] == '/') { + encoded_data[i] = '_'; + } else if (encoded_data[i] == '=') { + encoded_data[i] = '\0'; // Remove padding character + *out_len = i; + break; + } + } + + return encoded_data; +} + +//TODO: END /** * Context for a /token operation. @@ -67,6 +121,11 @@ struct TokenContext * Uploaded 'grant_type' field from POST data. */ char *grant_type; + + /** + * Uploaded 'code_verifier' field from POST data. + */ + char *code_verifier; }; @@ -90,6 +149,7 @@ cleanup_ctx (void *cls) GNUNET_free (bc->client_secret); GNUNET_free (bc->code); GNUNET_free (bc->grant_type); + GNUNET_free (bc->code_verifier); GNUNET_free (bc); } @@ -150,6 +210,10 @@ post_iter (void *cls, .ptr = &bc->grant_type }, { + .name = "code_verifier", + .ptr = &bc->code_verifier + }, + { .name = NULL, .ptr = NULL }, @@ -356,16 +420,20 @@ CH_handler_token (struct CH_HandlerContext *hc, char *client_scope; char *client_state; char *client_redirect_uri; + char *code_challenge; + char *code_challenge_method; enum GNUNET_DB_QueryStatus qs; char *code; - qs = CH_db->validation_get (CH_db->cls, + qs = CH_db->validation_get_pkce (CH_db->cls, &bc->nonce, &client_secret, &address, &client_scope, &client_state, - &client_redirect_uri); + &client_redirect_uri, + &code_challenge, + &code_challenge_method); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: @@ -389,6 +457,83 @@ CH_handler_token (struct CH_HandlerContext *hc, case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; } + + /* Verify the code_challenge if present*/ + if (code_challenge != NULL) + { + if (bc->code_verifier == NULL){ + GNUNET_break_op (0); + GNUNET_free (client_scope); + GNUNET_free (client_secret); + GNUNET_free (client_redirect_uri); + GNUNET_free (client_state); + GNUNET_free (code_challenge); + GNUNET_free (code_challenge_method); + return TALER_MHD_reply_with_oauth_error ( + hc->connection, + MHD_HTTP_UNAUTHORIZED, + "invalid_grant", + TALER_EC_GENERIC_PARAMETER_MISSING, + "code_verifier is missing"); + } + + if (0 == strcmp (code_challenge_method, "S256")) + { + gcry_md_hd_t hd; + unsigned char hash[32]; + char *encoded_hash; + size_t encoded_len; + + // Initialize libgcrypt + if (!gcry_check_version(GCRYPT_VERSION)) { + fprintf(stderr, "libgcrypt version mismatch\n"); + exit(2); + } + gcry_md_open(&hd, GCRY_MD_SHA256, 0); + gcry_md_write(hd, bc->code_verifier, strlen(bc->code_verifier)); + memcpy(hash, gcry_md_read(hd, 0), 32); + gcry_md_close(hd); + + // Perform base64url encoding + encoded_hash = base64url_encode(hash, 32, &encoded_len); + + if (0 != strcmp(encoded_hash, code_challenge)) { + GNUNET_break_op(0); + GNUNET_free(client_scope); + GNUNET_free(client_secret); + GNUNET_free(client_redirect_uri); + GNUNET_free(client_state); + GNUNET_free(code_challenge); + GNUNET_free(code_challenge_method); + return TALER_MHD_reply_with_oauth_error( + hc->connection, + MHD_HTTP_UNAUTHORIZED, + "invalid_grant", + TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE, + "code_verifier does not match code_challenge"); + } + } + else if (0 == strcmp (code_challenge_method, "plain")) + { + if (0 != strcmp (bc->code_verifier, code_challenge)) + { + GNUNET_break_op (0); + GNUNET_free (client_scope); + GNUNET_free (client_secret); + GNUNET_free (client_redirect_uri); + GNUNET_free (client_state); + GNUNET_free (code_challenge); + GNUNET_free (code_challenge_method); + return TALER_MHD_reply_with_oauth_error ( + hc->connection, + MHD_HTTP_UNAUTHORIZED, + "invalid_grant", + TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE, + "code_verifier does not match code_challenge"); + } + } + } + if (NULL == address) { GNUNET_break_op (0); @@ -396,6 +541,8 @@ CH_handler_token (struct CH_HandlerContext *hc, GNUNET_free (client_secret); GNUNET_free (client_redirect_uri); GNUNET_free (client_state); + GNUNET_free (code_challenge); + GNUNET_free (code_challenge_method); return TALER_MHD_reply_with_oauth_error ( hc->connection, MHD_HTTP_CONFLICT,