test_digestauth_sha256.c (10317B)
1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2010, 2018 Christian Grothoff 4 Copyright (C) 2019-2022 Evgeny Grin (Karlson2k) 5 6 libmicrohttpd is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published 8 by the Free Software Foundation; either version 2, or (at your 9 option) any later version. 10 11 libmicrohttpd is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with libmicrohttpd; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 Boston, MA 02110-1301, USA. 20 */ 21 22 /** 23 * @file daemontest_digestauth_sha256.c 24 * @brief Testcase for libmicrohttpd Digest Auth with SHA256 25 * @author Amr Ali 26 * @author Christian Grothoff 27 * @author Karlson2k (Evgeny Grin) 28 */ 29 30 #include "mhd_options.h" 31 #include "platform.h" 32 #include <curl/curl.h> 33 #include <microhttpd.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <time.h> 37 #include <errno.h> 38 39 /* Turn any MHD_PANIC() or failing mhd_assert() reached from this 40 test into a marked, classifiable test error (TESTING.md, P5). */ 41 #include "mhd_panic_tripwire.h" 42 43 #if defined(MHD_HTTPS_REQUIRE_GCRYPT) && \ 44 (defined(MHD_SHA256_TLSLIB) || defined(MHD_MD5_TLSLIB)) 45 #define NEED_GCRYP_INIT 1 46 #include <gcrypt.h> 47 #endif /* MHD_HTTPS_REQUIRE_GCRYPT && (MHD_SHA256_TLSLIB || MHD_MD5_TLSLIB) */ 48 49 #ifndef WINDOWS 50 #include <sys/socket.h> 51 #include <unistd.h> 52 #else 53 #include <wincrypt.h> 54 #endif 55 56 #define PAGE \ 57 "<html><head><title>libmicrohttpd demo</title></head><body>Access granted</body></html>" 58 59 #define DENIED \ 60 "<html><head><title>libmicrohttpd demo</title></head><body>Access denied</body></html>" 61 62 #define MY_OPAQUE "11733b200778ce33060f31c9af70a870ba96ddd4" 63 64 struct CBC 65 { 66 char *buf; 67 size_t pos; 68 size_t size; 69 }; 70 71 72 static size_t 73 copyBuffer (void *ptr, 74 size_t size, 75 size_t nmemb, 76 void *ctx) 77 { 78 struct CBC *cbc = ctx; 79 80 if (cbc->pos + size * nmemb > cbc->size) 81 return 0; /* overflow */ 82 memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb); 83 cbc->pos += size * nmemb; 84 return size * nmemb; 85 } 86 87 88 static enum MHD_Result 89 ahc_echo (void *cls, 90 struct MHD_Connection *connection, 91 const char *url, 92 const char *method, 93 const char *version, 94 const char *upload_data, 95 size_t *upload_data_size, 96 void **req_cls) 97 { 98 struct MHD_Response *response; 99 char *username; 100 const char *password = "testpass"; 101 const char *realm = "test@example.com"; 102 enum MHD_Result ret; 103 int ret_i; 104 static int already_called_marker; 105 (void) cls; (void) url; /* Unused. Silent compiler warning. */ 106 (void) method; (void) version; (void) upload_data; /* Unused. Silent compiler warning. */ 107 (void) upload_data_size; (void) req_cls; /* Unused. Silent compiler warning. */ 108 109 if (&already_called_marker != *req_cls) 110 { /* Called for the first time, request not fully read yet */ 111 *req_cls = &already_called_marker; 112 /* Wait for complete request */ 113 return MHD_YES; 114 } 115 116 username = MHD_digest_auth_get_username (connection); 117 if ( (username == NULL) || 118 (0 != strcmp (username, "testuser")) ) 119 { 120 response = MHD_create_response_from_buffer_static (strlen (DENIED), 121 DENIED); 122 ret = MHD_queue_auth_fail_response2 (connection, 123 realm, 124 MY_OPAQUE, 125 response, 126 MHD_NO, 127 MHD_DIGEST_ALG_SHA256); 128 MHD_destroy_response (response); 129 return ret; 130 } 131 ret_i = MHD_digest_auth_check2 (connection, 132 realm, 133 username, 134 password, 135 300, 136 MHD_DIGEST_ALG_SHA256); 137 MHD_free (username); 138 if (ret_i != MHD_YES) 139 { 140 response = MHD_create_response_from_buffer_static (strlen (DENIED), 141 DENIED); 142 if (NULL == response) 143 return MHD_NO; 144 ret = MHD_queue_auth_fail_response2 (connection, 145 realm, 146 MY_OPAQUE, 147 response, 148 (MHD_INVALID_NONCE == ret_i) ? 149 MHD_YES : MHD_NO, 150 MHD_DIGEST_ALG_SHA256); 151 MHD_destroy_response (response); 152 return ret; 153 } 154 response = MHD_create_response_from_buffer_static (strlen (PAGE), 155 PAGE); 156 ret = MHD_queue_response (connection, 157 MHD_HTTP_OK, 158 response); 159 MHD_destroy_response (response); 160 return ret; 161 } 162 163 164 static unsigned int 165 testDigestAuth (void) 166 { 167 CURL *c; 168 CURLcode errornum; 169 struct MHD_Daemon *d; 170 struct CBC cbc; 171 char buf[2048]; 172 char rnd[8]; 173 uint16_t port; 174 char url[128]; 175 #ifndef WINDOWS 176 int fd; 177 size_t len; 178 size_t off = 0; 179 #endif /* ! WINDOWS */ 180 181 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) 182 port = 0; 183 else 184 port = 1167; 185 186 cbc.buf = buf; 187 cbc.size = 2048; 188 cbc.pos = 0; 189 #ifndef WINDOWS 190 fd = open ("/dev/urandom", 191 O_RDONLY); 192 if (-1 == fd) 193 { 194 fprintf (stderr, 195 "Failed to open `%s': %s\n", 196 "/dev/urandom", 197 strerror (errno)); 198 return 1; 199 } 200 while (off < 8) 201 { 202 len = (size_t) read (fd, 203 rnd + off, 204 8 - off); 205 if (len == (size_t) -1) 206 { 207 fprintf (stderr, 208 "Failed to read `%s': %s\n", 209 "/dev/urandom", 210 strerror (errno)); 211 (void) close (fd); 212 return 1; 213 } 214 off += len; 215 } 216 (void) close (fd); 217 #else 218 { 219 HCRYPTPROV cc; 220 BOOL b; 221 222 b = CryptAcquireContext (&cc, 223 NULL, 224 NULL, 225 PROV_RSA_FULL, 226 CRYPT_VERIFYCONTEXT); 227 if (b == 0) 228 { 229 fprintf (stderr, 230 "Failed to acquire crypto provider context: %lu\n", 231 GetLastError ()); 232 return 1; 233 } 234 b = CryptGenRandom (cc, 8, (BYTE *) rnd); 235 if (b == 0) 236 { 237 fprintf (stderr, 238 "Failed to generate 8 random bytes: %lu\n", 239 GetLastError ()); 240 } 241 CryptReleaseContext (cc, 0); 242 if (b == 0) 243 return 1; 244 } 245 #endif 246 d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, 247 port, NULL, NULL, 248 &ahc_echo, NULL, 249 MHD_OPTION_DIGEST_AUTH_RANDOM, sizeof (rnd), rnd, 250 MHD_OPTION_NONCE_NC_SIZE, 300, 251 MHD_OPTION_DIGEST_AUTH_DEFAULT_MAX_NC, (uint32_t) 999, 252 MHD_OPTION_END); 253 if (d == NULL) 254 return 1; 255 if (0 == port) 256 { 257 const union MHD_DaemonInfo *dinfo; 258 259 dinfo = MHD_get_daemon_info (d, 260 MHD_DAEMON_INFO_BIND_PORT); 261 if ( (NULL == dinfo) || 262 (0 == dinfo->port) ) 263 { 264 MHD_stop_daemon (d); 265 return 32; 266 } 267 port = dinfo->port; 268 } 269 snprintf (url, 270 sizeof (url), 271 "http://127.0.0.1:%u/bar%%20foo?key=value", 272 (unsigned int) port); 273 c = curl_easy_init (); 274 curl_easy_setopt (c, CURLOPT_URL, url); 275 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 276 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 277 curl_easy_setopt (c, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); 278 curl_easy_setopt (c, CURLOPT_USERPWD, "testuser:testpass"); 279 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); 280 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 281 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 282 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 283 /* NOTE: use of CONNECTTIMEOUT without also 284 setting NOSIGNAL results in really weird 285 crashes on my system!*/ 286 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); 287 if (CURLE_OK != (errornum = curl_easy_perform (c))) 288 { 289 fprintf (stderr, 290 "curl_easy_perform failed: `%s'\n", 291 curl_easy_strerror (errornum)); 292 curl_easy_cleanup (c); 293 MHD_stop_daemon (d); 294 return 2; 295 } 296 curl_easy_cleanup (c); 297 MHD_stop_daemon (d); 298 if (cbc.pos != strlen (PAGE)) 299 return 4; 300 if (0 != strncmp (PAGE, cbc.buf, strlen (PAGE))) 301 return 8; 302 return 0; 303 } 304 305 306 int 307 main (int argc, char *const *argv) 308 { 309 unsigned int errorCount = 0; 310 curl_version_info_data *d = curl_version_info (CURLVERSION_NOW); 311 (void) argc; (void) argv; /* Unused. Silent compiler warning. */ 312 #if (LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR == 62) 313 if (1) 314 { 315 fprintf (stderr, "libcurl version 7.62.x has bug in processing" 316 "URI with GET arguments for Digest Auth.\n"); 317 fprintf (stderr, "This test cannot be performed.\n"); 318 exit (77); 319 } 320 #endif /* libcurl version 7.62.x */ 321 322 #ifdef CURL_VERSION_SSPI 323 if (0 != (d->features & CURL_VERSION_SSPI)) 324 return 77; /* Skip test, W32 SSPI doesn't support sha256 digest */ 325 #endif /* CURL_VERSION_SSPI */ 326 327 /* curl added SHA256 support in 7.57 = 7.0x39 */ 328 if (d->version_num < 0x073900) 329 return 77; /* skip test, curl is too old */ 330 #ifdef NEED_GCRYP_INIT 331 gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); 332 #ifdef GCRYCTL_INITIALIZATION_FINISHED 333 gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); 334 #endif /* GCRYCTL_INITIALIZATION_FINISHED */ 335 #endif /* NEED_GCRYP_INIT */ 336 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 337 return 2; 338 errorCount += testDigestAuth (); 339 if (errorCount != 0) 340 fprintf (stderr, "Error (code: %u)\n", errorCount); 341 curl_global_cleanup (); 342 return (0 == errorCount) ? 0 : 1; /* 0 == pass */ 343 }