openssl/demos/tunala
Geoff Thorpe 282d8b1c38 This change was a quick experiment that I'd wanted to try that works quite
well (and is a good demonstration of how encapsulating the SSL in a
memory-based state machine can make it easier to apply to different
situations).

The change implements a new command-line switch "-flipped <0|1>" which, if
set to 1, reverses the usual interpretation of a client and server for SSL
tunneling. Normally, an ssl client (ie. "-server 0") accepts "cleartext"
connections and conducts SSL/TLS over a proxied connection acting as an SSL
client. Likewise, an ssl server (ie. "-server 1") accepts connections and
conducts SSL/TLS (as an SSL server) over them and passes "cleartext" over
the proxied connection. With "-flipped 1", an SSL client (specified with
"-server 0") in fact accepts SSL connections and proxies clear, whereas an
SSL server ("-server 1") accepts clear and proxies SSL. NB: most of this
diff is command-line handling, the actual meat of the change is simply the
line or two that plugs "clean" and "dirty" file descriptors into the item
that holds the state-machine - reverse them and you get the desired
behaviour.

This allows a network server to be an SSL client, and a network client to
be an SSL server. Apart from curiosity value, there's a couple of possibly
interesting applications - SSL/TLS is inherently vulnerable to trivial DoS
attacks, because the SSL server usually has to perform a private key
operation first, even if the client is authenticated. With this scenario,
the network client is the SSL server and performs the first private key
operation, whereas the network server serves as the SSL client. Another
possible application is when client-only authentication is required (ie.
the underlying protocol handles (or doesn't care about) authenticating the
server). Eg. an SSL/TLS version of 'ssh' could be concocted where the
client's signed certificate is used to validate login to a server system -
whether or not the client needs to validate who the server is can be
configured at the client end rather than at the server end (ie. a complete
inversion of what happens in normal SSL/TLS).

NB: This is just an experiment/play-thing, using "-flipped 1" probably
creates something that is interoperable with exactly nothing. :-)
2001-02-12 02:28:29 +00:00
..
.cvsignore A typo and a couple of logic errors fixed. I think there may still be one 2000-11-28 19:09:58 +00:00
A-client.pem This is a demo that performs SSL tunneling (client and/or server) and is 2000-11-01 23:11:19 +00:00
A-server.pem This is a demo that performs SSL tunneling (client and/or server) and is 2000-11-01 23:11:19 +00:00
buffer.c Some minor changes to the "tunala" demo. 2000-12-20 19:30:19 +00:00
CA.pem This is a demo that performs SSL tunneling (client and/or server) and is 2000-11-01 23:11:19 +00:00
cb.c * Fix a slight bug in the state-machine. This caused the client end of a 2000-11-30 01:34:26 +00:00
ip.c This is a demo that performs SSL tunneling (client and/or server) and is 2000-11-01 23:11:19 +00:00
Makefile Minor tweaks and improvements to the tunala demo. 2000-11-28 23:27:23 +00:00
README Some minor changes to the "tunala" demo. 2000-12-20 19:30:19 +00:00
sm.c Minor tweaks and improvements to the tunala demo. 2000-11-28 23:27:23 +00:00
tunala.c This change was a quick experiment that I'd wanted to try that works quite 2001-02-12 02:28:29 +00:00
tunala.h Some minor changes to the "tunala" demo. 2000-12-20 19:30:19 +00:00

This is intended to be an example of a state-machine driven SSL application. It
acts as an SSL tunneler (functioning as either the server or client half,
depending on command-line arguments). *PLEASE* read the comments in tunala.h
before you treat this stuff as anything more than a curiosity - YOU HAVE BEEN
WARNED!! There, that's the draconian bit out of the way ...


Why "tunala"??
--------------

I thought I asked you to read tunala.h?? :-)


Show me
-------

If you want to simply see it running, skip to the end and see some example
command-line arguments to demonstrate with.


Where to look and what to do?
-----------------------------

The code is split up roughly coinciding with the detaching of an "abstract" SSL
state machine (which is the purpose of all this) and its surrounding application
specifics. This is primarily to make it possible for me to know when I could cut
corners and when I needed to be rigorous (or at least maintain the pretense as
such :-).

Network stuff:

Basically, the network part of all this is what is supposed to be abstracted out
of the way. The intention is to illustrate one way to stick OpenSSL's mechanisms
inside a little memory-driven sandbox and operate it like a pure state-machine.
So, the network code is inside both ip.c (general utility functions and gory
IPv4 details) and tunala.c itself, which takes care of application specifics
like the main select() loop. The connectivity between the specifics of this
application (TCP/IP tunneling and the associated network code) and the
underlying abstract SSL state machine stuff is through the use of the "buffer_t"
type, declared in tunala.h and implemented in buffer.c.

State machine:

Which leaves us, generally speaking, with the abstract "state machine" code left
over and this is sitting inside sm.c, with declarations inside tunala.h. As can
be seen by the definition of the state_machine_t structure and the associated
functions to manipulate it, there are the 3 OpenSSL "handles" plus 4 buffer_t
structures dealing with IO on both the encrypted and unencrypted sides ("dirty"
and "clean" respectively). The "SSL" handle is what facilitates the reading and
writing of the unencrypted (tunneled) data. The two "BIO" handles act as the
read and write channels for encrypted tunnel traffic - in other applications
these are often socket BIOs so that the OpenSSL framework operates with the
network layer directly. In this example, those two BIOs are memory BIOs
(BIO_s_mem()) so that the sending and receiving of the tunnel traffic stays
within the state-machine, and we can handle where this gets send to (or read
from) ourselves.


Why?
----

If you take a look at the "state_machine_t" section of tunala.h and the code in
sm.c, you will notice that nothing related to the concept of 'transport' is
involved. The binding to TCP/IP networking occurs in tunala.c, specifically
within the "tunala_item_t" structure that associates a state_machine_t object
with 4 file-descriptors. The way to best see where the bridge between the
outside world (TCP/IP reads, writes, select()s, file-descriptors, etc) and the
state machine is, is to examine the "tunala_item_io()" function in tunala.c.
This is currently around lines 641-732 but of course could be subject to change.


And...?
-------

Well, although that function is around 90 lines of code, it could easily have
been a lot less only I was trying to address an easily missed "gotcha" (item (2)
below). The main() code that drives the select/accept/IO loop initialises new
tunala_item_t structures when connections arrive, and works out which
file-descriptors go where depending on whether we're an SSL client or server
(client --> accepted connection is clean and proxied is dirty, server -->
accepted connection is dirty and proxied is clean). What that tunala_item_io()
function is attempting to do is 2 things;

  (1) Perform all reads and writes on the network directly into the
      state_machine_t's buffers (based on a previous select() result), and only
      then allow the abstact state_machine_t to "churn()" using those buffers.
      This will cause the SSL machine to consume as much input data from the two
      "IN" buffers as possible, and generate as much output data into the two
      "OUT" buffers as possible. Back up in the main() function, the next main
      loop loop will examine these output buffers and select() for writability
      on the corresponding sockets if the buffers are non-empty.

  (2) Handle the complicated tunneling-specific issue of cascading "close"s.
      This is the reason for most of the complexity in the logic - if one side
      of the tunnel is closed, you can't simply close the other side and throw
      away the whole thing - (a) there may still be outgoing data on the other
      side of the tunnel that hasn't been sent yet, (b) the close (or things
      happening during the close) may cause more data to be generated that needs
      sending on the other side. Of course, this logic is complicated yet futher
      by the fact that it's different depending on which side closes first :-)
      state_machine_close_clean() will indicate to the state machine that the
      unencrypted side of the tunnel has closed, so any existing outgoing data
      needs to be flushed, and the SSL stream needs to be closed down using the
      appropriate shutdown sequence. state_machine_close_dirty() is simpler
      because it indicates that the SSL stream has been disconnected, so all
      that remains before closing the other side is to flush out anything that
      remains and wait for it to all be sent.

Anyway, with those things in mind, the code should be a little easier to follow
in terms of "what is *this* bit supposed to achieve??!!".


How might this help?
--------------------

Well, the reason I wrote this is that there seemed to be rather a flood of
questions of late on the openssl-dev and openssl-users lists about getting this
whole IO logic thing sorted out, particularly by those who were trying to either
use non-blocking IO, or wanted SSL in an environment where "something else" was
handling the network already and they needed to operate in memory only. This
code is loosely based on some other stuff I've been working on, although that
stuff is far more complete, far more dependant on a whole slew of other
network/framework code I don't want to incorporate here, and far harder to look
at for 5 minutes and follow where everything is going. I will be trying over
time to suck in a few things from that into this demo in the hopes it might be
more useful, and maybe to even make this demo usable as a utility of its own.
Possible things include:

  * controlling multiple processes/threads - this can be used to combat
    latencies and get passed file-descriptor limits on some systems, and it uses
    a "controller" process/thread that maintains IPC links with the
    processes/threads doing the real work.

  * cert verification rules - having some say over which certs get in or out :-)

  * control over SSL protocols and cipher suites

  * A few other things you can already do in s_client and s_server :-)

  * Support (and control over) session resuming, particularly when functioning
    as an SSL client.

If you have a particular environment where this model might work to let you "do
SSL" without having OpenSSL be aware of the transport, then you should find you
could use the state_machine_t structure (or your own variant thereof) and hook
it up to your transport stuff in much the way tunala.c matches it up with those
4 file-descriptors. The state_machine_churn(), state_machine_close_clean(), and
state_machine_close_dirty() functions are the main things to understand - after
that's done, you just have to ensure you're feeding and bleeding the 4
state_machine buffers in a logical fashion. This state_machine loop handles not
only handshakes and normal streaming, but also renegotiates - there's no special
handling required beyond keeping an eye on those 4 buffers and keeping them in
sync with your outer "loop" logic. Ie. if one of the OUT buffers is not empty,
you need to find an opportunity to try and forward its data on. If one of the IN
buffers is not full, you should keep an eye out for data arriving that should be
placed there.

This approach could hopefully also allow you to run the SSL protocol in very
different environments. As an example, you could support encrypted event-driven
IPC where threads/processes pass messages to each other inside an SSL layer;
each IPC-message's payload would be in fact the "dirty" content, and the "clean"
payload coming out of the tunnel at each end would be the real intended message.
Likewise, this could *easily* be made to work across unix domain sockets, or
even entirely different network/comms protocols.

This is also a quick and easy way to do VPN if you (and the remote network's
gateway) support virtual network devices that are encapsulted in a single
network connection, perhaps PPP going through an SSL tunnel?


Suggestions
-----------

Please let me know if you find this useful, or if there's anything wrong or
simply too confusing about it. Patches are also welcome, but please attach a
description of what it changes and why, and "diff -urN" format is preferred.
Mail to geoff@openssl.org should do the trick.


Example
-------

Here is an example of how to use "tunala" ...

First, it's assumed that OpenSSL has already built, and that you are building
inside the ./demos/tunala/ directory. If not - please correct the paths and
flags inside the Makefile. Likewise, if you want to tweak the building, it's
best to try and do so in the makefile (eg. removing the debug flags and adding
optimisation flags).

Secondly, this code so far has only ever been built and run on Linux - network
specifics are more than likely to create little glitches on other unixen,
particularly Solaris in my experience. If you're not on Linux, please read the
code wherever compilation flares up and try to make the necessary changes -
usually the man-page associated with the relevant function is enough (eg. all
that AF_INET/PF_INET stuff, subtlely different parameters to various
IPv4-related functions like socket(), bind(), fcntl(), etc).

Thirdly, if you are Win32, you probably need to do some *major* rewriting of
ip.c to stand a hope in hell. Good luck, and please mail me the diff if you do
this, otherwise I will take a look at another time. It can certainly be done,
but it's very non-POSIXy.

Type make.

Now, if you don't have an executable "tunala" compiled, go back to "First,...".
Rinse and repeat.

Inside one console, try typing;

(i)  ./tunala -listen localhost:8080 -proxy localhost:8081 -cacert CA.pem \
              -cert A-client.pem -out_totals -v_peer -v_strict

In another console, type;

(ii) ./tunala -listen localhost:8081 -proxy localhost:23 -cacert CA.pem \
              -cert A-server.pem -server 1 -out_totals -v_peer -v_strict

Now if you open another console and "telnet localhost 8080", you should be
tunneled through to the telnet service on your local machine. Feel free to
experiment :-)

Notes:

 - the format for the "-listen" argument can skip the host part (eg. "-listen
   8080" is fine). If you do, the listening socket will listen on all interfaces
   so you can connect from other machines for example. Using the "localhost"
   form listens only on 127.0.0.1 so you can only connect locally (unless, of
   course, you've set up weird stuff with your networking in which case probably
   none of the above applies).

 - ./tunala -? gives you a list of other command-line options, but tunala.c is
   also a good place to look :-)