疑似快半个月没有更新了, 今天来说一下怎样用openssl库实现https.
关于https的握手协议, 请看之前的文章: Introduction to Cryptography (2)
其实我觉得openssl的crypt库(libeay32.dll)写的并不怎么样, 之前尝试过要用来写base64和md5的加密, 发现一旦输入的数据有点异常, 输出马上就错了, 而且还找不到错在哪里. 现在我们跳过这个库, 直接用ssl库(ssleay32.dll).
这个东西其实用起来不麻烦, 可是就是缺文档, google出来的也几乎是代码, 根本没有code snippet之类的东西. 于是最终我还是发现了它自带的几个demo程序: $(OPENSSL_LIB)/demos/ssl/*, 2个server, 1个client, 代码不多, 适合学习之用.
一般opensl初始化总是先调用2个函数: SSL_load_error_strings(), SSL_library_init(). 具体用途很明显, 这样我想到了万恶的winsock库, 也是如此.
然后你需要生成一个SSL_CTX* 指针, 它表示的是一个context(废话). 什么context呢? 对于https来说, 就是一个证书(certificate). 有了这个context之后, 所有的SSL* 的实例指针便可以都基于这个context, 分别绑定不同的http请求, 来实现https访问. 于是便有大概如下的流程:
a) 初始化SSL_CTX*: 分别调用SSLv23_server_method(), SSL_CTX_new()(对应的delete函数是SSL_CTX_free()) 这2个函数. 你会发现SSLv23_server_method()以外, 还有很多类似的函数, 返回的分别对应不同的ssl版本, v23是兼容性最好的一个.
b) 往SSL_CTX*填东西, 轮到证书出马了: 分别调用SSL_CTX_use_certificate_file(), SSL_CTX_use_PrivateKey_file(), SSL_CTX_check_private_key() 这3个函数. 1st函数加载证书(certificate), 2nd函数加载server的密钥(private key), 3rd函数验证前两项是否匹配. 关于ssl证书的生成, 请看这里: apache openssl/mod_ssl
c) 初始化并关联SSL*: 初始化调用的SSL_new() 函数(对应的delete函数是SSL_free()). 好了, 开一个普通的socket 描述符(descriptor), 不过一般这个socket是在调用了connect()或accept()函数之后, 调用SSL_set_fd(), 把这个SSL*跟socket关联起来. 之后便可以调用SSL_read(), SSL_write()之类的函数来发送https的加密数据了.
看文字大概还是比较茫然, 我们还是看代码吧, 一下是一个https server的大概雏形:
int main()
{
SSL_load_error_strings();
SSL_library_init();
SSL_CTX *pSSLCtx = SSL_CTX_new(SSLv23_server_method());
SSL_CTX_use_certificate_file(pSSLCtx, "xxx.cert", FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(pSSLCtx, "xxx.key", FILETYPE_PEM);
SSL_CTX_check_private_key(pSSLCtx);
int svrSkt = socket(...);
svrSkt.bind(...);
svrSkt.listen(...);
int cntSkt = accept(srvSkt, ...);
SSL *pSSL = SSL_new(pSSLCtx);
SSL_set_fd(pSSL, cntSkt);
SSL_read(...);
SSL_write(...);
close(cntSkt);
SSL_shutdown(pSSL);
SSL_free(pSSL);
SSL_CTX_free(pSSLCtx);
return 0;
}
因为https实际上是介于tcp/ip层和ghttp层之间的协议层, 所以是先用socket连上之后(tcp/ip层), 再加入ssl的支持. 然后所有之前用send(), recv()来完成的工作, 统统交给SSL_read(), SSL_write()来完成就行了.



