xref: /netbsd-src/libexec/httpd/ssl-bozo.c (revision 4002f84f7c2afe3b1791c917d2283219e0ee66bf)
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
bozo_ssl_proto(const char * name)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 *
bozo_ssl_name(unsigned version)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
bozo_clear_ssl_queue(bozohttpd_t * httpd)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
bozo_ssl_warn(bozohttpd_t * httpd,const char * fmt,...)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
bozo_ssl_err(bozohttpd_t * httpd,int code,const char * fmt,...)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
bozo_check_error_queue(bozohttpd_t * httpd,const char * tag,int ret)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
bozo_ssl_printf(bozohttpd_t * httpd,const char * fmt,va_list ap)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
bozo_ssl_read(bozohttpd_t * httpd,int fd,void * buf,size_t nbytes)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
bozo_ssl_write(bozohttpd_t * httpd,int fd,const void * buf,size_t nbytes)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
bozo_ssl_init(bozohttpd_t * httpd)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
bozo_ssl_accept(bozohttpd_t * httpd)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
bozo_ssl_shutdown(bozohttpd_t * httpd)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
bozo_ssl_destroy(bozohttpd_t * httpd)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 *
bozo_get_sslinfo(bozohttpd_t * httpd)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
bozo_ssl_set_opts(bozohttpd_t * httpd,const char * cert,const char * priv)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
bozo_ssl_set_ciphers(bozohttpd_t * httpd,const char * ciphers)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
bozo_printf(bozohttpd_t * httpd,const char * fmt,...)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
bozo_read(bozohttpd_t * httpd,int fd,void * buf,size_t len)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
bozo_write(bozohttpd_t * httpd,int fd,const void * buf,size_t len)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
bozo_flush(bozohttpd_t * httpd,FILE * fp)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