SSL技术是很常用的, 无处不在, 在你SSH的时候, 在你HTTPS的时候, 在任何一款想兼具身份认证与加密通信的应用中, 你可以随处看到它的身影.
本人之前只是用用脚本, SSL接口都被透明化了, 而且还是阻塞接口, 或者会使用Libevent支持的SSL体验一番异步的感觉, 也曾经读过Https的源码, 当时并没有在SSL方面引起重视, 直到我希望把Https引入到我的开源Http Server中, 终于有机会来编码实现异步SSL啦.要理解与正确的编程实践异步SSL, 需要对Feed工作模式的原理有一定的理解, 还需要对网络事件编程有掌握, 最后最重要的还是对SSL握手的过程有清晰的认识, 对SSL的原理有清晰的认识, 这样才不至于在编程时出现逻辑问题, 我们要清楚的明白每一步调用的目的与作用, 而不是胡猜或者模仿. <ignore_js_op>代码说明:可以通过浏览器访问或者使用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基础知识与握手流程观看:
- #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;
- }