Как на C++ работать с OpenSSL "вручную", оставляя за собой полный контроль сетевой части

Нужно добавить на сервер TLS шифрование. Поизучав документацию OpenSSL, нашёл функции вроде SSL_connect, SSL_do_handshake, и подобные удобные обёртки, которые берут на себя не только шифровку/дешифровку, но и сетевой функционал.

Я не знаю как эти функции устроены под капотом, и сетевую часть хочу контролировать самостоятельно, оставляя за библиотекой только криптографическую работу с буферами данных и файлами сертификатов.

Интересуют механизмы ответа на запрос handshake, валидации сертификата, применения согласованного алгоритма шифрования на вычитанных из сокета данных (или записываемых в сокет)

Подскажите пожалуйста какие объекты и функции библиотеки OpenSSL для этого подходят и как их использовать в данном контексте?


Ответы (1 шт):

Автор решения: Pak Uula

Вот пример С++ приложения, которое само читает-пишет сокет, а SSL движок работает отдельно от сетевого ввода-вывода.

Там много технических деталей, поэтому здесь я код только местами прокомментирую.

  1. В OpenSSL движок взаимодействует с внешним миром через потоки ввода-вывода, BIO. Если вы хотите сами работать с сокетами, то нужно инициализировать SSL с потоками, размещёнными в памяти:
  // Поток для зашифрованных входных данных
  p->rbio = BIO_new(BIO_s_mem());
  // Поток для зашифрованных выходных данных
  p->wbio = BIO_new(BIO_s_mem());

  // Экземпляр контекста для этой пары потоков
  p->ssl = SSL_new(ctx);

  SSL_set_accept_state(p->ssl); /* sets ssl to work in server mode. */
  // Прикрепляются потоки к контексту
  SSL_set_bio(p->ssl, p->rbio, p->wbio);
  1. Зашифрованные данные из сокета перекладываются во входной поток и вызывается функция SSL_read, которая пытается расшифровать содержимое входного потока и записать в предоставленный буфер. Важно понимать, что в процессе расшифровки может потребоваться что-то отправить другой стороне. Поэтому нужно проверять статус и при необходимости пересылать содержимое выходного потока в сеть.
    // Зашифрованные байты из src копируются в mem_bio
    n = BIO_write(client.rbio, src, len);
    // ...
    do {
      // Зачли несколько байтов из входного потока, результат расшифровки сохранили в buf
      n = SSL_read(client.ssl, buf, sizeof(buf));
      // ...
    } while (n > 0);

    status = get_sslstatus(client.ssl, n);
    // Фишка в том, что SSL может потребоваться обменяться служебными пакетами с другой стороной
    // Поэтому нужно обязательно проверить статус
    /* 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);
        // ...
      } while (n>0);
  1. Нет необходимости вручную вызывать handshake. Есть универсальная функция SSL_accept Она читает пакеты для хэндшейка из входного потока и пишет результаты в выходной поток.
    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 {
          // Пакеты протокола TLS в выходном потоке, готовы для отправки в сеть.
          n = BIO_read(client.wbio, buf, sizeof(buf));
          if (n > 0)
            queue_encrypted_bytes(buf, n);
          // ...
        } while (n>0);
      // ...
    }
  1. Шифрование данных для отправки осуществляет SSL_write. Результат шифрования находится в выходном потоке.
    // Открытый текст находится в client.encrypt_buf, длина client.encrypt_len
    int n = SSL_write(client.ssl, client.encrypt_buf, client.encrypt_len);
    status = get_sslstatus(client.ssl, n);

    if (n>0) {
      // ...
      // Результат шифрования в выходном потоке.
      /* take the output of the SSL object and queue it for socket write */
      do {
        // Содержимое выходного потока копируется в буфер для отправки в сеть
        n = BIO_read(client.wbio, buf, sizeof(buf));
        if (n > 0)
          queue_encrypted_bytes(buf, n);
        // ...
      } while (n>0);
    }
→ Ссылка