【libevent】OpenSSL加密
一、OpenSSL简介
OpenSSL是一个强大的加密库,广泛应用于互联网的各个角落,用于保护数据传输的安全。它实现了SSL和TLS协议,这些协议是现代网络安全的基石。
二、OpenSSL概述
OpenSSL是一个强大的开源工具,用于实现SSL和TLS协议,保障网络通信的安全。它不仅提供了加密库,还包括了命令行工具,可以用于创建证书、生成密钥、测试SSL/TLS连接等。
OpenSSL 库主要包含三大部分:
openssl: 多用途的命令行工具,可以执行交互或批量命令。
libcrypto: 加解密算法库。
libssl:加密模块应用库,实现了ssl及tls。
2.1 主要功能
-
**加密算法支持 **: 支持多种加密算法,如RSA、AES等。
-
SSL/TLS协议实现: 提供SSL v2/v3和TLS协议的实现。
-
**证书处理 **: 生成和管理SSL证书。
2.2 安装配置
2.3 基本概念
2.3.1 SSL/TLS
-
SSL (Secure Sockets Layer): 安全套接层,用于在互联网上提供加密通信。
-
TLS (Transport Layer Security):传输层安全, SSL的后续版本,提供更强的安全性。
2.3.2 密钥
- 公钥 (Public Key): 公钥是非对称加密算法中的一部分,公开给所有人。通常用于加密数据和验证数字签名。
- 加密数据:任何人都可以使用接收方的公钥来加密数据,但只有拥有相应私钥的接收方才能解密。
- 验证签名:验证消息的数字签名是否由相应的私钥生成,以确保消息的来源和完整性。
- 私钥 (Private Key): 与公钥配对的一部分,必须保密。它用于解密数据和生成数字签名。
- 解密数据:只有私钥的持有者才能解密用其公钥加密的数据。
- 生成签名:使用私钥对消息进行签名,接收方可以使用公钥来验证签名,确保消息未被篡改且确实来自私钥持有者。
2.3.3 证书
数字证书 (Digital Certificate): 证书是一种数字文档,用来证明公钥的真实性和持有者的身份。它由受信任的第三方机构(CA,证书颁发机构)签发。
2.3.4 加密
对称加密
对称加密使用同一个密钥进行加密和解密。这意味着发送方和接收方必须共享同一个密钥,并且必须确保密钥的安全性。
例子:
Bob:我们需要加密我们的通信。你有加密算法吗
Alice:是的,我有。我们可以使用AES算法。我们需要一个共享密钥。
Bob:好的,我生成了一个密钥。我们都使用这个密钥来加密和解密消息。
Alice:明白了,我也会使用密钥来加密我们的通信。
Bob和Alice共享了一个对称密钥,然后他们使用这个密钥来加密和解密他们的通信内容。
特点:
- 单一密钥:加密和解密使用相同的密钥。
- 速度快:由于算法简单且密钥相同,对称加密的处理速度比非对称加密快,适合加密大数据量。
- 安全性较低:如果密钥泄露,数据安全性将受到威胁。
非对称加密
非对称加密使用一对密钥:公钥和私钥。公钥用于加密数据,私钥用于解密数据。
例子:
Bob:给我你的公钥
Alice:这是我的公钥,请拿好。你可以使用它来加密你的消息给我。
Bob:好的。这是我的公钥,请拿好。你可以使用它来加密你的消息给我。
Alice:好的,我会使用你提供的公钥来加密我的消息。
Bob和Alice交换了彼此的公钥,并使用对方的公钥来加密他们的消息,以确保只有对方能够解密它们。私钥保留在各自的设备上,用于解密接收到的加密消息。
特点:
- 密钥对:使用公钥和私钥,公钥加密的数据只能由对应的私钥解密。
- 速度较慢:由于算法复杂,非对称加密的处理速度比对称加密慢,通常用于加密小数据量或加密对称密钥。
- 高安全性:由于密钥对的特性,不需要安全地共享密钥,公钥可以公开分发,私钥只需持有者保密。
一般在https 中,非对称加密用于安全地交换对称加密的会话密钥,然后使用对称加密进行后续的数据传输,以兼顾安全性和效率。
2.3.4 证书、私钥和证书签署请求(CSR)获取
OpenSSL 精粹:SSL 证书、私钥和 CSR | Linux 中国 - 知乎 (zhihu.com)
自签名证书
适合测试和开发环境。可以使用 OpenSSL 工具来生成
#生成私钥
openssl genrsa -des3 -out privkey.pem 2048 #这个命令会生成一个2048位的密钥,同时有一个des3方法加密的密码
#如果不想要每次都输入密码,可以改成:
openssl genrsa -out privkey.pem 2048
#生成证书文件
#用上面生成的密钥privkey.pem生成一个数字证书cacert.pem,有效期为3650天
#在这一步,会提示输入信息,Common Name (eg, your name or your server's hostname) []:是必填,其他的可以选择回车使用默认值,输入点"."将信息项留空
openssl req -new -x509 -key privkey.pem -out cacert.pem -days 3650
#生成公钥文件
openssl rsa -in privkey.pem -inform pem -pubout -out pubkey.pem
#查看密钥信息
openssl rsa -noout -text -in privkey.pem
三、SSL/TLS协议
SSL协议在互联网中应用特别广泛,成为事实上的互联网标准。在1999年IETF组织将SSL3.0协议规范进行了标准化,就是TLS协议。不过由于SSL3.0和TLS之间存在加密算法上的差异,因此不能互相操作。他们是两个不同的协议。这两个协议统称为SSL/TLS协议。
3.1 SSL和TLS的应用
SSL/TLS是一个安全通信框架,上面可以承载HTTP协议或者SMTP/POP3协议等。
3.2 SSL/TLS原理简介
SSL/TLS协议严格的说位于OSI-7层模型的会话层,在传输层(TCP, UDP)协议之上。
SSL/TLS协议是一个分层协议,本身可以分为上下两层:
下层为记录协议(record layer protocal)
上层为握手协议(handshake layer protocal)
Record层是SSL/TLS的基础封装协议,所有的交互报文都需要通过Record 层进行封装。
3.3 SSL 安全套接字层
3.3.1 SSL协议架构
- SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。
- SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
3.3.2 SSL协议的工作流程
服务器认证阶段:
1)客户端向服务器发送一个开始信息“Hello”开始一个新的会话连接;
2)服务器根据客户的信息确定是否需要生成新的主密钥,如需要则服务器在响应客户的“Hello”信息时将包含生成主密钥所需的信息;
3)客户根据收到的服务器响应信息,产生一个主密钥,并用服务器的公开密钥加密后传给服务器;
4)服务器恢复该主密钥,并返回给客户一个用主密钥认证的信息,以此让客户认证服务器。
用户认证阶段:
在此之前,服务器已经通过了客户认证,这一阶段主要完成对客户的认证。经认证的服务器发送一个提问给客户,客户则返回数字签名后的提问和其公开密钥,从而向服务器提供认证。
3.4 TLS安全传输层协议
3.4.1 TLS协议架构
- TLS记录协议(TLS Record):建立在可靠的传输协议(如TCP)之上,用于封装高层协议,使用对称密码对消息进行加密。
- TLS握手协议(TLS Handshake):它建立在TLS记录协议之上。主要分为握手协议,密码规格变更协议和应用数据协议4个部分。
- 握手协议负责在客户端和服务器端商定密码算法和共享密钥,包括证书认证。
- 密码规格变更协议负责向通信对象传达变更密码方式的信号
- 警告协议负责在发生错误的时候将错误传达给对方
- 应用数据协议负责将TLS承载的应用数据传达给通信对象的协议。
3.4.2 握手协议
握手协议是TLS协议中非常重要的协议,通过客户端和服务器端的交互,和共享一些必要信息,从而生成共享密钥和交互证书。
主密码和预备主密码
步骤8生成了预备主密码,主密码是根据密码套件中定义的单向散列函数实现的伪随机数生成器+预备主密码+客户端随机数+服务器端随机数生成的。
主密码主要用来生成称密码的密钥,消息认证码的密钥和对称密码的CBC模式所使用的初始化向量。
3.4.3 记录协议
消息首先将会被分段,然后压缩,再计算其消息验证码,然后使用对称密码进行加密。得到密文之后会附加类型,版本和长度等其他信息,最终组成最后的报文数据。
四、OpenSSL加密示例
4.1 生成密钥和证书
openssl genrsa -out privkey.pem 2048#生成服务器私钥
openssl req -new -x509 -key privkey.pem -out cacert.pem -days 3650 #生成自签名证书
4.2 服务器端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 11111
#define SERVER_CERT "cacert.pem"
#define SERVER_KEY "privkey.pem"
int main() {
SSL_CTX *ctx = NULL;
SSL *ssl = NULL;
int sock_fd = -1;
int cli_fd = -1;
struct sockaddr_in addr;
/*初始化OpenSSL库*/
SSL_library_init();//初始化整个 OpenSSL 库
SSL_load_error_strings(); // 加载所有错误信息
OpenSSL_add_ssl_algorithms();// 加载所有可用的加密算法
/*创建并初始化 SSL 上下文*/
ctx = SSL_CTX_new(TLS_server_method());
if (!ctx)
{
printf("Unable to create SSL context");
}
/*配置 SSL 上下文,加载服务器证书和密钥*/
SSL_CTX_use_certificate_file(ctx, SERVER_CERT, SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(ctx, SERVER_KEY, SSL_FILETYPE_PEM);
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr));
listen(sock_fd, 1);
cli_fd = accept(sock_fd, NULL, NULL);
ssl = SSL_new(ctx);//创建一个新的 SSL 连接对象。用于接受 SSL/TLS 连接。
SSL_set_fd(ssl, cli_fd);
if (SSL_accept(ssl) <= 0) // 执行 SSL/TLS 握手
{
printf("SSL_accept() failure\n");
}
else
{
SSL_write(ssl, "Hello, Client!", 32);
}
// 关闭 SSL 连接并释放资源
SSL_shutdown(ssl);
SSL_free(ssl);
close(cli_fd);
close(sock_fd);
SSL_CTX_free(ctx);
EVP_cleanup(); // 清理加载的算法
}
4.3 客户端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 11111
#define SERVER_CERT "cacert.pem"
int main() {
SSL_CTX *ctx = NULL;
SSL *ssl = NULL;
int conn_fd = -1;
struct sockaddr_in addr;
/*初始化OpenSSL库*/
SSL_library_init();//初始化整个 OpenSSL 库
SSL_load_error_strings(); // 加载所有错误信息
OpenSSL_add_ssl_algorithms ();// 加载所有可用的加密算法
ctx = SSL_CTX_new(TLS_client_method());
if (!ctx) {
printf("Unable to create SSL context");
}
SSL_CTX_load_verify_locations(ctx, SERVER_CERT, NULL);//设置验证服务器的证书
conn_fd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
inet_aton("127.0.0.1",&addr.sin_addr);
connect(conn_fd, (struct sockaddr*)&addr, sizeof(addr));
ssl = SSL_new(ctx);
SSL_set_fd(ssl, conn_fd);
if (SSL_connect(ssl) <= 0) // 执行 SSL/TLS 握手
{
printf("SSL_connect() failure\n");
}
else
{
char buf[256] = {0};
SSL_read(ssl, buf, sizeof(buf));
printf("Received: %s\n", buf);
}
// 关闭 SSL 连接并释放资源
SSL_shutdown(ssl);
SSL_free(ssl);
close(conn_fd);
SSL_CTX_free(ctx);
EVP_cleanup();
}
五、libevent
5.1 简介
Libevent 是一个开源的高性能异步事件通知库,广泛用于网络编程中处理大量并发连接。它提供了一个统一的接口来监视多个文件描述符(包括网络套接字),并在这些描述符可读、可写或有异常时触发回调函数。Libevent 可以使用多种底层机制(如 epoll
、kqueue
、poll
和 select
)来实现其功能,这使得它在不同操作系统上都能高效运行。
特点:
- 事件驱动: Libevent 基于事件驱动模型,允许应用程序在文件描述符准备好读写时进行处理,从而避免了阻塞操作,提高了效率。
- 多种后端支持: 它支持多种底层 I/O 多路复用机制。
- 定时器: Libevent 提供了精确的定时器功能,可以在指定的时间间隔后触发事件。
- 信号处理: 支持异步信号处理,允许程序在接收到信号时执行特定的回调函数。
- 缓冲事件: 提供了高级的缓冲事件(
bufferevent
)接口,简化了读写操作的实现,并支持 SSL/TLS 加密通信。 - 线程安全: Libevent 可以配置为线程安全,允许在多线程环境中使用。
模型:
libevent主框架提供注册方法,通过事件循环去检测事件就绪并通知libevent框架去调用回调函数。
关于libevent的学习可以参考:libevent - 标签 - CNHK19 - 博客园 (cnblogs.com)
5.2 示例
5.2.1 服务器端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <signal.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/util.h>
#include <event2/listener.h>
#include <arpa/inet.h>
#define PORT 11111
void read_cb(struct bufferevent *bev, void *arg)
{
char buf[32];
int len;
while( (len = bufferevent_read(bev, buf, sizeof(buf))) > 0 )
{
buf[len] = '\0';
printf("%s\n", buf);
}
}
void event_callback(struct bufferevent *bev, short events, void *arg)
{
if( events & BEV_EVENT_EOF )
{
printf("Connection closed\n");
}
else if( events & BEV_EVENT_ERROR )
{
printf("Connection error:%s\n", strerror(errno));
}
}
void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int len, void *arg)
{
struct event_base *base = (struct event_base *)arg;
struct bufferevent *bev = NULL;
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if( !bev )
{
printf("bufferevent_socket_new() failure:%s\n", strerror(errno));
close(fd);
return ;
}
bufferevent_setcb(bev, read_cb, NULL, event_callback, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE);
}
int main (int argc, char **argv)
{
struct sockaddr_in addr;
struct event_base *base = NULL;
struct evconnlistener *listener = NULL;
base = event_base_new();
if( !base )
{
printf("event_base_new() failure\n");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
listener = evconnlistener_new_bind(base, listener_cb, base, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1, (struct sockaddr *)&addr, sizeof(addr));
if( !listener )
{
printf("Can't create a listener\n");
return -2;
}
event_base_dispatch(base);
evconnlistener_free(listener);
event_base_free(base);
return 0;
}
5.2.2 客户端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <signal.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/util.h>
#include <arpa/inet.h>
#define IP "127.0.0.1"
#define PORT 11111
void event_callback(struct bufferevent *bev, short events, void *arg)
{
if( events & BEV_EVENT_EOF )
{
printf("Connection closed\n");
}
else if( events & BEV_EVENT_ERROR )
{
printf("Connection error:%s\n", strerror(errno));
}
}
void send_data(evutil_socket_t fd, short events, void *arg)
{
struct bufferevent *bev = (struct bufferevent *)arg;
char buf[32] = "Hello, world!";
printf("%s\n", buf);
bufferevent_write(bev, buf, strlen(buf));
}
int main (int argc, char **argv)
{
struct sockaddr_in addr;
int len = sizeof(addr);
struct event_base *base = NULL;
struct bufferevent *bev = NULL;
struct timeval tv;
struct event *ev = NULL;
base = event_base_new();
if( !base )
{
printf("event_base_new() failure\n");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
inet_aton(IP, &addr.sin_addr);
bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
if( !bev )
{
printf("Could not create bufferevent\n");
event_base_free(base);
return -2;
}
bufferevent_setcb(bev, NULL, NULL, event_callback, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE);
bufferevent_socket_connect(bev, (struct sockaddr *)&addr, len);
tv.tv_sec = 3;
tv.tv_usec = 0;
ev = event_new(base, -1, EV_PERSIST, send_data, bev);
event_add(ev, &tv);
event_base_dispatch(base);
event_free(ev);
bufferevent_free(bev);
event_base_free(base);
return 0;
}
六、ssl bufferevent
bufferevent 是 Libevent 提供的一种缓冲事件,用于简化I/O 操作。结合 SSL/TLS,它可以处理加密的异步网络通信。Libevent 中有专门用于 SSL 的 bufferevent 函数,这使得在网络编程中使用 SSL/TLS 变得更加容易。
5.1 常用函数
初始化
int SSL_library_init(void);
功能:
初始化整个 OpenSSL 库
void SSL_load_error_strings(void);
功能:
加载错误字符串,可以将错误代码转换为可读的错误消息。
void OpenSSL_add_all_algorithms(void);
功能:
注册所有可用的加密算法、摘要算法和哈希算法。
void ERR_load_crypto_strings(void);
功能:
加载 OpenSSL 加密库(Crypto)的错误消息字符串。
释放
SSL_CTX_free(ssl_ctx);
功能:
释放 SSL_CTX 对象,与SSL_library_init()对应
EVP_cleanup();
功能:
清理所有的加密算法。与 OpenSSL_add_all_algorithms() 对应。
ERR_free_strings();
功能:
释放所有加载的错误消息字符串。与SSL_load_error_strings() 对应。
创建并初始化对象
SSL_CTX *SSL_CTX_new(const SSL_METHOD *method);
功能:
创建并初始化一个新的SSL 对象,用于保存 SSL/TLS 配置和状态信息,并在多个 SSL 连接中共享这些信息。
参数:
const SSL_METHOD *method:指向一个 SSL_METHOD 结构的指针,定义了 SSL/TLS 的协议版本和选项。
常用的 SSL_METHOD 包括:
TLS_method():支持客户端和服务器端的通用 TLS 方法(包括 TLS 1.0 到 TLS 1.3)。
TLS_server_method():服务器端的通用 TLS 方法。
TLS_client_method():客户端的通用 TLS 方法。
返回值:
成功:指向新创建的 SSL_CTX 结构的指针。
失败:NULL,并设置相应的错误信息,可以通过 ERR_get_error 函数获取详细错误代码。
SSL *SSL_new(SSL_CTX *ssl_ctx);
功能:
创建一个新的 SSL 连接对象。这个新创建的对象可以用于发起或接受 SSL/TLS 连接。
参数:
SSL_CTX *ssl_ctx:通过 SSL_CTX_new() 函数创建的指向 SSL_CTX 结构的指针
返回值:
成功:指向新创建的 SSL 结构的指针
失败:NULL
SSL_CTX和SSL的区别
SSL_CTX
(SSL Context)是一个上下文对象,它包含了多个 SSL 连接共享的配置信息和状态。它的主要作用是存储 SSL/TLS 连接的全局设置,包括证书、私钥、会话参数、信任链等。
SSL
(SSL Connection)是一个连接对象,它包含了特定 SSL/TLS 连接的状态和数据。每个 SSL
对象对应一个单独的 SSL/TLS 会话,包含特定连接的详细信息。它的主要作用是是管理单个链接的状态,进行握手操作和读写数据。
步骤:
-
创建
SSL_CTX
对象。 -
配置
SSL_CTX
对象(加载证书、设置选项等)。 -
基于
SSL_CTX
创建SSL
对象。 -
关联
SSL
对象与一个网络连接,SSL_set_fd(ssl, sockfd)
-
进行 SSL/TLS 握手,
SSL_connect(ssl)
注:第四步和第五步可以使用
bufferevent_openssl_socket_new()
来代替,它提供了一种更高层次的抽象,简化了 OpenSSL 和 libevent 的集成 -
使用
SSL
对象进行数据传输。 -
关闭连接,释放
SSL
对象。
设置服务器证书链和私钥
int SSL_CTX_use_certificate_chain_file(SSL_CTX *ssl_ctx, const char *file);
功能:
设置服务器的证书链,从文件中加载包含证书链的证书文件。
参数:
SSL_CTX *ssl_ctx:SSL_CTX 对象
const char *file:包含证书链的文件名
返回值:
成功:1
失败:0
int SSL_CTX_use_PrivateKey_file(SSL_CTX *ssl_ctx, const char *file, int type);
功能:
设置服务器的私钥。它从文件中加载服务器的私钥。
参数:
SSL_CTX *ssl_ctx:SSL_CTX 对象
const char *file:包含私钥的文件名
int type:指定私钥文件的格式,通常为 SSL_FILETYPE_PEM(PEM 格式)或 SSL_FILETYPE_ASN1(DER 格式)
返回值:
成功:1
失败:0
创建SSL加密的缓存事件
struct bufferevent *bufferevent_openssl_socket_new(struct event_base *base, evutil_socket_t fd,SSL *ssl, enum bufferevent_ssl_state state, int options);
功能:
创建一个新的 SSL 缓冲事件(bufferevent),用于加密的数据传输。
使用场景(区别于bufferevent_openssl_filter_new函数):
直接处理一个套接字上的加密通信,适合于需要从头开始创建并管理套接字通信
参数:
struct event_base *base: 事件对象
evutil_socket_t fd: 套接字,如果为-1,会自动调用socket(),再异步调用connect(),如果指定了,那么bev只去做connect()操作
SSL *ssl: 已初始化的 SSL 对象
enum bufferevent_ssl_state state: SSL的初始状态,通常为 BUFFEREVENT_SSL_CONNECTING(客户端) 或 BUFFEREVENT_SSL_ACCEPTING(服务器端)
int options: 选项标志,如 BEV_OPT_CLOSE_ON_FREE
返回值:
成功:bufferevent 对象指针
失败:NULL
struct bufferevent *bufferevent_openssl_filter_new(struct event_base *base, struct bufferevent *underlying, SSL *ssl, enum bufferevent_ssl_state state, int options);
功能:
创建一个新的 SSL 过滤缓冲事件,使现有的 bufferevent 支持 SSL/TLS 加密。
使用场景(区别于bufferevent_openssl_socket_new函数):
已经有一个缓冲事件并希望在其上添加SSL/TLS层,适合于已有非加密通信需要升级为加密通信的情况
参数:
struct event_base *base:事件对象
struct bufferevent *underlying:底层的bufferevent对象,bufferevent_socket_new()的返回值
SSL *ssl: SSL 对象
enum bufferevent_ssl_state state: SSL 的初始状态
int options:选项标志
返回值:
成功:新的 bufferevent 对象指针
失败:NULL
获取SSL对象
SSL *bufferevent_get_ssl(struct bufferevent *bev);
功能:
获取与 bufferevent 关联的 SSL 对象。
参数:
struct bufferevent *bev:需要获取 SSL 对象的 bufferevent 对象
返回值:
与 bufferevent 关联的 SSL 对象指针
关闭SSL连接
int bufferevent_openssl_shutdown(struct bufferevent *bev, int how);
功能:
关闭 SSL bufferevent,包括关闭 SSL 连接
参数:
struct bufferevent *bev: SSL bufferevent 对象
int how: 关闭方式的标志,通常为 BEV_SSL_CLOSE_FREE
返回值:
成功:0
失败:-1
5.2 TLS和SSL加密的区别
在 bufferevent 中使用 TLS 和 SSL 加密的区别主要在于 SSL/TLS 上下文对象的初始化和配置上,以及选择特定的加密协议版本。(通常使用TLS)
创建并初始化对象:
TLS:SSL_CTX_new(TLS_method())
TLS_method()
:
SSL_CTX_new(TLS_client_method())
SSL_CTX_new(TLS_server_method())`
SSL:SSL_CTX_new(SSL_method())
SSL_method()
:
SSL_CTX_new(SSLv23_client_method())
SSL_CTX_new(SSLv23_server_method())
5.3 示例
5.3.1 服务器端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <signal.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/util.h>
#include <event2/listener.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <event2/bufferevent_ssl.h>
#define CACERT_FILE "./ssl/cacert.pem"
#define PRIVKEY_FILE "./ssl/privkey.pem"
#define PORT 11111
void read_cb(struct bufferevent *bev, void *arg)
{
char buf[1024];
int len;
while( (len = bufferevent_read(bev, buf, sizeof(buf))) > 0 )
{
buf[len] = '\0';
printf("data:%s\n", buf);
}
}
void event_callback(struct bufferevent *bev, short events, void *arg)
{
if( events & BEV_EVENT_EOF )
{
printf("Connection closed\n");
}
else if( events & BEV_EVENT_ERROR )
{
printf("Connection error\n");
}
else if( events & BEV_EVENT_CONNECTED )
{
printf("Connect successfully\n");
return ;
}
}
void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr * addr, int len, void *arg)
{
SSL *ssl = (SSL *)arg;
struct bufferevent *bev = NULL;
struct event_base *evbase;
evbase = evconnlistener_get_base(listener);
bev = bufferevent_openssl_socket_new(evbase, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_CLOSE_ON_FREE);
if( !bev )
{
printf("bufferevent_socket_new() failure\n");
close(fd);
return ;
}
bufferevent_setcb(bev, read_cb, NULL, event_callback, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE);
}
int main (int argc, char **argv)
{
struct event_base *base;
struct evconnlistener *listener;
SSL_CTX *ssl_ctx;
SSL *ssl;
struct sockaddr_in addr;
SSL_load_error_strings();
SSL_library_init();
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
ssl_ctx = SSL_CTX_new(TLS_server_method());
if( !ssl_ctx )
{
printf("SSL_CTX_new() failure\n");
return -1;
}
if( !SSL_CTX_use_certificate_chain_file(ssl_ctx, CACERT_FILE) || ! SSL_CTX_use_PrivateKey_file(ssl_ctx, PRIVKEY_FILE, SSL_FILETYPE_PEM) )
{
printf("Couldn't read 'pkey' or 'cert' file\n");
return -2;
}
ssl = SSL_new(ssl_ctx);
base = event_base_new();
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
listener = evconnlistener_new_bind(base, listener_cb, ssl, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1, (struct sockaddr *)&addr, sizeof(addr));
if( !listener )
{
printf("Can't create a listener\n");
return -3;
}
event_base_dispatch(base);
evconnlistener_free(listener);
event_base_free(base);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
EVP_cleanup();
return 0;
}
5.3.2 客户端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <signal.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/util.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <event2/bufferevent_ssl.h>
#define IP "127.0.0.1"
#define PORT 11111
void event_callback(struct bufferevent *bev, short events, void *arg)
{
if( events & BEV_EVENT_CONNECTED )
{
printf("Connect to server successfully\n");
return ;
}
else if( events & BEV_EVENT_EOF )
{
printf("Connection closed\n");
}
else if( events & BEV_EVENT_ERROR )
{
printf("Connection error\n");
}
}
void send_data(evutil_socket_t fd, short events, void *arg)
{
struct bufferevent *sslbev = (struct bufferevent *)arg;
const char *buf = "Hello World!";
printf("%s\n", buf);
bufferevent_write(sslbev, buf, 32);
}
int main (int argc, char **argv)
{
struct event_base *base;
struct bufferevent *sslbev;
SSL_CTX *ssl_ctx;
SSL *ssl;
struct event *ev;
struct timeval time;
struct sockaddr_in addr;
int len = sizeof(addr);
SSL_library_init();
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
SSL_load_error_strings();
ssl_ctx = SSL_CTX_new(TLS_client_method());
ssl = SSL_new(ssl_ctx);
base = event_base_new();
memset(&addr, 0, len);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
inet_aton(IP, &addr.sin_addr);
sslbev = bufferevent_openssl_socket_new(base, -1, ssl, BUFFEREVENT_SSL_CONNECTING, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(sslbev, NULL, NULL, event_callback, NULL);
bufferevent_enable(sslbev, EV_READ | EV_WRITE);
bufferevent_socket_connect(sslbev, (struct sockaddr *)&addr, len);
ev = event_new(base, -1, EV_PERSIST, send_data, sslbev);
time.tv_sec = 3;
time.tv_usec = 0;
event_add(ev, &time);
event_base_dispatch(base);
bufferevent_free(sslbev);
event_base_free(base);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
EVP_cleanup();
return 0;
}