/* This file is part of libmicrohttpd Copyright (C) 2010, 2011, 2012, 2015, 2018 Daniel Pittman and Christian Grothoff Copyright (C) 2014-2022 Evgeny Grin (Karlson2k) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** * @file digestauth.c * @brief Implements HTTP digest authentication * @author Amr Ali * @author Matthieu Speder * @author Christian Grothoff (RFC 7616 support) * @author Karlson2k (Evgeny Grin) (fixes, new API, improvements, large rewrite, * many RFC 7616 features implementation, * old RFC 2069 support) */ #include "digestauth.h" #include "gen_auth.h" #include "platform.h" #include "mhd_limits.h" #include "internal.h" #include "response.h" #ifdef MHD_MD5_SUPPORT # include "mhd_md5_wrap.h" #endif /* MHD_MD5_SUPPORT */ #ifdef MHD_SHA256_SUPPORT # include "mhd_sha256_wrap.h" #endif /* MHD_SHA256_SUPPORT */ #ifdef MHD_SHA512_256_SUPPORT # include "sha512_256.h" #endif /* MHD_SHA512_256_SUPPORT */ #include "mhd_locks.h" #include "mhd_mono_clock.h" #include "mhd_str.h" #include "mhd_compat.h" #include "mhd_bithelpers.h" #include "mhd_assert.h" /** * Allow re-use of the nonce-nc map array slot after #REUSE_TIMEOUT seconds, * if this slot is needed for the new nonce, while the old nonce was not used * even one time by the client. * Typically clients immediately use generated nonce for new request. */ #define REUSE_TIMEOUT 30 /** * The maximum value of artificial timestamp difference to avoid clashes. * The value must be suitable for bitwise AND operation. */ #define DAUTH_JUMPBACK_MAX (0x7F) /** * 48 bit value in bytes */ #define TIMESTAMP_BIN_SIZE (48 / 8) /** * Trim value to the TIMESTAMP_BIN_SIZE size */ #define TRIM_TO_TIMESTAMP(value) \ ((value) & ((UINT64_C (1) << (TIMESTAMP_BIN_SIZE * 8)) - 1)) /** * The printed timestamp size in chars */ #define TIMESTAMP_CHARS_LEN (TIMESTAMP_BIN_SIZE * 2) /** * Standard server nonce length, not including terminating null, * * @param digest_size digest size */ #define NONCE_STD_LEN(digest_size) \ ((digest_size) * 2 + TIMESTAMP_CHARS_LEN) #ifdef MHD_SHA512_256_SUPPORT /** * Maximum size of any digest hash supported by MHD. * (SHA-512/256 > MD5). */ #define MAX_DIGEST SHA512_256_DIGEST_SIZE /** * The common size of SHA-256 digest and SHA-512/256 digest */ #define SHA256_SHA512_256_DIGEST_SIZE SHA512_256_DIGEST_SIZE #elif defined(MHD_SHA256_SUPPORT) /** * Maximum size of any digest hash supported by MHD. * (SHA-256 > MD5). */ #define MAX_DIGEST SHA256_DIGEST_SIZE /** * The common size of SHA-256 digest and SHA-512/256 digest */ #define SHA256_SHA512_256_DIGEST_SIZE SHA256_DIGEST_SIZE #elif defined(MHD_MD5_SUPPORT) /** * Maximum size of any digest hash supported by MHD. */ #define MAX_DIGEST MD5_DIGEST_SIZE #else /* ! MHD_MD5_SUPPORT */ #error At least one hashing algorithm must be enabled #endif /* ! MHD_MD5_SUPPORT */ /** * Macro to avoid using VLAs if the compiler does not support them. */ #ifndef HAVE_C_VARARRAYS /** * Return #MAX_DIGEST. * * @param n length of the digest to be used for a VLA */ #define VLA_ARRAY_LEN_DIGEST(n) (MAX_DIGEST) #else /** * Return @a n. * * @param n length of the digest to be used for a VLA */ #define VLA_ARRAY_LEN_DIGEST(n) (n) #endif /** * Check that @a n is below #MAX_DIGEST */ #define VLA_CHECK_LEN_DIGEST(n) \ do { if ((n) > MAX_DIGEST) MHD_PANIC (_ ("VLA too big.\n")); } while (0) /** * Maximum length of a username for digest authentication. */ #define MAX_USERNAME_LENGTH 128 /** * Maximum length of a realm for digest authentication. */ #define MAX_REALM_LENGTH 256 /** * Maximum length of the response in digest authentication. */ #define MAX_AUTH_RESPONSE_LENGTH (MAX_DIGEST * 2) /** * The required prefix of parameter with the extended notation */ #define MHD_DAUTH_EXT_PARAM_PREFIX "UTF-8'" /** * The minimal size of the prefix for parameter with the extended notation */ #define MHD_DAUTH_EXT_PARAM_MIN_LEN \ MHD_STATICSTR_LEN_ (MHD_DAUTH_EXT_PARAM_PREFIX "'") /** * The result of nonce-nc map array check. */ enum MHD_CheckNonceNC_ { /** * The nonce and NC are OK (valid and NC was not used before). */ MHD_CHECK_NONCENC_OK = MHD_DAUTH_OK, /** * The 'nonce' was overwritten with newer 'nonce' in the same slot or * NC was already used. * The validity of the 'nonce' was not be checked. */ MHD_CHECK_NONCENC_STALE = MHD_DAUTH_NONCE_STALE, /** * The 'nonce' is wrong, it was not generated before. */ MHD_CHECK_NONCENC_WRONG = MHD_DAUTH_NONCE_WRONG, }; /** * Get base hash calculation algorithm from #MHD_DigestAuthAlgo3 value. * @param algo3 the MHD_DigestAuthAlgo3 value * @return the base hash calculation algorithm */ _MHD_static_inline enum MHD_DigestBaseAlgo get_base_digest_algo (enum MHD_DigestAuthAlgo3 algo3) { unsigned int base_algo; base_algo = ((unsigned int) algo3) & ~((unsigned int) (MHD_DIGEST_AUTH_ALGO3_NON_SESSION | MHD_DIGEST_AUTH_ALGO3_NON_SESSION)); return (enum MHD_DigestBaseAlgo) base_algo; } /** * Get digest size for specified algorithm. * * Internal inline version. * @param algo3 the algorithm to check * @return the size of the digest or zero if the input value is not * supported/valid */ _MHD_static_inline size_t digest_get_hash_size (enum MHD_DigestAuthAlgo3 algo3) { #ifdef MHD_MD5_SUPPORT mhd_assert (MHD_MD5_DIGEST_SIZE == MD5_DIGEST_SIZE); #endif /* MHD_MD5_SUPPORT */ #ifdef MHD_SHA256_SUPPORT mhd_assert (MHD_SHA256_DIGEST_SIZE == SHA256_DIGEST_SIZE); #endif /* MHD_SHA256_SUPPORT */ #ifdef MHD_SHA512_256_SUPPORT mhd_assert (MHD_SHA512_256_DIGEST_SIZE == SHA512_256_DIGEST_SIZE); #ifdef MHD_SHA256_SUPPORT mhd_assert (SHA256_DIGEST_SIZE == SHA512_256_DIGEST_SIZE); #endif /* MHD_SHA256_SUPPORT */ #endif /* MHD_SHA512_256_SUPPORT */ /* Only one algorithm must be specified */ mhd_assert (1 == \ (((0 != (algo3 & MHD_DIGEST_BASE_ALGO_MD5)) ? 1 : 0) \ + ((0 != (algo3 & MHD_DIGEST_BASE_ALGO_SHA256)) ? 1 : 0) \ + ((0 != (algo3 & MHD_DIGEST_BASE_ALGO_SHA512_256)) ? 1 : 0))); #ifdef MHD_MD5_SUPPORT if (0 != (((unsigned int) algo3) & ((unsigned int) MHD_DIGEST_BASE_ALGO_MD5))) return MHD_MD5_DIGEST_SIZE; else #endif /* MHD_MD5_SUPPORT */ #if defined(MHD_SHA256_SUPPORT) && defined(MHD_SHA512_256_SUPPORT) if (0 != (((unsigned int) algo3) & ( ((unsigned int) MHD_DIGEST_BASE_ALGO_SHA256) | ((unsigned int) MHD_DIGEST_BASE_ALGO_SHA512_256)))) return MHD_SHA256_DIGEST_SIZE; /* The same as SHA512_256_DIGEST_SIZE */ else #elif defined(MHD_SHA256_SUPPORT) if (0 != (((unsigned int) algo3) & ((unsigned int) MHD_DIGEST_BASE_ALGO_SHA256))) return MHD_SHA256_DIGEST_SIZE; else #elif defined(MHD_SHA512_256_SUPPORT) if (0 != (((unsigned int) algo3) & ((unsigned int) MHD_DIGEST_BASE_ALGO_SHA512_256))) return MHD_SHA512_256_DIGEST_SIZE; else #endif /* MHD_SHA512_256_SUPPORT */ (void) 0; /* Unsupported algorithm */ return 0; /* Wrong input or unsupported algorithm */ } /** * Get digest size for specified algorithm. * * The size of the digest specifies the size of the userhash, userdigest * and other parameters which size depends on used hash algorithm. * @param algo3 the algorithm to check * @return the size of the digest (either #MHD_MD5_DIGEST_SIZE or * #MHD_SHA256_DIGEST_SIZE/MHD_SHA512_256_DIGEST_SIZE) * or zero if the input value is not supported or not valid * @sa #MHD_digest_auth_calc_userdigest() * @sa #MHD_digest_auth_calc_userhash(), #MHD_digest_auth_calc_userhash_hex() * @note Available since #MHD_VERSION 0x00097526 * @ingroup authentication */ _MHD_EXTERN size_t MHD_digest_get_hash_size (enum MHD_DigestAuthAlgo3 algo3) { return digest_get_hash_size (algo3); } /** * Digest context data */ union DigestCtx { #ifdef MHD_MD5_SUPPORT struct Md5CtxWr md5_ctx; #endif /* MHD_MD5_SUPPORT */ #ifdef MHD_SHA256_SUPPORT struct Sha256CtxWr sha256_ctx; #endif /* MHD_SHA256_SUPPORT */ #ifdef MHD_SHA512_256_SUPPORT struct Sha512_256Ctx sha512_256_ctx; #endif /* MHD_SHA512_256_SUPPORT */ }; /** * The digest calculation structure. */ struct DigestAlgorithm { /** * A context for the digest algorithm, already initialized to be * useful for @e init, @e update and @e digest. */ union DigestCtx ctx; /** * The hash calculation algorithm. */ enum MHD_DigestBaseAlgo algo; /** * Buffer for hex-print of the final digest. */ #if _DEBUG bool uninitialised; /**< The structure has been not set-up */ bool algo_selected; /**< The algorithm has been selected */ bool ready_for_hashing; /**< The structure is ready to hash data */ bool hashing; /**< Some data has been hashed, but the digest has not finalised yet */ #endif /* _DEBUG */ }; /** * Return the size of the digest. * @param da the digest calculation structure to identify * @return the size of the digest. */ _MHD_static_inline unsigned int digest_get_size (struct DigestAlgorithm *da) { mhd_assert (! da->uninitialised); mhd_assert (da->algo_selected); #ifdef MHD_MD5_SUPPORT if (MHD_DIGEST_BASE_ALGO_MD5 == da->algo) return MD5_DIGEST_SIZE; #endif /* MHD_MD5_SUPPORT */ #ifdef MHD_SHA256_SUPPORT if (MHD_DIGEST_BASE_ALGO_SHA256 == da->algo) return SHA256_DIGEST_SIZE; #endif /* MHD_SHA256_SUPPORT */ #ifdef MHD_SHA512_256_SUPPORT if (MHD_DIGEST_BASE_ALGO_SHA512_256 == da->algo) return SHA512_256_DIGEST_SIZE; #endif /* MHD_SHA512_256_SUPPORT */ mhd_assert (0); /* May not happen */ return 0; } #if defined(MHD_MD5_HAS_DEINIT) || defined(MHD_SHA256_HAS_DEINIT) /** * Indicates presence of digest_deinit() function */ #define MHD_DIGEST_HAS_DEINIT 1 #endif /* MHD_MD5_HAS_DEINIT || MHD_SHA256_HAS_DEINIT */ #ifdef MHD_DIGEST_HAS_DEINIT /** * Zero-initialise digest calculation structure. * * This initialisation is enough to safely call #digest_deinit() only. * To make any real digest calculation, #digest_setup_and_init() must be called. * @param da the digest calculation */ _MHD_static_inline void digest_setup_zero (struct DigestAlgorithm *da) { #ifdef _DEBUG da->uninitialised = false; da->algo_selected = false; da->ready_for_hashing = false; da->hashing = false; #endif /* _DEBUG */ da->algo = MHD_DIGEST_BASE_ALGO_INVALID; } /** * De-initialise digest calculation structure. * * This function must be called if #digest_setup_and_init() was called for * @a da. * This function must not be called if @a da was not initialised by * #digest_setup_and_init() or by #digest_setup_zero(). * @param da the digest calculation */ _MHD_static_inline void digest_deinit (struct DigestAlgorithm *da) { mhd_assert (! da->uninitialised); #ifdef MHD_MD5_HAS_DEINIT if (MHD_DIGEST_BASE_ALGO_MD5 == da->algo) MHD_MD5_deinit (&da->ctx.md5_ctx); else #endif /* MHD_MD5_HAS_DEINIT */ #ifdef MHD_SHA256_HAS_DEINIT if (MHD_DIGEST_BASE_ALGO_SHA256 == da->algo) MHD_SHA256_deinit (&da->ctx.sha256_ctx); else #endif /* MHD_SHA256_HAS_DEINIT */ (void) 0; digest_setup_zero (da); } #else /* ! MHD_DIGEST_HAS_DEINIT */ #define digest_setup_zero(da) (void)0 #define digest_deinit(da) (void)0 #endif /* ! MHD_DIGEST_HAS_DEINIT */ /** * Set-up the digest calculation structure and initialise with initial values. * * If @a da was successfully initialised, #digest_deinit() must be called * after finishing using of the @a da. * * This function must not be called more than once for any @a da. * * @param da the structure to set-up * @param algo the algorithm to use for digest calculation * @return boolean 'true' if successfully set-up, * false otherwise. */ _MHD_static_inline bool digest_init_one_time (struct DigestAlgorithm *da, enum MHD_DigestBaseAlgo algo) { #ifdef _DEBUG da->uninitialised = false; da->algo_selected = false; da->ready_for_hashing = false; da->hashing = false; #endif /* _DEBUG */ #ifdef MHD_MD5_SUPPORT if (MHD_DIGEST_BASE_ALGO_MD5 == algo) { da->algo = MHD_DIGEST_BASE_ALGO_MD5; #ifdef _DEBUG da->algo_selected = true; #endif MHD_MD5_init_one_time (&da->ctx.md5_ctx); #ifdef _DEBUG da->ready_for_hashing = true; #endif return true; } #endif /* MHD_MD5_SUPPORT */ #ifdef MHD_SHA256_SUPPORT if (MHD_DIGEST_BASE_ALGO_SHA256 == algo) { da->algo = MHD_DIGEST_BASE_ALGO_SHA256; #ifdef _DEBUG da->algo_selected = true; #endif MHD_SHA256_init_one_time (&da->ctx.sha256_ctx); #ifdef _DEBUG da->ready_for_hashing = true; #endif return true; } #endif /* MHD_SHA256_SUPPORT */ #ifdef MHD_SHA512_256_SUPPORT if (MHD_DIGEST_BASE_ALGO_SHA512_256 == algo) { da->algo = MHD_DIGEST_BASE_ALGO_SHA512_256; #ifdef _DEBUG da->algo_selected = true; #endif MHD_SHA512_256_init (&da->ctx.sha512_256_ctx); #ifdef _DEBUG da->ready_for_hashing = true; #endif return true; } #endif /* MHD_SHA512_256_SUPPORT */ da->algo = MHD_DIGEST_BASE_ALGO_INVALID; return false; /* Unsupported or bad algorithm */ } /** * Feed digest calculation with more data. * @param da the digest calculation * @param data the data to process * @param length the size of the @a data in bytes */ _MHD_static_inline void digest_update (struct DigestAlgorithm *da, const void *data, size_t length) { mhd_assert (! da->uninitialised); mhd_assert (da->algo_selected); mhd_assert (da->ready_for_hashing); #ifdef MHD_MD5_SUPPORT if (MHD_DIGEST_BASE_ALGO_MD5 == da->algo) MHD_MD5_update (&da->ctx.md5_ctx, (const uint8_t *) data, length); else #endif /* MHD_MD5_SUPPORT */ #ifdef MHD_SHA256_SUPPORT if (MHD_DIGEST_BASE_ALGO_SHA256 == da->algo) MHD_SHA256_update (&da->ctx.sha256_ctx, (const uint8_t *) data, length); else #endif /* MHD_SHA256_SUPPORT */ #ifdef MHD_SHA512_256_SUPPORT if (MHD_DIGEST_BASE_ALGO_SHA512_256 == da->algo) MHD_SHA512_256_update (&da->ctx.sha512_256_ctx, (const uint8_t *) data, length); else #endif /* MHD_SHA512_256_SUPPORT */ mhd_assert (0); /* May not happen */ #ifdef _DEBUG da->hashing = true; #endif } /** * Feed digest calculation with more data from string. * @param da the digest calculation * @param str the zero-terminated string to process */ _MHD_static_inline void digest_update_str (struct DigestAlgorithm *da, const char *str) { const size_t str_len = strlen (str); digest_update (da, (const uint8_t *) str, str_len); } /** * Feed digest calculation with single colon ':' character. * @param da the digest calculation * @param str the zero-terminated string to process */ _MHD_static_inline void digest_update_with_colon (struct DigestAlgorithm *da) { static const uint8_t colon = (uint8_t) ':'; digest_update (da, &colon, 1); } /** * Finally calculate hash (the digest). * @param da the digest calculation * @param[out] digest the pointer to the buffer to put calculated digest, * must be at least digest_get_size(da) bytes large */ _MHD_static_inline void digest_calc_hash (struct DigestAlgorithm *da, uint8_t *digest) { mhd_assert (! da->uninitialised); mhd_assert (da->algo_selected); mhd_assert (da->ready_for_hashing); #ifdef MHD_MD5_SUPPORT if (MHD_DIGEST_BASE_ALGO_MD5 == da->algo) { #ifdef MHD_MD5_HAS_FINISH MHD_MD5_finish (&da->ctx.md5_ctx, digest); #ifdef _DEBUG da->ready_for_hashing = false; #endif /* _DEBUG */ #else /* ! MHD_MD5_HAS_FINISH */ MHD_MD5_finish_reset (&da->ctx.md5_ctx, digest); #ifdef _DEBUG da->ready_for_hashing = true; #endif /* _DEBUG */ #endif /* ! MHD_MD5_HAS_FINISH */ } else #endif /* MHD_MD5_SUPPORT */ #ifdef MHD_SHA256_SUPPORT if (MHD_DIGEST_BASE_ALGO_SHA256 == da->algo) { #ifdef MHD_SHA256_HAS_FINISH MHD_SHA256_finish (&da->ctx.sha256_ctx, digest); #ifdef _DEBUG da->ready_for_hashing = false; #endif /* _DEBUG */ #else /* ! MHD_SHA256_HAS_FINISH */ MHD_SHA256_finish_reset (&da->ctx.sha256_ctx, digest); #ifdef _DEBUG da->ready_for_hashing = true; #endif /* _DEBUG */ #endif /* ! MHD_SHA256_HAS_FINISH */ } else #endif /* MHD_SHA256_SUPPORT */ #ifdef MHD_SHA512_256_SUPPORT if (MHD_DIGEST_BASE_ALGO_SHA512_256 == da->algo) { MHD_SHA512_256_finish (&da->ctx.sha512_256_ctx, digest); #ifdef _DEBUG da->ready_for_hashing = false; #endif /* _DEBUG */ } else #endif /* MHD_SHA512_256_SUPPORT */ mhd_assert (0); /* Should not happen */ #ifdef _DEBUG da->hashing = false; #endif /* _DEBUG */ } /** * Reset the digest calculation structure. * * @param da the structure to reset */ _MHD_static_inline void digest_reset (struct DigestAlgorithm *da) { mhd_assert (! da->uninitialised); mhd_assert (da->algo_selected); mhd_assert (! da->hashing); #ifdef MHD_MD5_SUPPORT if (MHD_DIGEST_BASE_ALGO_MD5 == da->algo) { #ifdef MHD_MD5_HAS_FINISH mhd_assert (! da->ready_for_hashing); #else /* ! MHD_MD5_HAS_FINISH */ mhd_assert (da->ready_for_hashing); #endif /* ! MHD_MD5_HAS_FINISH */ MHD_MD5_reset (&da->ctx.md5_ctx); #ifdef _DEBUG da->ready_for_hashing = true; #endif /* _DEBUG */ } else #endif /* MHD_MD5_SUPPORT */ #ifdef MHD_SHA256_SUPPORT if (MHD_DIGEST_BASE_ALGO_SHA256 == da->algo) { #ifdef MHD_SHA256_HAS_FINISH mhd_assert (! da->ready_for_hashing); #else /* ! MHD_SHA256_HAS_FINISH */ mhd_assert (da->ready_for_hashing); #endif /* ! MHD_SHA256_HAS_FINISH */ MHD_SHA256_reset (&da->ctx.sha256_ctx); #ifdef _DEBUG da->ready_for_hashing = true; #endif /* _DEBUG */ } else #endif /* MHD_SHA256_SUPPORT */ #ifdef MHD_SHA512_256_SUPPORT if (MHD_DIGEST_BASE_ALGO_SHA512_256 == da->algo) { mhd_assert (! da->ready_for_hashing); MHD_SHA512_256_init (&da->ctx.sha512_256_ctx); #ifdef _DEBUG da->ready_for_hashing = true; #endif } else #endif /* MHD_SHA512_256_SUPPORT */ { #ifdef _DEBUG da->ready_for_hashing = false; #endif mhd_assert (0); /* May not happen, bad algorithm */ } } #if defined(MHD_MD5_HAS_EXT_ERROR) || defined(MHD_SHA256_HAS_EXT_ERROR) /** * Indicates that digest algorithm has external error status */ #define MHD_DIGEST_HAS_EXT_ERROR 1 #endif /* MHD_MD5_HAS_EXT_ERROR || MHD_SHA256_HAS_EXT_ERROR */ #ifdef MHD_DIGEST_HAS_EXT_ERROR /** * Get external error code. * * When external digest calculation used, an error may occur during * initialisation or hashing data. This function checks whether external * error has been reported for digest calculation. * @param da the digest calculation * @return true if external error occurs */ _MHD_static_inline bool digest_ext_error (struct DigestAlgorithm *da) { mhd_assert (! da->uninitialised); mhd_assert (da->algo_selected); #ifdef MHD_MD5_HAS_EXT_ERROR if (MHD_DIGEST_BASE_ALGO_MD5 == da->algo) return 0 != da->ctx.md5_ctx.ext_error; #endif /* MHD_MD5_HAS_EXT_ERROR */ #ifdef MHD_SHA256_HAS_EXT_ERROR if (MHD_DIGEST_BASE_ALGO_SHA256 == da->algo) return 0 != da->ctx.sha256_ctx.ext_error; #endif /* MHD_MD5_HAS_EXT_ERROR */ return false; } #else /* ! MHD_DIGEST_HAS_EXT_ERROR */ #define digest_ext_error(da) (false) #endif /* ! MHD_DIGEST_HAS_EXT_ERROR */ /** * Extract timestamp from the given nonce. * @param nonce the nonce to check * @param noncelen the length of the nonce, zero for autodetect * @param[out] ptimestamp the pointer to store extracted timestamp * @return true if timestamp was extracted, * false if nonce does not have valid timestamp. */ static bool get_nonce_timestamp (const char *const nonce, size_t noncelen, uint64_t *const ptimestamp) { if (0 == noncelen) noncelen = strlen (nonce); if (true #ifdef MHD_MD5_SUPPORT && (NONCE_STD_LEN (MD5_DIGEST_SIZE) != noncelen) #endif /* MHD_MD5_SUPPORT */ #if defined(MHD_SHA256_SUPPORT) || defined(MHD_SHA512_256_SUPPORT) && (NONCE_STD_LEN (SHA256_SHA512_256_DIGEST_SIZE) != noncelen) #endif /* MHD_SHA256_SUPPORT */ ) return false; if (TIMESTAMP_CHARS_LEN != MHD_strx_to_uint64_n_ (nonce + noncelen - TIMESTAMP_CHARS_LEN, TIMESTAMP_CHARS_LEN, ptimestamp)) return false; return true; } /** * Super-fast xor-based "hash" function * * @param data the data to calculate hash for * @param data_size the size of the data in bytes * @return the "hash" */ static uint32_t fast_simple_hash (const uint8_t *data, size_t data_size) { uint32_t hash; if (0 != data_size) { size_t i; hash = data[0]; for (i = 1; i < data_size; i++) hash = _MHD_ROTL32 (hash, 7) ^ data[i]; } else hash = 0; return hash; } /** * Get index of the nonce in the nonce-nc map array. * * @param arr_size the size of nonce_nc array * @param nonce the pointer that referenced a zero-terminated array of nonce * @param noncelen the length of @a nonce, in characters * @return #MHD_YES if successful, #MHD_NO if invalid (or we have no NC array) */ static size_t get_nonce_nc_idx (size_t arr_size, const char *nonce, size_t noncelen) { mhd_assert (0 != arr_size); mhd_assert (0 != noncelen); return fast_simple_hash ((const uint8_t *) nonce, noncelen) % arr_size; } /** * Check nonce-nc map array with the new nonce counter. * * @param connection The MHD connection structure * @param nonce the pointer that referenced hex nonce, does not need to be * zero-terminated * @param noncelen the length of @a nonce, in characters * @param nc The nonce counter * @return #MHD_DAUTH_NONCENC_OK if successful, * #MHD_DAUTH_NONCENC_STALE if nonce is stale (or no nonce-nc array * is available), * #MHD_DAUTH_NONCENC_WRONG if nonce was not recodered in nonce-nc map * array, while it should. */ static enum MHD_CheckNonceNC_ check_nonce_nc (struct MHD_Connection *connection, const char *nonce, size_t noncelen, uint64_t nonce_time, uint64_t nc) { struct MHD_Daemon *daemon = MHD_get_master (connection->daemon); struct MHD_NonceNc *nn; uint32_t mod; enum MHD_CheckNonceNC_ ret; mhd_assert (0 != noncelen); mhd_assert (0 != nc); if (MAX_DIGEST_NONCE_LENGTH < noncelen) return MHD_CHECK_NONCENC_WRONG; /* This should be impossible, but static analysis tools have a hard time with it *and* this also protects against unsafe modifications that may happen in the future... */ mod = daemon->nonce_nc_size; if (0 == mod) return MHD_CHECK_NONCENC_STALE; /* no array! */ if (nc >= UINT32_MAX - 64) return MHD_CHECK_NONCENC_STALE; /* Overflow, unrealistically high value */ nn = &daemon->nnc[get_nonce_nc_idx (mod, nonce, noncelen)]; MHD_mutex_lock_chk_ (&daemon->nnc_lock); mhd_assert (0 == nn->nonce[noncelen]); /* The old value must be valid */ if ( (0 != memcmp (nn->nonce, nonce, noncelen)) || (0 != nn->nonce[noncelen]) ) { /* The nonce in the slot does not match nonce from the client */ if (0 == nn->nonce[0]) { /* The slot was never used, while the client's nonce value should be * recorded when it was generated by MHD */ ret = MHD_CHECK_NONCENC_WRONG; } else if (0 != nn->nonce[noncelen]) { /* The value is the slot is wrong */ ret = MHD_CHECK_NONCENC_STALE; } else { uint64_t slot_ts; /**< The timestamp in the slot */ if (! get_nonce_timestamp (nn->nonce, noncelen, &slot_ts)) { mhd_assert (0); /* The value is the slot is wrong */ ret = MHD_CHECK_NONCENC_STALE; } else { /* Unsigned value, will be large if nonce_time is less than slot_ts */ const uint64_t ts_diff = TRIM_TO_TIMESTAMP (nonce_time - slot_ts); if ((REUSE_TIMEOUT * 1000) >= ts_diff) { /* The nonce from the client may not have been placed in the slot * because another nonce in that slot has not yet expired. */ ret = MHD_CHECK_NONCENC_STALE; } else if (TRIM_TO_TIMESTAMP (UINT64_MAX) / 2 >= ts_diff) { /* Too large value means that nonce_time is less than slot_ts. * The nonce from the client may have been overwritten by the newer * nonce. */ ret = MHD_CHECK_NONCENC_STALE; } else { /* The nonce from the client should be generated after the nonce * in the slot has been expired, the nonce must be recorded, but * it's not. */ ret = MHD_CHECK_NONCENC_WRONG; } } } } else if (nc > nn->nc) { /* 'nc' is larger, shift bitmask and bump limit */ const uint32_t jump_size = (uint32_t) nc - nn->nc; if (64 > jump_size) { /* small jump, less than mask width */ nn->nmask <<= jump_size; /* Set bit for the old 'nc' value */ nn->nmask |= (UINT64_C (1) << (jump_size - 1)); } else if (64 == jump_size) nn->nmask = (UINT64_C (1) << 63); else nn->nmask = 0; /* big jump, unset all bits in the mask */ nn->nc = (uint32_t) nc; ret = MHD_CHECK_NONCENC_OK; } else if (nc < nn->nc) { /* Note that we use 64 here, as we do not store the bit for 'nn->nc' itself in 'nn->nmask' */ if ( (nc + 64 >= nn->nc) && (0 == ((UINT64_C (1) << (nn->nc - nc - 1)) & nn->nmask)) ) { /* Out-of-order nonce, but within 64-bit bitmask, set bit */ nn->nmask |= (UINT64_C (1) << (nn->nc - nc - 1)); ret = MHD_CHECK_NONCENC_OK; } else /* 'nc' was already used or too old (more then 64 values ago) */ ret = MHD_CHECK_NONCENC_STALE; } else /* if (nc == nn->nc) */ /* 'nc' was already used */ ret = MHD_CHECK_NONCENC_STALE; MHD_mutex_unlock_chk_ (&daemon->nnc_lock); return ret; } /** * Get username type used by the client. * This function does not check whether userhash can be decoded or * extended notation (if used) is valid. * @param params the Digest Authorization parameters * @return the type of username */ _MHD_static_inline enum MHD_DigestAuthUsernameType get_rq_uname_type (const struct MHD_RqDAuth *params) { if (NULL != params->username.value.str) { if (NULL == params->username_ext.value.str) return params->userhash ? MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH : MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD; else /* Both 'username' and 'username*' are used */ return MHD_DIGEST_AUTH_UNAME_TYPE_INVALID; } else if (NULL != params->username_ext.value.str) { if (! params->username_ext.quoted && ! params->userhash && (MHD_DAUTH_EXT_PARAM_MIN_LEN <= params->username_ext.value.len) ) return MHD_DIGEST_AUTH_UNAME_TYPE_EXTENDED; else return MHD_DIGEST_AUTH_UNAME_TYPE_INVALID; } return MHD_DIGEST_AUTH_UNAME_TYPE_MISSING; } /** * Get total size required for 'username' and 'userhash_bin' * @param params the Digest Authorization parameters * @param uname_type the type of username * @return the total size required for 'username' and * 'userhash_bin' is userhash is used */ _MHD_static_inline size_t get_rq_unames_size (const struct MHD_RqDAuth *params, enum MHD_DigestAuthUsernameType uname_type) { size_t s; mhd_assert (get_rq_uname_type (params) == uname_type); s = 0; if ((MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD == uname_type) || (MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH == uname_type) ) { s += params->username.value.len + 1; /* Add one byte for zero-termination */ if (MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH == uname_type) s += (params->username.value.len + 1) / 2; } else if (MHD_DIGEST_AUTH_UNAME_TYPE_EXTENDED == uname_type) s += params->username_ext.value.len - MHD_DAUTH_EXT_PARAM_MIN_LEN + 1; /* Add one byte for zero-termination */ return s; } /** * Get unquoted version of Digest Authorization parameter. * This function automatically zero-teminate the result. * @param param the parameter to extract * @param[out] buf the output buffer, must be enough size to hold the result, * the recommended size is 'param->value.len + 1' * @return the size of the result, not including the terminating zero */ static size_t get_rq_param_unquoted_copy_z (const struct MHD_RqDAuthParam *param, char *buf) { size_t len; mhd_assert (NULL != param->value.str); if (! param->quoted) { memcpy (buf, param->value.str, param->value.len); buf [param->value.len] = 0; return param->value.len; } len = MHD_str_unquote (param->value.str, param->value.len, buf); mhd_assert (0 != len); mhd_assert (len < param->value.len); buf[len] = 0; return len; } /** * Get decoded version of username from extended notation. * This function automatically zero-teminate the result. * @param uname_ext the string of client's 'username*' parameter value * @param uname_ext_len the length of @a uname_ext in chars * @param[out] buf the output buffer to put decoded username value * @param buf_size the size of @a buf * @return the number of characters copied to the output buffer or * -1 if wrong extended notation is used. */ static ssize_t get_rq_extended_uname_copy_z (const char *uname_ext, size_t uname_ext_len, char *buf, size_t buf_size) { size_t r; size_t w; if ((size_t) SSIZE_MAX < uname_ext_len) return -1; /* Too long input string */ if (MHD_DAUTH_EXT_PARAM_MIN_LEN > uname_ext_len) return -1; /* Required prefix is missing */ if (! MHD_str_equal_caseless_bin_n_ (uname_ext, MHD_DAUTH_EXT_PARAM_PREFIX, MHD_STATICSTR_LEN_ ( \ MHD_DAUTH_EXT_PARAM_PREFIX))) return -1; /* Only UTF-8 is supported, as it is implied by RFC 7616 */ r = MHD_STATICSTR_LEN_ (MHD_DAUTH_EXT_PARAM_PREFIX); /* Skip language tag */ while (r < uname_ext_len && '\'' != uname_ext[r]) { const char chr = uname_ext[r]; if ((' ' == chr) || ('\t' == chr) || ('\"' == chr) || (',' == chr) || (';' == chr) ) return -1; /* Wrong char in language tag */ r++; } if (r >= uname_ext_len) return -1; /* The end of the language tag was not found */ r++; /* Advance to the next char */ w = MHD_str_pct_decode_strict_n_ (uname_ext + r, uname_ext_len - r, buf, buf_size); if ((0 == w) && (0 != uname_ext_len - r)) return -1; /* Broken percent encoding */ buf[w] = 0; /* Zero terminate the result */ mhd_assert (SSIZE_MAX > w); return (ssize_t) w; } /** * Get copy of username used by the client. * @param params the Digest Authorization parameters * @param uname_type the type of username * @param[out] uname_info the pointer to the structure to be filled * @param buf the buffer to be used for usernames * @param buf_size the size of the @a buf * @return the size of the @a buf used by pointers in @a unames structure */ static size_t get_rq_uname (const struct MHD_RqDAuth *params, enum MHD_DigestAuthUsernameType uname_type, struct MHD_DigestAuthUsernameInfo *uname_info, uint8_t *buf, size_t buf_size) { size_t buf_used; buf_used = 0; mhd_assert (get_rq_uname_type (params) == uname_type); mhd_assert (MHD_DIGEST_AUTH_UNAME_TYPE_INVALID != uname_type); mhd_assert (MHD_DIGEST_AUTH_UNAME_TYPE_MISSING != uname_type); uname_info->username = NULL; uname_info->username_len = 0; uname_info->userhash_hex = NULL; uname_info->userhash_hex_len = 0; uname_info->userhash_bin = NULL; if (MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD == uname_type) { uname_info->username = (char *) (buf + buf_used); uname_info->username_len = get_rq_param_unquoted_copy_z (¶ms->username, uname_info->username); buf_used += uname_info->username_len + 1; uname_info->uname_type = MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD; } else if (MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH == uname_type) { size_t res; uname_info->userhash_hex = (char *) (buf + buf_used); uname_info->userhash_hex_len = get_rq_param_unquoted_copy_z (¶ms->username, uname_info->userhash_hex); buf_used += uname_info->userhash_hex_len + 1; uname_info->userhash_bin = (uint8_t *) (buf + buf_used); res = MHD_hex_to_bin (uname_info->userhash_hex, uname_info->userhash_hex_len, uname_info->userhash_bin); if (res != uname_info->userhash_hex_len / 2) { uname_info->userhash_bin = NULL; uname_info->uname_type = MHD_DIGEST_AUTH_UNAME_TYPE_INVALID; } else { /* Avoid pointers outside allocated region when the size is zero */ if (0 == res) uname_info->userhash_bin = (uint8_t *) uname_info->username; uname_info->uname_type = MHD_DIGEST_AUTH_UNAME_TYPE_USERHASH; buf_used += res; } } else if (MHD_DIGEST_AUTH_UNAME_TYPE_EXTENDED == uname_type) { ssize_t res; res = get_rq_extended_uname_copy_z (params->username_ext.value.str, params->username_ext.value.len, (char *) (buf + buf_used), buf_size - buf_used); if (0 > res) uname_info->uname_type = MHD_DIGEST_AUTH_UNAME_TYPE_INVALID; else { uname_info->username = (char *) (buf + buf_used); uname_info->username_len = (size_t) res; uname_info->uname_type = MHD_DIGEST_AUTH_UNAME_TYPE_EXTENDED; buf_used += uname_info->username_len + 1; } } else { mhd_assert (0); uname_info->uname_type = MHD_DIGEST_AUTH_UNAME_TYPE_INVALID; } mhd_assert (buf_size >= buf_used); return buf_used; } /** * Result of request's Digest Authorization 'nc' value extraction */ enum MHD_GetRqNCResult { MHD_GET_RQ_NC_NONE = -1, /**< No 'nc' value */ MHD_GET_RQ_NC_VALID = 0, /**< Readable 'nc' value */ MHD_GET_RQ_NC_TOO_LONG = 1, /**< The 'nc' value is too long */ MHD_GET_RQ_NC_TOO_LARGE = 2,/**< The 'nc' value is too big to fit uint32_t */ MHD_GET_RQ_NC_BROKEN = 3 /**< The 'nc' value is not a number */ }; /** * Get 'nc' value from request's Authorization header * @param params the request digest authentication * @param[out] nc the pointer to put nc value to * @return enum value indicating the result */ static enum MHD_GetRqNCResult get_rq_nc (const struct MHD_RqDAuth *params, uint32_t *nc) { const struct MHD_RqDAuthParam *const nc_param = ¶ms->nc; char unq[16]; const char *val; size_t val_len; size_t res; uint64_t nc_val; if (NULL == nc_param->value.str) return MHD_GET_RQ_NC_NONE; if (0 == nc_param->value.len) return MHD_GET_RQ_NC_BROKEN; if (! nc_param->quoted) { val = nc_param->value.str; val_len = nc_param->value.len; } else { /* Actually no backslashes must be used in 'nc' */ if (sizeof(unq) < params->nc.value.len) return MHD_GET_RQ_NC_TOO_LONG; val_len = MHD_str_unquote (nc_param->value.str, nc_param->value.len, unq); if (0 == val_len) return MHD_GET_RQ_NC_BROKEN; val = unq; } res = MHD_strx_to_uint64_n_ (val, val_len, &nc_val); if (0 == res) { const char f = val[0]; if ( (('9' >= f) && ('0' <= f)) || (('F' >= f) && ('A' <= f)) || (('a' <= f) && ('f' >= f)) ) return MHD_GET_RQ_NC_TOO_LARGE; else return MHD_GET_RQ_NC_BROKEN; } if (val_len != res) return MHD_GET_RQ_NC_BROKEN; if (UINT32_MAX < nc_val) return MHD_GET_RQ_NC_TOO_LARGE; *nc = (uint32_t) nc_val; return MHD_GET_RQ_NC_VALID; } /** * Get information about Digest Authorization client's header. * * @param connection The MHD connection structure * @return NULL no valid Digest Authorization header is used in the request; * a pointer structure with information if the valid request header * found, free using #MHD_free(). * @note Available since #MHD_VERSION 0x00097519 * @ingroup authentication */ _MHD_EXTERN struct MHD_DigestAuthInfo * MHD_digest_auth_get_request_info3 (struct MHD_Connection *connection) { const struct MHD_RqDAuth *params; struct MHD_DigestAuthInfo *info; enum MHD_DigestAuthUsernameType uname_type; size_t unif_buf_size; uint8_t *unif_buf_ptr; size_t unif_buf_used; enum MHD_GetRqNCResult nc_res; params = MHD_get_rq_dauth_params_ (connection); if (NULL == params) return NULL; unif_buf_size = 0; uname_type = get_rq_uname_type (params); unif_buf_size += get_rq_unames_size (params, uname_type); if (NULL != params->opaque.value.str) unif_buf_size += params->opaque.value.len + 1; /* Add one for zero-termination */ if (NULL != params->realm.value.str) unif_buf_size += params->realm.value.len + 1; /* Add one for zero-termination */ info = (struct MHD_DigestAuthInfo *) MHD_calloc_ (1, (sizeof(struct MHD_DigestAuthInfo)) + unif_buf_size); unif_buf_ptr = (uint8_t *) (info + 1); unif_buf_used = 0; info->algo3 = params->algo3; if ( (MHD_DIGEST_AUTH_UNAME_TYPE_MISSING != uname_type) && (MHD_DIGEST_AUTH_UNAME_TYPE_INVALID != uname_type) ) unif_buf_used += get_rq_uname (params, uname_type, (struct MHD_DigestAuthUsernameInfo *) info, unif_buf_ptr + unif_buf_used, unif_buf_size - unif_buf_used); else info->uname_type = uname_type; if (NULL != params->opaque.value.str) { info->opaque = (char *) (unif_buf_ptr + unif_buf_used); info->opaque_len = get_rq_param_unquoted_copy_z (¶ms->opaque, info->opaque); unif_buf_used += info->opaque_len + 1; } if (NULL != params->realm.value.str) { info->realm = (char *) (unif_buf_ptr + unif_buf_used); info->realm_len = get_rq_param_unquoted_copy_z (¶ms->realm, info->realm); unif_buf_used += info->realm_len + 1; } mhd_assert (unif_buf_size >= unif_buf_used); info->qop = params->qop; if (NULL != params->cnonce.value.str) info->cnonce_len = params->cnonce.value.len; else info->cnonce_len = 0; nc_res = get_rq_nc (params, &info->nc); if (MHD_GET_RQ_NC_VALID != nc_res) info->nc = MHD_DIGEST_AUTH_INVALID_NC_VALUE; return info; } /** * Get the username from Digest Authorization client's header. * * @param connection The MHD connection structure * @return NULL if no valid Digest Authorization header is used in the request, * or no username parameter is present in the header, or username is * provided incorrectly by client (see description for * #MHD_DIGEST_AUTH_UNAME_TYPE_INVALID); * a pointer structure with information if the valid request header * found, free using #MHD_free(). * @sa MHD_digest_auth_get_request_info3() provides more complete information * @note Available since #MHD_VERSION 0x00097519 * @ingroup authentication */ _MHD_EXTERN struct MHD_DigestAuthUsernameInfo * MHD_digest_auth_get_username3 (struct MHD_Connection *connection) { const struct MHD_RqDAuth *params; struct MHD_DigestAuthUsernameInfo *uname_info; enum MHD_DigestAuthUsernameType uname_type; size_t unif_buf_size; uint8_t *unif_buf_ptr; size_t unif_buf_used; params = MHD_get_rq_dauth_params_ (connection); if (NULL == params) return NULL; uname_type = get_rq_uname_type (params); if ( (MHD_DIGEST_AUTH_UNAME_TYPE_MISSING == uname_type) || (MHD_DIGEST_AUTH_UNAME_TYPE_INVALID == uname_type) ) return NULL; unif_buf_size = get_rq_unames_size (params, uname_type); uname_info = (struct MHD_DigestAuthUsernameInfo *) MHD_calloc_ (1, (sizeof(struct MHD_DigestAuthUsernameInfo)) + unif_buf_size); unif_buf_ptr = (uint8_t *) (uname_info + 1); unif_buf_used = get_rq_uname (params, uname_type, uname_info, unif_buf_ptr, unif_buf_size); mhd_assert (unif_buf_size >= unif_buf_used); (void) unif_buf_used; /* Mute compiler warning on non-debug builds */ mhd_assert (MHD_DIGEST_AUTH_UNAME_TYPE_MISSING != uname_info->uname_type); if (MHD_DIGEST_AUTH_UNAME_TYPE_INVALID == uname_info->uname_type) { free (uname_info); return NULL; } mhd_assert (uname_type == uname_info->uname_type); uname_info->algo3 = params->algo3; return uname_info; } /** * Get the username from the authorization header sent by the client * * This function supports username in standard and extended notations. * "userhash" is not supported by this function. * * @param connection The MHD connection structure * @return NULL if no username could be found, username provided as * "userhash", extended notation broken or memory allocation error * occurs; * a pointer to the username if found, free using #MHD_free(). * @warning Returned value must be freed by #MHD_free(). * @sa #MHD_digest_auth_get_username3() * @ingroup authentication */ _MHD_EXTERN char * MHD_digest_auth_get_username (struct MHD_Connection *connection) { const struct MHD_RqDAuth *params; char *username; size_t buf_size; enum MHD_DigestAuthUsernameType uname_type; params = MHD_get_rq_dauth_params_ (connection); if (NULL == params) return NULL; uname_type = get_rq_uname_type (params); if ( (MHD_DIGEST_AUTH_UNAME_TYPE_STANDARD != uname_type) && (MHD_DIGEST_AUTH_UNAME_TYPE_EXTENDED != uname_type) ) return NULL; buf_size = get_rq_unames_size (params, uname_type); mhd_assert (0 != buf_size); username = (char *) MHD_calloc_ (1, buf_size); if (NULL == username) return NULL; if (1) { struct MHD_DigestAuthUsernameInfo uname_strct; size_t used; memset (&uname_strct, 0, sizeof(uname_strct)); used = get_rq_uname (params, uname_type, &uname_strct, (uint8_t *) username, buf_size); if (uname_type != uname_strct.uname_type) { /* Broken encoding for extended notation */ free (username); return NULL; } (void) used; /* Mute compiler warning for non-debug builds */ mhd_assert (buf_size >= used); } return username; } /** * Calculate the server nonce so that it mitigates replay attacks * The current format of the nonce is ... * H(timestamp:random data:various parameters) + Hex(timestamp) * * @param nonce_time The amount of time in seconds for a nonce to be invalid * @param mthd_e HTTP method as enum value * @param method HTTP method as a string * @param rnd the pointer to a character array for the random seed * @param rnd_size The size of the random seed array @a rnd * @param saddr the pointer to the socket address structure * @param saddr_size the size of the socket address structure @a saddr * @param uri the HTTP URI (in MHD, without the arguments ("?k=v") * @param uri_len the length of the @a uri * @param first_header the pointer to the first request's header * @param realm A string of characters that describes the realm of auth. * @param realm_len the length of the @a realm. * @param bind_options the nonce bind options (#MHD_DAuthBindNonce values). * @param da digest algorithm to use * @param[out] nonce the pointer to a character array for the nonce to put in, * must provide NONCE_STD_LEN(digest_get_size(da)) bytes, * result is NOT zero-terminated */ static void calculate_nonce (uint64_t nonce_time, enum MHD_HTTP_Method mthd_e, const char *method, const char *rnd, size_t rnd_size, const struct sockaddr_storage *saddr, size_t saddr_size, const char *uri, size_t uri_len, const struct MHD_HTTP_Req_Header *first_header, const char *realm, size_t realm_len, unsigned int bind_options, struct DigestAlgorithm *da, char *nonce) { mhd_assert (! da->hashing); if (1) { /* Add the timestamp to the hash calculation */ uint8_t timestamp[TIMESTAMP_BIN_SIZE]; /* If the nonce_time is milliseconds, then the same 48 bit value will repeat * every 8 919 years, which is more than enough to mitigate a replay attack */ #if TIMESTAMP_BIN_SIZE != 6 #error The code needs to be updated here #endif timestamp[0] = (uint8_t) (nonce_time >> (8 * (TIMESTAMP_BIN_SIZE - 1 - 0))); timestamp[1] = (uint8_t) (nonce_time >> (8 * (TIMESTAMP_BIN_SIZE - 1 - 1))); timestamp[2] = (uint8_t) (nonce_time >> (8 * (TIMESTAMP_BIN_SIZE - 1 - 2))); timestamp[3] = (uint8_t) (nonce_time >> (8 * (TIMESTAMP_BIN_SIZE - 1 - 3))); timestamp[4] = (uint8_t) (nonce_time >> (8 * (TIMESTAMP_BIN_SIZE - 1 - 4))); timestamp[5] = (uint8_t) (nonce_time >> (8 * (TIMESTAMP_BIN_SIZE - 1 - 5))); MHD_bin_to_hex (timestamp, sizeof (timestamp), nonce + digest_get_size (da) * 2); digest_update (da, timestamp, sizeof (timestamp)); } if (rnd_size > 0) { /* Add the unique random value to the hash calculation */ digest_update_with_colon (da); digest_update (da, rnd, rnd_size); } if ( (MHD_DAUTH_BIND_NONCE_NONE == bind_options) && (0 != saddr_size) ) { /* Add full client address including source port to make unique nonces * for requests received exactly at the same time */ digest_update_with_colon (da); digest_update (da, saddr, saddr_size); } if ( (0 != (bind_options & MHD_DAUTH_BIND_NONCE_CLIENT_IP)) && (0 != saddr_size) ) { /* Add the client's IP address to the hash calculation */ digest_update_with_colon (da); if (AF_INET == saddr->ss_family) digest_update (da, &((const struct sockaddr_in *) saddr)->sin_addr, sizeof(((const struct sockaddr_in *) saddr)->sin_addr)); #ifdef HAVE_INET6 else if (AF_INET6 == saddr->ss_family) digest_update (da, &((const struct sockaddr_in6 *) saddr)->sin6_addr, sizeof(((const struct sockaddr_in6 *) saddr)->sin6_addr)); #endif /* HAVE_INET6 */ } if ( (MHD_DAUTH_BIND_NONCE_NONE == bind_options) || (0 != (bind_options & MHD_DAUTH_BIND_NONCE_URI))) { /* Add the request method to the hash calculation */ digest_update_with_colon (da); if (MHD_HTTP_MTHD_OTHER != mthd_e) { uint8_t mthd_for_hash; if (MHD_HTTP_MTHD_HEAD != mthd_e) mthd_for_hash = (uint8_t) mthd_e; else /* Treat HEAD method in the same way as GET method */ mthd_for_hash = (uint8_t) MHD_HTTP_MTHD_GET; digest_update (da, &mthd_for_hash, sizeof(mthd_for_hash)); } else digest_update_str (da, method); } if (0 != (bind_options & MHD_DAUTH_BIND_NONCE_URI)) { /* Add the request URI to the hash calculation */ digest_update_with_colon (da); digest_update (da, uri, uri_len); } if (0 != (bind_options & MHD_DAUTH_BIND_NONCE_URI_PARAMS)) { /* Add the request URI parameters to the hash calculation */ const struct MHD_HTTP_Req_Header *h; digest_update_with_colon (da); for (h = first_header; NULL != h; h = h->next) { if (MHD_GET_ARGUMENT_KIND != h->kind) continue; digest_update (da, "\0", 2); if (0 != h->header_size) digest_update (da, h->header, h->header_size); digest_update (da, "", 1); if (0 != h->value_size) digest_update (da, h->value, h->value_size); } } if ( (MHD_DAUTH_BIND_NONCE_NONE == bind_options) || (0 != (bind_options & MHD_DAUTH_BIND_NONCE_REALM))) { /* Add the realm to the hash calculation */ digest_update_with_colon (da); digest_update (da, realm, realm_len); } if (1) { uint8_t hash[MAX_DIGEST]; digest_calc_hash (da, hash); MHD_bin_to_hex (hash, digest_get_size (da), nonce); } } /** * Check whether it is possible to use slot in nonce-nc map array. * * Should be called with mutex held to avoid external modification of * the slot data. * * @param nn the pointer to the nonce-nc slot * @param now the current time * @param new_nonce the new nonce supposed to be stored in this slot, * zero-terminated * @param new_nonce_len the length of the @a new_nonce in chars, not including * the terminating zero. * @return true if the slot can be used to store the new nonce, * false otherwise. */ static bool is_slot_available (const struct MHD_NonceNc *const nn, const uint64_t now, const char *const new_nonce, size_t new_nonce_len) { uint64_t timestamp; bool timestamp_valid; mhd_assert (new_nonce_len <= NONCE_STD_LEN (MAX_DIGEST)); mhd_assert (NONCE_STD_LEN (MAX_DIGEST) <= MAX_DIGEST_NONCE_LENGTH); if (0 == nn->nonce[0]) return true; /* The slot is empty */ if (0 == memcmp (nn->nonce, new_nonce, new_nonce_len)) { /* The slot has the same nonce already. This nonce cannot be registered * again as it would just clear 'nc' usage history. */ return false; } if (0 != nn->nc) return true; /* Client already used the nonce in this slot at least one time, re-use the slot */ /* The nonce must be zero-terminated */ mhd_assert (0 == nn->nonce[sizeof(nn->nonce) - 1]); if (0 != nn->nonce[sizeof(nn->nonce) - 1]) return true; /* Wrong nonce format in the slot */ timestamp_valid = get_nonce_timestamp (nn->nonce, 0, ×tamp); mhd_assert (timestamp_valid); if (! timestamp_valid) return true; /* Invalid timestamp in nonce-nc, should not be possible */ if ((REUSE_TIMEOUT * 1000) < TRIM_TO_TIMESTAMP (now - timestamp)) return true; return false; } /** * Calculate the server nonce so that it mitigates replay attacks and add * the new nonce to the nonce-nc map array. * * @param connection the MHD connection structure * @param timestamp the current timestamp * @param realm the string of characters that describes the realm of auth * @param realm_len the length of the @a realm * @param da the digest algorithm to use * @param[out] nonce the pointer to a character array for the nonce to put in, * must provide NONCE_STD_LEN(digest_get_size(da)) bytes, * result is NOT zero-terminated * @return true if the new nonce has been added to the nonce-nc map array, * false otherwise. */ static bool calculate_add_nonce (struct MHD_Connection *const connection, uint64_t timestamp, const char *realm, size_t realm_len, struct DigestAlgorithm *da, char *nonce) { struct MHD_Daemon *const daemon = MHD_get_master (connection->daemon); struct MHD_NonceNc *nn; const size_t nonce_size = NONCE_STD_LEN (digest_get_size (da)); bool ret; mhd_assert (! da->hashing); mhd_assert (MAX_DIGEST_NONCE_LENGTH >= nonce_size); mhd_assert (0 != nonce_size); calculate_nonce (timestamp, connection->rq.http_mthd, connection->rq.method, daemon->digest_auth_random, daemon->digest_auth_rand_size, connection->addr, (size_t) connection->addr_len, connection->rq.url, connection->rq.url_len, connection->rq.headers_received, realm, realm_len, daemon->dauth_bind_type, da, nonce); #ifdef MHD_DIGEST_HAS_EXT_ERROR if (digest_ext_error (da)) return false; #endif /* MHD_DIGEST_HAS_EXT_ERROR */ if (0 == daemon->nonce_nc_size) return false; /* Sanity check for values */ mhd_assert (MAX_DIGEST_NONCE_LENGTH == NONCE_STD_LEN (MAX_DIGEST)); nn = daemon->nnc + get_nonce_nc_idx (daemon->nonce_nc_size, nonce, nonce_size); MHD_mutex_lock_chk_ (&daemon->nnc_lock); if (is_slot_available (nn, timestamp, nonce, nonce_size)) { memcpy (nn->nonce, nonce, nonce_size); nn->nonce[nonce_size] = 0; /* With terminating zero */ nn->nc = 0; nn->nmask = 0; ret = true; } else ret = false; MHD_mutex_unlock_chk_ (&daemon->nnc_lock); return ret; } /** * Calculate the server nonce so that it mitigates replay attacks and add * the new nonce to the nonce-nc map array. * * @param connection the MHD connection structure * @param realm A string of characters that describes the realm of auth. * @param da digest algorithm to use * @param[out] nonce the pointer to a character array for the nonce to put in, * must provide NONCE_STD_LEN(digest_get_size(da)) bytes, * result is NOT zero-terminated */ static bool calculate_add_nonce_with_retry (struct MHD_Connection *const connection, const char *realm, struct DigestAlgorithm *da, char *nonce) { const uint64_t timestamp1 = MHD_monotonic_msec_counter (); const size_t realm_len = strlen (realm); mhd_assert (! da->hashing); #ifdef HAVE_MESSAGES if (0 == MHD_get_master (connection->daemon)->digest_auth_rand_size) MHD_DLOG (connection->daemon, _ ("Random value was not initialised by " \ "MHD_OPTION_DIGEST_AUTH_RANDOM or " \ "MHD_OPTION_DIGEST_AUTH_RANDOM_COPY, generated nonces " \ "are predictable.\n")); #endif if (! calculate_add_nonce (connection, timestamp1, realm, realm_len, da, nonce)) { /* Either: * 1. The same nonce was already generated. If it will be used then one * of the clients will fail (as no initial 'nc' value could be given to * the client, the second client which will use 'nc=00000001' will fail). * 2. Another nonce uses the same slot, and this nonce never has been * used by the client and this nonce is still fresh enough. */ const size_t digest_size = digest_get_size (da); char nonce2[NONCE_STD_LEN (MAX_DIGEST) + 1]; uint64_t timestamp2; #ifdef MHD_DIGEST_HAS_EXT_ERROR if (digest_ext_error (da)) return false; /* No need to re-try */ #endif /* MHD_DIGEST_HAS_EXT_ERROR */ if (0 == MHD_get_master (connection->daemon)->nonce_nc_size) return false; /* No need to re-try */ timestamp2 = MHD_monotonic_msec_counter (); if (timestamp1 == timestamp2) { /* The timestamps are equal, need to generate some arbitrary * difference for nonce. */ /* As the number is needed only to differentiate clients, weak * pseudo-random generators could be used. Seeding is not needed. */ uint64_t base1; uint32_t base2; uint16_t base3; uint8_t base4; #ifdef HAVE_RANDOM base1 = ((uint64_t) random ()) ^ UINT64_C (0x54a5acff5be47e63); base4 = 0xb8; #elif defined(HAVE_RAND) base1 = ((uint64_t) rand ()) ^ UINT64_C (0xc4bcf553b12f3965); base4 = 0x92; #else /* Monotonic msec counter alone does not really help here as it is already known that this value is not unique. */ base1 = ((uint64_t) (uintptr_t) nonce2) ^ UINT64_C (0xf2e1b21bc6c92655); base2 = ((uint32_t) (base1 >> 32)) ^ ((uint32_t) base1); base2 = _MHD_ROTR32 (base2, 4); base3 = ((uint16_t) (base2 >> 16)) ^ ((uint16_t) base2); base4 = ((uint8_t) (base3 >> 8)) ^ ((uint8_t) base3); base1 = ((uint64_t) MHD_monotonic_msec_counter ()) ^ UINT64_C (0xccab93f72cf5b15); #endif base2 = ((uint32_t) (base1 >> 32)) ^ ((uint32_t) base1); base2 = _MHD_ROTL32 (base2, (((base4 >> 4) ^ base4) % 32)); base3 = ((uint16_t) (base2 >> 16)) ^ ((uint16_t) base2); base4 = ((uint8_t) (base3 >> 8)) ^ ((uint8_t) base3); /* Use up to 127 ms difference */ timestamp2 -= (base4 & DAUTH_JUMPBACK_MAX); if (timestamp1 == timestamp2) timestamp2 -= 2; /* Fallback value */ } digest_reset (da); if (! calculate_add_nonce (connection, timestamp2, realm, realm_len, da, nonce2)) { /* No free slot has been found. Re-tries are expensive, just use * the generated nonce. As it is not stored in nonce-nc map array, * the next request of the client will be recognized as valid, but 'stale' * so client should re-try automatically. */ return false; } memcpy (nonce, nonce2, NONCE_STD_LEN (digest_size)); } return true; } /** * Calculate userdigest, return it as binary data. * * It is equal to H(A1) for non-session algorithms. * * MHD internal version. * * @param da the digest algorithm * @param username the username to use * @param username_len the length of the @a username * @param realm the realm to use * @param realm_len the length of the @a realm * @param password the password, must be zero-terminated * @param[out] ha1_bin the output buffer, must have at least * #digest_get_size(da) bytes available */ _MHD_static_inline void calc_userdigest (struct DigestAlgorithm *da, const char *username, const size_t username_len, const char *realm, const size_t realm_len, const char *password, uint8_t *ha1_bin) { mhd_assert (! da->hashing); digest_update (da, username, username_len); digest_update_with_colon (da); digest_update (da, realm, realm_len); digest_update_with_colon (da); digest_update_str (da, password); digest_calc_hash (da, ha1_bin); } /** * Calculate userdigest, return it as binary data. * * The "userdigest" is the hash of the "username:realm:password" string. * * The "userdigest" can be used to avoid storing the password in clear text * in database/files * * This function is designed to improve security of stored credentials, * the "userdigest" does not improve security of the authentication process. * * The results can be used to store username & userdigest pairs instead of * username & password pairs. To further improve security, application may * store username & userhash & userdigest triplets. * * @param algo3 the digest algorithm * @param username the username * @param realm the realm * @param password the password, must be zero-terminated * @param[out] userdigest_bin the output buffer for userdigest; * if this function succeeds, then this buffer has * #MHD_digest_get_hash_size(algo3) bytes of * userdigest upon return * @param userdigest_bin the size of the @a userdigest_bin buffer, must be * at least #MHD_digest_get_hash_size(algo3) bytes long * @return MHD_YES on success, * MHD_NO if @a userdigest_bin is too small or if @a algo3 algorithm is * not supported (or external error has occurred, * see #MHD_FEATURE_EXTERN_HASH). * @sa #MHD_digest_auth_check_digest3() * @note Available since #MHD_VERSION 0x00097535 * @ingroup authentication */ _MHD_EXTERN enum MHD_Result MHD_digest_auth_calc_userdigest (enum MHD_DigestAuthAlgo3 algo3, const char *username, const char *realm, const char *password, void *userdigest_bin, size_t bin_buf_size) { struct DigestAlgorithm da; enum MHD_Result ret; if (! digest_init_one_time (&da, get_base_digest_algo (algo3))) return MHD_NO; if (digest_get_size (&da) > bin_buf_size) ret = MHD_NO; else { calc_userdigest (&da, username, strlen (username), realm, strlen (realm), password, userdigest_bin); ret = MHD_YES; #ifdef MHD_DIGEST_HAS_EXT_ERROR if (digest_ext_error (&da)) ret = MHD_NO; #endif /* MHD_DIGEST_HAS_EXT_ERROR */ } digest_deinit (&da); return ret; } /** * Calculate userhash, return it as binary data. * * MHD internal version. * * @param da the digest algorithm * @param username the username to use * @param username_len the length of the @a username * @param realm the realm to use * @param realm_len the length of the @a realm * @param[out] digest_bin the output buffer, must have at least * #MHD_digest_get_hash_size(algo3) bytes available */ _MHD_static_inline void calc_userhash (struct DigestAlgorithm *da, const char *username, const size_t username_len, const char *realm, const size_t realm_len, uint8_t *digest_bin) { mhd_assert (NULL != username); mhd_assert (! da->hashing); digest_update (da, username, username_len); digest_update_with_colon (da); digest_update (da, realm, realm_len); digest_calc_hash (da, digest_bin); } /** * Calculate "userhash", return it as binary data. * * The "userhash" is the hash of the string "username:realm". * * The "Userhash" could be used to avoid sending username in cleartext in Digest * Authorization client's header. * * Userhash is not designed to hide the username in local database or files, * as username in cleartext is required for #MHD_digest_auth_check3() function * to check the response, but it can be used to hide username in HTTP headers. * * This function could be used when the new username is added to the username * database to save the "userhash" alongside with the username (preferably) or * when loading list of the usernames to generate the userhash for every loaded * username (this will cause delays at the start with the long lists). * * Once "userhash" is generated it could be used to identify users for clients * with "userhash" support. * Avoid repetitive usage of this function for the same username/realm * combination as it will cause excessive CPU load; save and re-use the result * instead. * * @param algo3 the algorithm for userhash calculations * @param username the username * @param realm the realm * @param[out] userhash_bin the output buffer for userhash as binary data; * if this function succeeds, then this buffer has * #MHD_digest_get_hash_size(algo3) bytes of userhash * upon return * @param bin_buf_size the size of the @a userhash_bin buffer, must be * at least #MHD_digest_get_hash_size(algo3) bytes long * @return MHD_YES on success, * MHD_NO if @a bin_buf_size is too small or if @a algo3 algorithm is * not supported (or external error has occurred, * see #MHD_FEATURE_EXTERN_HASH) * @note Available since #MHD_VERSION 0x00097535 * @ingroup authentication */ _MHD_EXTERN enum MHD_Result MHD_digest_auth_calc_userhash (enum MHD_DigestAuthAlgo3 algo3, const char *username, const char *realm, void *userhash_bin, size_t bin_buf_size) { struct DigestAlgorithm da; enum MHD_Result ret; if (! digest_init_one_time (&da, get_base_digest_algo (algo3))) return MHD_NO; if (digest_get_size (&da) > bin_buf_size) ret = MHD_NO; else { calc_userhash (&da, username, strlen (username), realm, strlen (realm), userhash_bin); ret = MHD_YES; #ifdef MHD_DIGEST_HAS_EXT_ERROR if (digest_ext_error (&da)) ret = MHD_NO; #endif /* MHD_DIGEST_HAS_EXT_ERROR */ } digest_deinit (&da); return ret; } /** * Calculate "userhash", return it as hexadecimal data. * * The "userhash" is the hash of the string "username:realm". * * The "Userhash" could be used to avoid sending username in cleartext in Digest * Authorization client's header. * * Userhash is not designed to hide the username in local database or files, * as username in cleartext is required for #MHD_digest_auth_check3() function * to check the response, but it can be used to hide username in HTTP headers. * * This function could be used when the new username is added to the username * database to save the "userhash" alongside with the username (preferably) or * when loading list of the usernames to generate the userhash for every loaded * username (this will cause delays at the start with the long lists). * * Once "userhash" is generated it could be used to identify users for clients * with "userhash" support. * Avoid repetitive usage of this function for the same username/realm * combination as it will cause excessive CPU load; save and re-use the result * instead. * * @param algo3 the algorithm for userhash calculations * @param username the username * @param realm the realm * @param[out] userhash_hex the output buffer for userhash as hex data; * if this function succeeds, then this buffer has * #MHD_digest_get_hash_size(algo3)*2 chars long * userhash string * @param bin_buf_size the size of the @a userhash_bin buffer, must be * at least #MHD_digest_get_hash_size(algo3)*2+1 chars long * @return MHD_YES on success, * MHD_NO if @a bin_buf_size is too small or if @a algo3 algorithm is * not supported (or external error has occurred, * see #MHD_FEATURE_EXTERN_HASH). * @note Available since #MHD_VERSION 0x00097535 * @ingroup authentication */ _MHD_EXTERN enum MHD_Result MHD_digest_auth_calc_userhash_hex (enum MHD_DigestAuthAlgo3 algo3, const char *username, const char *realm, char *userhash_hex, size_t hex_buf_size) { uint8_t userhash_bin[MAX_DIGEST]; size_t digest_size; digest_size = digest_get_hash_size (algo3); if (digest_size * 2 + 1 > hex_buf_size) return MHD_NO; if (MHD_NO == MHD_digest_auth_calc_userhash (algo3, username, realm, userhash_bin, MAX_DIGEST)) return MHD_NO; MHD_bin_to_hex_z (userhash_bin, digest_size, userhash_hex); return MHD_YES; } struct test_header_param { struct MHD_Connection *connection; size_t num_headers; }; /** * Test if the given key-value pair is in the headers for the * given connection. * * @param cls the test context * @param key the key * @param key_size number of bytes in @a key * @param value the value, can be NULL * @param value_size number of bytes in @a value * @param kind type of the header * @return #MHD_YES if the key-value pair is in the headers, * #MHD_NO if not */ static enum MHD_Result test_header (void *cls, const char *key, size_t key_size, const char *value, size_t value_size, enum MHD_ValueKind kind) { struct test_header_param *const param = (struct test_header_param *) cls; struct MHD_Connection *connection = param->connection; struct MHD_HTTP_Req_Header *pos; size_t i; param->num_headers++; i = 0; for (pos = connection->rq.headers_received; NULL != pos; pos = pos->next) { if (kind != pos->kind) continue; if (++i == param->num_headers) { if (key_size != pos->header_size) return MHD_NO; if (value_size != pos->value_size) return MHD_NO; if (0 != key_size) { mhd_assert (NULL != key); mhd_assert (NULL != pos->header); if (0 != memcmp (key, pos->header, key_size)) return MHD_NO; } if (0 != value_size) { mhd_assert (NULL != value); mhd_assert (NULL != pos->value); if (0 != memcmp (value, pos->value, value_size)) return MHD_NO; } return MHD_YES; } } return MHD_NO; } /** * Check that the arguments given by the client as part * of the authentication header match the arguments we * got as part of the HTTP request URI. * * @param connection connections with headers to compare against * @param args the copy of argument URI string (after "?" in URI), will be * modified by this function * @return boolean true if the arguments match, * boolean false if not */ static bool check_argument_match (struct MHD_Connection *connection, char *args) { struct MHD_HTTP_Req_Header *pos; enum MHD_Result ret; struct test_header_param param; param.connection = connection; param.num_headers = 0; ret = MHD_parse_arguments_ (connection, MHD_GET_ARGUMENT_KIND, args, &test_header, ¶m); if (MHD_NO == ret) { return false; } /* also check that the number of headers matches */ for (pos = connection->rq.headers_received; NULL != pos; pos = pos->next) { if (MHD_GET_ARGUMENT_KIND != pos->kind) continue; param.num_headers--; } if (0 != param.num_headers) { /* argument count mismatch */ return false; } return true; } /** * Check that the URI provided by the client as part * of the authentication header match the real HTTP request URI. * * @param connection connections with headers to compare against * @param uri the copy of URI in the authentication header, should point to * modifiable buffer at least @a uri_len + 1 characters long, * will be modified by this function, not valid upon return * @param uri_len the length of the @a uri string in characters * @return boolean true if the URIs match, * boolean false if not */ static bool check_uri_match (struct MHD_Connection *connection, char *uri, size_t uri_len) { char *qmark; char *args; struct MHD_Daemon *const daemon = connection->daemon; uri[uri_len] = 0; qmark = memchr (uri, '?', uri_len); if (NULL != qmark) *qmark = '\0'; /* Need to unescape URI before comparing with connection->url */ uri_len = daemon->unescape_callback (daemon->unescape_callback_cls, connection, uri); if ((uri_len != connection->rq.url_len) || (0 != memcmp (uri, connection->rq.url, uri_len))) { #ifdef HAVE_MESSAGES MHD_DLOG (daemon, _ ("Authentication failed, URI does not match.\n")); #endif return false; } args = (NULL != qmark) ? (qmark + 1) : uri + uri_len; if (! check_argument_match (connection, args) ) { #ifdef HAVE_MESSAGES MHD_DLOG (daemon, _ ("Authentication failed, arguments do not match.\n")); #endif return false; } return true; } /** * The size of the unquoting buffer in stack */ #define _MHD_STATIC_UNQ_BUFFER_SIZE 128 /** * Get the pointer to buffer with required size * @param tmp1 the first buffer with fixed size * @param ptmp2 the pointer to pointer to malloc'ed buffer * @param ptmp2_size the pointer to the size of the buffer pointed by @a ptmp2 * @param required_size the required size in buffer * @return the pointer to the buffer or NULL if failed to allocate buffer with * requested size */ static char * get_buffer_for_size (char tmp1[_MHD_STATIC_UNQ_BUFFER_SIZE], char **ptmp2, size_t *ptmp2_size, size_t required_size) { mhd_assert ((0 == *ptmp2_size) || (NULL != *ptmp2)); mhd_assert ((NULL != *ptmp2) || (0 == *ptmp2_size)); mhd_assert ((0 == *ptmp2_size) || \ (_MHD_STATIC_UNQ_BUFFER_SIZE < *ptmp2_size)); if (required_size <= _MHD_STATIC_UNQ_BUFFER_SIZE) return tmp1; if (required_size <= *ptmp2_size) return *ptmp2; if (required_size > _MHD_AUTH_DIGEST_MAX_PARAM_SIZE) return NULL; if (NULL != *ptmp2) free (*ptmp2); *ptmp2 = (char *) malloc (required_size); if (NULL == *ptmp2) *ptmp2_size = 0; else *ptmp2_size = required_size; return *ptmp2; } /** * The result of parameter unquoting */ enum _MHD_GetUnqResult { _MHD_UNQ_OK = 0, /**< Got unquoted string */ _MHD_UNQ_TOO_LARGE = -7, /**< The string is too large to unquote */ _MHD_UNQ_OUT_OF_MEM = 3 /**< Out of memory error */ }; /** * Get Digest authorisation parameter as unquoted string. * @param param the parameter to process * @param tmp1 the small buffer in stack * @param ptmp2 the pointer to pointer to malloc'ed buffer * @param ptmp2_size the pointer to the size of the buffer pointed by @a ptmp2 * @param[out] unquoted the pointer to store the result, NOT zero terminated * @return enum code indicating result of the process */ static enum _MHD_GetUnqResult get_unquoted_param (const struct MHD_RqDAuthParam *param, char tmp1[_MHD_STATIC_UNQ_BUFFER_SIZE], char **ptmp2, size_t *ptmp2_size, struct _MHD_str_w_len *unquoted) { char *str; size_t len; mhd_assert (NULL != param->value.str); mhd_assert (0 != param->value.len); if (! param->quoted) { unquoted->str = param->value.str; unquoted->len = param->value.len; return _MHD_UNQ_OK; } /* The value is present and is quoted, needs to be copied and unquoted */ str = get_buffer_for_size (tmp1, ptmp2, ptmp2_size, param->value.len); if (NULL == str) return (param->value.len > _MHD_AUTH_DIGEST_MAX_PARAM_SIZE) ? _MHD_UNQ_TOO_LARGE : _MHD_UNQ_OUT_OF_MEM; len = MHD_str_unquote (param->value.str, param->value.len, str); unquoted->str = str; unquoted->len = len; mhd_assert (0 != unquoted->len); mhd_assert (unquoted->len < param->value.len); return _MHD_UNQ_OK; } /** * Get copy of Digest authorisation parameter as unquoted string. * @param param the parameter to process * @param tmp1 the small buffer in stack * @param ptmp2 the pointer to pointer to malloc'ed buffer * @param ptmp2_size the pointer to the size of the buffer pointed by @a ptmp2 * @param[out] unquoted the pointer to store the result, NOT zero terminated, * but with enough space to zero-terminate * @return enum code indicating result of the process */ static enum _MHD_GetUnqResult get_unquoted_param_copy (const struct MHD_RqDAuthParam *param, char tmp1[_MHD_STATIC_UNQ_BUFFER_SIZE], char **ptmp2, size_t *ptmp2_size, struct _MHD_mstr_w_len *unquoted) { mhd_assert (NULL != param->value.str); mhd_assert (0 != param->value.len); /* The value is present and is quoted, needs to be copied and unquoted */ /* Allocate buffer with one more additional byte for zero-termination */ unquoted->str = get_buffer_for_size (tmp1, ptmp2, ptmp2_size, param->value.len + 1); if (NULL == unquoted->str) return (param->value.len + 1 > _MHD_AUTH_DIGEST_MAX_PARAM_SIZE) ? _MHD_UNQ_TOO_LARGE : _MHD_UNQ_OUT_OF_MEM; if (! param->quoted) { memcpy (unquoted->str, param->value.str, param->value.len); unquoted->len = param->value.len; return _MHD_UNQ_OK; } unquoted->len = MHD_str_unquote (param->value.str, param->value.len, unquoted->str); mhd_assert (0 != unquoted->len); mhd_assert (unquoted->len < param->value.len); return _MHD_UNQ_OK; } /** * Check whether Digest Auth request parameter is equal to given string * @param param the parameter to check * @param str the string to compare with, does not need to be zero-terminated * @param str_len the length of the @a str * @return true is parameter is equal to the given string, * false otherwise */ _MHD_static_inline bool is_param_equal (const struct MHD_RqDAuthParam *param, const char *const str, const size_t str_len) { mhd_assert (NULL != param->value.str); mhd_assert (0 != param->value.len); if (param->quoted) return MHD_str_equal_quoted_bin_n (param->value.str, param->value.len, str, str_len); return (str_len == param->value.len) && (0 == memcmp (str, param->value.str, str_len)); } /** * Check whether Digest Auth request parameter is caseless equal to given string * @param param the parameter to check * @param str the string to compare with, does not need to be zero-terminated * @param str_len the length of the @a str * @return true is parameter is caseless equal to the given string, * false otherwise */ _MHD_static_inline bool is_param_equal_caseless (const struct MHD_RqDAuthParam *param, const char *const str, const size_t str_len) { mhd_assert (NULL != param->value.str); mhd_assert (0 != param->value.len); if (param->quoted) return MHD_str_equal_quoted_bin_n (param->value.str, param->value.len, str, str_len); return (str_len == param->value.len) && (0 == memcmp (str, param->value.str, str_len)); } /** * Authenticates the authorization header sent by the client * * If RFC2069 mode is allowed by setting bit #MHD_DIGEST_AUTH_QOP_NONE in * @a mqop and the client uses this mode, then server generated nonces are * used as one-time nonces because nonce-count is not supported in this old RFC. * Communication in this mode is very inefficient, especially if the client * requests several resources one-by-one as for every request new nonce must be * generated and client repeat all requests twice (first time to get a new * nonce and second time to perform an authorised request). * * @param connection the MHD connection structure * @param realm the realm presented to the client * @param username the username needs to be authenticated * @param password the password used in the authentication * @param userdigest the optional precalculated binary hash of the string * "username:realm:password" * @param nonce_timeout the period of seconds since nonce generation, when * the nonce is recognised as valid and not stale. * @param max_nc the maximum allowed nc (Nonce Count) value, if client's nc * exceeds the specified value then MHD_DAUTH_NONCE_STALE is * returned; * zero for no limit * @param mqop the QOP to use * @param malgo3 digest algorithms allowed to use, fail if algorithm specified * by the client is not allowed by this parameter * @param[out] pbuf the pointer to pointer to internally malloc'ed buffer, * to be free if not NULL upon return * @return #MHD_DAUTH_OK if authenticated, * error code otherwise. * @ingroup authentication */ static enum MHD_DigestAuthResult digest_auth_check_all_inner (struct MHD_Connection *connection, const char *realm, const char *username, const char *password, const uint8_t *userdigest, unsigned int nonce_timeout, uint32_t max_nc, enum MHD_DigestAuthMultiQOP mqop, enum MHD_DigestAuthMultiAlgo3 malgo3, char **pbuf, struct DigestAlgorithm *da) { struct MHD_Daemon *daemon = MHD_get_master (connection->daemon); enum MHD_DigestAuthAlgo3 c_algo; /**< Client's algorithm */ enum MHD_DigestAuthQOP c_qop; /**< Client's QOP */ unsigned int digest_size; uint8_t hash1_bin[MAX_DIGEST]; uint8_t hash2_bin[MAX_DIGEST]; #if 0 const char *hentity = NULL; /* "auth-int" is not supported */ #endif uint64_t nonce_time; uint64_t nci; const struct MHD_RqDAuth *params; /** * Temporal buffer in stack for unquoting and other needs */ char tmp1[_MHD_STATIC_UNQ_BUFFER_SIZE]; char **const ptmp2 = pbuf; /**< Temporal malloc'ed buffer for unquoting */ size_t tmp2_size; /**< The size of @a tmp2 buffer */ struct _MHD_str_w_len unquoted; struct _MHD_mstr_w_len unq_copy; enum _MHD_GetUnqResult unq_res; size_t username_len; size_t realm_len; tmp2_size = 0; params = MHD_get_rq_dauth_params_ (connection); if (NULL == params) return MHD_DAUTH_WRONG_HEADER; /* ** Initial parameters checks and setup ** */ /* Get client's algorithm */ c_algo = params->algo3; /* Check whether client's algorithm is allowed by function parameter */ if (((unsigned int) c_algo) != (((unsigned int) c_algo) & ((unsigned int) malgo3))) return MHD_DAUTH_WRONG_ALGO; /* Check whether client's algorithm is supported */ if (0 != (((unsigned int) c_algo) & MHD_DIGEST_AUTH_ALGO3_SESSION)) { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("The 'session' algorithms are not supported.\n")); #endif /* HAVE_MESSAGES */ return MHD_DAUTH_WRONG_ALGO; } #ifndef MHD_MD5_SUPPORT if (0 != (((unsigned int) c_algo) & MHD_DIGEST_BASE_ALGO_MD5)) { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("The MD5 algorithm is not supported by this MHD build.\n")); #endif /* HAVE_MESSAGES */ return MHD_DAUTH_WRONG_ALGO; } #endif /* ! MHD_MD5_SUPPORT */ #ifndef MHD_SHA256_SUPPORT if (0 != (((unsigned int) c_algo) & MHD_DIGEST_BASE_ALGO_SHA256)) { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("The SHA-256 algorithm is not supported by " "this MHD build.\n")); #endif /* HAVE_MESSAGES */ return MHD_DAUTH_WRONG_ALGO; } #endif /* ! MHD_SHA256_SUPPORT */ #ifndef MHD_SHA512_256_SUPPORT if (0 != (((unsigned int) c_algo) & MHD_DIGEST_BASE_ALGO_SHA512_256)) { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("The SHA-512/256 algorithm is not supported by " "this MHD build.\n")); #endif /* HAVE_MESSAGES */ return MHD_DAUTH_WRONG_ALGO; } #endif /* ! MHD_SHA512_256_SUPPORT */ if (! digest_init_one_time (da, get_base_digest_algo (c_algo))) MHD_PANIC (_ ("Wrong 'malgo3' value, API violation")); /* Check 'mqop' value */ c_qop = params->qop; /* Check whether client's QOP is allowed by function parameter */ if (((unsigned int) c_qop) != (((unsigned int) c_qop) & ((unsigned int) mqop))) return MHD_DAUTH_WRONG_QOP; if (0 != (((unsigned int) c_qop) & MHD_DIGEST_AUTH_QOP_AUTH_INT)) { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("The 'auth-int' QOP is not supported.\n")); #endif /* HAVE_MESSAGES */ return MHD_DAUTH_WRONG_QOP; } #ifdef HAVE_MESSAGES if ((MHD_DIGEST_AUTH_QOP_NONE == c_qop) && (0 == (((unsigned int) c_algo) & MHD_DIGEST_BASE_ALGO_MD5))) MHD_DLOG (connection->daemon, _ ("RFC2069 with SHA-256 or SHA-512/256 algorithm is " \ "non-standard extension.\n")); #endif /* HAVE_MESSAGES */ digest_size = digest_get_size (da); /* ** A quick check for presence of all required parameters ** */ if ((NULL == params->username.value.str) && (NULL == params->username_ext.value.str)) return MHD_DAUTH_WRONG_USERNAME; else if ((NULL != params->username.value.str) && (NULL != params->username_ext.value.str)) return MHD_DAUTH_WRONG_USERNAME; /* Parameters cannot be used together */ else if ((NULL != params->username_ext.value.str) && (MHD_DAUTH_EXT_PARAM_MIN_LEN > params->username_ext.value.len)) return MHD_DAUTH_WRONG_USERNAME; /* Broken extended notation */ else if (params->userhash && (NULL == params->username.value.str)) return MHD_DAUTH_WRONG_USERNAME; /* Userhash cannot be used with extended notation */ else if (params->userhash && (digest_size * 2 > params->username.value.len)) return MHD_DAUTH_WRONG_USERNAME; /* Too few chars for correct userhash */ else if (params->userhash && (digest_size * 4 < params->username.value.len)) return MHD_DAUTH_WRONG_USERNAME; /* Too many chars for correct userhash */ if (NULL == params->realm.value.str) return MHD_DAUTH_WRONG_REALM; else if (((NULL == userdigest) || params->userhash) && (_MHD_AUTH_DIGEST_MAX_PARAM_SIZE < params->realm.value.len)) return MHD_DAUTH_TOO_LARGE; /* Realm is too large and should be used in hash calculations */ if (MHD_DIGEST_AUTH_QOP_NONE != c_qop) { if (NULL == params->nc.value.str) return MHD_DAUTH_WRONG_HEADER; else if (0 == params->nc.value.len) return MHD_DAUTH_WRONG_HEADER; else if (4 * 8 < params->nc.value.len) /* Four times more than needed */ return MHD_DAUTH_WRONG_HEADER; if (NULL == params->cnonce.value.str) return MHD_DAUTH_WRONG_HEADER; else if (0 == params->cnonce.value.len) return MHD_DAUTH_WRONG_HEADER; else if (_MHD_AUTH_DIGEST_MAX_PARAM_SIZE < params->cnonce.value.len) return MHD_DAUTH_TOO_LARGE; } /* The QOP parameter was checked already */ if (NULL == params->uri.value.str) return MHD_DAUTH_WRONG_URI; else if (0 == params->uri.value.len) return MHD_DAUTH_WRONG_URI; else if (_MHD_AUTH_DIGEST_MAX_PARAM_SIZE < params->uri.value.len) return MHD_DAUTH_TOO_LARGE; if (NULL == params->nonce.value.str) return MHD_DAUTH_NONCE_WRONG; else if (0 == params->nonce.value.len) return MHD_DAUTH_NONCE_WRONG; else if (NONCE_STD_LEN (digest_size) * 2 < params->nonce.value.len) return MHD_DAUTH_NONCE_WRONG; if (NULL == params->response.value.str) return MHD_DAUTH_RESPONSE_WRONG; else if (0 == params->response.value.len) return MHD_DAUTH_RESPONSE_WRONG; else if (digest_size * 4 < params->response.value.len) return MHD_DAUTH_RESPONSE_WRONG; /* ** Check simple parameters match ** */ /* Check 'algorithm' */ /* The 'algorithm' was checked at the start of the function */ /* 'algorithm' valid */ /* Check 'qop' */ /* The 'qop' was checked at the start of the function */ /* 'qop' valid */ /* Check 'realm' */ realm_len = strlen (realm); if (! is_param_equal (¶ms->realm, realm, realm_len)) return MHD_DAUTH_WRONG_REALM; /* 'realm' valid */ /* Check 'username' */ username_len = strlen (username); if (! params->userhash) { if (NULL != params->username.value.str) { /* Username in standard notation */ if (! is_param_equal (¶ms->username, username, username_len)) return MHD_DAUTH_WRONG_USERNAME; } else { /* Username in extended notation */ char *r_uname; size_t buf_size = params->username_ext.value.len; ssize_t res; mhd_assert (NULL != params->username_ext.value.str); mhd_assert (MHD_DAUTH_EXT_PARAM_MIN_LEN <= buf_size); /* It was checked already */ buf_size += 1; /* For zero-termination */ buf_size -= MHD_DAUTH_EXT_PARAM_MIN_LEN; r_uname = get_buffer_for_size (tmp1, ptmp2, &tmp2_size, buf_size); if (NULL == r_uname) return (_MHD_AUTH_DIGEST_MAX_PARAM_SIZE < buf_size) ? MHD_DAUTH_TOO_LARGE : MHD_DAUTH_ERROR; res = get_rq_extended_uname_copy_z (params->username_ext.value.str, params->username_ext.value.len, r_uname, buf_size); if (0 > res) return MHD_DAUTH_WRONG_HEADER; /* Broken extended notation */ if ((username_len != (size_t) res) || (0 != memcmp (username, r_uname, username_len))) return MHD_DAUTH_WRONG_USERNAME; } } else { /* Userhash */ mhd_assert (NULL != params->username.value.str); calc_userhash (da, username, username_len, realm, realm_len, hash1_bin); #ifdef MHD_DIGEST_HAS_EXT_ERROR if (digest_ext_error (da)) return MHD_DAUTH_ERROR; #endif /* MHD_DIGEST_HAS_EXT_ERROR */ mhd_assert (sizeof (tmp1) >= (2 * digest_size)); MHD_bin_to_hex (hash1_bin, digest_size, tmp1); if (! is_param_equal_caseless (¶ms->username, tmp1, 2 * digest_size)) return MHD_DAUTH_WRONG_USERNAME; /* To simplify the logic, the digest is reset here instead of resetting before the next hash calculation. */ digest_reset (da); } /* 'username' valid */ /* ** Do basic nonce and nonce-counter checks (size, timestamp) ** */ /* Get 'nc' digital value */ if (MHD_DIGEST_AUTH_QOP_NONE != c_qop) { unq_res = get_unquoted_param (¶ms->nc, tmp1, ptmp2, &tmp2_size, &unquoted); if (_MHD_UNQ_OK != unq_res) return MHD_DAUTH_ERROR; if (unquoted.len != MHD_strx_to_uint64_n_ (unquoted.str, unquoted.len, &nci)) { #ifdef HAVE_MESSAGES MHD_DLOG (daemon, _ ("Authentication failed, invalid nc format.\n")); #endif return MHD_DAUTH_WRONG_HEADER; /* invalid nonce format */ } if (0 == nci) { #ifdef HAVE_MESSAGES MHD_DLOG (daemon, _ ("Authentication failed, invalid 'nc' value.\n")); #endif return MHD_DAUTH_WRONG_HEADER; /* invalid nc value */ } if ((0 != max_nc) && (max_nc < nci)) return MHD_DAUTH_NONCE_STALE; /* Too large 'nc' value */ } else nci = 1; /* Force 'nc' value */ /* Got 'nc' digital value */ /* Get 'nonce' with basic checks */ unq_res = get_unquoted_param (¶ms->nonce, tmp1, ptmp2, &tmp2_size, &unquoted); if (_MHD_UNQ_OK != unq_res) return MHD_DAUTH_ERROR; if ((NONCE_STD_LEN (digest_size) != unquoted.len) || (! get_nonce_timestamp (unquoted.str, unquoted.len, &nonce_time))) { #ifdef HAVE_MESSAGES MHD_DLOG (daemon, _ ("Authentication failed, invalid nonce format.\n")); #endif return MHD_DAUTH_NONCE_WRONG; } if (1) { uint64_t t; t = MHD_monotonic_msec_counter (); /* * First level vetting for the nonce validity: if the timestamp * attached to the nonce exceeds `nonce_timeout', then the nonce is * invalid. */ if (TRIM_TO_TIMESTAMP (t - nonce_time) > (nonce_timeout * 1000)) return MHD_DAUTH_NONCE_STALE; /* too old */ } if (1) { enum MHD_CheckNonceNC_ nonce_nc_check; /* * Checking if that combination of nonce and nc is sound * and not a replay attack attempt. Refuse if nonce was not * generated previously. */ nonce_nc_check = check_nonce_nc (connection, unquoted.str, NONCE_STD_LEN (digest_size), nonce_time, nci); if (MHD_CHECK_NONCENC_STALE == nonce_nc_check) { #ifdef HAVE_MESSAGES if (MHD_DIGEST_AUTH_QOP_NONE != c_qop) MHD_DLOG (daemon, _ ("Stale nonce received. If this happens a lot, you should " "probably increase the size of the nonce array.\n")); else MHD_DLOG (daemon, _ ("Stale nonce received. This is expected when client " \ "uses RFC2069-compatible mode and makes more than one " \ "request.\n")); #endif return MHD_DAUTH_NONCE_STALE; } else if (MHD_CHECK_NONCENC_WRONG == nonce_nc_check) { #ifdef HAVE_MESSAGES MHD_DLOG (daemon, _ ("Received nonce that was not " "generated by MHD. This may indicate an attack attempt.\n")); #endif return MHD_DAUTH_NONCE_WRONG; } mhd_assert (MHD_CHECK_NONCENC_OK == nonce_nc_check); } /* The nonce was generated by MHD, is not stale and nonce-nc combination was not used before */ /* ** Build H(A2) and check URI match in the header and in the request ** */ /* Get 'uri' */ mhd_assert (! da->hashing); digest_update_str (da, connection->rq.method); digest_update_with_colon (da); #if 0 /* TODO: add support for "auth-int" */ digest_update_str (da, hentity); digest_update_with_colon (da); #endif unq_res = get_unquoted_param_copy (¶ms->uri, tmp1, ptmp2, &tmp2_size, &unq_copy); if (_MHD_UNQ_OK != unq_res) return MHD_DAUTH_ERROR; digest_update (da, unq_copy.str, unq_copy.len); /* The next check will modify copied URI string */ if (! check_uri_match (connection, unq_copy.str, unq_copy.len)) return MHD_DAUTH_WRONG_URI; digest_calc_hash (da, hash2_bin); #ifdef MHD_DIGEST_HAS_EXT_ERROR /* Skip digest calculation external error check, the next one checks both */ #endif /* MHD_DIGEST_HAS_EXT_ERROR */ /* Got H(A2) */ /* ** Build H(A1) ** */ if (NULL == userdigest) { mhd_assert (! da->hashing); digest_reset (da); calc_userdigest (da, username, username_len, realm, realm_len, password, hash1_bin); } /* TODO: support '-sess' versions */ #ifdef MHD_DIGEST_HAS_EXT_ERROR if (digest_ext_error (da)) return MHD_DAUTH_ERROR; #endif /* MHD_DIGEST_HAS_EXT_ERROR */ /* Got H(A1) */ /* ** Check 'response' ** */ mhd_assert (! da->hashing); digest_reset (da); /* Update digest with H(A1) */ mhd_assert (sizeof (tmp1) >= (digest_size * 2)); if (NULL == userdigest) MHD_bin_to_hex (hash1_bin, digest_size, tmp1); else MHD_bin_to_hex (userdigest, digest_size, tmp1); digest_update (da, (const uint8_t *) tmp1, digest_size * 2); /* H(A1) is not needed anymore, reuse the buffer. * Use hash1_bin for the client's 'response' decoded to binary form. */ unq_res = get_unquoted_param (¶ms->response, tmp1, ptmp2, &tmp2_size, &unquoted); if (_MHD_UNQ_OK != unq_res) return MHD_DAUTH_ERROR; if (digest_size != MHD_hex_to_bin (unquoted.str, unquoted.len, hash1_bin)) return MHD_DAUTH_RESPONSE_WRONG; /* Update digest with ':' */ digest_update_with_colon (da); /* Update digest with 'nonce' text value */ unq_res = get_unquoted_param (¶ms->nonce, tmp1, ptmp2, &tmp2_size, &unquoted); if (_MHD_UNQ_OK != unq_res) return MHD_DAUTH_ERROR; digest_update (da, (const uint8_t *) unquoted.str, unquoted.len); /* Update digest with ':' */ digest_update_with_colon (da); if (MHD_DIGEST_AUTH_QOP_NONE != c_qop) { /* Update digest with 'nc' text value */ unq_res = get_unquoted_param (¶ms->nc, tmp1, ptmp2, &tmp2_size, &unquoted); if (_MHD_UNQ_OK != unq_res) return MHD_DAUTH_ERROR; digest_update (da, (const uint8_t *) unquoted.str, unquoted.len); /* Update digest with ':' */ digest_update_with_colon (da); /* Update digest with 'cnonce' value */ unq_res = get_unquoted_param (¶ms->cnonce, tmp1, ptmp2, &tmp2_size, &unquoted); if (_MHD_UNQ_OK != unq_res) return MHD_DAUTH_ERROR; digest_update (da, (const uint8_t *) unquoted.str, unquoted.len); /* Update digest with ':' */ digest_update_with_colon (da); /* Update digest with 'qop' value */ unq_res = get_unquoted_param (¶ms->qop_raw, tmp1, ptmp2, &tmp2_size, &unquoted); if (_MHD_UNQ_OK != unq_res) return MHD_DAUTH_ERROR; digest_update (da, (const uint8_t *) unquoted.str, unquoted.len); /* Update digest with ':' */ digest_update_with_colon (da); } /* Update digest with H(A2) */ MHD_bin_to_hex (hash2_bin, digest_size, tmp1); digest_update (da, (const uint8_t *) tmp1, digest_size * 2); /* H(A2) is not needed anymore, reuse the buffer. * Use hash2_bin for the calculated response in binary form */ digest_calc_hash (da, hash2_bin); #ifdef MHD_DIGEST_HAS_EXT_ERROR if (digest_ext_error (da)) return MHD_DAUTH_ERROR; #endif /* MHD_DIGEST_HAS_EXT_ERROR */ if (0 != memcmp (hash1_bin, hash2_bin, digest_size)) return MHD_DAUTH_RESPONSE_WRONG; if (MHD_DAUTH_BIND_NONCE_NONE != daemon->dauth_bind_type) { mhd_assert (sizeof(tmp1) >= (NONCE_STD_LEN (digest_size) + 1)); /* It was already checked that 'nonce' (including timestamp) was generated by MHD. */ mhd_assert (! da->hashing); digest_reset (da); calculate_nonce (nonce_time, connection->rq.http_mthd, connection->rq.method, daemon->digest_auth_random, daemon->digest_auth_rand_size, connection->addr, (size_t) connection->addr_len, connection->rq.url, connection->rq.url_len, connection->rq.headers_received, realm, realm_len, daemon->dauth_bind_type, da, tmp1); #ifdef MHD_DIGEST_HAS_EXT_ERROR if (digest_ext_error (da)) return MHD_DAUTH_ERROR; #endif /* MHD_DIGEST_HAS_EXT_ERROR */ if (! is_param_equal (¶ms->nonce, tmp1, NONCE_STD_LEN (digest_size))) return MHD_DAUTH_NONCE_OTHER_COND; /* The 'nonce' was generated in the same conditions */ } return MHD_DAUTH_OK; } /** * Authenticates the authorization header sent by the client * * If RFC2069 mode is allowed by setting bit #MHD_DIGEST_AUTH_QOP_NONE in * @a mqop and the client uses this mode, then server generated nonces are * used as one-time nonces because nonce-count is not supported in this old RFC. * Communication in this mode is very inefficient, especially if the client * requests several resources one-by-one as for every request new nonce must be * generated and client repeat all requests twice (first time to get a new * nonce and second time to perform an authorised request). * * @param connection the MHD connection structure * @param realm the realm presented to the client * @param username the username needs to be authenticated * @param password the password used in the authentication * @param userdigest the optional precalculated binary hash of the string * "username:realm:password" * @param nonce_timeout the period of seconds since nonce generation, when * the nonce is recognised as valid and not stale. * @param max_nc the maximum allowed nc (Nonce Count) value, if client's nc * exceeds the specified value then MHD_DAUTH_NONCE_STALE is * returned; * zero for no limit * @param mqop the QOP to use * @param malgo3 digest algorithms allowed to use, fail if algorithm specified * by the client is not allowed by this parameter * @return #MHD_DAUTH_OK if authenticated, * error code otherwise. * @ingroup authentication */ static enum MHD_DigestAuthResult digest_auth_check_all (struct MHD_Connection *connection, const char *realm, const char *username, const char *password, const uint8_t *userdigest, unsigned int nonce_timeout, uint32_t max_nc, enum MHD_DigestAuthMultiQOP mqop, enum MHD_DigestAuthMultiAlgo3 malgo3) { enum MHD_DigestAuthResult res; char *buf; struct DigestAlgorithm da; buf = NULL; digest_setup_zero (&da); res = digest_auth_check_all_inner (connection, realm, username, password, userdigest, nonce_timeout, max_nc, mqop, malgo3, &buf, &da); digest_deinit (&da); if (NULL != buf) free (buf); return res; } /** * Authenticates the authorization header sent by the client. * Uses #MHD_DIGEST_ALG_MD5 (for now, for backwards-compatibility). * Note that this MAY change to #MHD_DIGEST_ALG_AUTO in the future. * If you want to be sure you get MD5, use #MHD_digest_auth_check2() * and specify MD5 explicitly. * * @param connection The MHD connection structure * @param realm The realm presented to the client * @param username The username needs to be authenticated * @param password The password used in the authentication * @param nonce_timeout The amount of time for a nonce to be * invalid in seconds * @return #MHD_YES if authenticated, #MHD_NO if not, * #MHD_INVALID_NONCE if nonce is invalid or stale * @deprecated use MHD_digest_auth_check3() * @ingroup authentication */ _MHD_EXTERN int MHD_digest_auth_check (struct MHD_Connection *connection, const char *realm, const char *username, const char *password, unsigned int nonce_timeout) { return MHD_digest_auth_check2 (connection, realm, username, password, nonce_timeout, MHD_DIGEST_ALG_MD5); } /** * Authenticates the authorization header sent by the client. * * If RFC2069 mode is allowed by setting bit #MHD_DIGEST_AUTH_QOP_NONE in * @a mqop and the client uses this mode, then server generated nonces are * used as one-time nonces because nonce-count is not supported in this old RFC. * Communication in this mode is very inefficient, especially if the client * requests several resources one-by-one as for every request new nonce must be * generated and client repeat all requests twice (first time to get a new * nonce and second time to perform an authorised request). * * @param connection the MHD connection structure * @param realm the realm to be used for authorization of the client * @param username the username needs to be authenticated, must be in clear text * even if userhash is used by the client * @param password the password used in the authentication * @param nonce_timeout the nonce validity duration in seconds * @param max_nc the maximum allowed nc (Nonce Count) value, if client's nc * exceeds the specified value then MHD_DAUTH_NONCE_STALE is * returned; * zero for no limit * @param mqop the QOP to use * @param malgo3 digest algorithms allowed to use, fail if algorithm used * by the client is not allowed by this parameter * @return #MHD_DAUTH_OK if authenticated, * the error code otherwise * @note Available since #MHD_VERSION 0x00097528 * @ingroup authentication */ _MHD_EXTERN enum MHD_DigestAuthResult MHD_digest_auth_check3 (struct MHD_Connection *connection, const char *realm, const char *username, const char *password, unsigned int nonce_timeout, uint32_t max_nc, enum MHD_DigestAuthMultiQOP mqop, enum MHD_DigestAuthMultiAlgo3 malgo3) { mhd_assert (NULL != password); return digest_auth_check_all (connection, realm, username, password, NULL, nonce_timeout, max_nc, mqop, malgo3); } /** * Authenticates the authorization header sent by the client by using * hash of "username:realm:password". * * If RFC2069 mode is allowed by setting bit #MHD_DIGEST_AUTH_QOP_NONE in * @a mqop and the client uses this mode, then server generated nonces are * used as one-time nonces because nonce-count is not supported in this old RFC. * Communication in this mode is very inefficient, especially if the client * requests several resources one-by-one as for every request new nonce must be * generated and client repeat all requests twice (first time to get a new * nonce and second time to perform an authorised request). * * @param connection the MHD connection structure * @param realm the realm to be used for authorization of the client * @param username the username needs to be authenticated, must be in clear text * even if userhash is used by the client * @param userdigest the precalculated binary hash of the string * "username:realm:password", * see #MHD_digest_auth_calc_userdigest() * @param userdigest_size the size of the @a userdigest in bytes, must match the * hashing algorithm (see #MHD_MD5_DIGEST_SIZE, * #MHD_SHA256_DIGEST_SIZE, #MHD_SHA512_256_DIGEST_SIZE, * #MHD_digest_get_hash_size()) * @param nonce_timeout the period of seconds since nonce generation, when * the nonce is recognised as valid and not stale. * @param max_nc the maximum allowed nc (Nonce Count) value, if client's nc * exceeds the specified value then MHD_DAUTH_NONCE_STALE is * returned; * zero for no limit * @param mqop the QOP to use * @param malgo3 digest algorithms allowed to use, fail if algorithm used * by the client is not allowed by this parameter; * more than one base algorithms (MD5, SHA-256, SHA-512/256) * cannot be used at the same time for this function * as @a userdigest must match specified algorithm * @return #MHD_DAUTH_OK if authenticated, * the error code otherwise * @sa #MHD_digest_auth_calc_userdigest() * @note Available since #MHD_VERSION 0x00097528 * @ingroup authentication */ _MHD_EXTERN enum MHD_DigestAuthResult MHD_digest_auth_check_digest3 (struct MHD_Connection *connection, const char *realm, const char *username, const void *userdigest, size_t userdigest_size, unsigned int nonce_timeout, uint32_t max_nc, enum MHD_DigestAuthMultiQOP mqop, enum MHD_DigestAuthMultiAlgo3 malgo3) { if (1 != (((0 != (malgo3 & MHD_DIGEST_BASE_ALGO_MD5)) ? 1 : 0) + ((0 != (malgo3 & MHD_DIGEST_BASE_ALGO_SHA256)) ? 1 : 0) + ((0 != (malgo3 & MHD_DIGEST_BASE_ALGO_SHA512_256)) ? 1 : 0))) MHD_PANIC (_ ("Wrong 'malgo3' value, only one base hashing algorithm " \ "(MD5, SHA-256 or SHA-512/256) must be specified, " \ "API violation")); #ifndef MHD_MD5_SUPPORT if (0 != (((unsigned int) malgo3) & MHD_DIGEST_BASE_ALGO_MD5)) { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("The MD5 algorithm is not supported by this MHD build.\n")); #endif /* HAVE_MESSAGES */ return MHD_DAUTH_WRONG_ALGO; } #endif /* ! MHD_MD5_SUPPORT */ #ifndef MHD_SHA256_SUPPORT if (0 != (((unsigned int) malgo3) & MHD_DIGEST_BASE_ALGO_SHA256)) { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("The SHA-256 algorithm is not supported by " "this MHD build.\n")); #endif /* HAVE_MESSAGES */ return MHD_DAUTH_WRONG_ALGO; } #endif /* ! MHD_SHA256_SUPPORT */ #ifndef MHD_SHA512_256_SUPPORT if (0 != (((unsigned int) malgo3) & MHD_DIGEST_BASE_ALGO_SHA512_256)) { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("The SHA-512/256 algorithm is not supported by " "this MHD build.\n")); #endif /* HAVE_MESSAGES */ return MHD_DAUTH_WRONG_ALGO; } #endif /* ! MHD_SHA512_256_SUPPORT */ if (digest_get_hash_size ((enum MHD_DigestAuthAlgo3) malgo3) != userdigest_size) MHD_PANIC (_ ("Wrong 'userdigest_size' value, does not match 'malgo3', " "API violation")); return digest_auth_check_all (connection, realm, username, NULL, (const uint8_t *) userdigest, nonce_timeout, max_nc, mqop, malgo3); } /** * Authenticates the authorization header sent by the client. * * @param connection The MHD connection structure * @param realm The realm presented to the client * @param username The username needs to be authenticated * @param password The password used in the authentication * @param nonce_timeout The amount of time for a nonce to be * invalid in seconds * @param algo digest algorithms allowed for verification * @return #MHD_YES if authenticated, #MHD_NO if not, * #MHD_INVALID_NONCE if nonce is invalid or stale * @note Available since #MHD_VERSION 0x00096200 * @deprecated use MHD_digest_auth_check3() * @ingroup authentication */ _MHD_EXTERN 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) { enum MHD_DigestAuthResult res; enum MHD_DigestAuthMultiAlgo3 malgo3; if (MHD_DIGEST_ALG_AUTO == algo) malgo3 = MHD_DIGEST_AUTH_MULT_ALGO3_ANY_NON_SESSION; else if (MHD_DIGEST_ALG_MD5 == algo) malgo3 = MHD_DIGEST_AUTH_MULT_ALGO3_MD5; else if (MHD_DIGEST_ALG_SHA256 == algo) malgo3 = MHD_DIGEST_AUTH_MULT_ALGO3_SHA256; else MHD_PANIC (_ ("Wrong 'algo' value, API violation")); res = MHD_digest_auth_check3 (connection, realm, username, password, nonce_timeout, 0, MHD_DIGEST_AUTH_MULT_QOP_AUTH, malgo3); if (MHD_DAUTH_OK == res) return MHD_YES; else if ((MHD_DAUTH_NONCE_STALE == res) || (MHD_DAUTH_NONCE_WRONG == res) || (MHD_DAUTH_NONCE_OTHER_COND == res) ) return MHD_INVALID_NONCE; return MHD_NO; } /** * Authenticates the authorization header sent by the client. * * @param connection The MHD connection structure * @param realm The realm presented to the client * @param username The username needs to be authenticated * @param digest An `unsigned char *' pointer to the binary MD5 sum * for the precalculated hash value "username:realm:password" * of @a digest_size bytes * @param digest_size number of bytes in @a digest (size must match @a algo!) * @param nonce_timeout The amount of time for a nonce to be * invalid in seconds * @param algo digest algorithms allowed for verification * @return #MHD_YES if authenticated, #MHD_NO if not, * #MHD_INVALID_NONCE if nonce is invalid or stale * @note Available since #MHD_VERSION 0x00096200 * @deprecated use MHD_digest_auth_check_digest3() * @ingroup authentication */ _MHD_EXTERN int MHD_digest_auth_check_digest2 (struct MHD_Connection *connection, const char *realm, const char *username, const uint8_t *digest, size_t digest_size, unsigned int nonce_timeout, enum MHD_DigestAuthAlgorithm algo) { enum MHD_DigestAuthResult res; enum MHD_DigestAuthMultiAlgo3 malgo3; if (MHD_DIGEST_ALG_AUTO == algo) malgo3 = MHD_DIGEST_AUTH_MULT_ALGO3_ANY_NON_SESSION; else if (MHD_DIGEST_ALG_MD5 == algo) malgo3 = MHD_DIGEST_AUTH_MULT_ALGO3_MD5; else if (MHD_DIGEST_ALG_SHA256 == algo) malgo3 = MHD_DIGEST_AUTH_MULT_ALGO3_SHA256; else MHD_PANIC (_ ("Wrong 'algo' value, API violation")); res = MHD_digest_auth_check_digest3 (connection, realm, username, digest, digest_size, nonce_timeout, 0, MHD_DIGEST_AUTH_MULT_QOP_AUTH, malgo3); if (MHD_DAUTH_OK == res) return MHD_YES; else if ((MHD_DAUTH_NONCE_STALE == res) || (MHD_DAUTH_NONCE_WRONG == res) || (MHD_DAUTH_NONCE_OTHER_COND == res) ) return MHD_INVALID_NONCE; return MHD_NO; } /** * Authenticates the authorization header sent by the client * Uses #MHD_DIGEST_ALG_MD5 (required, as @a digest is of fixed * size). * * @param connection The MHD connection structure * @param realm The realm presented to the client * @param username The username needs to be authenticated * @param digest An `unsigned char *' pointer to the binary hash * for the precalculated hash value "username:realm:password"; * length must be #MHD_MD5_DIGEST_SIZE bytes * @param nonce_timeout The amount of time for a nonce to be * invalid in seconds * @return #MHD_YES if authenticated, #MHD_NO if not, * #MHD_INVALID_NONCE if nonce is invalid or stale * @note Available since #MHD_VERSION 0x00096000 * @deprecated use #MHD_digest_auth_check_digest3() * @ingroup authentication */ _MHD_EXTERN int MHD_digest_auth_check_digest (struct MHD_Connection *connection, const char *realm, const char *username, const uint8_t digest[MHD_MD5_DIGEST_SIZE], unsigned int nonce_timeout) { return MHD_digest_auth_check_digest2 (connection, realm, username, digest, MHD_MD5_DIGEST_SIZE, nonce_timeout, MHD_DIGEST_ALG_MD5); } /** * Internal version of #MHD_queue_auth_required_response3() to simplify * cleanups. * * @param connection the MHD connection structure * @param realm the realm presented to the client * @param opaque the string for opaque value, can be NULL, but NULL is * not recommended for better compatibility with clients; * the recommended format is hex or Base64 encoded string * @param domain the optional space-separated list of URIs for which the * same authorisation could be used, URIs can be in form * "path-absolute" (the path for the same host with initial slash) * or in form "absolute-URI" (the full path with protocol), in * any case client may assume that URI is in the same "protection * space" if it starts with any of values specified here; * could be NULL (clients typically assume that the same * credentials could be used for any URI on the same host) * @param response the reply to send; should contain the "access denied" * body; note that this function sets the "WWW Authenticate" * header and that the caller should not do this; * the NULL is tolerated * @param signal_stale set to #MHD_YES if the nonce is stale to add 'stale=true' * to the authentication header, this instructs the client * to retry immediately with the new nonce and the same * credentials, without asking user for the new password * @param mqop the QOP to use * @param malgo3 digest algorithm to use, MHD selects; if several algorithms * are allowed then MD5 is preferred (currently, may be changed * in next versions) * @param userhash_support if set to non-zero value (#MHD_YES) then support of * userhash is indicated, the client may provide * hash("username:realm") instead of username in * clear text; * note that clients are allowed to provide the username * in cleartext even if this parameter set to non-zero; * when userhash is used, application must be ready to * identify users by provided userhash value instead of * username; see #MHD_digest_auth_calc_userhash() and * #MHD_digest_auth_calc_userhash_hex() * @param prefer_utf8 if not set to #MHD_NO, parameter 'charset=UTF-8' is * added, indicating for the client that UTF-8 encoding * is preferred * @param prefer_utf8 if not set to #MHD_NO, parameter 'charset=UTF-8' is * added, indicating for the client that UTF-8 encoding * is preferred * @return #MHD_YES on success, #MHD_NO otherwise * @note Available since #MHD_VERSION 0x00097526 * @ingroup authentication */ static enum MHD_Result queue_auth_required_response3_inner (struct MHD_Connection *connection, const char *realm, const char *opaque, const char *domain, struct MHD_Response *response, int signal_stale, enum MHD_DigestAuthMultiQOP mqop, enum MHD_DigestAuthMultiAlgo3 malgo3, int userhash_support, int prefer_utf8, char **buf_ptr, struct DigestAlgorithm *da) { static const char prefix_realm[] = "realm=\""; static const char prefix_qop[] = "qop=\""; static const char prefix_algo[] = "algorithm="; static const char prefix_nonce[] = "nonce=\""; static const char prefix_opaque[] = "opaque=\""; static const char prefix_domain[] = "domain=\""; static const char str_charset[] = "charset=UTF-8"; static const char str_userhash[] = "userhash=true"; static const char str_stale[] = "stale=true"; enum MHD_DigestAuthAlgo3 s_algo; /**< Selected algorithm */ size_t realm_len; size_t opaque_len; size_t domain_len; size_t buf_size; char *buf; size_t p; /* The position in the buffer */ char *hdr_name; if (0 != (((unsigned int) malgo3) & MHD_DIGEST_AUTH_ALGO3_SESSION)) { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("The 'session' algorithms are not supported.\n")); #endif /* HAVE_MESSAGES */ return MHD_NO; } #ifdef MHD_MD5_SUPPORT if (0 != (((unsigned int) malgo3) & MHD_DIGEST_BASE_ALGO_MD5)) s_algo = MHD_DIGEST_AUTH_ALGO3_MD5; else #endif /* MHD_MD5_SUPPORT */ #ifdef MHD_SHA256_SUPPORT if (0 != (((unsigned int) malgo3) & MHD_DIGEST_BASE_ALGO_SHA256)) s_algo = MHD_DIGEST_AUTH_ALGO3_SHA256; else #endif /* MHD_SHA256_SUPPORT */ #ifdef MHD_SHA512_256_SUPPORT if (0 != (((unsigned int) malgo3) & MHD_DIGEST_BASE_ALGO_SHA512_256)) s_algo = MHD_DIGEST_AUTH_ALGO3_SHA512_256; else #endif /* MHD_SHA512_256_SUPPORT */ { if (0 == (((unsigned int) malgo3) & (MHD_DIGEST_BASE_ALGO_MD5 | MHD_DIGEST_BASE_ALGO_SHA512_256 | MHD_DIGEST_BASE_ALGO_SHA512_256))) MHD_PANIC (_ ("Wrong 'malgo3' value, API violation")); else { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("No requested algorithm is supported by this MHD build.\n")); #endif /* HAVE_MESSAGES */ } return MHD_NO; } if (((unsigned int) mqop) != (((unsigned int) mqop) & MHD_DIGEST_AUTH_MULT_QOP_ANY_NON_INT)) MHD_PANIC (_ ("Wrong 'mqop' value, API violation")); if (! digest_init_one_time (da, get_base_digest_algo (s_algo))) MHD_PANIC (_ ("Wrong 'algo' value, API violation")); if (MHD_DIGEST_AUTH_MULT_QOP_NONE == mqop) { #ifdef HAVE_MESSAGES if ((0 != userhash_support) || (0 != prefer_utf8)) MHD_DLOG (connection->daemon, _ ("The 'userhash' and 'charset' ('prefer_utf8') parameters " \ "are not compatible with RFC2069 and ignored.\n")); if (0 == (((unsigned int) s_algo) & MHD_DIGEST_BASE_ALGO_MD5)) MHD_DLOG (connection->daemon, _ ("RFC2069 with SHA-256 or SHA-512/256 algorithm is " \ "non-standard extension.\n")); #endif userhash_support = 0; prefer_utf8 = 0; } if (0 == MHD_get_master (connection->daemon)->nonce_nc_size) { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("The nonce array size is zero.\n")); #endif /* HAVE_MESSAGES */ return MHD_NO; } /* Calculate required size */ buf_size = 0; /* 'Digest ' */ buf_size += MHD_STATICSTR_LEN_ (_MHD_AUTH_DIGEST_BASE) + 1; /* 1 for ' ' */ buf_size += MHD_STATICSTR_LEN_ (prefix_realm) + 3; /* 3 for '", ' */ /* 'realm="xxxx", ' */ realm_len = strlen (realm); if (_MHD_AUTH_DIGEST_MAX_PARAM_SIZE < realm_len) { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("The 'realm' is too large.\n")); #endif /* HAVE_MESSAGES */ return MHD_NO; } if ((NULL != memchr (realm, '\r', realm_len)) || (NULL != memchr (realm, '\n', realm_len))) return MHD_NO; buf_size += realm_len * 2; /* Quoting may double the size */ /* 'qop="xxxx", ' */ if (MHD_DIGEST_AUTH_MULT_QOP_NONE != mqop) { buf_size += MHD_STATICSTR_LEN_ (prefix_qop) + 3; /* 3 for '", ' */ buf_size += MHD_STATICSTR_LEN_ (MHD_TOKEN_AUTH_); } /* 'algorithm="xxxx", ' */ if (((MHD_DIGEST_AUTH_MULT_QOP_NONE) != mqop) || (0 == (((unsigned int) s_algo) & MHD_DIGEST_BASE_ALGO_MD5))) { buf_size += MHD_STATICSTR_LEN_ (prefix_algo) + 2; /* 2 for ', ' */ #ifdef MHD_MD5_SUPPORT if (MHD_DIGEST_AUTH_ALGO3_MD5 == s_algo) buf_size += MHD_STATICSTR_LEN_ (_MHD_MD5_TOKEN); else #endif /* MHD_MD5_SUPPORT */ #ifdef MHD_SHA256_SUPPORT if (MHD_DIGEST_AUTH_ALGO3_SHA256 == s_algo) buf_size += MHD_STATICSTR_LEN_ (_MHD_SHA256_TOKEN); else #endif /* MHD_SHA256_SUPPORT */ #ifdef MHD_SHA512_256_SUPPORT if (MHD_DIGEST_AUTH_ALGO3_SHA512_256 == s_algo) buf_size += MHD_STATICSTR_LEN_ (_MHD_SHA512_256_TOKEN); else #endif /* MHD_SHA512_256_SUPPORT */ mhd_assert (0); } /* 'nonce="xxxx", ' */ buf_size += MHD_STATICSTR_LEN_ (prefix_nonce) + 3; /* 3 for '", ' */ buf_size += NONCE_STD_LEN (digest_get_size (da)); /* Escaping not needed */ /* 'opaque="xxxx", ' */ if (NULL != opaque) { buf_size += MHD_STATICSTR_LEN_ (prefix_opaque) + 3; /* 3 for '", ' */ opaque_len = strlen (opaque); if ((NULL != memchr (opaque, '\r', opaque_len)) || (NULL != memchr (opaque, '\n', opaque_len))) return MHD_NO; buf_size += opaque_len * 2; /* Quoting may double the size */ } else opaque_len = 0; /* 'domain="xxxx", ' */ if (NULL != domain) { buf_size += MHD_STATICSTR_LEN_ (prefix_domain) + 3; /* 3 for '", ' */ domain_len = strlen (domain); if ((NULL != memchr (domain, '\r', domain_len)) || (NULL != memchr (domain, '\n', domain_len))) return MHD_NO; buf_size += domain_len * 2; /* Quoting may double the size */ } else domain_len = 0; /* 'charset=UTF-8' */ if (MHD_NO != prefer_utf8) buf_size += MHD_STATICSTR_LEN_ (str_charset) + 2; /* 2 for ', ' */ /* 'userhash=true' */ if (MHD_NO != userhash_support) buf_size += MHD_STATICSTR_LEN_ (str_userhash) + 2; /* 2 for ', ' */ /* 'stale=true' */ if (MHD_NO != signal_stale) buf_size += MHD_STATICSTR_LEN_ (str_stale) + 2; /* 2 for ', ' */ /* The calculated length is for string ended with ", ". One character will * be used for zero-termination, the last one will not be used. */ /* Allocate the buffer */ buf = malloc (buf_size); if (NULL == buf) return MHD_NO; *buf_ptr = buf; /* Build the challenge string */ p = 0; /* 'Digest: ' */ memcpy (buf + p, _MHD_AUTH_DIGEST_BASE, MHD_STATICSTR_LEN_ (_MHD_AUTH_DIGEST_BASE)); p += MHD_STATICSTR_LEN_ (_MHD_AUTH_DIGEST_BASE); buf[p++] = ' '; /* 'realm="xxxx", ' */ memcpy (buf + p, prefix_realm, MHD_STATICSTR_LEN_ (prefix_realm)); p += MHD_STATICSTR_LEN_ (prefix_realm); mhd_assert ((buf_size - p) >= (realm_len * 2)); if (1) { size_t quoted_size; quoted_size = MHD_str_quote (realm, realm_len, buf + p, buf_size - p); if (_MHD_AUTH_DIGEST_MAX_PARAM_SIZE < quoted_size) { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("The 'realm' is too large after 'quoting'.\n")); #endif /* HAVE_MESSAGES */ return MHD_NO; } p += quoted_size; } buf[p++] = '\"'; buf[p++] = ','; buf[p++] = ' '; /* 'qop="xxxx", ' */ if (MHD_DIGEST_AUTH_MULT_QOP_NONE != mqop) { memcpy (buf + p, prefix_qop, MHD_STATICSTR_LEN_ (prefix_qop)); p += MHD_STATICSTR_LEN_ (prefix_qop); memcpy (buf + p, MHD_TOKEN_AUTH_, MHD_STATICSTR_LEN_ (MHD_TOKEN_AUTH_)); p += MHD_STATICSTR_LEN_ (MHD_TOKEN_AUTH_); buf[p++] = '\"'; buf[p++] = ','; buf[p++] = ' '; } /* 'algorithm="xxxx", ' */ if (((MHD_DIGEST_AUTH_MULT_QOP_NONE) != mqop) || (0 == (((unsigned int) s_algo) & MHD_DIGEST_BASE_ALGO_MD5))) { memcpy (buf + p, prefix_algo, MHD_STATICSTR_LEN_ (prefix_algo)); p += MHD_STATICSTR_LEN_ (prefix_algo); #ifdef MHD_MD5_SUPPORT if (MHD_DIGEST_AUTH_ALGO3_MD5 == s_algo) { memcpy (buf + p, _MHD_MD5_TOKEN, MHD_STATICSTR_LEN_ (_MHD_MD5_TOKEN)); p += MHD_STATICSTR_LEN_ (_MHD_MD5_TOKEN); } else #endif /* MHD_MD5_SUPPORT */ #ifdef MHD_SHA256_SUPPORT if (MHD_DIGEST_AUTH_ALGO3_SHA256 == s_algo) { memcpy (buf + p, _MHD_SHA256_TOKEN, MHD_STATICSTR_LEN_ (_MHD_SHA256_TOKEN)); p += MHD_STATICSTR_LEN_ (_MHD_SHA256_TOKEN); } else #endif /* MHD_SHA256_SUPPORT */ #ifdef MHD_SHA512_256_SUPPORT if (MHD_DIGEST_AUTH_ALGO3_SHA512_256 == s_algo) { memcpy (buf + p, _MHD_SHA512_256_TOKEN, MHD_STATICSTR_LEN_ (_MHD_SHA512_256_TOKEN)); p += MHD_STATICSTR_LEN_ (_MHD_SHA512_256_TOKEN); } else #endif /* MHD_SHA512_256_SUPPORT */ mhd_assert (0); buf[p++] = ','; buf[p++] = ' '; } /* 'nonce="xxxx", ' */ memcpy (buf + p, prefix_nonce, MHD_STATICSTR_LEN_ (prefix_nonce)); p += MHD_STATICSTR_LEN_ (prefix_nonce); mhd_assert ((buf_size - p) >= (NONCE_STD_LEN (digest_get_size (da)))); if (! calculate_add_nonce_with_retry (connection, realm, da, buf + p)) { #ifdef MHD_DIGEST_HAS_EXT_ERROR if (digest_ext_error (da)) { #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("TLS library reported hash calculation error, nonce could " "not be generated.\n")); #endif /* HAVE_MESSAGES */ return MHD_NO; } #endif /* MHD_DIGEST_HAS_EXT_ERROR */ #ifdef HAVE_MESSAGES MHD_DLOG (connection->daemon, _ ("Could not register nonce. Client's requests with this " "nonce will be always 'stale'. Probably clients' requests " "are too intensive.\n")); #endif /* HAVE_MESSAGES */ (void) 0; /* Mute compiler warning for builds without messages */ } p += NONCE_STD_LEN (digest_get_size (da)); buf[p++] = '\"'; buf[p++] = ','; buf[p++] = ' '; /* 'opaque="xxxx", ' */ if (NULL != opaque) { memcpy (buf + p, prefix_opaque, MHD_STATICSTR_LEN_ (prefix_opaque)); p += MHD_STATICSTR_LEN_ (prefix_opaque); mhd_assert ((buf_size - p) >= (opaque_len * 2)); p += MHD_str_quote (opaque, opaque_len, buf + p, buf_size - p); buf[p++] = '\"'; buf[p++] = ','; buf[p++] = ' '; } /* 'domain="xxxx", ' */ if (NULL != domain) { memcpy (buf + p, prefix_domain, MHD_STATICSTR_LEN_ (prefix_domain)); p += MHD_STATICSTR_LEN_ (prefix_domain); mhd_assert ((buf_size - p) >= (domain_len * 2)); p += MHD_str_quote (domain, domain_len, buf + p, buf_size - p); buf[p++] = '\"'; buf[p++] = ','; buf[p++] = ' '; } /* 'charset=UTF-8' */ if (MHD_NO != prefer_utf8) { memcpy (buf + p, str_charset, MHD_STATICSTR_LEN_ (str_charset)); p += MHD_STATICSTR_LEN_ (str_charset); buf[p++] = ','; buf[p++] = ' '; } /* 'userhash=true' */ if (MHD_NO != userhash_support) { memcpy (buf + p, str_userhash, MHD_STATICSTR_LEN_ (str_userhash)); p += MHD_STATICSTR_LEN_ (str_userhash); buf[p++] = ','; buf[p++] = ' '; } /* 'stale=true' */ if (MHD_NO != signal_stale) { memcpy (buf + p, str_stale, MHD_STATICSTR_LEN_ (str_stale)); p += MHD_STATICSTR_LEN_ (str_stale); buf[p++] = ','; buf[p++] = ' '; } mhd_assert (buf_size >= p); /* The built string ends with ", ". Replace comma with zero-termination. */ --p; buf[--p] = 0; hdr_name = malloc (MHD_STATICSTR_LEN_ (MHD_HTTP_HEADER_WWW_AUTHENTICATE) + 1); if (NULL != hdr_name) { memcpy (hdr_name, MHD_HTTP_HEADER_WWW_AUTHENTICATE, MHD_STATICSTR_LEN_ (MHD_HTTP_HEADER_WWW_AUTHENTICATE) + 1); if (MHD_add_response_entry_no_alloc_ (response, MHD_HEADER_KIND, hdr_name, MHD_STATICSTR_LEN_ ( \ MHD_HTTP_HEADER_WWW_AUTHENTICATE), buf, p)) { *buf_ptr = NULL; /* The buffer will be free()ed when the response is destroyed */ return MHD_queue_response (connection, MHD_HTTP_UNAUTHORIZED, response); } #ifdef HAVE_MESSAGES else { MHD_DLOG (connection->daemon, _ ("Failed to add Digest auth header.\n")); } #endif /* HAVE_MESSAGES */ free (hdr_name); } return MHD_NO; } /** * Queues a response to request authentication from the client * * This function modifies provided @a response. The @a response must not be * reused and should be destroyed (by #MHD_destroy_response()) after call of * this function. * * If @a mqop allows both RFC 2069 (MHD_DIGEST_AUTH_QOP_NONE) and QOP with * value, then response is formed like if MHD_DIGEST_AUTH_QOP_NONE bit was * not set, because such response should be backward-compatible with RFC 2069. * * If @a mqop allows only MHD_DIGEST_AUTH_MULT_QOP_NONE, then the response is * formed in strict accordance with RFC 2069 (no 'qop', no 'userhash', no * 'charset'). For better compatibility with clients, it is recommended (but * not required) to set @a domain to NULL in this mode. * * @param connection the MHD connection structure * @param realm the realm presented to the client * @param opaque the string for opaque value, can be NULL, but NULL is * not recommended for better compatibility with clients; * the recommended format is hex or Base64 encoded string * @param domain the optional space-separated list of URIs for which the * same authorisation could be used, URIs can be in form * "path-absolute" (the path for the same host with initial slash) * or in form "absolute-URI" (the full path with protocol), in * any case client may assume that URI is in the same "protection * space" if it starts with any of values specified here; * could be NULL (clients typically assume that the same * credentials could be used for any URI on the same host) * @param response the reply to send; should contain the "access denied" * body; note that this function sets the "WWW Authenticate" * header and that the caller should not do this; * the NULL is tolerated * @param signal_stale set to #MHD_YES if the nonce is stale to add 'stale=true' * to the authentication header, this instructs the client * to retry immediately with the new nonce and the same * credentials, without asking user for the new password * @param mqop the QOP to use * @param malgo3 digest algorithm to use, MHD selects; if several algorithms * are allowed then MD5 is preferred (currently, may be changed * in next versions) * @param userhash_support if set to non-zero value (#MHD_YES) then support of * userhash is indicated, the client may provide * hash("username:realm") instead of username in * clear text; * note that clients are allowed to provide the username * in cleartext even if this parameter set to non-zero; * when userhash is used, application must be ready to * identify users by provided userhash value instead of * username; see #MHD_digest_auth_calc_userhash() and * #MHD_digest_auth_calc_userhash_hex() * @param prefer_utf8 if not set to #MHD_NO, parameter 'charset=UTF-8' is * added, indicating for the client that UTF-8 encoding * is preferred * @return #MHD_YES on success, #MHD_NO otherwise * @note Available since #MHD_VERSION 0x00097526 * @ingroup authentication */ _MHD_EXTERN enum MHD_Result MHD_queue_auth_required_response3 (struct MHD_Connection *connection, const char *realm, const char *opaque, const char *domain, struct MHD_Response *response, int signal_stale, enum MHD_DigestAuthMultiQOP mqop, enum MHD_DigestAuthMultiAlgo3 malgo3, int userhash_support, int prefer_utf8) { struct DigestAlgorithm da; char *buf_ptr; enum MHD_Result ret; buf_ptr = NULL; digest_setup_zero (&da); ret = queue_auth_required_response3_inner (connection, realm, opaque, domain, response, signal_stale, mqop, malgo3, userhash_support, prefer_utf8, &buf_ptr, &da); digest_deinit (&da); if (NULL != buf_ptr) free (buf_ptr); return ret; } /** * Queues a response to request authentication from the client * * @param connection The MHD connection structure * @param realm the realm presented to the client * @param opaque string to user for opaque value * @param response reply to send; should contain the "access denied" * body; note that this function will set the "WWW Authenticate" * header and that the caller should not do this; the NULL is tolerated * @param signal_stale #MHD_YES if the nonce is stale to add * 'stale=true' to the authentication header * @param algo digest algorithm to use * @return #MHD_YES on success, #MHD_NO otherwise * @note Available since #MHD_VERSION 0x00096200 * @ingroup authentication */ _MHD_EXTERN enum MHD_Result 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) { enum MHD_DigestAuthMultiAlgo3 algo3; if (MHD_DIGEST_ALG_MD5 == algo) algo3 = MHD_DIGEST_AUTH_MULT_ALGO3_MD5; else if (MHD_DIGEST_ALG_SHA256 == algo) algo3 = MHD_DIGEST_AUTH_MULT_ALGO3_SHA256; else if (MHD_DIGEST_ALG_AUTO == algo) algo3 = MHD_DIGEST_AUTH_MULT_ALGO3_ANY_NON_SESSION; else MHD_PANIC (_ ("Wrong algo value.\n")); /* API violation! */ return MHD_queue_auth_required_response3 (connection, realm, opaque, NULL, response, signal_stale, MHD_DIGEST_AUTH_MULT_QOP_AUTH, algo3, 0, 0); } /** * Queues a response to request authentication from the client. * For now uses MD5 (for backwards-compatibility). Still, if you * need to be sure, use #MHD_queue_auth_fail_response2(). * * @param connection The MHD connection structure * @param realm the realm presented to the client * @param opaque string to user for opaque value * @param response reply to send; should contain the "access denied" * body; note that this function will set the "WWW Authenticate" * header and that the caller should not do this; the NULL is tolerated * @param signal_stale #MHD_YES if the nonce is stale to add * 'stale=true' to the authentication header * @return #MHD_YES on success, #MHD_NO otherwise * @ingroup authentication * @deprecated use MHD_queue_auth_fail_response2() */ _MHD_EXTERN enum MHD_Result MHD_queue_auth_fail_response (struct MHD_Connection *connection, const char *realm, const char *opaque, struct MHD_Response *response, int signal_stale) { return MHD_queue_auth_fail_response2 (connection, realm, opaque, response, signal_stale, MHD_DIGEST_ALG_MD5); } /* end of digestauth.c */