libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

commit 03978499e6055c7e416108a3993441f1a33fe7a6
parent 7ba69b8c8bf341b923c49e38b40bc79d3206a668
Author: Evgeny Grin (Karlson2k) <k2k@drgrin.dev>
Date:   Tue, 18 Aug 2026 19:12:36 +0200

Implemented ACME ALPN challenge support. GnuTLS-only implementation.

Implemented basis for backend-independent parsing, detection and
challange certificates management.

Diffstat:
Mconfigure.ac | 14++++++++++++++
Msrc/incl_priv/mhd_sys_options.h | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/d_options.rec | 8++++++++
Msrc/include/microhttpd2.h | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/microhttpd2_generated_daemon_options.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/microhttpd2_preamble.h.in | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/Makefile.am | 14++++++++++++++
Msrc/mhd2/conn_tls_check.c | 7+++++++
Msrc/mhd2/daemon_options.h | 6++++++
Msrc/mhd2/daemon_set_options.c | 3+++
Msrc/mhd2/daemon_start.c | 94++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/mhd2/mhd_daemon.h | 33+++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_tls_acme_func.c | 485+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_tls_acme_func.h | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/mhd_tls_certs_list.h | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/mhd_tls_choice.h | 5+++++
Asrc/mhd2/mhd_tls_cred_ptr.h | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/mhd_tls_enums.h | 32++++++++++++++++++++++++++++++++
Msrc/mhd2/mhd_tls_funcs.h | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/mhd_tls_internal.c | 259+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/mhd_tls_internal.h | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/stream_funcs.c | 7+++++++
Msrc/mhd2/stream_funcs.h | 20++++++++++++++++++++
Msrc/mhd2/tls_gnu_conn_data.h | 26++++++++++++++++++++++++++
Msrc/mhd2/tls_gnu_daemon_data.h | 40++++++++++++++++++++++++++++++++++------
Msrc/mhd2/tls_gnu_funcs.c | 776++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/mhd2/tls_gnu_funcs.h | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/tls_gnu_tls_lib.h | 5+++++
Msrc/mhd2/tls_mbed_funcs.c | 12++++++++++++
Msrc/mhd2/tls_mbed_funcs.h | 39+++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/tls_multi_funcs.c | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/tls_multi_funcs.h | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/tls_open_funcs.c | 12++++++++++++
Msrc/mhd2/tls_open_funcs.h | 39+++++++++++++++++++++++++++++++++++++++
34 files changed, 3198 insertions(+), 116 deletions(-)

diff --git a/configure.ac b/configure.ac @@ -7356,6 +7356,18 @@ AS_VAR_IF([[enable_cookie]],[["yes"]], AM_CONDITIONAL([MHD_SUPPORT_COOKIES], [[test "x$enable_cookie" = "xyes"]]) AC_MSG_RESULT([[$enable_cookie]]) +# optional: ACME support. Enabled by default +AC_MSG_CHECKING([[whether to enable ACME functions]]) +AC_ARG_ENABLE([[acme]], + [AS_HELP_STRING([[--disable-acme]], [disable ACME functions])], + [AS_VAR_IF([[enable_acme]],[["no"]],[],[[enable_acme='yes']])], + [[enable_acme='yes']]) +AS_VAR_IF([[enable_acme]],[["yes"]], + [ + AC_DEFINE([[MHD_SUPPORT_ACME]],[[1]],[Define to 1 if libmicrohttpd is compiled with ACME functions support.]) ]) +AM_CONDITIONAL([MHD_SUPPORT_ACME], [[test "X$enable_acme" = "Xyes"]]) +AC_MSG_RESULT([[$enable_acme]]) + # optional: MD5 support for Digest Auth. Enabled by default. AC_ARG_ENABLE([[md5]], [AS_HELP_STRING([[--enable-md5=TYPE]], @@ -9971,6 +9983,7 @@ AC_MSG_NOTICE([[${PACKAGE_NAME} ${PACKAGE_VERSION} Configuration Summary: SHA-256 : ${enable_sha256_MSG} SHA-512/256 : ${enable_sha512_256_MSG} HTTP "Upgrade" : ${enable_httpupgrade} + ACME functions : ${enable_acme} Logging support : ${enable_log_messages} Verbose auto replies: ${enable_http_messages} @@ -10014,6 +10027,7 @@ AS_IF([test "x$enable_basic_auth" != "xyes" || \ test "x$enable_https" != "xyes" || \ test "x$enable_cookie" != "xyes" || \ test "x$enable_httpupgrade" != "xyes" || \ + test "X$enable_acme" != "Xyes" || \ test "x$enable_postparser" != "xyes"], [AC_MSG_WARN([This will be a custom build with missing symbols. Do NOT use this build in a distribution. Building with these kinds of configure options is only for custom builds for embedded systems.])] ) diff --git a/src/incl_priv/mhd_sys_options.h b/src/incl_priv/mhd_sys_options.h @@ -684,6 +684,65 @@ # endif #endif +#ifdef MHD_SUPPORT_HTTPS +# ifdef MHD_SUPPORT_ACME +# ifdef MHD_SUPPORT_GNUTLS +# ifdef HAVE_GNUTLS_HANDSHAKE_SET_HOOK_FUNCTION +/** + * Indicate that ACME ALPN challenge support is available in the GnuTLS backend + */ +# define mhd_HAVE_GNUTLS_ACME 1 +/* define MHD_FORCE_USE_MHD_CLIENTHELLO_BODY_PARSE to always use MHD internal + parser for ClientHello body instead of all TLS backend parsers */ +/* define MHD_FORCE_USE_GNUTLS_MHD_CLIENTHELLO_BODY_PARSE to use MHD internal + parser for ClientHello body instead of GnuTLS parser */ +# if defined(MHD_FORCE_USE_MHD_CLIENTHELLO_BODY_PARSE) \ + && !defined(MHD_FORCE_USE_GNUTLS_MHD_CLIENTHELLO_BODY_PARSE) +/** + * Use MHD internal parser for ClientHello body instead of GnuTLS parser + */ +# define MHD_FORCE_USE_GNUTLS_MHD_CLIENTHELLO_BODY_PARSE 1 +# endif +# if !defined(HAVE_GNUTLS_EXT_RAW_PARSE) || \ + defined(MHD_FORCE_USE_GNUTLS_MHD_CLIENTHELLO_BODY_PARSE) +# ifndef mhd_HAVE_TLS_MHD_CLIENTHELLO_BODY_PARSE +/** + * MHD internal parser for ClientHello body is available + */ +# define mhd_HAVE_TLS_MHD_CLIENTHELLO_BODY_PARSE 1 +# endif /* ! mhd_HAVE_TLS_MHD_CLIENTHELLO_BODY_PARSE */ +# else /* HAVE_GNUTLS_EXT_RAW_PARSE + && ! MHD_FORCE_USE_GNUTLS_MHD_CLIENTHELLO_BODY_PARSE */ +/** + * Use GnuTLS parser for ClientHello body + */ +# define mhd_USE_GNUTLS_EXT_RAW_PARSE 1 +# endif /* HAVE_GNUTLS_EXT_RAW_PARSE + && ! MHD_FORCE_USE_GNUTLS_MHD_CLIENTHELLO_BODY_PARSE */ +# endif /* HAVE_GNUTLS_HANDSHAKE_SET_HOOK_FUNCTION */ +# endif /* MHD_SUPPORT_GNUTLS */ +# else /* ! MHD_SUPPORT_ACME */ +# ifdef MHD_FORCE_USE_MHD_CLIENTHELLO_BODY_PARSE +# undef MHD_FORCE_USE_MHD_CLIENTHELLO_BODY_PARSE +# endif +# ifdef MHD_FORCE_USE_GNUTLS_MHD_CLIENTHELLO_BODY_PARSE +# undef MHD_FORCE_USE_GNUTLS_MHD_CLIENTHELLO_BODY_PARSE +# endif +# endif /* ! MHD_SUPPORT_ACME */ +#endif /* MHD_SUPPORT_HTTPS */ + +#if defined(MHD_SUPPORT_HTTPS) && defined(MHD_SUPPORT_ACME) && \ + defined(mhd_HAVE_GNUTLS_ACME) +/** + * Have support for ACME ALPN challenge + * @warning If MHD_SUPPORT_ACME is enabled but this macros is not defined then + * application API for ACME ALPN is available but it will fail + * at runtime + */ +# define mhd_HAVE_TLS_ACME 1 +#endif + + /* Eclipse parse compatibility */ #ifdef __CDT_PARSER__ # undef MHD_NORETURN_ diff --git a/src/include/d_options.rec b/src/include/d_options.rec @@ -253,6 +253,14 @@ Comment: Control ALPN for TLS connection. + Silently ignored for non-TLS. + By default ALPN is automatically used for TLS connections. +Name: acme_alpn_required +Value: 141 +Type: enum MHD_Bool +Comment: Require ACME ALPN challenge support in TLS backend. ++ If this option is enabled then TLS backends without ACME ALPN challenge ++ support will not be selected automatically. The daemon will fail to start ++ if no TLS backend with ACME ALPN is available. + Name: tls_app_name Value: 142 Type: struct MHD_DaemonOptionValueTlsAppName diff --git a/src/include/microhttpd2.h b/src/include/microhttpd2.h @@ -1491,6 +1491,16 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_HASH_FAILED = 51260 , /** + * Failed to acquire a mutex or RW-lock. + */ + MHD_SC_MUTEX_LOCK_FAILED = 51400 + , + /** + * Unspecified error in the TLS backend. + */ + MHD_SC_TLS_BACKEND_ERROR = 52000 + , + /** * Something wrong in the internal MHD logic. * This error should be never returned if MHD works as expected. * If this code is ever returned, please report to MHD maintainers. @@ -1635,6 +1645,16 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_TLS_BACKEND_DAEMON_INCOMPATIBLE_SETTINGS = 60034 , /** + * The TLS support is disabled in daemon + */ + MHD_SC_DAEMON_HAS_TLS_DISABLED = 60055 + , + /** + * The daemon supports TLS connections only + */ + MHD_SC_DAEMON_HAS_TLS_ENABLED = 60056 + , + /** * The pointer to the response object is NULL */ MHD_SC_RESP_POINTER_NULL = 60060 @@ -1805,6 +1825,17 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode * The application tried to add second CONNECTION header. */ MHD_SC_RESP_HEADER_CONNECTION_DUPLICATE = 60341 + , + /** + * The provided parameter is empty (for example, a zero-length string), + * but a non-empty value is required. + */ + MHD_SC_PARAM_EMPTY = 61000 + , + /** + * The requested item was not found + */ + MHD_SC_ITEM_NOT_FOUND = 61001 }; /** @@ -2707,6 +2738,90 @@ MHD_EXTERN_ void MHD_daemon_destroy (struct MHD_Daemon *daemon) MHD_FN_PAR_NONNULL_ALL_; +/* ******************* ACME daemon control ************************ */ + +/** + * @defgroup acme ACME protocol handling + */ + +/** + * Add or replace special certificate for ALPN challenge for ACME. + * + * Takes effect for connections accepted after this function returns. + * + * Note: the fallback (@p domain is NULL) certificate can be removed only + * together with all other certificates. + * + * Must be called only for started daemons. + * + * @param daemon the daemon to update + * @param domain the SNI domain for ACME challenge in ASCII/ACE (Punycode) + * format (case is ignored), + * NULL makes @p cert match any domain (used last, when no + * specific domain matches), + * must be a non-empty string if non-NULL + * @param cert the ACME challenge certificate in PEM format for + * the @p domain (certificate match is not checked) + * @param key the private key for @p cert in PEM format + * @param password the password for @p key, can be NULL + * @return #MHD_SC_OK on success, + * #MHD_SC_TOO_EARLY if the @p daemon has not been started yet, + * #MHD_SC_TOO_LATE if the @p daemon is failed, + * #MHD_SC_DAEMON_HAS_TLS_DISABLED if the @p daemon has + * no TLS backend enabled, + * #MHD_SC_PARAM_EMPTY if @p domain is a zero-length string, + * #MHD_SC_TLS_CONF_BAD_CERT if @p cert or @p key is empty, + * malformed or does not match @p password, + * #MHD_SC_TLS_BACKEND_OPERATION_UNSUPPORTED if ACME ALPN challenge + * is not supported by this MHD build or by the selected TLS + * backend, + * #MHD_SC_TLS_BACKEND_ERROR if the TLS backend failed to create + * the credentials, + * #MHD_SC_DAEMON_MEM_ALLOC_FAILURE if memory allocation failed, + * or other error code if failed for other reasons + * @ingroup acme + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_daemon_acme_alpn_cert_add (struct MHD_Daemon *MHD_RESTRICT daemon, + const char *MHD_RESTRICT domain, + const char *MHD_RESTRICT cert, + const char *MHD_RESTRICT key, + const char *MHD_RESTRICT password) +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_NONNULL_ (4) +MHD_FN_PAR_CSTR_ (2) MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4) +MHD_FN_PAR_CSTR_ (5); + +/** + * Remove ACME challenge certificate. + * + * Takes effect for connections accepted after this function returns. + * + * Must be called only for started daemons. + * + * @param daemon the daemon to update + * @param domain the SNI domain for ACME challenge to remove, + * NULL empties the list of special certificates, + * must be non-empty string if non-NULL + * @return #MHD_SC_OK on success, + * #MHD_SC_ITEM_NOT_FOUND if no certificate is set for the + * specified @p domain, + * #MHD_SC_TOO_EARLY if the @p daemon has not been started yet, + * #MHD_SC_TOO_LATE if the @p daemon is not running, + * #MHD_SC_DAEMON_HAS_TLS_DISABLED if the @p daemon has no TLS + * enabled, + * #MHD_SC_PARAM_EMPTY if @p domain is a zero-length string, + * #MHD_SC_TLS_BACKEND_OPERATION_UNSUPPORTED if ACME ALPN challenge + * is not supported by this MHD build or by the selected TLS + * backend, + * or other error code if failed for other reasons + * @ingroup acme + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_daemon_acme_alpn_cert_del (struct MHD_Daemon *MHD_RESTRICT daemon, + const char *domain) +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (2); + + /* ******************* External event loop ************************ */ /** @@ -4426,6 +4541,18 @@ MHD_D_OPTION_NO_ALPN ( ); /** + * Require ACME ALPN challenge support in TLS backend. + * If this option is enabled then TLS backends without ACME ALPN challenge + * support will not be selected automatically. The daemon will fail to start + * if no TLS backend with ACME ALPN is available. + * @param value the value of the parameter * @return structure with the requested setting + */ +struct MHD_DaemonOptionAndValue +MHD_D_OPTION_ACME_ALPN_REQUIRED ( + enum MHD_Bool value + ); + +/** * Provide application name to load dedicated section in TLS backend's configuration file. * Search for "System-wide configuration of the library" for GnuTLS documentation or * for "config, OPENSSL LIBRARY CONFIGURATION" for OpenSSL documentation. diff --git a/src/include/microhttpd2_generated_daemon_options.h b/src/include/microhttpd2_generated_daemon_options.h @@ -149,6 +149,15 @@ Works only when #MHD_D_OPTION_BIND_PORT() or #MHD_D_OPTION_BIND_SA() are used. , /** + * Require ACME ALPN challenge support in TLS backend. + * If this option is enabled then TLS backends without ACME ALPN challenge + * support will not be selected automatically. The daemon will fail to start + * if no TLS backend with ACME ALPN is available. + */ + MHD_D_O_ACME_ALPN_REQUIRED = 141 + , + + /** * Provide application name to load dedicated section in TLS backend's configuration file. * Search for "System-wide configuration of the library" for GnuTLS documentation or * for "config, OPENSSL LIBRARY CONFIGURATION" for OpenSSL documentation. @@ -790,6 +799,11 @@ union MHD_DaemonOptionValue enum MHD_Bool no_alpn; /** + * Value for #MHD_D_O_ACME_ALPN_REQUIRED. + */ + enum MHD_Bool acme_alpn_required; + + /** * Value for #MHD_D_O_TLS_APP_NAME. * the name of the application, used as converted to * uppercase (with '@'-prefixed) for GnuTLS and as converted to @@ -1242,6 +1256,21 @@ Works only when #MHD_D_OPTION_BIND_PORT() or #MHD_D_OPTION_BIND_SA() are used. } \ MHD_RESTORE_WARN_COMPOUND_LITERALS_ MHD_RESTORE_WARN_AGGR_DYN_INIT_ /** + * Require ACME ALPN challenge support in TLS backend. + * If this option is enabled then TLS backends without ACME ALPN challenge + * support will not be selected automatically. The daemon will fail to start + * if no TLS backend with ACME ALPN is available. + * @param value the value of the parameter * @return structure with the requested setting + */ +# define MHD_D_OPTION_ACME_ALPN_REQUIRED(value) \ + MHD_NOWARN_COMPOUND_LITERALS_ MHD_NOWARN_AGGR_DYN_INIT_ \ + (const struct MHD_DaemonOptionAndValue) \ + { \ + .opt = MHD_D_O_ACME_ALPN_REQUIRED, \ + .val.acme_alpn_required = (value) \ + } \ + MHD_RESTORE_WARN_COMPOUND_LITERALS_ MHD_RESTORE_WARN_AGGR_DYN_INIT_ +/** * Provide application name to load dedicated section in TLS backend's configuration file. * Search for "System-wide configuration of the library" for GnuTLS documentation or * for "config, OPENSSL LIBRARY CONFIGURATION" for OpenSSL documentation. @@ -2091,6 +2120,27 @@ MHD_D_OPTION_NO_ALPN ( /** + * Require ACME ALPN challenge support in TLS backend. + * If this option is enabled then TLS backends without ACME ALPN challenge + * support will not be selected automatically. The daemon will fail to start + * if no TLS backend with ACME ALPN is available. + * @param value the value of the parameter * @return structure with the requested setting + */ +static MHD_INLINE struct MHD_DaemonOptionAndValue +MHD_D_OPTION_ACME_ALPN_REQUIRED ( + enum MHD_Bool value + ) +{ + struct MHD_DaemonOptionAndValue opt_val; + + opt_val.opt = MHD_D_O_ACME_ALPN_REQUIRED; + opt_val.val.acme_alpn_required = (value); \ + + return opt_val; +} + + +/** * Provide application name to load dedicated section in TLS backend's configuration file. * Search for "System-wide configuration of the library" for GnuTLS documentation or * for "config, OPENSSL LIBRARY CONFIGURATION" for OpenSSL documentation. diff --git a/src/include/microhttpd2_preamble.h.in b/src/include/microhttpd2_preamble.h.in @@ -1491,6 +1491,16 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_HASH_FAILED = 51260 , /** + * Failed to acquire a mutex or RW-lock. + */ + MHD_SC_MUTEX_LOCK_FAILED = 51400 + , + /** + * Unspecified error in the TLS backend. + */ + MHD_SC_TLS_BACKEND_ERROR = 52000 + , + /** * Something wrong in the internal MHD logic. * This error should be never returned if MHD works as expected. * If this code is ever returned, please report to MHD maintainers. @@ -1635,6 +1645,16 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_TLS_BACKEND_DAEMON_INCOMPATIBLE_SETTINGS = 60034 , /** + * The TLS support is disabled in daemon + */ + MHD_SC_DAEMON_HAS_TLS_DISABLED = 60055 + , + /** + * The daemon supports TLS connections only + */ + MHD_SC_DAEMON_HAS_TLS_ENABLED = 60056 + , + /** * The pointer to the response object is NULL */ MHD_SC_RESP_POINTER_NULL = 60060 @@ -1805,6 +1825,17 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode * The application tried to add second CONNECTION header. */ MHD_SC_RESP_HEADER_CONNECTION_DUPLICATE = 60341 + , + /** + * The provided parameter is empty (for example, a zero-length string), + * but a non-empty value is required. + */ + MHD_SC_PARAM_EMPTY = 61000 + , + /** + * The requested item was not found + */ + MHD_SC_ITEM_NOT_FOUND = 61001 }; /** @@ -2707,6 +2738,90 @@ MHD_EXTERN_ void MHD_daemon_destroy (struct MHD_Daemon *daemon) MHD_FN_PAR_NONNULL_ALL_; +/* ******************* ACME daemon control ************************ */ + +/** + * @defgroup acme ACME protocol handling + */ + +/** + * Add or replace special certificate for ALPN challenge for ACME. + * + * Takes effect for connections accepted after this function returns. + * + * Note: the fallback (@p domain is NULL) certificate can be removed only + * together with all other certificates. + * + * Must be called only for started daemons. + * + * @param daemon the daemon to update + * @param domain the SNI domain for ACME challenge in ASCII/ACE (Punycode) + * format (case is ignored), + * NULL makes @p cert match any domain (used last, when no + * specific domain matches), + * must be a non-empty string if non-NULL + * @param cert the ACME challenge certificate in PEM format for + * the @p domain (certificate match is not checked) + * @param key the private key for @p cert in PEM format + * @param password the password for @p key, can be NULL + * @return #MHD_SC_OK on success, + * #MHD_SC_TOO_EARLY if the @p daemon has not been started yet, + * #MHD_SC_TOO_LATE if the @p daemon is failed, + * #MHD_SC_DAEMON_HAS_TLS_DISABLED if the @p daemon has + * no TLS backend enabled, + * #MHD_SC_PARAM_EMPTY if @p domain is a zero-length string, + * #MHD_SC_TLS_CONF_BAD_CERT if @p cert or @p key is empty, + * malformed or does not match @p password, + * #MHD_SC_TLS_BACKEND_OPERATION_UNSUPPORTED if ACME ALPN challenge + * is not supported by this MHD build or by the selected TLS + * backend, + * #MHD_SC_TLS_BACKEND_ERROR if the TLS backend failed to create + * the credentials, + * #MHD_SC_DAEMON_MEM_ALLOC_FAILURE if memory allocation failed, + * or other error code if failed for other reasons + * @ingroup acme + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_daemon_acme_alpn_cert_add (struct MHD_Daemon *MHD_RESTRICT daemon, + const char *MHD_RESTRICT domain, + const char *MHD_RESTRICT cert, + const char *MHD_RESTRICT key, + const char *MHD_RESTRICT password) +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (3) MHD_FN_PAR_NONNULL_ (4) +MHD_FN_PAR_CSTR_ (2) MHD_FN_PAR_CSTR_ (3) MHD_FN_PAR_CSTR_ (4) +MHD_FN_PAR_CSTR_ (5); + +/** + * Remove ACME challenge certificate. + * + * Takes effect for connections accepted after this function returns. + * + * Must be called only for started daemons. + * + * @param daemon the daemon to update + * @param domain the SNI domain for ACME challenge to remove, + * NULL empties the list of special certificates, + * must be non-empty string if non-NULL + * @return #MHD_SC_OK on success, + * #MHD_SC_ITEM_NOT_FOUND if no certificate is set for the + * specified @p domain, + * #MHD_SC_TOO_EARLY if the @p daemon has not been started yet, + * #MHD_SC_TOO_LATE if the @p daemon is not running, + * #MHD_SC_DAEMON_HAS_TLS_DISABLED if the @p daemon has no TLS + * enabled, + * #MHD_SC_PARAM_EMPTY if @p domain is a zero-length string, + * #MHD_SC_TLS_BACKEND_OPERATION_UNSUPPORTED if ACME ALPN challenge + * is not supported by this MHD build or by the selected TLS + * backend, + * or other error code if failed for other reasons + * @ingroup acme + */ +MHD_EXTERN_ enum MHD_StatusCode +MHD_daemon_acme_alpn_cert_del (struct MHD_Daemon *MHD_RESTRICT daemon, + const char *domain) +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (2); + + /* ******************* External event loop ************************ */ /** diff --git a/src/mhd2/Makefile.am b/src/mhd2/Makefile.am @@ -253,6 +253,7 @@ upgrade_OPTSOURCES = \ tls_common_OPTSOURCES = \ mhd_tls_enums.h mhd_tls_ver_stct.h tls_dh_params.h \ + mhd_tls_certs_list.h mhd_tls_cred_ptr.h \ mhd_tls_internal.c mhd_tls_internal.h \ mhd_tls_common.c mhd_tls_common.h \ mhd_tls_choice.h mhd_tls_funcs.h \ @@ -274,6 +275,15 @@ tls_mbed_OPTSOURCES = \ tls_mbed_tls_lib.h tls_mbed_daemon_data.h tls_mbed_conn_data.h \ tls_mbed_funcs.c tls_mbed_funcs.h +acme_OPTSOURCES = + +acme_https_OPTSOURCES = \ + mhd_tls_acme_func.c mhd_tls_acme_func.h + +if MHD_SUPPORT_HTTPS + acme_OPTSOURCES += $(acme_https_OPTSOURCES) +endif + if MHD_SUPPORT_HTTP2 libmicrohttpd2_la_SOURCES += $(httptwo_OPTSOURCES) endif @@ -322,6 +332,10 @@ if MHD_SUPPORT_MBEDTLS endif endif +if MHD_SUPPORT_ACME + libmicrohttpd2_la_SOURCES += $(acme_OPTSOURCES) +endif + libmicrohttpd2_la_CPPFLAGS = \ $(AM_CPPFLAGS) $(MHD_LIB_CPPFLAGS) $(MHD_TLS_LIB_CPPFLAGS) \ -DBUILDING_MHD_LIB=1 diff --git a/src/mhd2/conn_tls_check.c b/src/mhd2/conn_tls_check.c @@ -103,6 +103,13 @@ mhd_conn_tls_check (struct MHD_Connection *restrict c) & (~(enum mhd_SocketNetState) mhd_SOCKET_NET_STATE_RECV_READY)); } +#ifdef mhd_HAVE_TLS_ACME + if (mhd_tls_conn_is_acme (c->tls)) + { + mhd_conn_start_closing_acme_alpn_challenge_completed (c); + return mhd_COMM_LAYER_BROKEN; + } +#endif /* mhd_HAVE_TLS_ACME */ if (mhd_tls_conn_has_data_in (c->tls)) c->tls_has_data_in = mhd_TLS_BUF_HAS_DATA_IN; /* TLS is connected now, set event loop state based on HTTP protocol. diff --git a/src/mhd2/daemon_options.h b/src/mhd2/daemon_options.h @@ -129,6 +129,12 @@ struct DaemonOptions /** + * Value for #MHD_D_O_ACME_ALPN_REQUIRED. + */ + enum MHD_Bool acme_alpn_required; + + + /** * Value for #MHD_D_O_TLS_APP_NAME. * the name of the application, used as converted to * uppercase (with '@'-prefixed) for GnuTLS and as converted to diff --git a/src/mhd2/daemon_set_options.c b/src/mhd2/daemon_set_options.c @@ -152,6 +152,9 @@ MHD_daemon_set_options ( case MHD_D_O_NO_ALPN: settings->no_alpn = option->val.no_alpn; continue; + case MHD_D_O_ACME_ALPN_REQUIRED: + settings->acme_alpn_required = option->val.acme_alpn_required; + continue; case MHD_D_O_TLS_APP_NAME: /* custom setter */ settings->tls_app_name.v_disable_fallback = diff --git a/src/mhd2/daemon_start.c b/src/mhd2/daemon_start.c @@ -94,6 +94,9 @@ #ifdef MHD_SUPPORT_HTTPS # include "mhd_tls_common.h" # include "mhd_tls_funcs.h" +# ifdef MHD_SUPPORT_ACME +# include "mhd_tls_acme_func.h" +# endif #endif #include "events_process.h" @@ -104,6 +107,7 @@ # include "daemon_funcs.h" #endif + #include "mhd_public_api.h" @@ -2003,6 +2007,79 @@ daemon_deinit_large_buf (struct MHD_Daemon *restrict d) } +/** + * Initialise ACME data. + * @param d the daemon object + * @param s the user settings + * @return #MHD_SC_OK on success, + * the error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) +MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode +daemon_init_acme (struct MHD_Daemon *restrict d, + struct DaemonOptions *restrict s) +{ + mhd_assert (!mhd_D_HAS_MASTER (d)); + +#ifndef mhd_HAVE_TLS_ACME + if (s->acme_alpn_required) + { + mhd_LOG_MSG (d, MHD_SC_FEATURE_DISABLED, + "ACME ALPN challenge is not supported by this MHD build"); + return MHD_SC_FEATURE_DISABLED; + } +#endif /* mhd_HAVE_TLS_ACME */ + +#ifdef MHD_SUPPORT_ACME + if (!mhd_D_HAS_TLS (d)) + { + if (s->acme_alpn_required) + { + mhd_LOG_MSG (d, MHD_SC_DAEMON_HAS_TLS_DISABLED, + "ACME ALPN challenge requires TLS to be enabled"); + return MHD_SC_DAEMON_HAS_TLS_DISABLED; + } + } +# ifdef MHD_SUPPORT_HTTPS + else + { + mhd_StatusCodeInt res_i; + res_i = mhd_daemon_acme_certs_init (d); + return (enum MHD_StatusCode)res_i; + } +# endif /* MHD_SUPPORT_HTTPS */ +#else /* ! MHD_SUPPORT_ACME */ + (void)d; /* Unused */ +#endif /* ! MHD_SUPPORT_ACME */ + + return MHD_SC_OK; /* Success exit point */ +} + + +#ifdef MHD_SUPPORT_ACME + +/** + * Deinitialise ACME data. + * @param d the daemon object + */ +static MHD_FN_PAR_NONNULL_ (1) void +daemon_deinit_acme (struct MHD_Daemon *restrict d) +{ + mhd_assert (!mhd_D_HAS_MASTER (d)); + +# ifdef MHD_SUPPORT_HTTPS + if (mhd_D_HAS_TLS (d)) + mhd_daemon_acme_certs_deinit (d); +# else + (void)d; /* Unused */ +# endif +} + + +#else /* ! MHD_SUPPORT_ACME */ +# define daemon_deinit_acme(d) ((void) d) +#endif /* ! MHD_SUPPORT_ACME */ + #ifdef MHD_SUPPORT_KQUEUE /** @@ -4102,13 +4179,18 @@ daemon_start_internal (struct MHD_Daemon *restrict d, res = daemon_init_large_buf (d, s); if (MHD_SC_OK == res) { - res = daemon_start_threads (d); + res = daemon_init_acme (d, s); if (MHD_SC_OK == res) { - return MHD_SC_OK; - } + res = daemon_start_threads (d); + if (MHD_SC_OK == res) + { + return MHD_SC_OK; /* Success exit point */ + } - /* Below is a clean-up path */ + /* Below is a clean-up path */ + daemon_deinit_acme (d); + } daemon_deinit_large_buf (d); } daemon_deinit_threading_and_conn (d); @@ -4119,7 +4201,7 @@ daemon_start_internal (struct MHD_Daemon *restrict d, } daemon_deinit_net (d); mhd_assert (MHD_SC_OK != res); - return res; + return res; /* Failure exit point */ } @@ -4174,6 +4256,8 @@ MHD_daemon_destroy (struct MHD_Daemon *daemon) daemon_deinit_threading_and_conn (daemon); + daemon_deinit_acme (daemon); + daemon_deinit_large_buf (daemon); daemon_deinit_tls (daemon); diff --git a/src/mhd2/mhd_daemon.h b/src/mhd2/mhd_daemon.h @@ -80,6 +80,10 @@ #include "mempool_types.h" +#ifdef mhd_HAVE_TLS_ACME +# include "mhd_tls_certs_list.h" +#endif + #include "mhd_public_api.h" struct DaemonOptions; /* Forward declaration */ @@ -911,6 +915,28 @@ struct mhd_DaemonAuthDigestData #endif /* MHD_SUPPORT_AUTH_DIGEST */ +#ifdef MHD_SUPPORT_ACME + +/** + * Daemon's ACME data + */ +struct mhd_DaemonAcmeData +{ +# ifdef mhd_HAVE_TLS_ACME + /** + * Special certificates for ACME ALPN challenges + */ + struct mhd_TlsCertsList alpn; +# else + /** + * Unused + */ + int dummy; /* TODO: A placeholder. Remove after adding non-TLS data */ +# endif /* MHD_SUPPORT_HTTPS */ +}; +#endif /* MHD_SUPPORT_ACME */ + + #ifdef MHD_SUPPORT_THREADS /** @@ -1393,6 +1419,13 @@ struct MHD_Daemon struct mhd_DaemonAuthDigestData auth_dg; #endif /* MHD_SUPPORT_AUTH_DIGEST */ +#ifdef MHD_SUPPORT_ACME + /** + * Daemon's ACME data + */ + struct mhd_DaemonAcmeData acme; +#endif /* MHD_SUPPORT_ACME */ + #ifdef MHD_SUPPORT_HTTPS /** * The pointer to the daemon TLS data. diff --git a/src/mhd2/mhd_tls_acme_func.c b/src/mhd2/mhd_tls_acme_func.c @@ -0,0 +1,485 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2026 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/mhd_tls_acme_func.c + * @brief ALPN challenge functions for ACME protocol + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_base_types.h" + +#include "sys_malloc.h" +#include <string.h> + +#include "mhd_assert.h" +#include "mhd_assume.h" +#include "mhd_unreachable.h" +#include "mhd_predict.h" + +#include "mhd_locksrw.h" +#include "mhd_dlinked_list.h" +#include "mhd_tls_certs_list.h" +#include "mhd_daemon.h" + +#include "mhd_str.h" + +#include "daemon_logger.h" +#include "daemon_funcs.h" + +#include "mhd_tls_funcs.h" + +#include "mhd_tls_acme_func.h" + +#include "mhd_public_api.h" + +#ifdef mhd_HAVE_TLS_ACME + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_ +mhd_StatusCodeInt +mhd_daemon_acme_certs_init (struct MHD_Daemon *d) +{ + enum MHD_StatusCode res; + mhd_assert (!mhd_D_HAS_MASTER (d)); + mhd_assert (mhd_D_HAS_TLS (d)); + mhd_assert (mhd_DAEMON_STATE_STARTING == d->state); + +# ifndef HAVE_NULL_PTR_ALL_ZEROS + mhd_DLINKEDL_INIT_LIST (&(d->acme.alpn), certs); +# endif /* HAVE_NULL_PTR_ALL_ZEROS */ + + res = MHD_SC_OK; + if (!mhd_lockrw_init (&(d->acme.alpn.certs_lock))) + { + mhd_LOG_MSG (d, + MHD_SC_MUTEX_INIT_FAILURE, + "Failed to initialise RW-lock for ACME ALPN certificates"); + res = MHD_SC_MUTEX_INIT_FAILURE; + } + + return (mhd_StatusCodeInt)res; +} + + +/** + * Destroy ACME certificate item + * @param d the daemon object + * @param item the item to destroy + */ +static MHD_FN_PAR_NONNULL_ALL_ void +acme_cert_destroy (struct MHD_Daemon *d, + struct mhd_TlsCertEntry *item) +{ + mhd_tls_cred_destroy (d->tls, + item->cert_data); + free (item); +} + + +/** + * Destroy detached DL-linked list of ACME certificates + * @param d the daemon object + * @param first the first member of the DL-linked list, NULL is tolerated + */ +static MHD_FN_PAR_NONNULL_ (1) void +acme_certs_list_destroy (struct MHD_Daemon *d, + struct mhd_TlsCertEntry *first) +{ + struct mhd_TlsCertEntry *entry = first; + + while (NULL != entry) + { + struct mhd_TlsCertEntry *next = mhd_DLINKEDL_GET_NEXT (entry, certs); + acme_cert_destroy (d, + entry); + entry = next; + } +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_daemon_acme_certs_deinit (struct MHD_Daemon *d) +{ + mhd_assert (!mhd_D_HAS_MASTER (d)); + mhd_assert (mhd_D_HAS_TLS (d)); + mhd_assert ((mhd_DAEMON_STATE_STARTING == d->state) /* Failed start rewinding */ + || (mhd_DAEMON_STATE_STOPPING == d->state)); + + mhd_lockrw_destroy_chk (&(d->acme.alpn.certs_lock)); + + acme_certs_list_destroy (d, + mhd_DLINKEDL_GET_FIRST (&(d->acme.alpn), certs)); +} + + +#endif /* mhd_HAVE_TLS_ACME */ + + +MHD_EXTERN_ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (3) +MHD_FN_PAR_NONNULL_ (4) MHD_FN_PAR_CSTR_ (2) MHD_FN_PAR_CSTR_ (3) +MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_CSTR_ (5) enum MHD_StatusCode +MHD_daemon_acme_alpn_cert_add (struct MHD_Daemon *MHD_RESTRICT daemon, + const char *MHD_RESTRICT domain, + const char *MHD_RESTRICT cert, + const char *MHD_RESTRICT key, + const char *MHD_RESTRICT password) +{ +#ifdef mhd_HAVE_TLS_ACME + const size_t domain_len = (NULL != domain) ? strlen (domain) : 0u; + const size_t cert_len = strlen (cert); + const size_t key_len = strlen (key); + const size_t password_len = (NULL != password) ? strlen (password) : 0u; + size_t alloc_size; + struct mhd_TlsCertEntry *new_entry; + char *str_store; + enum MHD_StatusCode res; + + mhd_assert (!mhd_D_HAS_MASTER (daemon)); + if (mhd_DAEMON_STATE_STARTED > daemon->state) + return MHD_SC_TOO_EARLY; + if (mhd_DAEMON_STATE_STARTED < daemon->state) + return MHD_SC_TOO_LATE; + + if (!mhd_D_HAS_TLS (daemon)) + return MHD_SC_DAEMON_HAS_TLS_DISABLED; + + if ((0u == domain_len) && (NULL != domain)) + return MHD_SC_PARAM_EMPTY; + + if ((0u == cert_len) || (0u == key_len)) + return MHD_SC_TLS_CONF_BAD_CERT; + + alloc_size = sizeof(*new_entry); + alloc_size += domain_len + 1u; + if (mhd_COND_HARDLY_EVER (alloc_size <= domain_len)) + return MHD_SC_DAEMON_MEM_ALLOC_FAILURE; + + new_entry = + (struct mhd_TlsCertEntry *) + malloc (alloc_size); + if (NULL == new_entry) + return MHD_SC_DAEMON_MEM_ALLOC_FAILURE; + + str_store = (char *)(new_entry + 1u); + + if (NULL != domain) + { + mhd_str_to_lowercase_bin_n (domain_len + 1u, domain, str_store); + new_entry->domain.len = domain_len; + new_entry->domain.cstr = str_store; + } + else + { + new_entry->domain.len = 0u; + new_entry->domain.cstr = NULL; + } + + switch (mhd_tls_cred_create (daemon->tls, + &(new_entry->cert_data), + cert_len, + cert, + key_len, + key, + password_len, + password)) + { + case mhd_TLS_CRED_CREATE_OK: + res = MHD_SC_OK; + break; + + case mhd_TLS_CRED_CREATE_BAD_CRED_DATA: + res = MHD_SC_TLS_CONF_BAD_CERT; + break; + + case mhd_TLS_CRED_CREATE_ALLOC_FAILED: + res = MHD_SC_DAEMON_MEM_ALLOC_FAILURE; + break; + + case mhd_TLS_CRED_CREATE_UNSUPPORTED: + res = MHD_SC_TLS_BACKEND_OPERATION_UNSUPPORTED; + break; + + case mhd_TLS_CRED_CREATE_FAILED: + res = MHD_SC_TLS_BACKEND_ERROR; + break; + + default: + mhd_UNREACHABLE (); + res = MHD_SC_INTERNAL_ERROR; + break; + } + + if (MHD_SC_OK == res) + { + mhd_DLINKEDL_INIT_LINKS (new_entry, certs); + + if (mhd_lockrw_w_lock (&(daemon->acme.alpn.certs_lock))) + { + struct mhd_TlsCertEntry *old_entry; + + if (0u != domain_len) + { + for (old_entry = mhd_DLINKEDL_GET_FIRST (&(daemon->acme.alpn), certs); + NULL != old_entry; + old_entry = mhd_DLINKEDL_GET_NEXT (old_entry, certs)) + { + if ((domain_len == old_entry->domain.len) + && (0 == memcmp (new_entry->domain.cstr, + old_entry->domain.cstr, + domain_len))) + break; + } + } + else + { + for (old_entry = mhd_DLINKEDL_GET_FIRST (&(daemon->acme.alpn), certs); + NULL != old_entry; + old_entry = mhd_DLINKEDL_GET_NEXT (old_entry, certs)) + { + if (0u == old_entry->domain.len) + break; + } + } + + if (NULL != old_entry) + mhd_DLINKEDL_DEL (&(daemon->acme.alpn), old_entry, certs); + + mhd_DLINKEDL_INS_LAST (&(daemon->acme.alpn), new_entry, certs); + + mhd_lockrw_w_unlock_chk (&(daemon->acme.alpn.certs_lock)); + + /* Destroy content of removed item */ + if (NULL != old_entry) + acme_cert_destroy (daemon, + old_entry); + + return MHD_SC_OK; + } + else + res = MHD_SC_MUTEX_LOCK_FAILED; + + mhd_tls_cred_destroy (daemon->tls, + new_entry->cert_data); + } + free (new_entry); + + mhd_assert (MHD_SC_OK != res); + return res; +#else /* ! mhd_HAVE_TLS_ACME */ + (void)daemon; + (void)domain; + (void)cert; + (void)key; + (void)password; + + return MHD_SC_TLS_BACKEND_OPERATION_UNSUPPORTED; +#endif /* ! mhd_HAVE_TLS_ACME */ +} + + +MHD_EXTERN_ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (2) enum MHD_StatusCode +MHD_daemon_acme_alpn_cert_del (struct MHD_Daemon *MHD_RESTRICT daemon, + const char *domain) +{ +#ifdef mhd_HAVE_TLS_ACME + struct mhd_TlsCertEntry *check; + size_t domain_len; + + mhd_assert (!mhd_D_HAS_MASTER (daemon)); + + if (mhd_DAEMON_STATE_STARTED > daemon->state) + return MHD_SC_TOO_EARLY; + if (mhd_DAEMON_STATE_STARTED < daemon->state) + return MHD_SC_TOO_LATE; + + if (!mhd_D_HAS_TLS (daemon)) + return MHD_SC_DAEMON_HAS_TLS_DISABLED; + + if (NULL == domain) + { + struct mhd_TlsCertEntry *first; + + if (!mhd_lockrw_w_lock (&(daemon->acme.alpn.certs_lock))) + return MHD_SC_MUTEX_LOCK_FAILED; + + first = mhd_DLINKEDL_GET_FIRST (&(daemon->acme.alpn), certs); + /* Detach the list from the daemon */ + mhd_DLINKEDL_INIT_LIST (&(daemon->acme.alpn), certs); + /* Early unlock */ + mhd_lockrw_w_unlock_chk (&(daemon->acme.alpn.certs_lock)); + /* Destroy content of detached list */ + acme_certs_list_destroy (daemon, + first); + return MHD_SC_OK; + } + + domain_len = strlen (domain); + if (0u == domain_len) + return MHD_SC_PARAM_EMPTY; + + if (!mhd_lockrw_w_lock (&(daemon->acme.alpn.certs_lock))) + return MHD_SC_MUTEX_LOCK_FAILED; + + for (check = mhd_DLINKEDL_GET_FIRST (&(daemon->acme.alpn), certs); + NULL != check; + check = mhd_DLINKEDL_GET_NEXT (check, certs)) + { + if ((domain_len == check->domain.len) + && mhd_str_equal_lowercase_bin_n (domain, + check->domain.cstr, + domain_len)) + { + mhd_DLINKEDL_DEL (&(daemon->acme.alpn), check, certs); + break; + } + } + + /* Early unlock */ + mhd_lockrw_w_unlock_chk (&(daemon->acme.alpn.certs_lock)); + + if (NULL == check) + return MHD_SC_ITEM_NOT_FOUND; + + /* Destroy content of removed item */ + acme_cert_destroy (daemon, + check); + + return MHD_SC_OK; +#else /* ! mhd_HAVE_TLS_ACME */ + (void)daemon; + (void)domain; + return MHD_SC_TLS_BACKEND_OPERATION_UNSUPPORTED; +#endif /* ! mhd_HAVE_TLS_ACME */ +} + + +#ifdef mhd_HAVE_TLS_ACME +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_RETURNS_NONNULL_ +struct mhd_TlsCertsList * +mhd_daemon_get_acme_certs (struct MHD_Daemon *d) +{ + struct MHD_Daemon *const master = mhd_daemon_get_master_daemon (d); + mhd_assert ((mhd_DAEMON_STATE_STARTING == master->state) + || (mhd_D_HAS_TLS (d))); + mhd_assert ((mhd_DAEMON_STATE_STARTING == master->state) + || (mhd_D_HAS_TLS (master))); + + return &(master->acme.alpn); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_daemon_has_acme_certs (struct mhd_TlsCertsList *restrict acme_certs) +{ +# ifdef MHD_USE_THREAD_SYNC_EXTRA_CAUTION + bool ret; + + mhd_lockrw_r_lock_chk (&(acme_certs->certs_lock)); + ret = (NULL != mhd_DLINKEDL_GET_LAST (acme_certs, certs)); + mhd_lockrw_r_unlock_chk (&(acme_certs->certs_lock)); + + return ret; +# else /* ! MHD_USE_THREAD_SYNC_EXTRA_CAUTION */ + /* Fast check without locking. + Hypothetically on platforms with non-atomic pointer updates there is + a chance that NULL will be matched when updating specific non-NULL + pointer to another non-NULL pointer, but this is very unlikely and + the worst possible outcome it skipping a single ACME ALPN challenge + when ACME certificates are being added or removed at precisely the same + time. */ + return (NULL != mhd_DLINKEDL_GET_LAST (acme_certs, certs)); +# endif /* ! MHD_USE_THREAD_SYNC_EXTRA_CAUTION */ +} + + +MHD_INTERNAL MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (3, 2) const union mhd_TlsCredDataPtr * +mhd_daemon_acme_cert_get_r_lock (struct mhd_TlsCertsList *restrict acme_certs, + size_t domain_len, + const char *restrict domain) +{ + const struct mhd_TlsCertEntry *check; + const struct mhd_TlsCertEntry *any; + + mhd_ASSUME (0u != domain_len); + + any = NULL; + + mhd_lockrw_r_lock_chk (&(acme_certs->certs_lock)); + + for (check = mhd_DLINKEDL_GET_FIRST (acme_certs, certs); + NULL != check; + check = mhd_DLINKEDL_GET_NEXT (check, certs)) + { + if ((domain_len == check->domain.len) + && mhd_str_equal_lowercase_bin_n (domain, + check->domain.cstr, + domain_len)) + break; + else if (NULL == check->domain.cstr) + { + mhd_assert (NULL == any); + any = check; + } + + } + + if (NULL == check) + check = any; + + if (NULL == check) + mhd_lockrw_r_unlock_chk (&(acme_certs->certs_lock)); + + return (NULL == check) ? NULL : &(check->cert_data); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_daemon_acme_cert_r_unlock (struct mhd_TlsCertsList *restrict acme_certs) +{ +# ifdef MHD_SUPPORT_THREADS + mhd_lockrw_r_unlock_chk (&(acme_certs->certs_lock)); +# else /* ! MHD_SUPPORT_THREADS */ + (void)acme_certs; +# endif /* ! MHD_SUPPORT_THREADS */ +} + + +#endif /* mhd_HAVE_TLS_ACME */ diff --git a/src/mhd2/mhd_tls_acme_func.h b/src/mhd2/mhd_tls_acme_func.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2026 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/mhd_tls_acme_func.h + * @brief Internal header for ALPN challenge functions for ACME protocol + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_TLS_ACME_FUNC_H +#define MHD_TLS_ACME_FUNC_H 1 + +#include "mhd_sys_options.h" + +#ifndef MHD_SUPPORT_HTTPS +# error This file can be used only with TLS support enabled +#endif +#ifndef MHD_SUPPORT_ACME +# error This file can be used only with ACME support enabled +#endif + +#include "sys_bool_type.h" +#include "sys_sizet_type.h" + +#include "mhd_tls_choice.h" + +#include "mhd_status_code_int.h" + +struct MHD_Daemon; /* Forward declaration */ +struct mhd_TlsCertsList; /* Forward declaration */ + +#ifdef mhd_HAVE_TLS_ACME +/** + * Initialise daemon ACME certificates data. + * Must be called only for daemons with TLS/HTTPS support enabled + * @param d the daemon to initialise + * @return #MHD_SC_OK on success, + * error code otherwise + */ +MHD_INTERNAL mhd_StatusCodeInt +mhd_daemon_acme_certs_init (struct MHD_Daemon *d) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_; + +/** + * De-initialise daemon ACME certificates data + * + * Must be called only if #mhd_daemon_acme_certs_init() has been called + * earlier. + * @param d the daemon to de-initialise + */ +MHD_INTERNAL void +mhd_daemon_acme_certs_deinit (struct MHD_Daemon *d) +MHD_FN_PAR_NONNULL_ALL_; + +#else /* ! mhd_HAVE_TLS_ACME */ +# define mhd_daemon_acme_certs_init(d) ((void)(d), 0) +# define mhd_daemon_acme_certs_deinit(d) ((void)d) +#endif /* ! mhd_HAVE_TLS_ACME */ + +/** + * Get the list of ACME certificates for the daemon + * @param d the daemon to get the list of ACME certificates for + * @return pointer to the list of ACME certificates (valid until + * the daemon @p d is de-initialised) + */ +MHD_INTERNAL struct mhd_TlsCertsList * +mhd_daemon_get_acme_certs (struct MHD_Daemon *d) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_RETURNS_NONNULL_; + +/** + * Quick check whether the daemon has any ACME certificates + * + * @param acme_certs the list of ACME certificates to check + * @return 'true' if the list @p acme_certs has any ACME certificate, + * 'false' otherwise + */ +MHD_INTERNAL bool +mhd_daemon_has_acme_certs (struct mhd_TlsCertsList *restrict acme_certs) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Get ACME certificate for specified DNS domain name. + * + * When function returns non-NULL pointer the data in the daemon remains + * locked. + * Function #mhd_daemon_acme_cert_r_unlock() must be called when certificate + * data is processed. + * + * @param acme_certs the list of ACME certificates to search in + * @param domain_len the number of characters in @p domain, must not be zero + * @param domain the domain name indicated by the client, does not need to be + * zero-terminated + * @return NULL if no ACME certificate was assigned to the requested @p domain, + * pointer to ACME certificate data (valid until unlock function + * #mhd_daemon_acme_cert_r_unlock() is called) + */ +MHD_INTERNAL const union mhd_TlsCredDataPtr * +mhd_daemon_acme_cert_get_r_lock (struct mhd_TlsCertsList *restrict acme_certs, + size_t domain_len, + const char *restrict domain) +MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (3, 2); + +/** + * This function must be called after each successful (return is non-NULL) call + * of #mhd_daemon_acme_cert_get_r_lock() function. + * + * @param acme_certs the list of ACME certificates to search in + */ +MHD_INTERNAL void +mhd_daemon_acme_cert_r_unlock (struct mhd_TlsCertsList *restrict acme_certs) +MHD_FN_PAR_NONNULL_ALL_; + + +#endif /* ! MHD_TLS_ACME_FUNC_H */ diff --git a/src/mhd2/mhd_tls_certs_list.h b/src/mhd2/mhd_tls_certs_list.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2026 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/mhd_tls_certs_list.h + * @brief Structures for certificate list management + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_TLS_CERTS_LIST_H +#define MHD_TLS_CERTS_LIST_H 1 + +#include "mhd_sys_options.h" + +#ifndef MHD_SUPPORT_HTTPS +# error This header should be used only if HTTPS is enabled +#endif + +#include "mhd_dlinked_list.h" + +#include "mhd_str_types.h" + +#include "mhd_tls_choice.h" + +#include "mhd_locksrw.h" + +#include "mhd_tls_cred_ptr.h" + +struct mhd_TlsCertEntry; /* Forward declaration */ + +/** + * Inter-list links for the @a mhd_TlsCertEntry structure + */ +mhd_DLINKEDL_LINKS_DEF (mhd_TlsCertEntry); + +/** + * The certificate entry for a specific domain name + */ +struct mhd_TlsCertEntry +{ + /** + * The name of the domain. + * NULL if it matches everything. + */ + struct MHD_StringNullable domain; + /** + * The certificate data for the @a domain + */ + union mhd_TlsCredDataPtr cert_data; + /** + * Links to other entries in the @a certs list + */ + mhd_DLNKDL_LINKS (mhd_TlsCertEntry, certs); +}; + +/** + * The list of @a mhd_TlsCertEntry entries + */ +mhd_DLINKEDL_LIST_DEF (mhd_TlsCertEntry); + +/** + * List of certificates + */ +struct mhd_TlsCertsList +{ +#ifdef MHD_SUPPORT_THREADS + /** + * RW-lock protecting access to the @a certs certificate list + */ + mhd_lockrw certs_lock; +#endif /* MHD_SUPPORT_THREADS */ + /** + * The certificates list + */ + mhd_DLNKDL_LIST (mhd_TlsCertEntry, certs); +}; + +#endif /* ! MHD_TLS_CERTS_LIST_H */ diff --git a/src/mhd2/mhd_tls_choice.h b/src/mhd2/mhd_tls_choice.h @@ -263,6 +263,11 @@ /* ** Forward declarations ** */ /** + * The credentials data + */ +union mhd_TlsCredDataPtr; /* Forward declaration */ + +/** * The structure with daemon-specific TLS data */ struct mhd_TlsDaemonData; /* Forward declaration */ diff --git a/src/mhd2/mhd_tls_cred_ptr.h b/src/mhd2/mhd_tls_cred_ptr.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2026 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/mhd2/mhd_tls_cred_ptr.h + * @brief The definition of internal TLS-related credential pointers + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_TLS_CRED_PTR_H +#define MHD_TLS_CRED_PTR_H 1 + +#include "mhd_sys_options.h" + +#ifndef MHD_SUPPORT_HTTPS +# error This header should be used only if HTTPS is enabled +#endif + +#ifdef MHD_SUPPORT_GNUTLS +struct mhd_TlsGnuCredData; /* Forward declaration */ +#endif /* MHD_SUPPORT_GNUTLS */ +#ifdef MHD_SUPPORT_OPENSSL +struct mhd_TlsOpenCredData; /* Forward declaration */ +#endif /* MHD_SUPPORT_OPENSSL */ +#ifdef MHD_SUPPORT_MBEDTLS +struct mhd_TlsMbedCredData; /* Forward declaration */ +#endif /* MHD_SUPPORT_MBEDTLS */ + +/** + * Pointers to backend-specific TLS credentials data. + * + * Only the member for the active daemon's TLS backend is valid. + */ +union mhd_TlsCredDataPtr +{ +#ifdef MHD_SUPPORT_GNUTLS + /** + * Pointer to GnuTLS credentials data + */ + struct mhd_TlsGnuCredData *gnu; +#endif /* MHD_SUPPORT_GNUTLS */ +#ifdef MHD_SUPPORT_OPENSSL + /** + * Pointer to OpenSSL credentials data + */ + struct mhd_TlsOpenCredData *open; +#endif /* MHD_SUPPORT_OPENSSL */ +#ifdef MHD_SUPPORT_MBEDTLS + /** + * Pointer to MbedTLS credentials data + */ + struct mhd_TlsMbedCredData *mbed; +#endif /* MHD_SUPPORT_MBEDTLS */ +}; + +#endif /* ! MHD_TLS_CRED_PTR_H */ diff --git a/src/mhd2/mhd_tls_enums.h b/src/mhd2/mhd_tls_enums.h @@ -111,4 +111,36 @@ enum MHD_FIXED_ENUM_ mhd_TlsAlpnProt mhd_TLS_ALPN_PROT_ERROR }; +/** + * Result of TLS credentials creation + */ +enum MHD_FIXED_ENUM_ mhd_TlsCredCreateResult +{ + /** + * Credentials created successfully + */ + mhd_TLS_CRED_CREATE_OK = 0 + , + /** + * Credentials creation failed due to invalid input data (bad certificate, + * key etc.) + */ + mhd_TLS_CRED_CREATE_BAD_CRED_DATA + , + /** + * Credentials creation failed due to memory/resource allocation failure + */ + mhd_TLS_CRED_CREATE_ALLOC_FAILED + , + /** + * Credentials creation unsupported by the selected TLS backend + */ + mhd_TLS_CRED_CREATE_UNSUPPORTED + , + /** + * Credentials creation failed due to backend-specific error + */ + mhd_TLS_CRED_CREATE_FAILED +}; + #endif /* ! MHD_TLS_ENUMS_H */ diff --git a/src/mhd2/mhd_tls_funcs.h b/src/mhd2/mhd_tls_funcs.h @@ -93,6 +93,17 @@ #define mhd_tls_is_edge_trigg_supported(s) \ mhd_TLS_FUNC (_is_edge_trigg_supported)((s)) +#ifdef mhd_HAVE_TLS_ACME +/** + * Check whether selected backend supports ACME ALPN challenge protocol + * @param s the daemon settings + * @return 'true' if the backend supports ACME ALPN challenge protocol, + * 'false' otherwise + */ +# define mhd_tls_is_acme_alpn_supported(s) \ + mhd_TLS_FUNC (_is_acme_alpn_supported)((s)) +#endif /* mhd_HAVE_TLS_ACME */ + /** * Allocate and initialise daemon TLS parameters * @param d the daemon handle @@ -124,6 +135,45 @@ mhd_TLS_FUNC (_thread_cleanup)((d_tls)) +/* ** Credentials creation / destruction ** */ + +/** + * Create TLS credentials from the provided certificate and key data + * + * Created credentials must be destroyed with mhd_tls_cred_destroy() + * function before de-initialisation of the daemon's TLS settings. + * + * @param d_tls the pointer to the daemon's TLS settings + * @param pp_c the pointer to the pointer to the credentials data structure + * to be allocated and filled, set to NULL on failure + * @param cert_l the length of the @p cert buffer, not including the + * zero-termination byte, + * must not be zero + * @param cert the certificate data in PEM format, zero-terminated + * @param key_l the length of the @p key buffer, not including the + * zero-termination byte, + * must not be zero + * @param key the private key data in PEM format, zero-terminated + * @param p_l the length of the @p pass buffer, not including the + * zero-termination byte, + * must be zero if the @p pass is NULL + * @param pass the password for the private key, zero-terminated, + * may be NULL if the private key is not password-protected + * @return #mhd_TLS_CRED_CREATE_OK on success, + * other enum mhd_TlsCredCreateResult values on failure + */ +#define mhd_tls_cred_create(d_tls, pp_c, cert_l, cert, key_l, key, p_l, pass) \ + mhd_TLS_FUNC (_cred_create)((d_tls),(pp_c),(cert_l),(cert), \ + (key_l),(key),(p_l),(pass)) + +/** + * Destroy TLS credentials and free allocated memory + * @param d_tls the daemon TLS settings + * @param cred the pointer to the credentials data structure to be destroyed + */ +#define mhd_tls_cred_destroy(d_tls, cred) \ + mhd_TLS_FUNC (_cred_destroy)((d_tls),(cred)) + /* ** Connection initialisation / de-initialisation ** */ /** @@ -263,4 +313,16 @@ #define mhd_tls_conn_get_alpn_prot(c_tls) \ mhd_TLS_FUNC (_conn_get_alpn_prot)((c_tls)) +#ifdef mhd_HAVE_TLS_ACME +/** + * Check whether the connection is ACME ALPN challenge connection + * @param c_tls the connection TLS handle + * @return 'true' if the connection is ACME ALPN challenge connection, + * 'false' otherwise + */ +# define mhd_tls_conn_is_acme(c_tls) \ + mhd_TLS_FUNC (_conn_is_acme)((c_tls)) +#endif /* mhd_HAVE_TLS_ACME */ + + #endif /* ! MHD_TLS_FUNCS_H */ diff --git a/src/mhd2/mhd_tls_internal.c b/src/mhd2/mhd_tls_internal.c @@ -52,6 +52,9 @@ #include "mhd_assert.h" #include "mhd_unreachable.h" +#ifdef mhd_HAVE_TLS_ACME +# include "mhd_tls_acme_func.h" +#endif #include "mhd_tls_internal.h" MHD_INTERNAL @@ -88,3 +91,259 @@ mhd_tls_alpn_decode_n (size_t alpn_id_size, return mhd_TLS_ALPN_PROT_ERROR; } + + +#ifdef mhd_HAVE_TLS_ACME +static const unsigned char expected_acme_alpn[] = { + 0x00, mhd_ALPN_ACME_LEN + 1u, /* ProtocolNameList length */ + mhd_ALPN_ACME_LEN, /* ProtocolName length */ + 'a', 'c', 'm', 'e', '-', 't', 'l', 's', '/', '1' +}; + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (2, 1) bool +mhd_tls_acme_check_ext_alpn (size_t ext_alpn_data_size, + const unsigned char *ext_alpn_data) +{ + if (sizeof(expected_acme_alpn) != ext_alpn_data_size) + return false; + return (0 == memcmp (ext_alpn_data, + expected_acme_alpn, + sizeof(expected_acme_alpn))); +} + + +/* Sanity check */ +# if SIZEOF_SIZE_T < 2 +# error Unsupported size_t size, must be at least 2 bytes +# endif + +/** + * Read value as big-endian 16-bit unsigned integer + * @param data the data to read, must be at least 2 bytes long + * @return the value + */ +mhd_static_inline size_t +get_uint16_be (const unsigned char data[MHD_FN_PAR_FIX_ARR_SIZE_ (2)]) +{ + return (size_t)(((unsigned int)data[0]) << 8u) | data[1]; +} + + +MHD_INTERNAL MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2, 1) const union mhd_TlsCredDataPtr * +mhd_tls_acme_check_ext_sni (size_t ext_sni_data_size, + const unsigned char *ext_sni_data, + struct mhd_TlsCertsList *acme_certs) +{ + size_t sni_list_size; + size_t name_size; + + /* Complete ServerNameList must be exactly: + + uint16 list_size + + uint8 name_type (0x00 for host_name) + + uint16 name_size + + uint8[] name (not zero-terminated) */ + + if (ext_sni_data_size < 2u + 1u + 2u + 1u) /* list_size + name_type + name_size, 1 for at least one byte of name */ + return NULL; /* Too short for ServerNameList */ + + sni_list_size = get_uint16_be (ext_sni_data); + if (ext_sni_data_size - 2u != sni_list_size) + return NULL; /* List size does not match the actual data size */ + + if (ext_sni_data[2] != 0x00) + return NULL; /* Not a host_name type */ + + name_size = get_uint16_be (ext_sni_data + 3); + if (sni_list_size - 3u != name_size) + return NULL; /* Not exactly one complete server name */ + + return mhd_daemon_acme_cert_get_r_lock (acme_certs, + name_size, + (const char *)(ext_sni_data + 5u)); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (3, 2) +MHD_FN_PAR_INOUT_ (4) bool +mhd_tls_acme_check_ext (unsigned int ext_id, + size_t ext_data_size, + const unsigned char *ext_data, + struct mhd_TlsClientHelloAcmeCheckData *restrict check) +{ + if (check->is_rejected) + return false; /* Already rejected, no need to check further */ + + switch (ext_id) + { + case 0u: /* server_name extension */ + check->is_rejected = (NULL != check->acme_cred); + + if (!check->is_rejected) + { + check->acme_cred = mhd_tls_acme_check_ext_sni (ext_data_size, + ext_data, + check->certs_list); + check->is_rejected = (NULL == check->acme_cred); + } + break; + + case 16u: /* ALPN extension */ + check->alpn_match = + (!check->alpn_match) /* Only one ALPN extension is allowed */ + && mhd_tls_acme_check_ext_alpn (ext_data_size, + ext_data); + check->is_rejected = !check->alpn_match; + break; + + default: /* Other extensions are ignored for ACME check */ + break; + } + + if ((NULL != check->acme_cred) + && check->is_rejected) + { + mhd_daemon_acme_cert_r_unlock (check->certs_list); + check->acme_cred = NULL; + } + + /* If rejected: no need to check that for other extensions */ + return !check->is_rejected; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1) +const union mhd_TlsCredDataPtr * +mhd_tls_acme_check_ext_finish ( + struct mhd_TlsClientHelloAcmeCheckData *restrict check, + bool parse_succeeded) +{ + bool is_acme; + + is_acme = parse_succeeded + && !check->is_rejected + && (NULL != check->acme_cred) + && check->alpn_match; + + if (!is_acme && (NULL != check->acme_cred)) + { + mhd_daemon_acme_cert_r_unlock (check->certs_list); + check->acme_cred = NULL; + } + + mhd_assert (is_acme || (NULL == check->acme_cred)); + mhd_assert (!is_acme || (NULL != check->acme_cred)); + + return check->acme_cred; +} + + +# ifdef mhd_HAVE_TLS_MHD_CLIENTHELLO_BODY_PARSE + +MHD_INTERNAL MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (2, 1) const union mhd_TlsCredDataPtr * +mhd_tls_acme_check_clienthello_body ( + size_t body_size, + const unsigned char *restrict body, + struct mhd_TlsCertsList *restrict acme_certs) +{ + static const size_t min_ext_block_size = + (2u + 2u + sizeof(expected_acme_alpn)) /* ALPN extension size */ + + (2u + 2u + (2u + 1u + 2u + 1u)); /* Minimal SNI size */ + size_t pos; + size_t elements_size; + struct mhd_TlsClientHelloAcmeCheckData check; + + /* Complete ClientHello body must be exactly: + + uint16 legacy_version + + uint8[32] random + + uint8 legacy_session_id_length + + uint8[] legacy_session_id + + uint16 cipher_suites_length + + uint16[] cipher_suites + + uint8 compression_methods_length + + uint8[] compression_methods + + uint16 extensions_length + + uint8[] extensions */ + + pos = 0u; + if (2u + 32u + 1u + 2u + 1u + 2u + + min_ext_block_size > body_size) + return NULL; /* Too short for ClientHello body with ACME ALPN challenge */ + if (0x03u != body[pos]) + return NULL; /* Legacy version is not TLS 1.x */ + if (0x01u > body[pos + 1u]) + return NULL; /* Legacy version is not TLS 1.0 or higher */ + + pos += 2u; /* legacy_version */ + + pos += 32u; /* random */ + + elements_size = (size_t)body[pos]; /* legacy_session_id_length */ + pos += 1u; /* legacy_session_id_length */ + if (elements_size > body_size - pos) + return NULL; /* Too short for legacy_session_id */ + pos += elements_size; /* legacy_session_id */ + + if (2u > body_size - pos) + return NULL; /* Too short for cipher_suites_length */ + elements_size = get_uint16_be (body + pos); /* cipher_suites_length */ + pos += 2u; /* cipher_suites_length */ + if (elements_size > body_size - pos) + return NULL; /* Too short for cipher_suites */ + pos += elements_size; /* cipher_suites */ + + if (1u > body_size - pos) + return NULL; /* Too short for compression_methods_length */ + elements_size = (size_t)body[pos]; /* compression_methods_length */ + pos += 1u; /* compression_methods_length */ + if (elements_size > body_size - pos) + return NULL; /* Too short for compression_methods */ + pos += elements_size; /* compression_methods */ + + if (2u > body_size - pos) + return NULL; /* Too short for extensions_length */ + elements_size = get_uint16_be (body + pos); /* extensions_length */ + pos += 2u; /* extensions_length */ + if (min_ext_block_size > elements_size) + return NULL; /* No space for minimal ACME TLS-ALPN-01 challenge extensions */ + if (elements_size != body_size - pos) + return NULL; /* Too short for extensions or extra bytes at the end */ + + mhd_tls_acme_check_ext_init (&check, + acme_certs); + + /* Extensions body is a sequence of extensions. + Each extension is: + + uint16 ext_id + + uint16 ext_data_size + + uint8[] ext_data */ + do + { + unsigned int ext_id; + size_t ext_data_size; + ext_id = (unsigned int)get_uint16_be (body + pos); + pos += 2u; /* ext_id */ + ext_data_size = get_uint16_be (body + pos); + pos += 2u; /* ext_data_size */ + if (ext_data_size > body_size - pos) + { + --pos; /* Force "unprocess" to indicate a broken extension */ + break; /* Broken extension, larger than data available */ + } + if (!mhd_tls_acme_check_ext (ext_id, + ext_data_size, + body + pos, + &check)) + break; /* Rejected, no need to check further extensions */ + pos += ext_data_size; /* ext_data */ + } while (body_size - (2u + 2u) >= pos); /* 2 bytes for ext_id and 2 bytes for ext_data_size */ + + return mhd_tls_acme_check_ext_finish (&check, + (body_size == pos)); /* No broken extensions */ +} + + +# endif /* mhd_HAVE_TLS_MHD_CLIENTHELLO_BODY_PARSE */ + +#endif /* mhd_HAVE_TLS_ACME */ diff --git a/src/mhd2/mhd_tls_internal.h b/src/mhd2/mhd_tls_internal.h @@ -51,6 +51,10 @@ # error This header should be used only if HTTPS is enabled #endif +#ifdef mhd_HAVE_TLS_ACME +# include "sys_bool_type.h" +# include "sys_null_macro.h" +#endif #include "sys_sizet_type.h" #include "mhd_str_macros.h" @@ -97,6 +101,17 @@ */ #define mhd_ALPN_H3_LEN mhd_SSTR_LEN (mhd_ALPN_H3) +#ifdef mhd_HAVE_TLS_ACME +/** + * Registered ALPN value for ACME TLS-ALPN-01 challenge + */ +# define mhd_ALPN_ACME "acme-tls/1" +/** + * The length of #mhd_ALPN_ACME + */ +# define mhd_ALPN_ACME_LEN mhd_SSTR_LEN (mhd_ALPN_ACME) +#endif /* mhd_HAVE_TLS_ACME */ + /** * Decode provided ALPN identifier * @param alpn_id_size the size in bytes of the @a alpn_id @@ -113,4 +128,179 @@ mhd_tls_alpn_decode_n (size_t alpn_id_size, MHD_FN_PAR_IN_SIZE_ (2, 1); +#ifdef mhd_HAVE_TLS_ACME + +# if defined(_MSC_FULL_VER) +# pragma warning(push) +/* Disable C4505 "unreferenced local function has been removed" */ +# pragma warning(disable:4505) +# endif /* _MSC_FULL_VER */ + +struct mhd_TlsCertsList; /* Forward declaration */ +union mhd_TlsCredDataPtr; /* Forward declaration */ + +/** + * State of an incremental ACME TLS-ALPN-01 ClientHello check. + * + * Initialise with #mhd_tls_acme_check_ext_init() before checking the first + * extension, update with #mhd_tls_acme_check_ext() for each extension, and + * finalise with #mhd_tls_acme_check_ext_finish(). + */ +struct mhd_TlsClientHelloAcmeCheckData +{ + /** + * 'true' if the ClientHello cannot be an ACME TLS-ALPN-01 challenge + */ + bool is_rejected; + /** + * 'true' if the ClientHello has an ALPN extension containing only + * #mhd_ALPN_ACME + */ + bool alpn_match; + + /** + * The credentials selected by the SNI extension, or NULL if none are + * selected. + * @warning While this pointer is non-NULL, the ACME certificate list remains + * read-locked. + */ + const union mhd_TlsCredDataPtr *acme_cred; + + /** + * The list of ACME certificates to check against + */ + struct mhd_TlsCertsList *certs_list; +}; + +/** + * Check whether the provided ALPN extension data contains only the + * ACME TLS-ALPN-01 protocol, as required by RFC 8737, section 3. + * + * @param ext_alpn_data_size the size in bytes of the @p ext_alpn_data + * @param ext_alpn_data the complete ALPN extension data + * @return 'true' if the provided ALPN extension data is the ACME TLS-ALPN-01 + * challenge, + * 'false' otherwise + */ +MHD_INTERNAL bool +mhd_tls_acme_check_ext_alpn (size_t ext_alpn_data_size, + const unsigned char *ext_alpn_data) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (2, 1); + +/** + * Check whether the provided SNI extension data has exactly one host name, + * which is in the list of the expected host names for the ACME TLS-ALPN-01 + * challenge. + * + * @param ext_sni_data_size the size in bytes of the @p ext_sni_data + * @param ext_sni_data the complete SNI extension data + * @param acme_certs the list of ACME certificates to check against + * @return NULL if the provided SNI extension data does not match, + * a non-NULL pointer to the matching ACME credentials otherwise + * @warning If a non-NULL pointer is returned, the daemon's ACME certificate + * list remains read-locked and must be unlocked with + * #mhd_daemon_acme_cert_r_unlock(). + */ +MHD_INTERNAL const union mhd_TlsCredDataPtr * +mhd_tls_acme_check_ext_sni (size_t ext_sni_data_size, + const unsigned char *ext_sni_data, + struct mhd_TlsCertsList *acme_certs) +MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (2, 1); + +/** + * Initialise the state of an ACME TLS-ALPN-01 ClientHello check. + * + * @param[out] check the state structure to initialise + * @param certs_list the list of ACME certificates + */ +mhd_static_inline MHD_FN_PAR_OUT_ (1) void +mhd_tls_acme_check_ext_init ( + struct mhd_TlsClientHelloAcmeCheckData *restrict check, + struct mhd_TlsCertsList *restrict certs_list) +{ + check->is_rejected = false; + check->alpn_match = false; + check->acme_cred = (const union mhd_TlsCredDataPtr *)NULL; + check->certs_list = certs_list; +} + + +/** + * Process one TLS ClientHello extension while checking for an + * ACME TLS-ALPN-01 challenge. + * + * Call this function in extension order for every extension in the ClientHello + * message. + * + * @param ext_id the extension identifier + * @param ext_data_size the size in bytes of the @p ext_data + * @param ext_data the complete extension data + * @param[in,out] check the structure to keep the state of the check + * @return 'true' if the extension matches the required form and checking + * should continue, + * 'false' if the ClientHello cannot be an ACME TLS-ALPN-01 challenge + */ +MHD_INTERNAL bool +mhd_tls_acme_check_ext (unsigned int ext_id, + size_t ext_data_size, + const unsigned char *ext_data, + struct mhd_TlsClientHelloAcmeCheckData *restrict check) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (3, 2) MHD_FN_PAR_INOUT_ (4); + +/** + * Finalise the check of the TLS ClientHello extensions for + * the ACME TLS-ALPN-01 challenge. + * + * This function must be called after all extensions in the ClientHello message + * have been processed or when checking stopped after a rejection. + * + * @param check the structure that keeps the state of the check + * @param parse_succeeded set to 'true' if the ClientHello was successfully + * parsed, + * set to 'false' if parsing of the ClientHello failed + * @return non-NULL if the ClientHello message is recognised as a TLS-ALPN-01 + * challenge and the ACME credentials are selected, + * NULL otherwise + * @warning If a non-NULL pointer is returned, the daemon's ACME certificate + * list remains read-locked and must be unlocked with + * #mhd_daemon_acme_cert_r_unlock(). + */ +MHD_INTERNAL const union mhd_TlsCredDataPtr * +mhd_tls_acme_check_ext_finish ( + struct mhd_TlsClientHelloAcmeCheckData *restrict check, + bool parse_succeeded) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); + + +# ifdef mhd_HAVE_TLS_MHD_CLIENTHELLO_BODY_PARSE +/** + * Parse the entire TLS ClientHello message body and check whether it is the + * ACME TLS-ALPN-01 challenge. + * + * @param body_size the size in bytes of the @p body + * @param body the TLS ClientHello message body, not including the handshake + * header + * @param acme_certs the list of ACME certificates to check against + * @return non-NULL if the ClientHello message is recognised as a TLS-ALPN-01 + * challenge and the ACME credentials are selected, + * NULL otherwise + * @warning If a non-NULL pointer is returned, the daemon's ACME certificate + * list remains read-locked and must be unlocked with + * #mhd_daemon_acme_cert_r_unlock(). + */ +MHD_INTERNAL const union mhd_TlsCredDataPtr * +mhd_tls_acme_check_clienthello_body ( + size_t body_size, + const unsigned char *restrict body, + struct mhd_TlsCertsList *restrict acme_certs) +MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (2, 1); +# endif /* mhd_HAVE_TLS_MHD_CLIENTHELLO_BODY_PARSE */ + +# if defined(_MSC_FULL_VER) +/* Restore warnings */ +# pragma warning(pop) +# endif /* _MSC_FULL_VER */ + +#endif /* mhd_HAVE_TLS_ACME */ + #endif /* ! MHD_TLS_INTERNAL_H */ diff --git a/src/mhd2/stream_funcs.c b/src/mhd2/stream_funcs.c @@ -953,6 +953,13 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c, close_hard = false; end_code = MHD_REQUEST_ENDED_COMPLETED_OK; break; +#ifdef mhd_HAVE_TLS_ACME + case mhd_CONN_CLOSE_ACME_ALPN_CHALLENGE_COMPLETED: + close_hard = false; + end_code = MHD_REQUEST_ENDED_COMPLETED_OK; + break; +#endif /* mhd_HAVE_TLS_ACME */ + #ifdef MHD_SUPPORT_HTTP2 case mhd_CONN_CLOSE_H2_CLOSE_SOFT: diff --git a/src/mhd2/stream_funcs.h b/src/mhd2/stream_funcs.h @@ -300,6 +300,14 @@ enum mhd_ConnCloseReason */ mhd_CONN_CLOSE_H2_CLOSE_HARD #endif /* MHD_SUPPORT_HTTP2 */ +#ifdef mhd_HAVE_TLS_ACME + , + /** + * The connection is an ACME ALPN challenge connection and + * the challenge has been completed. + */ + mhd_CONN_CLOSE_ACME_ALPN_CHALLENGE_COMPLETED +#endif /* mhd_HAVE_TLS_ACME */ }; @@ -406,6 +414,18 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3); #define mhd_conn_start_closing_no_sys_res(c) \ mhd_conn_start_closing ((c), mhd_CONN_CLOSE_NO_SYS_RESOURCES, NULL) +#ifdef mhd_HAVE_TLS_ACME +/** + * Perform initial clean-up and mark for closing. + * Set the reason to "ACME ALPN challenge completed". + * @param c the connection for pre-closing + */ +# define mhd_conn_start_closing_acme_alpn_challenge_completed(c) \ + mhd_conn_start_closing ((c), \ + mhd_CONN_CLOSE_ACME_ALPN_CHALLENGE_COMPLETED, \ + NULL) +#endif /* mhd_HAVE_TLS_ACME */ + #ifdef MHD_SUPPORT_UPGRADE /** * Perform initial clean-up and prepare for HTTP Upgrade. diff --git a/src/mhd2/tls_gnu_conn_data.h b/src/mhd2/tls_gnu_conn_data.h @@ -55,6 +55,11 @@ #include "sys_bool_type.h" +#ifdef mhd_HAVE_GNUTLS_ACME +# include "mhd_tls_cred_ptr.h" +struct mhd_TlsGnuCredData; /* Forward declaration */ +#endif /* mhd_HAVE_GNUTLS_ACME */ + #ifndef NDEBUG struct mhd_TlsGnuConnDebug { @@ -79,6 +84,27 @@ struct mhd_TlsGnuConnData * 'true' if received EOF (the remote side initiated shutting down) */ bool rmt_shut_tls_wr; + +#ifdef mhd_HAVE_GNUTLS_ACME + /** + * The ACME credentials referenced by the GnuTLS session. + * Only the @a acme_cred.gnu member is used for GnuTLS; it is NULL if the + * connection does not use ACME credentials. + */ + union mhd_TlsCredDataPtr acme_cred; + /** + * Pointer to the daemon's TLS data. + * NULL if no ACME credentials were available during connection + * initialisation. + */ + const struct mhd_TlsGnuDaemonData *d_tls; + /** + * 'true' if the ClientHello message has been processed + * Used mainly for detection of second ClientHello after TLS 1.3 HRR + */ + bool clienthello_processed; +#endif /* mhd_HAVE_GNUTLS_ACME */ + #ifndef NDEBUG /** * Debugging data diff --git a/src/mhd2/tls_gnu_daemon_data.h b/src/mhd2/tls_gnu_daemon_data.h @@ -47,23 +47,39 @@ #include "mhd_sys_options.h" -#include "sys_bool_type.h" - #ifndef MHD_SUPPORT_GNUTLS # error This header can be used only if GnuTLS is enabled #endif +#include "sys_bool_type.h" + +#include "mhd_atomic_counter.h" + #include "tls_gnu_tls_lib.h" /** - * The structure with daemon-specific GnuTLS data + * The GnuTLS credentials data */ -struct mhd_TlsGnuDaemonData +struct mhd_TlsGnuCredData { /** - * The credentials + * The GnuTLS credentials + */ + gnutls_certificate_credentials_t gnu_cred; + + /** + * The reference counter. + * Not used for credentials valid for the daemon's lifetime. + * On non-DEBUG builds it is not initialised when not used. */ - gnutls_certificate_credentials_t cred; + struct mhd_AtomicCounter ref_cnt; +}; + +/** + * The structure with daemon-specific GnuTLS data + */ +struct mhd_TlsGnuDaemonData +{ #ifdef mhd_TLS_GNU_DH_PARAMS_NEEDS_PKCS3 /** @@ -72,6 +88,18 @@ struct mhd_TlsGnuDaemonData gnutls_dh_params_t dh_params; #endif +#ifdef mhd_HAVE_GNUTLS_ACME + /** + * The list of ACME ALPN certificates + */ + struct mhd_TlsCertsList *acme_certs; +#endif /* ! mhd_HAVE_GNUTLS_ACME */ + + /** + * The credentials + */ + struct mhd_TlsGnuCredData cred; + /** * TLS priorities cache */ diff --git a/src/mhd2/tls_gnu_funcs.c b/src/mhd2/tls_gnu_funcs.c @@ -57,6 +57,8 @@ #include "mhd_assert.h" #include "mhd_assume.h" +#include "mhd_predict.h" +#include "mhd_unreachable.h" #include "mhd_socket_type.h" #include "mhd_str_types.h" @@ -80,6 +82,10 @@ #include "daemon_options.h" #include "daemon_logger.h" +#ifdef mhd_HAVE_GNUTLS_ACME +# include "mhd_tls_acme_func.h" +#endif /* mhd_HAVE_GNUTLS_ACME */ + #ifdef mhd_TLS_GNU_DH_PARAMS_NEEDS_PKCS3 # include "tls_dh_params.h" #endif @@ -115,6 +121,9 @@ static const char mhd_alpn_str_http1_1[] = mhd_ALPN_H1_1; # ifdef MHD_SUPPORT_HTTP2 static const char mhd_alpn_str_http2[] = mhd_ALPN_H2; # endif +# ifdef mhd_HAVE_GNUTLS_ACME +static const char mhd_alpn_str_acme[] = mhd_ALPN_ACME; +# endif # if 0 /* Disabled code */ static const char alpn_http_3[] = mhd_ALPN_H3; # endif @@ -132,6 +141,12 @@ static const gnutls_datum_t mhd_alpn_dat_http2 = { mhd_SSTR_LEN (mhd_alpn_str_http2) }; # endif +# ifdef mhd_HAVE_GNUTLS_ACME +static const gnutls_datum_t mhd_alpn_dat_acme = { + (unsigned char *)mhd_DROP_CONST (mhd_alpn_str_acme), + mhd_SSTR_LEN (mhd_alpn_str_acme) +}; +# endif #endif /* mhd_TLS_GNU_HAS_ALPN */ @@ -199,36 +214,45 @@ check_app_tls_settings (struct MHD_Daemon *restrict d, return MHD_SC_TLS_CONF_BAD_CERT; } mhd_assert (NULL != s->tls_cert_key.v_mem_key); +#ifdef mhd_HAVE_TLS_ACME + if (!mhd_tls_gnu_is_acme_alpn_supported (s) + && s->acme_alpn_required) + { + mhd_LOG_MSG (d, MHD_SC_TLS_BACKEND_DAEMON_INCOMPATIBLE_SETTINGS, + "This build of GnuTLS backend does not support " + "ACME ALPN challenge protocol, but daemon settings " + "require it"); + return MHD_SC_TLS_BACKEND_DAEMON_INCOMPATIBLE_SETTINGS; + } +#endif return MHD_SC_OK; } +/* Sanity check: both macros cannot be defined at the same time */ +#if defined(mhd_TLS_GNU_DH_PARAMS_USE_KNOWN) && \ + defined(mhd_TLS_GNU_DH_PARAMS_NEEDS_PKCS3) +# error Invalid macro combination: both mhd_TLS_GNU_DH_PARAMS_USE_KNOWN and \ + mhd_TLS_GNU_DH_PARAMS_NEEDS_PKCS3 are defined +#endif + /** * Initialise daemon TLS Diffie-Hellman parameters. * - * This function initialise Diffie-Hellman parameters for the daemon based + * This function initialises Diffie-Hellman parameters for the daemon based * on GnuTLS recommended defaults. - * With modern GnuTLS versions this function is no-op and always succeed. + * With modern GnuTLS versions this function is a no-op and always succeeds. * * This function does not put any messages to the log. * @param d_tls the daemon TLS data - * @return 'true' if succeed, + * @return 'true' if succeeded, * 'false' if failed */ static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_ bool daemon_init_dh_data (struct mhd_TlsGnuDaemonData *restrict d_tls) { -#if defined(mhd_TLS_GNU_DH_PARAMS_USE_KNOWN) - /* Rely on reasonable TLS defaults set in the TLS library. - Modern GnuTLS versions relies completely on RFC 7919 and do not need - this function therefore do not bother implementing special - application-defined settings just for limited number of GnuTLS - versions (>= 3.5.6 && < 3.6.0). */ - return (GNUTLS_E_SUCCESS == - gnutls_certificate_set_known_dh_params (d_tls->cred, - GNUTLS_SEC_PARAM_MEDIUM)); -#elif defined(mhd_TLS_GNU_DH_PARAMS_NEEDS_PKCS3) +#if defined(mhd_TLS_GNU_DH_PARAMS_NEEDS_PKCS3) gnutls_datum_t dh_data; if (GNUTLS_E_SUCCESS != gnutls_dh_params_init (&(d_tls->dh_params))) @@ -240,11 +264,8 @@ daemon_init_dh_data (struct mhd_TlsGnuDaemonData *restrict d_tls) gnutls_dh_params_import_pkcs3 (d_tls->dh_params, &dh_data, GNUTLS_X509_FMT_PEM)) - { - gnutls_certificate_set_dh_params (d_tls->cred, - d_tls->dh_params); return true; /* success exit point */ - } + /* Below is a clean-up code path */ gnutls_dh_params_deinit (d_tls->dh_params); return false; @@ -272,7 +293,248 @@ daemon_deinit_dh_data (struct mhd_TlsGnuDaemonData *restrict d_tls) /** - * Set daemon TLS credentials (and Diffie-Hellman parameters). + * Set credentials TLS Diffie-Hellman parameters. + * + * This function initialises Diffie-Hellman parameters based on GnuTLS + * recommended defaults. + * With modern GnuTLS versions this function is a no-op and always succeeds. + * + * This function does not put any messages to the log. + * @param d_tls the daemon TLS data + * @param cred the credentials TLS data + * @return 'true' if succeeded, + * 'false' if failed + */ +mhd_static_inline MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_ bool +tld_gnu_cred_set_dh_data (struct mhd_TlsGnuDaemonData *restrict d_tls, + struct mhd_TlsGnuCredData *restrict cred) +{ +#if defined(mhd_TLS_GNU_DH_PARAMS_USE_KNOWN) + (void)d_tls; /* Mute compiler warning */ + /* Rely on reasonable TLS defaults set in the TLS library. + Modern GnuTLS versions rely completely on RFC 7919 and do not need + this function; therefore do not bother implementing special + application-defined settings for security level just for a limited + number of GnuTLS versions (>= 3.5.6 && < 3.6.0). */ + return (GNUTLS_E_SUCCESS == + gnutls_certificate_set_known_dh_params (cred->gnu_cred, + GNUTLS_SEC_PARAM_MEDIUM)); +#elif defined(mhd_TLS_GNU_DH_PARAMS_NEEDS_PKCS3) + gnutls_certificate_set_dh_params (cred->gnu_cred, + d_tls->dh_params); + return true; /* success exit point */ +#else + (void)cred; /* Mute compiler warning */ + (void)d_tls; /* Mute compiler warning */ + return true; +#endif +} + + +/** + * Create TLS credentials from the provided certificate and key data + * + * @param d_tls the daemon TLS data + * @param[out] cred the credentials data structure to be filled + * @param with_ref_cnt whether to use the reference counter + * @param cert_len the length of the @p cert buffer, not including the + * zero-termination byte, + * must not be zero + * @param cert the certificate data in PEM format, zero-terminated + * @param key_len the length of the @p key buffer, not including the + * zero-termination byte, + * must not be zero + * @param key the private key data in PEM format, zero-terminated + * @param pass the password for the private key, zero-terminated, + * may be NULL if the private key is not password-protected + * @return #mhd_TLS_CRED_CREATE_OK on success, + * other enum mhd_TlsCredCreateResult values otherwise + */ +static MHD_FN_MUST_CHECK_RESULT_ +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) +MHD_FN_PAR_NONNULL_ (5) MHD_FN_PAR_NONNULL_ (7) +MHD_FN_PAR_OUT_ (2) +MHD_FN_PAR_IN_SIZE_ (5, 4) MHD_FN_PAR_IN_SIZE_ (7, 6) +MHD_FN_PAR_CSTR_ (5) MHD_FN_PAR_CSTR_ (7) MHD_FN_PAR_CSTR_ (8) +enum mhd_TlsCredCreateResult +tls_gnu_cred_init (struct mhd_TlsGnuDaemonData *restrict d_tls, + struct mhd_TlsGnuCredData *restrict cred, + bool with_ref_cnt, + size_t cert_len, + const char *restrict cert, + size_t key_len, + const char *restrict key, + const char *restrict pass) +{ + enum mhd_TlsCredCreateResult ret; + int res; + gnutls_datum_t cert_data; + gnutls_datum_t key_data; + + mhd_assert (0 != cert_len); + mhd_assert (0 != key_len); + + if (mhd_COND_HARDLY_EVER (cert_len != (unsigned int)cert_len) + || mhd_COND_HARDLY_EVER (key_len != (unsigned int)key_len)) + return mhd_TLS_CRED_CREATE_BAD_CRED_DATA; + + if (GNUTLS_E_SUCCESS != + gnutls_certificate_allocate_credentials (&(cred->gnu_cred))) + return mhd_TLS_CRED_CREATE_ALLOC_FAILED; + + cert_data.data = (unsigned char *)mhd_DROP_CONST (cert); + cert_data.size = (unsigned int)cert_len; + key_data.data = (unsigned char *)mhd_DROP_CONST (key); + key_data.size = (unsigned int)key_len; + res = gnutls_certificate_set_x509_key_mem2 (cred->gnu_cred, + &cert_data, + &key_data, + GNUTLS_X509_FMT_PEM, + pass, + 0); + + switch (res) + { + case GNUTLS_E_SUCCESS: + ret = mhd_TLS_CRED_CREATE_OK; + break; + case GNUTLS_E_MEMORY_ERROR: + ret = mhd_TLS_CRED_CREATE_ALLOC_FAILED; + break; + default: + ret = mhd_TLS_CRED_CREATE_BAD_CRED_DATA; + break; + } + + if (mhd_TLS_CRED_CREATE_OK == ret) + { + if (!tld_gnu_cred_set_dh_data (d_tls, + cred)) + ret = mhd_TLS_CRED_CREATE_FAILED; + + if (mhd_TLS_CRED_CREATE_OK == ret) + { + if (with_ref_cnt) + { + if (!mhd_atomic_counter_init (&(cred->ref_cnt), 1u)) + ret = mhd_TLS_CRED_CREATE_ALLOC_FAILED; /* Not fully correct code path, but it is very unlikely to happen */ + } +#ifndef NDEBUG + else + { + /* Initialise the reference counter to 0 for debug builds to catch + any accidental use of the reference counter when it is not used. */ + if (!mhd_atomic_counter_init (&(cred->ref_cnt), 0u)) + ret = mhd_TLS_CRED_CREATE_ALLOC_FAILED; /* Not fully correct code path, but it is very unlikely to happen */ + } +#endif /* ! NDEBUG */ + + if (mhd_TLS_CRED_CREATE_OK == ret) + return ret; /* Success exit point */ + + /* Below is a clean-up code path */ + } + } + + gnutls_certificate_free_credentials (cred->gnu_cred); + mhd_assert (mhd_TLS_CRED_CREATE_OK != ret); + return ret; /* Failure exit point */ +} + + +/** + * Set certificate credentials for a GnuTLS session without acquiring a + * reference. + * + * The GnuTLS session borrows the credentials. The caller must keep @p cred + * alive until the session is de-initialised or different certificate + * credentials are set for the session. + * + * @param[in,out] c_tls the connection TLS data containing the session + * @param cred the credentials to set + * @return 'true' if the credentials were set successfully, + * 'false' otherwise + */ +static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1) bool +tls_gnu_cred_use_norefcnt (struct mhd_TlsGnuConnData *restrict c_tls, + const struct mhd_TlsGnuCredData *restrict cred) +{ + return (GNUTLS_E_SUCCESS == + gnutls_credentials_set (c_tls->sess, + GNUTLS_CRD_CERTIFICATE, + cred->gnu_cred)); +} + + +#ifdef mhd_HAVE_GNUTLS_ACME +/** + * Set certificate credentials for a GnuTLS session and acquire a reference. + * + * The reference counter is incremented only after GnuTLS accepts the + * credentials. The caller must release the acquired reference after the + * session is de-initialised or the credentials are replaced in the session. + * + * @param[in,out] c_tls the connection TLS data containing the session + * @param[in,out] cred the credentials to set and reference + * @return 'true' if the credentials were set and referenced successfully, + * 'false' otherwise + */ +static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1) bool +tls_gnu_cred_use_refcnt (struct mhd_TlsGnuConnData *restrict c_tls, + struct mhd_TlsGnuCredData *restrict cred) +{ + bool res; + + res = tls_gnu_cred_use_norefcnt (c_tls, + cred); + if (res) + mhd_atomic_counter_inc (&(cred->ref_cnt)); + + return res; +} + + +#endif /* mhd_HAVE_GNUTLS_ACME */ + +/** + * Decrement reference count and de-initialise TLS credentials if no + * references left. + * @param cred the credentials data structure to be deinitialised + * @param with_ref_cnt whether to use the reference counter + * @return 'true' if no references left and credentials de-initialised, + * 'false' if there are still references left (never returned with + * @p with_ref_cnt set to 'false') + */ +static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1) bool +tls_gnu_cred_deinit (struct mhd_TlsGnuCredData *restrict cred, + bool with_ref_cnt) +{ + mhd_assert (NULL != cred->gnu_cred); + + if (with_ref_cnt) + { + if (1u != mhd_atomic_counter_get_dec (&(cred->ref_cnt))) + return false; /* Not the last reference, do not free credentials */ + mhd_atomic_counter_deinit (&(cred->ref_cnt)); + } +#ifndef NDEBUG + else + { + mhd_assert (0u == mhd_atomic_counter_get (&(cred->ref_cnt))); + mhd_atomic_counter_deinit (&(cred->ref_cnt)); + } +#endif /* ! NDEBUG */ + + gnutls_certificate_free_credentials (cred->gnu_cred); +#ifndef NDEBUG + cred->gnu_cred = NULL; +#endif /* ! NDEBUG */ + return true; +} + + +/** + * Set daemon TLS credentials. * This function puts error messages to the log if needed. * @param d the daemon handle * @param d_tls the daemon TLS settings @@ -285,18 +547,9 @@ daemon_init_credentials (struct MHD_Daemon *restrict d, struct mhd_TlsGnuDaemonData *restrict d_tls, struct DaemonOptions *restrict s) { - enum MHD_StatusCode ret; size_t cert_len; size_t key_len; - if (GNUTLS_E_SUCCESS != - gnutls_certificate_allocate_credentials (&(d_tls->cred))) - { - mhd_LOG_MSG (d, MHD_SC_TLS_DAEMON_INIT_FAILED, \ - "Failed to initialise TLS credentials for the daemon"); - return MHD_SC_TLS_DAEMON_INIT_FAILED; - } - // TODO: Support multiple certificates cert_len = strlen (s->tls_cert_key.v_mem_cert); // TODO: Reuse calculated length key_len = strlen (s->tls_cert_key.v_mem_key); // TODO: Reuse calculated length @@ -304,68 +557,55 @@ daemon_init_credentials (struct MHD_Daemon *restrict d, mhd_assert (0 != cert_len); mhd_assert (0 != key_len); - if ((cert_len != (unsigned int)cert_len) - || (key_len != (unsigned int)key_len)) - ret = MHD_SC_TLS_CONF_BAD_CERT; /* Very unlikely, do not waste space on special message */ - else + switch (tls_gnu_cred_init (d_tls, + &(d_tls->cred), + false, + cert_len, + s->tls_cert_key.v_mem_cert, + key_len, + s->tls_cert_key.v_mem_key, + s->tls_cert_key.v_mem_pass)) { - gnutls_datum_t cert_data; - gnutls_datum_t key_data; - int res; + case mhd_TLS_CRED_CREATE_OK: + break; - cert_data.data = - (unsigned char *)mhd_DROP_CONST (s->tls_cert_key.v_mem_cert); - cert_data.size = (unsigned int)cert_len; - key_data.data = - (unsigned char *)mhd_DROP_CONST (s->tls_cert_key.v_mem_key); - key_data.size = (unsigned int)key_len; - res = gnutls_certificate_set_x509_key_mem2 (d_tls->cred, - &cert_data, - &key_data, - GNUTLS_X509_FMT_PEM, - s->tls_cert_key.v_mem_pass, - 0); - if (0 > res) - { - mhd_LOG_PRINT (d, \ - MHD_SC_TLS_CONF_BAD_CERT, \ - mhd_LOG_FMT ("Failed to set the provided " \ - "TLS certificate: %s"), - gnutls_strerror (res)); - ret = MHD_SC_TLS_CONF_BAD_CERT; - } - else - { - if (!daemon_init_dh_data (d_tls)) - { - mhd_LOG_MSG (d, MHD_SC_TLS_DAEMON_INIT_FAILED, \ - "Failed to initialise Diffie-Hellman parameters " \ - "for the daemon"); - ret = MHD_SC_TLS_DAEMON_INIT_FAILED; - } - else - return MHD_SC_OK; - } + case mhd_TLS_CRED_CREATE_BAD_CRED_DATA: + mhd_LOG_MSG (d, MHD_SC_TLS_CONF_BAD_CERT, + "Failed to set the provided TLS certificate"); + return MHD_SC_TLS_CONF_BAD_CERT; + + case mhd_TLS_CRED_CREATE_ALLOC_FAILED: + mhd_LOG_MSG (d, MHD_SC_DAEMON_MEM_ALLOC_FAILURE, + "Failed to allocate memory or other resources " + "for TLS credentials"); + return MHD_SC_DAEMON_MEM_ALLOC_FAILURE; + + case mhd_TLS_CRED_CREATE_FAILED: + case mhd_TLS_CRED_CREATE_UNSUPPORTED: + mhd_LOG_MSG (d, MHD_SC_TLS_DAEMON_INIT_FAILED, + "Failed to initialise TLS credentials for the daemon"); + return MHD_SC_TLS_DAEMON_INIT_FAILED; + default: + mhd_UNREACHABLE (); + return MHD_SC_INTERNAL_ERROR; } - gnutls_certificate_free_credentials (d_tls->cred); - mhd_assert (MHD_SC_OK != ret); - return ret; /* Failure exit point */ + return MHD_SC_OK; } /** - * Free daemon fully allocated credentials (and Diffie-Hellman parameters). + * Free daemon fully allocated credentials. * @param d_tls the daemon TLS settings */ static MHD_FN_PAR_NONNULL_ALL_ void daemon_deinit_credentials (struct mhd_TlsGnuDaemonData *restrict d_tls) { - mhd_assert (NULL != d_tls->cred); + mhd_assert (NULL != d_tls->cred.gnu_cred); /* To avoid dangling pointer to DH data in the credentials, free credentials first and then free DH data. */ - gnutls_certificate_free_credentials (d_tls->cred); - daemon_deinit_dh_data (d_tls); + (void)tls_gnu_cred_deinit (&(d_tls->cred), + false); } @@ -533,6 +773,10 @@ mhd_tls_gnu_daemon_init3 (struct MHD_Daemon *restrict d, if (NULL == d_tls) return MHD_SC_DAEMON_MEM_ALLOC_FAILURE; +#ifdef mhd_HAVE_GNUTLS_ACME + d_tls->acme_certs = mhd_daemon_get_acme_certs (d); +#endif /* mhd_HAVE_GNUTLS_ACME */ + #ifdef mhd_TLS_GNU_HAS_ALPN // TODO: use daemon option to disable ALPN // TODO: use daemon option to select protocols for ALPN @@ -552,19 +796,30 @@ mhd_tls_gnu_daemon_init3 (struct MHD_Daemon *restrict d, mhd_assert (mhd_ARR_NUM_ELEMS (d_tls->alpn_prots) >= d_tls->num_alpn_prots); #endif /* mhd_TLS_GNU_HAS_ALPN */ - res = daemon_init_credentials (d, - d_tls, - s); - if (MHD_SC_OK == res) + if (!daemon_init_dh_data (d_tls)) + { + mhd_LOG_MSG (d, MHD_SC_TLS_DAEMON_INIT_FAILED, \ + "Failed to initialise Diffie-Hellman parameters " \ + "for the daemon"); + res = MHD_SC_TLS_DAEMON_INIT_FAILED; + } + else { - res = daemon_init_priorities_cache (d, - d_tls, - s); + res = daemon_init_credentials (d, + d_tls, + s); if (MHD_SC_OK == res) - return MHD_SC_OK; /* Success exit point */ - - /* Below is a clean-up code path */ - daemon_deinit_credentials (d_tls); + { + res = daemon_init_priorities_cache (d, + d_tls, + s); + if (MHD_SC_OK == res) + return MHD_SC_OK; /* Success exit point */ + + /* Below is a clean-up code path */ + daemon_deinit_credentials (d_tls); + } + daemon_deinit_dh_data (d_tls); } free (d_tls); @@ -581,10 +836,69 @@ mhd_tls_gnu_daemon_deinit (struct mhd_TlsGnuDaemonData *restrict d_tls) mhd_assert (NULL != d_tls); daemon_deinit_priorities_cache (d_tls); daemon_deinit_credentials (d_tls); + daemon_deinit_dh_data (d_tls); free (d_tls); } +/* ** Credentials creation / destruction ** */ + +#ifdef mhd_HAVE_GNUTLS_ACME + +MHD_INTERNAL MHD_FN_MUST_CHECK_RESULT_ +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) +MHD_FN_PAR_NONNULL_ (4) MHD_FN_PAR_NONNULL_ (6) +MHD_FN_PAR_OUT_ (2) +MHD_FN_PAR_IN_SIZE_ (4, 3) MHD_FN_PAR_IN_SIZE_ (6, 5) +MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_CSTR_ (6) MHD_FN_PAR_CSTR_ (7) +enum mhd_TlsCredCreateResult +mhd_tls_gnu_cred_create_nopl (struct mhd_TlsGnuDaemonData *restrict d_tls, + union mhd_TlsCredDataPtr *restrict pp_cred, + size_t cert_len, + const char *restrict cert, + size_t key_len, + const char *restrict key, + const char *restrict pass) +{ + enum mhd_TlsCredCreateResult res; + + pp_cred->gnu = (struct mhd_TlsGnuCredData *) + mhd_calloc (1, + sizeof(struct mhd_TlsGnuCredData)); + if (NULL == pp_cred->gnu) + return mhd_TLS_CRED_CREATE_ALLOC_FAILED; + + res = tls_gnu_cred_init (d_tls, + pp_cred->gnu, + true, + cert_len, + cert, + key_len, + key, + pass); + if (mhd_TLS_CRED_CREATE_OK == res) + return res; /* Success exit point */ + + free (pp_cred->gnu); + pp_cred->gnu = NULL; + mhd_assert (mhd_TLS_CRED_CREATE_OK != res); + return res; /* Failure exit point */ +} + + +MHD_INTERNAL void +mhd_tls_gnu_cred_destroy_nodmn (union mhd_TlsCredDataPtr cred) +{ + mhd_ASSUME (NULL != cred.gnu); + if (tls_gnu_cred_deinit (cred.gnu, + true)) + free (cred.gnu); +} + + +#endif /* mhd_HAVE_GNUTLS_ACME */ + + /* ** Connection initialisation / de-initialisation ** */ MHD_INTERNAL size_t @@ -594,6 +908,227 @@ mhd_tls_gnu_conn_get_tls_size_v (void) } +#ifdef mhd_TLS_GNU_HAS_ALPN +/** + * Set the configured ALPN protocols for a GnuTLS session. + * + * @param d_tls the daemon TLS settings containing the configured protocols + * @param[in,out] c_tls the connection TLS data containing the session + * @return 'true' if no protocols are configured or the protocols were set + * successfully, + * 'false' otherwise + */ +static MHD_FN_PAR_NONNULL_ALL_ bool +conn_set_configured_alpn (const struct mhd_TlsGnuDaemonData *restrict d_tls, + struct mhd_TlsGnuConnData *restrict c_tls) +{ + unsigned int alpn_flags; + + if (0u == d_tls->num_alpn_prots) + return true; /* Nothing to set */ + + + alpn_flags = 0; +# if 0 + alpn_flags |= GNUTLS_ALPN_SERVER_PRECEDENCE; +# endif + + return (GNUTLS_E_SUCCESS == + gnutls_alpn_set_protocols (c_tls->sess, + d_tls->alpn_prots, + d_tls->num_alpn_prots, + alpn_flags)); +} + + +#endif /* mhd_TLS_GNU_HAS_ALPN */ + + +#ifdef mhd_HAVE_GNUTLS_ACME + +# ifdef mhd_USE_GNUTLS_EXT_RAW_PARSE +/** + * Check one ClientHello extension reported by the GnuTLS raw extension parser. + * + * @param[in,out] cls the incremental ACME ClientHello check state + * @param tls_id the TLS extension identifier + * @param data the complete extension data + * @param data_size the size of @p data in bytes + * @return #GNUTLS_E_SUCCESS if checking should continue, + * #GNUTLS_E_ILLEGAL_PARAMETER if checking should stop + */ +static int +mhd_tls_gnu_acme_check_cb (void *cls, + unsigned int tls_id, + const unsigned char *data, + unsigned int data_size) +{ + struct mhd_TlsClientHelloAcmeCheckData *const restrict check + = (struct mhd_TlsClientHelloAcmeCheckData *)cls; + size_t data_size_t = (size_t)data_size; + + if (data_size != data_size_t) + return GNUTLS_E_ILLEGAL_PARAMETER; /* The extension is too large, reject */ + + if (!mhd_tls_acme_check_ext (tls_id, + data_size_t, + data, + check)) + return GNUTLS_E_ILLEGAL_PARAMETER; /* Any negative number stops processing */ + + return GNUTLS_E_SUCCESS; +} + + +# endif /* mhd_USE_GNUTLS_EXT_RAW_PARSE */ + +/** + * GnuTLS handshake hook for checking a ClientHello message for ACME ALPN. + * + * @param[in,out] sess the GnuTLS session processing the ClientHello message + * @param htype the type of the handshake message being processed, + * one of the @a gnutls_handshake_description_t values + * @param when set to non-zero called during post-processing or generation, + * and to zero otherwise + * @param incoming set to non-zero if processing incoming message and + * to zero if processing an outgoing message + * @param msg the content of the handshake message without the handshake + * headers + * @return #GNUTLS_E_SUCCESS for a normal HTTPS ClientHello or after successful + * ACME setup, + * a negative GnuTLS error code to abort the handshake otherwise + */ +static int +mhd_tls_gnu_hello_hook_acme (gnutls_session_t sess, + unsigned int htype, + unsigned when, + unsigned int incoming, + const gnutls_datum_t *msg) +{ + struct mhd_TlsGnuConnData *const restrict c_tls = + (struct mhd_TlsGnuConnData *)gnutls_session_get_ptr (sess); + const union mhd_TlsCredDataPtr *acme_cred; /* Set to non-NULL if request is recognised ACME challenge */ + union mhd_TlsCredDataPtr replaced_cred; /* Set to non-NULL if the ACME credentials are replaced */ + bool acme_ok; /* Used special response for ACME challenge */ + + mhd_assert (GNUTLS_HANDSHAKE_CLIENT_HELLO == htype); + mhd_assert (GNUTLS_HOOK_PRE == when); + mhd_assert (incoming); + mhd_assert (NULL != c_tls); + mhd_assert (NULL != c_tls->d_tls); + mhd_assert (c_tls->sess == sess); + +# ifdef NDEBUG + (void)htype; + (void)when; + (void)incoming; +# endif /* NDEBUG */ + +# ifdef mhd_USE_GNUTLS_EXT_RAW_PARSE + if (1) + { + bool parse_ok; + struct mhd_TlsClientHelloAcmeCheckData acme_check; + + mhd_tls_acme_check_ext_init (&acme_check, + c_tls->d_tls->acme_certs); + + parse_ok = (GNUTLS_E_SUCCESS == + gnutls_ext_raw_parse (&acme_check, + &mhd_tls_gnu_acme_check_cb, + msg, + GNUTLS_EXT_RAW_FLAG_TLS_CLIENT_HELLO)); + + acme_cred = mhd_tls_acme_check_ext_finish (&acme_check, + parse_ok); + } +# else /* ! mhd_USE_GNUTLS_EXT_RAW_PARSE */ + if (1) + { + size_t msg_size = (size_t)msg->size; + + if (msg_size != msg->size) + return GNUTLS_E_ILLEGAL_PARAMETER; /* The message is too large, reject */ + + acme_cred = + mhd_tls_acme_check_clienthello_body (msg_size, + msg->data, + c_tls->d_tls->acme_certs); + } +# endif /* ! mhd_USE_GNUTLS_EXT_RAW_PARSE */ + + if (NULL == acme_cred) + { + /* Normal (not ACME ALPN challenge) connection */ + if (NULL != c_tls->acme_cred.gnu) /* Second ClientHello after TLS 1.3 HRR with another result */ + { + mhd_assert (c_tls->clienthello_processed); + return GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET; /* Unmatched ClientHello, abort handshake */ + } + + /* Process the connection as a normal HTTPS connection */ + if (!c_tls->clienthello_processed) + (void)conn_set_configured_alpn (c_tls->d_tls, /* Function cannot be called twice for the same connection */ + c_tls); + + c_tls->clienthello_processed = true; + return GNUTLS_E_SUCCESS; /* Non-ACME connection exit point */ + } + + /* Process the connection as an ACME ALPN challenge connection */ + + mhd_assert (NULL != acme_cred->gnu); + + acme_ok = true; /* Assume success unless something fails below */ + if (c_tls->clienthello_processed) + acme_ok = (NULL != c_tls->acme_cred.gnu); /* The first ClientHello was processed as an ACME connection too */ + + replaced_cred.gnu = NULL; + if (acme_ok) + { + if ((NULL != c_tls->acme_cred.gnu) + && (acme_cred->gnu != c_tls->acme_cred.gnu)) + { /* The list of ACME credentials has changed since the last ClientHello, release the old credentials */ + replaced_cred = c_tls->acme_cred; + c_tls->acme_cred.gnu = NULL; + } + + if (NULL == c_tls->acme_cred.gnu) + { + acme_ok = tls_gnu_cred_use_refcnt (c_tls, + acme_cred->gnu); + if (acme_ok) + c_tls->acme_cred = *acme_cred; + } + } + /* If non-NULL was returned, the daemon's certificates list must be + unlocked */ + mhd_daemon_acme_cert_r_unlock (c_tls->d_tls->acme_certs); + + /* Release old credentials if any */ + if (NULL != replaced_cred.gnu) + mhd_tls_gnu_cred_destroy_nodmn (replaced_cred); + + if (acme_ok) + { + /* Handle ALPN */ + if (!c_tls->clienthello_processed) + acme_ok = (GNUTLS_E_SUCCESS == + gnutls_alpn_set_protocols (c_tls->sess, /* Function cannot be called twice for the same connection */ + &mhd_alpn_dat_acme, + 1u, + GNUTLS_ALPN_MANDATORY)); + } + + c_tls->clienthello_processed = true; + return acme_ok ? + GNUTLS_E_SUCCESS + : GNUTLS_E_INTERNAL_ERROR; /* Any negative number stops processing */ +} + + +#endif /* mhd_HAVE_GNUTLS_ACME */ + MHD_INTERNAL MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_ (3) bool mhd_tls_gnu_conn_init (const struct mhd_TlsGnuDaemonData *restrict d_tls, @@ -638,10 +1173,8 @@ mhd_tls_gnu_conn_init (const struct mhd_TlsGnuDaemonData *restrict d_tls, if (GNUTLS_E_SUCCESS == res) { - if (GNUTLS_E_SUCCESS == - gnutls_credentials_set (c_tls->sess, - GNUTLS_CRD_CERTIFICATE, - d_tls->cred)) + if (tls_gnu_cred_use_norefcnt (c_tls, + &(d_tls->cred))) { #if defined(mhd_TLS_GNU_HAS_TRANSP_SET_INT) && defined(MHD_SOCKETS_KIND_POSIX) gnutls_transport_set_int (c_tls->sess, @@ -656,25 +1189,32 @@ mhd_tls_gnu_conn_init (const struct mhd_TlsGnuDaemonData *restrict d_tls, /* The basic TLS session properties has been set. The rest is optional settings. */ -#ifdef mhd_TLS_GNU_HAS_ALPN - if (0 != d_tls->num_alpn_prots) - { - int alpn_res; - unsigned int alpn_flags; - - alpn_flags = 0; -# if 0 - alpn_flags |= GNUTLS_ALPN_SERVER_PRECEDENCE; -# endif - alpn_res = - gnutls_alpn_set_protocols (c_tls->sess, - d_tls->alpn_prots, - d_tls->num_alpn_prots, - alpn_flags); - (void)alpn_res; /* Ignore any possible ALPN set errors */ +#if defined(mhd_HAVE_GNUTLS_ACME) +# ifndef HAVE_NULL_PTR_ALL_ZEROS + c_tls->acme_cred.gnu = NULL; + c_tls->d_tls = NULL; +# endif /* HAVE_NULL_PTR_ALL_ZEROS */ + if (mhd_daemon_has_acme_certs (d_tls->acme_certs)) + { + c_tls->d_tls = d_tls; + gnutls_session_set_ptr (c_tls->sess, + c_tls); + gnutls_handshake_set_hook_function (c_tls->sess, + GNUTLS_HANDSHAKE_CLIENT_HELLO, + GNUTLS_HOOK_PRE, + &mhd_tls_gnu_hello_hook_acme); + /* Do not set ALPN here as ALPN selection depends on whether + connection is processed as HTTPS connection or as ACME ALPN + challenge connection. */ } + else +#endif /* mhd_HAVE_GNUTLS_ACME */ +#ifdef mhd_TLS_GNU_HAS_ALPN + (void)conn_set_configured_alpn (d_tls, + c_tls); /* Ignore result */ #endif /* mhd_TLS_GNU_HAS_ALPN */ + #ifndef NDEBUG c_tls->dbg.is_inited = true; #endif /* ! NDEBUG */ @@ -700,6 +1240,10 @@ mhd_tls_gnu_conn_deinit (struct mhd_TlsGnuConnData *restrict c_tls) mhd_assert (NULL != c_tls->sess); mhd_assert (c_tls->dbg.is_inited); gnutls_deinit (c_tls->sess); +#ifdef mhd_HAVE_GNUTLS_ACME + if (NULL != c_tls->acme_cred.gnu) + mhd_tls_gnu_cred_destroy_nodmn (c_tls->acme_cred); +#endif /* mhd_HAVE_GNUTLS_ACME */ } @@ -814,6 +1358,9 @@ mhd_tls_gnu_conn_recv (struct mhd_TlsGnuConnData *restrict c_tls, mhd_assert (c_tls->dbg.is_tls_handshake_completed); mhd_assert (!c_tls->dbg.is_finished); mhd_assert (!c_tls->dbg.is_failed); +#ifdef mhd_HAVE_GNUTLS_ACME + mhd_assert (NULL == c_tls->acme_cred.gnu); +#endif /* mhd_HAVE_GNUTLS_ACME */ /* Check for GnuTLS return value limitation */ if (0 > (ssize_t)buf_size) @@ -872,6 +1419,9 @@ mhd_tls_gnu_conn_send4 (struct mhd_TlsGnuConnData *restrict c_tls, mhd_assert (c_tls->dbg.is_tls_handshake_completed); mhd_assert (!c_tls->dbg.is_finished); mhd_assert (!c_tls->dbg.is_failed); +#ifdef mhd_HAVE_GNUTLS_ACME + mhd_assert (NULL == c_tls->acme_cred.gnu); +#endif /* mhd_HAVE_GNUTLS_ACME */ /* Check for GnuTLS return value limitation */ if (0 > (ssize_t)buf_size) @@ -957,6 +1507,9 @@ mhd_tls_gnu_conn_get_alpn_prot (struct mhd_TlsGnuConnData *restrict c_tls) { #ifdef mhd_TLS_GNU_HAS_ALPN gnutls_datum_t sel_prot; +# ifdef mhd_HAVE_GNUTLS_ACME + mhd_assert (NULL == c_tls->acme_cred.gnu); +# endif /* mhd_HAVE_GNUTLS_ACME */ if (GNUTLS_E_SUCCESS == gnutls_alpn_get_selected_protocol (c_tls->sess, &sel_prot)) @@ -966,3 +1519,16 @@ mhd_tls_gnu_conn_get_alpn_prot (struct mhd_TlsGnuConnData *restrict c_tls) return mhd_TLS_ALPN_PROT_NOT_SELECTED; } + + +#ifdef mhd_HAVE_TLS_ACME +# ifdef mhd_HAVE_GNUTLS_ACME +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_tls_gnu_conn_is_acme (struct mhd_TlsGnuConnData *restrict c_tls) +{ + return (NULL != c_tls->acme_cred.gnu); +} + + +# endif /* mhd_HAVE_GNUTLS_ACME */ +#endif /* mhd_HAVE_TLS_ACME */ diff --git a/src/mhd2/tls_gnu_funcs.h b/src/mhd2/tls_gnu_funcs.h @@ -57,6 +57,8 @@ #include "mhd_status_code_int.h" #include "mhd_tls_enums.h" +#include "mhd_tls_cred_ptr.h" + #include "mhd_socket_error.h" /** @@ -65,6 +67,11 @@ struct mhd_TlsGnuDaemonData; /* Forward declaration */ /** + * The structure with credentials + */ +struct mhd_TlsGnuCredData; /* Forward declaration */ + +/** * The structure with connection-specific GnuTLS data */ struct mhd_TlsGnuConnData; /* Forward declaration */ @@ -116,6 +123,20 @@ struct DaemonOptions; /* Forward declaration */ */ #define mhd_tls_gnu_is_edge_trigg_supported(s) (! 0) +#ifdef mhd_HAVE_TLS_ACME +/** + * Check whether GnuTLS backend supports ACME ALPN challenge protocol + * @param s the daemon settings + * @return 'true' if the backend supports ACME ALPN challenge protocol, + * 'false' otherwise + */ +# ifdef mhd_HAVE_GNUTLS_ACME +# define mhd_tls_gnu_is_acme_alpn_supported(s) ((void)(s), (!0)) +# else /* ! mhd_HAVE_GNUTLS_ACME */ +# define mhd_tls_gnu_is_acme_alpn_supported(s) ((void)(s), (!!0)) +# endif /* mhd_HAVE_GNUTLS_ACME */ +#endif /* mhd_HAVE_TLS_ACME */ + /** * Allocate and initialise daemon TLS parameters @@ -163,6 +184,107 @@ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); */ #define mhd_tls_gnu_thread_cleanup(d_tls) ((void) 0) +/* ** Credentials creation / destruction ** */ + +#ifdef mhd_HAVE_GNUTLS_ACME +/** + * Create TLS credentials from the provided certificate and key data + * + * Created credentials must be destroyed with #mhd_tls_gnu_cred_destroy_nodmn() + * function before de-initialisation of the daemon's TLS settings. + * + * @param d_tls the pointer to the daemon's TLS settings + * @param[out] pp_cred the backend-specific credentials pointer to initialise; + * the GnuTLS member is set to the allocated credentials on + * success and to NULL on failure + * @param cert_len the length of the @p cert buffer, not including the + * zero-termination byte, + * must not be zero + * @param cert the certificate data in PEM format, zero-terminated + * @param key_len the length of the @p key buffer, not including the + * zero-termination byte, + * must not be zero + * @param key the private key data in PEM format, zero-terminated + * @param pass the password for the private key, zero-terminated, + * may be NULL if the private key is not password-protected + * @return #mhd_TLS_CRED_CREATE_OK on success, + * other enum mhd_TlsCredCreateResult values on failure + */ +MHD_INTERNAL enum mhd_TlsCredCreateResult +mhd_tls_gnu_cred_create_nopl (struct mhd_TlsGnuDaemonData *restrict d_tls, + union mhd_TlsCredDataPtr *restrict pp_cred, + size_t cert_len, + const char *restrict cert, + size_t key_len, + const char *restrict key, + const char *restrict pass) +MHD_FN_MUST_CHECK_RESULT_ +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) +MHD_FN_PAR_NONNULL_ (4) MHD_FN_PAR_NONNULL_ (6) +MHD_FN_PAR_OUT_ (2) +MHD_FN_PAR_IN_SIZE_ (4, 3) MHD_FN_PAR_IN_SIZE_ (6, 5) +MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_CSTR_ (6) MHD_FN_PAR_CSTR_ (7); + +/** + * Create TLS credentials from the provided certificate and key data. + * + * Created credentials must be destroyed with #mhd_tls_gnu_cred_destroy_nodmn() + * function before de-initialisation of the daemon's TLS settings. + * + * @param d_tls the pointer to the daemon's TLS settings + * @param[out] pp_c the backend-specific credentials pointer to initialise; + * the GnuTLS member is set to the allocated credentials on + * success and to NULL on failure + * @param cert_l the length of the @p cert buffer, not including the + * zero-termination byte, + * must not be zero + * @param cert the certificate data in PEM format, zero-terminated + * @param k_l the length of the @p key buffer, not including the + * zero-termination byte, + * must not be zero + * @param k the private key data in PEM format, zero-terminated + * @param ps_l the length of the @p pass buffer, not including the + * zero-termination byte, + * must be zero if the @p pass is NULL + * @param ps the password for the private key, zero-terminated, + * may be NULL if the private key is not password-protected + * @return #mhd_TLS_CRED_CREATE_OK on success, + * other enum mhd_TlsCredCreateResult values on failure + */ +# define mhd_tls_gnu_cred_create(d_tls, pp_c, cert_l, cert, k_l, k, ps_l, ps) \ + mhd_tls_gnu_cred_create_nopl((d_tls),(pp_c),(cert_l),(cert),\ + (k_l),(k),(((void)(ps_l)),(ps))) + +/** + * Release TLS credentials and free the allocated memory when no references + * remain. + * + * @param cred the backend-specific credentials pointer to release; the GnuTLS + * member must be non-NULL + */ +MHD_INTERNAL void +mhd_tls_gnu_cred_destroy_nodmn (union mhd_TlsCredDataPtr cred) +MHD_FN_PAR_NONNULL_ALL_; + +#else /* ! mhd_HAVE_GNUTLS_ACME */ +# define mhd_tls_gnu_cred_create(d_tls, pp_c, cert_l, cert, k_l, k, ps_l, ps) \ + ((void)d_tls, (void)pp_c, (void)cert_l, (void)cert, \ + (void)k_l, (void)k, (void)ps_l, (void)ps, \ + mhd_TLS_CRED_CREATE_UNSUPPORTED) +# define mhd_tls_gnu_cred_destroy_nodmn(cred) ((void)(cred)) +#endif /* ! mhd_HAVE_GNUTLS_ACME */ + +/** + * Release TLS credentials and free the allocated memory when no references + * remain. + * + * @param d_tls the pointer to the daemon's TLS settings + * @param cred the backend-specific credentials pointer to release; the GnuTLS + * member must be non-NULL + */ +#define mhd_tls_gnu_cred_destroy(d_tls, cred) \ + do { (void)(d_tls); mhd_tls_gnu_cred_destroy_nodmn ((cred)); } while (0) + /* ** Connection initialisation / de-initialisation ** */ struct mhd_ConnSocket; /* Forward declaration */ @@ -344,5 +466,22 @@ MHD_INTERNAL enum mhd_TlsAlpnProt mhd_tls_gnu_conn_get_alpn_prot (struct mhd_TlsGnuConnData *restrict c_tls) MHD_FN_PAR_NONNULL_ALL_; +#ifdef mhd_HAVE_TLS_ACME +/** + * Check whether the connection is ACME ALPN challenge connection + * @param c_tls the connection TLS handle + * @return 'true' if the connection is ACME ALPN challenge connection, + * 'false' otherwise + */ +# ifdef mhd_HAVE_GNUTLS_ACME +MHD_INTERNAL bool +mhd_tls_gnu_conn_is_acme (struct mhd_TlsGnuConnData *restrict c_tls) +MHD_FN_PAR_NONNULL_ALL_; + +# else /* ! mhd_HAVE_GNUTLS_ACME */ +# define mhd_tls_gnu_conn_is_acme(c_tls) (((void)(c_tls)), (!!0)) +# endif /* ! mhd_HAVE_GNUTLS_ACME */ +#endif /* mhd_HAVE_TLS_ACME */ + #endif /* ! MHD_TLS_GNU_FUNCS_H */ diff --git a/src/mhd2/tls_gnu_tls_lib.h b/src/mhd2/tls_gnu_tls_lib.h @@ -140,4 +140,9 @@ # define mhd_TLS_GNU_HAS_ALPN 1 #endif +/* Sanity check */ +#if defined(mhd_HAVE_GNUTLS_ACME) && !defined(mhd_TLS_GNU_HAS_ALPN) +# error mhd_HAVE_GNUTLS_ACME is defined but mhd_TLS_GNU_HAS_ALPN is not +#endif + #endif /* ! MHD_TLS_GNU_TLS_LIB_H */ diff --git a/src/mhd2/tls_mbed_funcs.c b/src/mhd2/tls_mbed_funcs.c @@ -445,6 +445,18 @@ check_app_tls_settings (struct MHD_Daemon *restrict d, } } +#ifdef mhd_HAVE_TLS_ACME + if (!mhd_tls_mbed_is_acme_alpn_supported (s) + && s->acme_alpn_required) + { + mhd_LOG_MSG (d, MHD_SC_TLS_BACKEND_DAEMON_INCOMPATIBLE_SETTINGS, + "This build of MbedTLS backend does not support " + "ACME ALPN challenge protocol, but daemon settings " + "require it"); + return MHD_SC_TLS_BACKEND_DAEMON_INCOMPATIBLE_SETTINGS; + } +#endif + return MHD_SC_OK; } diff --git a/src/mhd2/tls_mbed_funcs.h b/src/mhd2/tls_mbed_funcs.h @@ -116,6 +116,15 @@ struct DaemonOptions; /* Forward declaration */ */ #define mhd_tls_mbed_is_edge_trigg_supported(s) (! 0) +#ifdef mhd_HAVE_TLS_ACME +/** + * Check whether MbedTLS backend supports ACME ALPN challenge protocol + * @param s the daemon settings + * @return 'true' if the backend supports ACME ALPN challenge protocol, + * 'false' otherwise + */ +# define mhd_tls_mbed_is_acme_alpn_supported(s) ((void)(s), (!!0)) +#endif /* mhd_HAVE_TLS_ACME */ /** * Allocate and initialise daemon TLS parameters @@ -162,6 +171,26 @@ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); */ #define mhd_tls_mbed_thread_cleanup(d_tls) ((void) 0) +/* ** Credentials creation / destruction ** */ + +#define mhd_tls_mbed_cred_create(d_tls, pp_c, cert_l, cert, k_l, k, ps_l, ps) \ + ((void)d_tls, (void)pp_c, (void)cert_l, (void)cert, \ + (void)k_l, (void)k, (void)ps_l, (void)ps, \ + mhd_TLS_CRED_CREATE_UNSUPPORTED) +#define mhd_tls_mbed_cred_destroy_nodmn(cred) ((void)(cred)) + +/** + * Release TLS credentials and free the allocated memory when no references + * remain. + * + * @param d_tls the pointer to the daemon's TLS settings + * @param cred the backend-specific credentials pointer to release; the MbedTLS + * member must be non-NULL + */ +#define mhd_tls_mbed_cred_destroy(d_tls, cred) \ + do { (void)(d_tls); \ + mhd_tls_mbed_cred_destroy_nodmn ((cred)); } while (0) + /* ** Connection initialisation / de-initialisation ** */ struct mhd_ConnSocket; /* Forward declaration */ @@ -332,5 +361,15 @@ MHD_INTERNAL enum mhd_TlsAlpnProt mhd_tls_mbed_conn_get_alpn_prot (struct mhd_TlsMbedConnData *restrict c_tls) MHD_FN_PAR_NONNULL_ALL_; +#ifdef mhd_HAVE_TLS_ACME +/** + * Check whether the connection is ACME ALPN challenge connection + * @param c_tls the connection TLS handle + * @return 'true' if the connection is ACME ALPN challenge connection, + * 'false' otherwise + */ +# define mhd_tls_mbed_conn_is_acme(c_tls) (((void)(c_tls)), (!!0)) +#endif /* mhd_HAVE_TLS_ACME */ + #endif /* ! MHD_TLS_MBED_FUNCS_H */ diff --git a/src/mhd2/tls_multi_funcs.c b/src/mhd2/tls_multi_funcs.c @@ -155,16 +155,28 @@ mhd_tls_multi_is_edge_trigg_supported (struct DaemonOptions *s) #ifdef MHD_SUPPORT_GNUTLS if (mhd_tls_gnu_is_edge_trigg_supported (s) && mhd_tls_gnu_is_inited_fine ()) +# ifdef mhd_HAVE_TLS_ACME + if (mhd_tls_gnu_is_acme_alpn_supported (s) + || !s->acme_alpn_required) +# endif /* mhd_HAVE_TLS_ACME */ return true; #endif #ifdef MHD_SUPPORT_OPENSSL if (mhd_tls_open_is_edge_trigg_supported (s) && mhd_tls_open_is_inited_fine ()) +# ifdef mhd_HAVE_TLS_ACME + if (mhd_tls_open_is_acme_alpn_supported (s) + || !s->acme_alpn_required) +# endif /* mhd_HAVE_TLS_ACME */ return true; #endif #ifdef MHD_SUPPORT_MBEDTLS if (mhd_tls_mbed_is_edge_trigg_supported (s) && mhd_tls_mbed_is_inited_fine ()) +# ifdef mhd_HAVE_TLS_ACME + if (mhd_tls_mbed_is_acme_alpn_supported (s) + || !s->acme_alpn_required) +# endif /* mhd_HAVE_TLS_ACME */ return true; #endif return false; @@ -197,6 +209,63 @@ mhd_tls_multi_is_edge_trigg_supported (struct DaemonOptions *s) } +#ifdef mhd_HAVE_TLS_ACME +MHD_INTERNAL MHD_FN_PURE_ bool +mhd_tls_multi_is_acme_alpn_supported (struct DaemonOptions *s) +{ + switch (s->tls) + { + case MHD_TLS_BACKEND_NONE: + mhd_UNREACHABLE (); + return false; + case MHD_TLS_BACKEND_ANY: +# ifdef MHD_SUPPORT_GNUTLS + if (mhd_tls_gnu_is_acme_alpn_supported (s) + && mhd_tls_gnu_is_inited_fine ()) + return true; +# endif +# ifdef MHD_SUPPORT_OPENSSL + if (mhd_tls_open_is_acme_alpn_supported (s) + && mhd_tls_open_is_inited_fine ()) + return true; +# endif +# ifdef MHD_SUPPORT_MBEDTLS + if (mhd_tls_mbed_is_acme_alpn_supported (s) + && mhd_tls_mbed_is_inited_fine ()) + return true; +# endif + return false; + case MHD_TLS_BACKEND_GNUTLS: +# ifdef MHD_SUPPORT_GNUTLS + /* Ignore "backend inited" status here, + it will be checked on daemon TLS init */ + return mhd_tls_gnu_is_acme_alpn_supported (s); +# endif + break; + case MHD_TLS_BACKEND_OPENSSL: +# ifdef MHD_SUPPORT_OPENSSL + /* Ignore "backend inited" status here, + it will be checked on daemon TLS init */ + return mhd_tls_open_is_acme_alpn_supported (s); +# endif + break; + case MHD_TLS_BACKEND_MBEDTLS: +# ifdef MHD_SUPPORT_MBEDTLS + /* Ignore "backend inited" status here, + it will be checked on daemon TLS init */ + return mhd_tls_mbed_is_acme_alpn_supported (s); +# endif + break; + default: + mhd_UNREACHABLE (); + break; + } + return false; +} + + +#endif /* mhd_HAVE_TLS_ACME */ + /** * Initialise selected TLS backend for the daemon * @param route the selected TLS backend @@ -462,6 +531,104 @@ mhd_tls_multi_thread_cleanup (struct mhd_TlsMultiDaemonData *restrict d_tls) #endif /* mhd_HAVE_TLS_THREAD_CLEANUP */ +/* ** Credentials creation / destruction ** */ + +#ifdef mhd_HAVE_TLS_ACME + +MHD_INTERNAL MHD_FN_MUST_CHECK_RESULT_ +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) +MHD_FN_PAR_NONNULL_ (4) MHD_FN_PAR_NONNULL_ (6) +MHD_FN_PAR_OUT_ (2) +MHD_FN_PAR_IN_SIZE_ (4, 3) MHD_FN_PAR_IN_SIZE_ (6, 5) +MHD_FN_PAR_IN_SIZE_ (8, 7) +MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_CSTR_ (6) MHD_FN_PAR_CSTR_ (8) +enum mhd_TlsCredCreateResult +mhd_tls_multi_cred_create (struct mhd_TlsMultiDaemonData *restrict d_tls, + union mhd_TlsCredDataPtr *restrict pp_cred, + size_t cert_len, + const char *restrict cert, + size_t key_len, + const char *restrict key, + size_t pass_len, + const char *restrict pass) +{ + switch (d_tls->choice) + { +# ifdef MHD_SUPPORT_GNUTLS + case mhd_TLS_MULTI_ROUTE_GNU: + return mhd_tls_gnu_cred_create (d_tls->data.gnutls, + pp_cred, + cert_len, + cert, + key_len, + key, + pass_len, + pass); +# endif +# ifdef MHD_SUPPORT_OPENSSL + case mhd_TLS_MULTI_ROUTE_OPEN: + return mhd_tls_open_cred_create (d_tls->data.openssl, + pp_cred, + cert_len, + cert, + key_len, + key, + pass_len, + pass); +# endif +# ifdef MHD_SUPPORT_MBEDTLS + case mhd_TLS_MULTI_ROUTE_MBED: + return mhd_tls_mbed_cred_create (d_tls->data.mbedtls, + pp_cred, + cert_len, + cert, + key_len, + key, + pass_len, + pass); +# endif + case mhd_TLS_MULTI_ROUTE_NONE: + default: + mhd_UNREACHABLE (); + } + + return mhd_TLS_CRED_CREATE_UNSUPPORTED; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_tls_multi_cred_destroy (struct mhd_TlsMultiDaemonData *restrict d_tls, + union mhd_TlsCredDataPtr cred) +{ + switch (d_tls->choice) + { +# ifdef MHD_SUPPORT_GNUTLS + case mhd_TLS_MULTI_ROUTE_GNU: + mhd_tls_gnu_cred_destroy (d_tls->data.gnutls, + cred); + break; +# endif +# ifdef MHD_SUPPORT_OPENSSL + case mhd_TLS_MULTI_ROUTE_OPEN: + mhd_tls_open_cred_destroy (d_tls->data.openssl, + cred); + break; +# endif +# ifdef MHD_SUPPORT_MBEDTLS + case mhd_TLS_MULTI_ROUTE_MBED: + mhd_tls_mbed_cred_destroy (d_tls->data.mbedtls, + cred); + break; +# endif + case mhd_TLS_MULTI_ROUTE_NONE: + default: + mhd_UNREACHABLE (); + } +} + + +#endif /* mhd_HAVE_TLS_ACME */ + /* ** Connection initialisation / de-initialisation ** */ MHD_INTERNAL size_t @@ -858,3 +1025,33 @@ mhd_tls_multi_conn_get_alpn_prot (struct mhd_TlsMultiConnData *restrict c_tls) } return mhd_TLS_ALPN_PROT_NOT_SELECTED; } + + +#ifdef mhd_HAVE_TLS_ACME +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_tls_multi_conn_is_acme (struct mhd_TlsMultiConnData *restrict c_tls) +{ + switch (c_tls->choice) + { +# ifdef MHD_SUPPORT_GNUTLS + case mhd_TLS_MULTI_ROUTE_GNU: + return mhd_tls_gnu_conn_is_acme (c_tls->data.gnutls); +# endif +# ifdef MHD_SUPPORT_OPENSSL + case mhd_TLS_MULTI_ROUTE_OPEN: + return mhd_tls_open_conn_is_acme (c_tls->data.openssl); +# endif +# ifdef MHD_SUPPORT_MBEDTLS + case mhd_TLS_MULTI_ROUTE_MBED: + return mhd_tls_mbed_conn_is_acme (c_tls->data.mbedtls); +# endif + case mhd_TLS_MULTI_ROUTE_NONE: + default: + mhd_UNREACHABLE (); + break; + } + return false; +} + + +#endif /* mhd_HAVE_TLS_ACME */ diff --git a/src/mhd2/tls_multi_funcs.h b/src/mhd2/tls_multi_funcs.h @@ -115,6 +115,19 @@ MHD_INTERNAL bool mhd_tls_multi_is_edge_trigg_supported (struct DaemonOptions *s) MHD_FN_PURE_; +#ifdef mhd_HAVE_TLS_ACME +/** + * Check whether GnuTLS backend supports ACME ALPN challenge protocol + * @param s the daemon settings + * @return 'true' if the backend supports ACME ALPN challenge protocol, + * 'false' otherwise + */ +MHD_INTERNAL bool +mhd_tls_multi_is_acme_alpn_supported (struct DaemonOptions *s) +MHD_FN_PURE_; + +#endif /* mhd_HAVE_TLS_ACME */ + /** * Allocate and initialise daemon TLS parameters * @param d the daemon handle @@ -156,6 +169,65 @@ MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); # define mhd_tls_multi_thread_cleanup(d_tls) ((void) 0) #endif /* ! mhd_HAVE_TLS_THREAD_CLEANUP */ +/* ** Credentials creation / destruction ** */ + +#ifdef mhd_HAVE_TLS_ACME +/** + * Create TLS credentials from the provided certificate and key data + * + * Created credentials must be destroyed with #mhd_tls_gnu_cred_destroy_nodmn() + * function before de-initialisation of the daemon's TLS settings. + * + * @param d_tls the pointer to the daemon's TLS settings + * @param[out] pp_cred the backend-specific credentials pointer to initialise; + * the GnuTLS member is set to the allocated credentials on + * success and to NULL on failure + * @param cert_len the length of the @p cert buffer, not including the + * zero-termination byte, + * must not be zero + * @param cert the certificate data in PEM format, zero-terminated + * @param key_len the length of the @p key buffer, not including the + * zero-termination byte, + * must not be zero + * @param key the private key data in PEM format, zero-terminated + * @param pass_len the length of the @p pass buffer, not including the + * zero-termination byte, + * must be zero if the @p pass is NULL + * @param pass the password for the private key, zero-terminated, + * may be NULL if the private key is not password-protected + * @return #mhd_TLS_CRED_CREATE_OK on success, + * other enum mhd_TlsCredCreateResult values on failure + */ +MHD_INTERNAL enum mhd_TlsCredCreateResult +mhd_tls_multi_cred_create (struct mhd_TlsMultiDaemonData *restrict d_tls, + union mhd_TlsCredDataPtr *restrict pp_cred, + size_t cert_len, + const char *restrict cert, + size_t key_len, + const char *restrict key, + size_t pass_len, + const char *restrict pass) +MHD_FN_MUST_CHECK_RESULT_ +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) +MHD_FN_PAR_NONNULL_ (4) MHD_FN_PAR_NONNULL_ (6) +MHD_FN_PAR_OUT_ (2) +MHD_FN_PAR_IN_SIZE_ (4, 3) MHD_FN_PAR_IN_SIZE_ (6, 5) +MHD_FN_PAR_IN_SIZE_ (8, 7) +MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_CSTR_ (6) MHD_FN_PAR_CSTR_ (8); + +/** + * Release TLS credentials and free the allocated memory when no references + * remain. + * + * @param cred the backend-specific credentials pointer to release; the GnuTLS + * member must be non-NULL + */ +MHD_INTERNAL void +mhd_tls_multi_cred_destroy (struct mhd_TlsMultiDaemonData *restrict d_tls, + union mhd_TlsCredDataPtr cred) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* mhd_HAVE_TLS_ACME */ /* ** Connection initialisation / de-initialisation ** */ @@ -322,4 +394,18 @@ MHD_INTERNAL enum mhd_TlsAlpnProt mhd_tls_multi_conn_get_alpn_prot (struct mhd_TlsMultiConnData *restrict c_tls) MHD_FN_PAR_NONNULL_ALL_; + +#ifdef mhd_HAVE_TLS_ACME +/** + * Check whether the connection is ACME ALPN challenge connection + * @param c_tls the connection TLS handle + * @return 'true' if the connection is ACME ALPN challenge connection, + * 'false' otherwise + */ +MHD_INTERNAL bool +mhd_tls_multi_conn_is_acme (struct mhd_TlsMultiConnData *restrict c_tls) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* mhd_HAVE_TLS_ACME */ + #endif /* ! MHD_TLS_MULTI_FUNCS_H */ diff --git a/src/mhd2/tls_open_funcs.c b/src/mhd2/tls_open_funcs.c @@ -190,6 +190,18 @@ check_app_tls_settings (struct MHD_Daemon *restrict d, return MHD_SC_TLS_BACKEND_DAEMON_INCOMPATIBLE_SETTINGS; } +#ifdef mhd_HAVE_TLS_ACME + if (!mhd_tls_open_is_acme_alpn_supported (s) + && s->acme_alpn_required) + { + mhd_LOG_MSG (d, MHD_SC_TLS_BACKEND_DAEMON_INCOMPATIBLE_SETTINGS, + "This build of OpenSSL backend does not support " + "ACME ALPN challenge protocol, but daemon settings " + "require it"); + return MHD_SC_TLS_BACKEND_DAEMON_INCOMPATIBLE_SETTINGS; + } +#endif + return MHD_SC_OK; } diff --git a/src/mhd2/tls_open_funcs.h b/src/mhd2/tls_open_funcs.h @@ -117,6 +117,15 @@ struct DaemonOptions; /* Forward declaration */ */ #define mhd_tls_open_is_edge_trigg_supported(s) (! ! 0) +#ifdef mhd_HAVE_TLS_ACME +/** + * Check whether OpenSSL backend supports ACME ALPN challenge protocol + * @param s the daemon settings + * @return 'true' if the backend supports ACME ALPN challenge protocol, + * 'false' otherwise + */ +# define mhd_tls_open_is_acme_alpn_supported(s) ((void)(s), (!!0)) +#endif /* mhd_HAVE_TLS_ACME */ /** * Allocate and initialise daemon TLS parameters @@ -154,6 +163,26 @@ MHD_INTERNAL void mhd_tls_open_thread_cleanup (struct mhd_TlsOpenDaemonData *restrict d_tls) MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1); +/* ** Credentials creation / destruction ** */ + +#define mhd_tls_open_cred_create(d_tls, pp_c, cert_l, cert, k_l, k, ps_l, ps) \ + ((void)d_tls, (void)pp_c, (void)cert_l, (void)cert, \ + (void)k_l, (void)k, (void)ps_l, (void)ps, \ + mhd_TLS_CRED_CREATE_UNSUPPORTED) +#define mhd_tls_open_cred_destroy_nodmn(cred) ((void)(cred)) + +/** + * Release TLS credentials and free the allocated memory when no references + * remain. + * + * @param d_tls the pointer to the daemon's TLS settings + * @param cred the backend-specific credentials pointer to release; the OpenSSL + * member must be non-NULL + */ +#define mhd_tls_open_cred_destroy(d_tls, cred) \ + do { (void)(d_tls); \ + mhd_tls_open_cred_destroy_nodmn ((cred)); } while (0) + /* ** Connection initialisation / de-initialisation ** */ struct mhd_ConnSocket; /* Forward declaration */ @@ -335,4 +364,14 @@ MHD_INTERNAL enum mhd_TlsAlpnProt mhd_tls_open_conn_get_alpn_prot (struct mhd_TlsOpenConnData *restrict c_tls) MHD_FN_PAR_NONNULL_ALL_; +#ifdef mhd_HAVE_TLS_ACME +/** + * Check whether the connection is ACME ALPN challenge connection + * @param c_tls the connection TLS handle + * @return 'true' if the connection is ACME ALPN challenge connection, + * 'false' otherwise + */ +# define mhd_tls_open_conn_is_acme(c_tls) (((void)(c_tls)), (!!0)) +#endif /* mhd_HAVE_TLS_ACME */ + #endif /* ! MHD_TLS_OPEN_FUNCS_H */