简体   繁体   English

带有 TCP 流的 TLS/SSL 中的 ssl_read() 不返回 BIO_write() 写入的整个缓冲区

[英]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.以下代码部分的目的是轮询套接字 fd-set,如果数据(ssl 加密)可用,则读取它并通过 openssl 库解密。 The underlying transport layer is TCP Stream, so data comes as stream (not packet).底层传输层是 TCP Stream,因此数据以流的形式出现(而不是数据包)。

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).现在,如果从对等方快速连续发送了多个数据包(假设有 2 个长度为 85 字节的数据包),那么 TCP 接收将返回同一缓冲区中的两个数据包,接收的字节数为 170。因此,我们有一个携带 2 个 ssl 加密数据包(或 n 个数据包)的缓冲区。 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.对于 ssl 解密,我们需要调用 BIO_write() 将缓冲区写入 ssl_bio,然后调用 ssl_read() 检索解密的缓冲区。 But though BIO_write() is writing 170 bytes into the bio, it seems like ssl_read() is only returning one decrypted packet (43 bytes).但是,尽管 BIO_write() 正在向 bio 中写入 170 个字节,但 ssl_read() 似乎只返回一个解密的数据包(43 个字节)。 There is no error returned.没有返回错误。 How to know if there is still unprocessed bytes in the bio.如何知道 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().当在 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--;
}

OpenSSL will, normally, just read and decrypt one record at a time. OpenSSL 通常一次只读取和解密一条记录。 Calling SSL_read again will give you the next record.再次调用 SSL_read 将为您提供下一条记录。 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).如果您不知道是否有另一条记录要读取,您可以询问底层传输它当前是否“可读”——或者只是调用 SSL_read() 并处理错误(如果使用非阻塞 IO)。

In some circumstances (such as if you are using the "read_ahead" capability), OpenSSL may buffer some data internally.在某些情况下(例如,如果您使用“read_ahead”功能),OpenSSL 可能会在内部缓冲一些数据。 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+).您可以使用 SSL_has_pending()(对于 OpenSSL 1.1.0)或 SSL_pending()(对于 OpenSSL 1.0.2+)来查明是否有缓冲的内部数据。 See https://www.openssl.org/docs/man1.1.0/ssl/SSL_has_pending.html .请参阅https://www.openssl.org/docs/man1.1.0/ssl/SSL_has_pending.html

Well after a few experiments and reading openssl documents, I've been able to resolve the issue.经过几次实验和阅读 openssl 文档后,我已经能够解决这个问题。 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.据我了解,在相当高的速度下(每秒通过 ssl 连接处理超过 1000 个应用程序数据事务),在使用异步 openssl 实现的情况下肯定会出现此问题。

Coming to the answer, according to openssl implementation -根据openssl的实现,得出答案-

  1. There are two bios associated with a ssl connection(or context) - read bio and write bio有两个与 ssl 连接(或上下文)相关的 bios - 读取 bio 和 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. read bio 是 BIO_write() 写入接收到的缓冲区的地方,随后的 ssl_read() 从中读取未处理的加密缓冲区并将其解密为纯文本。
  3. write bio is where ssl_write(), upon taking a plain text and encrypting it, puts the encrypted buffer. write bio 是 ssl_write() 在获取纯文本并对其进行加密后放置加密缓冲区的地方。 BIO_read() reads from this bio and the buffer BIO_read() returns is ready for sending into the network. BIO_read() 从此 bio 中读取,并且缓冲区 BIO_read() 返回已准备好发送到网络中。

As this question is related to ssl_read(), we are continuing our discussion from 2nd point above.由于这个问题与 ssl_read() 相关,我们从上面的第二点继续我们的讨论。 To my understanding, ssl_read() actually executes openssl's internal fsm that reads and processes buffer from read_bio one packet at a time.据我了解,ssl_read() 实际上是执行 openssl 的内部 fsm,它一次从 read_bio 读取和处理一个数据包的缓冲区。 The rest of the buffer(if any), rests in the read bio as unprocessed buffer.缓冲区的其余部分(如果有)作为未处理的缓冲区位于读取 bio 中。 So, ssl_pending() will never return anything, as there is no processed buffer left to read.因此, ssl_pending() 永远不会返回任何内容,因为没有处理的缓冲区可供读取。 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.确定是否有任何东西在 bio 中需要处理和读取的唯一方法是连续调用 ssl_read() 直到它返回接收到的 0 个字节。

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.检查如何连续使用 ssl_read() 以确保 read_BIO 中没有未处理的数据。

while(iReadData = SSL_read(m_psSSL, pcDecodedBuff, 1024) > 0) { doSomething(pcDecodedBuff, iReadData); 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.希望它也能帮助其他人。

I'm also using memory BIO's to use SSL with non-blocking sockets (using poll).我还使用内存 BIO 将 SSL 与非阻塞套接字一起使用(使用轮询)。 The code I ended up with looks similar to your solutions, but with one extra step;我最终得到的代码看起来与您的解决方案相似,但多了一个步骤; after the call to SSL_get_error you need to check if the SSL has requested a write operation.在调用SSL_get_error之后,您需要检查 SSL 是否已请求写入操作。 An SSL_read can result in a the SSL object needing to perform a socket write; SSL_read可能导致 SSL 对象需要执行套接字写入; this can happen if the peer requested renegotiaton.如果对等方请求重新协商,就会发生这种情况。

Below is a snippet from the full code at ssl_server_nonblock.c ... its the part perform the BIO_write and SSL_read下面是ssl_server_nonblock.c 上完整代码的片段......它的部分执行BIO_writeSSL_read

/* Process SSL bytes received from the peer. The data needs to be fed into the
   SSL object to be unencrypted.  On success returns 0, on SSL error -1. */
int on_read_cb(char* src, size_t len) {
char buf[DEFAULT_BUF_SIZE]; /* used for copying bytes out of SSL/BIO */
enum sslstatus status;
int n;

while (len > 0) {
  n = BIO_write(client.rbio, src, len);

  if (n<=0)
    return -1; /* if BIO write fails, assume unrecoverable */

  src += n;
  len -= n;

  if (!SSL_is_init_finished(client.ssl)) {
    n = SSL_accept(client.ssl);
    status = get_sslstatus(client.ssl, n);

    /* Did SSL request to write bytes? */
    if (status == SSLSTATUS_WANT_IO)
      do {
        n = BIO_read(client.wbio, buf, sizeof(buf));
        if (n > 0)
          queue_encrypted_bytes(buf, n);
        else if (!BIO_should_retry(client.wbio))
          return -1;
      } while (n>0);

    if (status == SSLSTATUS_FAIL)
      return -1;

    if (!SSL_is_init_finished(client.ssl))
      return 0;
  }

  /* The encrypted data is now in the input bio so now we can perform actual
   * read of unencrypted data. */

  do {
    n = SSL_read(client.ssl, buf, sizeof(buf));
    if (n > 0)
      client.do_something(buf, (size_t)n);
  } while (n > 0);

  status = get_sslstatus(client.ssl, n);

  /* Did SSL request to write bytes? This can happen if peer has requested SSL
   * renegotiation. */
  if (status == SSLSTATUS_WANT_IO)
    do {
      n = BIO_read(client.wbio, buf, sizeof(buf));
      if (n > 0)
        queue_encrypted_bytes(buf, n);
      else if (!BIO_should_retry(client.wbio))
        return -1;
    } while (n>0);

  if (status == SSLSTATUS_FAIL)
    return -1;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM