From 6a1a29c795ee21f376f1f2c6580176b9fc34ef12 Mon Sep 17 00:00:00 2001 From: "Evgeny Grin (Karlson2k)" Date: Sun, 22 Dec 2024 19:09:53 +0100 Subject: [PATCH] Digest Auth: part one --- configure.ac | 20 +- src/include/microhttpd2.h | 326 ++++++++++++++-- src/include/microhttpd2_main.h.in | 316 ++++++++++++++-- src/include/microhttpd2_preamble.h.in | 10 + src/mhd2/Makefile.am | 8 + src/mhd2/mhd_auth_digest_hdr.h | 66 ++++ src/mhd2/mhd_digest_auth_data.h | 53 +++ src/mhd2/mhd_response.h | 10 + src/mhd2/response_auth_digest.c | 521 ++++++++++++++++++++++++++ src/mhd2/response_auth_digest.h | 42 +++ src/mhd2/response_destroy.c | 7 + src/mhd2/response_from.c | 3 + src/microhttpd/Makefile.am | 20 +- src/testcurl/Makefile.am | 8 +- 14 files changed, 1316 insertions(+), 94 deletions(-) create mode 100644 src/mhd2/mhd_auth_digest_hdr.h create mode 100644 src/mhd2/mhd_digest_auth_data.h create mode 100644 src/mhd2/response_auth_digest.c create mode 100644 src/mhd2/response_auth_digest.h diff --git a/configure.ac b/configure.ac index 8b292d3c..e4adfa7e 100644 --- a/configure.ac +++ b/configure.ac @@ -5635,18 +5635,18 @@ AS_CASE([${enable_md5}], ) AS_IF([test "x${enable_md5}" = "xbuiltin" || test "x${enable_md5}" = "xtlslib" ], [ - AC_DEFINE([[MHD_MD5_SUPPORT]],[[1]], + AC_DEFINE([[MHD_SUPPORT_MD5]],[[1]], [Define to 1 if libmicrohttpd is compiled with MD5 hashing support.]) ] ) AS_IF([test "x${enable_md5}" = "xtlslib" ], [ - AC_DEFINE([[MHD_MD5_TLSLIB]],[[1]], + AC_DEFINE([[MHD_MD5_EXTR]],[[1]], [Define to 1 if libmicrohttpd is compiled with MD5 hashing by TLS library.]) ] ) -AM_CONDITIONAL([ENABLE_MD5], [[test "x${enable_md5}" = "xbuiltin" || test "x${enable_md5}" = "xtlslib" ]]) -AM_CONDITIONAL([ENABLE_MD5_EXT], [[test "x${enable_md5}" = "xtlslib" ]]) +AM_CONDITIONAL([MHD_SUPPORT_MD5], [[test "x${enable_md5}" = "xbuiltin" || test "x${enable_md5}" = "xtlslib" ]]) +AM_CONDITIONAL([MHD_MD5_EXTR], [[test "x${enable_md5}" = "xtlslib" ]]) AC_MSG_RESULT([[${enable_md5_MSG}]]) # optional: SHA-256 support for Digest Auth. Enabled by default. @@ -5757,18 +5757,18 @@ AS_CASE([${enable_sha256}], ) AS_IF([test "x${enable_sha256}" = "xbuiltin" || test "x${enable_sha256}" = "xtlslib" ], [ - AC_DEFINE([[MHD_SHA256_SUPPORT]],[[1]], + AC_DEFINE([[MHD_SUPPORT_SHA256]],[[1]], [Define to 1 if libmicrohttpd is compiled with SHA-256 hashing support.]) ] ) AS_IF([test "x${enable_sha256}" = "xtlslib" ], [ - AC_DEFINE([[MHD_SHA256_TLSLIB]],[[1]], + AC_DEFINE([[MHD_SHA256_EXTR]],[[1]], [Define to 1 if libmicrohttpd is compiled with SHA-256 hashing by TLS library.]) ] ) -AM_CONDITIONAL([ENABLE_SHA256], [[test "x${enable_sha256}" = "xbuiltin" || test "x${enable_sha256}" = "xtlslib" ]]) -AM_CONDITIONAL([ENABLE_SHA256_EXT], [[test "x${enable_sha256}" = "xtlslib" ]]) +AM_CONDITIONAL([MHD_SUPPORT_SHA256], [[test "x${enable_sha256}" = "xbuiltin" || test "x${enable_sha256}" = "xtlslib" ]]) +AM_CONDITIONAL([MHD_SHA256_EXTR], [[test "x${enable_sha256}" = "xtlslib" ]]) AC_MSG_RESULT([[${enable_sha256_MSG}]]) # optional: SHA-512/256 support for Digest Auth. Enabled by default. @@ -5797,11 +5797,11 @@ AS_CASE([${enable_sha512_256}], ) AS_VAR_IF([[enable_sha512_256]],[["yes"]], [ - AC_DEFINE([[MHD_SHA512_256_SUPPORT]],[[1]], + AC_DEFINE([[MHD_SUPPORT_SHA512_256]],[[1]], [Define to 1 if libmicrohttpd is compiled with SHA-512/256 hashing support.]) ] ) -AM_CONDITIONAL([ENABLE_SHA512_256], [[test "x${enable_sha512_256}" = "xyes"]]) +AM_CONDITIONAL([MHD_SUPPORT_SHA512_256], [[test "x${enable_sha512_256}" = "xyes"]]) AC_MSG_RESULT([[${enable_sha512_256_MSG}]]) AS_IF([test "x$enable_digest_auth" != "xno"], diff --git a/src/include/microhttpd2.h b/src/include/microhttpd2.h index 18805011..56ab15f3 100644 --- a/src/include/microhttpd2.h +++ b/src/include/microhttpd2.h @@ -1621,6 +1621,16 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode * cannot be provided due to some error or other reasons. */ MHD_SC_INFO_GET_TYPE_UNAVAILALBE = 60204 + , + /** + * The type of the Digest Auth algorithm is unknown or not supported. + */ + MHD_SC_AUTH_DIGEST_ALGO_NOT_SUPPORTED = 60240 + , + /** + * The the Digest Auth QOP value is unknown or not supported. + */ + MHD_SC_AUTH_DIGEST_QOP_NOT_SUPPORTED = 60241 }; /** @@ -7907,20 +7917,32 @@ MHD_FN_PAR_CSTR_ (4); /** - * Create an action to request authentication from the client + * Add Digest Authentication "challenge" to the response. * - * 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. + * The response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code. + * + * If @a mqop allows both RFC 2069 (#MHD_DIGEST_AUTH_QOP_NONE) and other QOP + * values, then the "challenge" is formed like if MHD_DIGEST_AUTH_QOP_NONE bit + * was not set, because such "challenge" 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. * - * At most one action can be created for any request. + * New nonces are generated each time when the resulting response is used. * - * @param request the request + * See RFC 7616, section 3.3 for details. + * + * @param response the response to update; should contain the "access denied" + * body; + * note: this function sets the "WWW Authenticate" header and + * the caller should not set this header; + * the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status + * code; + * the NULL is tolerated (the result is + * #MHD_SC_RESP_POINTER_NULL) * @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; @@ -7935,24 +7957,17 @@ MHD_FN_PAR_CSTR_ (4); * credentials could be used for any URI on the same host); * this list provides information for the client only and does * not actually restrict anything on the server side - * @param response the reply to send; should contain the "access denied" - * body; - * note: this function sets the "WWW Authenticate" header and - * the caller should not set this header; - * the response must have #MHD_HTTP_STATUS_FORBIDDEN status - * code, must not have #MHD_R_O_REUSABLE enabled; - * the NULL is tolerated (the result is NULL) - * @param signal_stale if set to #MHD_YES then indication of stale nonce used in - * the client's request is signalled by adding '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 indicate_stale if set to #MHD_YES then indication of stale nonce used + * in the client's request is indicated by adding + * '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 algo digest algorithm to use; 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, allowing client to provide + * then one challenge for each allowed algorithm is added + * @param userhash_support if set to #MHD_YES then support of userhash is + * indicated, allowing client to provide * hash("username:realm") instead of the username in * clear text; * note that clients are allowed to provide the username @@ -7964,24 +7979,265 @@ MHD_FN_PAR_CSTR_ (4); * @param prefer_utf8 if not set to #MHD_NO, parameter 'charset=UTF-8' is * added, indicating for the client that UTF-8 encoding for * the username is preferred - * @return pointer to the action on success, - * NULL on failure + * @return #MHD_SC_OK if succeed, + * #MHD_SC_TOO_LATE if the response has been already "frozen" (used to + * create an action), + * #MHD_SC_RESP_HEADERS_CONFLICT if Digest Authentication "challenge" + * has been added already, + * #MHD_SC_RESP_POINTER_NULL if @a response is NULL, + * #MHD_SC_RESP_HTTP_CODE_NOT_SUITABLE is response status code is wrong, + * #MHD_SC_RESP_HEADER_VALUE_INVALID if @a realm, @a opaque or @a domain + * have wrong characters or zero length (for @a realm), + * #MHD_SC_RESPONSE_HEADER_MEM_ALLOC_FAILED if memory allocation failed, + * or other error code if failed * @ingroup authentication */ -MHD_EXTERN_ const struct MHD_Action * -MHD_action_digest_auth_required_response ( - struct MHD_Request *request, - const char *realm, - const char *opaque, - const char *domain, - struct MHD_Response *response, - enum MHD_Bool signal_stale, +MHD_EXTERN_ enum MHD_StatusCode +MHD_response_add_auth_digest_challenge ( + struct MHD_Response *MHD_RESTRICT response, + const char *MHD_RESTRICT realm, + const char *MHD_RESTRICT opaque, + const char *MHD_RESTRICT domain, + enum MHD_Bool indicate_stale, enum MHD_DigestAuthMultiQOP mqop, - enum MHD_DigestAuthMultiAlgo algo, + enum MHD_DigestAuthMultiAlgo malgo, enum MHD_Bool userhash_support, enum MHD_Bool prefer_utf8) -MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) -MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_NONNULL_ (5); +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4); + + +#ifndef MHD_NO_STATIC_INLINE + +/** + * Create action to reply with Digest Authentication "challenge". + * + * The @a response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code. + * + * See RFC 7616, section 3.3 for details. + * + * @param request the request to create the action for + * @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); + * this list provides information for the client only and does + * not actually restrict anything on the server side + * @param indicate_stale if set to #MHD_YES then indication of stale nonce used + * in the client's request is indicated by adding + * '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 algo digest algorithm to use; if several algorithms are allowed + * then one challenge for each allowed algorithm is added + * @param userhash_support if set to #MHD_YES then support of userhash is + * indicated, allowing client to provide + * hash("username:realm") instead of the 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 for + * the username is preferred + * @param response the response to update; should contain the "access denied" + * body; + * note: this function sets the "WWW Authenticate" header and + * the caller should not set this header; + * the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status + * code; + * the NULL is tolerated (the result is + * #MHD_SC_RESP_POINTER_NULL) + * @param abort_if_failed if set to #MHD_NO the response will be used even if + * failed to add Basic Authentication "challenge", + * if not set to #MHD_NO the request will be aborted + * if the "challenge" could not be added. + * @return pointer to the action, the action must be consumed + * otherwise response object may leak; + * NULL if failed or if any action has been already created for + * the @a request; + * when failed the response object is consumed and need not + * to be "destroyed" + * @ingroup authentication + */ +MHD_STATIC_INLINE_ +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +const struct MHD_Action * +MHD_action_digest_auth_challenge (struct MHD_Request *MHD_RESTRICT request, + const char *MHD_RESTRICT realm, + const char *MHD_RESTRICT opaque, + const char *MHD_RESTRICT domain, + enum MHD_Bool indicate_stale, + enum MHD_DigestAuthMultiQOP mqop, + enum MHD_DigestAuthMultiAlgo malgo, + enum MHD_Bool userhash_support, + enum MHD_Bool prefer_utf8, + struct MHD_Response *MHD_RESTRICT response, + enum MHD_Bool abort_if_failed) +{ + if ((MHD_SC_OK != + MHD_response_add_auth_digest_challenge (response, realm, opaque, domain, + indicate_stale, mqop, malgo, + userhash_support, prefer_utf8)) + && (MHD_NO != abort_if_failed)) + { + MHD_response_destroy (response); + return MHD_action_abort_request (request); + } + return MHD_action_from_response (request, response); +} + + +MHD_STATIC_INLINE_END_ + +/** + * Create action to reply with Digest Authentication "challenge". + * + * The @a response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code. + * + * If the @a response object cannot be extended with the "challenge", + * the @a response is used to reply without the "challenge". + * + * @param request the request to create the action for + * @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); + * this list provides information for the client only and does + * not actually restrict anything on the server side + * @param indicate_stale if set to #MHD_YES then indication of stale nonce used + * in the client's request is indicated by adding + * '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 algo digest algorithm to use; if several algorithms are allowed + * then one challenge for each allowed algorithm is added + * @param userhash_support if set to #MHD_YES then support of userhash is + * indicated, allowing client to provide + * hash("username:realm") instead of the 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 for + * the username is preferred + * @param response the response to update; should contain the "access denied" + * body; + * note: this function sets the "WWW Authenticate" header and + * the caller should not set this header; + * the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status + * code; + * the NULL is tolerated (the result is + * #MHD_SC_RESP_POINTER_NULL) + * @return pointer to the action, the action must be consumed + * otherwise response object may leak; + * NULL if failed or if any action has been already created for + * the @a request; + * when failed the response object is consumed and need not + * to be "destroyed" + * @ingroup authentication + */ +#define MHD_action_digest_auth_challenge_p(rq,rlm,opq,dmn,stl,mqop,malgo, \ + uh,utf,resp) \ + MHD_action_digest_auth_challenge ((rq),(rlm),(opq),(dmn),(stl),(mqop), \ + (malgo),(uh),(utf),(resp),MHD_NO) + + +/** + * Create action to reply with Digest Authentication "challenge". + * + * The @a response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code. + * + * If the @a response object cannot be extended with the "challenge", + * the @a response is aborted. + * + * @param request the request to create the action for + * @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); + * this list provides information for the client only and does + * not actually restrict anything on the server side + * @param indicate_stale if set to #MHD_YES then indication of stale nonce used + * in the client's request is indicated by adding + * '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 algo digest algorithm to use; if several algorithms are allowed + * then one challenge for each allowed algorithm is added + * @param userhash_support if set to #MHD_YES then support of userhash is + * indicated, allowing client to provide + * hash("username:realm") instead of the 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 for + * the username is preferred + * @param response the response to update; should contain the "access denied" + * body; + * note: this function sets the "WWW Authenticate" header and + * the caller should not set this header; + * the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status + * code; + * the NULL is tolerated (the result is + * #MHD_SC_RESP_POINTER_NULL) + * @return pointer to the action, the action must be consumed + * otherwise response object may leak; + * NULL if failed or if any action has been already created for + * the @a request; + * when failed the response object is consumed and need not + * to be "destroyed" + * @ingroup authentication + */ +#define MHD_action_digest_auth_challenge_a(rq,rlm,opq,dmn,stl,mqop,malgo, \ + uh,utf,resp) \ + MHD_action_digest_auth_challenge ((rq),(rlm),(opq),(dmn),(stl),(mqop), \ + (malgo),(uh),(utf),(resp),MHD_YES) + +#endif /* ! MHD_NO_STATIC_INLINE */ /** diff --git a/src/include/microhttpd2_main.h.in b/src/include/microhttpd2_main.h.in index cb3f9c3b..17246917 100644 --- a/src/include/microhttpd2_main.h.in +++ b/src/include/microhttpd2_main.h.in @@ -3325,20 +3325,32 @@ MHD_FN_PAR_CSTR_ (4); /** - * Create an action to request authentication from the client + * Add Digest Authentication "challenge" to the response. * - * 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. + * The response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code. + * + * If @a mqop allows both RFC 2069 (#MHD_DIGEST_AUTH_QOP_NONE) and other QOP + * values, then the "challenge" is formed like if MHD_DIGEST_AUTH_QOP_NONE bit + * was not set, because such "challenge" 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. * - * At most one action can be created for any request. + * New nonces are generated each time when the resulting response is used. * - * @param request the request + * See RFC 7616, section 3.3 for details. + * + * @param response the response to update; should contain the "access denied" + * body; + * note: this function sets the "WWW Authenticate" header and + * the caller should not set this header; + * the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status + * code; + * the NULL is tolerated (the result is + * #MHD_SC_RESP_POINTER_NULL) * @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; @@ -3353,24 +3365,17 @@ MHD_FN_PAR_CSTR_ (4); * credentials could be used for any URI on the same host); * this list provides information for the client only and does * not actually restrict anything on the server side - * @param response the reply to send; should contain the "access denied" - * body; - * note: this function sets the "WWW Authenticate" header and - * the caller should not set this header; - * the response must have #MHD_HTTP_STATUS_FORBIDDEN status - * code, must not have #MHD_R_O_REUSABLE enabled; - * the NULL is tolerated (the result is NULL) - * @param signal_stale if set to #MHD_YES then indication of stale nonce used in - * the client's request is signalled by adding '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 indicate_stale if set to #MHD_YES then indication of stale nonce used + * in the client's request is indicated by adding + * '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 algo digest algorithm to use; 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, allowing client to provide + * then one challenge for each allowed algorithm is added + * @param userhash_support if set to #MHD_YES then support of userhash is + * indicated, allowing client to provide * hash("username:realm") instead of the username in * clear text; * note that clients are allowed to provide the username @@ -3382,24 +3387,265 @@ MHD_FN_PAR_CSTR_ (4); * @param prefer_utf8 if not set to #MHD_NO, parameter 'charset=UTF-8' is * added, indicating for the client that UTF-8 encoding for * the username is preferred - * @return pointer to the action on success, - * NULL on failure + * @return #MHD_SC_OK if succeed, + * #MHD_SC_TOO_LATE if the response has been already "frozen" (used to + * create an action), + * #MHD_SC_RESP_HEADERS_CONFLICT if Digest Authentication "challenge" + * has been added already, + * #MHD_SC_RESP_POINTER_NULL if @a response is NULL, + * #MHD_SC_RESP_HTTP_CODE_NOT_SUITABLE is response status code is wrong, + * #MHD_SC_RESP_HEADER_VALUE_INVALID if @a realm, @a opaque or @a domain + * have wrong characters or zero length (for @a realm), + * #MHD_SC_RESPONSE_HEADER_MEM_ALLOC_FAILED if memory allocation failed, + * or other error code if failed * @ingroup authentication */ -MHD_EXTERN_ const struct MHD_Action * -MHD_action_digest_auth_required_response ( - struct MHD_Request *request, - const char *realm, - const char *opaque, - const char *domain, - struct MHD_Response *response, - enum MHD_Bool signal_stale, +MHD_EXTERN_ enum MHD_StatusCode +MHD_response_add_auth_digest_challenge ( + struct MHD_Response *MHD_RESTRICT response, + const char *MHD_RESTRICT realm, + const char *MHD_RESTRICT opaque, + const char *MHD_RESTRICT domain, + enum MHD_Bool indicate_stale, enum MHD_DigestAuthMultiQOP mqop, - enum MHD_DigestAuthMultiAlgo algo, + enum MHD_DigestAuthMultiAlgo malgo, enum MHD_Bool userhash_support, enum MHD_Bool prefer_utf8) -MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) -MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_NONNULL_ (5); +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4); + + +#ifndef MHD_NO_STATIC_INLINE + +/** + * Create action to reply with Digest Authentication "challenge". + * + * The @a response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code. + * + * See RFC 7616, section 3.3 for details. + * + * @param request the request to create the action for + * @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); + * this list provides information for the client only and does + * not actually restrict anything on the server side + * @param indicate_stale if set to #MHD_YES then indication of stale nonce used + * in the client's request is indicated by adding + * '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 algo digest algorithm to use; if several algorithms are allowed + * then one challenge for each allowed algorithm is added + * @param userhash_support if set to #MHD_YES then support of userhash is + * indicated, allowing client to provide + * hash("username:realm") instead of the 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 for + * the username is preferred + * @param response the response to update; should contain the "access denied" + * body; + * note: this function sets the "WWW Authenticate" header and + * the caller should not set this header; + * the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status + * code; + * the NULL is tolerated (the result is + * #MHD_SC_RESP_POINTER_NULL) + * @param abort_if_failed if set to #MHD_NO the response will be used even if + * failed to add Basic Authentication "challenge", + * if not set to #MHD_NO the request will be aborted + * if the "challenge" could not be added. + * @return pointer to the action, the action must be consumed + * otherwise response object may leak; + * NULL if failed or if any action has been already created for + * the @a request; + * when failed the response object is consumed and need not + * to be "destroyed" + * @ingroup authentication + */ +MHD_STATIC_INLINE_ +MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +const struct MHD_Action * +MHD_action_digest_auth_challenge (struct MHD_Request *MHD_RESTRICT request, + const char *MHD_RESTRICT realm, + const char *MHD_RESTRICT opaque, + const char *MHD_RESTRICT domain, + enum MHD_Bool indicate_stale, + enum MHD_DigestAuthMultiQOP mqop, + enum MHD_DigestAuthMultiAlgo malgo, + enum MHD_Bool userhash_support, + enum MHD_Bool prefer_utf8, + struct MHD_Response *MHD_RESTRICT response, + enum MHD_Bool abort_if_failed) +{ + if ((MHD_SC_OK != + MHD_response_add_auth_digest_challenge (response, realm, opaque, domain, + indicate_stale, mqop, malgo, + userhash_support, prefer_utf8)) + && (MHD_NO != abort_if_failed)) + { + MHD_response_destroy (response); + return MHD_action_abort_request (request); + } + return MHD_action_from_response (request, response); +} + + +MHD_STATIC_INLINE_END_ + +/** + * Create action to reply with Digest Authentication "challenge". + * + * The @a response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code. + * + * If the @a response object cannot be extended with the "challenge", + * the @a response is used to reply without the "challenge". + * + * @param request the request to create the action for + * @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); + * this list provides information for the client only and does + * not actually restrict anything on the server side + * @param indicate_stale if set to #MHD_YES then indication of stale nonce used + * in the client's request is indicated by adding + * '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 algo digest algorithm to use; if several algorithms are allowed + * then one challenge for each allowed algorithm is added + * @param userhash_support if set to #MHD_YES then support of userhash is + * indicated, allowing client to provide + * hash("username:realm") instead of the 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 for + * the username is preferred + * @param response the response to update; should contain the "access denied" + * body; + * note: this function sets the "WWW Authenticate" header and + * the caller should not set this header; + * the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status + * code; + * the NULL is tolerated (the result is + * #MHD_SC_RESP_POINTER_NULL) + * @return pointer to the action, the action must be consumed + * otherwise response object may leak; + * NULL if failed or if any action has been already created for + * the @a request; + * when failed the response object is consumed and need not + * to be "destroyed" + * @ingroup authentication + */ +#define MHD_action_digest_auth_challenge_p(rq,rlm,opq,dmn,stl,mqop,malgo, \ + uh,utf,resp) \ + MHD_action_digest_auth_challenge ((rq),(rlm),(opq),(dmn),(stl),(mqop), \ + (malgo),(uh),(utf),(resp),MHD_NO) + + +/** + * Create action to reply with Digest Authentication "challenge". + * + * The @a response must have #MHD_HTTP_STATUS_UNAUTHORIZED status code. + * + * If the @a response object cannot be extended with the "challenge", + * the @a response is aborted. + * + * @param request the request to create the action for + * @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); + * this list provides information for the client only and does + * not actually restrict anything on the server side + * @param indicate_stale if set to #MHD_YES then indication of stale nonce used + * in the client's request is indicated by adding + * '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 algo digest algorithm to use; if several algorithms are allowed + * then one challenge for each allowed algorithm is added + * @param userhash_support if set to #MHD_YES then support of userhash is + * indicated, allowing client to provide + * hash("username:realm") instead of the 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 for + * the username is preferred + * @param response the response to update; should contain the "access denied" + * body; + * note: this function sets the "WWW Authenticate" header and + * the caller should not set this header; + * the response must have #MHD_HTTP_STATUS_UNAUTHORIZED status + * code; + * the NULL is tolerated (the result is + * #MHD_SC_RESP_POINTER_NULL) + * @return pointer to the action, the action must be consumed + * otherwise response object may leak; + * NULL if failed or if any action has been already created for + * the @a request; + * when failed the response object is consumed and need not + * to be "destroyed" + * @ingroup authentication + */ +#define MHD_action_digest_auth_challenge_a(rq,rlm,opq,dmn,stl,mqop,malgo, \ + uh,utf,resp) \ + MHD_action_digest_auth_challenge ((rq),(rlm),(opq),(dmn),(stl),(mqop), \ + (malgo),(uh),(utf),(resp),MHD_YES) + +#endif /* ! MHD_NO_STATIC_INLINE */ /** diff --git a/src/include/microhttpd2_preamble.h.in b/src/include/microhttpd2_preamble.h.in index a943bf3d..4f91564f 100644 --- a/src/include/microhttpd2_preamble.h.in +++ b/src/include/microhttpd2_preamble.h.in @@ -1621,6 +1621,16 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode * cannot be provided due to some error or other reasons. */ MHD_SC_INFO_GET_TYPE_UNAVAILALBE = 60204 + , + /** + * The type of the Digest Auth algorithm is unknown or not supported. + */ + MHD_SC_AUTH_DIGEST_ALGO_NOT_SUPPORTED = 60240 + , + /** + * The the Digest Auth QOP value is unknown or not supported. + */ + MHD_SC_AUTH_DIGEST_QOP_NOT_SUPPORTED = 60241 }; /** diff --git a/src/mhd2/Makefile.am b/src/mhd2/Makefile.am index 373d15df..2ece4a12 100644 --- a/src/mhd2/Makefile.am +++ b/src/mhd2/Makefile.am @@ -110,6 +110,10 @@ auth_basic_OPTSOURCES = \ auth_basic.c auth_basic.h \ response_auth_basic.c +auth_digest_OPTSOURCES = \ + mhd_digest_auth_data.h mhd_auth_digest_hdr.h \ + response_auth_digest.c response_auth_digest.h + upgrade_OPTSOURCES = \ mhd_upgrade.h \ upgrade_prep.c upgrade_prep.h \ @@ -146,6 +150,10 @@ if MHD_SUPPORT_AUTH_BASIC libmicrohttpd2_la_SOURCES += $(auth_basic_OPTSOURCES) endif +if MHD_SUPPORT_AUTH_DIGEST + libmicrohttpd2_la_SOURCES += $(auth_digest_OPTSOURCES) +endif + if MHD_UPGRADE_SUPPORT libmicrohttpd2_la_SOURCES += $(upgrade_OPTSOURCES) endif diff --git a/src/mhd2/mhd_auth_digest_hdr.h b/src/mhd2/mhd_auth_digest_hdr.h new file mode 100644 index 00000000..b06d00d9 --- /dev/null +++ b/src/mhd2/mhd_auth_digest_hdr.h @@ -0,0 +1,66 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + 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 src/mhd2/mhd_auth_digest_hdr.h + * @brief The definition of the Digest Auth response header structure + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_AUTH_DIGEST_HDR_H +#define MHD_AUTH_DIGEST_HDR_H 1 + +#include "mhd_sys_options.h" + +#ifndef MHD_SUPPORT_AUTH_DIGEST +#error This header should be used only if Digest Auth is build +#endif + +#include "mhd_str_types.h" +#include "mhd_dlinked_list.h" + +struct MHD_Response; /* forward declaration */ +struct mhd_RespAuthDigestHeader; /* forward declaration */ + +mhd_DLINKEDL_STRUCTS_DEFS (mhd_RespAuthDigestHeader); + +/** + * The Digest Auth response header (the challenge) + */ +struct mhd_RespAuthDigestHeader +{ + /** + * The complete header string, including the name, the colon, the value and + * CRLF terminating chars + */ + struct MHD_String hdr; + + /** + * The position of the nonce placeholder in the @a hdr + */ + size_t nonce_pos; + + /** + * The links to the other headers for the same response + */ + mhd_DLNKDL_LINKS_TYPE (mhd_RespAuthDigestHeader) auth_d_hdrs; +}; + +#endif /* ! MHD_AUTH_DIGEST_HDR_H */ diff --git a/src/mhd2/mhd_digest_auth_data.h b/src/mhd2/mhd_digest_auth_data.h new file mode 100644 index 00000000..767599b2 --- /dev/null +++ b/src/mhd2/mhd_digest_auth_data.h @@ -0,0 +1,53 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + 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 src/mhd2/mhd_digest_auth_data.h + * @brief The macros and other data for Digest Auth + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_DIGEST_AUTH_DATA_H +#define MHD_DIGEST_AUTH_DATA_H 1 + +#include "mhd_sys_options.h" + +/** + * The name of the Auth scheme + */ +#define mhd_AUTH_DIGEST_SCHEME "Digest" + +/** + * The length of the random part of the nonce (in chars) + */ +#define mhd_AUTH_DIGEST_NONCE_RAND_LEN 64 + +/** + * The length of the validity time part of the nonce (in chars) + */ +#define mhd_AUTH_DIGEST_NONCE_VALD_LEN 8 + +/** + * The total length of the nonce (in chars) + */ +#define mhd_AUTH_DIGEST_NONCE_LEN \ + (mhd_AUTH_DIGEST_NONCE_RAND_LEN + mhd_AUTH_DIGEST_NONCE_VALD_LEN) + +#endif /* ! MHD_DIGEST_AUTH_DATA_H */ diff --git a/src/mhd2/mhd_response.h b/src/mhd2/mhd_response.h index bd6c11bf..5d3f0093 100644 --- a/src/mhd2/mhd_response.h +++ b/src/mhd2/mhd_response.h @@ -36,6 +36,9 @@ #include "mhd_dlinked_list.h" #include "mhd_str_types.h" +#ifdef MHD_SUPPORT_AUTH_DIGEST +# include "mhd_auth_digest_hdr.h" +#endif #include "mhd_iovec.h" @@ -356,6 +359,13 @@ struct MHD_Response */ mhd_DLNKDL_LIST (mhd_ResponseHeader,headers); +#ifdef MHD_SUPPORT_AUTH_DIGEST + /** + * The double linked list of the Digest Auth response headers + */ + mhd_DLNKDL_LIST (mhd_RespAuthDigestHeader,auth_d_hdrs); +#endif + /** * Special data for internal error responses */ diff --git a/src/mhd2/response_auth_digest.c b/src/mhd2/response_auth_digest.c new file mode 100644 index 00000000..54fa5ec8 --- /dev/null +++ b/src/mhd2/response_auth_digest.c @@ -0,0 +1,521 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2022-2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + 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 src/mhd2/response_auth_digest.c + * @brief The definitions of MHD_response_add_auth_basic_challenge() function + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +#include "mhd_unreachable.h" +#include "mhd_assert.h" + +#include "mhd_digest_auth_data.h" + +#include "mhd_response.h" +#include "mhd_locks.h" +#include "mhd_str_types.h" +#include +#include "sys_malloc.h" + +#include "mhd_str_macros.h" +#include "mhd_str.h" + +#include "mhd_public_api.h" + +#include "response_auth_digest.h" +#include "mhd_auth_digest_hdr.h" + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ (1) void +mhd_response_remove_auth_digest_headers (struct MHD_Response*response) +{ + struct mhd_RespAuthDigestHeader *hdr_d; + + for (hdr_d = mhd_DLINKEDL_GET_LAST (response, auth_d_hdrs); + NULL != hdr_d; + hdr_d = mhd_DLINKEDL_GET_LAST (response, auth_d_hdrs)) + { + mhd_DLINKEDL_DEL (response, hdr_d, auth_d_hdrs); + free (hdr_d); + } +} + + +/** + * Create and add Digest Auth challenge header with specified algorithm + * @param response the response to update + * @param rlm the realm to use + * @param opq the "opaque" string to use + * @param dmn the "domain" string to use + * @param indicate_stale whether to indicate "stale" nonce + * @param qop_none whether to use RFC 2069 subset only + * @param algo the algorithm to use + * @param userhash_support whether to indicate support for "userhash" + * @param prefer_utf8 whether to indicate UTF-8 support + * @return #MHD_SC_OK on success, + * error code otherwise + */ +static MHD_FN_PAR_NONNULL_ALL_ enum MHD_StatusCode +response_add_auth_digest_challenge_alg ( + struct MHD_Response *restrict response, + const struct MHD_String *restrict rlm, + const struct MHD_StringNullable *restrict opq, + const struct MHD_StringNullable *restrict dmn, + enum MHD_Bool indicate_stale, + bool qop_none, + enum MHD_DigestAuthAlgo algo, + enum MHD_Bool userhash_support, + enum MHD_Bool prefer_utf8) +{ + static const struct MHD_String empty_str = + mhd_MSTR_INIT (""); + static const struct MHD_String hdr_pref_realm_pref = + mhd_MSTR_INIT (MHD_HTTP_HEADER_WWW_AUTHENTICATE ": " \ + mhd_AUTH_DIGEST_SCHEME " realm=\""); + static const struct MHD_String qop_str = + mhd_MSTR_INIT (", qop=\"auth\""); +#ifdef MHD_SUPPORT_MD5 + static const struct MHD_String algo_mh5_str = + mhd_MSTR_INIT (", algorithm=MD5"); +#endif +#ifdef MHD_SUPPORT_SHA256 + static const struct MHD_String algo_sha256_str = + mhd_MSTR_INIT (", algorithm=SHA-256"); +#endif +#ifdef MHD_SUPPORT_SHA512_256 + static const struct MHD_String algo_sha512_256_str = + mhd_MSTR_INIT (", algorithm=SHA-512-256"); +#endif + static const struct MHD_String nonce_str = + mhd_MSTR_INIT (", nonce=\"" \ + "00000000000000000000000000000000" \ + "00000000000000000000000000000000" \ + "00000000" \ + "\""); + static size_t nonce_off = 9; /* Position of nonce value in the nonce_str */ + static const struct MHD_String opaque_pref = + mhd_MSTR_INIT (", opaque=\""); + static const struct MHD_String domain_pref = + mhd_MSTR_INIT (", domain=\""); + static const struct MHD_String stale_str = + mhd_MSTR_INIT (", stale=true"); + static const struct MHD_String charset_str = + mhd_MSTR_INIT (", charset=UTF-8"); + static const struct MHD_String userhash_str = + mhd_MSTR_INIT (", userhash=true"); + + /* Header content: + + Scheme name and space; + + realm, quoted; + + qop="auth", quoted (optional); + + algorithm, NOT quoted (optional); + + nonce (placeholder), quoted; + + opaque, quoted (optional); + + domain, quoted (optional); + + stale=true (optional); + + charset=UTF-8 (optional); + + userhash=true (optional). + */ + + struct MHD_String algo_str; + char *hdr_str; + size_t hdr_maxlen; + struct mhd_RespAuthDigestHeader *new_hdr; + size_t pos; + size_t elm_len; + + mhd_assert ('0' == nonce_str.cstr[nonce_off]); + mhd_assert ('0' == nonce_str.cstr[nonce_off + mhd_AUTH_DIGEST_NONCE_LEN]); + mhd_assert ('"' == nonce_str.cstr[nonce_off - 1]); + mhd_assert ('"' == nonce_str.cstr[nonce_off + mhd_AUTH_DIGEST_NONCE_LEN + 1]); + +#ifdef MHD_SUPPORT_MD5 + if (MHD_DIGEST_AUTH_ALGO_MD5 == algo) + algo_str = qop_none ? empty_str : algo_mh5_str; +#endif +#ifdef MHD_SUPPORT_SHA256 + else if (MHD_DIGEST_AUTH_ALGO_SHA256 == algo) + algo_str = algo_sha256_str; +#endif +#ifdef MHD_SUPPORT_SHA512_256 + else if (MHD_DIGEST_AUTH_ALGO_SHA512_256 == algo) + algo_str = algo_sha512_256_str; +#endif + else + mhd_UNREACHABLE (); + + /* ** Calculate the maximum length of the header string ** */ + hdr_maxlen = 0; + + /* realm */ + hdr_maxlen += hdr_pref_realm_pref.len; + hdr_maxlen += rlm->len * 2; /* Double length for quoting */ + hdr_maxlen += 1; /* closing quote (") */ + + /* qop */ + hdr_maxlen += qop_str.len; + + /* algorithm */ + hdr_maxlen += algo_str.len; + + /* nonce */ + hdr_maxlen += nonce_str.len; + + /* opaque */ + hdr_maxlen += opaque_pref.len; + hdr_maxlen += opq->len * 2; /* Double length for quoting */ + hdr_maxlen += 1; /* closing quote (") */ + + /* domain */ + hdr_maxlen += domain_pref.len; + hdr_maxlen += dmn->len; + hdr_maxlen += 1; /* closing quote (") */ + + /* stale */ + hdr_maxlen += stale_str.len; + + /* charset */ + hdr_maxlen += charset_str.len; + + /* userhash */ + hdr_maxlen += userhash_str.len; + + /* CRLF */ + hdr_maxlen += 2; + + /* ** Allocate ** */ + new_hdr = (struct mhd_RespAuthDigestHeader *) + malloc (sizeof(struct mhd_RespAuthDigestHeader *) + + hdr_maxlen + 1); + if (NULL == new_hdr) + return MHD_SC_RESPONSE_HEADER_MEM_ALLOC_FAILED; + hdr_str = (char *) (new_hdr + 1); + + /* ** Build the header ** */ + pos = 0; + + /* realm */ + memcpy (hdr_str + pos, + hdr_pref_realm_pref.cstr, + hdr_pref_realm_pref.len); + pos += hdr_pref_realm_pref.len; + elm_len = mhd_str_quote (rlm->cstr, + rlm->len, + hdr_str + pos, + hdr_maxlen - pos); + mhd_assert (0 != elm_len); + pos += elm_len; + hdr_str[pos++] = '"'; + + /* qop */ + if (! qop_none) + { + memcpy (hdr_str + pos, + qop_str.cstr, + qop_str.len); + pos += qop_str.len; + } + + /* algorithm */ + if (0 != algo_str.len) + { + memcpy (hdr_str + pos, + algo_str.cstr, + algo_str.len); + pos += algo_str.len; + } + + /* nonce */ + memcpy (hdr_str + pos, + nonce_str.cstr, + nonce_str.len); + new_hdr->nonce_pos = pos + nonce_off; + pos += nonce_str.len; + + /* opaque */ + if (0 != opq->len) + { + memcpy (hdr_str + pos, + opaque_pref.cstr, + opaque_pref.len); + pos += opaque_pref.len; + elm_len = mhd_str_quote (opq->cstr, + opq->len, + hdr_str + pos, + hdr_maxlen - pos); + mhd_assert (0 != elm_len); + pos += elm_len; + hdr_str[pos++] = '"'; + } + + /* domain */ + if (0 != dmn->len) + { + memcpy (hdr_str + pos, + domain_pref.cstr, + domain_pref.len); + pos += domain_pref.len; + memcpy (hdr_str + pos, + dmn->cstr, + dmn->len); + pos += dmn->len; + hdr_str[pos++] = '"'; + } + + /* stale */ + if (MHD_NO != indicate_stale) + { + memcpy (hdr_str + pos, + stale_str.cstr, + stale_str.len); + pos += stale_str.len; + } + + /* charset */ + if ((! qop_none) && + (MHD_NO != prefer_utf8)) + { + memcpy (hdr_str + pos, + charset_str.cstr, + charset_str.len); + pos += charset_str.len; + } + + /* userhash */ + if ((! qop_none) && + (MHD_NO != userhash_support)) + { + memcpy (hdr_str + pos, + userhash_str.cstr, + userhash_str.len); + pos += userhash_str.len; + } + + /* CRLF */ + hdr_str[pos++] = '\r'; + hdr_str[pos++] = '\n'; + + mhd_assert (pos <= hdr_maxlen); + hdr_str[pos] = 0; /* Zero-terminate the string */ + + if (1) + { /* Try to shrink malloc'ed area */ + void *new_ptr; + new_ptr = realloc (new_hdr, + sizeof(struct mhd_RespAuthDigestHeader) + + pos); + /* Just use the old pointer if realloc() failed */ + if (NULL != new_ptr) + new_hdr = (struct mhd_RespAuthDigestHeader *) new_ptr; + } + + new_hdr->hdr.cstr = (char *) (new_hdr + 1); + new_hdr->hdr.len = pos; + mhd_assert (0 == \ + memcmp (new_hdr->hdr.cstr, \ + MHD_HTTP_HEADER_WWW_AUTHENTICATE ": ", \ + mhd_SSTR_LEN (MHD_HTTP_HEADER_WWW_AUTHENTICATE ": "))); + mhd_assert (0 == new_hdr->hdr.cstr[new_hdr->hdr.len]); + + mhd_DLINKEDL_INIT_LINKS (new_hdr, auth_d_hdrs); + mhd_DLINKEDL_INS_LAST (response, new_hdr, auth_d_hdrs); + + return MHD_SC_OK; +} + + +/** + * Create and add Digest Auth challenge headers for all specified algorithms + * @param response the response to update + * @param realm the real to use + * @param opaque the "opaque" string, could be NULL + * @param domain the "domain" string, could be NULL + * @param indicate_stale whether to indicate "stale" nonce + * @param mqop the QOP values to use + * @param malgo the algorithms to use + * @param userhash_support whether to indicate support for "userhash" + * @param prefer_utf8 whether to indicate UTF-8 support + * @return #MHD_SC_OK on success, + * error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4) enum MHD_StatusCode +response_add_auth_digest_challenge_int (struct MHD_Response *restrict response, + const char *restrict realm, + const char *restrict opaque, + const char *restrict domain, + enum MHD_Bool indicate_stale, + enum MHD_DigestAuthMultiQOP mqop, + enum MHD_DigestAuthMultiAlgo malgo, + enum MHD_Bool userhash_support, + enum MHD_Bool prefer_utf8) +{ + const struct MHD_String rlm = { strlen (realm), realm }; + const struct MHD_StringNullable opq = + { (NULL != opaque ? strlen (opaque) : 0), opaque }; + const struct MHD_StringNullable dmn = + { (NULL != domain ? strlen (domain) : 0), domain }; + const bool qop_none = + (0 != (MHD_DIGEST_AUTH_QOP_NONE & ((unsigned int) mqop))); + enum MHD_StatusCode res; + + /* Check validity of the input data */ + + if (0 == rlm.len) + return MHD_SC_RESP_HEADER_VALUE_INVALID; /* Failure exit point */ + if ((NULL != memchr (rlm.cstr, '\n', rlm.len)) || + (NULL != memchr (rlm.cstr, '\r', rlm.len))) + return MHD_SC_RESP_HEADER_VALUE_INVALID; /* Failure exit point */ + + if ((0 != opq.len) && + ((NULL != memchr (opq.cstr, '\n', opq.len)) || + (NULL != memchr (opq.cstr, '\r', opq.len)))) + return MHD_SC_RESP_HEADER_VALUE_INVALID; /* Failure exit point */ + + if ((0 != dmn.len) && + ((NULL != memchr (dmn.cstr, '\n', dmn.len)) || + (NULL != memchr (dmn.cstr, '\r', dmn.len)) || + (NULL != memchr (dmn.cstr, '"', dmn.len)))) + return MHD_SC_RESP_HEADER_VALUE_INVALID; /* Failure exit point */ + + if (0 == (MHD_DIGEST_AUTH_ALGO_NON_SESSION & ((unsigned int) malgo))) + return MHD_SC_AUTH_DIGEST_ALGO_NOT_SUPPORTED; /* Failure exit point */ + + if (0 == ((MHD_DIGEST_AUTH_QOP_NONE | MHD_DIGEST_AUTH_QOP_AUTH) + & ((unsigned int) mqop))) + return MHD_SC_AUTH_DIGEST_QOP_NOT_SUPPORTED; /* Failure exit point */ + + res = MHD_SC_OK; + +#ifdef MHD_SUPPORT_MD5 + if ((MHD_SC_OK == res) && + (0 == (MHD_DIGEST_BASE_ALGO_MD5 & ((unsigned int) malgo)))) + res = response_add_auth_digest_challenge_alg (response, + &rlm, + &opq, + &dmn, + indicate_stale, + qop_none, + MHD_DIGEST_AUTH_ALGO_MD5, + userhash_support, + prefer_utf8); +#endif +#ifdef MHD_SUPPORT_SHA256 + if ((MHD_SC_OK == res) && + (0 == (MHD_DIGEST_BASE_ALGO_SHA256 & ((unsigned int) malgo)))) + res = response_add_auth_digest_challenge_alg (response, + &rlm, + &opq, + &dmn, + indicate_stale, + qop_none, + MHD_DIGEST_AUTH_ALGO_SHA256, + userhash_support, + prefer_utf8); +#endif +#ifdef MHD_SUPPORT_SHA512_256 + if ((MHD_SC_OK == res) && + (0 == (MHD_DIGEST_BASE_ALGO_SHA256 & ((unsigned int) malgo)))) + res = response_add_auth_digest_challenge_alg ( + response, + &rlm, + &opq, + &dmn, + indicate_stale, + qop_none, + MHD_DIGEST_AUTH_ALGO_SHA512_256, + userhash_support, + prefer_utf8); +#endif + + if (MHD_SC_OK != res) + { + mhd_response_remove_auth_digest_headers (response); + return res; /* Failure exit point */ + } + + if (NULL == mhd_DLINKEDL_GET_FIRST (response, auth_d_hdrs)) + return MHD_SC_AUTH_DIGEST_ALGO_NOT_SUPPORTED; /* Failure exit point */ + + return MHD_SC_OK; /* Success exit point */ +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2) +MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4) enum MHD_StatusCode +MHD_response_add_auth_digest_challenge ( + struct MHD_Response *MHD_RESTRICT response, + const char *MHD_RESTRICT realm, + const char *MHD_RESTRICT opaque, + const char *MHD_RESTRICT domain, + enum MHD_Bool indicate_stale, + enum MHD_DigestAuthMultiQOP mqop, + enum MHD_DigestAuthMultiAlgo malgo, + enum MHD_Bool userhash_support, + enum MHD_Bool prefer_utf8) +{ + bool need_unlock; + enum MHD_StatusCode res; + + if (NULL == response) + return MHD_SC_RESP_POINTER_NULL; + if (response->frozen) + return MHD_SC_TOO_LATE; + if (MHD_HTTP_STATUS_UNAUTHORIZED != response->sc) + return MHD_SC_RESP_HTTP_CODE_NOT_SUITABLE; + + if (response->reuse.reusable) + { + need_unlock = true; + if (! mhd_mutex_lock (&(response->reuse.settings_lock))) + return MHD_SC_RESPONSE_MUTEX_LOCK_FAILED; + mhd_assert (1 == mhd_atomic_counter_get (&(response->reuse.counter))); + } + else + need_unlock = false; + + if (response->frozen) /* Re-check with the lock held */ + res = MHD_SC_TOO_LATE; + else if (NULL != mhd_DLINKEDL_GET_FIRST (response, auth_d_hdrs)) + res = MHD_SC_RESP_HEADERS_CONFLICT; + else + res = response_add_auth_digest_challenge_int (response, + realm, + opaque, + domain, + indicate_stale, + mqop, + malgo, + userhash_support, + prefer_utf8); + + if (need_unlock) + mhd_mutex_unlock_chk (&(response->reuse.settings_lock)); + + return res; +} diff --git a/src/mhd2/response_auth_digest.h b/src/mhd2/response_auth_digest.h new file mode 100644 index 00000000..4ddbf202 --- /dev/null +++ b/src/mhd2/response_auth_digest.h @@ -0,0 +1,42 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + 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 src/mhd2/response_auth_digest.h + * @brief The declaration of the Digest Auth response header helper functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_RESPONSE_AUTH_DIGEST_H +#define MHD_RESPONSE_AUTH_DIGEST_H 1 + +#include "mhd_sys_options.h" + +struct MHD_Response; /* forward declaration */ + +/** + * Remove all Digest Auth headers (if any) from the response + * @param response the response to clean + */ +MHD_INTERNAL void +mhd_response_remove_auth_digest_headers (struct MHD_Response*response) +MHD_FN_PAR_NONNULL_ (1); + +#endif /* ! MHD_RESPONSE_AUTH_DIGEST_H */ diff --git a/src/mhd2/response_destroy.c b/src/mhd2/response_destroy.c index b310185f..e61b7146 100644 --- a/src/mhd2/response_destroy.c +++ b/src/mhd2/response_destroy.c @@ -40,6 +40,10 @@ #include "response_funcs.h" #include "response_from.h" +#ifdef MHD_SUPPORT_AUTH_DIGEST +# include "response_auth_digest.h" +#endif + #define mhd_RESPONSE_DESTOYED "Attempt to use destroyed response, " \ "re-use non-reusable response or wrong MHD_Response pointer" @@ -52,6 +56,9 @@ static MHD_FN_PAR_NONNULL_ (1) void response_full_deinit (struct MHD_Response *restrict r) { +#ifdef MHD_SUPPORT_AUTH_DIGEST + mhd_response_remove_auth_digest_headers (r); +#endif mhd_response_remove_all_headers (r); if (NULL != r->special_resp.spec_hdr) free (r->special_resp.spec_hdr); diff --git a/src/mhd2/response_from.c b/src/mhd2/response_from.c index d4c0645c..7f5853c0 100644 --- a/src/mhd2/response_from.c +++ b/src/mhd2/response_from.c @@ -68,6 +68,9 @@ response_create_basic (enum MHD_HTTP_StatusCode sc, { #ifndef HAVE_NULL_PTR_ALL_ZEROS mhd_DLINKEDL_INIT_LIST (r, headers); +#ifdef MHD_SUPPORT_AUTH_DIGEST + mhd_DLINKEDL_INIT_LIST (r, auth_d_hdrs); +#endif r->free.cb = NULL; r->free.cls = NULL; r->special_resp.spec_hdr = NULL; diff --git a/src/microhttpd/Makefile.am b/src/microhttpd/Makefile.am index bd3b5a6f..65c39be8 100644 --- a/src/microhttpd/Makefile.am +++ b/src/microhttpd/Makefile.am @@ -175,10 +175,10 @@ if MHD_SUPPORT_AUTH_DIGEST libmicrohttpd_la_SOURCES += \ digestauth.c digestauth.h \ mhd_bithelpers.h mhd_byteorder.h mhd_align.h -if ENABLE_MD5 +if MHD_SUPPORT_MD5 libmicrohttpd_la_SOURCES += \ mhd_md5_wrap.h -if ! ENABLE_MD5_EXT +if ! MHD_MD5_EXTR libmicrohttpd_la_SOURCES += \ md5.c md5.h else @@ -186,10 +186,10 @@ libmicrohttpd_la_SOURCES += \ md5_ext.c md5_ext.h endif endif -if ENABLE_SHA256 +if MHD_SUPPORT_SHA256 libmicrohttpd_la_SOURCES += \ mhd_sha256_wrap.h -if ! ENABLE_SHA256_EXT +if ! MHD_SHA256_EXTR libmicrohttpd_la_SOURCES += \ sha256.c sha256.h else @@ -197,7 +197,7 @@ libmicrohttpd_la_SOURCES += \ sha256_ext.c sha256_ext.h endif endif -if ENABLE_SHA512_256 +if MHD_SUPPORT_SHA512_256 libmicrohttpd_la_SOURCES += \ sha512_256.c sha512_256.h endif @@ -244,15 +244,15 @@ check_PROGRAMS = \ test_mhd_version \ test_set_panic -if ENABLE_MD5 +if MHD_SUPPORT_MD5 check_PROGRAMS += \ test_md5 endif -if ENABLE_SHA256 +if MHD_SUPPORT_SHA256 check_PROGRAMS += \ test_sha256 endif -if ENABLE_SHA512_256 +if MHD_SUPPORT_SHA512_256 check_PROGRAMS += \ test_sha512_256 endif @@ -518,7 +518,7 @@ test_http_reasons_SOURCES = \ test_md5_SOURCES = \ test_md5.c test_helpers.h mhd_md5_wrap.h ../include/mhd_options.h -if ! ENABLE_MD5_EXT +if ! MHD_MD5_EXTR test_md5_SOURCES += \ md5.c md5.h mhd_bithelpers.h mhd_byteorder.h mhd_align.h test_md5_CPPFLAGS = $(AM_CPPFLAGS) @@ -536,7 +536,7 @@ endif test_sha256_SOURCES = \ test_sha256.c test_helpers.h mhd_sha256_wrap.h ../include/mhd_options.h -if ! ENABLE_SHA256_EXT +if ! MHD_SHA256_EXTR test_sha256_SOURCES += \ sha256.c sha256.h mhd_bithelpers.h mhd_byteorder.h mhd_align.h test_sha256_CPPFLAGS = $(AM_CPPFLAGS) diff --git a/src/testcurl/Makefile.am b/src/testcurl/Makefile.am index a1745f7a..1c7acf86 100644 --- a/src/testcurl/Makefile.am +++ b/src/testcurl/Makefile.am @@ -181,18 +181,18 @@ endif if MHD_SUPPORT_AUTH_DIGEST -if ENABLE_MD5 +if MHD_SUPPORT_MD5 THREAD_ONLY_TESTS += \ test_digestauth \ test_digestauth_with_arguments \ test_digestauth_concurrent endif -if ENABLE_SHA256 +if MHD_SUPPORT_SHA256 THREAD_ONLY_TESTS += \ test_digestauth_sha256 endif -if ENABLE_MD5 +if MHD_SUPPORT_MD5 check_PROGRAMS += \ test_digestauth_emu_ext \ test_digestauth_emu_ext_oldapi \ @@ -211,7 +211,7 @@ check_PROGRAMS += \ test_digestauth2_oldapi1_bind_all \ test_digestauth2_oldapi1_bind_uri endif -if ENABLE_SHA256 +if MHD_SUPPORT_SHA256 check_PROGRAMS += \ test_digestauth2_sha256 \ test_digestauth2_sha256_userhash \