openssl tutorial
证书生成
利用openssl套件里的工具可以方便地生成用来测试SSL连接所需要的证书。
证书生成的基本步骤为私钥生成、请求文件生成、交由CA签发。
所以,首先是CA私钥和自签名证书的生成。
生成用于签发client和server证书的ca根证书
mkdir ca
# 生成CA私钥,1024位RSA私钥,这里简单起见,没有对私钥进行加密。
openssl genrsa -out ca/ca.key 1024
# 生成ca的证书请求文件
openssl req -new -out ca/ca.csr -key ca/ca.key
# 用CA私钥进行自签名,生成ca的证书文件
openssl x509 -req -in ca/ca.csr -out ca/ca.cert -signkey ca/ca.key -days 365
利用ca签发生成客户端证书
mkdir client
# 生成client私钥
openssl genrsa -out client/client.key 1024
# 生成client证书请求文件
openssl req -new -out client/client.csr -key client/client.key
# ca签发client证书
openssl x509 -req -in client/client.csr -out client/client.crt -signkey client/client.key \
-CA ca/ca.cert -CAkey ca/ca.key -CAcreateserial -days 365
利用ca签发生成服务端证书
mkdir server
# 生成server私钥
openssl genrsa -out server/server.key 1024
# 生成server证书请求文件
openssl req -new -out server/server.csr -key server/server.key
# 利用ca签发server端证书
openssl x509 -req -in server/server.csr -out server/server.crt -signkey server/server.key \
-CA ca/ca.cert -CAkey ca/ca.key -CAcreateserial -days 365
- 到目前为止,我们生成了ssl通信所需要的全部证书和私钥,目录结构如下:
./ca:
ca.key # ca私钥
ca.cert # ca自签名证书
./client:
client.key # client私钥
client.crt # client证书
./server:
server.key # server私钥
server.crt # server证书
下一步就可以利用这些证书和 openssl 的开发库(libssl) api 进行客户端和服务端的 ssl 连接通信。
会话建立
证书生成好之后,会话的建立主要是一些 openssl 库 libssl API 的使用。
SSL 是一个建立在 tcp 之上的协议。
建立 SSL 连接基本的步骤是先建立普通的 tcp 连接,之后再进行 SSL 握手(shakehand)。
前面还有一些初始化和证书的加载。
openssl 关于建立 SSL 连接的关键函数
- 库的初始化和加载出错信息:
int SSL_library_init(void);
void SSL_load_error_strings(void);
- 设定使用的协议版本,SSLv2、SSLv3或者TLSv1:
const SSL_METHOD *SSLv3_server_method(void);
客户端和服务端需要使用相同的协议版本。例如对应的客户端调用SSLv3_client_method()
- 根据协议的版本,创建SSL_CTX(SSL上下文)对象:
SSL_CTX *SSL_CTX_new(const SSL_METHOD *method);
之后的证书加载、SSL对象的建立都是在这个上下文的对象上进行操作。
关于证书加载的函数
- 加载可信的CA证书。
int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath);
CAfile 直接指定 pem 格式的 ca 证书文件。
需要注意的是,如果是使用 CApath 指定证书文件所在的路径,
这个路径目录下除了需要存在 ca 证书文件,还需要有证书文件对应的hash link。
hash link 的生成:cd 到证书文件所在的目录;执行命令 c_rehash .
- 加载客户端或服务端的证书文件
int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type);
type一般为SSL_FILETYPE_PEM(PEM格式) 例如客户端file需要制定client目录下的client.cert。
- 加载客户端或服务端的私钥文件
int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);
type 一般为SSL_FILETYPE_PEM(PEM格式)
例如客户端 file需要指定 client 目录下的 client.key。
关于会话建立和读写函数
- 建立SSL对象(结构),参数为SSL上下文。
SSL *SSL_new(SSL_CTX *ctx);
- 关联tcp socket描述符
int SSL_set_fd(SSL *ssl, int fd);
fd为普通tcp连接建立完成后的套接字fd。
- SSL握手函数,
int SSL_connect(SSL *ssl);
int SSL_accept(SSL *ssl);
客户端调用SSL_connect, 服务端对应调用SSL_accept。
- SSL 读写函数
int SSL_read(SSL *ssl, void *buf, int num);
int SSL_write(SSL *ssl, const void *buf, int num);
int SSL_pending(const SSL *ssl);
SSL_pending返回可读的字节数。
- SSL会话的关闭和对象的释放。
int SSL_shutdown(SSL* ssl);;
void SSL_free(SSL* ssl);
void SSL_CTX_free(SSL_CTX* ctx);
Demo
一个简单的SSL会话Demo
使用 libevent
libevent是一个基于Reactor模式的跨平台网络库。它也对部分openssl的功能进行了封装。
利用libevent中bufferevent的几个ssl函数,可以实现异步SSL/TLS传输。
ps:另一个异步网络库boost.asio(基于Proactor模式)也是有提供SSL的支持。
libevent的openssl功能在在单独的libevent-openssl库中实现。使用时不需要再链接openssl库。
这部分功能的函数都在libevent的头文件 event2/bufferevent_ssl.h 中声明。
libevent 关于ssl的主要api只有两个,都只是用来建立连接。
至于SSL_CTX对象、SSL对象、证书的加载等操作还是要通过openssl自身的api。
一个bufferevent_openssl_filter_new,基于已有的bufferevent之上建立SSL bufferevent。
一个bufferevent_openssl_socket_new,基于套接字,直接使用openssl通信的bufferevent。
后者的应用情景更常见一些。
bufferevent_openssl_socket_new(struct event_base *base,
evutil_socket_t fd,
SSL *ssl,
enum bufferevent_ssl_state state,
int options);
服务端和客户端都是调用这一个函数来建立ssl bufferevent的,state参数会有所区分。
对于服务端,state参数一般为BUFFEREVENT_SSL_ACCEPTING;客户端一般为BUFFEREVENT_SSL_CONNECTING。
参数ssl需要传递一个SSL对象的指针,该对象的建立同上篇一样。
options一般为BEV_OPT_CLOSE_ON_FREE,表示释放时会自动关闭SSL对象和底层的fd。
关于调用时机,首先对于服务端,显然该函数并不接受一个socket地址的参数,
所以在之前是一系列的socket、bind和listen。之后accept得的新连接的fd。
这个时候调用,把新连接的fd传递进去。
对于客户端会简单一些。可以直接fd参数为-1。
之后调用bufferevent_socket_connect就可以直接来建立ssl连接。
openssl bufferevent建立完成后,再设定读写回调和事件回调,其后就是普通的libevent流程了。
不过比较有用的一点是,事件回调中的BEV_EVENT_CONNECTED事件可以来判断握手的完成。
P.S libevent openssl的两个辅助函数
unsigned long bufferevent_get_openssl_error(struct bufferevent *bev);
用来获取openssl bufferevent的最近的ssl出错代码。
SSL* bufferevent_openssl_get_ssl(struct bufferevent *bufev);
用来获取bufferevent使用的SSL对象。
一个简单的示例:libevent_openssl demo

浙公网安备 33010602011771号