簡體   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