Как обнаружить символ завершения в клиенте HTTPS на основе SChannel - PullRequest
0 голосов
/ 14 апреля 2020

Я искал StackOverflow, пытаясь найти подобную проблему, но не сталкивался с ней, поэтому я отправляю этот вопрос.

Я пытаюсь написать C ++ HTTPS-клиент с использованием библиотек Microsoft SChannel, и я получаю стохасти c ошибок с частичной передачей сообщений. Эта проблема возникает только при очень длинных загрузках - короткие обычно работают нормально. Большую часть времени код работает правильно - даже для длительных загрузок - но иногда команда recv () изящно истекает, отключая мой сеанс TLS, и в других случаях я получаю неполный последний пакет. Ошибки Stochasti c, по-видимому, являются результатом фрагментов различного размера и блоков шифрования, которые сервер использует для передачи данных. Я знаю, что мне нужно справиться с этим вариантом, но хотя это будет легко решить на незашифрованном HTTP-соединении, проблема с шифрованием вызывает у меня проблемы.

Во-первых, проблема тайм-аута, возникающая примерно в 5% случаев, когда я запрашиваю большие HTTP-запросы (около 10 МБ данных из одного HTTP-запроса GET).

Истекло время ожидания, потому что в последнем блоке я указал больший буфер приема, чем данные, оставшиеся в блокирующем сокете. Очевидное решение этой проблемы - запросить только то количество байтов, которое мне нужно для следующего фрагмента, и именно это я и сделал. Но по какой-то причине сумма, полученная по каждому запросу, меньше, чем та, которую я запрашиваю, и, тем не менее, после расшифровки данных не хватает. Я предполагаю, что это должно быть из-за некоторого сжатия в потоке данных, но я не знаю. В любом случае, если он использует сжатие, я понятия не имею, как преобразовать размер расшифрованного несжатого потока байтов в размер сжатого зашифрованного потока байтов, включая заголовки шифрования и трейлеры, чтобы запросить точное правильное количество байтов. Может ли кто-нибудь помочь мне сделать это?

Альтернативный подход для меня состоит в том, чтобы просто искать два CR + LF подряд, что также будет сигнализировать об окончании ответа HTTPS. Но поскольку данные зашифрованы, я не могу понять, как выглядеть побайтно. Кажется, что DecryptMessage () в SChannel выполняет расшифровку в блоках, а не побайтно. Может ли кто-нибудь на этом форуме дать какой-либо совет о том, как сделать побитовое расшифрование, чтобы я мог найти конец чанкованного вывода?

Вторая проблема заключается в том, что DecryptMessage иногда ошибочно думает, что он завершил дешифрование, прежде чем я достигну фактического конца сообщения. Результирующее поведение I go при следующем HTTP-запросе, и я получаю остаток от предыдущего ответа, где я ожидаю увидеть заголовок нового запроса.

Очевидное решение для этого - проверить содержимое расшифрованного сообщения, чтобы увидеть, действительно ли мы достигли конца, и если нет, попытаться получить больше данных перед отправкой следующего HTTP-запроса. Но когда я делаю это и пытаюсь расшифровать, я получаю сообщение об ошибке расшифровки.

Буду признателен за любые советы / помощь, которые кто-либо может дать по стратегии. Я приложил соответствующие разделы кода для процесса чтения / дешифрования тела HTTP - я не включаю чтение и анализ заголовка, потому что это работает без каких-либо проблем.

    do
    {
        // Note this receives large files OK, but I can't tell when I hit the end of the buffer, and this
        // hangs.  Need to consider a non-blocking socket? 

//      numBytesReceived = recv(windowsSocket, (char*)inputBuffer, inputBufSize, 0);
        m_ErrorLog << "Next read size expected " << nextReadSize << endl;
        numBytesReceived = recv(windowsSocket, (char*)inputBuffer, nextReadSize, 0);
        m_ErrorLog << "NumBytesReceived = " << numBytesReceived << endl;
        if (m_BinaryBufLen + numBytesReceived > m_BinaryBufAllocatedSize)
            ::EnlargeBinaryBuffer(m_BinaryBuffer,m_BinaryBufAllocatedSize,m_BinaryBufLen,numBytesReceived+1);
        memcpy(m_BinaryBuffer+m_BinaryBufLen,inputBuffer,numBytesReceived);
        m_BinaryBufLen += numBytesReceived;

        lenStartDecryptedChunk = decryptedBodyLen;

        do
        {
            // Decrypt the received data. 

            Buffers[0].pvBuffer     = m_BinaryBuffer;
            Buffers[0].cbBuffer     = m_BinaryBufLen;
            Buffers[0].BufferType   = SECBUFFER_DATA;  // Initial Type of the buffer 1
            Buffers[1].BufferType   = SECBUFFER_EMPTY; // Initial Type of the buffer 2 
            Buffers[2].BufferType   = SECBUFFER_EMPTY; // Initial Type of the buffer 3 
            Buffers[3].BufferType   = SECBUFFER_EMPTY; // Initial Type of the buffer 4 

            Message.ulVersion       = SECBUFFER_VERSION;    // Version number
            Message.cBuffers        = 4;                                    // Number of buffers - must contain four SecBuffer structures.
            Message.pBuffers        = Buffers;                        // Pointer to array of buffers
            scRet = m_pSSPI->DecryptMessage(phContext, &Message, 0, NULL);
            if (scRet == SEC_E_INCOMPLETE_MESSAGE)
                break;
            if( scRet == SEC_I_CONTEXT_EXPIRED )
            {
                m_ErrorLog << "Server shut down connection before I finished reading" << endl;
                m_ErrorLog << "# of Bytes Requested = " << nextReadSize << endl;
                m_ErrorLog << "# of Bytes received = " << numBytesReceived << endl;
                m_ErrorLog << "Decrypted data to this point = " << endl;
                m_ErrorLog << decryptedBody << endl;
                m_ErrorLog << "BinaryData just decrypted: " << endl;
                m_ErrorLog << Buffers[0].pvBuffer << endl;
                break; // Server signalled end of session
            }
            if( scRet != SEC_E_OK && 
                scRet != SEC_I_RENEGOTIATE && 
                scRet != SEC_I_CONTEXT_EXPIRED ) 
            { 
                DisplaySECError((DWORD)scRet,errmsg);
                m_ErrorLog << "CSISPDoc::ReadDecrypt(): " << "Failed to decrypt message--Error=" << errmsg;
                if (decryptedBody)
                    m_ErrorLog << decryptedBody << endl;
                return scRet; 
            }
            // Locate data and (optional) extra buffers.

            pDataBuffer  = NULL;
            pExtraBuffer = NULL;
            for(i = 1; i < 4; i++)
            {
                if( pDataBuffer  == NULL && Buffers[i].BufferType == SECBUFFER_DATA  ) 
                    pDataBuffer  = &Buffers[i];
                if( pExtraBuffer == NULL && Buffers[i].BufferType == SECBUFFER_EXTRA ) 
                    pExtraBuffer = &Buffers[i];
            }

            // Display the decrypted data.

            if(pDataBuffer)
            {
                length = pDataBuffer->cbBuffer;
                if( length ) // check if last two chars are CR LF
                {
                    buff = (PBYTE)pDataBuffer->pvBuffer; // printf( "n-2= %d, n-1= %d \n", buff[length-2], buff[length-1] );
                    if (decryptedBodyLen+length+1 > decryptedBodyAllocatedSize)
                        ::EnlargeBuffer(decryptedBody,decryptedBodyAllocatedSize,decryptedBodyLen,length+1);
                    memcpy_s(decryptedBody+decryptedBodyLen,decryptedBodyAllocatedSize-decryptedBodyLen,buff,length);
                    decryptedBodyLen += length;
                    m_ErrorLog << buff << endl;
                }
            }

            // Move any "extra" data to the input buffer -- this has not yet been decrypted.

            if(pExtraBuffer)
            {
                MoveMemory(m_BinaryBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer);
                m_BinaryBufLen = pExtraBuffer->cbBuffer; // printf("inputStrLen= %d  \n", inputStrLen);
            }
        }
        while (pExtraBuffer); 


        if (decryptedBody)
        {
            if (incompletePacket)
                p1 = decryptedBody + lenStartFragmentedPacket;
            else
                p1 = decryptedBody + lenStartDecryptedChunk;
            p2 = p1;
            pEndDecryptedBody = decryptedBody+decryptedBodyLen;

            if (lastDecryptRes != SEC_E_INCOMPLETE_MESSAGE)
                chunkSizeBlock = true;

            do
            {
                while (p2 < pEndDecryptedBody && (*p2 != '\r' || *(p2+1) != '\n'))
                    p2++;

                // if we're here, we probably found the end of the current line.  The pattern we are
                // reading is chunk length, chunk, chunk length, chunk,...,chunk lenth (==0)

                if (*p2 == '\r' && *(p2+1) == '\n') // new line character -- found chunk size
                {
                    if (chunkSizeBlock) // reading the size of the chunk
                    {
                        pStartHexNum = SkipWhiteSpace(p1,p2);
                        pEndHexNum = SkipWhiteSpaceBackwards(p1,p2);
                        chunkSize = HexCharToInt(pStartHexNum,pEndHexNum);
                        p2 += 2; // skip past the newline character
                        chunkSizeBlock = false;
                        if (!chunkSize)  // chunk size of 0 means we're done
                        {
                            bulkReadDone = true;
                            p2 += 2;  // skip past the final CR+LF
                        }
                        nextReadSize = chunkSize+8; // chunk + CR/LF + next chunk size (4 hex digits) + CR/LF + encryption header/trailer
                    }
                    else // copy the actual chunk
                    {
                        if (p2-p1 != chunkSize)
                        {
                            m_ErrorLog << "Warning: Actual chunk size of " << p2 - p1 << " != stated chunk size = " << chunkSize << endl;
                        }
                        else
                        {
                        // copy over the actual chunk data // 
                            if (m_HTTPBodyLen + chunkSize > m_HTTPBodyAllocatedSize)
                                ::EnlargeBuffer(m_HTTPBody,m_HTTPBodyAllocatedSize,m_HTTPBodyLen,chunkSize+1);
                            memcpy_s(m_HTTPBody+m_HTTPBodyLen,m_HTTPBodyAllocatedSize,p1,chunkSize);
                            m_HTTPBodyLen += chunkSize;
                            m_HTTPBody[m_HTTPBodyLen] = 0;  // null-terminate
                            p2 += 2; // skip over chunk and end of line characters
                            chunkSizeBlock = true;
                            chunkSize = 0;
                            incompletePacket = false;
                            lenStartFragmentedPacket = 0;
                        }
                    }
                    p1 = p2; // move to start of next chunk field
                }
                else // got to end of encrypted body with no CR+LF found --> fragmeneted chunk.  So we need to read and decrypt at least one more chunk 
                {
                    incompletePacket = true;
                    lenStartFragmentedPacket = p1-decryptedBody;
                }
            }   
            while (p2 < pEndDecryptedBody);
            lastDecryptRes = scRet;
        }
    }
    while (scRet == SEC_E_INCOMPLETE_MESSAGE && !bulkReadDone);

1 Ответ

0 голосов
/ 14 апреля 2020

TLS не поддерживает побайтное дешифрование.

TLS 1.2 разбивает свои входные данные на блоки размером до 16 КБ, а затем шифрует их в блоки зашифрованного текста, которые немного больше из-за необходимости шифрования IV / одноразовых номеров и тегов защиты целостности / MAC. Невозможно расшифровать блок, пока не будет доступен весь блок. Вы можете найти полную информацию по https://tools.ietf.org/html/rfc5246#section -6.2 .

Поскольку вы уже можете расшифровать несколько первых блоков (содержащих заголовки), вы сможете прочитать Длина HTTP, чтобы вы по крайней мере знали ожидаемую длину открытого текста, которую затем можно сравнить с количеством байтов, которые вы расшифровали из потока. Это не скажет вам, сколько байтов зашифрованного текста вам нужно - вы можете получить верхнюю границу размера фрагмента, вызвав m_pSPPI->QueryContextAttributes(), и затем должны прочитать либо по крайней мере это число байтов, либо до конца потока прежде чем пытаться расшифровать.

Вы пробовали смотреть другие примеры? http://www.coastrd.com/c-schannel-smtp содержит подробный пример клиента TLS на основе SChannel.

...