Я пытаюсь внедрить 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:
Я провел несколько тестов с командной строкой 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
Что следует отметить:
1. Я использую последнюю версию OpenSSL 1.0.2h
2. Приложение работает в системе Unix
3. Использование самозаверяющих сертификатов для шифрования сетевого трафика
Спасибо всем, кто собирается помочь мне.
Редактировать:
Я забыл упомянуть, что сокеты находятся в неблокирующем режиме, поскольку приложение обслуживает несколько клиентов за один раз. Хотя на стороне клиента они находятся в режиме блокировки.
Edit2:
Оставьте это здесь для дальнейшего использования: jmarshall.com/stuff/handling-nbio-errors-in-openssl.html
Вы пояснили, что вопрос сокета неблокируемый.
Ну, это твой ответ. Очевидно, что когда сокет находится в неблокирующем режиме, квитирование не может быть немедленно завершено. Рукопожатие включает в себя обмен пакетами протокола между клиентом и сервером, причем каждому приходится ждать ответа от своего партнера. Это прекрасно работает, когда сокет находится в режиме блокировки по умолчанию. Библиотека просто read()
с и write()s
, который блокирует и ждет, пока сообщение не будет успешно прочитано или записано. Это, очевидно, не может произойти, когда сокет находится в неблокирующем режиме. Либо read()
или же write()
немедленно завершается успешно или завершается неудачей, если нечего читать или если буфер вывода сокета заполнен.
Страницы руководства для SSL_accept()
а также SSL-connect()
объясните процедуру, которую вы должны реализовать, чтобы выполнить рукопожатие SSL, когда базовый сокет находится в неблокирующем режиме. Вместо того, чтобы повторять все это здесь, вы должны прочитать страницы руководства самостоятельно. Краткое содержание капсулы для использования SSL_get_error()
определить, действительно ли сбой рукопожатия или библиотека хочет читать или писать в / из сокета; и в этом случае вызов poll()
или же select()
соответственно звони SSL_accept()
а также SSL_connect()
снова.
Любой другой подход, например, глупое разбрызгивание sleep()
звонки, здесь и там, приведут к ненадежному карточному домику, который будет случайным образом терпеть неудачу.
Других решений пока нет …