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