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