libmicrohttpd2

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

mhd_tls_internal.c (11787B)


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