python: Make robust against traceback.format_exception returning error.
[nbdkit/ericb.git] / server / crypto.c
blob9cd1bb08579439c221821ce7d660144e2ae67ed2
1 /* nbdkit
2 * Copyright (C) 2017 Red Hat Inc.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
33 #include <config.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <stdarg.h>
38 #include <stdint.h>
39 #include <stdbool.h>
40 #include <inttypes.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include <fcntl.h>
44 #include <limits.h>
45 #include <errno.h>
46 #include <sys/types.h>
47 #include <assert.h>
49 #include "internal.h"
51 #ifdef HAVE_GNUTLS
53 #include <gnutls/gnutls.h>
55 static int crypto_auth;
56 #define CRYPTO_AUTH_CERTIFICATES 1
57 #define CRYPTO_AUTH_PSK 2
59 static gnutls_certificate_credentials_t x509_creds;
60 static gnutls_psk_server_credentials_t psk_creds;
62 static void print_gnutls_error (int err, const char *fs, ...)
63 __attribute__((format (printf, 2, 3)));
65 static void
66 print_gnutls_error (int err, const char *fs, ...)
68 va_list args;
70 fprintf (stderr, "%s: GnuTLS error: ", program_name);
72 va_start (args, fs);
73 vfprintf (stderr, fs, args);
74 va_end (args);
76 fprintf (stderr, ": %s\n", gnutls_strerror (err));
79 /* Try to load certificates from 'path'. Returns true if successful.
80 * If it's not a certicate directory it returns false. Exits on
81 * other errors.
83 static int
84 load_certificates (const char *path)
86 CLEANUP_FREE char *ca_cert_filename = NULL;
87 CLEANUP_FREE char *server_cert_filename = NULL;
88 CLEANUP_FREE char *server_key_filename = NULL;
89 CLEANUP_FREE char *ca_crl_filename = NULL;
90 int err;
92 if (asprintf (&ca_cert_filename, "%s/ca-cert.pem", path) == -1) {
93 perror ("asprintf");
94 exit (EXIT_FAILURE);
96 if (asprintf (&server_cert_filename, "%s/server-cert.pem", path) == -1) {
97 perror ("asprintf");
98 exit (EXIT_FAILURE);
100 if (asprintf (&server_key_filename, "%s/server-key.pem", path) == -1) {
101 perror ("asprintf");
102 exit (EXIT_FAILURE);
104 if (asprintf (&ca_crl_filename, "%s/ca-crl.pem", path) == -1) {
105 perror ("asprintf");
106 exit (EXIT_FAILURE);
109 /* Our test for a certificate directory is that ca-cert.pem,
110 * server-cert.pem and server-key.pem must all exist in the path.
112 if (access (ca_cert_filename, R_OK) == -1)
113 return 0;
114 if (access (server_cert_filename, R_OK) == -1)
115 return 0;
116 if (access (server_key_filename, R_OK) == -1)
117 return 0;
119 /* Any problem past here is a hard error. */
121 err = gnutls_certificate_allocate_credentials (&x509_creds);
122 if (err < 0) {
123 print_gnutls_error (err, "allocating credentials");
124 exit (EXIT_FAILURE);
126 err = gnutls_certificate_set_x509_trust_file (x509_creds, ca_cert_filename,
127 GNUTLS_X509_FMT_PEM);
128 if (err < 0) {
129 print_gnutls_error (err, "loading %s", ca_cert_filename);
130 exit (EXIT_FAILURE);
133 if (access (ca_crl_filename, R_OK) == 0) {
134 err = gnutls_certificate_set_x509_crl_file (x509_creds, ca_crl_filename,
135 GNUTLS_X509_FMT_PEM);
136 if (err < 0) {
137 print_gnutls_error (err, "loading %s", ca_crl_filename);
138 exit (EXIT_FAILURE);
142 err = gnutls_certificate_set_x509_key_file (x509_creds,
143 server_cert_filename,
144 server_key_filename,
145 GNUTLS_X509_FMT_PEM);
146 if (err < 0) {
147 print_gnutls_error (err, "loading server certificate and key (%s, %s)",
148 server_cert_filename, server_key_filename);
149 exit (EXIT_FAILURE);
152 debug ("successfully loaded TLS certificates from %s", path);
153 return 1;
156 static int
157 start_certificates (void)
159 /* Try to locate the certificates directory and load them. */
160 if (tls_certificates_dir == NULL) {
161 const char *home;
162 CLEANUP_FREE char *path = NULL;
164 if (geteuid () != 0) {
165 home = getenv ("HOME");
166 if (home) {
167 if (asprintf (&path, "%s/.pki/%s", home, PACKAGE_NAME) == -1) {
168 perror ("asprintf");
169 exit (EXIT_FAILURE);
171 if (load_certificates (path))
172 goto found_certificates;
173 free (path);
174 if (asprintf (&path, "%s/.config/pki/%s", home, PACKAGE_NAME) == -1) {
175 perror ("asprintf");
176 exit (EXIT_FAILURE);
178 if (load_certificates (path))
179 goto found_certificates;
182 else { /* geteuid () == 0 */
183 if (load_certificates (root_tls_certificates_dir))
184 goto found_certificates;
187 else {
188 if (load_certificates (tls_certificates_dir))
189 goto found_certificates;
191 return -1;
193 found_certificates:
194 #ifdef HAVE_GNUTLS_CERTIFICATE_SET_KNOWN_DH_PARAMS
195 gnutls_certificate_set_known_dh_params (x509_creds, GNUTLS_SEC_PARAM_MEDIUM);
196 #endif
197 return 0;
200 static int
201 start_psk (void)
203 int err;
204 CLEANUP_FREE char *abs_psk_file = NULL;
206 /* Make sure the path to the PSK file is absolute. */
207 abs_psk_file = realpath (tls_psk, NULL);
208 if (abs_psk_file == NULL) {
209 perror (tls_psk);
210 exit (EXIT_FAILURE);
213 err = gnutls_psk_allocate_server_credentials (&psk_creds);
214 if (err < 0) {
215 print_gnutls_error (err, "allocating PSK credentials");
216 exit (EXIT_FAILURE);
219 /* Note that this function makes a copy of the string.
220 * CLEANUP_FREE macro above will free abs_psk_file when
221 * we return, but this is safe.
223 gnutls_psk_set_server_credentials_file (psk_creds, abs_psk_file);
225 return 0;
228 /* Initialize crypto. This also handles the command line parameters
229 * and loading the server certificate.
231 void
232 crypto_init (bool tls_set_on_cli)
234 int err, r;
235 const char *what;
237 err = gnutls_global_init ();
238 if (err < 0) {
239 print_gnutls_error (err, "initializing GnuTLS");
240 exit (EXIT_FAILURE);
243 if (tls == 0) /* --tls=off */
244 return;
246 /* --tls-psk overrides certificates. */
247 if (tls_psk != NULL) {
248 what = "Pre-Shared Keys (PSK)";
249 r = start_psk ();
250 if (r == 0)
251 crypto_auth = CRYPTO_AUTH_PSK;
253 else {
254 what = "X.509 certificates";
255 r = start_certificates ();
256 if (r == 0)
257 crypto_auth = CRYPTO_AUTH_CERTIFICATES;
260 if (r == 0) {
261 debug ("TLS enabled using: %s", what);
262 return;
265 /* If we get here, we didn't manage to load the PSK file /
266 * certificates. If --tls=require was given on the command line
267 * then that's a problem.
269 if (tls == 2) { /* --tls=require */
270 fprintf (stderr,
271 "%s: --tls=require but could not load TLS certificates.\n"
272 "Try setting ‘--tls-certificates=/path/to/certificates’ or read\n"
273 "the \"TLS\" section in nbdkit(1).\n",
274 program_name);
275 exit (EXIT_FAILURE);
278 /* If --tls=on was given on the command line, warn before we turn
279 * TLS off.
281 if (tls == 1 && tls_set_on_cli) { /* explicit --tls=on */
282 fprintf (stderr,
283 "%s: warning: --tls=on but could not load TLS certificates.\n"
284 "TLS will be disabled and TLS connections will be rejected.\n"
285 "Try setting ‘--tls-certificates=/path/to/certificates’ or read\n"
286 "the \"TLS\" section in nbdkit(1).\n",
287 program_name);
290 tls = 0;
291 debug ("TLS disabled: could not load TLS certificates");
294 void
295 crypto_free (void)
297 if (tls > 0) {
298 switch (crypto_auth) {
299 case CRYPTO_AUTH_CERTIFICATES:
300 gnutls_certificate_free_credentials (x509_creds);
301 break;
302 case CRYPTO_AUTH_PSK:
303 gnutls_psk_free_server_credentials (psk_creds);
304 break;
308 gnutls_global_deinit ();
311 /* Read buffer from GnuTLS and either succeed completely
312 * (returns > 0), read an EOF (returns 0), or fail (returns -1).
314 static int
315 crypto_recv (struct connection *conn, void *vbuf, size_t len)
317 gnutls_session_t session = conn->crypto_session;
318 char *buf = vbuf;
319 ssize_t r;
320 bool first_read = true;
322 assert (session != NULL);
324 while (len > 0) {
325 r = gnutls_record_recv (session, buf, len);
326 if (r < 0) {
327 if (r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN)
328 continue;
329 nbdkit_error ("gnutls_record_recv: %s", gnutls_strerror (r));
330 errno = EIO;
331 return -1;
333 if (r == 0) {
334 if (first_read)
335 return 0;
336 /* Partial record read. This is an error. */
337 errno = EBADMSG;
338 return -1;
340 first_read = false;
341 buf += r;
342 len -= r;
345 return 1;
348 /* If this send()'s length is so large that it is going to require
349 * multiple TCP segments anyway, there's no need to try and merge it
350 * with any corked data from a previous send that used SEND_MORE.
352 #define MAX_SEND_MORE_LEN (64 * 1024)
354 /* Write buffer to GnuTLS and either succeed completely
355 * (returns 0) or fail (returns -1). flags is ignored for now.
357 static int
358 crypto_send (struct connection *conn, const void *vbuf, size_t len, int flags)
360 gnutls_session_t session = conn->crypto_session;
361 const char *buf = vbuf;
362 ssize_t r;
364 assert (session != NULL);
366 if (len + gnutls_record_check_corked (session) > MAX_SEND_MORE_LEN) {
367 if (gnutls_record_uncork (session, GNUTLS_RECORD_WAIT) < 0)
368 return -1;
370 else if (flags & SEND_MORE)
371 gnutls_record_cork (session);
373 while (len > 0) {
374 r = gnutls_record_send (session, buf, len);
375 if (r < 0) {
376 if (r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN)
377 continue;
378 return -1;
380 buf += r;
381 len -= r;
384 if (!(flags & SEND_MORE) &&
385 gnutls_record_uncork (session, GNUTLS_RECORD_WAIT) < 0)
386 return -1;
388 return 0;
391 /* There's no place in the NBD protocol to send back errors from
392 * close, so this function ignores errors.
394 static void
395 crypto_close (struct connection *conn)
397 gnutls_session_t session = conn->crypto_session;
398 int sockin, sockout;
400 assert (session != NULL);
402 gnutls_transport_get_int2 (session, &sockin, &sockout);
404 gnutls_bye (session, GNUTLS_SHUT_RDWR);
406 if (sockin >= 0)
407 close (sockin);
408 if (sockout >= 0 && sockin != sockout)
409 close (sockout);
411 gnutls_deinit (session);
412 conn->crypto_session = NULL;
415 /* Upgrade an existing connection to TLS. Also this should do access
416 * control if enabled. The protocol code ensures this function can
417 * only be called once per connection.
420 crypto_negotiate_tls (struct connection *conn, int sockin, int sockout)
422 gnutls_session_t session;
423 CLEANUP_FREE char *priority = NULL;
424 int err;
426 /* Create the GnuTLS session. */
427 err = gnutls_init (&session, GNUTLS_SERVER);
428 if (err < 0) {
429 nbdkit_error ("gnutls_init: %s", gnutls_strerror (err));
430 free (session);
431 return -1;
434 switch (crypto_auth) {
435 case CRYPTO_AUTH_CERTIFICATES:
436 /* Associate the session with the server credentials (key, cert). */
437 err = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE,
438 x509_creds);
439 if (err < 0) {
440 nbdkit_error ("gnutls_credentials_set: %s", gnutls_strerror (err));
441 goto error;
444 /* If verify peer is enabled, tell GnuTLS to request the client
445 * certificates. (Note the default is to not request or verify
446 * certificates).
448 if (tls_verify_peer) {
449 #ifdef HAVE_GNUTLS_SESSION_SET_VERIFY_CERT
450 gnutls_certificate_server_set_request (session, GNUTLS_CERT_REQUEST);
451 gnutls_session_set_verify_cert (session, NULL, 0);
452 #else
453 nbdkit_error ("--tls-verify-peer: "
454 "GnuTLS >= 3.4.6 is required for this feature");
455 goto error;
456 #endif
459 priority = strdup (TLS_PRIORITY);
460 if (priority == NULL) {
461 nbdkit_error ("strdup: %m");
462 goto error;
464 break;
466 case CRYPTO_AUTH_PSK:
467 /* Associate the session with the server PSK credentials. */
468 err = gnutls_credentials_set (session, GNUTLS_CRD_PSK, psk_creds);
469 if (err < 0) {
470 nbdkit_error ("gnutls_credentials_set: %s", gnutls_strerror (err));
471 goto error;
474 if (asprintf (&priority,
475 "%s:+ECDHE-PSK:+DHE-PSK:+PSK", TLS_PRIORITY) == -1) {
476 nbdkit_error ("asprintf: %m");
477 goto error;
479 break;
481 default:
482 abort ();
485 assert (priority != NULL);
486 err = gnutls_priority_set_direct (session, priority, NULL);
487 if (err < 0) {
488 nbdkit_error ("failed to set TLS session priority to %s: %s",
489 priority, gnutls_strerror (err));
490 goto error;
493 /* Set up GnuTLS so it reads and writes on the raw sockets. */
494 gnutls_transport_set_int2 (session, sockin, sockout);
496 /* Perform the handshake. */
497 debug ("starting TLS handshake");
498 gnutls_handshake_set_timeout (session,
499 GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
501 do {
502 err = gnutls_handshake (session);
503 } while (err < 0 && gnutls_error_is_fatal (err) == 0);
504 if (err < 0) {
505 gnutls_handshake_description_t in, out;
507 /* Get some additional debug information about where in the
508 * handshake protocol it failed. You have to look up these codes in
509 * <gnutls/gnutls.h>.
511 in = gnutls_handshake_get_last_in (session);
512 out = gnutls_handshake_get_last_out (session);
513 nbdkit_error ("gnutls_handshake: %s (%d/%d)",
514 gnutls_strerror (err), (int) in, (int) out);
515 goto error;
517 debug ("TLS handshake completed");
519 /* Set up the connection recv/send/close functions so they call
520 * GnuTLS wrappers instead.
522 conn->crypto_session = session;
523 conn->recv = crypto_recv;
524 conn->send = crypto_send;
525 conn->close = crypto_close;
526 return 0;
528 error:
529 gnutls_deinit (session);
530 return -1;
533 #else /* !HAVE_GNUTLS */
535 /* GnuTLS was not available at compile time. These are stub versions
536 * of the above functions which either do nothing or report errors as
537 * appropriate.
540 void
541 crypto_init (bool tls_set_on_cli)
543 if (tls > 0) {
544 fprintf (stderr,
545 "%s: TLS cannot be enabled because "
546 "this binary was compiled without GnuTLS.\n",
547 program_name);
548 exit (EXIT_FAILURE);
551 tls = 0;
552 debug ("TLS disabled: nbdkit was not compiled with GnuTLS support");
555 void
556 crypto_free (void)
558 /* nothing */
562 crypto_negotiate_tls (struct connection *conn, int sockin, int sockout)
564 /* Should never be called because tls == 0. */
565 abort ();
568 #endif /* !HAVE_GNUTLS */