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