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

Я пытаюсь внедрить 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 () и 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
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 с памятью 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

http://pastebin.com/9u1bfuf4

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

Спасибо всем, кто поможет мне.

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

Edit2: оставьте это здесь для использования в будущем: jmarshall.com/stuff/handling-nbio-errors-in-openssl.html


person Giuseppe P.    schedule 13.08.2016    source источник
comment
Ваша розетка в неблокирующем режиме?   -  person Sam Varshavchik    schedule 14.08.2016
comment
да. Я забыл добавить это. Это все неблокирующее, поскольку оно должно обслуживать несколько клиентов.   -  person Giuseppe P.    schedule 14.08.2016


Ответы (1)


Вы уточнили, что вопрос о сокете неблокирующий.

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

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

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

person Sam Varshavchik    schedule 14.08.2016
comment
Спасибо за ваш ответ. Решение, которое сработало для меня, как и для другого пользователя из SO, заключалось в использовании SSL_get_error и проверке того, что OpenSSL хочет, чтобы мы делали. Мое беспокойство, как я полагаю, было в основном связано с тем, что я не использовал эти коды ошибок для правильной обработки цикла, поэтому я думал, что это может / могло закончиться бесконечным циклом. Только что я наткнулся на реализацию SSL библиотеки ACE, которая дала мне реальное представление о том, как с этим справиться. Если кому-то еще интересно: dre.vanderbilt. edu / ~ schmidt / DOC_ROOT / ACE / ace / SSL / - person Giuseppe P.; 14.08.2016