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