Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ssl_read() in TLS/SSL with TCP stream not returning the whole buffer written by BIO_write()

The purpose of the following portion of code is to poll on a socket fd-set and if data (ssl encrypted) is available, read it and decrypt it by openssl library. The underlying transport layer is TCP Stream, so data comes as stream (not packet).

Now, if more than one packets (lets assume 2 packets of length 85 bytes) are sent in quick succession from the peer, then TCP receive will return both the packets in same buffer with Number of Bytes received as 170. So, we have one buffer that carries 2 ssl encrypted packets (or n number of packets). For ssl decryption, we need to call BIO_write() to write the buffer into ssl_bio and then ssl_read() to retrieve the decrypted buffer. But though BIO_write() is writing 170 bytes into the bio, it seems like ssl_read() is only returning one decrypted packet (43 bytes). There is no error returned. How to know if there is still unprocessed bytes in the bio. Is there any way out or is there any bug in the code?

The code is working fine when single packets are received in tcp recv().

int iReadyFds = poll( PollFdSet, iFdCount, iTimeout);

for(iFdIndx = 0; iFdIndx < (iFdCount) && (iReadyFds>0); ++iFdIndx)
{
    if((PollFdSet[iFdIndx].events == 0) ||
       (PollFdSet[iFdIndx].fd == 0) ||
       (PollFdSet[iFdIndx].revents != POLLIN)
       )
    {
        continue;
    }

    /* we have data to read */
    int iMsgLen = 0;
    int iFd = PollFdSet[iFdIndx].fd;


    /*This is TCP Receive. Returns 170 bytes*/
    iRcvdBytes = recv( iSocketId, ( void* )pcInBuffer, PN_TCP_STREAM_MAX_RX_BUFF_SIZE, 0 );
    
    /*Writing into SSL BIO, this will be retrieved by ssl_read*/
    /*iNoOFBytes  = 170*/
    iNoOFBytes = BIO_write(m_pRead_bio, pcInBuffer, iRcvdBytes);

    if(iNoOFBytes <= 0)
    {
        printf("Error");
        return -1;
    }
    
    char* pcDecodedBuff = (char*)malloc(1024);
    
    /*here it returns 43 bytes of decrypted buffer(1 packet). the other packet vanishes*/
    iReadData = SSL_read(m_psSSL, pcDecodedBuff, 1024);

    if ((iReadData == -1) || (iReadData == 0))
    {
        error = SSL_get_error(psPskTls->m_psSSL, iReadData);

        if(error == SSL_ERROR_ZERO_RETURN
        || error == SSL_ERROR_NONE
        || error == SSL_ERROR_WANT_READ)
        {
            printf("Error");
        }
    }

    iReadyFds--;
}
like image 526
AmiyaG Avatar asked Dec 19 '22 09:12

AmiyaG


2 Answers

OpenSSL will, normally, just read and decrypt one record at a time. Calling SSL_read again will give you the next record. If you don't know if there is another record to read you can ask the underlying transport if it is currently "readable" - or just call SSL_read() anyway and handle the error (if using non-blocking IO).

In some circumstances (such as if you are using the "read_ahead" capability), OpenSSL may buffer some data internally. You can find out if there is buffered internal data using SSL_has_pending() (for OpenSSL 1.1.0) or SSL_pending() (for OpenSSL 1.0.2+). See https://www.openssl.org/docs/man1.1.0/ssl/SSL_has_pending.html.

like image 123
Matt Caswell Avatar answered May 14 '23 13:05

Matt Caswell


Well after a few experiments and reading openssl documents, I've been able to resolve the issue. To my understanding, at reasonably high speed(more than 1000 application data transactions over ssl connection per second) this issue will certainly occur in cases where asynchronous openssl implementations are used.

Coming to the answer, according to openssl implementation -

  1. There are two bios associated with a ssl connection(or context) - read bio and write bio
  2. read bio is where BIO_write() writes received buffer and from which the successive ssl_read() reads unprocessed encrypted buffer and decrypts it into plain text.
  3. write bio is where ssl_write(), upon taking a plain text and encrypting it, puts the encrypted buffer. BIO_read() reads from this bio and the buffer BIO_read() returns is ready for sending into the network.

As this question is related to ssl_read(), we are continuing our discussion from 2nd point above. To my understanding, ssl_read() actually executes openssl's internal fsm that reads and processes buffer from read_bio one packet at a time. The rest of the buffer(if any), rests in the read bio as unprocessed buffer. So, ssl_pending() will never return anything, as there is no processed buffer left to read. The only way to find out if anything is left to process and read in the bio is making successive ssl_read() calls until it returns 0 bytes received.

The modified code should look something like this -

int iReadyFds = poll( PollFdSet, iFdCount, iTimeout);

for(iFdIndx = 0; iFdIndx < (iFdCount) && (iReadyFds>0); ++iFdIndx)
{
    if((PollFdSet[iFdIndx].events == 0) ||
       (PollFdSet[iFdIndx].fd == 0) ||
       (PollFdSet[iFdIndx].revents != POLLIN)
       )
    {
        continue;
    }

    /* we have data to read */
    int iMsgLen = 0;
    int iFd = PollFdSet[iFdIndx].fd;


    /*This is TCP Receive. Returns 170 bytes*/
    iRcvdBytes = recv( iSocketId, ( void* )pcInBuffer, PN_TCP_STREAM_MAX_RX_BUFF_SIZE, 0 );

    /*Writing into SSL BIO, this will be retrieved by ssl_read*/
    /*iNoOFBytes  = 170*/
    iNoOFBytes = BIO_write(m_pRead_bio, pcInBuffer, iRcvdBytes);

    if(iNoOFBytes <= 0)
    {
        printf("Error");
        return -1;
    }

    char* pcDecodedBuff = (char*)malloc(1024);

    /*here it returns 43 bytes of decrypted buffer(1 packet). 
    So we keep on reading until all the packets are processed and read*/
    while(iReadData = SSL_read(m_psSSL, pcDecodedBuff, 1024) > 0)
    {
        doSomething(pcDecodedBuff, iReadData);**
    }

    if ((iReadData == -1) || (iReadData == 0))
    {
        error = SSL_get_error(psPskTls->m_psSSL, iReadData);

        if(error == SSL_ERROR_ZERO_RETURN
        || error == SSL_ERROR_NONE
        || error == SSL_ERROR_WANT_READ)
        {
            printf("Error");
        }
    }

    iReadyFds--;
}

Check how successive ssl_read() has been used to make sure there is no unprocessed data left in the read_BIO.

while(iReadData = SSL_read(m_psSSL, pcDecodedBuff, 1024) > 0) { doSomething(pcDecodedBuff, iReadData); }

Using this code, the problem I was facing got solved. Hope it will help others too.

like image 36
AmiyaG Avatar answered May 14 '23 13:05

AmiyaG