challenger-httpd_common.c (10467B)
1 /* 2 This file is part of Challenger 3 Copyright (C) 2023, 2024 Taler Systems SA 4 5 Challenger is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 Challenger is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 Challenger; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file challenger-httpd_common.c 18 * @brief common helper functions 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 #include "challenger-httpd_common.h" 23 #include <gnunet/gnunet_pq_lib.h> 24 #include "challenger-database/validation_get.h" 25 26 27 /** 28 * Prefix required for all Bearer tokens. 29 */ 30 #define RFC_8959_PREFIX "secret-token:" 31 32 33 // FIXME: enums would be nicer... 34 int 35 CH_get_output_type (struct MHD_Connection *connection) 36 { 37 const char *mime; 38 double q_html; 39 double q_json; 40 41 mime = MHD_lookup_connection_value (connection, 42 MHD_HEADER_KIND, 43 MHD_HTTP_HEADER_ACCEPT); 44 if (NULL == mime) 45 return 0; /* default to HTML */ 46 q_html = TALER_pattern_matches (mime, 47 "text/html"); 48 q_json = TALER_pattern_matches (mime, 49 "application/json"); 50 return (q_html > q_json) ? 0 : 1; 51 } 52 53 54 const char * 55 CH_get_client_secret (struct MHD_Connection *connection) 56 { 57 const char *bearer = "Bearer "; 58 const char *auth; 59 const char *tok; 60 61 auth = MHD_lookup_connection_value (connection, 62 MHD_HEADER_KIND, 63 MHD_HTTP_HEADER_AUTHORIZATION); 64 if (NULL == auth) 65 return NULL; 66 if (0 != strncmp (auth, 67 bearer, 68 strlen (bearer))) 69 { 70 return NULL; 71 } 72 tok = auth + strlen (bearer); 73 while (' ' == *tok) 74 tok++; 75 if (0 != strncasecmp (tok, 76 RFC_8959_PREFIX, 77 strlen (RFC_8959_PREFIX))) 78 { 79 return NULL; 80 } 81 return tok; 82 } 83 84 85 char * 86 CH_compute_code (const struct CHALLENGER_ValidationNonceP *nonce, 87 const char *client_secret, 88 const char *client_scope, 89 const json_t *address, 90 const char *client_redirect_uri) 91 { 92 char *code; 93 char *ns; 94 char *hs; 95 struct GNUNET_ShortHashCode h; 96 char *astr; 97 98 astr = json_dumps (address, 99 JSON_COMPACT); 100 GNUNET_assert (GNUNET_YES == 101 GNUNET_CRYPTO_hkdf_gnunet ( 102 &h, 103 sizeof (h), 104 nonce, 105 sizeof (*nonce), 106 client_secret, 107 strlen (client_secret), 108 GNUNET_CRYPTO_kdf_arg_string (astr), 109 GNUNET_CRYPTO_kdf_arg_string (client_redirect_uri), 110 GNUNET_CRYPTO_kdf_arg_string (NULL != client_scope 111 ? client_scope 112 : ""))); 113 free (astr); 114 ns = GNUNET_STRINGS_data_to_string_alloc (nonce, 115 sizeof (*nonce)); 116 hs = GNUNET_STRINGS_data_to_string_alloc (&h, 117 sizeof (h)); 118 GNUNET_asprintf (&code, 119 "%s-%s", 120 ns, 121 hs); 122 GNUNET_free (ns); 123 GNUNET_free (hs); 124 return code; 125 } 126 127 128 enum GNUNET_GenericReturnValue 129 CH_code_to_nonce (const char *code, 130 struct CHALLENGER_ValidationNonceP *nonce) 131 { 132 const char *dash = strchr (code, '-'); 133 134 if (NULL == dash) 135 { 136 GNUNET_break_op (0); 137 return GNUNET_SYSERR; 138 } 139 if (GNUNET_OK != 140 GNUNET_STRINGS_string_to_data (code, 141 (size_t) (dash - code), 142 nonce, 143 sizeof (*nonce))) 144 { 145 GNUNET_break_op (0); 146 return GNUNET_SYSERR; 147 } 148 return GNUNET_OK; 149 } 150 151 152 enum MHD_Result 153 CH_reply_with_oauth_error ( 154 struct MHD_Connection *connection, 155 unsigned int http_status, 156 const char *oauth_error, 157 enum TALER_ErrorCode ec, 158 const char *detail) 159 { 160 struct MHD_Response *resp; 161 enum MHD_Result mret; 162 163 resp = TALER_MHD_make_json_steal ( 164 GNUNET_JSON_PACK ( 165 TALER_MHD_PACK_EC (ec), 166 GNUNET_JSON_pack_string ("error", 167 oauth_error), 168 GNUNET_JSON_pack_allow_null ( 169 GNUNET_JSON_pack_string ("detail", 170 detail)))); 171 if (MHD_HTTP_UNAUTHORIZED == http_status) 172 GNUNET_break (MHD_YES == 173 MHD_add_response_header ( 174 resp, 175 "WWW-Authenticate", 176 "Bearer, error=\"invalid_token\"")); 177 mret = MHD_queue_response (connection, 178 http_status, 179 resp); 180 MHD_destroy_response (resp); 181 return mret; 182 } 183 184 185 enum MHD_Result 186 TALER_MHD_redirect_with_oauth_status ( 187 struct MHD_Connection *connection, 188 const char *client_redirect_uri, 189 const char *state, 190 const char *oauth_error, 191 const char *oauth_error_description, 192 const char *oauth_error_uri) 193 { 194 struct MHD_Response *response; 195 unsigned int http_status; 196 197 if (0 == CH_get_output_type (connection)) 198 { 199 char *url; 200 char *enc_err; 201 char *enc_state; 202 char *enc_desc = NULL; 203 char *enc_uri = NULL; 204 205 response = MHD_create_response_from_buffer (strlen (oauth_error), 206 (void *) oauth_error, 207 MHD_RESPMEM_PERSISTENT); 208 if (NULL == response) 209 { 210 GNUNET_break (0); 211 return MHD_NO; 212 } 213 TALER_MHD_add_global_headers (response, 214 false); 215 GNUNET_break (MHD_YES == 216 MHD_add_response_header (response, 217 MHD_HTTP_HEADER_CONTENT_TYPE, 218 "text/plain")); 219 enc_err = TALER_urlencode (oauth_error); 220 enc_state = TALER_urlencode (state); 221 if (NULL != oauth_error_description) 222 enc_desc = TALER_urlencode (oauth_error_description); 223 if (NULL != oauth_error_uri) 224 enc_uri = TALER_urlencode (oauth_error_uri); 225 url = TALER_url_join ( 226 client_redirect_uri, 227 "", 228 "state", enc_state, 229 "error", enc_err, 230 "error_description", enc_desc, 231 "error_uri", enc_uri, 232 NULL); 233 GNUNET_free (enc_err); 234 GNUNET_free (enc_state); 235 GNUNET_free (enc_desc); 236 GNUNET_free (enc_uri); 237 if (MHD_NO == 238 MHD_add_response_header (response, 239 MHD_HTTP_HEADER_LOCATION, 240 url)) 241 { 242 GNUNET_break (0); 243 MHD_destroy_response (response); 244 GNUNET_free (url); 245 return MHD_NO; 246 } 247 http_status = MHD_HTTP_FOUND; 248 GNUNET_free (url); 249 } 250 else 251 { 252 json_t *args; 253 254 args = GNUNET_JSON_PACK ( 255 GNUNET_JSON_pack_string ("state", 256 state), 257 GNUNET_JSON_pack_string ("error", 258 oauth_error), 259 GNUNET_JSON_pack_allow_null ( 260 GNUNET_JSON_pack_string ("description", 261 oauth_error_description)), 262 GNUNET_JSON_pack_allow_null ( 263 GNUNET_JSON_pack_string ("uri", 264 oauth_error_uri))); 265 266 response = TALER_MHD_make_json_steal (args); 267 TALER_MHD_add_global_headers (response, 268 false); 269 http_status = MHD_HTTP_TOO_MANY_REQUESTS; 270 } 271 272 { 273 enum MHD_Result ret; 274 275 ret = MHD_queue_response (connection, 276 http_status, 277 response); 278 MHD_destroy_response (response); 279 return ret; 280 } 281 } 282 283 284 enum GNUNET_GenericReturnValue 285 CH_build_full_redirect_url (const struct CHALLENGER_ValidationNonceP *nonce, 286 struct MHD_Connection *connection, 287 char **url) 288 { 289 char *client_secret; 290 json_t *address; 291 char *client_scope; 292 char *client_state; 293 char *client_redirect_uri; 294 enum GNUNET_DB_QueryStatus qs; 295 enum MHD_Result ret; 296 297 qs = CHALLENGERDB_validation_get (CH_context, 298 nonce, 299 &client_secret, 300 &address, 301 &client_scope, 302 &client_state, 303 &client_redirect_uri); 304 switch (qs) 305 { 306 case GNUNET_DB_STATUS_HARD_ERROR: 307 case GNUNET_DB_STATUS_SOFT_ERROR: 308 GNUNET_break (0); 309 ret = TALER_MHD_reply_with_error ( 310 connection, 311 MHD_HTTP_INTERNAL_SERVER_ERROR, 312 TALER_EC_GENERIC_DB_FETCH_FAILED, 313 "validation_get"); 314 return (MHD_NO == ret) ? GNUNET_SYSERR : GNUNET_NO; 315 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 316 GNUNET_break (0); 317 ret = TALER_MHD_reply_with_error ( 318 connection, 319 MHD_HTTP_NOT_FOUND, 320 TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN, 321 NULL); 322 return (MHD_NO == ret) ? GNUNET_SYSERR : GNUNET_NO; 323 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 324 break; 325 } 326 { 327 char *code; 328 329 code = CH_compute_code (nonce, 330 client_secret, 331 client_scope, 332 address, 333 client_redirect_uri); 334 if (NULL == client_state) 335 { 336 GNUNET_asprintf (url, 337 "%s?code=%s", 338 client_redirect_uri, 339 code); 340 } 341 else 342 { 343 char *url_encoded; 344 345 url_encoded = TALER_urlencode (client_state); 346 GNUNET_asprintf (url, 347 "%s?code=%s&state=%s", 348 client_redirect_uri, 349 code, 350 url_encoded); 351 GNUNET_free (url_encoded); 352 } 353 GNUNET_free (code); 354 } 355 json_decref (address); 356 GNUNET_free (client_scope); 357 GNUNET_free (client_secret); 358 GNUNET_free (client_redirect_uri); 359 GNUNET_free (client_state); 360 return GNUNET_OK; 361 }