C ++ OpenSSL Не выполняет рукопожатие при приеме в неблокирующем режиме. Как правильно?

Я пытаюсь внедрить OpenSSL в свое приложение, которое использует необработанные сокеты C, и единственная проблема, с которой я сталкиваюсь, — это часть кода SSL_accept / SSL_connect, которая запускает этап KeyExchange, но, похоже, не завершает его на стороне сервера.

Я посмотрел на бесчисленные веб-сайты и Q&А здесь, в StackOverflow, чтобы разобраться с API-интерфейсом OpenSSL, так как это в основном первый раз, когда я пытаюсь внедрить SSL в приложение, но единственное, что я пока не смог найти, — это как правильно управлять неудачными рукопожатиями.

По сути, работающий процесс A, который служит сервером, будет прослушивать входящие соединения. После запуска процесса B, который действует как клиент, он успешно подключится к процессу A, но SSL_accept (на сервере) завершится ошибкой с кодом ошибки -2 SSL_ERROR_WANT_READ.

В соответствии с OpenSLS рукопожатие не удалось, проблема «легко» решается путем вызова SSL_accept внутри цикла, пока он, наконец, не вернет 1 (он успешно подключается и завершает рукопожатие). Тем не менее, я не верю, что это правильный способ ведения дел, так как это выглядит как подвох. Причина, по которой я считаю это подвохом, заключается в том, что я попытался запустить небольшое приложение, которое нашел на https://www.cs.utah.edu/~swalton/listings/articles/ (ssl_client и ssl_server) и волшебным образом все работает просто отлично. Нет нескольких вызовов SSL_accept, и рукопожатие завершается сразу же.

Вот некоторый код, где я принимаю SSL-соединение на сервере:

if (SSL_accept(conn.ssl) == -1)
{
fprintf(stderr, "Connection failed.\n");
fprintf(stderr, "SSL State: %s [%d]\n", SSL_state_string_long(conn.ssl), SSL_state(conn.ssl));
ERR_print_errors_fp(stderr);
PrintSSLError(conn.ssl, -1, "SSL_accept");
return -1;
}
else
{
fprintf(stderr, "Connection accepted.\n");
fprintf(stderr, "Server -> Client handshake completed");
}

Это вывод PrintSSLError:

SSL State: SSLv3 read client hello B [8465]
[DEBUG] SSL_accept : Failed with return -1
[DEBUG]     SSL_get_error() returned : 2
[DEBUG]     Error string : error:00000002:lib(0):func(0):system lib
[DEBUG]     ERR_get_error() returned : 0
[DEBUG]     errno returned : Resource temporarily unavailable

А вот фрагмент клиентской части, который подключается к серверу:

if (SSL_connect(conn.ssl) == -1)
{
fprintf(stderr, "Connection failed.\n");
ERR_print_errors_fp(stderr);
PrintSSLError(conn.ssl, -1, "SSL_connect");
return -1;
}
else
{
fprintf(stderr, "Connection established.\n");
fprintf(stderr, "Client -> Server handshake completed");
PrintSSLInfo(conn.ssl);
}

Соединение успешно установлено на стороне клиента (SSL_connect не возвращает -1) и выводит PrintSSLInfo:

Connection established.
Cipher: DHE-RSA-AES256-GCM-SHA384
SSL State: SSL negotiation finished successfully [3]

И вот как я обернуть C Socket в SSL:

SSLConnection conn;
conn.fd = fd;
conn.ctx = sslContext;

conn.ssl = SSL_new(conn.ctx);
SSL_set_fd(conn.ssl, conn.fd);

Здесь фрагмент кода находится в функции, которая принимает файловый дескриптор принятого входящего соединения в необработанном сокете и контекст SSL для использования.

Для инициализации контекстов SSL я использую TLSv1_2_server_method () и TLSv1_2_client_method (). Да, я знаю, что это предотвратит подключение клиентов, если они не поддерживают TLS 1.2, но это именно то, что я хочу. Тот, кто подключится к моему приложению, все равно должен будет сделать это через мой клиент.

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

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

while ((accept = SSL_accept(conn.ssl)) != 1)

И внутри цикла while я проверяю код возврата, сохраненный внутри accept.

Вещи, которые я попытался обойти ошибку SSL_ERROR_WANT_READ:

  1. Добавлен usleep (50) внутри цикла while (по-прежнему требуется несколько циклов)
  2. Добавлены SSL_do_handshake (conn.ssl) после SSL_connect и SSL_accept (ничего не изменилось в конечном результате)
  3. Посмотрел код, показанный на roxlu.com (поищите в Google «Использование OpenSSL с БИО памяти — Roxlu»), чтобы провести меня через фазу рукопожатия, но так как я новичок в этом, и я не использую напрямую БИО в моем коде, но просто обернуть мои родные сокеты C в SSL, это было немного запутанно. Я также не могу переписать часть приложения, посвященную работе в сети, так как сейчас для меня было бы слишком много работы.

Я провел несколько тестов с командной строкой openssl, чтобы устранить проблему, но она не выдает ошибку. Рукопожатие кажется успешным, так как нет ошибок, таких как:

24069864:error:1409E0E5:SSL routines:ssl3_write_bytes:ssl handshake failure:s3_pkt.c:656

появляются. Вот весь вывод команды

openssl s_client -connect IP:Port -tls1_2 -prexit -msg

http://pastebin.com/9u1bfuf4

Что следует отметить:
1. Я использую последнюю версию OpenSSL 1.0.2h
2. Приложение работает в системе Unix
3. Использование самозаверяющих сертификатов для шифрования сетевого трафика

Спасибо всем, кто собирается помочь мне.

Редактировать:
Я забыл упомянуть, что сокеты находятся в неблокирующем режиме, поскольку приложение обслуживает несколько клиентов за один раз. Хотя на стороне клиента они находятся в режиме блокировки.

Edit2:
Оставьте это здесь для дальнейшего использования: jmarshall.com/stuff/handling-nbio-errors-in-openssl.html

1

Решение

Вы пояснили, что вопрос сокета неблокируемый.

Ну, это твой ответ. Очевидно, что когда сокет находится в неблокирующем режиме, квитирование не может быть немедленно завершено. Рукопожатие включает в себя обмен пакетами протокола между клиентом и сервером, причем каждому приходится ждать ответа от своего партнера. Это прекрасно работает, когда сокет находится в режиме блокировки по умолчанию. Библиотека просто read()с и write()s, который блокирует и ждет, пока сообщение не будет успешно прочитано или записано. Это, очевидно, не может произойти, когда сокет находится в неблокирующем режиме. Либо read() или же write() немедленно завершается успешно или завершается неудачей, если нечего читать или если буфер вывода сокета заполнен.

Страницы руководства для SSL_accept() а также SSL-connect() объясните процедуру, которую вы должны реализовать, чтобы выполнить рукопожатие SSL, когда базовый сокет находится в неблокирующем режиме. Вместо того, чтобы повторять все это здесь, вы должны прочитать страницы руководства самостоятельно. Краткое содержание капсулы для использования SSL_get_error() определить, действительно ли сбой рукопожатия или библиотека хочет читать или писать в / из сокета; и в этом случае вызов poll() или же select()соответственно звони SSL_accept() а также SSL_connect() снова.

Любой другой подход, например, глупое разбрызгивание sleep() звонки, здесь и там, приведут к ненадежному карточному домику, который будет случайным образом терпеть неудачу.

2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]