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