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

posted @ 2013-03-11 09:46  jonahgao  阅读(1575)  评论(0)    收藏  举报