| /* |
| 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 */ |