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:
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,