Added TLS Tunnel command, client or server mode.
authorRick van Rein <rick@openfortress.nl>
Fri, 26 Jul 2013 10:34:09 +0000 (11:34 +0100)
committerRick van Rein <rick@openfortress.nl>
Fri, 26 Jul 2013 10:34:09 +0000 (11:34 +0100)
To test connect to TCP server through client and server sides:
./tlstunnel -c -L rick@hiero ::1 22334 ::1 22335
./tlstunnel -s -L rick@daaro ::1 22335 2001:db8:123::456 80
nc ::1 22334

doc/man/tlspool.man
doc/man/tlstunnel.man [new file with mode: 0644]
lib/Makefile
lib/tlstunnel.c [new file with mode: 0644]

index 206e232..0d5cf1c 100644 (file)
@@ -66,3 +66,4 @@ SEE ALSO
 
 starttls_client
 starttls_server
+tlstunnel
diff --git a/doc/man/tlstunnel.man b/doc/man/tlstunnel.man
new file mode 100644 (file)
index 0000000..a0b8541
--- /dev/null
@@ -0,0 +1,142 @@
+NAME
+
+tlstunnel -- Forward TLS/DTLS connections as TCP/UDP/SCTP, and vice versa.
+
+SYNOPSIS
+
+tlstunnel -c|-s [-u|-t|-x #|-y #] -l id [-r id] [-S path] inaddr inport fwaddr fwport
+
+DESCRIPTION
+
+The tlstunnel command wraps TLS around connections that are otherwise
+plaintext.  This cannot used for protocols that do STARTTLS in the
+course of their protocol interactions; it is purely meant to support
+protocols that iniatiate TLS immediately over newly established
+connections; for example, a HTTPS connection wraps TLS around a plain
+HTTP connection.
+
+The tunnel can be built up using either TLS or DTLS, the latter of which
+is the datagram variety of normal, stream-oriented TLS.  When relaying
+a TCP connection, TLS is the only available option; when relaying a
+UDP connection, DTLS is the only available option; when relaying an
+SCPT connection, both varieties are possible but DTLS is advised for
+efficiency reasons.
+
+The tunnel runs in either client or server mode.  Used as a client, the
+service listens for plain text connections made to address inaddr and
+port inport, wraps them in TLS as a client and forwards the connection
+to address fwaddr and fwport.  When used as a server, the same thing
+is done, however the wrapping of TLS is changed to unwrapping of TLS.
+Addresses are written as IPv6 addresses, with backward compatibility to
+IPv4 through the prefixing of :: in front of an IPv4 address.  It is
+not required that inaddr and fwaddr are of the same IP version.
+
+The end points authenticate, and are authorized, as configured in the
+system-wide TLS Pool configuration settings.  These settings ensure a
+consistent default behaviour for all TLS Pool connections.  These
+settings include certificate capabilities and visibilities.
+
+
+OPTIONS
+
+-c | --client
+
+       Run the TLS Tunnel as a client, meaning that it is connected to by
+       a plaintext connection and forwarded as a TLS-wrapped variety.
+
+       Client authentication and authorization are implemented as specified
+       in the TLS Pool configuration.  This includes choice of certificates
+       and settings about their visibility.
+
+-s | --server
+
+       Run the TLS Tunnel as a server, meaning that it is connected to by
+       a TLS-wrapped connection and forwarded as a plaintext variety.
+
+       Client authentication and authorization are implemented as specified
+       in the TLS Pool configuration.  This includes choice of certificates
+       and settings about their visibility.
+
+-t | --tcp | --tcp-tls
+
+       Listen to, and forward as, TCP connection.  The secure wrapping
+       is implemented through stream-oriented TLS.
+
+       This is also the default option, selected when none of --tcp-tls,
+       --udp-dtls, --sctp-tls or --sctp-dtls has been supplied.
+
+-u | --udp | --udp-dtls
+
+       THIS FEATURE IS NOT IMPLEMENTED.  The semantics cannot be clearly
+       defined, because UDP has no notion of a connection, while TLS does
+       need it to keep state.  Rather than implementing guesses here and
+       load it with configuration options such as timeouts, it is advised
+       to implement DTLS directly in the UDP application.
+
+-x stream | --sctp-dtls stream
+
+       Listen to, and forward as, SCTP.  The secure wrapping
+       is implemented through frame-oriented DTLS, as advised for
+       SCTP because that retains the indepent delivery that SCTP offers.
+
+       The stream is the number of the stream withing SCTP over which the
+       DTLS negotiations are made.
+
+-y stream | --sctp-tls stream
+
+       Listen to, and forward as, SCTP.  The secure wrapping
+       is implemented through stream-oriented TLS, which is not advised for
+       SCTP but which may nonetheless be used by a protocol implementation.
+
+       The stream is the number of the stream withing SCTP over which the
+       TLS negotiations are made.
+
+-L id | --local-id id
+
+       Set the local identity.  This should be of the form of a domain
+       name, or user@domainname.  This paramater is required.
+
+-R id | --remote-id id
+
+       Constrain the remote identity.  This should be of the form of a domain
+       name, or user@domainname.  This parameter is optional.
+
+-S path | --tlspool-socket-path path
+
+       Override the built-in default path for the socket pool.  This can be
+       used to address a specific instance of the TLS Pool, for instance one
+       that is run under a personal account, accessing a personally held
+       PKCS #11 token.
+
+
+LIMITATIONS
+
+The TLS Pool can handle various options when connecting to a remote site.
+These options have not yet been implemented as commandline switches, so
+this level of refinement cannot be added when using a TLS Tunnel.
+
+The TLS Pool usually negotiates local and remote identities as part of
+the authentication and authorization procedures.  These procedures do not
+interface optimally with the TLS Tunnel, because there is no place to
+leave this information.  What has been implemented is the mandatory
+-l option that sets the local identifier to use, and an optional -r option
+tp constrain the remote identity.
+
+Note that the TLS Tunnel does not implement a PIN entry interface; this
+is a function that is separately coordinated with the TLS Pool, so it can
+bring up a dialog in a trusted environment, making it easier to secure than
+any per-command PIN entry dialog.
+
+Where TLS and even DTLS have a notion of connection state, necessary for the
+management of things like session keys, there is no concept of a connection
+for UDP transmission.  For this reason, the UDP variant of this tool has not
+been implemented; it would involve close-to-NAT practices that can easily
+lead to confusion.
+
+AUTHOR
+
+Written by Rick van Rein of OpenFortress.
+
+SEE ALSO
+
+tlspool
index 3f79cf1..0cfbe8d 100644 (file)
@@ -1,7 +1,7 @@
-all: libstarttls.so libstarttls.a testcli testsrv
+all: libstarttls.so libstarttls.a tlstunnel testcli testsrv
 
 clean:
-       rm -f *.o libstarttls.so libstarttls.a testcli testsrv
+       rm -f *.o libstarttls.so libstarttls.a testcli testsrv tlstunnel
 
 .c.o:
        gcc -ggdb3 -pthread -fPIC -I ../include -I /usr/local/include -c -o $@ $<
@@ -13,6 +13,9 @@ libstarttls.a: libfun.o
        rm -f $@
        ar rc $@ libfun.o
 
+tlstunnel: tlstunnel.o libstarttls.so
+       gcc -pthread -I ../include -o $@ tlstunnel.o libstarttls.so
+
 testcli: testcli.o libstarttls.a
        gcc -o $@ testcli.o libstarttls.a
 
diff --git a/lib/tlstunnel.c b/lib/tlstunnel.c
new file mode 100644 (file)
index 0000000..2f6f899
--- /dev/null
@@ -0,0 +1,330 @@
+/* tlspool/tlstunnel.c -- Simple utility, forward TLS as TCP and vice versa */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <errno.h>
+#include <poll.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <tlspool/starttls.h>
+
+
+static starttls_t tlsdata = {
+       .flags = 0,
+       .local = 0,
+};
+
+
+struct copydata {
+       pthread_t thread;
+       int cnx;
+       int fwd;
+};
+
+
+void copycat (void *cd_void) {
+       struct copydata *cd = (struct copydata *) cd_void;
+       struct pollfd inout [2];
+       char buf [1024];
+       ssize_t sz;
+       inout [0].fd = cd->cnx;
+       inout [1].fd = cd->fwd;
+       inout [0].events = inout [1].events = POLLIN;
+       printf ("DEBUG: Starting copycat cycle for insox=%d, fwsox=%d\n", cd->cnx, cd->fwd);
+       while (1) {
+               if (poll (inout, 2, -1) == -1) {
+                       printf ("DEBUG: Copycat polling returned an error\n");
+                       break;
+               }
+               if ((inout [0].revents | inout [1].revents) & ~POLLIN) {
+                       printf ("DEBUG: Copycat polling returned a special condition\n");
+                       break;
+               }
+               if (inout [0].revents & POLLIN) {
+                       sz = recv (cd->cnx, buf, sizeof (buf), MSG_DONTWAIT);
+                       printf ("DEBUG: Copycat received %d local bytes from %d\n", (int) sz, cd->cnx);
+                       if (sz == -1) {
+                               break;  // stream error
+                       } else if (sz == 0) {
+                               errno = 0;
+                               break;  // orderly shutdown
+                       } else if (send (cd->fwd, buf, sz, MSG_DONTWAIT) != sz) {
+                               break;  // communication error
+                       } else {
+                               printf ("Copycat forwarded %d bytes to %d\n", (int) sz, cd->fwd);
+                       }
+               }
+               if (inout [1].revents & POLLIN) {
+                       sz = recv (cd->fwd, buf, sizeof (buf), MSG_DONTWAIT);
+                       printf ("DEBUG: Copycat received %d reply bytes from %d\n", (int) sz, cd->fwd);
+                       if (sz == -1) {
+                               break;  // stream error
+                       } else if (sz == 0) {
+                               errno = 0;
+                               break;  // orderly shutdown
+                       } else if (send (cd->cnx, buf, sz, MSG_DONTWAIT) != sz) {
+                               break;  // communication error
+                       } else {
+                               printf ("Copycat returned %d bytes to %d\n", (int) sz, cd->cnx);
+                       }
+               }
+       }
+       printf ("DEBUG: Ending copycat cycle for insox=%d, fwsox=%d\n", cd->cnx, cd->fwd);
+       close (cd->cnx);
+       close (cd->fwd);
+       free (cd);      // Contains pthread_t but that's only an ID
+}
+
+
+int str2port (char *portstr) {
+       char *portrest;
+       long int retval = strtol (portstr, &portrest, 0);
+       if (*portrest) {
+               fprintf (stderr, "Not a port number: %s\n", portstr);
+               exit (1);
+       }
+       if ((retval <= 0) || (retval > 65535)) {
+               fprintf (stderr, "Port numbers range from 1 to 65535: %s\n", portstr);
+               exit (1);
+       }
+       return (int) retval;
+}
+
+
+int main (int argc, char *argv []) {
+       int parsing = 1;
+       int carrier = -1;
+       int dtls = 0;
+       int role = -1;
+       int stream;
+       struct sockaddr_in6 insa = { .sin6_family = AF_INET6, 0 };
+       struct sockaddr_in6 fwsa = { .sin6_family = AF_INET6, 0 };
+       char *localid = NULL;
+       char *remotid = NULL;
+       char *cmdsoxpath = NULL;
+       int sox = -1;
+       //
+       // Parse the command line arguments
+       while (parsing) {
+               //TODO// getlongopt
+               int opt = getopt (argc, argv, "csutx:y:L:R:S:");
+               switch (opt) {
+               case 'c':
+               case 's':
+                       if (role != -1) {
+                               fprintf (stderr, "Specify -c or -s only once\n");
+                               exit (1);
+                       }
+                       role = opt;
+                       break;
+               case 'u':
+                       fprintf (stderr, "UDP mode wrapping with DTLS is not implemented -- see man page\n");
+                       exit (1);
+               case 't':
+               case 'x':
+               case 'y':
+                       if (carrier != -1) {
+                               fprintf (stderr, "Specify -t or -x or -y only once\n");
+                               exit (1);
+                       }
+                       carrier = opt;
+                       if ((carrier == 'x') || (carrier == 'y')) {
+                               long int parsed = strtol (optarg, &optarg, 0);
+                               if (*optarg) {
+                                       fprintf (stderr, "Syntax error in stream number to -%d\n", opt);
+                                       exit (1);
+                               }
+                               if ((parsed < 0) || (parsed > 65535)) {
+                                       fprintf (stderr, "Stream numbers range from 0 to 65535\n");
+                                       exit (1);
+                               }
+                               stream = parsed;
+                       }
+                       break;
+               case 'L':
+                       if (*tlsdata.localid) {
+                               fprintf (stderr, "For now, it is not permitted to specify multiple local identities\n");
+                               exit (1);
+                       }
+                       if (1 + strlen (optarg) > sizeof (tlsdata.localid)) {
+                               fprintf (stderr, "Local identity is too long\n");
+                               exit (1);
+                       }
+                       strcpy (tlsdata.localid, optarg);
+                       break;
+               case 'R':
+                       if (*tlsdata.remoteid) {
+                               fprintf (stderr, "Cannot constrain to multiple remote identities at the same time\n");
+                               exit (1);
+                       }
+                       if (1 + strlen (optarg) > sizeof (tlsdata.remoteid)) {
+                               fprintf (stderr, "Remote identity is too long\n");
+                               exit (1);
+                       }
+                       strcpy (tlsdata.remoteid, optarg);
+                       break;
+               case 'S':
+                       if (cmdsoxpath) {
+                               fprintf (stderr, "You can specify only one TLS Pool command socket path\n");
+                               exit (1);
+                       }
+                       cmdsoxpath = strdup (optarg);
+                       break;
+               case -1:
+                       parsing = 0;
+               }
+       }
+       if (role == -1) {
+               fprintf (stderr, "Specify either -c or -s\n");
+               exit (1);
+       }
+       if (!*tlsdata.localid) {
+               fprintf (stderr, "You need to specify -L\n");
+               exit (1);
+       }
+       if (argc != optind + 4) {
+               fprintf (stderr, "After options, specify: inaddr inport fwaddr fwport\n");
+               exit (1);
+       }
+       if (carrier == -1) {
+               carrier = 't';
+       } else if (carrier == 'x') {
+               dtls = 1;
+       } else if (carrier == 'y') {
+               carrier = 'x';  // With dtls==0
+       }
+       if (cmdsoxpath) {
+               if (tlspool_socket (cmdsoxpath) == -1) {
+                       perror ("Failed to open TLS Pool command socket");
+                       exit (1);
+               }
+       }
+       tlsdata.flags = (dtls? PIOF_STARTTLS_DTLS: 0);
+       //
+       // Parse addresses and ports in the remaining arguments
+       if (inet_pton (AF_INET6, argv [optind + 0], &insa.sin6_addr) == 0) {
+               fprintf (stderr, "Not an incoming IPv6 address: %s\n", argv [optind + 0]);
+               exit (1);
+       }
+       if (inet_pton (AF_INET6, argv [optind + 2], &fwsa.sin6_addr) == 0) {
+               fprintf (stderr, "Not a forwarding IPv6 address: %s\n", argv [optind + 2]);
+               exit (1);
+       }
+       insa.sin6_port = htons (str2port (argv [optind + 1]));
+       fwsa.sin6_port = htons (str2port (argv [optind + 3]));
+       //
+       // Listen to the incoming address
+       switch (carrier) {
+       case 't':
+               tlsdata.ipproto = IPPROTO_TCP;
+               sox = socket (AF_INET6, SOCK_STREAM, 0);
+               break;
+       case 'u':
+               tlsdata.ipproto = IPPROTO_UDP;
+               sox = socket (AF_INET6, SOCK_DGRAM, 0);
+               break;
+       case 'x':
+               tlsdata.ipproto = IPPROTO_SCTP;
+               tlsdata.streamid = stream;
+               sox = socket (AF_INET6, SOCK_SEQPACKET, 0);
+               break;
+       }
+       if (sox == -1) {
+               perror ("Failed to open socket");
+               exit (1);
+       }
+       if (bind (sox, (struct sockaddr *) &insa, sizeof (insa)) == -1) {
+               perror ("Failed to bind socket to inaddr:inport");
+               exit (1);
+       }
+       if (listen (sox, 5) == -1) {
+               perror ("Failed to listen to socket");
+               exit (1);
+       }
+       //
+       // Fork off a daemon process
+       switch (fork ()) {
+       case -1:
+               perror ("Failed to fork background daemon");
+               exit (1);
+       case 0:
+               if (setsid () == (pid_t) -1) {
+                       perror ("Failed to create background session");
+                       exit (1);
+               }
+               break;
+       default:
+               printf ("DEBUG: Forked daemon process to the background\n");
+               exit (0);
+       }
+       //
+       // Handle incoming connections
+       while (1) {
+               int cnx;
+               int fwd;
+               struct copydata *sub;
+               cnx = accept (sox, NULL, 0);
+               if (cnx == -1) {
+                       perror ("Failed to accept incoming connection");
+                       continue;
+               }
+               if (role == 's') {
+                       cnx = starttls_server (cnx, &tlsdata, NULL);
+                       if (cnx == -1) {
+                               perror ("Failed to setup TLS server");
+                               continue;
+                       }
+               }
+               switch (carrier) {
+               case 't':
+                       fwd = socket (AF_INET6, SOCK_STREAM, 0);
+                       break;
+               case 'u':
+                       fwd = socket (AF_INET6, SOCK_DGRAM, 0);
+                       break;
+               case 'x':
+                       fwd = socket (AF_INET6, SOCK_SEQPACKET, 0);
+                       break;
+               }
+               if (fwd == -1) {
+                       perror ("Failed to create forwarding socket");
+                       exit (1);
+               }
+               if (connect (fwd, (struct sockaddr *) &fwsa, sizeof (fwsa)) == -1) {
+                       perror ("Failed to forward connection");
+                       close (fwd);
+                       close (cnx);
+                       continue;
+               }
+               if (role == 'c') {
+                       fwd = starttls_client (fwd, &tlsdata);
+                       if (fwd == -1) {
+                               perror ("Failed to setup TLS client");
+                               close (cnx);
+                               continue;
+                       }
+               }
+               if (!(sub = (struct copydata *) malloc (sizeof (struct copydata)))) {
+                       perror ("Failed to allocate thread data");
+                       exit (1);
+               }
+               sub->cnx = cnx;
+               sub->fwd = fwd;
+               errno = pthread_create (&sub->thread, NULL, copycat, (void *) sub);
+               if (errno) {
+                       perror ("Failed to start copycat thread");
+                       close (cnx);
+                       close (fwd);
+                       free (sub);
+               }
+       }
+       exit (1);
+}
+