本人之前只是用用脚本, SSL接口都被透明化了, 而且还是阻塞接口, 或者会使用Libevent支持的SSL体验一番异步的感觉, 也曾经读过Https的源码, 当时并没有在SSL方面引起重视, 直到我希望把Https引入到我的开源Http Server中, 终于有机会来编码实现异步SSL啦.
要理解与正确的编程实践异步SSL, 需要对Feed工作模式的原理有一定的理解, 还需要对网络事件编程有掌握, 最后最重要的还是对SSL握手的过程有清晰的认识, 对SSL的原理有清晰的认识, 这样才不至于在编程时出现逻辑问题, 我们要清楚的明白每一步调用的目的与作用, 而不是胡猜或者模仿.

代码说明:可以通过浏览器访问或者使用curl/wget进行访问, 记住使用https访问指定端口.
逻辑说明:
1, SSL握手阶段, 很明显的使用了feed模式, 调用链:read -> bio_write(rbio) -> ssl_accept, 即将读到的数据feed给ssl_accept使用.
feed后根据bio_pending(wbio)确定ssl是否产生了一些待回复的握手数据, 如果有, 我们注册写事件帮ssl发送出去, 此时调用链为: bio_read -> write
2, 数据交互阶段, 注意SSL握手阶段是绝对不可能交互普通数据的, 因为交互普通数据依赖于SSL彻底完成后获得的对称密钥.
在SSL完全结束前, 是不可能有正常数据到来的, 客户端与服务端是默契的一问一答, 直到SSL握手结束, 我们可以看一下握手交互时序图来看一下这个关系, 客户端在接受到服务端最后一次MAC应答之前是不会操作普通数据的, 服务端在接收到客户端发来的MAC后进入了最后一步握手输出阶段, 在最后一步时我们送出MAC兵彻底进入了数据交互阶段, 这些逻辑在代码里有所体现.
在代码中, SSL_accept意味着客户端MAC被接受, 此时SSL会在wbio中pending上最后一步要发送给客户端的MAC, 我们进行了状态的记录, 在MAC送给客户端后, 我们立即将HTTP应答送出而不等待客户端HTTP请求, 因为我不想解析与理解HTTP协议, 所以在这时候发送是最佳的时机, 并且一定是附带了connection:close来让客户端主动的关闭掉, 因为我们没有主动关闭客户端的逻辑. 在我们消耗掉wbio中的pending MAC后, 客户端发送HTTP请求到来, 服务端读事件将会检测ssl握手完成(因为我们的wbio pending的MAC已经被消耗掉了), 于是我们接下来的数据都被当作普通交互数据, 经历调用链: read -> bio_write -> ssl_read -> print to screen.
我不确定SSL的使用方法一定是完全正确的, 但至少目前运行还没有出过问题, 欢迎大家批评指正, 感兴趣的同学又有福了.(PS:懒得弄证书和prikey的同学可以直接拷走.)
SSL基础知识与握手流程观看:http://www.cnblogs.com/happyhippy/archive/2007/05/14/746476.html
#include <iostream>
#include <string>
#include <openssl/ssl.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <openssl/err.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "event2/event.h"
#include "event2/util.h"
#define CERT_FILE_PATH "cacert.pem"
#define PRIVATE_FILE_PATH "prvtkey.pem"
#define TRACE(fmt, ...) do{fprintf(stderr, fmt"\n", ##__VA_ARGS__);}while(0)
struct event_base *srv_base;
int lis_fd;
struct event *lisev;
std::string ok_res = "HTTP/1.1 200 OK\r\nConnection:close\r\nContent-Length:2\r\n\r\nok";
struct Client
{
int m_fd;
std::string m_tmpbuf;
std::string m_outbuf;
int m_done;
SSL *m_ssl;
SSL_CTX *m_ctx;
BIO *m_rbio;
BIO *m_wbio;
struct event *m_rev;
struct event *m_wev;
};
void CliCallback(int cli_fd, short event, void *userdata);
Client* InitClient(int fd)
{
Client *client = new Client();
#define M(mem) client->m_##mem
M(fd) = fd;
evutil_make_socket_nonblocking(fd);
M(ctx) = SSL_CTX_new(SSLv23_server_method());
SSL_CTX_use_certificate_file(M(ctx), CERT_FILE_PATH, SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(M(ctx), PRIVATE_FILE_PATH, SSL_FILETYPE_PEM);
M(ssl) = SSL_new(M(ctx));
M(rbio) = BIO_new(BIO_s_mem());
M(wbio) = BIO_new(BIO_s_mem());
SSL_set_bio(M(ssl), M(rbio), M(wbio));
SSL_set_accept_state(M(ssl));
M(tmpbuf).reserve(1024 * 1024);
M(rev) = event_new(srv_base, fd, EV_READ | EV_PERSIST, CliCallback, client);
M(wev) = event_new(srv_base, fd, EV_WRITE| EV_PERSIST, CliCallback, client);
event_add(M(rev), NULL);
return client;
}
void FreeClient(Client *client)
{
if (client)
{
SSL_free(M(ssl));
SSL_CTX_free(M(ctx));
event_free(M(rev));
event_free(M(wev));
close(M(fd));
delete client;
}
}
void LisCallback(int lis_fd, short event, void *userdata)
{
int cli_fd = accept(lis_fd, NULL, NULL);
if (cli_fd > 0)
{
Client *client = InitClient(cli_fd);
TRACE("New Client Comes");
}
}
void CliCallback(int cli_fd, short event, void *userdata)
{
Client *client = (Client *)userdata;
if (event & EV_READ)
{
int nb = read(cli_fd, &M(tmpbuf[0]), M(tmpbuf).capacity());
TRACE("read %d bytes", nb);
if (nb == -1)
{
if (errno != EAGAIN)
return FreeClient(client);
}
else if (nb == 0)
{
TRACE("Client Leave");
return FreeClient(client);
}
else
{
/* feed bio */
assert(BIO_write(M(rbio), &M(tmpbuf[0]), nb) == nb);
if (!SSL_is_init_finished(M(ssl)))
{
TRACE("ssl is not finished");
int ac = SSL_accept(M(ssl));
if (ac < 0)
{
int err = SSL_get_error(M(ssl), ac);
if (err != SSL_ERROR_WANT_READ)
{
return FreeClient(client);
}
TRACE("SSL_accept wants more");
}
if (ac > 0)
{
/*ssl reach the last step, send back the MAC*/
TRACE("SSL_accept done");
M(done) = 1;
}
if (ac == 0)
{
TRACE("ac = 0");
}
int np = BIO_pending(M(wbio));
if (np)
{
TRACE("BIO_pending=%d", np);
event_add(M(wev), NULL);
}
}
else
{
int decb;
while ((decb = SSL_read(M(ssl), &M(tmpbuf[0]), M(tmpbuf).capacity())) > 0)
{
TRACE("ssl read %d dec bytes", decb);
TRACE("%.*s", decb, &M(tmpbuf[0]));
}
if (decb < 0)
{
int err = SSL_get_error(M(ssl), decb);
if (err != SSL_ERROR_WANT_READ)
{
return FreeClient(client);
}
TRACE("SSL_read wants more");
}
}
}
}
if (event & EV_WRITE)
{
int nenc;
while ((nenc = BIO_read(M(wbio), &M(tmpbuf[0]), M(tmpbuf).capacity())) > 0)
{
TRACE("BIO_read %d enc bytes to send off", nenc);
M(outbuf).append(&M(tmpbuf[0]), nenc);
}
int nb = write(M(fd), M(outbuf).c_str(), M(outbuf).size());
TRACE("write out %d bytes", nb);
if (nb == -1)
{
if (errno != EAGAIN)
{
return FreeClient(client);
}
}
else
{
M(outbuf).erase(0, nb);
}
if (!M(outbuf).size())
{
if (M(done) == 1) //ssl last step finish
{
SSL_write(M(ssl), ok_res.c_str(), ok_res.size()); /*ssl is totally done, do response*/
M(done) = 2; //begin ordinary communication with symmetric-key
}
else if (M(done) == 2) /* response has been send out */
{
return FreeClient(client);
}
else
{
event_del(M(wev));
}
}
}
}
int main(int argc, char* const argv[])
{
SSL_library_init();
OpenSSL_add_ssl_algorithms();
SSL_load_error_strings();
ERR_load_crypto_strings();
int reuse = 1;
srv_base = event_base_new();
lis_fd = socket(AF_INET, SOCK_STREAM, 0);
evutil_make_socket_nonblocking(lis_fd);
setsockopt(lis_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
struct sockaddr_in lis_addr;
lis_addr.sin_family = AF_INET;
lis_addr.sin_port = htons(18888);
lis_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(lis_fd, (struct sockaddr*)&lis_addr, sizeof(lis_addr));
listen(lis_fd, 5);
lisev = event_new(srv_base, lis_fd, EV_READ | EV_PERSIST, LisCallback, NULL);
event_add(lisev, NULL);
event_base_dispatch(srv_base);
return 0;
}
[root@vps616 ssl]# cat Makefile
ssl:
g++ -o ssl_server ssl_server.cpp -levent -lssl
[root@vps616 ssl]# cat cacert.pem
-----BEGIN CERTIFICATE-----
MIICNjCCAZ+gAwIBAgIJAJlZp5TvKRUvMA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV
BAYTAmNuMQswCQYDVQQIDAJiajELMAkGA1UEBwwCYmoxCzAJBgNVBAoMAmJkMB4X
DTEyMTExNDE1MjMzNloXDTE1MTExNDE1MjMzNlowNDELMAkGA1UEBhMCY24xCzAJ
BgNVBAgMAmJqMQswCQYDVQQHDAJiajELMAkGA1UECgwCYmQwgZ8wDQYJKoZIhvcN
AQEBBQADgY0AMIGJAoGBAM/0hbKqSMSgOtO/ioZygTJBKEeSm6Mulrckkq1nTx0q
bQazLrx6hCGfxppeymKjPx8ubnnf0mngjf4ReAP02bDnbLQvEkbGfsdTUHDeuIab
fdspbGzB/nDesZWAR4Ib9GapuwF/TfJmsWZRN4bvYC77b8eqsVXNT8VnfJblXYGH
AgMBAAGjUDBOMB0GA1UdDgQWBBR5I71Os/CxEaJVdXj2myIDMURgEzAfBgNVHSME
GDAWgBR5I71Os/CxEaJVdXj2myIDMURgEzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
DQEBBQUAA4GBAHCjE+Qas3LKzPsV7goGQeI9pOl9961c+VQGjbutQDmKduM+VMF5
uoyHcu0f8ER39f5wEeB+qdrkuMTt14Vyn8kaGefe4c2wMMXnEGZFbWeayufh9cR6
zRlQDCUkBCPIHu/tTtxTxPTTFw9Ev+M5OP6kVDgPLc5qZRhNaZYDa9QS
-----END CERTIFICATE-----
[root@vps616 ssl]# cat prvtkey.pem
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDP9IWyqkjEoDrTv4qGcoEyQShHkpujLpa3JJKtZ08dKm0Gsy68
eoQhn8aaXspioz8fLm5539Jp4I3+EXgD9Nmw52y0LxJGxn7HU1Bw3riGm33bKWxs
wf5w3rGVgEeCG/RmqbsBf03yZrFmUTeG72Au+2/HqrFVzU/FZ3yW5V2BhwIDAQAB
AoGBAMmctap8MUSAW8hDIVgr11oTlaueVcolNvWkPZhkWm1aXo1qVttgpI28y92K
HQj4YBApAe6isur3THKQGR0s24dItZRPtR35CRKzRwiXd7X+llvG0QzogXyWOGS1
dpXekdR9wuOJ//QvuFsJzIMzTavDNYHORvVzJii7m3ig9mzhAkEA95lQ0uny+n3Q
F6ypqaqbdjrH43OOIaslpOgh/rxf3D3xP+gUFgeeWruk8I0WOUg0s0F/0h5uBBF6
s3MInq1aTwJBANcC2s0SJwycJquFPPvCcqiZx5xFlaMcpaGrPMR4LPQaqF/4pocq
Havnh5eC8XMJwj3s+SBoy2WQujSCBkKx70kCQQCmDuuIKWPO4GaaGjFIG6Zcaxv4
zl1680AyE4YJROm92sVcqRgflkh8bfE3bEiFbon512oU0FfU3qw+gl47neQ9AkBH
yUs2Nr5U5nm+wJB42hYgFp/fnBf2yqS+UobKbflMUu4uhL1M2ZHoiDfsLSriJrr0
o/8VhAeM1IJm75aZhAEJAkEAzVwKrEE3DZa39D4sY6/HCSdOlrlDeCqAeXXGIIVO
uhMNnPkMt53BQ+uqTMaG4xG69lcdedS4djKiSzukgjyfnw==
-----END RSA PRIVATE KEY-----