views:

306

answers:

2

I'm using openssl BIO objects to convert a binary string into a base64 string. The code is as follows:

void ToBase64(std::string & s_in) {
    BIO * b_s = BIO_new( BIO_s_mem() );
    BIO * b64_f = BIO_new( BIO_f_base64() );

    b_s = BIO_push( b64_f , b_s);

    std::cout << "IN::" << s_in.length();
    BIO_write(b_s, s_in.c_str(), s_in.length());


    char * pp;
    int sz = BIO_get_mem_data(b_s, &pp);

    std::cout << "OUT::"  << sz << endl;

    s_in.assign(pp,sz);
    //std::cout << sz << " " << std::string(pp,sz) << std::endl;

    BIO_free (b64_f); // TODO ret error potential
    BIO_free (b_s);   // 
  }

The in length is either 64 or 72. However the output is always 65, which is incorrect it should be much larger than that. The documentation isn't the best in the world, AFAIK the bio_s_mem object is supposed to grow dynamically. What am I doing wrong ?

I am probably better off finding a self contained C++ class that doesn't offer streaming support, and supports base64 conversions. Streaming support is not suited to my application. However I just wanted to stick to openSSL since I am allready depending on some of the crypto routines. Anyhow I'll make such a decision after profiling.

+1  A: 

You have two problems:

  • You need to call BIO_get_mem_data() on the mem bio - but you've lost the reference to it (you overwrite it with the return value from BIO_push, which is equal to b64_f).
  • You should call BIO_flush() on the base64 bio after you've written all your data to it.
caf
Thank you, both points make perfect sense (I suspect the flush will do the trick). In response to the overwritting point: I copied the push operation from the example shown here: http://www.openssl.org/docs/crypto/BIO_f_base64.html# The target bio object to which the push is applied to, is overwritten.
Hassan Syed
Yes, the flush did indeed do the trick, any thoughts on the potential handle leak ? I think point 1 is moot.
Hassan Syed
Well in that example, it doesn't matter that they lose the reference to the fp bio, because they never need to access it directly - its output goes to the file. On the other hand you need to pull the output explicitly from the mem bio. I suspect it might only be working "by accident", because `BIO_get_mem_data()` is working on the base64 bio (maybe it's built on a mem bio underneath?) - and it may stop working if you push a lot more data through it.
caf
You were right about the leak. Someone needs to document the internals of ssl "correctly" (thats not a jab, as writing documentation is boring work). Valgrind caught it :D
Hassan Syed
You could get around the leak by using `BIO_free_all()` on the base64 bio (that bio is the start of the chain, and `BIO_free_all()` will walk the chain of bios freeing each one). But you still need a reference to the mem bio, because that's the one you should be reading from with `BIO_get_mem_data()`.
caf
A: 

I think what you want to do is change the order of the args to BIO_push.

b_s = BIO_push( b_s, b64_f )

Fred
Thank you for the response. The push is putting a filter earlier in the pipe, the examples on the openSSL documentation show this: http://www.openssl.org/docs/crypto/BIO_f_base64.html#. Therefore the order in my function is correct.
Hassan Syed
Interesting. According to the BIO_push page http://www.openssl.org/docs/crypto/BIO_push.html the BIO_push call returns the first argument with the second one appended. That would have the base64 example overwriting (and losing) the fp. One of the pages must be wrong.
Fred