Я пытаюсь внедрить OpenSSL в свое приложение, которое использует необработанные сокеты C, и единственная проблема, с которой я сталкиваюсь, - это часть кода SSL_accept / SSL_connect, которая запускает фазу KeyExchange, но, похоже, не завершает ее на стороне сервера.
Я просмотрел бесчисленное количество веб-сайтов и вопросов и ответов здесь, в StackOverflow, чтобы пройти через OpenSSL API, так как это, по сути, первый раз, когда я пытаюсь внедрить SSL в приложение, но единственное, что я пока не мог найти, - это то, как правильно управлять неудавшимися рукопожатиями.
По сути, запущенный процесс A, который служит сервером, будет прослушивать входящие соединения. Как только я запустил процесс B, который действует как клиент, он успешно подключится к процессу A, но SSL_accept (на сервере) не работает с кодом ошибки -2 SSL_ERROR_WANT_READ.
Согласно рукопожатие openssl не удалось, проблему «легко» обойти, вызвав 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 в 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
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
server_method () и TLSv1SSL 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
client_method (). Да, я знаю, что это предотвратит подключение клиентов, если они не поддерживают TLS 1.2, но это именно то, что я хочу. Тот, кто подключается к моему приложению, в любом случае должен будет сделать это через моего клиента.
В любом случае, что я делаю не так? Я бы хотел избежать циклов на этапе аутентификации, чтобы избежать возможных зависаний / замедлений работы приложения из-за неожиданных бесконечных циклов, поскольку OpenSSL не указывает, сколько попыток может потребоваться.
Обходной путь, который сработал, но которого я бы хотел избежать, заключается в следующем:
while ((accept = SSL_accept(conn.ssl)) != 1)
И внутри цикла while я проверяю код возврата, хранящийся внутри accept.
Что я пытался обойти ошибку SSL_ERROR_WANT_READ:
- Добавлен usleep (50) внутри цикла while (для завершения по-прежнему требуется несколько циклов)
- Добавлен SSL_do_handshake (conn.ssl) после SSL_connect и SSL_accept (ничего не изменил в конечном результате)
- Взглянул на код, показанный на roxlu.com (поиск в Google по запросу «Использование OpenSSL с памятью BIO - Roxlu»), чтобы провести меня через фазу подтверждения связи, но поскольку я новичок в этом, и я не использую BIO напрямую в моем коде, но просто оберните мои собственные сокеты 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
На заметку: 1. Я использую последнюю версию OpenSSL 1.0.2h. 2. Приложение работает в системе Unix. 3. Использование самозаверяющих сертификатов для шифрования сетевого трафика.
Спасибо всем, кто поможет мне.
Изменить: я забыл упомянуть, что сокеты находятся в неблокирующем режиме, поскольку приложение обслуживает несколько клиентов за один раз. Хотя на стороне клиента они находятся в режиме блокировки.
Edit2: оставьте это здесь для использования в будущем: jmarshall.com/stuff/handling-nbio-errors-in-openssl.html