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

时间:2017-03-10 13:59:04

标签: c sockets tcp openssl polling

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

现在,如果从对等方快速连续发送多个数据包(假设2个长度为85字节的数据包),那么tcp receive将返回同一缓冲区中的数据包,并且接收的字节数为170.因此,我们有一个缓冲区,它携带2个ssl加密数据包(或n个数据包)。对于ssl解密,我们需要调用BIO_write()将缓冲区写入ssl_bio,然后调用ssl_read()来检索dycrypted缓冲区。但是,尽管BIO_write()正在将170个字节写入bio,但似乎ssl_read()只返回一个dycrypted数据包(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--;
}

3 个答案:

答案 0 :(得分:4)

OpenSSL通常只会一次读取和解密一条记录。再次调用SSL_read将为您提供下一条记录。如果您不知道是否有其他记录需要阅读,您可以询问基础传输,如果它当前是“可读的”#34; - 或者只是调用SSL_read()并处理错误(如果使用非阻塞IO)。

在某些情况下(例如,如果您使用&#34; read_ahead&#34;功能),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

答案 1 :(得分:2)

经过几次实验并阅读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()返回的缓冲区已准备好发送到网络中。
  4. 由于这个问题与ssl_read()有关,我们将从上面的第二点继续讨论。据我所知,ssl_read()实际上执行openssl的内部fsm,它一次从read_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)&gt; 0)   {   doSomething(pcDecodedBuff,iReadData);   }

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

答案 2 :(得分:2)

我也在使用内存BIO将SSL与非阻塞套接字一起使用(使用poll)。我最终得到的代码看起来与您的解决方案类似,但还有一个额外的步骤;在调用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;
}