Как на C++ работать с OpenSSL "вручную", оставляя за собой полный контроль сетевой части
Нужно добавить на сервер TLS шифрование. Поизучав документацию OpenSSL, нашёл функции вроде SSL_connect, SSL_do_handshake, и подобные удобные обёртки, которые берут на себя не только шифровку/дешифровку, но и сетевой функционал.
Я не знаю как эти функции устроены под капотом, и сетевую часть хочу контролировать самостоятельно, оставляя за библиотекой только криптографическую работу с буферами данных и файлами сертификатов.
Интересуют механизмы ответа на запрос handshake, валидации сертификата, применения согласованного алгоритма шифрования на вычитанных из сокета данных (или записываемых в сокет)
Подскажите пожалуйста какие объекты и функции библиотеки OpenSSL для этого подходят и как их использовать в данном контексте?
Ответы (1 шт):
Вот пример С++ приложения, которое само читает-пишет сокет, а SSL движок работает отдельно от сетевого ввода-вывода.
Там много технических деталей, поэтому здесь я код только местами прокомментирую.
- В 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);
- Зашифрованные данные из сокета перекладываются во входной поток и вызывается функция 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);
- Нет необходимости вручную вызывать 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);
// ...
}
- Шифрование данных для отправки осуществляет 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);
}