PascalLqX Ответов: 1

Сообщения Schannel больше, чем cbmaximummessage


Привет,

У меня есть проблема с отправкой сообщения SChannel TLS, превышающего согласованную максимальную длину. Я определил класс, обрабатывающий контекст SChannel (класс my_context), и класс, обрабатывающий сокет, который использует класс my_context для шифрования передаваемых данных.

Когда my_socket.write () вызывается с буфером, большим, чем SecPkgContext_StreamSizes.cbMaximumMessage, часть, большая, чем SecPkgContext_StreamSizes.cbMaximumMessage, не понимается сервером (ни Wireshark).

class my_socket
{
public:

void write(::boost::asio::const_buffers_1 const& buf)
{
  //this signature implies const_buffers_1 i.e. only one buffer at a time
  auto b = *buf.begin();
  auto util = IoBuffer.size() - phContext->GetBufferReservedSize();
  char const* begin = ::boost::asio::buffer_cast<char const*>(b);
  char const* curr = begin;
  char const* end = begin + ::boost::asio::buffer_size(b);

  while (curr != end) {
    auto const buffSz = std::min(util, (size_t)(end - curr));
    auto const encryptedMessage = phContext->EncryptMessage( 
               ::boost::asio::buffer(curr, buffSz), IoBuffer);
    auto const data = ::boost::asio::buffer_cast<char const*>(encryptedMessage);
    auto const sz = ::boost::asio::buffer_size(encryptedMessage);

    auto cbData = send(Socket, data, sz, 0);
    if (cbData != sz)
       throw std::invalid_argument("Can't send data to the server (error code:" +
                    std::to_string(WSAGetLastError()) + ")");
    curr += buffSz;
  }
}

private:
    void _AllocateIoBuffer() {
      phContext->ResizeBuffer(IoBuffer);
    }
    SOCKET              Socket;
    my_context          *phContext;
    std::vector<char>   IoBuffer;
};

class my_context
{
[...]
public:
    long RoundUp(long n, long s) {
        return ((n + s - 1) / s)*s;
    }

    int GetBufferReservedSize() {
        auto const& sz = GetSizes_();
        return RoundUp(sz.cbHeader, sz.cbBlockSize)  + RoundUp(sz.cbTrailer, sz.cbBlockSize) ;
    }

    void ResizeBuffer(std::vector<char>& buf) {
        auto const& sz = GetSizes_();
        buf.reserve(sz.cbHeader + sz.cbMaximumMessage + sz.cbTrailer);
        buf.resize(sz.cbMaximumMessage);
    }

    boost::asio::const_buffer EncryptMessage( 
                boost::asio::const_buffer const& message, 
                std::vector<char>& IoBuffer) const 
    {
        SecBufferDesc   Message;
        SecBuffer       Buffers[4];

        auto sz = boost::asio::buffer_size(message);
        char const* data = boost::asio::buffer_cast<char const*>(message);

        // Encrypt the request.
        auto const headerSize = GetSizes_().cbHeader;
        Buffers[0].pvBuffer = IoBuffer.data();
        Buffers[0].cbBuffer = headerSize;
        Buffers[0].BufferType = SECBUFFER_STREAM_HEADER;

        CopyMemory(IoBuffer.data() + headerSize, data, sz);
        Buffers[1].pvBuffer = IoBuffer.data() + headerSize;
        Buffers[1].cbBuffer = sz;
        Buffers[1].BufferType = SECBUFFER_DATA;

        Buffers[2].pvBuffer = IoBuffer.data() + headerSize + sz;
        Buffers[2].cbBuffer = GetSizes_().cbTrailer;
        Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;

        Buffers[3].BufferType = SECBUFFER_EMPTY; 

        Message.ulVersion = SECBUFFER_VERSION;
        Message.cBuffers = 4; 
        Message.pBuffers = Buffers;
        SECURITY_STATUS const scRet = ::EncryptMessage( 
              const_cast<PCtxtHandle>(&hContext), 0, &Message, 0);
        if (FAILED(scRet)) {
            throw std::invalid_argument("Unable to ecrypt message (error code:" +
                    std::to_string(scRet) + ")");
        }

        return boost::asio::buffer(IoBuffer, Buffers[0].cbBuffer +
                     Buffers[1].cbBuffer + Buffers[2].cbBuffer);
    }

    SecPkgContext_StreamSizes GetSizes_() const {
        if (Sizes_.cbHeader == Sizes_.cbMaximumMessage == Sizes_.cbTrailer == 0) {
           SECURITY_STATUS const scRet = ::QueryContextAttributes(
             const_cast<CtxtHandle*>(&hContext),
             SECPKG_ATTR_STREAM_SIZES, &Sizes_);
        if (scRet != SEC_E_OK) {
           throw std::invalid_argument("Can't get the buffer sizes (error code:" +
               std::to_string(scRet) + ")");
            }
        }
        return Sizes_;
    }

    CredHandle      hClientCreds;
    CtxtHandle      hContext;
    mutable SecPkgContext_StreamSizes Sizes_;

    const DWORD     dwProtocol = SP_PROT_TLS1_2;
};


Что я уже пробовал:

Я попытался зашифровать сообщение одним звонком, но это не удалось.

1 Ответов

Рейтинг:
1

KarstenK

Во-первых: вы можете написать только тот максимальный размер, который поддерживается. Обычно вы пишете сообщение кусок за куском, пока не будут записаны все данные.

Возможно, вы должны разделить незашифрованные данные на небольшие пакеты, затем зашифровать и затем отправить, пока все данные не будут переданы.

Взгляните на это пример кода для пакетной отправки данных.


PascalLqX

Спасибо Вам за идею ограничить размер буфера для шифрования. Я успешно использовал 90% cbMaximumMessage. Я продолжаю расследование, чтобы получить максимальный размер, а затем понять, что не так для SChannel описание функции EncryptMessage (https://msdn.microsoft.com/en-us/library/windows/desktop/aa375378%28v=vs.85%29.aspx)

PascalLqX

Я исправил ошибку, уменьшающую размер буфера для шифрования на размер заголовка+трейлера, округленного до cbBlockSize. Код был обновлен с помощью этого патча.
Большое спасибо Карстенку за эту идею.

dnyan28

Привет PascalLqx,

Не могли бы вы предоставить модифицированный код Schannel, так как мы тоже пытаемся сделать то же самое.

dnyan28

Я попробовал использовать вышеприведенное решение и отправил данные кусками, но не получил должного ответа от сервера. Нужно ли было обрабатывать это и на сервере?

PascalLqX

@dryan28 мы ничего не изменили на стороне сервера, так как используем общедоступную почтовую службу (gmail и т. д)

dnyan28

Спасибо за ответ. вы использовали механизм chunk (например, chunk length\r\n chunk data...) или отправляли ответ непосредственно кусками.

PascalLqX

Мы отправляем ответ непосредственно в чанках