1 /* $NetBSD: ssl-bozo.c,v 1.34 2023/12/18 03:48:57 riastradh Exp $ */ 2 3 /* $eterna: ssl-bozo.c,v 1.15 2011/11/18 09:21:15 mrg Exp $ */ 4 5 /* 6 * Copyright (c) 1997-2023 Matthew R. Green 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer and 16 * dedication in the documentation and/or other materials provided 17 * with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 * 31 */ 32 33 /* this code implements SSL and backend IO for bozohttpd */ 34 35 #include <stdarg.h> 36 #include <stdio.h> 37 #include <string.h> 38 #include <syslog.h> 39 #include <unistd.h> 40 41 #include "bozohttpd.h" 42 43 #ifndef USE_ARG 44 #define USE_ARG(x) /*LINTED*/(void)&(x) 45 #endif 46 47 #ifndef NO_SSL_SUPPORT 48 49 #include <openssl/ssl.h> 50 #include <openssl/err.h> 51 52 #ifndef BOZO_SSL_CIPHERS 53 #define BOZO_SSL_CIPHERS \ 54 "HIGH:" \ 55 "-SHA:-ADH:" \ 56 "-PSK-AES128-CCM:-PSK-AES256-CCM:" \ 57 "-DHE-PSK-AES128-CCM8:-DHE-PSK-AES256-CCM8:" \ 58 "-AES128-CCM8:-AES256-CCM8:" \ 59 "-DHE-RSA-AES128-CCM8:-DHE-RSA-AES256-CCM8:" \ 60 "-PSK-AES128-CCM8:-PSK-AES256-CCM8:" \ 61 "-CAMELLIA128:-CAMELLIA256:" \ 62 "-RSA-PSK-CHACHA20-POLY1305:" \ 63 "!aNULL:!eNULL:" \ 64 "!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:" \ 65 "!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:" \ 66 "!KRB5-DES-CBC3-SHA" 67 #endif 68 69 /* this structure encapsulates the ssl info */ 70 typedef struct sslinfo_t { 71 SSL_CTX *ssl_context; 72 const SSL_METHOD *ssl_method; 73 SSL *bozossl; 74 char *certificate_file; 75 char *privatekey_file; 76 char *ciphers; 77 } sslinfo_t; 78 79 /* Default to TLS 1.3. */ 80 struct { 81 unsigned proto; 82 const char *name; 83 } protos[] = { 84 { TLS1_3_VERSION, "TLSv1.3" }, 85 { TLS1_2_VERSION, "TLSv1.2" }, 86 { TLS1_1_VERSION, "TLSv1.1" }, 87 { 0, NULL }, 88 }; 89 90 static int 91 bozo_ssl_proto(const char *name) 92 { 93 unsigned i; 94 95 if (name) 96 for (i = 0; protos[0].proto != 0; i++) 97 if (strcasecmp(name, protos[i].name) == 0) 98 return protos[i].proto; 99 return protos[0].proto; 100 } 101 102 static const char * 103 bozo_ssl_name(unsigned version) 104 { 105 unsigned i; 106 107 for (i = 0; protos[0].proto != 0; i++) 108 if (version == protos[i].proto) 109 return protos[i].name; 110 return protos[0].name; 111 } 112 113 /* 114 * bozo_clear_ssl_queue: print the contents of the SSL error queue 115 */ 116 static void 117 bozo_clear_ssl_queue(bozohttpd_t *httpd) 118 { 119 unsigned long sslcode = ERR_get_error(); 120 121 do { 122 static const char sslfmt[] = "SSL Error: %s:%s:%s"; 123 124 if (httpd->nolog) 125 continue; 126 127 if (httpd->logstderr || isatty(STDERR_FILENO)) { 128 fprintf(stderr, sslfmt, 129 ERR_lib_error_string(sslcode), 130 ERR_func_error_string(sslcode), 131 ERR_reason_error_string(sslcode)); 132 fputs("\n", stderr); 133 } else { 134 syslog(LOG_ERR, sslfmt, 135 ERR_lib_error_string(sslcode), 136 ERR_func_error_string(sslcode), 137 ERR_reason_error_string(sslcode)); 138 } 139 } while (0 != (sslcode = ERR_get_error())); 140 } 141 142 /* 143 * bozo_ssl_warn works just like bozowarn, plus the SSL error queue 144 */ 145 BOZO_PRINTFLIKE(2, 3) static void 146 bozo_ssl_warn(bozohttpd_t *httpd, const char *fmt, ...) 147 { 148 va_list ap; 149 150 va_start(ap, fmt); 151 if (!httpd->nolog) { 152 if (httpd->logstderr || isatty(STDERR_FILENO)) { 153 vfprintf(stderr, fmt, ap); 154 fputs("\n", stderr); 155 } else 156 vsyslog(LOG_ERR, fmt, ap); 157 } 158 va_end(ap); 159 160 bozo_clear_ssl_queue(httpd); 161 } 162 163 164 /* 165 * bozo_ssl_err works just like bozoerr, plus the SSL error queue 166 */ 167 BOZO_PRINTFLIKE(3, 4) BOZO_DEAD static void 168 bozo_ssl_err(bozohttpd_t *httpd, int code, const char *fmt, ...) 169 { 170 va_list ap; 171 172 va_start(ap, fmt); 173 if (!httpd->nolog) { 174 if (httpd->logstderr || isatty(STDERR_FILENO)) { 175 vfprintf(stderr, fmt, ap); 176 fputs("\n", stderr); 177 } else 178 vsyslog(LOG_ERR, fmt, ap); 179 } 180 va_end(ap); 181 182 bozo_clear_ssl_queue(httpd); 183 exit(code); 184 } 185 186 /* 187 * bozo_check_error_queue: print warnings if the error isn't expected 188 */ 189 static void 190 bozo_check_error_queue(bozohttpd_t *httpd, const char *tag, int ret) 191 { 192 if (ret > 0) 193 return; 194 195 const sslinfo_t *sslinfo = httpd->sslinfo; 196 const int sslerr = SSL_get_error(sslinfo->bozossl, ret); 197 198 if (sslerr != SSL_ERROR_ZERO_RETURN && 199 sslerr != SSL_ERROR_SYSCALL && 200 sslerr != SSL_ERROR_NONE) 201 bozo_ssl_warn(httpd, "%s: SSL_ERROR %d", tag, sslerr); 202 } 203 204 static BOZO_PRINTFLIKE(2, 0) int 205 bozo_ssl_printf(bozohttpd_t *httpd, const char * fmt, va_list ap) 206 { 207 char *buf; 208 int nbytes; 209 210 if ((nbytes = vasprintf(&buf, fmt, ap)) != -1) { 211 const sslinfo_t *sslinfo = httpd->sslinfo; 212 int ret = SSL_write(sslinfo->bozossl, buf, nbytes); 213 bozo_check_error_queue(httpd, "write", ret); 214 } 215 216 free(buf); 217 218 return nbytes; 219 } 220 221 static ssize_t 222 bozo_ssl_read(bozohttpd_t *httpd, int fd, void *buf, size_t nbytes) 223 { 224 const sslinfo_t *sslinfo = httpd->sslinfo; 225 int ret; 226 227 USE_ARG(fd); 228 ret = SSL_read(sslinfo->bozossl, buf, (int)nbytes); 229 bozo_check_error_queue(httpd, "read", ret); 230 231 return (ssize_t)ret; 232 } 233 234 static ssize_t 235 bozo_ssl_write(bozohttpd_t *httpd, int fd, const void *buf, size_t nbytes) 236 { 237 const sslinfo_t *sslinfo = httpd->sslinfo; 238 int ret; 239 240 USE_ARG(fd); 241 ret = SSL_write(sslinfo->bozossl, buf, (int)nbytes); 242 bozo_check_error_queue(httpd, "write", ret); 243 244 return (ssize_t)ret; 245 } 246 247 void 248 bozo_ssl_init(bozohttpd_t *httpd) 249 { 250 sslinfo_t *sslinfo = httpd->sslinfo; 251 int proto; 252 253 if (sslinfo == NULL || !sslinfo->certificate_file) 254 return; 255 SSL_library_init(); 256 SSL_load_error_strings(); 257 258 sslinfo->ssl_method = SSLv23_server_method(); 259 sslinfo->ssl_context = SSL_CTX_new(sslinfo->ssl_method); 260 261 if (NULL == sslinfo->ssl_context) 262 bozo_ssl_err(httpd, EXIT_FAILURE, 263 "SSL context creation failed"); 264 265 proto = bozo_ssl_proto(httpd->ssl_min_proto); 266 267 if (!SSL_CTX_set_min_proto_version(sslinfo->ssl_context, proto)) 268 bozo_ssl_err(httpd, EXIT_FAILURE, 269 "Error setting minimum protocol version '%s'", 270 bozo_ssl_name(proto)); 271 272 if (!SSL_CTX_set_cipher_list(sslinfo->ssl_context, 273 sslinfo->ciphers ? sslinfo->ciphers : BOZO_SSL_CIPHERS)) 274 bozo_ssl_err(httpd, EXIT_FAILURE, 275 "Error setting cipher list '%s'", sslinfo->ciphers); 276 277 if (1 != SSL_CTX_use_certificate_chain_file(sslinfo->ssl_context, 278 sslinfo->certificate_file)) 279 bozo_ssl_err(httpd, EXIT_FAILURE, 280 "Unable to use certificate file '%s'", 281 sslinfo->certificate_file); 282 283 if (1 != SSL_CTX_use_PrivateKey_file(sslinfo->ssl_context, 284 sslinfo->privatekey_file, SSL_FILETYPE_PEM)) 285 bozo_ssl_err(httpd, EXIT_FAILURE, 286 "Unable to use private key file '%s'", 287 sslinfo->privatekey_file); 288 289 /* check consistency of key vs certificate */ 290 if (!SSL_CTX_check_private_key(sslinfo->ssl_context)) 291 bozo_ssl_err(httpd, EXIT_FAILURE, 292 "Check private key failed"); 293 } 294 295 /* 296 * returns non-zero for failure 297 */ 298 int 299 bozo_ssl_accept(bozohttpd_t *httpd) 300 { 301 sslinfo_t *sslinfo = httpd->sslinfo; 302 303 if (sslinfo == NULL || !sslinfo->ssl_context) 304 return 0; 305 306 alarm(httpd->ssl_timeout); 307 308 sslinfo->bozossl = SSL_new(sslinfo->ssl_context); 309 if (sslinfo->bozossl == NULL) 310 bozoerr(httpd, 1, "SSL_new failed"); 311 312 SSL_set_rfd(sslinfo->bozossl, 0); 313 SSL_set_wfd(sslinfo->bozossl, 1); 314 315 const int ret = SSL_accept(sslinfo->bozossl); 316 bozo_check_error_queue(httpd, "accept", ret); 317 318 alarm(0); 319 320 if (bozo_timeout_hit) { 321 SSL_free(sslinfo->bozossl); 322 sslinfo->bozossl = NULL; 323 return 1; 324 } 325 326 return ret != 1; 327 } 328 329 void 330 bozo_ssl_shutdown(bozohttpd_t *httpd) 331 { 332 const sslinfo_t *sslinfo = httpd->sslinfo; 333 334 if (sslinfo && sslinfo->bozossl) 335 SSL_shutdown(sslinfo->bozossl); 336 } 337 338 void 339 bozo_ssl_destroy(bozohttpd_t *httpd) 340 { 341 const sslinfo_t *sslinfo = httpd->sslinfo; 342 343 if (sslinfo && sslinfo->bozossl) 344 SSL_free(sslinfo->bozossl); 345 } 346 347 static sslinfo_t * 348 bozo_get_sslinfo(bozohttpd_t *httpd) 349 { 350 sslinfo_t *sslinfo; 351 if (httpd->sslinfo) 352 return httpd->sslinfo; 353 sslinfo = bozomalloc(httpd, sizeof(*sslinfo)); 354 if (sslinfo == NULL) 355 bozoerr(httpd, 1, "sslinfo allocation failed"); 356 memset(sslinfo, 0, sizeof(*sslinfo)); 357 return httpd->sslinfo = sslinfo; 358 } 359 360 void 361 bozo_ssl_set_opts(bozohttpd_t *httpd, const char *cert, const char *priv) 362 { 363 sslinfo_t *sslinfo = bozo_get_sslinfo(httpd); 364 365 sslinfo->certificate_file = bozostrdup(httpd, NULL, cert); 366 sslinfo->privatekey_file = bozostrdup(httpd, NULL, priv); 367 debug((httpd, DEBUG_NORMAL, "using cert/priv files: %s & %s", 368 sslinfo->certificate_file, 369 sslinfo->privatekey_file)); 370 if (!httpd->bindport) 371 httpd->bindport = bozostrdup(httpd, NULL, BOZO_HTTPS_PORT); 372 } 373 374 void 375 bozo_ssl_set_ciphers(bozohttpd_t *httpd, const char *ciphers) 376 { 377 sslinfo_t *sslinfo = bozo_get_sslinfo(httpd); 378 379 sslinfo->ciphers = bozostrdup(httpd, NULL, ciphers); 380 debug((httpd, DEBUG_NORMAL, "using ciphers: %s", sslinfo->ciphers)); 381 } 382 383 #endif /* NO_SSL_SUPPORT */ 384 385 /* 386 * These functions are always present, so that caller code can simply 387 * use bozo_*() for IO, regardless of SSL. 388 */ 389 int 390 bozo_printf(bozohttpd_t *httpd, const char *fmt, ...) 391 { 392 va_list args; 393 int cc; 394 395 USE_ARG(httpd); 396 397 va_start(args, fmt); 398 #ifndef NO_SSL_SUPPORT 399 if (httpd->sslinfo) 400 cc = bozo_ssl_printf(httpd, fmt, args); 401 else 402 #endif 403 cc = vprintf(fmt, args); 404 va_end(args); 405 return cc; 406 } 407 408 ssize_t 409 bozo_read(bozohttpd_t *httpd, int fd, void *buf, size_t len) 410 { 411 #ifndef NO_SSL_SUPPORT 412 if (httpd->sslinfo) 413 return bozo_ssl_read(httpd, fd, buf, len); 414 #endif 415 USE_ARG(httpd); 416 return read(fd, buf, len); 417 } 418 419 ssize_t 420 bozo_write(bozohttpd_t *httpd, int fd, const void *buf, size_t len) 421 { 422 #ifndef NO_SSL_SUPPORT 423 if (httpd->sslinfo) 424 return bozo_ssl_write(httpd, fd, buf, len); 425 #endif 426 USE_ARG(httpd); 427 return write(fd, buf, len); 428 } 429 430 int 431 bozo_flush(bozohttpd_t *httpd, FILE *fp) 432 { 433 #ifndef NO_SSL_SUPPORT 434 if (httpd->sslinfo) 435 return 0; 436 #endif 437 USE_ARG(httpd); 438 return fflush(fp); 439 } 440