views:

80

answers:

2

Hi,

I'm writing a small ftp project for my own amusement which is suppose to be able to do 4 things:

  1. connect directly to a ftp
  2. connect directly to a ftp with SSL (wrapper)
  3. connect through ssh tunnel to a ftp
  4. connect through ssh tunnel to a ftp with SSL.

I'm writing my program in plain C (unix, not that it matters in this case) using standard libraries for 1 and 2, using libssh2 for 3 and 4 in addition to OpenSSL for 2 and 4.

I'm able to get 1-3 working, but not 4. Here's where I'm at:

  1. done by opening a socket to host:port, connecting, writing/reading from the socket.
  2. done by opening a socket to host:port, connecting, writing "AUTH SSL", initiate a SSL object with BIO from previous socket -> SSL_connect(), SSL_read(), SSL_write().
  3. done by opening a tunnel between target and localhost (although i'm not sure what i'm using the localhost bind for in my approac:)

something like:

test_ssh_channel = libssh2_channel_direct_tcpip_ex(test_ssh_session, "100.100.100.100", 21, "127.0.0.1", 21);

I then write/read to that channel (libssh2_channel_read()) - which, as I see it, gives the following flow: plain text -> send over ssh -> deliver plain text to target from ssh host. For the purpose of 3. that is fine and does the job.

Now, for 4. I'm stuck, as I (trying to keep it simple) need to somehow turn this channel into a socket. So the way I see it I have two choices:

  • a. Make a pseudo socket and every time i need to read/write, I read/write from the channel, send/retrieve it into the socket, and let SSL_connect(pseudo_socket) communicate with my pseudo socket, every time taking in what the ssl_write sends, send it to the channel, and vice versa.

or

  • b. set up a BIO buffer (there are many more BIO functions than I can wrap my head around; the documentation hasn't been utterly helpful) and somehow read/write to that.

Ideally I'd go for 2, for this reason: Since my project is written in C, maintaining a socket read/write while executing other code will become a bit more complicated than I'd prefer.

However, b. gives a problem: I'm worried how the handshaking will work; in particular I'm afraid that I end up handshaking with localhost instead of the remotehost so to speak.

To summarize my question: Can I, through the channel, read/write in SSL (put a wrapper around it), so that the flow for 4. becomes: plain text -> SSL(plaintext) -> through ssh -> deliver SSL(plaintext) to target host from ssh host?

It's a bit long, but I hope it was understandable. If not, please let me know, and I'll clearify. From googling/searching stackoverflow it seems it's me and a guy working with mysql who has this same problem, and limited answers.

ANY input is greatly appreciated!

  • James
A: 

Yes, option 2 is the right way to go. Create a BIO pair with BIO_make_bio_pair(), and assign them to the SSL object with SSL_set_bio().

You then read the encrypted-side SSL data with BIO_read() and write it to the libssh tunnel, and read from the libssh tunnel and write it with BIO_write().


Addendum:

When you use this method, your SSL object doesn't have a file descriptor of its own - the BIOs replace the file descriptor / socket. Instead of reading and writing from a file descriptor, OpenSSL will read and write from the BIOs you provided.

The BIOs are simply an interface that sit between the OpenSSL library and your own code. They're a way for you to implement the actual shipping of the encrypted-side SSL data to and from the other side (instead of OpenSSL directly using a socket).

When you do a BIO_read() on the BIO you provided as wbio to SSL_set_bio(), you will read the encrypted-side SSL data, and you must then send this on to the other side yourself (presumably using some libssh2 function). Similarly, when you recieve encrypted-side SSL data from the other side (again, from some libssh2 function), you pump it into SSL by using BIO_write() on the BIO you provided as the rbio.

Perhaps this illustration will help. When you read and write from the SSL object, OpenSSL will just read and write from the underlying BIO, leaving the data there for you to deal with later:

+------+               +-----+               +-----+
| Your | SSL_write()   | SSL | BIO_read()    | BIO |
| code | ------------> |     | <------------ |     |
|      |               |     |               |     |
|      |               |     | BIO_write()   |     |
|      |               |     | ------------> |     |
+------+               +-----+               +-----+

+------+              +-----+               +-----+
| Your | SSL_read()   | SSL | BIO_read()    | BIO |
| code | <----------- |     | <------------ |     |
|      |              |     |               |     |
|      |              |     | BIO_write()   |     |
|      |              |     | ------------> |     |
+------+              +-----+               +-----+

(Note, though, that an SSL_write() may cause a read from the underlying BIO, and vice-versa).

When there is data in the wbio, you must read it and ship it to the other side:

+------+              +-----+
| Your | BIO_read()   | BIO |
| code | <----------- |     |
|      |              +-----+
|      |                           +---------+
|      | libssh2_channel_write()   | libssh2 |
|      | ------------------------> |         | -> (... to other side)
|      |                           +---------+
+------+

Conversely, when there is data available from the other side, you should read it and pass it into the rbio:

+------+
| Your |                          +---------+
| code | libssh2_channel_read()   | libssh2 |
|      | <----------------------- |         | -> (... from other side)
|      |                          +---------+
|      |              +-----+
|      | BIO_write()  | BIO |
|      | -----------> |     |
|      |              +-----+
+------+
caf
A: 

Thanks, caf! That's actually starting to make sense to me. I do however have one follow up question: What are the two bio's suppose to point to? I follow the read with bio, write to tunnel channel, but how do i connect the bio's with the "ssh" socket? or that's not what I'm doing? one bio as BIO_new_socket to the SSH host? Then what about the other?

Sorry if I'm stupid, but I've spend five days reading the OpenSSL docs, toying with everything from sockets to get/set_fd, all of which, so far, has caused me nothing but segmentation faults, hehe.

(Yes I know this will give me a -1 for asking a follow up question, but please answer anyway if you don't mind. Rephrasing the question to the above would be more confusing than helpful for other viewers).

Best regards,

James

James
I've updated my answer.
caf
Thanks a lot, caf! You've been really helpful. Have a great summer :)
James
Could I persuade you to have a *quick* look at http://pastebin.com/ZF2NMmCT ? (the bottom). Have I misunderstood what you were saying? I've marked tunnel_read as illustration 2 and write_to_bio as illustration 3, but "of course" the handshake fails as there's no data in the BIOs for handshaking - or is there no link between my BIOs and the channel? Sorry to bother you again.
James
@James: I've made some changes here: http://pastebin.com/bZnESbhc . Note how the two BIOs get created and used. Also note that you can't use your `tunnel_read()` and `tunnel_write()` functions after you switch to SSL, because SSL data isn't text - it's binary. Furthermore, it's *expected* that the handshake will "fail" with `SSL_ERROR_WANTS_READ` or `SSL_ERROR_WANTS_WRITE` - those are "temporary failures", indicating that you need to ship some data from/to the network, and then retry the SSL operation. That's what my `do{}while()` loop around your `SSL_read()` does.
caf
(By the way, this is totally untested, written-in-the-browser code, so it may need tweaking).
caf
caf: Thanks for the clearification. I can see I misunderstood the role of network_bio somewhat. There is however a problem with it never recieving any additional data for it to handshake on. To illustrate, I've been successful before, by setting up a "ssh -f ssh_host -L 21:remote_host:21 -N" openssh tunnel, and SSL_connect() and having a (one) BIO as BIO_new_socket() to that localhost, which produces an OK handshake when "SSL_connect"'ing after AUTH SSL has been send (and the output "AUTH SSL successsful." has been recieved in plaintext). It never recieves WANT_WRITE/READ, only case ERROR_SSL.
James
You should try using `SSL_connect()` instead of starting with `SSL_read()` - since the transparent negotiation doesn't work unless you've already told the `SSL` object to be a client or server. At this point, it might be worth posting a new question, containing your current example code.
caf
Thanks again, caf. You've been a tremendous help. I've taken your advice and posted an updated question specifically to the handshaking in this scenario.
James