nbd-client: Add support for setting /sys/block/nbdN/backend
[nbd/ericb.git] / crypto-gnutls.c
blobb6cbcbafeecaa513951df573e96b23bba76f989c
1 /*
3 The MIT License (MIT)
5 Copyright (c) 2016 Wrymouth Innovation Ltd
7 Permission is hereby granted, free of charge, to any person obtaining a
8 copy of this software and associated documentation files (the "Software"),
9 to deal in the Software without restriction, including without limitation
10 the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 and/or sell copies of the Software, and to permit persons to whom the
12 Software is furnished to do so, subject to the following conditions:
14 The above copyright notice and this permission notice shall be included
15 in all copies or substantial portions of the Software.
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
21 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 OTHER DEALINGS IN THE SOFTWARE.
27 #define _GNU_SOURCE
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <signal.h>
31 #include <string.h>
32 #include <stdint.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <sys/select.h>
36 #include <sys/socket.h>
37 #include <sys/time.h>
38 #include <sys/types.h>
39 #include <unistd.h>
41 #include <gnutls/gnutls.h>
42 #include <gnutls/crypto.h>
43 #include <gnutls/x509.h>
44 #include <gnutls/abstract.h>
46 #include "crypto-gnutls.h"
47 #include "buffer.h"
49 #define MAX_CERTS 10
51 #define FALSE 0
52 #define TRUE 1
54 #define PRIORITY "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.2"
56 struct tlssession
58 gnutls_certificate_credentials_t creds;
59 gnutls_session_t session;
60 char *hostname;
61 int (*quitfn) (void *opaque);
62 int (*erroutfn) (void *opaque, const char *format, va_list ap);
63 int debug;
64 void *opaque;
67 #define BUF_SIZE 65536
68 #define BUF_HWM ((BUF_SIZE*3)/4)
70 static int
71 falsequit (void *opaque)
73 return FALSE;
76 static int
77 quit (tlssession_t * s)
79 return s->quitfn (s->opaque);
83 static int
84 stderrout (void *opaque, const char *format, va_list ap)
86 return vfprintf (stderr, format, ap);
89 static int
90 errout (tlssession_t * s, const char *format, ...)
92 va_list ap;
93 int ret;
94 va_start (ap, format);
95 ret = s->erroutfn (s->opaque, format, ap);
96 va_end (ap);
97 return ret;
100 static int
101 debugout (tlssession_t * s, const char *format, ...)
103 va_list ap;
104 int ret = 0;
105 va_start (ap, format);
106 if (s->debug)
107 ret = s->erroutfn (s->opaque, format, ap);
108 va_end (ap);
109 return ret;
112 static int
113 socksetnonblock (int fd, int nb)
115 int sf = fcntl (fd, F_GETFL, 0);
116 if (sf == -1)
117 return -1;
118 return fcntl (fd, F_SETFL, nb ? (sf | O_NONBLOCK) : (sf & ~O_NONBLOCK));
121 /* From (public domain) example file in GNUTLS
123 * This function will try to verify the peer's certificate, and
124 * also check if the hostname matches, and the activation, expiration dates.
126 static int
127 verify_certificate_callback (gnutls_session_t session)
129 unsigned int status;
130 const gnutls_datum_t *cert_list;
131 unsigned int cert_list_size;
132 int ret;
133 gnutls_x509_crt_t cert;
134 tlssession_t *s;
136 /* read session pointer */
137 s = (tlssession_t *) gnutls_session_get_ptr (session);
139 /* This verification function uses the trusted CAs in the credentials
140 * structure. So you must have installed one or more CA certificates.
142 ret = gnutls_certificate_verify_peers2 (session, &status);
143 if (ret < 0)
145 debugout (s, "Could not verfify peer certificate due to an error\n");
146 return GNUTLS_E_CERTIFICATE_ERROR;
149 if (status & GNUTLS_CERT_INVALID)
150 debugout (s, "The certificate is not trusted.\n");
152 if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
153 debugout (s, "The certificate hasn't got a known issuer.\n");
155 if (status & GNUTLS_CERT_REVOKED)
156 debugout (s, "The certificate has been revoked.\n");
158 if (status & GNUTLS_CERT_EXPIRED)
159 debugout (s, "The certificate has expired\n");
161 if (status & GNUTLS_CERT_NOT_ACTIVATED)
162 debugout (s, "The certificate is not yet activated\n");
164 if (status)
165 return GNUTLS_E_CERTIFICATE_ERROR;
167 if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509)
168 return GNUTLS_E_CERTIFICATE_ERROR;
170 if (gnutls_x509_crt_init (&cert) < 0)
172 debugout (s, "error in initialization\n");
173 return GNUTLS_E_CERTIFICATE_ERROR;
176 cert_list = gnutls_certificate_get_peers (session, &cert_list_size);
177 if (cert_list == NULL)
179 debugout (s, "No certificate was found!\n");
180 return GNUTLS_E_CERTIFICATE_ERROR;
183 /* check only the first certificate - seems to be what curl does */
184 if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0)
186 debugout (s, "error parsing certificate\n");
187 return GNUTLS_E_CERTIFICATE_ERROR;
190 if (s->hostname && *s->hostname)
192 if (!gnutls_x509_crt_check_hostname (cert, s->hostname))
194 debugout (s,
195 "The certificate's owner does not match hostname '%s'\n",
196 s->hostname);
197 return GNUTLS_E_CERTIFICATE_ERROR;
201 gnutls_x509_crt_deinit (cert);
203 debugout (s, "Peer passed certificate verification\n");
205 /* notify gnutls to continue handshake normally */
206 return 0;
209 tlssession_t *
210 tlssession_new (int isserver,
211 char *keyfile, char *certfile, char *cacertfile,
212 char *hostname, char *priority, int insecure, int debug,
213 int (*quitfn) (void *opaque),
214 int (*erroutfn) (void *opaque, const char *format,
215 va_list ap), void *opaque)
217 int ret;
218 tlssession_t *s = calloc (1, sizeof (tlssession_t));
220 if (quitfn)
221 s->quitfn = quitfn;
222 else
223 s->quitfn = falsequit;
225 if (erroutfn)
226 s->erroutfn = erroutfn;
227 else
228 s->erroutfn = stderrout;
230 if (hostname)
231 s->hostname = strdup (hostname);
233 s->debug = debug;
235 if (gnutls_certificate_allocate_credentials (&s->creds) < 0)
237 errout (s, "Certificate allocation memory error\n");
238 goto error;
241 if (cacertfile != NULL)
243 ret =
244 gnutls_certificate_set_x509_trust_file (s->creds, cacertfile,
245 GNUTLS_X509_FMT_PEM);
246 if (ret < 0)
248 errout (s, "Error setting the x509 trust file: %s\n",
249 gnutls_strerror (ret));
250 goto error;
253 if (!insecure)
255 gnutls_certificate_set_verify_function (s->creds,
256 verify_certificate_callback);
257 gnutls_certificate_set_verify_flags (s->creds,
258 GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
262 if (keyfile && !certfile)
263 certfile = keyfile;
265 if (certfile != NULL && keyfile != NULL)
267 ret =
268 gnutls_certificate_set_x509_key_file (s->creds, certfile, keyfile,
269 GNUTLS_X509_FMT_PEM);
271 if (ret < 0)
273 errout (s,
274 "Error loading certificate or key file (%s, %s): %s\n",
275 certfile, keyfile, gnutls_strerror (ret));
276 goto error;
280 if (isserver)
282 ret = gnutls_init (&s->session, GNUTLS_SERVER);
284 else
286 ret = gnutls_init (&s->session, GNUTLS_CLIENT);
288 if (ret < 0)
290 errout (s, "Cannot initialize GNUTLS session: %s\n",
291 gnutls_strerror (ret));
292 goto error;
295 gnutls_session_set_ptr (s->session, (void *) s);
297 ret = gnutls_set_default_priority (s->session);
298 if (ret < 0)
300 errout (s, "Cannot set default GNUTLS session priority: %s\n",
301 gnutls_strerror (ret));
302 goto error;
305 const char *errpos = NULL;
306 ret = gnutls_priority_set_direct (s->session, priority ? priority : PRIORITY,
307 &errpos);
308 if (ret < 0)
310 errout (s, "Cannot set GNUTLS session priority: %s\n",
311 gnutls_strerror (ret));
312 goto error;
315 gnutls_session_set_ptr (s->session, (void *) s);
317 ret = gnutls_credentials_set (s->session, GNUTLS_CRD_CERTIFICATE, s->creds);
318 if (ret < 0)
320 errout (s, "Cannot set session GNUTL credentials: %s\n",
321 gnutls_strerror (ret));
322 goto error;
325 if (isserver)
327 /* requests but does not check a client certificate */
328 gnutls_certificate_server_set_request (s->session, GNUTLS_CERT_REQUEST);
332 return s;
334 error:
335 if (s->session)
336 gnutls_deinit (s->session);
337 free (s);
338 return NULL;
341 void
342 tlssession_close (tlssession_t * s)
344 if (s->session)
345 gnutls_deinit (s->session);
346 free (s->hostname);
347 free (s);
351 tlssession_init ()
353 return gnutls_global_init ();
358 tlssession_mainloop (int cryptfd, int plainfd, tlssession_t * s)
360 fd_set readfds;
361 fd_set writefds;
362 int maxfd;
363 int tls_wr_interrupted = 0;
364 int plainEOF = FALSE;
365 int cryptEOF = FALSE;
366 int ret;
368 buffer_t *plainToCrypt = bufNew (BUF_SIZE, BUF_HWM);
369 buffer_t *cryptToPlain = bufNew (BUF_SIZE, BUF_HWM);
371 if (socksetnonblock (cryptfd, 0) < 0)
373 errout (s, "Could not turn on blocking: %m");
374 goto error;
377 /* set it up to work with our FD */
378 gnutls_transport_set_ptr (s->session,
379 (gnutls_transport_ptr_t) (intptr_t) cryptfd);
382 /* Now do the handshake */
383 ret = gnutls_handshake (s->session);
384 if (ret < 0)
386 errout (s, "TLS handshake failed: %s\n", gnutls_strerror (ret));
387 goto error;
390 if (socksetnonblock (cryptfd, 1) < 0)
392 errout (s, "Could not turn on non-blocking on crypt FD: %m");
393 goto error;
396 if (socksetnonblock (plainfd, 1) < 0)
398 errout (s, "Could not turn on non-blocking on plain FD: %m");
399 goto error;
402 maxfd = (plainfd > cryptfd) ? plainfd + 1 : cryptfd + 1;
404 while ((!plainEOF || !cryptEOF) && !quit (s))
406 struct timeval timeout;
407 int result;
408 int selecterrno;
409 int wait = TRUE;
411 FD_ZERO (&readfds);
412 FD_ZERO (&writefds);
414 size_t buffered = gnutls_record_check_pending (s->session);
415 if (buffered)
416 wait = FALSE; /* do not wait for select to return if we have buffered data */
418 if (plainEOF)
420 /* plain text end has closed, but me may still have
421 * data yet to write to the crypt end */
422 if (bufIsEmpty (plainToCrypt) && !tls_wr_interrupted)
424 cryptEOF = TRUE;
425 break;
428 else
430 if (!bufIsEmpty (cryptToPlain))
431 FD_SET (plainfd, &writefds);
432 if (!bufIsOverHWM (plainToCrypt))
433 FD_SET (plainfd, &readfds);
436 if (cryptEOF)
438 /* crypt end has closed, but me way still have data to
439 * write from the crypt buffer */
440 if (bufIsEmpty (cryptToPlain) && !buffered)
442 plainEOF = TRUE;
443 break;
446 else
448 if (!bufIsEmpty (plainToCrypt) || tls_wr_interrupted)
449 FD_SET (cryptfd, &writefds);
450 if (!bufIsOverHWM (cryptToPlain))
451 FD_SET (cryptfd, &readfds);
454 /* Repeat select whilst EINTR happens */
457 timeout.tv_sec = wait ? 1 : 0;
458 timeout.tv_usec = 0;
459 result = select (maxfd, &readfds, &writefds, NULL, &timeout);
461 selecterrno = errno;
463 while ((result == -1) && (selecterrno == EINTR) && !quit (s));
464 if (quit (s))
465 break;
467 if (FD_ISSET (plainfd, &readfds))
469 /* we can read at least one byte */
470 void *addr = NULL;
471 /* get a span of characters to write to the
472 * buffer. As the empty portion may wrap the end of the
473 * circular buffer this might not be all we could read.
475 ssize_t len = bufGetWriteSpan (plainToCrypt, &addr);
476 if (len > 0)
478 ssize_t ret;
481 ret = read (plainfd, addr, (size_t) len);
483 while ((ret < 0) && (errno == EINTR) && !quit (s));
484 if (quit (s))
485 break;
486 if (ret < 0)
488 errout (s, "Error on read from plain socket: %m\n");
489 goto error;
491 if (ret == 0)
493 plainEOF = TRUE;
495 else
497 bufDoneWrite (plainToCrypt, ret); /* mark ret bytes as written to the buffer */
502 if (FD_ISSET (plainfd, &writefds))
504 /* we can write at least one byte */
505 void *addr = NULL;
506 /* get a span of characters to read from the buffer
507 * as the full portion may wrap the end of the circular buffer
508 * this might not be all we have to write.
510 ssize_t len = bufGetReadSpan (cryptToPlain, &addr);
511 if (len > 0)
513 ssize_t ret;
516 ret = write (plainfd, addr, (size_t) len);
518 while ((ret < 0) && (errno == EINTR) && !quit (s));
519 if (quit (s))
520 break;
521 if (ret < 0)
523 errout (s, "Error on write to plain socket: %m\n");
524 goto error;
526 bufDoneRead (cryptToPlain, ret); /* mark ret bytes as read from the buffer */
530 if (FD_ISSET (cryptfd, &readfds) || buffered)
532 /* we can read at least one byte */
533 void *addr = NULL;
534 /* get a span of characters to write to the
535 * buffer. As the empty portion may wrap the end of the
536 * circular buffer this might not be all we could read.
538 ssize_t len = bufGetWriteSpan (cryptToPlain, &addr);
539 if (len > 0)
541 ssize_t ret;
544 ret = gnutls_record_recv (s->session, addr, (size_t) len);
546 while (ret == GNUTLS_E_INTERRUPTED && !quit (s));
547 /* do not loop on GNUTLS_E_AGAIN - this means we'd block so we'd loop for
548 * ever
550 if (quit (s))
551 break;
552 if (ret < 0 && ret != GNUTLS_E_AGAIN)
554 errout (s, "Error on read from crypt socket: %s\n",
555 gnutls_strerror (ret));
556 goto error;
558 if (ret == 0)
560 cryptEOF = TRUE;
562 else
564 bufDoneWrite (cryptToPlain, ret); /* mark ret bytes as written to the buffer */
569 if (FD_ISSET (cryptfd, &writefds))
571 /* we can write at least one byte */
572 void *addr = NULL;
573 /* get a span of characters to read from the buffer
574 * as the full portion may wrap the end of the circular buffer
575 * this might not be all we have to write.
577 ssize_t len = bufGetReadSpan (plainToCrypt, &addr);
578 if (len > 0)
580 ssize_t ret;
583 if (tls_wr_interrupted)
585 ret = gnutls_record_send (s->session, NULL, 0);
587 else
589 ret = gnutls_record_send (s->session, addr, len);
592 while (ret == GNUTLS_E_INTERRUPTED && !quit (s));
593 if (quit (s))
594 break;
595 if (ret == GNUTLS_E_AGAIN)
597 /* we need to call this again with NULL parameters
598 * as it blocked
600 tls_wr_interrupted = TRUE;
602 else if (ret < 0)
604 errout (s, "Error on write to crypto socket: %s\n",
605 gnutls_strerror (ret));
606 goto error;
608 else
610 bufDoneRead (plainToCrypt, ret); /* mark ret bytes as read from the buffer */
616 ret = 0;
617 goto freereturn;
619 error:
620 ret = -1;
622 freereturn:
623 gnutls_bye (s->session, GNUTLS_SHUT_RDWR);
624 shutdown (plainfd, SHUT_RDWR);
625 bufFree (plainToCrypt);
626 bufFree (cryptToPlain);
627 return ret;