libmicrohttpd2

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

mhd_tls_acme_func.c (14130B)


      1 /* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
      2 /*
      3   This file is part of GNU libmicrohttpd.
      4   Copyright (C) 2026 Evgeny Grin (Karlson2k)
      5 
      6   GNU libmicrohttpd is free software; you can redistribute it and/or
      7   modify it under the terms of the GNU Lesser General Public
      8   License as published by the Free Software Foundation; either
      9   version 2.1 of the License, or (at your option) any later version.
     10 
     11   GNU libmicrohttpd is distributed in the hope that it will be useful,
     12   but WITHOUT ANY WARRANTY; without even the implied warranty of
     13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14   Lesser General Public License for more details.
     15 
     16   Alternatively, you can redistribute GNU libmicrohttpd and/or
     17   modify it under the terms of the GNU General Public License as
     18   published by the Free Software Foundation; either version 2 of
     19   the License, or (at your option) any later version, together
     20   with the eCos exception, as follows:
     21 
     22     As a special exception, if other files instantiate templates or
     23     use macros or inline functions from this file, or you compile this
     24     file and link it with other works to produce a work based on this
     25     file, this file does not by itself cause the resulting work to be
     26     covered by the GNU General Public License. However the source code
     27     for this file must still be made available in accordance with
     28     section (3) of the GNU General Public License v2.
     29 
     30     This exception does not invalidate any other reasons why a work
     31     based on this file might be covered by the GNU General Public
     32     License.
     33 
     34   You should have received copies of the GNU Lesser General Public
     35   License and the GNU General Public License along with this library;
     36   if not, see <https://www.gnu.org/licenses/>.
     37 */
     38 
     39 /**
     40  * @file src/mhd2/mhd_tls_acme_func.c
     41  * @brief  ALPN challenge functions for ACME protocol
     42  * @author Karlson2k (Evgeny Grin)
     43  */
     44 
     45 #include "mhd_sys_options.h"
     46 
     47 #include "sys_base_types.h"
     48 
     49 #include "sys_malloc.h"
     50 #include <string.h>
     51 
     52 #include "mhd_assert.h"
     53 #include "mhd_assume.h"
     54 #include "mhd_unreachable.h"
     55 #include "mhd_predict.h"
     56 
     57 #include "mhd_locksrw.h"
     58 #include "mhd_dlinked_list.h"
     59 #include "mhd_tls_certs_list.h"
     60 #include "mhd_daemon.h"
     61 
     62 #include "mhd_str.h"
     63 
     64 #include "daemon_logger.h"
     65 #include "daemon_funcs.h"
     66 
     67 #include "mhd_tls_funcs.h"
     68 
     69 #include "mhd_tls_acme_func.h"
     70 
     71 #include "mhd_public_api.h"
     72 
     73 #ifdef mhd_HAVE_TLS_ACME
     74 
     75 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_
     76 mhd_StatusCodeInt
     77 mhd_daemon_acme_certs_init (struct MHD_Daemon *d)
     78 {
     79   enum MHD_StatusCode res;
     80   mhd_assert (!mhd_D_HAS_MASTER (d));
     81   mhd_assert (mhd_D_HAS_TLS (d));
     82   mhd_assert (mhd_DAEMON_STATE_STARTING == d->state);
     83 
     84 #  ifndef HAVE_NULL_PTR_ALL_ZEROS
     85   mhd_DLINKEDL_INIT_LIST (&(d->acme.alpn), certs);
     86 #  endif /* HAVE_NULL_PTR_ALL_ZEROS */
     87 
     88   res = MHD_SC_OK;
     89   if (!mhd_lockrw_init (&(d->acme.alpn.certs_lock)))
     90   {
     91     mhd_LOG_MSG (d,
     92                  MHD_SC_MUTEX_INIT_FAILURE,
     93                  "Failed to initialise RW-lock for ACME ALPN certificates");
     94     res = MHD_SC_MUTEX_INIT_FAILURE;
     95   }
     96 
     97   return (mhd_StatusCodeInt)res;
     98 }
     99 
    100 
    101 /**
    102  * Destroy ACME certificate item
    103  * @param d the daemon object
    104  * @param item the item to destroy
    105  */
    106 static MHD_FN_PAR_NONNULL_ALL_ void
    107 acme_cert_destroy (struct MHD_Daemon *d,
    108                    struct mhd_TlsCertEntry *item)
    109 {
    110   mhd_tls_cred_destroy (d->tls,
    111                         item->cert_data);
    112   free (item);
    113 }
    114 
    115 
    116 /**
    117  * Destroy detached DL-linked list of ACME certificates
    118  * @param d the daemon object
    119  * @param first the first member of the DL-linked list, NULL is tolerated
    120  */
    121 static MHD_FN_PAR_NONNULL_ (1) void
    122 acme_certs_list_destroy (struct MHD_Daemon *d,
    123                          struct mhd_TlsCertEntry *first)
    124 {
    125   struct mhd_TlsCertEntry *entry = first;
    126 
    127   while (NULL != entry)
    128   {
    129     struct mhd_TlsCertEntry *next = mhd_DLINKEDL_GET_NEXT (entry, certs);
    130     acme_cert_destroy (d,
    131                        entry);
    132     entry = next;
    133   }
    134 }
    135 
    136 
    137 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void
    138 mhd_daemon_acme_certs_deinit (struct MHD_Daemon *d)
    139 {
    140   mhd_assert (!mhd_D_HAS_MASTER (d));
    141   mhd_assert (mhd_D_HAS_TLS (d));
    142   mhd_assert ((mhd_DAEMON_STATE_STARTING == d->state) /* Failed start rewinding */
    143               || (mhd_DAEMON_STATE_STOPPING == d->state));
    144 
    145   mhd_lockrw_destroy_chk (&(d->acme.alpn.certs_lock));
    146 
    147   acme_certs_list_destroy (d,
    148                            mhd_DLINKEDL_GET_FIRST (&(d->acme.alpn), certs));
    149 }
    150 
    151 
    152 #endif /* mhd_HAVE_TLS_ACME */
    153 
    154 
    155 MHD_EXTERN_ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (3)
    156 MHD_FN_PAR_NONNULL_ (4) MHD_FN_PAR_CSTR_ (2) MHD_FN_PAR_CSTR_ (3)
    157 MHD_FN_PAR_CSTR_ (4) MHD_FN_PAR_CSTR_ (5) enum MHD_StatusCode
    158 MHD_daemon_acme_alpn_cert_add (struct MHD_Daemon *MHD_RESTRICT daemon,
    159                                const char *MHD_RESTRICT domain,
    160                                const char *MHD_RESTRICT cert,
    161                                const char *MHD_RESTRICT key,
    162                                const char *MHD_RESTRICT password)
    163 {
    164 #ifdef mhd_HAVE_TLS_ACME
    165   const size_t domain_len = (NULL != domain) ? strlen (domain) : 0u;
    166   const size_t cert_len = strlen (cert);
    167   const size_t key_len = strlen (key);
    168   const size_t password_len = (NULL != password) ? strlen (password) : 0u;
    169   size_t alloc_size;
    170   struct mhd_TlsCertEntry *new_entry;
    171   char *str_store;
    172   enum MHD_StatusCode res;
    173 
    174   mhd_assert (!mhd_D_HAS_MASTER (daemon));
    175   if (mhd_DAEMON_STATE_STARTED > daemon->state)
    176     return MHD_SC_TOO_EARLY;
    177   if (mhd_DAEMON_STATE_STARTED < daemon->state)
    178     return MHD_SC_TOO_LATE;
    179 
    180   if (!mhd_D_HAS_TLS (daemon))
    181     return MHD_SC_DAEMON_HAS_TLS_DISABLED;
    182 
    183   if ((0u == domain_len) && (NULL != domain))
    184     return MHD_SC_PARAM_EMPTY;
    185 
    186   if ((0u == cert_len) || (0u == key_len))
    187     return MHD_SC_TLS_CONF_BAD_CERT;
    188 
    189   alloc_size = sizeof(*new_entry);
    190   alloc_size += domain_len + 1u;
    191   if (mhd_COND_HARDLY_EVER (alloc_size <= domain_len))
    192     return MHD_SC_DAEMON_MEM_ALLOC_FAILURE;
    193 
    194   new_entry =
    195     (struct mhd_TlsCertEntry *)
    196     malloc (alloc_size);
    197   if (NULL == new_entry)
    198     return MHD_SC_DAEMON_MEM_ALLOC_FAILURE;
    199 
    200   str_store = (char *)(new_entry + 1u);
    201 
    202   if (NULL != domain)
    203   {
    204     mhd_str_to_lowercase_bin_n (domain_len + 1u, domain, str_store);
    205     new_entry->domain.len = domain_len;
    206     new_entry->domain.cstr = str_store;
    207   }
    208   else
    209   {
    210     new_entry->domain.len = 0u;
    211     new_entry->domain.cstr = NULL;
    212   }
    213 
    214   switch (mhd_tls_cred_create (daemon->tls,
    215                                &(new_entry->cert_data),
    216                                cert_len,
    217                                cert,
    218                                key_len,
    219                                key,
    220                                password_len,
    221                                password))
    222   {
    223   case mhd_TLS_CRED_CREATE_OK:
    224     res = MHD_SC_OK;
    225     break;
    226 
    227   case mhd_TLS_CRED_CREATE_BAD_CRED_DATA:
    228     res = MHD_SC_TLS_CONF_BAD_CERT;
    229     break;
    230 
    231   case mhd_TLS_CRED_CREATE_ALLOC_FAILED:
    232     res = MHD_SC_DAEMON_MEM_ALLOC_FAILURE;
    233     break;
    234 
    235   case mhd_TLS_CRED_CREATE_UNSUPPORTED:
    236     res = MHD_SC_TLS_BACKEND_OPERATION_UNSUPPORTED;
    237     break;
    238 
    239   case mhd_TLS_CRED_CREATE_FAILED:
    240     res = MHD_SC_TLS_BACKEND_ERROR;
    241     break;
    242 
    243   default:
    244     mhd_UNREACHABLE ();
    245     res = MHD_SC_INTERNAL_ERROR;
    246     break;
    247   }
    248 
    249   if (MHD_SC_OK == res)
    250   {
    251     mhd_DLINKEDL_INIT_LINKS (new_entry, certs);
    252 
    253     if (mhd_lockrw_w_lock (&(daemon->acme.alpn.certs_lock)))
    254     {
    255       struct mhd_TlsCertEntry *old_entry;
    256 
    257       if (0u != domain_len)
    258       {
    259         for (old_entry = mhd_DLINKEDL_GET_FIRST (&(daemon->acme.alpn), certs);
    260              NULL != old_entry;
    261              old_entry = mhd_DLINKEDL_GET_NEXT (old_entry, certs))
    262         {
    263           if ((domain_len == old_entry->domain.len)
    264               && (0 == memcmp (new_entry->domain.cstr,
    265                                old_entry->domain.cstr,
    266                                domain_len)))
    267             break;
    268         }
    269       }
    270       else
    271       {
    272         for (old_entry = mhd_DLINKEDL_GET_FIRST (&(daemon->acme.alpn), certs);
    273              NULL != old_entry;
    274              old_entry = mhd_DLINKEDL_GET_NEXT (old_entry, certs))
    275         {
    276           if (0u == old_entry->domain.len)
    277             break;
    278         }
    279       }
    280 
    281       if (NULL != old_entry)
    282         mhd_DLINKEDL_DEL (&(daemon->acme.alpn), old_entry, certs);
    283 
    284       mhd_DLINKEDL_INS_LAST (&(daemon->acme.alpn), new_entry, certs);
    285 
    286       mhd_lockrw_w_unlock_chk (&(daemon->acme.alpn.certs_lock));
    287 
    288       /* Destroy content of removed item */
    289       if (NULL != old_entry)
    290         acme_cert_destroy (daemon,
    291                            old_entry);
    292 
    293       return MHD_SC_OK;
    294     }
    295     else
    296       res = MHD_SC_MUTEX_LOCK_FAILED;
    297 
    298     mhd_tls_cred_destroy (daemon->tls,
    299                           new_entry->cert_data);
    300   }
    301   free (new_entry);
    302 
    303   mhd_assert (MHD_SC_OK != res);
    304   return res;
    305 #else  /* ! mhd_HAVE_TLS_ACME */
    306   (void)daemon;
    307   (void)domain;
    308   (void)cert;
    309   (void)key;
    310   (void)password;
    311 
    312   return MHD_SC_TLS_BACKEND_OPERATION_UNSUPPORTED;
    313 #endif /* ! mhd_HAVE_TLS_ACME */
    314 }
    315 
    316 
    317 MHD_EXTERN_ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (2) enum MHD_StatusCode
    318 MHD_daemon_acme_alpn_cert_del (struct MHD_Daemon *MHD_RESTRICT daemon,
    319                                const char *domain)
    320 {
    321 #ifdef mhd_HAVE_TLS_ACME
    322   struct mhd_TlsCertEntry *check;
    323   size_t domain_len;
    324 
    325   mhd_assert (!mhd_D_HAS_MASTER (daemon));
    326 
    327   if (mhd_DAEMON_STATE_STARTED > daemon->state)
    328     return MHD_SC_TOO_EARLY;
    329   if (mhd_DAEMON_STATE_STARTED < daemon->state)
    330     return MHD_SC_TOO_LATE;
    331 
    332   if (!mhd_D_HAS_TLS (daemon))
    333     return MHD_SC_DAEMON_HAS_TLS_DISABLED;
    334 
    335   if (NULL == domain)
    336   {
    337     struct mhd_TlsCertEntry *first;
    338 
    339     if (!mhd_lockrw_w_lock (&(daemon->acme.alpn.certs_lock)))
    340       return MHD_SC_MUTEX_LOCK_FAILED;
    341 
    342     first = mhd_DLINKEDL_GET_FIRST (&(daemon->acme.alpn), certs);
    343     /* Detach the list from the daemon */
    344     mhd_DLINKEDL_INIT_LIST (&(daemon->acme.alpn), certs);
    345     /* Early unlock */
    346     mhd_lockrw_w_unlock_chk (&(daemon->acme.alpn.certs_lock));
    347     /* Destroy content of detached list */
    348     acme_certs_list_destroy (daemon,
    349                              first);
    350     return MHD_SC_OK;
    351   }
    352 
    353   domain_len = strlen (domain);
    354   if (0u == domain_len)
    355     return MHD_SC_PARAM_EMPTY;
    356 
    357   if (!mhd_lockrw_w_lock (&(daemon->acme.alpn.certs_lock)))
    358     return MHD_SC_MUTEX_LOCK_FAILED;
    359 
    360   for (check = mhd_DLINKEDL_GET_FIRST (&(daemon->acme.alpn), certs);
    361        NULL != check;
    362        check = mhd_DLINKEDL_GET_NEXT (check, certs))
    363   {
    364     if ((domain_len == check->domain.len)
    365         && mhd_str_equal_lowercase_bin_n (domain,
    366                                           check->domain.cstr,
    367                                           domain_len))
    368     {
    369       mhd_DLINKEDL_DEL (&(daemon->acme.alpn), check, certs);
    370       break;
    371     }
    372   }
    373 
    374   /* Early unlock */
    375   mhd_lockrw_w_unlock_chk (&(daemon->acme.alpn.certs_lock));
    376 
    377   if (NULL == check)
    378     return MHD_SC_ITEM_NOT_FOUND;
    379 
    380   /* Destroy content of removed item */
    381   acme_cert_destroy (daemon,
    382                      check);
    383 
    384   return MHD_SC_OK;
    385 #else  /* ! mhd_HAVE_TLS_ACME */
    386   (void)daemon;
    387   (void)domain;
    388   return MHD_SC_TLS_BACKEND_OPERATION_UNSUPPORTED;
    389 #endif /* ! mhd_HAVE_TLS_ACME */
    390 }
    391 
    392 
    393 #ifdef mhd_HAVE_TLS_ACME
    394 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_RETURNS_NONNULL_
    395 struct mhd_TlsCertsList *
    396 mhd_daemon_get_acme_certs (struct MHD_Daemon *d)
    397 {
    398   struct MHD_Daemon *const master = mhd_daemon_get_master_daemon (d);
    399   mhd_assert ((mhd_DAEMON_STATE_STARTING == master->state)
    400               || (mhd_D_HAS_TLS (d)));
    401   mhd_assert ((mhd_DAEMON_STATE_STARTING == master->state)
    402               || (mhd_D_HAS_TLS (master)));
    403 
    404   return &(master->acme.alpn);
    405 }
    406 
    407 
    408 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
    409 mhd_daemon_has_acme_certs (struct mhd_TlsCertsList *restrict acme_certs)
    410 {
    411 #  ifdef MHD_USE_THREAD_SYNC_EXTRA_CAUTION
    412   bool ret;
    413 
    414   mhd_lockrw_r_lock_chk (&(acme_certs->certs_lock));
    415   ret = (NULL != mhd_DLINKEDL_GET_LAST (acme_certs, certs));
    416   mhd_lockrw_r_unlock_chk (&(acme_certs->certs_lock));
    417 
    418   return ret;
    419 #  else  /* ! MHD_USE_THREAD_SYNC_EXTRA_CAUTION */
    420   /* Fast check without locking.
    421      Hypothetically on platforms with non-atomic pointer updates there is
    422      a chance that NULL will be matched when updating specific non-NULL
    423      pointer to another non-NULL pointer, but this is very unlikely and
    424      the worst possible outcome it skipping a single ACME ALPN challenge
    425      when ACME certificates are being added or removed at precisely the same
    426      time. */
    427   return (NULL != mhd_DLINKEDL_GET_LAST (acme_certs, certs));
    428 #  endif /* ! MHD_USE_THREAD_SYNC_EXTRA_CAUTION */
    429 }
    430 
    431 
    432 MHD_INTERNAL MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_
    433 MHD_FN_PAR_IN_SIZE_ (3, 2) const union mhd_TlsCredDataPtr *
    434 mhd_daemon_acme_cert_get_r_lock (struct mhd_TlsCertsList *restrict acme_certs,
    435                                  size_t domain_len,
    436                                  const char *restrict domain)
    437 {
    438   const struct mhd_TlsCertEntry *check;
    439   const struct mhd_TlsCertEntry *any;
    440 
    441   mhd_ASSUME (0u != domain_len);
    442 
    443   any = NULL;
    444 
    445   mhd_lockrw_r_lock_chk (&(acme_certs->certs_lock));
    446 
    447   for (check = mhd_DLINKEDL_GET_FIRST (acme_certs, certs);
    448        NULL != check;
    449        check = mhd_DLINKEDL_GET_NEXT (check, certs))
    450   {
    451     if ((domain_len == check->domain.len)
    452         && mhd_str_equal_lowercase_bin_n (domain,
    453                                           check->domain.cstr,
    454                                           domain_len))
    455       break;
    456     else if (NULL == check->domain.cstr)
    457     {
    458       mhd_assert (NULL == any);
    459       any = check;
    460     }
    461 
    462   }
    463 
    464   if (NULL == check)
    465     check = any;
    466 
    467   if (NULL == check)
    468     mhd_lockrw_r_unlock_chk (&(acme_certs->certs_lock));
    469 
    470   return (NULL == check) ? NULL : &(check->cert_data);
    471 }
    472 
    473 
    474 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void
    475 mhd_daemon_acme_cert_r_unlock (struct mhd_TlsCertsList *restrict acme_certs)
    476 {
    477 #  ifdef MHD_SUPPORT_THREADS
    478   mhd_lockrw_r_unlock_chk (&(acme_certs->certs_lock));
    479 #  else  /* ! MHD_SUPPORT_THREADS */
    480   (void)acme_certs;
    481 #  endif /* ! MHD_SUPPORT_THREADS */
    482 }
    483 
    484 
    485 #endif /* mhd_HAVE_TLS_ACME */