7

我花了一些时间搜索互联网寻找更好的方法来分析和调试我的问题,但我似乎找不到解决方案。所以我想我会问。

简要地。我正在尝试创建一个非阻塞 ssl 转发代理。代理的服务器部分使用的是自签名服务器证书,我使用自己的 CA 证书进行了签名。如果重要的话,我正在使用 libev。我首先成功地创建了一个非加密代理(它盲目地转发了网络流量),现在我正在尝试向它添加 SSL。:)

我在让客户端连接到代理时遇到问题。我已经尝试过 wget 和 ssl 的 s_client 作为测试客户端,因为我想进行一些自动化测试。

ssl 服务器设置(此代码在 EV_READ 事件上从 libev 观察程序侦听套接字 accept_handler() 调用):

/* 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

这就是它挂起的地方。如果我检测到 SSL_ERROR_WANT_READ(这是 SSL_get_error() 在上面的第二个“读取客户端证书 A”消息之后返回的内容),我尝试将 client_handshake() 函数修改为在 SSL_accept() 周围循环(1){}。

那什么也没做,只是让我进入一个无限循环(),不断调用 SSL_accept()。

我假设 SSL 状态机需要一些它无法获得的附加信息。起初我以为我需要继续从套接字读取,但这没有用。

另外,我对为什么我的代理尝试读取客户端证书感到困惑,因为我已明确指定我不想验证上面的客户端证书 (SSL_VERIFY_NONE);除非我误解了该功能的目的。

如果有人对此有任何见解,我将不胜感激。或者也许是调试此问题的更好方法。strace() 对此毫无用处,而且我从 wget 或 s_client 都没有得到任何好的返回/错误消息。

我尝试在 SSL 的状态机中设置 alert_callbacks 和 msg_callbacks,但这并没有给我提供比 info 回调更多的信息。

在这一点上,我不确定这是套接字问题,还是 SSL 问题,或者什么。

edit1:我想指出,在accept_handler() 中,我首先通过ssl 连接到服务器,以便在完成accept() 之前验证我正在代理的主机的证书。如果我颠倒操作顺序,并在connect()之前先接受(),它就可以工作。

edit2:我尝试查看 s_client 和代理之间的 tcpdump 输出。在client_info_cb中引用的写入服务器数据和flush数据后,客户端发送“Client Key Exchange”、“Change Cipher Spec”和“Encrypted Finished Message”。但是 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
4

3 回答 3

1

只需使用libevent API 中的 OpenSSL bufferevents 即可。您将获得相同甚至更好的功能。我不确定,但您似乎正在尝试在非常低的级别实现 SSL 服务器,但是,例如,使用某些原生 Linux 异步 I/O 机制 会很痛苦。

于 2013-09-18T21:03:16.293 回答
0

也许您可能会遇到一些会话缓存问题(默认情况下已启用并且“edit 1”指向它),¿您是否试图向客户端提供来自服务器的欺骗证书?也许内部缓存是按证书名称存储的,它会尝试恢复服务器会话......

SSL_CTX_set_session_cache_mode尝试使用该SSL_SESS_CACHE_OFF选项关闭缓存。

关于“编辑 2”,客户端密钥交换阶段不讨论发送客户端的证书,它发送一个用于所选密码系统的秘密(或什么都没有)(如果所选密码不需要,它可以发送一个空的客户端交换秘密)。在这里阅读它。

于 2012-10-24T13:07:54.850 回答
0

您是否首先将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);
}
于 2017-06-14T18:18:56.570 回答