Land
Land

Reputation: 189

BIO don't flush data in OpenSSL

I am using BIO to compute the digest of some file with OpenSSL. My code is as following:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/bio.h>

int main(){

    BIO* bio_infile;
    bio_infile = BIO_new_file("test.txt", "rb");
    
    BIO* bio_md = BIO_new(BIO_f_md());
    BIO_set_md(bio_md, EVP_sha1());

    BIO* bio_outfile;
    bio_outfile = BIO_new_file("dgst.txt","w");
    
    BIO_push(bio_md, bio_outfile);
    BIO_push(bio_infile, bio_md);
    
    BIO_flush(bio_infile);
    BIO_flush(bio_md);
    
    BIO_free(bio_infile); 
    BIO_free(bio_md);
    BIO_free(bio_outfile);
    
    return 0;
}

However, When my program run out, I find nothings in file dgst.txt.

Why?

Upvotes: 2

Views: 609

Answers (1)

The BIO chaining mechanism is meant for prepending filter BIOs to a source/sink BIO. It is not designed for connecting a source BIO with a sink BIO.

So what you can do:

  • Create a BIO chain of filters with a final BIO sink. When writing data to the head of the chain, the input will traverse each filter until it reaches the sink.

  • Create a BIO chain of filters with a final BIO source. When reading data from the head of the chain, the read request will be passed along the chain up to the source. The data read from the source will then traverse the chain in the reverse direction through each filter.

But what you can not do:

  • Creating a BIO chain that starts with a source and ends with a sink with filters in between. There is no mechanism for "pumping" data from the source to the sink automatically.

One additional note: The BIO_f_md is a filter BIO that calculates the digest of the data passing through it, but it does not modify the data. So the output of the BIO is the original data, not the digest of the data. The digest can be retrieved after all data has been processed with BIO_get_md (reference). So in your example, even if there was support for chaining a source to a sink, the final dgst.txt would only contain the original data from test.txt.

To fix your example, there are two approaches:

Approach 1

Create a chain with the digest BIO followed by the source BIO for the input file. We can then read from the digest BIO, which internally pulls the data from the source BIO. We read the data and immediately discard it until the complete file has been processed. We can then retrieve the digest from the digest BIO.

    // Create chain: Digest -> Source File
    BIO* bio_infile;
    bio_infile = BIO_new_file("test.txt", "rb");

    BIO* bio_md = BIO_new(BIO_f_md());
    BIO_set_md(bio_md, EVP_sha1());

    BIO* head = BIO_push(bio_md, bio_infile);

    // Read from head of chain
    std::array<char,1024> buf{};
    int read;

    while ((read = BIO_read(head, buf.data(), buf.size())) > 0) {
      // Ignore buffer, we only want the data to pass throug the digest BIO
    }

    // Read result from digest BIO
    EVP_MD *md;
    BIO_get_md(bio_md, &md);

    std::array<char, EVP_MAX_MD_SIZE> md_buf{};
    int mdlen = BIO_gets(bio_md, md_buf.data(), md_buf.size());

    // Print digest to stdout
    for (int i = 0; i < mdlen; i++) {
      printf("%02x", static_cast<uint8_t>(md_buf[i]));
    }
    printf("\n");

    // Cleanup
    BIO_free_all(head); 

Approach 2

Create a chain with the digest BIO followed by a special sink BIO for discarding data (BIO_s_null). We then read the data from source BIO for the input file (without any chaining) and write the data to the digest BIO. The written data passes through the digest BIO to the sink BIO, which will discard the data. When all data has been processed, we can retrieve the digest from the digest BIO.

    // Create chain: Digest -> Null Sink
    BIO* bio_infile;
    bio_infile = BIO_new_file("test.txt", "rb");

    BIO* bio_md = BIO_new(BIO_f_md());
    BIO_set_md(bio_md, EVP_sha1());

    BIO* bio_null = BIO_new(BIO_s_null());

    BIO* head = BIO_push(bio_md, bio_null);

    // Read from file and write to chain
    std::array<char,1024> buf{};
    int read;

    while ((read = BIO_read(bio_infile, buf.data(), buf.size())) > 0) {
      BIO_write(head, buf.data(), read);
    }

    // Read result from digest BIO
    EVP_MD *md;
    BIO_get_md(bio_md, &md);

    std::array<char, EVP_MAX_MD_SIZE> md_buf{};
    int mdlen = BIO_gets(bio_md, md_buf.data(), md_buf.size());

    // Print digest to stdout
    for (int i = 0; i < mdlen; i++) {
      printf("%02x", static_cast<uint8_t>(md_buf[i]));
    }
    printf("\n");

    // Cleanup
    BIO_free(bio_infile);
    BIO_free_all(head); 

Upvotes: 3

Related Questions