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 */ 70typedef 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. */ 80struct { 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 90static int 91bozo_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 102static const char * 103bozo_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 */ 116static void 117bozo_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 */ 145BOZO_PRINTFLIKE(2, 3) static void 146bozo_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 */ 167BOZO_PRINTFLIKE(3, 4) BOZO_DEAD static void 168bozo_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 */ 189static void 190bozo_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 204static BOZO_PRINTFLIKE(2, 0) int 205bozo_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 221static ssize_t 222bozo_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 234static ssize_t 235bozo_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 247void 248bozo_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 */ 298int 299bozo_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 329void 330bozo_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 338void 339bozo_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 347static sslinfo_t * 348bozo_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 360void 361bozo_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 374void 375bozo_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 */ 389int 390bozo_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 408ssize_t 409bozo_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 419ssize_t 420bozo_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 430int 431bozo_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