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