diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 07:33:12 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 07:33:12 +0000 |
commit | 36082a2fe36ecd800d784ae44c14f1f18c66a7e9 (patch) | |
tree | 6c68e0c0097987aff85a01dabddd34b862309a7c /doc/examples/tlsproxy | |
parent | Initial commit. (diff) | |
download | gnutls28-upstream.tar.xz gnutls28-upstream.zip |
Adding upstream version 3.7.9.upstream/3.7.9upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | doc/examples/tlsproxy/LICENSE | 21 | ||||
-rw-r--r-- | doc/examples/tlsproxy/README.md | 53 | ||||
-rw-r--r-- | doc/examples/tlsproxy/buffer.c | 228 | ||||
-rw-r--r-- | doc/examples/tlsproxy/buffer.h | 45 | ||||
-rw-r--r-- | doc/examples/tlsproxy/crypto-gnutls.c | 585 | ||||
-rw-r--r-- | doc/examples/tlsproxy/crypto-gnutls.h | 43 | ||||
-rw-r--r-- | doc/examples/tlsproxy/tlsproxy.c | 464 |
7 files changed, 1439 insertions, 0 deletions
diff --git a/doc/examples/tlsproxy/LICENSE b/doc/examples/tlsproxy/LICENSE new file mode 100644 index 0000000..43f5934 --- /dev/null +++ b/doc/examples/tlsproxy/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Wrymouth Innovation Ltd + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/doc/examples/tlsproxy/README.md b/doc/examples/tlsproxy/README.md new file mode 100644 index 0000000..3c7a17f --- /dev/null +++ b/doc/examples/tlsproxy/README.md @@ -0,0 +1,53 @@ +tlsproxy +======== + +`tlsproxy` is a TLS proxy written with GnuTLS. It is mostly designed as an +example of how to use asynchronous (non-blocking) I/O with GnuTLS. More +accurately, it was designed so I could learn how to do it. I think I've +got it right. + +To that end, it's been divided up as follows: + +* `crypto.c` does all the crypto, and `tlssession_mainloop()` does the hard work. +* `buffer.c` provides ring buffer support. +* `tlsproxy.c` deals with command line options and connecting sockets. + +It can be used in two modes: + +* Client mode (default). Listens on an unencrypted port, connects to + an encrypted port. +* Server mode (run with `-s`). Listens on an encrypted port, connects to + an unencrypted port. + +Usage +===== + +``` +tlsproxy + +Usage: + tlsproxy [OPTIONS] + +A TLS client or server proxy + +Options: + -c, --connect ADDRESS Connect to ADDRESS + -l, --listen ADDRESS Listen on ADDRESS + -K, --key FILE Use FILE as private key + -C, --cert FILE Use FILE as public key + -A, --cacert FILE Use FILE as public CA cert file + -H, --hostname HOSTNAME Use HOSTNAME to validate the CN of the peer + rather than hostname extracted from -C option + -s, --server Run the listen port encrypted rather than the + connect port + -i, --insecure Do not validate certificates + -n, --nofork Do not fork off (aids debugging); specify twice + to stop forking on accept as well + -d, --debug Turn on debugging + -h, --help Show this usage message +``` + +License +======= + +MIT diff --git a/doc/examples/tlsproxy/buffer.c b/doc/examples/tlsproxy/buffer.c new file mode 100644 index 0000000..05c8212 --- /dev/null +++ b/doc/examples/tlsproxy/buffer.c @@ -0,0 +1,228 @@ +/* + +The MIT License (MIT) + +Copyright (c) 2016 Wrymouth Innovation Ltd + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#include "config.h" +#include <sys/types.h> + +#include "buffer.h" + +struct buffer +{ + char *buf; + ssize_t size; + ssize_t hwm; + ssize_t ridx; + ssize_t widx; + int empty; +}; + +/* the buffer is organised internally as follows: + * + * * There are b->size bytes in the buffer. + * + * * Bytes are at offsets 0 to b->size-1 + * + * * b->ridx points to the first readable byte + * + * * b->widx points to the first empty space + * + * * b->ridx < b->widx indicates a non-wrapped buffer: + * + * 0 ridx widx size + * | | | | + * V V V V + * ........XXXXXXXXX................ + * + * * b->ridx > b->widx indicates a wrapped buffer: + * + * 0 widx ridx size + * | | | | + * V V V V + * XXXXXXXX.........XXXXXXXXXXXXXXXX + * + * * b->ridx == b->widx indicates a FULL buffer: + * + * * b->ridx == b->widx indicates a wrapped buffer: + * + * 0 widx == ridx size + * | | | + * V V V + * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + * + * An empty buffer is indicated by empty=1 + * + */ + +buffer_t * +bufNew (ssize_t size, ssize_t hwm) +{ + buffer_t *b = calloc (1, sizeof (buffer_t)); + if (!b) return NULL; + + b->buf = calloc (1, size); + b->size = size; + b->hwm = hwm; + b->empty = 1; + return b; +} + + +void +bufFree (buffer_t * b) +{ + free (b->buf); + free (b); +} + +/* get a maximal span to read. Returns 0 if buffer + * is empty + */ +ssize_t +bufGetReadSpan (buffer_t * b, void **addr) +{ + if (b->empty) + { + *addr = NULL; + return 0; + } + *addr = &(b->buf[b->ridx]); + ssize_t len = b->widx - b->ridx; + if (len <= 0) + len = b->size - b->ridx; + return len; +} + +/* get a maximal span to write. Returns 0 id buffer is full + */ +ssize_t +bufGetWriteSpan (buffer_t * b, void **addr) +{ + if (b->empty) + { + *addr = b->buf; + b->ridx = 0; + b->widx = 0; + return b->size; + } + if (b->ridx == b->widx) + { + *addr = NULL; + return 0; + } + *addr = &(b->buf[b->widx]); + ssize_t len = b->ridx - b->widx; + if (len <= 0) + len = b->size - b->widx; + return len; +} + +/* mark size bytes as read */ +void +bufDoneRead (buffer_t * b, ssize_t size) +{ + while (!b->empty && (size > 0)) + { + /* empty can't occur here, so equal pointers means full */ + ssize_t len = b->widx - b->ridx; + if (len <= 0) + len = b->size - b->ridx; + + /* len is the number of bytes in one read span */ + if (len > size) + len = size; + + b->ridx += len; + if (b->ridx >= b->size) + b->ridx = 0; + + if (b->ridx == b->widx) + { + b->ridx = 0; + b->widx = 0; + b->empty = 1; + } + + size -= len; + } +} + +/* mark size bytes as written */ +void +bufDoneWrite (buffer_t * b, ssize_t size) +{ + while ((b->empty || (b->ridx != b->widx)) && (size > 0)) + { + /* full can't occur here, so equal pointers means empty */ + ssize_t len = b->ridx - b->widx; + if (len <= 0) + len = b->size - b->widx; + + /* len is the number of bytes in one write span */ + if (len > size) + len = size; + + b->widx += len; + if (b->widx >= b->size) + b->widx = 0; + + /* it can't be empty as we've written at least one byte */ + b->empty = 0; + + size -= len; + } +} + +int +bufIsEmpty (buffer_t * b) +{ + return b->empty; +} + +int +bufIsFull (buffer_t * b) +{ + return !b->empty && (b->ridx == b->widx); +} + +int +bufIsOverHWM (buffer_t * b) +{ + return bufGetCount (b) > b->hwm; +} + +ssize_t +bufGetFree (buffer_t * b) +{ + return b->size - bufGetCount (b); +} + +ssize_t +bufGetCount (buffer_t * b) +{ + if (b->empty) + return 0; + return b->widx - b->ridx + ((b->ridx < b->widx) ? 0 : b->size); +} diff --git a/doc/examples/tlsproxy/buffer.h b/doc/examples/tlsproxy/buffer.h new file mode 100644 index 0000000..c92b9a6 --- /dev/null +++ b/doc/examples/tlsproxy/buffer.h @@ -0,0 +1,45 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 Wrymouth Innovation Ltd + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef __TLSPROXY_BUFFERS_H +#define __TLSPROXY_BUFFERS_H + +#include <stdlib.h> +#include <sys/types.h> + +typedef struct buffer buffer_t; + +buffer_t *bufNew (ssize_t size, ssize_t hwm); +void bufFree (buffer_t * b); +ssize_t bufGetReadSpan (buffer_t * b, void **addr); +ssize_t bufGetWriteSpan (buffer_t * b, void **addr); +void bufDoneRead (buffer_t * b, ssize_t size); +void bufDoneWrite (buffer_t * b, ssize_t size); +int bufIsEmpty (buffer_t * b); +int bufIsFull (buffer_t * b); +int bufIsOverHWM (buffer_t * b); +ssize_t bufGetFree (buffer_t * b); +ssize_t bufGetCount (buffer_t * b); + +#endif diff --git a/doc/examples/tlsproxy/crypto-gnutls.c b/doc/examples/tlsproxy/crypto-gnutls.c new file mode 100644 index 0000000..5db51a3 --- /dev/null +++ b/doc/examples/tlsproxy/crypto-gnutls.c @@ -0,0 +1,585 @@ +/* + +The MIT License (MIT) + +Copyright (c) 2016 Wrymouth Innovation Ltd + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#include "config.h" + +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#include <gnutls/x509.h> +#include <gnutls/abstract.h> + +#include "crypto-gnutls.h" +#include "buffer.h" + +#define FALSE 0 +#define TRUE 1 + +struct tlssession +{ + gnutls_certificate_credentials_t creds; + gnutls_session_t session; + char *hostname; + int (*quitfn) (void *opaque); + int (*erroutfn) (void *opaque, const char *format, va_list ap); + int debug; + void *opaque; +}; + +#define BUF_SIZE 65536 +#define BUF_HWM ((BUF_SIZE*3)/4) + +static int +falsequit (void *opaque) +{ + return FALSE; +} + +static int +quit (tlssession_t * s) +{ + return s->quitfn (s->opaque); +} + +#if defined __clang__ || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) +# pragma GCC diagnostic ignored "-Wsuggest-attribute=format" +#endif + +static int stderrout (void *opaque, const char *format, va_list ap) +{ + return vfprintf (stderr, format, ap); +} + +static int +errout (tlssession_t * s, const char *format, ...) +{ + va_list ap; + int ret; + va_start (ap, format); + ret = s->erroutfn (s->opaque, format, ap); + va_end (ap); + return ret; +} + +static int +debugout (tlssession_t * s, const char *format, ...) +{ + va_list ap; + int ret = 0; + va_start (ap, format); + if (s->debug) + ret = s->erroutfn (s->opaque, format, ap); + va_end (ap); + return ret; +} + +static int +socksetnonblock (int fd, int nb) +{ + int sf = fcntl (fd, F_GETFL, 0); + if (sf == -1) + return -1; + return fcntl (fd, F_SETFL, nb ? (sf | O_NONBLOCK) : (sf & ~O_NONBLOCK)); +} + +/* From (public domain) example file in GNUTLS + * + * This function will try to verify the peer's certificate, and + * also check if the hostname matches, and the activation, expiration dates. + */ +static int +verify_certificate_callback (gnutls_session_t session) +{ + unsigned int status; + int ret; + tlssession_t *s; + + /* read session pointer */ + s = (tlssession_t *) gnutls_session_get_ptr (session); + + if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) + return GNUTLS_E_CERTIFICATE_ERROR; + + /* This verification function uses the trusted CAs in the credentials + * structure. So you must have installed one or more CA certificates. + */ + if (s->hostname && *s->hostname) + ret = gnutls_certificate_verify_peers3 (session, s->hostname, &status); + else + ret = gnutls_certificate_verify_peers2 (session, &status); + + if (ret < 0) + { + debugout (s, "Could not verify peer certificate due to an error\n"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + if (status) + { + gnutls_datum_t txt; + ret = gnutls_certificate_verification_status_print(status, GNUTLS_CRT_X509, + &txt, 0); + if (ret >= 0) + { + debugout (s, "verification error: %s\n", txt.data); + gnutls_free(txt.data); + } + + return GNUTLS_E_CERTIFICATE_ERROR; + } + + debugout (s, "Peer passed certificate verification\n"); + + /* notify gnutls to continue handshake normally */ + return 0; +} + +tlssession_t * +tlssession_new (int isserver, + char *keyfile, char *certfile, char *cacertfile, + char *hostname, int insecure, int debug, + int (*quitfn) (void *opaque), + int (*erroutfn) (void *opaque, const char *format, + va_list ap), void *opaque) +{ + int ret; + tlssession_t *s = calloc (1, sizeof (tlssession_t)); + if (!s) + return NULL; + + if (quitfn) + s->quitfn = quitfn; + else + s->quitfn = falsequit; + + if (erroutfn) + s->erroutfn = erroutfn; + else + s->erroutfn = stderrout; + + if (hostname) + s->hostname = strdup (hostname); + + s->debug = debug; + + if (gnutls_certificate_allocate_credentials (&s->creds) < 0) + { + errout (s, "Certificate allocation memory error\n"); + goto error; + } + + if (cacertfile != NULL) + { + ret = + gnutls_certificate_set_x509_trust_file (s->creds, cacertfile, + GNUTLS_X509_FMT_PEM); + if (ret < 0) + { + errout (s, "Error setting the x509 trust file: %s\n", + gnutls_strerror (ret)); + goto error; + } + + if (!insecure) + { + gnutls_certificate_set_verify_function (s->creds, + verify_certificate_callback); + gnutls_certificate_set_verify_flags (s->creds, + GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); + } + } + + if (keyfile && !certfile) + certfile = keyfile; + + if (certfile != NULL && keyfile != NULL) + { + ret = + gnutls_certificate_set_x509_key_file (s->creds, certfile, keyfile, + GNUTLS_X509_FMT_PEM); + + if (ret < 0) + { + errout (s, + "Error loading certificate or key file (%s, %s): %s\n", + certfile, keyfile, gnutls_strerror (ret)); + goto error; + } + } + + if (isserver) + ret = gnutls_init (&s->session, GNUTLS_SERVER); + else + ret = gnutls_init (&s->session, GNUTLS_CLIENT); + + if (ret < 0) + { + errout (s, "Cannot initialize GNUTLS session: %s\n", + gnutls_strerror (ret)); + goto error; + } + + gnutls_session_set_ptr (s->session, (void *) s); + + if (!isserver && s->hostname && *s->hostname) + { + ret = gnutls_server_name_set (s->session, GNUTLS_NAME_DNS, s->hostname, + strlen (s->hostname)); + if (ret < 0) + { + errout (s, "Cannot set server name: %s\n", + gnutls_strerror (ret)); + goto error; + } + } + + ret = gnutls_set_default_priority (s->session); + if (ret < 0) + { + errout (s, "Cannot set default GNUTLS session priority: %s\n", + gnutls_strerror (ret)); + goto error; + } + + ret = gnutls_credentials_set (s->session, GNUTLS_CRD_CERTIFICATE, s->creds); + if (ret < 0) + { + errout (s, "Cannot set session GNUTL credentials: %s\n", + gnutls_strerror (ret)); + goto error; + } + + if (isserver) + { + /* requests but does not check a client certificate */ + gnutls_certificate_server_set_request (s->session, GNUTLS_CERT_REQUEST); + } + + + return s; + +error: + if (s->session) + gnutls_deinit (s->session); + free (s); + return NULL; +} + +void +tlssession_close (tlssession_t * s) +{ + if (s->session) + gnutls_deinit (s->session); + free (s->hostname); + free (s); +} + +int +tlssession_init (void) +{ + return gnutls_global_init (); +} + + +int +tlssession_mainloop (int cryptfd, int plainfd, tlssession_t * s) +{ + fd_set readfds; + fd_set writefds; + int maxfd; + int tls_wr_interrupted = 0; + int plainEOF = FALSE; + int cryptEOF = FALSE; + ssize_t ret; + + buffer_t *plainToCrypt = bufNew (BUF_SIZE, BUF_HWM); + buffer_t *cryptToPlain = bufNew (BUF_SIZE, BUF_HWM); + + if (socksetnonblock (cryptfd, 0) < 0) + { + errout (s, "Could not turn on blocking: %m"); + goto error; + } + + /* set it up to work with our FD */ + gnutls_transport_set_ptr (s->session, + (gnutls_transport_ptr_t) (intptr_t) cryptfd); + + + /* Now do the handshake */ + ret = gnutls_handshake (s->session); + if (ret < 0) + { + errout (s, "TLS handshake failed: %s\n", gnutls_strerror (ret)); + goto error; + } + + if (socksetnonblock (cryptfd, 1) < 0) + { + errout (s, "Could not turn on non-blocking on crypt FD: %m"); + goto error; + } + + if (socksetnonblock (plainfd, 1) < 0) + { + errout (s, "Could not turn on non-blocking on plain FD: %m"); + goto error; + } + + maxfd = (plainfd > cryptfd) ? plainfd + 1 : cryptfd + 1; + + while ((!plainEOF || !cryptEOF) && !quit (s)) + { + struct timeval timeout; + int result; + int selecterrno; + int wait = TRUE; + + FD_ZERO (&readfds); + FD_ZERO (&writefds); + + size_t buffered = gnutls_record_check_pending (s->session); + if (buffered) + wait = FALSE; /* do not wait for select to return if we have buffered data */ + + if (plainEOF) + { + /* plain text end has closed, but me may still have + * data yet to write to the crypt end */ + if (bufIsEmpty (plainToCrypt) && !tls_wr_interrupted) + { + cryptEOF = TRUE; + break; + } + } + else + { + if (!bufIsEmpty (cryptToPlain)) + FD_SET (plainfd, &writefds); + if (!bufIsOverHWM (plainToCrypt)) + FD_SET (plainfd, &readfds); + } + + if (cryptEOF) + { + /* crypt end has closed, but me way still have data to + * write from the crypt buffer */ + if (bufIsEmpty (cryptToPlain) && !buffered) + { + plainEOF = TRUE; + break; + } + } + else + { + if (!bufIsEmpty (plainToCrypt) || tls_wr_interrupted) + FD_SET (cryptfd, &writefds); + if (!bufIsOverHWM (cryptToPlain)) + FD_SET (cryptfd, &readfds); + } + + /* Repeat select whilst EINTR happens */ + do + { + timeout.tv_sec = wait ? 1 : 0; + timeout.tv_usec = 0; + result = select (maxfd, &readfds, &writefds, NULL, &timeout); + + selecterrno = errno; + } + while ((result == -1) && (selecterrno == EINTR) && !quit (s)); + if (quit (s)) + break; + + if (FD_ISSET (plainfd, &readfds)) + { + /* we can read at least one byte */ + void *addr = NULL; + /* get a span of characters to write to the + * buffer. As the empty portion may wrap the end of the + * circular buffer this might not be all we could read. + */ + ssize_t len = bufGetWriteSpan (plainToCrypt, &addr); + if (len > 0) + { + do + { + ret = read (plainfd, addr, (size_t) len); + } + while ((ret < 0) && (errno == EINTR) && !quit (s)); + if (quit (s)) + break; + if (ret < 0) + { + errout (s, "Error on read from plain socket: %m\n"); + goto error; + } + if (ret == 0) + { + plainEOF = TRUE; + } + else + { + bufDoneWrite (plainToCrypt, ret); /* mark ret bytes as written to the buffer */ + } + } + } + + if (FD_ISSET (plainfd, &writefds)) + { + /* we can write at least one byte */ + void *addr = NULL; + /* get a span of characters to read from the buffer + * as the full portion may wrap the end of the circular buffer + * this might not be all we have to write. + */ + ssize_t len = bufGetReadSpan (cryptToPlain, &addr); + if (len > 0) + { + do + { + ret = write (plainfd, addr, (size_t) len); + } + while ((ret < 0) && (errno == EINTR) && !quit (s)); + if (quit (s)) + break; + if (ret < 0) + { + errout (s, "Error on write to plain socket: %m\n"); + goto error; + } + bufDoneRead (cryptToPlain, ret); /* mark ret bytes as read from the buffer */ + } + } + + if (FD_ISSET (cryptfd, &readfds) || buffered) + { + /* we can read at least one byte */ + void *addr = NULL; + /* get a span of characters to write to the + * buffer. As the empty portion may wrap the end of the + * circular buffer this might not be all we could read. + */ + ssize_t len = bufGetWriteSpan (cryptToPlain, &addr); + if (len > 0) + { + do + { + ret = gnutls_record_recv (s->session, addr, (size_t) len); + } + while (ret == GNUTLS_E_INTERRUPTED && !quit (s)); + /* do not loop on GNUTLS_E_AGAIN - this means we'd block so we'd loop for + * ever + */ + if (quit (s)) + break; + if (ret < 0 && ret != GNUTLS_E_AGAIN) + { + errout (s, "Error on read from crypt socket: %s\n", + gnutls_strerror (ret)); + goto error; + } + if (ret == 0) + { + cryptEOF = TRUE; + } + else + { + bufDoneWrite (cryptToPlain, ret); /* mark ret bytes as written to the buffer */ + } + } + } + + if (FD_ISSET (cryptfd, &writefds)) + { + /* we can write at least one byte */ + void *addr = NULL; + /* get a span of characters to read from the buffer + * as the full portion may wrap the end of the circular buffer + * this might not be all we have to write. + */ + ssize_t len = bufGetReadSpan (plainToCrypt, &addr); + if (len > 0) + { + do + { + if (tls_wr_interrupted) + { + ret = gnutls_record_send (s->session, NULL, 0); + } + else + { + ret = gnutls_record_send (s->session, addr, len); + } + } + while (ret == GNUTLS_E_INTERRUPTED && !quit (s)); + if (quit (s)) + break; + if (ret == GNUTLS_E_AGAIN) + { + /* we need to call this again with NULL parameters + * as it blocked + */ + tls_wr_interrupted = TRUE; + } + else if (ret < 0) + { + errout (s, "Error on write to crypto socket: %s\n", + gnutls_strerror (ret)); + goto error; + } + else + { + bufDoneRead (plainToCrypt, ret); /* mark ret bytes as read from the buffer */ + } + } + } + } + + ret = 0; + goto freereturn; + +error: + ret = -1; + +freereturn: + gnutls_bye (s->session, GNUTLS_SHUT_RDWR); + shutdown (plainfd, SHUT_RDWR); + bufFree (plainToCrypt); + bufFree (cryptToPlain); + return ret; +} diff --git a/doc/examples/tlsproxy/crypto-gnutls.h b/doc/examples/tlsproxy/crypto-gnutls.h new file mode 100644 index 0000000..2b6c402 --- /dev/null +++ b/doc/examples/tlsproxy/crypto-gnutls.h @@ -0,0 +1,43 @@ +/* + +The MIT License (MIT) + +Copyright (c) 2016 Wrymouth Innovation Ltd + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifndef __TLSPROXY_CRYPTO_GNUTLS_H +#define __TLSPROXY_CRYPTO_GNUTLS_H + +int tlssession_init (void); + +typedef struct tlssession tlssession_t; +tlssession_t *tlssession_new (int isserver, + char *keyfile, char *certfile, char *cacertfile, + char *hostname, int insecure, int debug, + int (*quitfn) (void *opaque), + int (*erroutfn) (void *opaque, + const char *format, + va_list ap), void *opaque); +void tlssession_close (tlssession_t * s); +int tlssession_mainloop (int cryptfd, int plainfd, tlssession_t * session); + +#endif diff --git a/doc/examples/tlsproxy/tlsproxy.c b/doc/examples/tlsproxy/tlsproxy.c new file mode 100644 index 0000000..8e781f7 --- /dev/null +++ b/doc/examples/tlsproxy/tlsproxy.c @@ -0,0 +1,464 @@ +/* + +The MIT License (MIT) + +Copyright (c) 2016 Wrymouth Innovation Ltd + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#include "config.h" + +#include <errno.h> +#include <getopt.h> +#include <netdb.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> + +#include "crypto-gnutls.h" + +static char *connectaddr = NULL; +static char *listenaddr = NULL; +static char *keyfile = NULL; +static char *certfile = NULL; +static char *cacertfile = NULL; +static char *hostname = NULL; +static int debug = 0; +static int insecure = 0; +static int nofork = 0; +static int server = 0; + +static const char *defaultport = "12345"; + +static volatile sig_atomic_t rxsigquit = 0; + +static int +bindtoaddress (char *addrport) +{ + struct addrinfo hints; + struct addrinfo *result, *rp; + int fd, s; + char addr[128]; + + snprintf(addr, sizeof(addr), "%s", addrport); + + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_STREAM; /* Stream socket */ + hints.ai_protocol = 0; /* any protocol */ + + char *colon = strrchr (addr, ':'); + const char *port = defaultport; + if (colon) + { + *colon = 0; + port = colon + 1; + } + + s = getaddrinfo (addr, port, &hints, &result); + if (s != 0) + { + fprintf (stderr, "Error in address %s: %s\n", addr, gai_strerror (s)); + return -1; + } + + /* attempt to bind to each address */ + + for (rp = result; rp != NULL; rp = rp->ai_next) + { + fd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); + + if (fd >= 0) + { + int one = 1; + if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof (one)) < + 0) + { + close (fd); + continue; + } + if (bind (fd, rp->ai_addr, rp->ai_addrlen) == 0) + break; + close (fd); + } + } + + if (!rp) + { + fprintf (stderr, "Error binding to %s:%s: %m\n", addr, port); + freeaddrinfo (result); + return -1; + } + + freeaddrinfo (result); /* No longer needed */ + + if (listen (fd, 5) < 0) + { + close (fd); + return -1; + } + + return fd; +} + +static int +connecttoaddress (char *addrport) +{ + struct addrinfo hints; + struct addrinfo *result, *rp; + int fd, s; + char addr[128]; + + snprintf(addr, sizeof(addr), "%s", addrport); + + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_STREAM; /* Stream socket */ + hints.ai_protocol = 0; /* any protocol */ + + char *colon = strrchr (addr, ':'); + const char *port = defaultport; + if (colon) + { + *colon = 0; + port = colon + 1; + } + + if (!hostname && !server) + hostname = strdup (addr); + + s = getaddrinfo (addr, port, &hints, &result); + if (s != 0) + { + fprintf (stderr, "Error in address %s: %s\n", addr, gai_strerror (s)); + return -1; + } + + /* attempt to connect to each address */ + for (rp = result; rp != NULL; rp = rp->ai_next) + { + fd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd >= 0) + { + if (connect (fd, rp->ai_addr, rp->ai_addrlen) == 0) + break; + close (fd); + } + } + + if (!rp) + { + fprintf (stderr, "Error connecting to %s:%s: %m\n", addr, port); + freeaddrinfo (result); + return -1; + } + + freeaddrinfo (result); /* No longer needed */ + + return fd; +} + +static int +quitfn (void *opaque) +{ + return rxsigquit; +} + +static int +runproxy (int acceptfd) +{ + int connectfd; + if ((connectfd = connecttoaddress (connectaddr)) < 0) + { + fprintf (stderr, "Could not connect\n"); + close (acceptfd); + return -1; + } + + tlssession_t *session = + tlssession_new (server, keyfile, certfile, cacertfile, hostname, insecure, + debug, quitfn, NULL, NULL); + if (!session) + { + fprintf (stderr, "Could create TLS session\n"); + close (connectfd); + close (acceptfd); + return -1; + } + + int ret; + if (server) + ret = tlssession_mainloop (acceptfd, connectfd, session); + else + ret = tlssession_mainloop (connectfd, acceptfd, session); + + tlssession_close (session); + close (connectfd); + close (acceptfd); + + if (ret < 0) + { + fprintf (stderr, "TLS proxy exited with an error\n"); + return -1; + } + return 0; +} + +static int +runlistener (void) +{ + int listenfd; + if ((listenfd = bindtoaddress (listenaddr)) < 0) + { + fprintf (stderr, "Could not bind listener\n"); + return -1; + } + + /* + if (!nofork) + daemon (FALSE, FALSE); + */ + + int fd; + while (!rxsigquit) + { + do + { + if ((fd = accept (listenfd, NULL, NULL)) < 0) + { + if (errno != EINTR) + { + fprintf (stderr, "Accept failed\n"); + return -1; + } + } + } + while (fd < 0 && !rxsigquit); + if (rxsigquit) + break; + if (nofork < 2) + { + int ret = runproxy (fd); + if (ret < 0) + return -1; + } + else + { + int cpid = fork (); + if (cpid == 0) + { + /* we're the child */ + runproxy (fd); + exit (0); + } + else + close (fd); + } + } + return 0; +} + +static void +usage (void) +{ + fprintf (stderr, "tlsproxy\n\n\ +Usage:\n\ + tlsproxy [OPTIONS]\n\ +\n\ +A TLS client or server proxy\n\ +\n\ +Options:\n\ + -c, --connect ADDRESS Connect to ADDRESS\n\ + -l, --listen ADDRESS Listen on ADDRESS\n\ + -K, --key FILE Use FILE as private key\n\ + -C, --cert FILE Use FILE as public key\n\ + -A, --cacert FILE Use FILE as public CA cert file\n\ + -H, --hostname HOSTNAME Use HOSTNAME to validate the CN of the peer\n\ + rather than hostname extracted from -C option\n\ + -s, --server Run the listen port encrypted rather than the\n\ + connect port\n\ + -i, --insecure Do not validate certificates\n\ + -n, --nofork Do not fork off (aids debugging); specify twice\n\ + to stop forking on accept as well\n\ + -d, --debug Turn on debugging\n\ + -h, --help Show this usage message\n\ +\n\ +\n"); +} + +static void +processoptions (int argc, char **argv) +{ + while (1) + { + static const struct option longopts[] = { + {"connect", required_argument, 0, 'c'}, + {"listen", required_argument, 0, 'l'}, + {"key", required_argument, 0, 'K'}, + {"cert", required_argument, 0, 'C'}, + {"cacert", required_argument, 0, 'A'}, + {"hostname", required_argument, 0, 'H'}, + {"server", no_argument, 0, 's'}, + {"insecure", no_argument, 0, 'i'}, + {"nofork", no_argument, 0, 'n'}, + {"debug", no_argument, 0, 'd'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + int optidx = 0; + + int c = + getopt_long (argc, argv, "c:l:K:C:A:H:sindh", longopts, &optidx); + if (c == -1) + break; + + switch (c) + { + case 0: /* set a flag, nothing else to do */ + break; + + case 'c': + free (connectaddr); + connectaddr = strdup (optarg); + break; + + case 'l': + free (listenaddr); + listenaddr = strdup (optarg); + break; + + case 'K': + free (keyfile); + keyfile = strdup (optarg); + break; + + case 'C': + free (certfile); + certfile = strdup (optarg); + break; + + case 'A': + free (cacertfile); + cacertfile = strdup (optarg); + break; + + case 'H': + free (hostname); + hostname = strdup (optarg); + break; + + case 's': + server = 1; + break; + + case 'i': + insecure = 1; + break; + + case 'n': + nofork++; + break; + + case 'd': + debug++; + break; + + case 'h': + usage (); + exit (0); + break; + + default: + usage (); + exit (1); + } + } + + if (optind != argc || !connectaddr || !listenaddr) + { + usage (); + exit (1); + } + + if (!certfile && keyfile) + certfile = strdup (keyfile); +} + +static void +handlesignal (int sig) +{ + switch (sig) + { + case SIGINT: + case SIGTERM: + rxsigquit++; + break; + default: + break; + } +} + +static void +setsignalmasks (void) +{ + struct sigaction sa; + /* Set up the structure to specify the new action. */ + memset (&sa, 0, sizeof (struct sigaction)); + sa.sa_handler = handlesignal; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGINT, &sa, NULL); + sigaction (SIGTERM, &sa, NULL); + + memset (&sa, 0, sizeof (struct sigaction)); + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_RESTART; + sigaction (SIGPIPE, &sa, NULL); +} + +int +main (int argc, char **argv) +{ + processoptions (argc, argv); + + setsignalmasks (); + + if (tlssession_init ()) + exit (1); + + runlistener (); + + free (connectaddr); + free (listenaddr); + free (keyfile); + free (certfile); + free (cacertfile); + free (hostname); + + exit (0); +} |