1 /* $NetBSD: ssl-bozo.c,v 1.33 2023/06/07 20:12:31 mrg 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 } else { 133 syslog(LOG_ERR, sslfmt, 134 ERR_lib_error_string(sslcode), 135 ERR_func_error_string(sslcode), 136 ERR_reason_error_string(sslcode)); 137 } 138 } while (0 != (sslcode = ERR_get_error())); 139 } 140 141 /* 142 * bozo_ssl_warn works just like bozowarn, plus the SSL error queue 143 */ 144 BOZO_PRINTFLIKE(2, 3) static void 145 bozo_ssl_warn(bozohttpd_t *httpd, const char *fmt, ...) 146 { 147 va_list ap; 148 149 va_start(ap, fmt); 150 if (!httpd->nolog) { 151 if (httpd->logstderr || isatty(STDERR_FILENO)) { 152 vfprintf(stderr, fmt, ap); 153 fputs("\n", stderr); 154 } else 155 vsyslog(LOG_ERR, fmt, ap); 156 } 157 va_end(ap); 158 159 bozo_clear_ssl_queue(httpd); 160 } 161 162 163 /* 164 * bozo_ssl_err works just like bozoerr, plus the SSL error queue 165 */ 166 BOZO_PRINTFLIKE(3, 4) BOZO_DEAD static void 167 bozo_ssl_err(bozohttpd_t *httpd, int code, const char *fmt, ...) 168 { 169 va_list ap; 170 171 va_start(ap, fmt); 172 if (!httpd->nolog) { 173 if (httpd->logstderr || isatty(STDERR_FILENO)) { 174 vfprintf(stderr, fmt, ap); 175 fputs("\n", stderr); 176 } else 177 vsyslog(LOG_ERR, fmt, ap); 178 } 179 va_end(ap); 180 181 bozo_clear_ssl_queue(httpd); 182 exit(code); 183 } 184 185 /* 186 * bozo_check_error_queue: print warnings if the error isn't expected 187 */ 188 static void 189 bozo_check_error_queue(bozohttpd_t *httpd, const char *tag, int ret) 190 { 191 if (ret > 0) 192 return; 193 194 const sslinfo_t *sslinfo = httpd->sslinfo; 195 const int sslerr = SSL_get_error(sslinfo->bozossl, ret); 196 197 if (sslerr != SSL_ERROR_ZERO_RETURN && 198 sslerr != SSL_ERROR_SYSCALL && 199 sslerr != SSL_ERROR_NONE) 200 bozo_ssl_warn(httpd, "%s: SSL_ERROR %d", tag, sslerr); 201 } 202 203 static BOZO_PRINTFLIKE(2, 0) int 204 bozo_ssl_printf(bozohttpd_t *httpd, const char * fmt, va_list ap) 205 { 206 char *buf; 207 int nbytes; 208 209 if ((nbytes = vasprintf(&buf, fmt, ap)) != -1) { 210 const sslinfo_t *sslinfo = httpd->sslinfo; 211 int ret = SSL_write(sslinfo->bozossl, buf, nbytes); 212 bozo_check_error_queue(httpd, "write", ret); 213 } 214 215 free(buf); 216 217 return nbytes; 218 } 219 220 static ssize_t 221 bozo_ssl_read(bozohttpd_t *httpd, int fd, void *buf, size_t nbytes) 222 { 223 const sslinfo_t *sslinfo = httpd->sslinfo; 224 int ret; 225 226 USE_ARG(fd); 227 ret = SSL_read(sslinfo->bozossl, buf, (int)nbytes); 228 bozo_check_error_queue(httpd, "read", ret); 229 230 return (ssize_t)ret; 231 } 232 233 static ssize_t 234 bozo_ssl_write(bozohttpd_t *httpd, int fd, const void *buf, size_t nbytes) 235 { 236 const sslinfo_t *sslinfo = httpd->sslinfo; 237 int ret; 238 239 USE_ARG(fd); 240 ret = SSL_write(sslinfo->bozossl, buf, (int)nbytes); 241 bozo_check_error_queue(httpd, "write", ret); 242 243 return (ssize_t)ret; 244 } 245 246 void 247 bozo_ssl_init(bozohttpd_t *httpd) 248 { 249 sslinfo_t *sslinfo = httpd->sslinfo; 250 int proto; 251 252 if (sslinfo == NULL || !sslinfo->certificate_file) 253 return; 254 SSL_library_init(); 255 SSL_load_error_strings(); 256 257 sslinfo->ssl_method = SSLv23_server_method(); 258 sslinfo->ssl_context = SSL_CTX_new(sslinfo->ssl_method); 259 260 if (NULL == sslinfo->ssl_context) 261 bozo_ssl_err(httpd, EXIT_FAILURE, 262 "SSL context creation failed"); 263 264 proto = bozo_ssl_proto(httpd->ssl_min_proto); 265 266 if (!SSL_CTX_set_min_proto_version(sslinfo->ssl_context, proto)) 267 bozo_ssl_err(httpd, EXIT_FAILURE, 268 "Error setting minimum protocol version '%s'", 269 bozo_ssl_name(proto)); 270 271 if (!SSL_CTX_set_cipher_list(sslinfo->ssl_context, 272 sslinfo->ciphers ? sslinfo->ciphers : BOZO_SSL_CIPHERS)) 273 bozo_ssl_err(httpd, EXIT_FAILURE, 274 "Error setting cipher list '%s'", sslinfo->ciphers); 275 276 if (1 != SSL_CTX_use_certificate_chain_file(sslinfo->ssl_context, 277 sslinfo->certificate_file)) 278 bozo_ssl_err(httpd, EXIT_FAILURE, 279 "Unable to use certificate file '%s'", 280 sslinfo->certificate_file); 281 282 if (1 != SSL_CTX_use_PrivateKey_file(sslinfo->ssl_context, 283 sslinfo->privatekey_file, SSL_FILETYPE_PEM)) 284 bozo_ssl_err(httpd, EXIT_FAILURE, 285 "Unable to use private key file '%s'", 286 sslinfo->privatekey_file); 287 288 /* check consistency of key vs certificate */ 289 if (!SSL_CTX_check_private_key(sslinfo->ssl_context)) 290 bozo_ssl_err(httpd, EXIT_FAILURE, 291 "Check private key failed"); 292 } 293 294 /* 295 * returns non-zero for failure 296 */ 297 int 298 bozo_ssl_accept(bozohttpd_t *httpd) 299 { 300 sslinfo_t *sslinfo = httpd->sslinfo; 301 302 if (sslinfo == NULL || !sslinfo->ssl_context) 303 return 0; 304 305 alarm(httpd->ssl_timeout); 306 307 sslinfo->bozossl = SSL_new(sslinfo->ssl_context); 308 if (sslinfo->bozossl == NULL) 309 bozoerr(httpd, 1, "SSL_new failed"); 310 311 SSL_set_rfd(sslinfo->bozossl, 0); 312 SSL_set_wfd(sslinfo->bozossl, 1); 313 314 const int ret = SSL_accept(sslinfo->bozossl); 315 bozo_check_error_queue(httpd, "accept", ret); 316 317 alarm(0); 318 319 if (bozo_timeout_hit) { 320 SSL_free(sslinfo->bozossl); 321 sslinfo->bozossl = NULL; 322 return 1; 323 } 324 325 return ret != 1; 326 } 327 328 void 329 bozo_ssl_shutdown(bozohttpd_t *httpd) 330 { 331 const sslinfo_t *sslinfo = httpd->sslinfo; 332 333 if (sslinfo && sslinfo->bozossl) 334 SSL_shutdown(sslinfo->bozossl); 335 } 336 337 void 338 bozo_ssl_destroy(bozohttpd_t *httpd) 339 { 340 const sslinfo_t *sslinfo = httpd->sslinfo; 341 342 if (sslinfo && sslinfo->bozossl) 343 SSL_free(sslinfo->bozossl); 344 } 345 346 static sslinfo_t * 347 bozo_get_sslinfo(bozohttpd_t *httpd) 348 { 349 sslinfo_t *sslinfo; 350 if (httpd->sslinfo) 351 return httpd->sslinfo; 352 sslinfo = bozomalloc(httpd, sizeof(*sslinfo)); 353 if (sslinfo == NULL) 354 bozoerr(httpd, 1, "sslinfo allocation failed"); 355 memset(sslinfo, 0, sizeof(*sslinfo)); 356 return httpd->sslinfo = sslinfo; 357 } 358 359 void 360 bozo_ssl_set_opts(bozohttpd_t *httpd, const char *cert, const char *priv) 361 { 362 sslinfo_t *sslinfo = bozo_get_sslinfo(httpd); 363 364 sslinfo->certificate_file = bozostrdup(httpd, NULL, cert); 365 sslinfo->privatekey_file = bozostrdup(httpd, NULL, priv); 366 debug((httpd, DEBUG_NORMAL, "using cert/priv files: %s & %s", 367 sslinfo->certificate_file, 368 sslinfo->privatekey_file)); 369 if (!httpd->bindport) 370 httpd->bindport = bozostrdup(httpd, NULL, BOZO_HTTPS_PORT); 371 } 372 373 void 374 bozo_ssl_set_ciphers(bozohttpd_t *httpd, const char *ciphers) 375 { 376 sslinfo_t *sslinfo = bozo_get_sslinfo(httpd); 377 378 sslinfo->ciphers = bozostrdup(httpd, NULL, ciphers); 379 debug((httpd, DEBUG_NORMAL, "using ciphers: %s", sslinfo->ciphers)); 380 } 381 382 #endif /* NO_SSL_SUPPORT */ 383 384 /* 385 * These functions are always present, so that caller code can simply 386 * use bozo_*() for IO, regardless of SSL. 387 */ 388 int 389 bozo_printf(bozohttpd_t *httpd, const char *fmt, ...) 390 { 391 va_list args; 392 int cc; 393 394 USE_ARG(httpd); 395 396 va_start(args, fmt); 397 #ifndef NO_SSL_SUPPORT 398 if (httpd->sslinfo) 399 cc = bozo_ssl_printf(httpd, fmt, args); 400 else 401 #endif 402 cc = vprintf(fmt, args); 403 va_end(args); 404 return cc; 405 } 406 407 ssize_t 408 bozo_read(bozohttpd_t *httpd, int fd, void *buf, size_t len) 409 { 410 #ifndef NO_SSL_SUPPORT 411 if (httpd->sslinfo) 412 return bozo_ssl_read(httpd, fd, buf, len); 413 #endif 414 USE_ARG(httpd); 415 return read(fd, buf, len); 416 } 417 418 ssize_t 419 bozo_write(bozohttpd_t *httpd, int fd, const void *buf, size_t len) 420 { 421 #ifndef NO_SSL_SUPPORT 422 if (httpd->sslinfo) 423 return bozo_ssl_write(httpd, fd, buf, len); 424 #endif 425 USE_ARG(httpd); 426 return write(fd, buf, len); 427 } 428 429 int 430 bozo_flush(bozohttpd_t *httpd, FILE *fp) 431 { 432 #ifndef NO_SSL_SUPPORT 433 if (httpd->sslinfo) 434 return 0; 435 #endif 436 USE_ARG(httpd); 437 return fflush(fp); 438 } 439