Ensure this is available, too
[nbd.git] / crypto-gnutls.c
blob9ce394d023dc274b8f98141936f619fbac9dc7ee
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, 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, &errpos);
307 if (ret < 0)
309 errout (s, "Cannot set GNUTLS session priority: %s\n",
310 gnutls_strerror (ret));
311 goto error;
314 gnutls_session_set_ptr (s->session, (void *) s);
316 ret = gnutls_credentials_set (s->session, GNUTLS_CRD_CERTIFICATE, s->creds);
317 if (ret < 0)
319 errout (s, "Cannot set session GNUTL credentials: %s\n",
320 gnutls_strerror (ret));
321 goto error;
324 if (isserver)
326 /* requests but does not check a client certificate */
327 gnutls_certificate_server_set_request (s->session, GNUTLS_CERT_REQUEST);
331 return s;
333 error:
334 if (s->session)
335 gnutls_deinit (s->session);
336 free (s);
337 return NULL;
340 void
341 tlssession_close (tlssession_t * s)
343 if (s->session)
344 gnutls_deinit (s->session);
345 free (s->hostname);
346 free (s);
350 tlssession_init ()
352 return gnutls_global_init ();
357 tlssession_mainloop (int cryptfd, int plainfd, tlssession_t * s)
359 fd_set readfds;
360 fd_set writefds;
361 int maxfd;
362 int tls_wr_interrupted = 0;
363 int plainEOF = FALSE;
364 int cryptEOF = FALSE;
365 int ret;
367 buffer_t *plainToCrypt = bufNew (BUF_SIZE, BUF_HWM);
368 buffer_t *cryptToPlain = bufNew (BUF_SIZE, BUF_HWM);
370 if (socksetnonblock (cryptfd, 0) < 0)
372 errout (s, "Could not turn on blocking: %m");
373 goto error;
376 /* set it up to work with our FD */
377 gnutls_transport_set_ptr (s->session,
378 (gnutls_transport_ptr_t) (intptr_t) cryptfd);
381 /* Now do the handshake */
382 ret = gnutls_handshake (s->session);
383 if (ret < 0)
385 errout (s, "TLS handshake failed: %s\n", gnutls_strerror (ret));
386 goto error;
389 if (socksetnonblock (cryptfd, 1) < 0)
391 errout (s, "Could not turn on non-blocking on crypt FD: %m");
392 goto error;
395 if (socksetnonblock (plainfd, 1) < 0)
397 errout (s, "Could not turn on non-blocking on plain FD: %m");
398 goto error;
401 maxfd = (plainfd > cryptfd) ? plainfd + 1 : cryptfd + 1;
403 while ((!plainEOF || !cryptEOF) && !quit (s))
405 struct timeval timeout;
406 int result;
407 int selecterrno;
408 int wait = TRUE;
410 FD_ZERO (&readfds);
411 FD_ZERO (&writefds);
413 size_t buffered = gnutls_record_check_pending (s->session);
414 if (buffered)
415 wait = FALSE; /* do not wait for select to return if we have buffered data */
417 if (plainEOF)
419 /* plain text end has closed, but me may still have
420 * data yet to write to the crypt end */
421 if (bufIsEmpty (plainToCrypt) && !tls_wr_interrupted)
423 cryptEOF = TRUE;
424 break;
427 else
429 if (!bufIsEmpty (cryptToPlain))
430 FD_SET (plainfd, &writefds);
431 if (!bufIsOverHWM (plainToCrypt))
432 FD_SET (plainfd, &readfds);
435 if (cryptEOF)
437 /* crypt end has closed, but me way still have data to
438 * write from the crypt buffer */
439 if (bufIsEmpty (cryptToPlain) && !buffered)
441 plainEOF = TRUE;
442 break;
445 else
447 if (!bufIsEmpty (plainToCrypt) || tls_wr_interrupted)
448 FD_SET (cryptfd, &writefds);
449 if (!bufIsOverHWM (cryptToPlain))
450 FD_SET (cryptfd, &readfds);
453 /* Repeat select whilst EINTR happens */
456 timeout.tv_sec = wait ? 1 : 0;
457 timeout.tv_usec = 0;
458 result = select (maxfd, &readfds, &writefds, NULL, &timeout);
460 selecterrno = errno;
462 while ((result == -1) && (selecterrno == EINTR) && !quit (s));
463 if (quit (s))
464 break;
466 if (FD_ISSET (plainfd, &readfds))
468 /* we can read at least one byte */
469 void *addr = NULL;
470 /* get a span of characters to write to the
471 * buffer. As the empty portion may wrap the end of the
472 * circular buffer this might not be all we could read.
474 ssize_t len = bufGetWriteSpan (plainToCrypt, &addr);
475 if (len > 0)
477 ssize_t ret;
480 ret = read (plainfd, addr, (size_t) len);
482 while ((ret < 0) && (errno == EINTR) && !quit (s));
483 if (quit (s))
484 break;
485 if (ret < 0)
487 errout (s, "Error on read from plain socket: %m\n");
488 goto error;
490 if (ret == 0)
492 plainEOF = TRUE;
494 else
496 bufDoneWrite (plainToCrypt, ret); /* mark ret bytes as written to the buffer */
501 if (FD_ISSET (plainfd, &writefds))
503 /* we can write at least one byte */
504 void *addr = NULL;
505 /* get a span of characters to read from the buffer
506 * as the full portion may wrap the end of the circular buffer
507 * this might not be all we have to write.
509 ssize_t len = bufGetReadSpan (cryptToPlain, &addr);
510 if (len > 0)
512 ssize_t ret;
515 ret = write (plainfd, addr, (size_t) len);
517 while ((ret < 0) && (errno == EINTR) && !quit (s));
518 if (quit (s))
519 break;
520 if (ret < 0)
522 errout (s, "Error on write to plain socket: %m\n");
523 goto error;
525 bufDoneRead (cryptToPlain, ret); /* mark ret bytes as read from the buffer */
529 if (FD_ISSET (cryptfd, &readfds) || buffered)
531 /* we can read at least one byte */
532 void *addr = NULL;
533 /* get a span of characters to write to the
534 * buffer. As the empty portion may wrap the end of the
535 * circular buffer this might not be all we could read.
537 ssize_t len = bufGetWriteSpan (cryptToPlain, &addr);
538 if (len > 0)
540 ssize_t ret;
543 ret = gnutls_record_recv (s->session, addr, (size_t) len);
545 while (ret == GNUTLS_E_INTERRUPTED && !quit (s));
546 /* do not loop on GNUTLS_E_AGAIN - this means we'd block so we'd loop for
547 * ever
549 if (quit (s))
550 break;
551 if (ret < 0 && ret != GNUTLS_E_AGAIN)
553 errout (s, "Error on read from crypt socket: %s\n",
554 gnutls_strerror (ret));
555 goto error;
557 if (ret == 0)
559 cryptEOF = TRUE;
561 else
563 bufDoneWrite (cryptToPlain, ret); /* mark ret bytes as written to the buffer */
568 if (FD_ISSET (cryptfd, &writefds))
570 /* we can write at least one byte */
571 void *addr = NULL;
572 /* get a span of characters to read from the buffer
573 * as the full portion may wrap the end of the circular buffer
574 * this might not be all we have to write.
576 ssize_t len = bufGetReadSpan (plainToCrypt, &addr);
577 if (len > 0)
579 ssize_t ret;
582 if (tls_wr_interrupted)
584 ret = gnutls_record_send (s->session, NULL, 0);
586 else
588 ret = gnutls_record_send (s->session, addr, len);
591 while (ret == GNUTLS_E_INTERRUPTED && !quit (s));
592 if (quit (s))
593 break;
594 if (ret == GNUTLS_E_AGAIN)
596 /* we need to call this again with NULL parameters
597 * as it blocked
599 tls_wr_interrupted = TRUE;
601 else if (ret < 0)
603 errout (s, "Error on write to crypto socket: %s\n",
604 gnutls_strerror (ret));
605 goto error;
607 else
609 bufDoneRead (plainToCrypt, ret); /* mark ret bytes as read from the buffer */
615 ret = 0;
616 goto freereturn;
618 error:
619 ret = -1;
621 freereturn:
622 gnutls_bye (s->session, GNUTLS_SHUT_RDWR);
623 shutdown (plainfd, SHUT_RDWR);
624 bufFree (plainToCrypt);
625 bufFree (cryptToPlain);
626 return ret;