libmicrohttpd2

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

mhd_tls_internal.c (11749B)


      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) 2024-2025 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_internal.c
     41  * @brief  The TLS handling internal functions
     42  * @author Karlson2k (Evgeny Grin)
     43  */
     44 
     45 #include "mhd_sys_options.h"
     46 
     47 #include "sys_null_macro.h"
     48 #include "sys_sizet_type.h"
     49 
     50 #include <string.h>
     51 
     52 #include "mhd_assert.h"
     53 #include "mhd_unreachable.h"
     54 
     55 #ifdef mhd_HAVE_TLS_ACME
     56 #  include "mhd_tls_acme_func.h"
     57 #endif
     58 #include "mhd_tls_internal.h"
     59 
     60 MHD_INTERNAL
     61 MHD_FN_PAR_IN_SIZE_ (2, 1) enum mhd_TlsAlpnProt
     62 mhd_tls_alpn_decode_n (size_t alpn_id_size,
     63                        const unsigned char *alpn_id)
     64 {
     65   mhd_assert ((NULL != alpn_id) || (0u == alpn_id_size));
     66 
     67   if (NULL == alpn_id)
     68     return mhd_TLS_ALPN_PROT_NOT_SELECTED;
     69 
     70 #ifdef MHD_SUPPORT_HTTP2
     71   if ((alpn_id_size == mhd_ALPN_H2_LEN)
     72       && (0 == memcmp (alpn_id,
     73                        mhd_ALPN_H2,
     74                        mhd_ALPN_H2_LEN)))
     75     return mhd_TLS_ALPN_PROT_HTTP2;
     76 #endif /* MHD_SUPPORT_HTTP2 */
     77 
     78   if ((alpn_id_size == mhd_ALPN_H1_1_LEN)
     79       && (0 == memcmp (alpn_id,
     80                        mhd_ALPN_H1_1,
     81                        mhd_ALPN_H1_1_LEN)))
     82     return mhd_TLS_ALPN_PROT_HTTP1_1;
     83 
     84   if ((alpn_id_size == mhd_ALPN_H1_0_LEN)
     85       && (0 == memcmp (alpn_id,
     86                        mhd_ALPN_H1_0,
     87                        mhd_ALPN_H1_0_LEN)))
     88     return mhd_TLS_ALPN_PROT_HTTP1_0;
     89 
     90   mhd_UNREACHABLE_D ("ALPN can negotiate only one of the provided values");
     91 
     92   return mhd_TLS_ALPN_PROT_ERROR;
     93 }
     94 
     95 
     96 #ifdef mhd_HAVE_TLS_ACME
     97 static const unsigned char expected_acme_alpn[] = {
     98   0x00, mhd_ALPN_ACME_LEN + 1u,       /* ProtocolNameList length */
     99   mhd_ALPN_ACME_LEN,                  /* ProtocolName length */
    100   'a', 'c', 'm', 'e', '-', 't', 'l', 's', '/', '1'
    101 };
    102 
    103 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (2, 1) bool
    104 mhd_tls_acme_check_ext_alpn (size_t ext_alpn_data_size,
    105                              const unsigned char *ext_alpn_data)
    106 {
    107   if (sizeof(expected_acme_alpn) != ext_alpn_data_size)
    108     return false;
    109   return (0 == memcmp (ext_alpn_data,
    110                        expected_acme_alpn,
    111                        sizeof(expected_acme_alpn)));
    112 }
    113 
    114 
    115 /* Sanity check */
    116 #  if SIZEOF_SIZE_T < 2
    117 #    error Unsupported size_t size, must be at least 2 bytes
    118 #  endif
    119 
    120 /**
    121  * Read value as big-endian 16-bit unsigned integer
    122  * @param data the data to read, must be at least 2 bytes long
    123  * @return the value
    124  */
    125 mhd_static_inline size_t
    126 get_uint16_be (const unsigned char data[MHD_FN_PAR_FIX_ARR_SIZE_ (2)])
    127 {
    128   return (size_t)(((unsigned int)data[0]) << 8u) | data[1];
    129 }
    130 
    131 
    132 MHD_INTERNAL MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_
    133 MHD_FN_PAR_IN_SIZE_ (2, 1) const union mhd_TlsCredDataPtr *
    134 mhd_tls_acme_check_ext_sni (size_t ext_sni_data_size,
    135                             const unsigned char *ext_sni_data,
    136                             struct mhd_TlsCertsList *acme_certs)
    137 {
    138   size_t sni_list_size;
    139   size_t name_size;
    140 
    141   /* Complete ServerNameList must be exactly:
    142      + uint16  list_size
    143      + uint8   name_type (0x00 for host_name)
    144      + uint16  name_size
    145      + uint8[] name (not zero-terminated) */
    146 
    147   if (ext_sni_data_size < 2u + 1u + 2u + 1u) /* list_size + name_type + name_size, 1 for at least one byte of name */
    148     return NULL; /* Too short for ServerNameList */
    149 
    150   sni_list_size = get_uint16_be (ext_sni_data);
    151   if (ext_sni_data_size - 2u != sni_list_size)
    152     return NULL;  /* List size does not match the actual data size */
    153 
    154   if (ext_sni_data[2] != 0x00)
    155     return NULL;  /* Not a host_name type */
    156 
    157   name_size = get_uint16_be (ext_sni_data + 3);
    158   if (sni_list_size - 3u != name_size)
    159     return NULL;  /* Not exactly one complete server name */
    160 
    161   return mhd_daemon_acme_cert_get_r_lock (acme_certs,
    162                                           name_size,
    163                                           (const char *)(ext_sni_data + 5u));
    164 }
    165 
    166 
    167 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (3, 2)
    168 MHD_FN_PAR_INOUT_ (4) bool
    169 mhd_tls_acme_check_ext (unsigned int ext_id,
    170                         size_t ext_data_size,
    171                         const unsigned char *ext_data,
    172                         struct mhd_TlsClientHelloAcmeCheckData *restrict check)
    173 {
    174   if (check->is_rejected)
    175     return false; /* Already rejected, no need to check further */
    176 
    177   switch (ext_id)
    178   {
    179   case 0u:      /* server_name extension */
    180     check->is_rejected = (NULL != check->acme_cred);
    181 
    182     if (!check->is_rejected)
    183     {
    184       check->acme_cred = mhd_tls_acme_check_ext_sni (ext_data_size,
    185                                                      ext_data,
    186                                                      check->certs_list);
    187       check->is_rejected = (NULL == check->acme_cred);
    188     }
    189     break;
    190 
    191   case 16u:     /* ALPN extension */
    192     check->alpn_match =
    193       (!check->alpn_match) /* Only one ALPN extension is allowed */
    194       && mhd_tls_acme_check_ext_alpn (ext_data_size,
    195                                       ext_data);
    196     check->is_rejected = !check->alpn_match;
    197     break;
    198 
    199   default:      /* Other extensions are ignored for ACME check */
    200     break;
    201   }
    202 
    203   if ((NULL != check->acme_cred)
    204       && check->is_rejected)
    205   {
    206     mhd_daemon_acme_cert_r_unlock (check->certs_list);
    207     check->acme_cred = NULL;
    208   }
    209 
    210   /* If rejected: no need to check that for other extensions */
    211   return !check->is_rejected;
    212 }
    213 
    214 
    215 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_INOUT_ (1)
    216 const union mhd_TlsCredDataPtr *
    217 mhd_tls_acme_check_ext_finish (
    218   struct mhd_TlsClientHelloAcmeCheckData *restrict check,
    219   bool parse_succeeded)
    220 {
    221   bool is_acme;
    222 
    223   is_acme = parse_succeeded
    224             && !check->is_rejected
    225             && (NULL != check->acme_cred)
    226             && check->alpn_match;
    227 
    228   if (!is_acme && (NULL != check->acme_cred))
    229   {
    230     mhd_daemon_acme_cert_r_unlock (check->certs_list);
    231     check->acme_cred = NULL;
    232   }
    233 
    234   mhd_assert (is_acme || (NULL == check->acme_cred));
    235   mhd_assert (!is_acme || (NULL != check->acme_cred));
    236 
    237   return check->acme_cred;
    238 }
    239 
    240 
    241 #  ifdef mhd_HAVE_TLS_MHD_CLIENTHELLO_BODY_PARSE
    242 
    243 MHD_INTERNAL MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_
    244 MHD_FN_PAR_IN_SIZE_ (2, 1) const union mhd_TlsCredDataPtr *
    245 mhd_tls_acme_check_clienthello_body (
    246   size_t body_size,
    247   const unsigned char *restrict body,
    248   struct mhd_TlsCertsList *restrict acme_certs)
    249 {
    250   static const size_t min_ext_block_size =
    251     (2u + 2u + sizeof(expected_acme_alpn)) /* ALPN extension size */
    252     + (2u + 2u + (2u + 1u + 2u + 1u)); /* Minimal SNI size */
    253   size_t pos;
    254   size_t elements_size;
    255   struct mhd_TlsClientHelloAcmeCheckData check;
    256 
    257   /* Complete ClientHello body must be exactly:
    258      + uint16     legacy_version
    259      + uint8[32]  random
    260      + uint8      legacy_session_id_length
    261      + uint8[]    legacy_session_id
    262      + uint16     cipher_suites_length
    263      + uint16[]   cipher_suites
    264      + uint8      compression_methods_length
    265      + uint8[]    compression_methods
    266      + uint16     extensions_length
    267      + uint8[]    extensions */
    268 
    269   pos = 0u;
    270   if (2u + 32u + 1u + 2u + 1u + 2u
    271       + min_ext_block_size > body_size)
    272     return NULL; /* Too short for ClientHello body with ACME ALPN challenge */
    273   if (0x03u != body[pos])
    274     return NULL; /* Legacy version is not TLS 1.x */
    275   if (0x01u > body[pos + 1u])
    276     return NULL; /* Legacy version is not TLS 1.0 or higher */
    277 
    278   pos += 2u;  /* legacy_version */
    279 
    280   pos += 32u; /* random */
    281 
    282   elements_size = (size_t)body[pos];  /* legacy_session_id_length */
    283   pos += 1u;  /* legacy_session_id_length */
    284   if (elements_size > body_size - pos)
    285     return NULL; /* Too short for legacy_session_id */
    286   pos += elements_size; /* legacy_session_id */
    287 
    288   if (2u > body_size - pos)
    289     return NULL; /* Too short for cipher_suites_length */
    290   elements_size = get_uint16_be (body + pos); /* cipher_suites_length */
    291   pos += 2u;  /* cipher_suites_length */
    292   if (elements_size > body_size - pos)
    293     return NULL; /* Too short for cipher_suites */
    294   pos += elements_size; /* cipher_suites */
    295 
    296   if (1u > body_size - pos)
    297     return NULL; /* Too short for compression_methods_length */
    298   elements_size = (size_t)body[pos];  /* compression_methods_length */
    299   pos += 1u;  /* compression_methods_length */
    300   if (elements_size > body_size - pos)
    301     return NULL; /* Too short for compression_methods */
    302   pos += elements_size; /* compression_methods */
    303 
    304   if (2u > body_size - pos)
    305     return NULL; /* Too short for extensions_length */
    306   elements_size = get_uint16_be (body + pos); /* extensions_length */
    307   pos += 2u;  /* extensions_length */
    308   if (min_ext_block_size > elements_size)
    309     return NULL; /* No space for minimal ACME TLS-ALPN-01 challenge extensions */
    310   if (elements_size != body_size - pos)
    311     return NULL; /* Too short for extensions or extra bytes at the end */
    312 
    313   mhd_tls_acme_check_ext_init (&check,
    314                                acme_certs);
    315 
    316   /* Extensions body is a sequence of extensions.
    317      Each extension is:
    318      + uint16  ext_id
    319      + uint16  ext_data_size
    320      + uint8[] ext_data */
    321   do
    322   {
    323     unsigned int ext_id;
    324     size_t ext_data_size;
    325     ext_id = (unsigned int)get_uint16_be (body + pos);
    326     pos += 2u; /* ext_id */
    327     ext_data_size = get_uint16_be (body + pos);
    328     pos += 2u; /* ext_data_size */
    329     if (ext_data_size > body_size - pos)
    330     {
    331       --pos; /* Force "unprocess" to indicate a broken extension */
    332       break; /* Broken extension, larger than data available */
    333     }
    334     if (!mhd_tls_acme_check_ext (ext_id,
    335                                  ext_data_size,
    336                                  body + pos,
    337                                  &check))
    338       break; /* Rejected, no need to check further extensions */
    339     pos += ext_data_size; /* ext_data */
    340   } while (body_size - (2u + 2u) >= pos); /* 2 bytes for ext_id and 2 bytes for ext_data_size */
    341 
    342   return mhd_tls_acme_check_ext_finish (&check,
    343                                         (body_size == pos)); /* No broken extensions */
    344 }
    345 
    346 
    347 #  endif /* mhd_HAVE_TLS_MHD_CLIENTHELLO_BODY_PARSE */
    348 
    349 #endif /* mhd_HAVE_TLS_ACME */