| 
									
										
										
										
											2000-11-02 07:12:01 +08: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 :-) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2000-12-21 03:30:19 +08:00
										 |  |  |   * Support (and control over) session resuming, particularly when functioning | 
					
						
							|  |  |  |     as an SSL client. | 
					
						
							| 
									
										
										
										
											2000-11-02 07:12:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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 | 
					
						
							| 
									
										
										
										
											2000-12-21 03:30:19 +08:00
										 |  |  | 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). | 
					
						
							| 
									
										
										
										
											2000-11-02 07:12:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-07-24 03:03:48 +08:00
										 |  |  | Secondly, this code has mostly only been tested on Linux. However, some | 
					
						
							|  |  |  | autoconf/etc support has been added and the code has been compiled on openbsd | 
					
						
							|  |  |  | and solaris using that. | 
					
						
							| 
									
										
										
										
											2000-11-02 07:12:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-07-24 03:03:48 +08:00
										 |  |  | See the INSTALL document for details on building. | 
					
						
							| 
									
										
										
										
											2000-11-02 07:12:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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 \ | 
					
						
							| 
									
										
										
										
											2000-12-21 03:30:19 +08:00
										 |  |  |               -cert A-client.pem -out_totals -v_peer -v_strict | 
					
						
							| 
									
										
										
										
											2000-11-02 07:12:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | In another console, type; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | (ii) ./tunala -listen localhost:8081 -proxy localhost:23 -cacert CA.pem \ | 
					
						
							| 
									
										
										
										
											2000-12-21 03:30:19 +08:00
										 |  |  |               -cert A-server.pem -server 1 -out_totals -v_peer -v_strict | 
					
						
							| 
									
										
										
										
											2000-11-02 07:12:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | Now if you open another console and "telnet localhost 8080", you should be | 
					
						
							| 
									
										
										
										
											2001-07-24 03:03:48 +08:00
										 |  |  | tunneled through to the telnet service on your local machine (if it's running - | 
					
						
							|  |  |  | you could change it to port "22" and tunnel ssh instead if you so desired). When | 
					
						
							|  |  |  | you logout of the telnet session, the tunnel should cleanly shutdown and show | 
					
						
							|  |  |  | you some traffic stats in both consoles. Feel free to experiment. :-) | 
					
						
							| 
									
										
										
										
											2000-11-02 07:12:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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 :-) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 |