使用带有非阻塞套接字的libev,OpenSSL读取客户端证书错误

时间:2012-10-23 14:00:21

标签: openssl libev

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

简言之。我试图创建一个非阻塞的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,但这并没有提供比信息回调更多的信息。

此时我不确定它是套接字问题,还是SSL问题,或者是什么。

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

edit2:我试着查看s_client和代理之间的tcpdump输出。在client_info_cb中引用的写入服务器数据和刷新数据之后,客户端发送"客户端密钥交换""更改密码规范"和#34;加密完成消息" 。但是,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 个答案:

答案 0 :(得分:1)

只需使用 libevent API中的OpenSSL bufferevents即可。您将获得相同甚至更好的功能。我不确定,但似乎您正在尝试以非常低的级别实现SSL服务器,但是,例如,它would be painfull具有一些本机Linux异步I / O机制。

答案 1 :(得分:0)

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

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

关于“编辑2”客户端密钥交换阶段没有谈到发送客户端的证书,它发送一个秘密(或没有)用于选定的密码系统(它可以发送一个空客户端交换,如果选择的密码不需要秘密)。 Read here about it.

答案 2 :(得分: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);
}
相关问题