繁体   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()

以下代码部分的目的是轮询套接字 fd-set,如果数据(ssl 加密)可用,则读取它并通过 openssl 库解密。 底层传输层是 TCP Stream,因此数据以流的形式出现(而不是数据包)。

现在,如果从对等方快速连续发送了多个数据包(假设有 2 个长度为 85 字节的数据包),那么 TCP 接收将返回同一缓冲区中的两个数据包,接收的字节数为 170。因此,我们有一个携带 2 个 ssl 加密数据包(或 n 个数据包)的缓冲区。 对于 ssl 解密,我们需要调用 BIO_write() 将缓冲区写入 ssl_bio,然后调用 ssl_read() 检索解密的缓冲区。 但是,尽管 BIO_write() 正在向 bio 中写入 170 个字节,但 ssl_read() 似乎只返回一个解密的数据包(43 个字节)。 没有返回错误。 如何知道 bio 中是否还有未处理的字节。 有什么出路吗或者代码中有什么错误?

当在 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 通常一次只读取和解密一条记录。 再次调用 SSL_read 将为您提供下一条记录。 如果您不知道是否有另一条记录要读取,您可以询问底层传输它当前是否“可读”——或者只是调用 SSL_read() 并处理错误(如果使用非阻塞 IO)。

在某些情况下(例如,如果您使用“read_ahead”功能),OpenSSL 可能会在内部缓冲一些数据。 您可以使用 SSL_has_pending()(对于 OpenSSL 1.1.0)或 SSL_pending()(对于 OpenSSL 1.0.2+)来查明是否有缓冲的内部数据。 请参阅https://www.openssl.org/docs/man1.1.0/ssl/SSL_has_pending.html

经过几次实验和阅读 openssl 文档后,我已经能够解决这个问题。 据我了解,在相当高的速度下(每秒通过 ssl 连接处理超过 1000 个应用程序数据事务),在使用异步 openssl 实现的情况下肯定会出现此问题。

根据openssl的实现,得出答案-

  1. 有两个与 ssl 连接(或上下文)相关的 bios - 读取 bio 和 write bio
  2. read bio 是 BIO_write() 写入接收到的缓冲区的地方,随后的 ssl_read() 从中读取未处理的加密缓冲区并将其解密为纯文本。
  3. write bio 是 ssl_write() 在获取纯文本并对其进行加密后放置加密缓冲区的地方。 BIO_read() 从此 bio 中读取,并且缓冲区 BIO_read() 返回已准备好发送到网络中。

由于这个问题与 ssl_read() 相关,我们从上面的第二点继续我们的讨论。 据我了解,ssl_read() 实际上是执行 openssl 的内部 fsm,它一次从 read_bio 读取和处理一个数据包的缓冲区。 缓冲区的其余部分(如果有)作为未处理的缓冲区位于读取 bio 中。 因此, ssl_pending() 永远不会返回任何内容,因为没有处理的缓冲区可供读取。 确定是否有任何东西在 bio 中需要处理和读取的唯一方法是连续调用 ssl_read() 直到它返回接收到的 0 个字节。

修改后的代码应该是这样的——

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--;
}

检查如何连续使用 ssl_read() 以确保 read_BIO 中没有未处理的数据。

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

使用此代码,我面临的问题得到了解决。 希望它也能帮助其他人。

我还使用内存 BIO 将 SSL 与非阻塞套接字一起使用(使用轮询)。 我最终得到的代码看起来与您的解决方案相似,但多了一个步骤; 在调用SSL_get_error之后,您需要检查 SSL 是否已请求写入操作。 SSL_read可能导致 SSL 对象需要执行套接字写入; 如果对等方请求重新协商,就会发生这种情况。

下面是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