libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit 2897bd23e57cf016a713876e551cd20b8a28427d
parent bcba3f58c5fc9b4a3776494d3edddceb244ab110
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat,  8 Dec 2018 22:54:33 +0100

add test for RFC 7616 and document new API

Diffstat:
MChangeLog | 3+++
Mdoc/libmicrohttpd.texi | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/include/microhttpd.h | 2+-
Msrc/testcurl/.gitignore | 3+--
Msrc/testcurl/Makefile.am | 9++++++++-
Asrc/testcurl/test_digestauth_sha256.c | 314+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 438 insertions(+), 19 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,6 @@ +Sat Dec 8 22:53:56 CET 2018 + Added test for RFC 7616 and documented new API. -CG + Sat Dec 8 17:34:58 CET 2018 Adding support for RFC 7616, experimental, needs testing and documentation still! -CG diff --git a/doc/libmicrohttpd.texi b/doc/libmicrohttpd.texi @@ -2440,15 +2440,57 @@ client with a 401 HTTP status. @node microhttpd-dauth digest @section Using Digest Authentication +MHD supports MD5 (deprecated by IETF) and SHA-256 hash algorithms +for digest authentication. The @code{MHD_DigestAuthAlgorithm} enumeration +is used to specify which algorithm should be used. + +@deftp {Enumeration} MHD_DigestAuthAlgorithm +Which digest algorithm should be used. Must be used consistently. + +@table @code +@item MHD_DIGEST_ALG_AUTO +Have MHD pick an algorithm currently considered secure. For now defaults to SHA-256. + +@item MHD_DIGEST_ALG_MD5 +Force use of (deprecated, ancient, insecure) MD5. + +@item MHD_DIGEST_ALG_SHA256 +Force use of SHA-256. + +@end table +@end deftp + + @deftypefun {char *} MHD_digest_auth_get_username (struct MHD_Connection *connection) Find and return a pointer to the username value from the request header. Return @code{NULL} if the value is not found or header does not exist. If returned value is not @code{NULL}, the value must be @code{MHD_free()}'ed. @end deftypefun +@deftypefun int MHD_digest_auth_check2 (struct MHD_Connection *connection, const char *realm, const char *username, const char *password, unsigned int nonce_timeout, enum MHD_DigestAuthAlgorithm algo) +Checks if the provided values in the WWW-Authenticate header are valid +and sound according to RFC2716. If valid return @code{MHD_YES}, otherwise return @code{MHD_NO}. + +@var{realm} must reference to a zero-terminated string representing the realm. + +@var{username} must reference to a zero-terminated string representing the username, +it is usually the returned value from MHD_digest_auth_get_username. + +@var{password} must reference to a zero-terminated string representing the password, +most probably it will be the result of a lookup of the username against a local database. + +@var{nonce_timeout} is the amount of time in seconds for a nonce to be invalid. +Most of the time it is sound to specify 300 seconds as its values. + +@var{algo} which digest algorithm should we use. +@end deftypefun + + @deftypefun int MHD_digest_auth_check (struct MHD_Connection *connection, const char *realm, const char *username, const char *password, unsigned int nonce_timeout) Checks if the provided values in the WWW-Authenticate header are valid and sound according to RFC2716. If valid return @code{MHD_YES}, otherwise return @code{MHD_NO}. +Deprecated, use @code{MHD_digest_auth_check2} instead. + @var{realm} must reference to a zero-terminated string representing the realm. @@ -2462,9 +2504,29 @@ most probably it will be the result of a lookup of the username against a local Most of the time it is sound to specify 300 seconds as its values. @end deftypefun + + +@deftypefun int MHD_digest_auth_check_digest2 (struct MHD_Connection *connection, const char *realm, const char *username, const uint8_t *digest, unsigned int nonce_timeout, enum MHD_DigestAuthAlgorithm algo) +Checks if the provided values in the WWW-Authenticate header are valid +and sound according to RFC2716. If valid return @code{MHD_YES}, otherwise return @code{MHD_NO}. + +@var{realm} must reference to a zero-terminated string representing the realm. + +@var{username} must reference to a zero-terminated string representing the username, +it is usually the returned value from MHD_digest_auth_get_username. + +@var{digest} pointer to the binary MD5 sum for the precalculated hash value ``userame:realm:password''. The size must match the selected @var{algo}! + +@var{nonce_timeout} is the amount of time in seconds for a nonce to be invalid. +Most of the time it is sound to specify 300 seconds as its values. + +@var{algo} digest authentication algorithm to use. +@end deftypefun + @deftypefun int MHD_digest_auth_check_digest (struct MHD_Connection *connection, const char *realm, const char *username, const unsigned char digest[MHD_MD5_DIGEST_SIZE], unsigned int nonce_timeout) Checks if the provided values in the WWW-Authenticate header are valid and sound according to RFC2716. If valid return @code{MHD_YES}, otherwise return @code{MHD_NO}. +Deprecated, use @code{MHD_digest_auth_check_digest2} instead. @var{realm} must reference to a zero-terminated string representing the realm. @@ -2477,6 +2539,31 @@ it is usually the returned value from MHD_digest_auth_get_username. Most of the time it is sound to specify 300 seconds as its values. @end deftypefun + +@deftypefun int MHD_queue_auth_fail_response2 (struct MHD_Connection *connection, const char *realm, const char *opaque, struct MHD_Response *response, int signal_stale, enum MHD_DigestAuthAlgorithm algo) +Queues a response to request authentication from the client, +return @code{MHD_YES} if successful, otherwise @code{MHD_NO}. + +@var{realm} must reference to a zero-terminated string representing the realm. + +@var{opaque} must reference to a zero-terminated string representing a value +that gets passed to the client and expected to be passed again to the server +as-is. This value can be a hexadecimal or base64 string. + +@var{response} a response structure to specify what shall be presented to the +client with a 401 HTTP status. + +@var{signal_stale} a value that signals "stale=true" in the response header to +indicate the invalidity of the nonce and no need to ask for authentication +parameters and only a new nonce gets generated. @code{MHD_YES} to generate a new +nonce, @code{MHD_NO} to ask for authentication parameters. + +@var{algo} which digest algorithm should we use. The same algorithm +must then be selected when checking digests received from clients! + +@end deftypefun + + @deftypefun int MHD_queue_auth_fail_response (struct MHD_Connection *connection, const char *realm, const char *opaque, struct MHD_Response *response, int signal_stale) Queues a response to request authentication from the client, return @code{MHD_YES} if successful, otherwise @code{MHD_NO}. @@ -2517,23 +2604,27 @@ ahc_echo (void *cls, const char *realm = "test@@example.com"; int ret; - username = MHD_digest_auth_get_username(connection); + username = MHD_digest_auth_get_username (connection); if (username == NULL) @{ response = MHD_create_response_from_buffer(strlen (DENIED), DENIED, MHD_RESPMEM_PERSISTENT); - ret = MHD_queue_auth_fail_response(connection, realm, - OPAQUE, - response, - MHD_NO); + ret = MHD_queue_auth_fail_response2 (connection, + realm, + OPAQUE, + response, + MHD_NO, + MHD_DIGEST_ALG_SHA256); MHD_destroy_response(response); return ret; @} - ret = MHD_digest_auth_check(connection, realm, - username, - password, - 300); + ret = MHD_digest_auth_check2 (connection, + realm, + username, + password, + 300, + MHD_DIGEST_ALG_SHA256); free(username); if ( (ret == MHD_INVALID_NONCE) || (ret == MHD_NO) ) @@ -2543,16 +2634,21 @@ ahc_echo (void *cls, MHD_RESPMEM_PERSISTENT); if (NULL == response) return MHD_NO; - ret = MHD_queue_auth_fail_response(connection, realm, - OPAQUE, - response, - (ret == MHD_INVALID_NONCE) ? MHD_YES : MHD_NO); + ret = MHD_queue_auth_fail_response2 (connection, + realm, + OPAQUE, + response, + (ret == MHD_INVALID_NONCE) ? MHD_YES : MHD_NO, + MHD_DIGEST_ALG_SHA256); MHD_destroy_response(response); return ret; @} - response = MHD_create_response_from_buffer (strlen(PAGE), PAGE, + response = MHD_create_response_from_buffer (strlen(PAGE), + PAGE, MHD_RESPMEM_PERSISTENT); - ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + ret = MHD_queue_response (connection, + MHD_HTTP_OK, + response); MHD_destroy_response(response); return ret; @} diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h @@ -3331,7 +3331,7 @@ MHD_digest_auth_check_digest (struct MHD_Connection *connection, * @return #MHD_YES on success, #MHD_NO otherwise * @ingroup authentication */ -int +_MHD_EXTERN int MHD_queue_auth_fail_response2 (struct MHD_Connection *connection, const char *realm, const char *opaque, diff --git a/src/testcurl/.gitignore b/src/testcurl/.gitignore @@ -104,4 +104,4 @@ *.exe test_quiesce_stream test_large_put_inc11 -/test_delete -\ No newline at end of file +/test_deletetest_digestauth_sha256 diff --git a/src/testcurl/Makefile.am b/src/testcurl/Makefile.am @@ -23,7 +23,7 @@ THREAD_ONLY_TESTS = \ test_long_header11 \ test_iplimit11 \ test_termination \ - test_timeout + test_timeout @@ -42,6 +42,7 @@ endif if ENABLE_DAUTH THREAD_ONLY_TESTS += \ test_digestauth \ + test_digestauth_sha256 \ test_digestauth_with_arguments endif @@ -156,6 +157,12 @@ test_digestauth_LDADD = \ $(top_builddir)/src/microhttpd/libmicrohttpd.la \ @LIBGCRYPT_LIBS@ @LIBCURL@ +test_digestauth_sha256_SOURCES = \ + test_digestauth_sha256.c +test_digestauth_sha256_LDADD = \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la \ + @LIBGCRYPT_LIBS@ @LIBCURL@ + test_digestauth_with_arguments_SOURCES = \ test_digestauth_with_arguments.c test_digestauth_with_arguments_LDADD = \ diff --git a/src/testcurl/test_digestauth_sha256.c b/src/testcurl/test_digestauth_sha256.c @@ -0,0 +1,314 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2010, 2018 Christian Grothoff + + libmicrohttpd is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + libmicrohttpd 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libmicrohttpd; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * @file daemontest_digestauth_sha256.c + * @brief Testcase for libmicrohttpd Digest Auth with SHA256 + * @author Amr Ali + * @author Christian Grothoff + */ + +#include "MHD_config.h" +#include "platform.h" +#include <curl/curl.h> +#include <microhttpd.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#ifdef MHD_HTTPS_REQUIRE_GRYPT +#ifdef HAVE_GCRYPT_H +#include <gcrypt.h> +#endif +#endif /* MHD_HTTPS_REQUIRE_GRYPT */ + +#ifndef WINDOWS +#include <sys/socket.h> +#include <unistd.h> +#else +#include <wincrypt.h> +#endif + +#define PAGE "<html><head><title>libmicrohttpd demo</title></head><body>Access granted</body></html>" + +#define DENIED "<html><head><title>libmicrohttpd demo</title></head><body>Access denied</body></html>" + +#define MY_OPAQUE "11733b200778ce33060f31c9af70a870ba96ddd4" + +struct CBC +{ + char *buf; + size_t pos; + size_t size; +}; + + +static size_t +copyBuffer (void *ptr, + size_t size, + size_t nmemb, + void *ctx) +{ + struct CBC *cbc = ctx; + + if (cbc->pos + size * nmemb > cbc->size) + return 0; /* overflow */ + memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb); + cbc->pos += size * nmemb; + return size * nmemb; +} + + +static int +ahc_echo (void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **unused) +{ + struct MHD_Response *response; + char *username; + const char *password = "testpass"; + const char *realm = "test@example.com"; + int ret; + (void)cls;(void)url; /* Unused. Silent compiler warning. */ + (void)method;(void)version;(void)upload_data; /* Unused. Silent compiler warning. */ + (void)upload_data_size;(void)unused; /* Unused. Silent compiler warning. */ + + username = MHD_digest_auth_get_username (connection); + if ( (username == NULL) || + (0 != strcmp (username, "testuser")) ) + { + response = MHD_create_response_from_buffer (strlen (DENIED), + DENIED, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_auth_fail_response2 (connection, + realm, + MY_OPAQUE, + response, + MHD_NO, + MHD_DIGEST_ALG_SHA256); + MHD_destroy_response(response); + return ret; + } + ret = MHD_digest_auth_check2 (connection, + realm, + username, + password, + 300, + MHD_DIGEST_ALG_SHA256); + free (username); + if ( (ret == MHD_INVALID_NONCE) || + (ret == MHD_NO) ) + { + response = MHD_create_response_from_buffer (strlen (DENIED), + DENIED, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + return MHD_NO; + ret = MHD_queue_auth_fail_response2 (connection, + realm, + MY_OPAQUE, + response, + (MHD_INVALID_NONCE == ret) ? MHD_YES : MHD_NO, + MHD_DIGEST_ALG_SHA256); + MHD_destroy_response(response); + return ret; + } + response = MHD_create_response_from_buffer (strlen(PAGE), + PAGE, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_OK, + response); + MHD_destroy_response (response); + return ret; +} + + +static int +testDigestAuth () +{ + CURL *c; + CURLcode errornum; + struct MHD_Daemon *d; + struct CBC cbc; + char buf[2048]; + char rnd[8]; + int port; + char url[128]; +#ifndef WINDOWS + int fd; + size_t len; + size_t off = 0; +#endif /* ! WINDOWS */ + + if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) + port = 0; + else + port = 1165; + + cbc.buf = buf; + cbc.size = 2048; + cbc.pos = 0; +#ifndef WINDOWS + fd = open ("/dev/urandom", + O_RDONLY); + if (-1 == fd) + { + fprintf (stderr, + "Failed to open `%s': %s\n", + "/dev/urandom", + strerror(errno)); + return 1; + } + while (off < 8) + { + len = read (fd, + rnd, + 8); + if (len == (size_t)-1) + { + fprintf (stderr, + "Failed to read `%s': %s\n", + "/dev/urandom", + strerror(errno)); + (void) close(fd); + return 1; + } + off += len; + } + (void) close(fd); +#else + { + HCRYPTPROV cc; + BOOL b; + + b = CryptAcquireContext (&cc, + NULL, + NULL, + PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT); + if (b == 0) + { + fprintf (stderr, + "Failed to acquire crypto provider context: %lu\n", + GetLastError ()); + return 1; + } + b = CryptGenRandom (cc, 8, (BYTE*)rnd); + if (b == 0) + { + fprintf (stderr, + "Failed to generate 8 random bytes: %lu\n", + GetLastError ()); + } + CryptReleaseContext (cc, 0); + if (b == 0) + return 1; + } +#endif + d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, + port, NULL, NULL, + &ahc_echo, PAGE, + MHD_OPTION_DIGEST_AUTH_RANDOM, sizeof (rnd), rnd, + MHD_OPTION_NONCE_NC_SIZE, 300, + MHD_OPTION_END); + if (d == NULL) + return 1; + if (0 == port) + { + const union MHD_DaemonInfo *dinfo; + + dinfo = MHD_get_daemon_info (d, + MHD_DAEMON_INFO_BIND_PORT); + if ( (NULL == dinfo) || + (0 == dinfo->port) ) + { + MHD_stop_daemon (d); + return 32; + } + port = (int)dinfo->port; + } + snprintf (url, + sizeof (url), + "http://127.0.0.1:%d/bar%%20foo%%3Fkey%%3Dvalue", + port); + c = curl_easy_init (); + curl_easy_setopt (c, CURLOPT_URL, url); + curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer); + curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); + curl_easy_setopt (c, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + curl_easy_setopt (c, CURLOPT_USERPWD, "testuser:testpass"); + curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + /* NOTE: use of CONNECTTIMEOUT without also + setting NOSIGNAL results in really weird + crashes on my system!*/ + curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); + if (CURLE_OK != (errornum = curl_easy_perform (c))) + { + fprintf (stderr, + "curl_easy_perform failed: `%s'\n", + curl_easy_strerror (errornum)); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 2; + } + curl_easy_cleanup (c); + MHD_stop_daemon (d); + if (cbc.pos != strlen (PAGE)) + return 4; + if (0 != strncmp (PAGE, cbc.buf, strlen (PAGE))) + return 8; + return 0; +} + + +int +main (int argc, char *const *argv) +{ + unsigned int errorCount = 0; + curl_version_info_data *d = curl_version_info (CURLVERSION_NOW); + (void)argc; (void)argv; /* Unused. Silent compiler warning. */ + + /* curl added SHA256 support in 7.57 = 7.0x39 */ + if (d->version_num < 0x073900) + return 77; /* skip test, curl is too old */ +#ifdef MHD_HTTPS_REQUIRE_GRYPT +#ifdef HAVE_GCRYPT_H + gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); +#ifdef GCRYCTL_INITIALIZATION_FINISHED + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); +#endif +#endif +#endif /* MHD_HTTPS_REQUIRE_GRYPT */ +if (0 != curl_global_init (CURL_GLOBAL_WIN32)) + return 2; + errorCount += testDigestAuth (); + if (errorCount != 0) + fprintf (stderr, "Error (code: %u)\n", errorCount); + curl_global_cleanup (); + return errorCount != 0; /* 0 == pass */ +}