一段破代码,对SSL异步编程感兴趣的同学有福了, 欢迎高手指正使用错误

SSL技术是很常用的, 无处不在, 在你SSH的时候, 在你HTTPS的时候, 在任何一款想兼具身份认证与加密通信的应用中, 你可以随处看到它的身影.




本人之前只是用用脚本, 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的同学可以直接拷走.)

#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-----



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值