Ошибка чтения сертификата клиента OpenSSL при использовании libev с неблокирующими сокетами

Я потратил некоторое время на поиск в сети, чтобы найти лучший способ проанализировать и отладить мою проблему, но я не могу найти решение. Я решил спросить.

Кратко. Я пытаюсь создать неблокирующий прокси-сервер пересылки ssl. Серверная часть прокси-сервера использует самозаверяющий сертификат сервера, который я подписал, используя свой собственный сертификат CA. Если это имеет значение, я использую libev. Сначала я успешно создал незашифрованный прокси (он слепо перенаправлял веб-трафик), и теперь я пытаюсь добавить к нему SSL.:)

У меня проблемы с подключением клиента к прокси. Я пробовал как wget, так и ssl s_client в качестве тестовых клиентов, так как я хотел провести автоматическое тестирование.

Настройка сервера ssl (этот код вызывается из прослушивающего сокета libev watcher accept_handler() для события EV_READ):

/* setup client side ssl state (we are a SERVER) */
ctx = SSL_CTX_new(SSLv23_server_mode());
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLV2 | SSL_OP_ALL);
SSL_CTX_set_info_callback(ctx, client_info_cb);
SSL_CTX_set_cipher_list(ctx, "ALL:!SSLv2:-aNULL");
//SSL_CTX_load_verify_locations(ctx, CA_CERTIFICATE, NULL);
//SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(CA_CERTIFICATE));
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
SSL_CTX_set_verify_depth(ctx, 0);
SSL *client_ssl = SSN_new(ctx);
SSL_set_mode(client_ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_set_accept_state(client_ssl);
SSL_set_fd(client_ssl, client_fd);

/* initialize client handshake watchers */
ev_io_init(&ev_r_ch, client_handshake, client_fd, EV_READ);
ev_io_init(&ev_w_ch, client_handshake, client_fd, EV_WRITE);
... other watcher inits and set watcher data portions ...

/* start the read */
ev_io_start(loop, &ev_r_ch);

Цикл libev был настроен так:

loop = ev_default_loop(EVFLAG_AUTO);

У меня есть таймеры и тому подобное, чтобы проверить флаг отключения, а также другие действия по дому.

Моя основная функция client_handshake() выглядит следующим образом:

int t = SSL_accept(client_ssl);
if (t == 1) { // SSL_ERROR_NONE
    end_client_handshake(...);
} else {
    int err = SSL_get_error(client_ssl, t);
    if (err == SSL_ERROR_WANT_READ) {
        ev_io_stop(loop, &ev_w_ch);
        ev_io_start(loop, &ev_r_ch);
    }
    else if (err == SSL_ERROR_WANT_WRITE) {
        ev_io_stop(loop, &ev_r_ch);
        ev_io_start(loop, &ev_w_ch);
    }
    else ...
}

В client_info_cb() я распечатываю внутреннее состояние SSL по мере продвижения и получаю следующее из моей функции print():

client_info_cb: 8193: SSLv3 read client hello A
client_info_cb: 8193: SSLv3 write server hello A
client_info_cb: 8193: SSLv3 write certificate A
client_info_cb: 8193: SSLv3 write server done A
client_info_cb: 8193: SSLv3 flush data
client_info_cb: 8194: SSLv3 read client certificate A
client_info_cb: 8194: SSLv3 read client certificate A

И вот где он висит. Я попытался изменить функцию client_handshake(), чтобы она зациклилась (1) {} на SSL_accept(), если обнаружила SSL_ERROR_WANT_READ (именно это и возвращает SSL_get_error() после второго сообщения "прочитать сертификат клиента A" выше).

Это ничего не сделало, но поставило меня в бесконечный цикл (), постоянно вызывающий SSL_accept().

Я предполагаю, что конечному автомату SSL нужна дополнительная информация, которую он не может получить. Сначала я подумал, что мне нужно продолжить чтение из сокета, но это не сработало.

Кроме того, меня смущает вопрос, почему мой прокси-сервер пытается прочитать сертификат клиента, поскольку я явно указал, что не хочу проверять сертификат клиента (SSL_VERIFY_NONE) выше; если только я неправильно понимаю цель этой функции.

Если у кого-то есть понимание этого, я был бы благодарен. Или, возможно, лучший метод устранения этой проблемы. strace() бесполезен для этого, и я не получаю никаких хороших сообщений возврата / ошибки ни из wget, ни из s_client.

Я попытался настроить alert_callbacks и msg_callbacks в конечном автомате SSL, но это не дало мне больше информации, чем информационный обратный вызов.

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

edit1: Я хотел бы отметить, что в accept_handler(), я сначала подключаюсь к серверу через ssl, чтобы проверить сертификат хоста, который я проксирую, до завершения accept (). Если я переверну порядок операций и приму () сначала перед подключением (), это работает.

edit2: я попытался посмотреть на вывод tcpdump между s_client и прокси. После записи данных сервера и сбрасывания данных, указанных в client_info_cb, клиент отправляет "Обмен ключами клиента", "Изменение спецификации шифра" и "Зашифрованное завершенное сообщение". Однако конечный автомат ssl ищет сертификат клиента???

--> Client Key Exchange
write to 0x9547a78 [0x9592e90] (523 bytes => 523 (0x20B))
0000 - 16 03 01 02 06 10 00 02-02 02 00 be 51 c7 3d 77   ............Q.=w
0010 - 5a b3 9e 28 81 f4 4e b5-63 ce ce 0b 19 f3 85 64   Z..(..N.c......d
0020 - 29 0e e8 22 83 b8 60 a6-54 e3 7a 62 b3 37 d8 04   ).."..`.T.zb.7..
0030 - 6c f1 8e ff 50 44 ed cc-7b 08 61 0c 16 88 f4 61   l...PD..{.a....a
0040 - 7b 8d f2 1e 04 1d 74 3d-cc ee a4 93 d3 bb 90 ee   {.....t=........
<snip>
--> Change Cipher Spec
write to 0x9547a78 [0x9592e90] (6 bytes => 6 (0x6))
0000 - 14 03 01 00 01 01
--> Finished Message                                 ......
write to 0x9547a78 [0x9592e90] (53 bytes => 53 (0x35))
0000 - 16 03 01 00 30 9a 88 8b-14 d6 d1 f1 f7 d8 0d ac   ....0...........
0010 - 38 cd 54 78 26 85 7b 11-c8 e9 db 8d a2 0c 6a a8   8.Tx&.{.......j.
0020 - d4 e7 d4 ad 5d 7a 6d 47-eb f9 5f 2c f6 ca 6a 1f   ....]zmG.._,..j.
0030 - 17 a6 58 25 41                                    ..X%A

3 ответа

Просто используйте вместо этого OpenSSL bufferevents из libevent API. Вы получите такую ​​же или даже лучшую функциональность. Я не уверен, но кажется, что вы пытаетесь внедрить сервер SSL на очень низком уровне, но, например, это будет болезненно с некоторыми встроенными механизмами асинхронного ввода-вывода в Linux.

Вы установили client_fd сокет в неблокирующем режиме на первом месте?

int fd_nonblock(int client_fd)
{
    int flags = fcntl(client_fd, F_GETFL, 0);
    flags |= O_NONBLOCK;
    return fcntl(client_fd, F_SETFL, flags);
}

Возможно, у вас могут возникнуть проблемы с кэшированием сеанса (он включен по умолчанию и на него указывает "edit 1"), trying пытаетесь ли вы предоставить клиенту поддельный сертификат с сервера? Может быть, внутренний кеш хранится по имени сертификата и пытается возобновить сеанс сервера...

Попробуйте отключить кеш с помощью SSL_CTX_set_session_cache_mode с SSL_SESS_CACHE_OFF вариант.

О "редактировании 2" на этапе обмена ключами клиента не говорится об отправке сертификата клиента, он отправляет секрет (или ничего), используемый для выбранной системы шифрования (он может отправить пустой обмен клиентом, если выбранный шифр не нуждается в секрет). Читайте здесь об этом.

Другие вопросы по тегам