commit db4354649a0a483d44fb979f06e6d40c55b49edd
parent 03978499e6055c7e416108a3993441f1a33fe7a6
Author: Evgeny Grin (Karlson2k) <k2k@drgrin.dev>
Date: Wed, 19 Aug 2026 18:47:22 +0200
Added ACME ALPN challenge support for OpenSSL backend
Diffstat:
5 files changed, 1111 insertions(+), 10 deletions(-)
diff --git a/src/incl_priv/mhd_sys_options.h b/src/incl_priv/mhd_sys_options.h
@@ -721,6 +721,33 @@
&& ! MHD_FORCE_USE_GNUTLS_MHD_CLIENTHELLO_BODY_PARSE */
# endif /* HAVE_GNUTLS_HANDSHAKE_SET_HOOK_FUNCTION */
# endif /* MHD_SUPPORT_GNUTLS */
+# ifdef MHD_SUPPORT_OPENSSL
+# ifndef MHD_NO_OPENSSL_ACME_ALPN
+/**
+ * Indicate that ACME ALPN challenge support is available in the OpenSSL backend
+ */
+# define mhd_HAVE_OPENSSL_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_OPENSSL_MHD_CLIENTHELLO_BODY_PARSE to use MHD internal
+ parser for ClientHello body instead of OpenSSL parser */
+# if defined(MHD_FORCE_USE_MHD_CLIENTHELLO_BODY_PARSE) \
+ && !defined(MHD_FORCE_USE_OPENSSL_MHD_CLIENTHELLO_BODY_PARSE)
+/**
+ * Use MHD internal parser for ClientHello body instead of OpenSSL parser
+ */
+# define MHD_FORCE_USE_OPENSSL_MHD_CLIENTHELLO_BODY_PARSE 1
+# endif
+# ifndef MHD_FORCE_USE_OPENSSL_MHD_CLIENTHELLO_BODY_PARSE
+# define mhd_USE_OPENSSL_CLIENT_HELLO_GET0_EXT 1
+# endif /* ! MHD_FORCE_USE_OPENSSL_MHD_CLIENTHELLO_BODY_PARSE */
+# ifndef mhd_USE_OPENSSL_CLIENT_HELLO_GET0_EXT
+# ifndef mhd_HAVE_TLS_MHD_CLIENTHELLO_BODY_PARSE
+# define mhd_HAVE_TLS_MHD_CLIENTHELLO_BODY_PARSE 1
+# endif /* ! mhd_HAVE_TLS_MHD_CLIENTHELLO_BODY_PARSE */
+# endif /* MHD_FORCE_USE_OPENSSL_MHD_CLIENTHELLO_BODY_PARSE */
+# endif /* MHD_NO_OPENSSL_ACME_ALPN */
+# endif /* MHD_SUPPORT_OPENSSL */
# else /* ! MHD_SUPPORT_ACME */
# ifdef MHD_FORCE_USE_MHD_CLIENTHELLO_BODY_PARSE
# undef MHD_FORCE_USE_MHD_CLIENTHELLO_BODY_PARSE
@@ -728,11 +755,14 @@
# ifdef MHD_FORCE_USE_GNUTLS_MHD_CLIENTHELLO_BODY_PARSE
# undef MHD_FORCE_USE_GNUTLS_MHD_CLIENTHELLO_BODY_PARSE
# endif
+# ifdef MHD_FORCE_USE_OPENSSL_MHD_CLIENTHELLO_BODY_PARSE
+# undef MHD_FORCE_USE_OPENSSL_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)
+ (defined(mhd_HAVE_GNUTLS_ACME) || defined(mhd_HAVE_OPENSSL_ACME))
/**
* Have support for ACME ALPN challenge
* @warning If MHD_SUPPORT_ACME is enabled but this macros is not defined then
diff --git a/src/mhd2/tls_open_conn_data.h b/src/mhd2/tls_open_conn_data.h
@@ -83,6 +83,19 @@ struct mhd_TlsOpenConnData
* 'true' if received EOF (the remote side initiated shutting down)
*/
bool shut_tls_wr_received;
+
+#ifdef mhd_HAVE_OPENSSL_ACME
+ /**
+ * 'true' if the connection is used for ACME ALPN challenge
+ */
+ bool is_acme;
+ /**
+ * '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_OPENSSL_ACME */
+
#ifndef NDEBUG
/**
* Debugging data
diff --git a/src/mhd2/tls_open_daemon_data.h b/src/mhd2/tls_open_daemon_data.h
@@ -54,6 +54,25 @@
#include "tls_open_tls_lib.h"
/**
+ * The OpenSSL credentials data
+ */
+struct mhd_TlsOpenCredData
+{
+ /**
+ * The certificate object
+ */
+ X509 *cert;
+ /**
+ * The private key object
+ */
+ EVP_PKEY *key;
+ /**
+ * The intermediate certificates chain object
+ */
+ STACK_OF (X509) * chain;
+};
+
+/**
* The structure with daemon-specific OpenSSL data
*/
struct mhd_TlsOpenDaemonData
@@ -79,6 +98,13 @@ struct mhd_TlsOpenDaemonData
* The size of the data pointed by @a alpn_prots
*/
unsigned int alpn_prots_size;
+
+#ifdef mhd_HAVE_OPENSSL_ACME
+ /**
+ * The list of ACME ALPN certificates
+ */
+ struct mhd_TlsCertsList *acme_certs;
+#endif /* mhd_HAVE_OPENSSL_ACME */
};
#endif /* ! MHD_TLS_OPEN_DAEMON_DATA_H */
diff --git a/src/mhd2/tls_open_funcs.c b/src/mhd2/tls_open_funcs.c
@@ -58,8 +58,10 @@
#include "mhd_assert.h"
#include "mhd_unreachable.h"
#include "mhd_assume.h"
+#include "mhd_predict.h"
#include "mhd_str.h"
+#include "mhd_str_types.h"
#include "mhd_conn_socket.h"
#include "mhd_tls_internal.h"
@@ -76,6 +78,10 @@
#include "daemon_logger.h"
+#ifdef mhd_HAVE_OPENSSL_ACME
+# include "mhd_tls_acme_func.h"
+#endif /* mhd_HAVE_OPENSSL_ACME */
+
#include "microhttpd2_portability.h"
#include "mhd_public_api.h"
@@ -131,6 +137,16 @@ mhd_tls_open_dbg_print_errs (const char *msg,
static bool openssl_lib_inited = false;
+#ifdef mhd_HAVE_OPENSSL_ACME
+/**
+ * The index of the pointer to MHD connection TLS data in the "ex_data" storage
+ * of the TLS session.
+ * This leaves the "app_data" (index zero of the "ex_data" storage) for other
+ * possible uses.
+ */
+static int conn_mhd_ctls_idx = -1;
+#endif /* mhd_HAVE_OPENSSL_ACME */
+
MHD_INTERNAL void
mhd_tls_open_global_init_once (void)
{
@@ -146,6 +162,18 @@ mhd_tls_open_global_init_once (void)
this call would make sure that the library is initialised before used. */
openssl_lib_inited = openssl_lib_inited
&& (0 < OPENSSL_init_ssl (0, NULL));
+
+#ifdef mhd_HAVE_OPENSSL_ACME
+ if (openssl_lib_inited)
+ {
+ conn_mhd_ctls_idx = SSL_get_ex_new_index (0,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ openssl_lib_inited = (0 <= conn_mhd_ctls_idx);
+ }
+#endif /* mhd_HAVE_OPENSSL_ACME */
}
@@ -820,6 +848,11 @@ daemon_deinit_lib_ctx (struct mhd_TlsOpenDaemonData *restrict d_tls)
# define mhd_ALPN_CODE_HTTP3 \
'h', '3' /* Registered value for HTTP/3 */
#endif /* Disabled code */
+#ifdef mhd_HAVE_OPENSSL_ACME
+/* Registered value for ACME TLS-ALPN-01 challenge */
+# define mhd_ALPN_CODE_ACME \
+ 'a', 'c', 'm', 'e', '-', 't', 'l', 's', '/', '1'
+#endif /* mhd_HAVE_OPENSSL_ACME */
#ifdef MHD_SUPPORT_HTTP2
@@ -839,6 +872,13 @@ static const unsigned char alpn_list_http1x_only[] = {
,
mhd_ALPN_H1_0_LEN, mhd_ALPN_CODE_HTTP1_0
};
+#ifdef mhd_HAVE_OPENSSL_ACME
+/* RFC 8737, section 3: the ACME TLS-ALPN-01 challenge is answered with
+ the "acme-tls/1" protocol and no other protocol */
+static const unsigned char alpn_list_acme_only[] = {
+ mhd_ALPN_ACME_LEN, mhd_ALPN_CODE_ACME
+};
+#endif /* mhd_HAVE_OPENSSL_ACME */
#ifndef OPENSSL_NO_NEXTPROTONEG
/**
@@ -868,7 +908,7 @@ get_npn_list (SSL *sess,
/**
* Select protocol from the provided list for ALPN extension
- * @param sess the TLS session (ignored)
+ * @param sess the TLS session
* @param[out] out the pointer to get the location of selected protocol value
* @param[out] outlen the size of the selected protocol value
* @param in the list of protocols values provided by the client
@@ -885,9 +925,32 @@ select_alpn_prot (SSL *sess,
unsigned int inlen,
void *cls)
{
- struct mhd_TlsOpenDaemonData *const d_tls =
+ struct mhd_TlsOpenDaemonData *const restrict d_tls =
(struct mhd_TlsOpenDaemonData *)cls;
- (void)sess; /* Unused */
+
+#ifdef mhd_HAVE_OPENSSL_ACME
+ if (!0)
+ {
+ const struct mhd_TlsOpenConnData *const restrict c_tls =
+ (const struct mhd_TlsOpenConnData *)SSL_get_ex_data (sess,
+ conn_mhd_ctls_idx);
+ mhd_assert (NULL != c_tls);
+
+ if (c_tls->is_acme)
+ {
+ /* ALPN has been checked: ACME TLS-ALPN-01 only */
+ mhd_assert (inlen == (unsigned int)sizeof(alpn_list_acme_only));
+ mhd_assert (0 == memcmp (in, alpn_list_acme_only, inlen));
+
+ *out = alpn_list_acme_only + 1; /* Skip the length byte */
+ *outlen = (unsigned char)mhd_ALPN_ACME_LEN;
+ return SSL_TLSEXT_ERR_OK; /* Success */
+ }
+ }
+#else /* ! mhd_HAVE_OPENSSL_ACME */
+ (void)sess; /* Unused */
+#endif /* ! mhd_HAVE_OPENSSL_ACME */
+
if (OPENSSL_NPN_NEGOTIATED ==
SSL_select_next_proto ((unsigned char **)mhd_DROP_CONST (out),
outlen,
@@ -901,6 +964,444 @@ select_alpn_prot (SSL *sess,
}
+#ifdef mhd_USE_OPENSSL_CLIENT_HELLO_GET0_EXT
+# ifndef mhd_HAVE_OPENSSL_ACME
+# error mhd_USE_OPENSSL_CLIENT_HELLO_GET0_EXT requires mhd_HAVE_OPENSSL_ACME
+# endif /* ! mhd_HAVE_OPENSSL_ACME */
+#endif /* mhd_USE_OPENSSL_CLIENT_HELLO_GET0_EXT */
+
+#ifdef mhd_HAVE_OPENSSL_ACME
+
+/**
+ * The TLS session ID context for the ACME ALPN challenge connections.
+ * The value is not interpreted by the TLS library, it only has to differ from
+ * the context used for the normal connections, which is the empty default one.
+ */
+static const unsigned char acme_sid_ctx[] = { 'A', 'C', 'M', 'E' };
+
+
+/**
+ * Callback to prevent storing of the ACME ALPN challenge session
+ * @param sess the TLS session (unused)
+ * @param is_forward_secure set to non-zero if the session is forward secure
+ * (unused)
+ * @return always non-zero, the session must not be stored
+ */
+static int
+acme_sess_not_resumable (SSL *sess,
+ int is_forward_secure)
+{
+ (void)sess; /* Unused */
+ (void)is_forward_secure; /* Unused */
+ return !0;
+}
+
+
+/**
+ * Exclude the connection from the TLS sessions resumption.
+ *
+ * The ACME certificate is set for the single connection, while a resumed
+ * handshake sends no certificate at all and the client keeps the certificate
+ * of the original session. Therefore both directions must be blocked:
+ * the session of this connection must not be stored, so that no normal
+ * connection could resume it and get the ACME certificate, and this connection
+ * must not resume any session stored for the normal connections, as it would
+ * get the daemon certificate instead of the ACME certificate.
+ *
+ * The session ID context must be set before the TLS library looks up
+ * the session requested by the client, the callback preventing the storing is
+ * used later.
+ *
+ * @param sess the TLS session to exclude from the sessions resumption
+ * @return 'true' on success,
+ * 'false' otherwise
+ */
+static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_ bool
+mark_acme_no_resume (SSL *sess)
+{
+ /* Any stored session has another session ID context, which is treated by
+ the TLS library as a cache miss */
+ if (0 == SSL_set_session_id_context (sess,
+ acme_sid_ctx,
+ (unsigned int)sizeof(acme_sid_ctx)))
+ return false;
+
+ SSL_set_not_resumable_session_callback (sess,
+ &acme_sess_not_resumable);
+ return true;
+}
+
+
+/**
+ * Result of the application of the ACME ALPN challenge check to a connection
+ */
+enum mhd_TlsOpenConnAcmeCheckFinishResult
+{
+ /**
+ * The connection is prepared and the handshake can be continued, either as
+ * a normal HTTPS connection or as an ACME ALPN challenge connection
+ */
+ mhd_TLS_OPEN_ACME_CHECK_FINISH_OK = 0,
+ /**
+ * The TLS library failed to use the ACME credentials for the connection.
+ * The handshake must be aborted, the "internal error" alert matches
+ * the failure.
+ */
+ mhd_TLS_OPEN_ACME_CHECK_FINISH_ERROR,
+ /**
+ * The second ClientHello message, received after TLS 1.3 HelloRetryRequest,
+ * has been recognised differently than the first one.
+ * The handshake must be aborted, the "illegal parameter" alert matches
+ * the failure.
+ */
+ mhd_TLS_OPEN_ACME_CHECK_FINISH_MISMATCH_HRR
+};
+
+
+/**
+ * Apply the result of the ACME ALPN challenge check to the connection.
+ *
+ * If the ClientHello message has been recognised as the challenge, the ACME
+ * certificate, the private key and the certificates chain are set for
+ * the connection and the connection is excluded from the TLS sessions
+ * resumption. Otherwise only the state of the connection is updated, as
+ * the ALPN protocols for a normal HTTPS connection are set for the TLS
+ * context.
+ *
+ * The function must be called for every checked ClientHello message,
+ * including the second one received after TLS 1.3 HelloRetryRequest.
+ *
+ * @param[in,out] c_tls the connection TLS handle
+ * @param acme_certs the list of the ACME certificates used for the check
+ * @param acme_cred the credentials selected by the check,
+ * or NULL if the ClientHello message is not
+ * an ACME ALPN challenge
+ * @return #mhd_TLS_OPEN_ACME_CHECK_FINISH_OK if the handshake can be
+ * continued,
+ * other values if the handshake must be aborted
+ * @warning If @p acme_cred is not NULL, the list of the ACME certificates is
+ * read-locked by the check and is unlocked by this function
+ */
+static MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ (1)
+MHD_FN_PAR_NONNULL_ (2) enum mhd_TlsOpenConnAcmeCheckFinishResult
+mhd_tls_open_conn_acme_check_finish (
+ struct mhd_TlsOpenConnData *restrict c_tls,
+ struct mhd_TlsCertsList *restrict acme_certs,
+ const union mhd_TlsCredDataPtr *restrict acme_cred)
+{
+ enum mhd_TlsOpenConnAcmeCheckFinishResult res;
+
+ mhd_assert (NULL != c_tls);
+
+ if (NULL == acme_cred)
+ {
+ /* Normal (not ACME ALPN challenge) connection */
+ if (c_tls->is_acme) /* Second ClientHello after TLS 1.3 HRR with another result */
+ {
+ mhd_assert (c_tls->clienthello_processed);
+ c_tls->is_acme = false; /* Reset to non-ACME connection */
+ return mhd_TLS_OPEN_ACME_CHECK_FINISH_MISMATCH_HRR; /* Unmatched ClientHello, abort handshake */
+ }
+
+ /* Process the connection as a normal HTTPS connection. */
+ c_tls->clienthello_processed = true;
+ return mhd_TLS_OPEN_ACME_CHECK_FINISH_OK; /* Non-ACME connection exit point */
+ }
+
+ /* Assume OK unless the checks below fail */
+ res = mhd_TLS_OPEN_ACME_CHECK_FINISH_OK;
+
+ if (c_tls->clienthello_processed && !c_tls->is_acme)
+ res = mhd_TLS_OPEN_ACME_CHECK_FINISH_MISMATCH_HRR;
+
+ if (mhd_TLS_OPEN_ACME_CHECK_FINISH_OK == res)
+ {
+ struct mhd_TlsOpenCredData *const restrict acme_open_cred =
+ acme_cred->open;
+
+ mhd_assert (NULL != acme_open_cred);
+
+ ERR_clear_error ();
+ /* Remove the daemon certificate: the certificate of another key type
+ would not be replaced by the ACME certificate and could be selected
+ by the TLS library instead of it. */
+ SSL_certs_clear (c_tls->sess);
+ /* The TLS library takes its own references for the objects, therefore
+ the ACME credentials could be removed by the application at any moment
+ after this point */
+ if (!mark_acme_no_resume (c_tls->sess)
+ || (0 >= SSL_use_certificate (c_tls->sess,
+ acme_open_cred->cert))
+ || (0 >= SSL_set1_chain (c_tls->sess,
+ acme_open_cred->chain))
+ || (0 >= SSL_use_PrivateKey (c_tls->sess,
+ acme_open_cred->key)))
+ {
+ mhd_DBG_PRINT_TLS_ERRS ();
+ res = mhd_TLS_OPEN_ACME_CHECK_FINISH_ERROR;
+ }
+ }
+ mhd_daemon_acme_cert_r_unlock (acme_certs);
+
+ c_tls->clienthello_processed = true;
+ c_tls->is_acme = (mhd_TLS_OPEN_ACME_CHECK_FINISH_OK == res);
+ return res;
+}
+
+
+# ifdef mhd_USE_OPENSSL_CLIENT_HELLO_GET0_EXT
+
+
+/**
+ * Check whether the ClientHello message is the ACME TLS-ALPN-01 challenge and
+ * select the ACME credentials for the indicated domain.
+ *
+ * The extensions are taken from the ClientHello message parsed by the TLS
+ * library. The TLS library rejects any duplicated extension before this
+ * function is called.
+ *
+ * @param sess the TLS session processing the ClientHello message
+ * @param acme_certs the list of the 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().
+ */
+static MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_
+const union mhd_TlsCredDataPtr *
+clnthello_check_acme (SSL *sess,
+ struct mhd_TlsCertsList *restrict acme_certs)
+{
+ struct mhd_TlsClientHelloAcmeCheckData check;
+ bool all_checks_ok;
+
+ mhd_tls_acme_check_ext_init (&check,
+ acme_certs);
+
+ all_checks_ok = true;
+
+ /* The ALPN extension is checked first as it is checked without locking
+ the list of the ACME certificates. Any typical HTTPS ClientHello is
+ rejected by this check alone. */
+ if (all_checks_ok)
+ {
+ static const unsigned int ext_alpn_id =
+ TLSEXT_TYPE_application_layer_protocol_negotiation;
+ const unsigned char *ext_alpn_data;
+ size_t ext_alpn_size;
+
+ all_checks_ok = (0 != SSL_client_hello_get0_ext (sess,
+ ext_alpn_id,
+ &ext_alpn_data,
+ &ext_alpn_size));
+ if (all_checks_ok)
+ all_checks_ok = mhd_tls_acme_check_ext (ext_alpn_id,
+ ext_alpn_size,
+ ext_alpn_data,
+ &check);
+ }
+
+ if (all_checks_ok)
+ {
+ static const unsigned int ext_sni_id = TLSEXT_TYPE_server_name;
+ const unsigned char *ext_sni_data;
+ size_t ext_sni_size;
+
+ all_checks_ok = (0 != SSL_client_hello_get0_ext (sess,
+ ext_sni_id,
+ &ext_sni_data,
+ &ext_sni_size));
+ if (all_checks_ok)
+ all_checks_ok = mhd_tls_acme_check_ext (ext_sni_id,
+ ext_sni_size,
+ ext_sni_data,
+ &check);
+ }
+
+ /* The TLS library parsed the complete ClientHello message before calling
+ this function, no need to make additional check for parse completeness */
+ return mhd_tls_acme_check_ext_finish (&check,
+ all_checks_ok);
+}
+
+
+/**
+ * The TLS library callback for checking the ClientHello message for
+ * the ACME ALPN challenge.
+ *
+ * The callback is registered for the TLS context as the TLS library has no
+ * session-specific version of this callback.
+ *
+ * @param sess the TLS session processing the ClientHello message
+ * @param[out] alert the TLS alert to send if the handshake is aborted
+ * @param cls the closure, the pointer to the daemon TLS settings
+ * @return #SSL_CLIENT_HELLO_SUCCESS for a normal HTTPS ClientHello or after
+ * successful ACME setup,
+ * #SSL_CLIENT_HELLO_ERROR to abort the handshake otherwise
+ */
+static int
+check_clnt_hello_cb (SSL *sess,
+ int *alert,
+ void *cls)
+{
+ struct mhd_TlsOpenDaemonData *const restrict d_tls =
+ (struct mhd_TlsOpenDaemonData *)cls;
+ struct mhd_TlsOpenConnData *const restrict c_tls =
+ (struct mhd_TlsOpenConnData *)SSL_get_ex_data (sess,
+ conn_mhd_ctls_idx);
+ const union mhd_TlsCredDataPtr *acme_cred; /* Set to non-NULL if request is recognised ACME challenge */
+ enum mhd_TlsOpenConnAcmeCheckFinishResult res;
+
+ mhd_assert (NULL != c_tls);
+ mhd_assert (c_tls->sess == sess);
+
+ acme_cred = NULL;
+ if (mhd_daemon_has_acme_certs (d_tls->acme_certs))
+ acme_cred = clnthello_check_acme (sess,
+ d_tls->acme_certs);
+
+ res = mhd_tls_open_conn_acme_check_finish (c_tls,
+ d_tls->acme_certs,
+ acme_cred);
+
+ switch (res)
+ {
+ case mhd_TLS_OPEN_ACME_CHECK_FINISH_OK:
+ return SSL_CLIENT_HELLO_SUCCESS; /* Success exit point */
+
+ case mhd_TLS_OPEN_ACME_CHECK_FINISH_ERROR:
+ *alert = SSL_AD_INTERNAL_ERROR;
+ break;
+
+ case mhd_TLS_OPEN_ACME_CHECK_FINISH_MISMATCH_HRR:
+ *alert = SSL_AD_ILLEGAL_PARAMETER;
+ break;
+
+ default:
+ mhd_UNREACHABLE ();
+ *alert = SSL_AD_INTERNAL_ERROR;
+ break;
+ }
+
+ return SSL_CLIENT_HELLO_ERROR; /* Failure exit point */
+}
+
+
+# else /* ! mhd_USE_OPENSSL_CLIENT_HELLO_GET0_EXT */
+
+/**
+ * Callback to abort the TLS handshake.
+ *
+ * The messages callback cannot report any failure, therefore this callback is
+ * set by the messages callback when the handshake must be aborted. The TLS
+ * library calls it after the ClientHello message has been processed and
+ * before the response is constructed.
+ *
+ * @param sess the TLS session (unused)
+ * @param cls the closure (unused)
+ * @return always zero, the handshake is aborted with the "internal error"
+ * alert
+ */
+static int
+cert_cb_always_fail (SSL *sess,
+ void *cls)
+{
+ (void)sess; /* Unused */
+ (void)cls; /* Unused */
+ return 0;
+}
+
+
+/**
+ * The TLS library callback for checking the ClientHello message for
+ * the ACME ALPN challenge.
+ *
+ * The callback is set for the session, but only when the daemon has any ACME
+ * certificate. The body of the ClientHello message is parsed by MHD.
+ *
+ * The callback removes itself as soon as no ClientHello message can be
+ * received on the connection any more.
+ *
+ * @param write_p zero if the message is received, non-zero if it is sent
+ * @param version the TLS protocol version (unused)
+ * @param content_type the type of the record carrying the message
+ * @param buf the message data, including the handshake header
+ * @param len the size of the data pointed by @a buf
+ * @param sess the TLS session processing the message
+ * @param cls the closure, the list of the ACME certificates
+ */
+static void
+check_hello_msg_acme (int write_p,
+ int version,
+ int content_type,
+ const void *buf,
+ size_t len,
+ SSL *sess,
+ void *cls)
+{
+ struct mhd_TlsCertsList *const restrict acme_certs =
+ (struct mhd_TlsCertsList *)cls;
+ struct mhd_TlsOpenConnData *const restrict c_tls =
+ (struct mhd_TlsOpenConnData *)SSL_get_ex_data (sess,
+ conn_mhd_ctls_idx);
+ const unsigned char *const restrict msg = (const unsigned char *)buf;
+ const union mhd_TlsCredDataPtr *acme_cred; /* Set to non-NULL if request is recognised ACME challenge */
+
+ (void)version; /* Unused */
+ mhd_assert (NULL != c_tls);
+ mhd_assert (c_tls->sess == sess);
+
+ if ((0 != write_p) || (SSL3_RT_HANDSHAKE != content_type))
+ return; /* Not an incoming handshake message */
+
+ /* The first byte of the handshake header is the type of the message,
+ the body of the message follows the header */
+ if ((((size_t)SSL3_HM_HEADER_LENGTH) >= len)
+ || (SSL3_MT_CLIENT_HELLO != msg[0]))
+ {
+ /* The client has sent another handshake message, no ClientHello can be
+ received on this connection any more: TLS1.3 has no renegotiation and
+ TLS1.2 renegotiation is rejected by #SSL_OP_NO_RENEGOTIATION */
+ SSL_set_msg_callback (sess,
+ NULL);
+ return;
+ }
+
+ /* If this is the second ClientHello then stop the callback,
+ the TLS library accepts no more ClientHello */
+ if (c_tls->clienthello_processed)
+ SSL_set_msg_callback (sess,
+ NULL);
+
+ acme_cred =
+ mhd_tls_acme_check_clienthello_body (len - SSL3_HM_HEADER_LENGTH,
+ msg + SSL3_HM_HEADER_LENGTH,
+ acme_certs);
+
+ if (mhd_TLS_OPEN_ACME_CHECK_FINISH_OK !=
+ mhd_tls_open_conn_acme_check_finish (c_tls,
+ acme_certs,
+ acme_cred))
+ {
+ /* This callback cannot abort the handshake, the abort is performed by
+ the certificates callback after the ClientHello message is processed.
+ The alert cannot be selected this way: the client always receives
+ the "internal error" alert. The "illegal parameter" alert, used for
+ the unmatched ClientHello when the ClientHello callback is available,
+ is not delivered to the client. */
+ SSL_set_cert_cb (sess,
+ &cert_cb_always_fail,
+ NULL);
+ }
+}
+
+
+# endif /* ! mhd_USE_OPENSSL_CLIENT_HELLO_GET0_EXT */
+
+#endif /* mhd_HAVE_OPENSSL_ACME */
+
/**
* Initialise TLS server context
* @param d the daemon handle
@@ -1009,6 +1510,14 @@ daemon_init_ctx (struct MHD_Daemon *restrict d,
SSL_CTX_set_alpn_select_cb (d_tls->ctx,
&select_alpn_prot,
d_tls);
+#ifdef mhd_USE_OPENSSL_CLIENT_HELLO_GET0_EXT
+ /* The ACME ALPN challenge is detected by the ClientHello message.
+ The callback is set unconditionally as OpenSSL does not allow to set
+ it on connection (session) basis. */
+ SSL_CTX_set_client_hello_cb (d_tls->ctx,
+ &check_clnt_hello_cb,
+ d_tls);
+#endif /* mhd_USE_OPENSSL_CLIENT_HELLO_GET0_EXT */
#ifndef OPENSSL_NO_NEXTPROTONEG
SSL_CTX_set_next_protos_advertised_cb (d_tls->ctx,
&get_npn_list,
@@ -1318,6 +1827,10 @@ mhd_tls_open_daemon_init (struct MHD_Daemon *restrict d,
if (NULL == d_tls)
return MHD_SC_DAEMON_MEM_ALLOC_FAILURE;
+#ifdef mhd_HAVE_OPENSSL_ACME
+ d_tls->acme_certs = mhd_daemon_get_acme_certs (d);
+#endif /* mhd_HAVE_OPENSSL_ACME */
+
res = daemon_init_lib_ctx (d,
d_tls,
s);
@@ -1365,6 +1878,377 @@ mhd_tls_open_thread_cleanup (struct mhd_TlsOpenDaemonData *restrict d_tls)
}
+/* ** Credentials creation / destruction ** */
+
+#ifdef mhd_HAVE_OPENSSL_ACME
+
+/**
+ * Read the end-entity certificate and the chain of the signing certificates
+ * from the provided PEM data.
+ * @param d_tls the pointer to the daemon's TLS settings
+ * @param[in,out] cred the credentials data with the initialised (empty)
+ * @a chain member, the @a cert member is set on success
+ * @param cert_len the length of the @p cert buffer
+ * @param cert the certificates data in PEM format
+ * @return #mhd_TLS_CRED_CREATE_OK on success,
+ * other enum mhd_TlsCredCreateResult values on failure
+ */
+static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_
+MHD_FN_PAR_IN_SIZE_ (4, 3) enum mhd_TlsCredCreateResult
+cred_read_certs (struct mhd_TlsOpenDaemonData *restrict d_tls,
+ struct mhd_TlsOpenCredData *restrict cred,
+ int cert_len,
+ const char *restrict cert)
+{
+ BIO *bio;
+ enum mhd_TlsCredCreateResult res;
+
+ mhd_assert (NULL == cred->cert);
+ mhd_assert (NULL != cred->chain);
+
+ res = mhd_TLS_CRED_CREATE_OK;
+ bio = BIO_new_mem_buf (cert,
+ cert_len);
+ if (NULL == bio)
+ {
+ mhd_DBG_PRINT_TLS_ERRS ();
+ res = mhd_TLS_CRED_CREATE_ALLOC_FAILED;
+ }
+ else
+ {
+ cred->cert = X509_new_ex (d_tls->libctx,
+ NULL);
+ if (NULL == cred->cert)
+ {
+ mhd_DBG_PRINT_TLS_ERRS ();
+ res = mhd_TLS_CRED_CREATE_ALLOC_FAILED;
+ }
+ else
+ {
+ if (NULL == PEM_read_bio_X509_AUX (bio,
+ &(cred->cert),
+ &null_passwd_cb,
+ NULL))
+ {
+ mhd_DBG_PRINT_TLS_ERRS ();
+ res = mhd_TLS_CRED_CREATE_BAD_CRED_DATA;
+ }
+ else
+ {
+ do
+ {
+ X509 *inter_ca; /* Certifying certificate */
+
+ inter_ca = X509_new_ex (d_tls->libctx,
+ NULL);
+ if (NULL == inter_ca)
+ {
+ mhd_DBG_PRINT_TLS_ERRS ();
+ res = mhd_TLS_CRED_CREATE_ALLOC_FAILED;
+ break;
+ }
+
+ if (NULL != PEM_read_bio_X509 (bio,
+ &inter_ca,
+ &null_passwd_cb,
+ NULL))
+ {
+ mhd_NOWARN_USED_UNUSED
+
+ if (0 < sk_X509_push (cred->chain,
+ inter_ca))
+ {
+ /* The certificate 'inter_ca' is owned by the chain now.
+ Read the next certificate in the chain. */
+ continue;
+ }
+
+ mhd_RESTORE_WARN_USED_UNUSED
+
+ /* The cleanup path */
+ res = mhd_TLS_CRED_CREATE_ALLOC_FAILED;
+ }
+ else
+ {
+ unsigned long err;
+ err = ERR_peek_last_error ();
+
+ mhd_NOWARN_USED_UNUSED
+
+ if ((ERR_LIB_PEM == ERR_GET_LIB (err))
+ && (PEM_R_NO_START_LINE == ERR_GET_REASON (err)))
+ {
+ X509_free (inter_ca); /* Allocated but not used, not needed */
+ ERR_clear_error (); /* End of data, all certificates are read */
+ BIO_free (bio);
+ return mhd_TLS_CRED_CREATE_OK; /* Success exit point */
+ }
+
+ /* The cleanup path */
+ res = mhd_TLS_CRED_CREATE_BAD_CRED_DATA;
+
+ mhd_RESTORE_WARN_USED_UNUSED
+ }
+ mhd_DBG_PRINT_TLS_ERRS ();
+ mhd_assert (mhd_TLS_CRED_CREATE_OK != res);
+ X509_free (inter_ca); /* Empty or unusable, not needed */
+ break;
+ } while (!0);
+ mhd_assert (mhd_TLS_CRED_CREATE_OK != res);
+ }
+ X509_free (cred->cert);
+ }
+ BIO_free (bio);
+ }
+
+ mhd_assert (mhd_TLS_CRED_CREATE_OK != res);
+ return res;
+}
+
+
+/**
+ * Provide the application-supplied password for the private key.
+ *
+ * Unlike the OpenSSL built-in callback, this one uses the length known by MHD
+ * instead of deriving it from the zero-termination and refuses to truncate
+ * the password silently.
+ * @param[out] buf the buffer to put the password to
+ * @param size the size of the @a buf
+ * @param rwflag not used, the password is used for decryption only
+ * @param cls the pointer to the password string
+ * @return the number of characters put to the @a buf,
+ * -1 if the password does not fit the @a buf
+ */
+static int
+mem_passwd_cb (char *buf,
+ int size,
+ int rwflag,
+ void *cls)
+{
+ const struct MHD_String *const pass = (const struct MHD_String *)cls;
+
+ (void)rwflag; /* Unused */
+
+ if ((0 > size) || (pass->len > (size_t)size))
+ return -1; /* The password does not fit, do not truncate it silently */
+
+ memcpy (buf,
+ pass->cstr,
+ pass->len);
+ return (int)pass->len;
+}
+
+
+/**
+ * Read the private key from the provided PEM data and check that it matches
+ * the certificate.
+ * @param d_tls the pointer to the daemon's TLS settings
+ * @param[in,out] cred the credentials data with the set @a cert member,
+ * the @a key member is set on success
+ * @param key_len the length of the @p key buffer
+ * @param key the private key data in PEM format
+ * @param pass_len the length of the @p pass buffer, must be zero if the @p pass
+ * is NULL
+ * @param pass the password for the private key, zero-terminated,
+ * may be NULL
+ * @return #mhd_TLS_CRED_CREATE_OK on success,
+ * other enum mhd_TlsCredCreateResult values on failure
+ */
+static MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2)
+MHD_FN_PAR_NONNULL_ (4) MHD_FN_MUST_CHECK_RESULT_
+MHD_FN_PAR_IN_SIZE_ (4, 3) MHD_FN_PAR_IN_SIZE_ (6, 5) MHD_FN_PAR_CSTR_ (6)
+enum mhd_TlsCredCreateResult
+cred_read_key (struct mhd_TlsOpenDaemonData *restrict d_tls,
+ struct mhd_TlsOpenCredData *restrict cred,
+ int key_len,
+ const char *restrict key,
+ size_t pass_len,
+ const char *restrict pass)
+{
+ enum mhd_TlsCredCreateResult res;
+ BIO *bio;
+ EVP_PKEY *key_obj;
+ struct MHD_String pass_str;
+
+ mhd_assert (NULL != cred->cert);
+ mhd_assert (NULL == cred->key);
+ mhd_assert ((NULL != pass) || (0 == pass_len));
+
+ res = mhd_TLS_CRED_CREATE_OK;
+ pass_str.len = pass_len;
+ pass_str.cstr = pass;
+
+ bio = BIO_new_mem_buf (key,
+ key_len);
+ if (NULL == bio)
+ {
+ mhd_DBG_PRINT_TLS_ERRS ();
+ res = mhd_TLS_CRED_CREATE_ALLOC_FAILED;
+ }
+ else
+ {
+ key_obj =
+ PEM_read_bio_PrivateKey_ex (bio,
+ NULL,
+ (NULL == pass) ? &null_passwd_cb :
+ &mem_passwd_cb,
+ (NULL == pass) ? NULL : &pass_str,
+ d_tls->libctx,
+ NULL);
+ BIO_free (bio);
+
+ if (NULL == key_obj)
+ {
+ mhd_DBG_PRINT_TLS_ERRS ();
+ res = mhd_TLS_CRED_CREATE_BAD_CRED_DATA;
+ }
+ else
+ {
+ ERR_clear_error ();
+ if (1 != X509_check_private_key (cred->cert,
+ key_obj))
+ {
+ mhd_DBG_PRINT_TLS_ERRS ();
+ res = mhd_TLS_CRED_CREATE_BAD_CRED_DATA;
+ }
+ else
+ {
+ ERR_clear_error ();
+ cred->key = key_obj;
+ return mhd_TLS_CRED_CREATE_OK; /* Success exit point */
+ }
+ EVP_PKEY_free (key_obj);
+ }
+ }
+
+ return res; /* Failure exit point */
+}
+
+
+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_open_cred_create (struct mhd_TlsOpenDaemonData *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)
+{
+ struct mhd_TlsOpenCredData *cred;
+ enum mhd_TlsCredCreateResult res;
+ int cert_len_i;
+ int key_len_i;
+
+ mhd_assert (0 != cert_len);
+ mhd_assert (0 != key_len);
+ mhd_assert ((NULL != pass) || (0 == pass_len));
+
+ pp_cred->open = NULL;
+
+ cert_len_i = (int)cert_len;
+ key_len_i = (int)key_len;
+ if (mhd_COND_HARDLY_EVER (cert_len != (size_t)cert_len_i)
+ || mhd_COND_HARDLY_EVER (0 > cert_len_i)
+ || mhd_COND_HARDLY_EVER (key_len != (size_t)key_len_i)
+ || mhd_COND_HARDLY_EVER (0 > key_len_i))
+ return mhd_TLS_CRED_CREATE_BAD_CRED_DATA; /* The data is too large */
+
+ cred = (struct mhd_TlsOpenCredData *)
+ mhd_calloc (1,
+ sizeof(struct mhd_TlsOpenCredData));
+ if (NULL == cred)
+ return mhd_TLS_CRED_CREATE_ALLOC_FAILED;
+
+# ifndef NDEBUG
+# ifndef HAVE_NULL_PTR_ALL_ZEROS
+ cred->cert = NULL;
+ cred->key = NULL;
+ cred->chain = NULL;
+# endif /* HAVE_NULL_PTR_ALL_ZEROS */
+# endif /* NDEBUG */
+
+ ERR_clear_error ();
+ /* The chain is always allocated (and stays empty if no chain is provided) as
+ the empty chain must replace the chain inherited from the daemon's context
+ when the credentials are used for the connection. */
+ cred->chain = sk_X509_new_null ();
+ if (NULL == cred->chain)
+ {
+ mhd_DBG_PRINT_TLS_ERRS ();
+ res = mhd_TLS_CRED_CREATE_ALLOC_FAILED;
+ }
+ else
+ {
+ ERR_clear_error ();
+
+ res = cred_read_certs (d_tls,
+ cred,
+ cert_len_i,
+ cert);
+ if (mhd_TLS_CRED_CREATE_OK == res)
+ {
+ res = cred_read_key (d_tls,
+ cred,
+ key_len_i,
+ key,
+ pass_len,
+ pass);
+ if (mhd_TLS_CRED_CREATE_OK == res)
+ {
+ pp_cred->open = cred;
+ return mhd_TLS_CRED_CREATE_OK; /* Success exit point */
+ }
+
+ /* Below is a clean-up code path */
+ X509_free (cred->cert);
+ }
+ mhd_NOWARN_USED_UNUSED
+ sk_X509_pop_free (cred->chain,
+ X509_free);
+
+ mhd_RESTORE_WARN_USED_UNUSED
+ }
+
+ free (cred);
+ mhd_assert (mhd_TLS_CRED_CREATE_OK != res);
+ return res; /* Failure exit point */
+}
+
+
+MHD_INTERNAL void
+mhd_tls_open_cred_destroy_nodmn (union mhd_TlsCredDataPtr cred)
+{
+ mhd_ASSUME (NULL != cred.open);
+ mhd_assert (NULL != cred.open->key);
+ mhd_assert (NULL != cred.open->cert);
+ mhd_assert (NULL != cred.open->chain);
+
+ /* Release in the reverse order of the creation.
+ The objects still used by any TLS session or context are not destroyed
+ here as OpenSSL holds its own references for them. */
+ EVP_PKEY_free (cred.open->key);
+ X509_free (cred.open->cert);
+ mhd_NOWARN_USED_UNUSED
+ sk_X509_pop_free (cred.open->chain,
+ &X509_free);
+
+ mhd_RESTORE_WARN_USED_UNUSED
+ free (cred.open);
+
+}
+
+
+#endif /* mhd_HAVE_OPENSSL_ACME */
+
+
/* ** Connection initialisation / de-initialisation ** */
MHD_INTERNAL size_t
@@ -1374,6 +2258,54 @@ mhd_tls_open_conn_get_tls_size_v (void)
}
+#ifdef mhd_HAVE_OPENSSL_ACME
+/**
+ * Prepare the connection data for the detection of the ACME ALPN challenge
+ * @param d_tls the daemon TLS settings
+ * @param[in,out] c_tls the connection TLS handle with the initialised session
+ * @return 'true' on success,
+ * 'false' otherwise
+ */
+static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_ bool
+conn_init_acme_data (const struct mhd_TlsOpenDaemonData *restrict d_tls,
+ struct mhd_TlsOpenConnData *restrict c_tls)
+{
+ bool res;
+
+ /* c_tls is created by calloc(), all bool member must be 'false' */
+ mhd_assert (!c_tls->clienthello_processed);
+ mhd_assert (!c_tls->is_acme);
+
+ /* The additional context for ClientHello and ALPN callbacks */
+ res = (0 != SSL_set_ex_data (c_tls->sess,
+ conn_mhd_ctls_idx,
+ c_tls));
+
+# ifdef mhd_USE_OPENSSL_CLIENT_HELLO_GET0_EXT
+ (void)d_tls; /* Unused, the ClientHello callback is set for the context */
+# else /* ! mhd_USE_OPENSSL_CLIENT_HELLO_GET0_EXT */
+ /* Conditionally process ClientHello message - only if any ACME certificates
+ are present */
+ if (res
+ && (mhd_daemon_has_acme_certs (d_tls->acme_certs)))
+ {
+ /* The unparsed ClientHello message is provided only by the messages
+ callback */
+ (void)SSL_set_msg_callback_arg (c_tls->sess, /* does not actually return a value */
+ d_tls->acme_certs);
+ SSL_set_msg_callback (c_tls->sess,
+ &check_hello_msg_acme);
+ }
+# endif /* ! mhd_USE_OPENSSL_CLIENT_HELLO_GET0_EXT */
+ return res;
+}
+
+
+#else /* ! mhd_HAVE_OPENSSL_ACME */
+# define conn_init_acme_data(d_tls, c_tls) \
+ (((void) (d_tls)), ((void) (c_tls)), (! 0))
+#endif /* ! mhd_HAVE_OPENSSL_ACME */
+
MHD_INTERNAL MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_
MHD_FN_PAR_OUT_ (3) bool
mhd_tls_open_conn_init (const struct mhd_TlsOpenDaemonData *restrict d_tls,
@@ -1396,7 +2328,9 @@ mhd_tls_open_conn_init (const struct mhd_TlsOpenDaemonData *restrict d_tls,
return false;
}
- if (0 < SSL_set_fd (c_tls->sess, fd))
+ if ((0 < SSL_set_fd (c_tls->sess, fd))
+ && conn_init_acme_data (d_tls,
+ c_tls))
{
SSL_set_accept_state (c_tls->sess); /* Force server mode */
@@ -1585,6 +2519,10 @@ mhd_tls_open_conn_recv (struct mhd_TlsOpenConnData *restrict c_tls,
mhd_assert (c_tls->dbg.is_tls_handshake_completed);
mhd_assert (!c_tls->shut_tls_wr_sent);
mhd_assert (!c_tls->dbg.is_failed);
+#ifdef mhd_HAVE_OPENSSL_ACME
+ mhd_assert (!c_tls->is_acme); /* ACME ALPN challenge connection is not used
+ for the data exchange */
+#endif /* mhd_HAVE_OPENSSL_ACME */
ERR_clear_error ();
@@ -1665,6 +2603,10 @@ mhd_tls_open_conn_send4 (struct mhd_TlsOpenConnData *restrict c_tls,
mhd_assert (c_tls->dbg.is_tls_handshake_completed);
mhd_assert (!c_tls->shut_tls_wr_sent);
mhd_assert (!c_tls->dbg.is_failed);
+#ifdef mhd_HAVE_OPENSSL_ACME
+ mhd_assert (!c_tls->is_acme); /* ACME ALPN challenge connection is not used
+ for the data exchange */
+#endif /* mhd_HAVE_OPENSSL_ACME */
ERR_clear_error ();
@@ -1775,6 +2717,9 @@ mhd_tls_open_conn_get_alpn_prot (struct mhd_TlsOpenConnData *restrict c_tls)
{
const unsigned char *sel_prot;
unsigned int sel_prot_len;
+#ifdef mhd_HAVE_OPENSSL_ACME
+ mhd_assert (!c_tls->is_acme);
+#endif /* mhd_HAVE_OPENSSL_ACME */
SSL_get0_alpn_selected (c_tls->sess,
&sel_prot,
@@ -1795,3 +2740,16 @@ mhd_tls_open_conn_get_alpn_prot (struct mhd_TlsOpenConnData *restrict c_tls)
return mhd_tls_alpn_decode_n ((size_t)sel_prot_len,
sel_prot);
}
+
+
+#ifdef mhd_HAVE_TLS_ACME
+# ifdef mhd_HAVE_OPENSSL_ACME
+MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
+mhd_tls_open_conn_is_acme (struct mhd_TlsOpenConnData *restrict c_tls)
+{
+ return c_tls->is_acme;
+}
+
+
+# endif /* mhd_HAVE_OPENSSL_ACME */
+#endif /* mhd_HAVE_TLS_ACME */
diff --git a/src/mhd2/tls_open_funcs.h b/src/mhd2/tls_open_funcs.h
@@ -62,6 +62,7 @@
#include "mhd_status_code_int.h"
#include "mhd_tls_enums.h"
+#include "mhd_tls_cred_ptr.h"
#include "mhd_socket_error.h"
/**
@@ -124,7 +125,11 @@ struct DaemonOptions; /* Forward declaration */
* @return 'true' if the backend supports ACME ALPN challenge protocol,
* 'false' otherwise
*/
-# define mhd_tls_open_is_acme_alpn_supported(s) ((void)(s), (!!0))
+# ifdef mhd_HAVE_OPENSSL_ACME
+# define mhd_tls_open_is_acme_alpn_supported(s) ((void)(s), (!0))
+# else /* ! mhd_HAVE_OPENSSL_ACME */
+# define mhd_tls_open_is_acme_alpn_supported(s) ((void)(s), (!!0))
+# endif /* ! mhd_HAVE_OPENSSL_ACME */
#endif /* mhd_HAVE_TLS_ACME */
/**
@@ -165,11 +170,73 @@ 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, \
+#ifdef mhd_HAVE_OPENSSL_ACME
+/**
+ * Create TLS credentials from the provided certificate and key data.
+ *
+ * Created credentials must be destroyed with #mhd_tls_open_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 OpenSSL 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 certificates data in PEM format, zero-terminated;
+ * the first certificate is the end-entity certificate, any
+ * following certificates form the chain of the signing
+ * certificates
+ * @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_open_cred_create (struct mhd_TlsOpenDaemonData *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.
+ *
+ * The certificate, the private key and the certificates chain are released.
+ * The objects still used by any TLS session or context stay alive as OpenSSL
+ * holds its own references for them.
+ *
+ * @param cred the backend-specific credentials pointer to release; the OpenSSL
+ * member must be non-NULL
+ */
+MHD_INTERNAL void
+mhd_tls_open_cred_destroy_nodmn (union mhd_TlsCredDataPtr cred);
+
+#else /* ! mhd_HAVE_OPENSSL_ACME */
+# define mhd_tls_open_cred_create(d_tls, pp_c, c_l, c, k_l, k, ps_l, ps) \
+ ((void)d_tls, (void)pp_c, (void)c_l, (void)c, \
(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))
+# define mhd_tls_open_cred_destroy_nodmn(cred) ((void)(cred))
+#endif /* ! mhd_HAVE_OPENSSL_ACME */
/**
* Release TLS credentials and free the allocated memory when no references
@@ -371,7 +438,14 @@ MHD_FN_PAR_NONNULL_ALL_;
* @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))
+# ifdef mhd_HAVE_OPENSSL_ACME
+MHD_INTERNAL bool
+mhd_tls_open_conn_is_acme (struct mhd_TlsOpenConnData *restrict c_tls)
+MHD_FN_PAR_NONNULL_ALL_;
+
+# else /* ! mhd_HAVE_OPENSSL_ACME */
+# define mhd_tls_open_conn_is_acme(c_tls) (((void)(c_tls)), (!!0))
+# endif /* ! mhd_HAVE_OPENSSL_ACME */
#endif /* mhd_HAVE_TLS_ACME */
#endif /* ! MHD_TLS_OPEN_FUNCS_H */