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