ssl/tls是什么?是怎么工作的?

1
https://www.openssl.org/docs/man1.1.1/man3/index.html

最近在思考一个关于邮箱服务器的问题,其中涉及到了SSL/TLS加密传输,于是想研究一下什么,到底是怎么实现加密的,通过查找各方面的资料,发现这个涉及到的东西还蛮多的,可以单独写一篇文章整理一下自己的理解。

SSL/TLS是什么?

SSL(安全套接字层)是一种标准安全协议,用于在在线通信中建立Web服务器和浏览器之间的加密链接。

那TLS又是什么?Transport Layer Security (TLS)是SSL协议(Secure Sockets Layer)的升级版,TLS 1.0通常被标示为SSL 3.1,TLS 1.1为SSL 3.2,TLS 1.2为SSL 3.3。现在习惯将这个两个组合在一起称为SSL/TLS,只要知道它是一种用于加密的安全协议就好了。

当网页期望用户提交机密数据(包括个人信息,密码或信用卡详细信息)时,网页应使用加密,这个时候web服务器就应该使用HTTPS协议来传输数据,它其实就是HTTP和SSL/TLS结合实现的;同样的还有SMTPS,它是加密的简单邮件通信协议,这样在传输邮件的时候就不是明文传输了,一般我们在设置邮箱服务器的时候可以选择是否勾选SSL/TLS的,如果没有勾选的话邮件就是明文传输了。

SSL/TLS有什么作用?

这里参考了网上的一些观点:
不使用SSL/TLS的HTTP通信,就是不加密的通信。所有信息明文传播,带来了三大风险。

  • 窃听风险(eavesdropping):第三方可以获知通信内容。
  • 篡改风险(tampering):第三方可以修改通信内容。
  • 冒充风险(pretending):第三方可以冒充他人身份参与通信。

SSL/TLS协议是为了解决这三大风险而设计的,希望达到

  • 所有信息都是加密传播,第三方无法窃听。
  • 具有校验机制,一旦被篡改,通信双方会立刻发现。
  • 配备身份证书,防止身份被冒充。

SSL证书

先说明一下:SSL/TLS协议的基本思路是采用公钥加密法,也就是说,客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。

在讲SSL/TLS的工作流程之前,先要说明一下SSL证书这个东西,来思考一个问题:
基本思路里面的公钥加密法,如何保证公钥不被篡改?答案是:将公钥放在数字证书中。只要证书是可信的,公钥就是可信的。

那SSL证书是什么?怎么保证它是可信任的?
SSL 证书就是遵守 SSL协议,由受信任的数字证书颁发机构CA,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能。
怎么才能申请到SSL证书?或者说影响该证书申请的因素有哪些?
影响证书的因素:申请中提到的企业/公司是否有合法身份以及申请人是否控制证书​​中提到的域名。

申请到证书的步骤是怎么样的?

  1. 制作CSR文件
    CSR就是Certificate Signing Request证书请求文件。这个文件是由申请人制作,在制作的同时,系统会产生2个密钥,一个是公钥就是这个CSR文件,另外一个是私钥,存放在服务器上。要制作CSR文件,申请人可以参考WEB SERVER的文档,一般APACHE等,使用OPENSSL命令行来生成KEY+CSR2个文件。
  2. CA认证
    域名认证,一般通过对管理员邮箱认证的方式,这种方式认证速度快,但是签发的证书中没有企业的名称;
    企业文档认证,需要提供企业的营业执照。一般需要3-5个工作日。 也有需要同时认证以上2种方式的证书,叫EV证书,这种证书可以使IE7以上的浏览器地址栏变成绿色,所以认证也最严格。
  3. 证书的安装
    在收到CA的证书后,可以将证书部署上服务器,一般APACHE文件直接将KEY+CER复制到文件上,然后修改HTTPD.CONF文件。

注意:证书分为单域证书、通配符证书、多域证书及扩展验证证书,申请证书的时候要看是申请那一类了。

证书在什么情况下被使用?是怎么使用的?

  1. 我们在点击web站点的时候,比如输入https://www.domain.com ,进行dns解析后web服务器进行响应,web服务器自动传送https://www.domain.com 网站的数字证书给用户,上文说到了,证书是安装在web服务器里面的,证书里面含有公钥,所以这里相当于服务器把公钥传递给了客户端,当然服务器那里还有自己的私钥,具体过程如下图。
  2. 客户端是使用浏览器进行操作的,不同版本的浏览器自动产生40位或128位的会话密钥,用于对交易的信息进行加密,也就是说客户向服务器索要公钥后还要与服务器协商生成一个“会话秘钥”。
    如下图,第三步获取到crt证书后,需要检验证书是否有效,如果无效则会显示警告信息,有效则生成一个随机数,即会话密钥,这个会话密钥再使用crt里面的公钥加密后传输给web服务器,服务器使用自己的私钥进行解密,获取浏览器生成的随机“会话密钥”,现在客户端服务器都知道这个“会话密钥”了,后续通信都用这个会话密钥进行加密通信了。

HTTPS 证书校验的流程是确保客户端和服务器之间的通信安全的关键步骤。它通过验证服务器的证书来确认服务器的身份,并建立一个加密的通信通道。以下是 HTTPS 证书校验的详细流程:

1. 客户端发起 HTTPS 请求

当客户端(如浏览器)发起一个 HTTPS 请求时,首先会与服务器建立 TCP 连接,然后启动 TLS(传输层安全)握手过程。

2. TLS 握手开始

SSL_set_tlsext_host_name 是 OpenSSL 库中的一个函数,用于设置 Server Name Indication (SNI) 扩展。SNI 是一个 TLS 扩展,它允许客户端在握手过程中指明其所期望访问的服务器主机名。

背景:在 TLS 连接的初始握手阶段,客户端和服务器交换信息以协商加密算法和密钥。然而,最初的 TLS 规范没有支持在握手时指定服务器主机名,因此如果一台服务器托管多个网站(例如通过虚拟主机),它就无法根据客户端请求选择正确的证书。

SNI 解决了这个问题,它允许客户端在握手的 Client Hello 消息中包含目标主机名。服务器根据这个主机名选择适当的证书进行握手。
客户端和服务器开始 TLS 握手过程,这一过程包括多个步骤:

  • 客户端问候(Client Hello):客户端发送一个问候消息,包含客户端支持的 TLS 版本、加密算法列表、随机数等信息。

3. 服务器响应

服务器响应客户端的问候:

  • 服务器问候(Server Hello):服务器选择 TLS 版本和加密算法,并发送给客户端,同时也包含一个随机数。
  • 服务器证书(Server Certificate):服务器发送其数字证书给客户端。证书包含服务器的公钥以及由证书颁发机构(CA)签名的服务器身份信息。
  • 服务器完成(Server Hello Done):服务器表明其问候消息结束。

4. 客户端验证服务器证书

X509_get_subject_name 是 OpenSSL 库中用于获取 X.509 证书的主体名称(subject name)的函数。主体名称是证书持有者的标识信息,它通常包含诸如国家名称、组织名称、通用名称(例如域名)等信息。
客户端接收到服务器的证书后(SSL_get_peer_certificate获得证书、X509_get_subject_name解析域名),会进行以下验证步骤:

  • 检查证书的有效性
    • 证书链验证:客户端检查服务器证书的链条,从服务器证书到中间证书,再到根证书,确保每一个证书都被上一级证书签名并且可信。
    • 证书颁发机构(CA):验证签名是否由受信任的CA签发。客户端通常有一个受信任的根证书列表。
    • 证书有效期:检查证书是否在有效期内。
    • 域名匹配:验证证书中的域名是否与客户端请求的域名匹配。

5. 客户端生成会话密钥

  • 生成预主密钥(Pre-master secret):客户端生成一个预主密钥,并使用服务器的公钥加密。
  • 发送加密的预主密钥:客户端将加密后的预主密钥发送给服务器。

6. 服务器解密预主密钥

  • 解密预主密钥:服务器使用其私钥解密预主密钥。
  • 生成会话密钥:客户端和服务器都使用相同的预主密钥和之前交换的随机数生成会话密钥。

7. 完成握手

  • 客户端完成握手(Client Finished):客户端发送一个“握手完成”消息,表明它的握手部分已经完成。
  • 服务器完成握手(Server Finished):服务器也发送一个“握手完成”消息,表明它的握手部分已经完成。

8. 加密通信

握手完成后,客户端和服务器使用生成的会话密钥进行对称加密通信。此时,通信内容是加密的,确保数据在传输过程中的机密性和完整性。

9. 连接关闭

当通信结束后,客户端和服务器通过发送“关闭通知”消息来关闭 TLS 连接。然后,TCP 连接也会关闭。

图解流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Client                                Server
| |
| ------ Client Hello ---------------->|
| |
|<----- Server Hello ------------------|
|<----- Server Certificate ------------|
| |
|<----- Server Hello Done -------------|
| |
|------ Client Key Exchange ---------->|
|------ Change Cipher Spec ------------>|
|------ Client Finished --------------->|
| |
|<----- Change Cipher Spec ------------|
|<----- Server Finished ---------------|
| |
| (Secure Communication) |
| |
|------ Application Data ------------->|
|<----- Application Data --------------|
| |
|------ Close Notify ----------------->|
|<----- Close Notify ------------------|
| |

关键点总结

  • 证书校验:客户端验证服务器的证书,以确保通信的对方确实是声称的服务器。
  • 会话密钥生成:使用非对称加密交换密钥,再使用对称加密进行数据传输,以提高性能。
  • 加密通信:所有的应用数据都通过对称加密传输,确保机密性和数据完整性。

这一流程确保了客户端和服务器之间的通信安全,有效防止了中间人攻击和数据泄露。

SSL是怎么工作的?

简单概括就是:

  1. 客户端向服务器端索要并验证公钥。
  2. 双方协商生成”对话密钥”。
  3. 双方采用”对话密钥”进行加密通信。
    具体是怎么协商生成“会话密钥”的,上文提到了,这里还有一个疑问,为什么不直接使用crt证书里面的公钥进行加密,再使用服务器里面的私钥进行解密呢?更何况服务器里面的私钥也可以进行加密,crt里面的证书再进行解密即可。
    原因为:每一次对话(session),客户端和服务器端都生成一个”会话密钥”(session key),用它来加密信息。由于”对话密钥”是对称加密,所以运算速度非常快,而服务器公钥加密是非对称加密,比较耗时间,所以证书里面的公钥只用于加密”会话密钥”本身,这样就减少了加密运算的消耗时间。

那么会话密钥和公钥有什么区别呢?

  1. 会话加密是对称加密,服务器和客户端协商后生产一个会话密钥,所以服务器和客户端是共享一个相同的密钥的,当然不是服务器和所有客户端共享一个相同的密钥,而是每个客户端都有自己的密钥,比较这个会话密钥是客户端(浏览器)随机生成的,所以服务器需要维护多个密钥。
  2. 公钥是采用的非对称加密,服务器把证书(公钥)下发给每个用户正在使用的客户端(浏览器),所以是客户端共享公钥,服务器只掌控私钥,服务端与客户端密钥是一个1对多的关系,客户端发送的加密信息只能服务端解密,安全级别也更高,但是由于非对称加密太慢了,才采用了不同客户端不同密钥的“会话密钥”来解决这个问题。

具体TLS/SSL里面涉及到的非对称加密和对称加密的区别以及算法如下图所示:

总的来说,客户端(浏览器)与web服务器进入加密通信,就完全是使用普通的HTTP协议,只不过用”会话密钥”加密内容,会话密钥即图中的随机数。

编码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
sslCtx = SSL_CTX_new(SSLv23_server_method());
SSL_CTX_set_tlsext_servername_callback(m_sslCtx, SslServerNameCallback);
SSL_CTX_set_timeout(sslCtx, 600);

SSL *ssl = SSL_new(m_sslCtx);

int SslServerNameCallback(SSL *ssl, int *ad, void *arg)
{
if (NULL == ssl)
return SSL_TLSEXT_ERR_NOACK;

const char *tmpName = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
string servername = tmpName ? tmpName : "";
CAutoLock lock(serverSSlCtxMapLock); // 从sslSanCtxMap查找
SSL_CTX *ctx = FindSslCtx(servername.c_str(), false);
if (ctx)
{
SSL_set_SSL_CTX(ssl, ctx);
SSL_set_verify(ssl, SSL_CTX_get_verify_mode(ctx), SSL_CTX_get_verify_callback(ctx));
SSL_set_verify_depth(ssl, SSL_CTX_get_verify_depth(ctx));
SSL_set_options(ssl, SSL_CTX_get_options(ctx));
#ifndef OPENSSL_NO_NEXTPROTONEG
SSL_CTX_set_next_protos_advertised_cb(ctx, NextProtoCb, NULL);
#endif /* !OPENSSL_NO_NEXTPROTONEG */

#if OPENSSL_VERSION_NUMBER >= 0x10002000L
SSL_CTX_set_alpn_select_cb(ctx, AlpnSelectProtoCb, NULL);
#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
}
return SSL_TLSEXT_ERR_OK;
}

结束语

对SSL/TLS涉及到的底层原理做了一个大概的说明,对学习过程中遇到的一些问题做了理解和解答,把一些问题理清楚后,对概念的理解就更加深刻了,比如明白了某个技术的由来背景,为什么会出现这个东西,问题的初衷是什么?怎么实现的?有哪些应用场景,如果把这些都调查清楚,那无论是对这个技术的使用还是新技术的创新都是有好处了,比只会用这个东西来做一些事情局限于表面工程要好得多,之前听大佬讲,有的人工作了6,7年,比别人刚毕业处理工作1,2年写出来的文章还要屎,听后感觉都可怕和悲哀,好好沉淀下去,打好基础,哪怕是寒冬来了也就不慌了,后续有时间再研究一下SSL证书申请的具体事宜。

扩展

是的,HTTPS 客户端在校验证书时,需要先加载受信任的 CA(Certificate Authority)证书。这是因为客户端需要验证服务器证书的签名,而服务器证书通常是由 CA 签名的。通过加载受信任的 CA 证书,客户端可以验证服务器证书的有效性和真实性。

下面是使用 OpenSSLBoost.Asio 分别实现的示例代码,展示如何加载 CA 证书并进行服务器证书的验证。

使用 OpenSSL

OpenSSL 中,你需要创建一个 SSL 上下文,并加载 CA 证书文件或目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <iostream>
#include <openssl/ssl.h>
#include <openssl/err.h>

void init_openssl() {
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
}

void cleanup_openssl() {
EVP_cleanup();
}

SSL_CTX* create_context() {
const SSL_METHOD* method = TLS_client_method();
SSL_CTX* ctx = SSL_CTX_new(method);
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
return ctx;
}

void configure_context(SSL_CTX* ctx) {
// 设置验证路径
if (SSL_CTX_load_verify_locations(ctx, "path/to/ca-bundle.crt", NULL) != 1) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
// 或者指定一个包含多个 CA 证书的目录
// if (SSL_CTX_load_verify_locations(ctx, NULL, "path/to/ca-directory") != 1) {
// ERR_print_errors_fp(stderr);
// exit(EXIT_FAILURE);
// }

// 启用证书验证
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
}

int main(int argc, char** argv) {
const char* hostname = "example.com";
const char* port = "443";

init_openssl();
SSL_CTX* ctx = create_context();
configure_context(ctx);

SSL* ssl = SSL_new(ctx);
if (!ssl) {
std::cerr << "Unable to create SSL object\n";
ERR_print_errors_fp(stderr);
SSL_CTX_free(ctx);
return EXIT_FAILURE;
}

BIO* bio = BIO_new_ssl_connect(ctx);
if (!bio) {
std::cerr << "Unable to create BIO\n";
ERR_print_errors_fp(stderr);
SSL_free(ssl);
SSL_CTX_free(ctx);
return EXIT_FAILURE;
}

std::string host_and_port = std::string(hostname) + ":" + port;
BIO_set_conn_hostname(bio, host_and_port.c_str());

if (BIO_do_connect(bio) <= 0) {
std::cerr << "Unable to connect\n";
ERR_print_errors_fp(stderr);
BIO_free_all(bio);
SSL_CTX_free(ctx);
return EXIT_FAILURE;
}

if (BIO_do_handshake(bio) <= 0) {
std::cerr << "SSL handshake failed\n";
ERR_print_errors_fp(stderr);
BIO_free_all(bio);
SSL_CTX_free(ctx);
return EXIT_FAILURE;
}

std::cout << "Connected with " << SSL_get_cipher(ssl) << " encryption\n";

BIO_free_all(bio);
SSL_CTX_free(ctx);
cleanup_openssl();

return EXIT_SUCCESS;
}

使用 Boost.Asio

Boost.Asio 中,你也需要加载 CA 证书并配置 SSL 上下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <iostream>

void load_root_certificates(boost::asio::ssl::context& ctx) {
// 加载 CA 证书文件
ctx.load_verify_file("path/to/ca-bundle.crt");
// 或者指定一个包含多个 CA 证书的目录
// ctx.add_verify_path("path/to/ca-directory");
}

int main() {
try {
boost::asio::io_context io_context;
boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12_client);

load_root_certificates(ctx);

// 启用证书验证
ctx.set_verify_mode(boost::asio::ssl::verify_peer);

boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket(io_context, ctx);

boost::asio::ip::tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve("example.com", "https");

boost::asio::connect(ssl_socket.lowest_layer(), endpoints);

ssl_socket.handshake(boost::asio::ssl::stream_base::client);

std::cout << "Connected with " << SSL_get_cipher(ssl_socket.native_handle()) << " encryption\n";
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}

return 0;
}

代码解释

  1. 加载 CA 证书

    • 在 OpenSSL 中使用 SSL_CTX_load_verify_locations 函数加载 CA 证书文件或目录。
    • 在 Boost.Asio 中使用 load_verify_fileadd_verify_path 方法加载 CA 证书文件或目录。
  2. 启用证书验证

    • 在 OpenSSL 中使用 SSL_CTX_set_verify 函数启用证书验证。
    • 在 Boost.Asio 中使用 set_verify_mode 方法启用证书验证。

注意事项

  • 确保 CA 证书文件路径正确。
  • 在生产环境中,始终启用并验证服务器证书,以确保通信安全。

通过加载 CA 证书并启用证书验证,客户端可以确保连接的服务器是可信的,从而防止中间人攻击和其他安全威胁。

当浏览器进行 HTTPS 连接时,它会使用预装的根证书列表(Root Certificate Store)来验证服务器端的证书。这个根证书列表中包含了一些公认的根证书颁发机构(CA),例如 Symantec、DigiCert、Let’s Encrypt 等,这些机构的证书是由操作系统或者浏览器厂商预先安装的。

当浏览器收到服务器返回的证书时,它会检查这个证书是否是由预装的根证书列表中的任何一个机构签发的。如果是,那么浏览器就会认为这个证书是可信的。如果证书的颁发机构不在根证书列表中,或者证书本身已经过期、被吊销等,浏览器就会给出相应的警告或错误。

因此,虽然浏览器在验证证书时不需要用户手动加载 .crt 文件,但它会依赖于操作系统或浏览器厂商提供的预装的根证书列表来进行验证。

####只校验域名
在 Boost.Asio 中使用的 SSL/TLS 功能并没有直接提供只开启域名校验而不校验证书的选项。通常情况下,如果要进行域名校验,就需要进行证书的验证。

不过,你可以实现一个自定义的证书验证回调函数,仅在回调函数中进行域名校验,而不对证书进行其他的验证。这样可以实现只开启域名校验而不校验证书的效果。

以下是一个示例代码,展示了如何在 Boost.Asio 中实现这样的自定义证书验证回调函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <iostream>

bool verify_certificate(bool preverified, boost::asio::ssl::verify_context& ctx) {
// 仅在域名匹配时返回 true,否则返回 false
return preverified;
}

int main() {
try {
boost::asio::io_context io_context;
boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12_client);

// 设置 SSL 上下文
ctx.set_default_verify_paths();

boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket(io_context, ctx);

// 设置证书验证回调函数
ssl_socket.set_verify_callback(verify_certificate);

boost::asio::ip::tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve("example.com", "https");

boost::asio::connect(ssl_socket.lowest_layer(), endpoints);

ssl_socket.handshake(boost::asio::ssl::stream_base::client);

std::cout << "Connected with " << SSL_get_cipher(ssl_socket.native_handle()) << " encryption\n";
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}

return 0;
}

在这个示例中,verify_certificate 函数只是简单地返回 preverified 的值,这意味着它仅在证书的根证书和日期验证通过时才返回 true,而不进行其他的额外验证。你可以在这个函数中添加逻辑以确保主机名的匹配,以实现只开启域名校验而不校验证书的效果。

openssl编译

1
2
3
4
5
6
# 静态库
$ ./config no-shared no-err no-hw no-zlib no-sse2 threads no-async no-asm --prefix=$(pwd)/install/install_dir --cross-compile-prefix=x86_64-linux-musl-
# 动态库
# linux-armv4 linux-x86_64
$ ./Configure linux-x86_64 no-asm -fPIC --prefix=$(pwd)/install/install_dir --cross-compile-prefix=x86_64-linux-musl-
$ make depend && make install

主要参数说明如下:
no-shared: 生成静态连接库。
no-err:不进行报错
threads:支持多线程
no-asm: 在交叉编译过程中不使用汇编代码代码加速编译过程;
no-async: 交叉编译工具链没有提供GNU C的ucontext库
-prefix=: 安装路径
-cross-compile-prefix=: 交叉编译工具

如果编译提示错误:x86_64-linux-musl-gcc: error: unrecognized command line option ‘-m64’
修复方法:打开Makefile, 删除里面所有的-m64和-m32编译选项。

Boost.Asio 默认不会自动复用 SSL 会话。你需要显式地配置 SSL 会话缓存,并实现会话存储和检索逻辑,才能实现 SSL 会话的复用。这涉及在客户端和服务器端配置相应的 OpenSSL 设置。

客户端配置 SSL 会话复用

客户端需要启用 SSL 会话缓存模式,并在握手后存储会话,在下次连接时复用该会话。

以下是一个客户端示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <iostream>

int main() {
try {
boost::asio::io_context io_context;
boost::asio::ssl::context ssl_context(boost::asio::ssl::context::sslv23);

ssl_context.set_default_verify_paths();
ssl_context.set_verify_mode(boost::asio::ssl::verify_peer);

// 启用客户端会话缓存
SSL_CTX_set_session_cache_mode(ssl_context.native_handle(), SSL_SESS_CACHE_CLIENT);

boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_stream(io_context, ssl_context);

SSL_SESSION* session = nullptr;

// 解析并连接服务器
boost::asio::ip::tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve("www.example.com", "https");
boost::asio::connect(ssl_stream.lowest_layer(), endpoints);

if (session) {
// 复用先前存储的会话
SSL_set_session(ssl_stream.native_handle(), session);
}

// 执行 SSL 握手
ssl_stream.handshake(boost::asio::ssl::stream_base::client);

// 存储当前会话
session = SSL_get1_session(ssl_stream.native_handle());

// 进行数据传输...

// 关闭连接
ssl_stream.shutdown();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}

return 0;
}

服务器配置 SSL 会话复用

服务器需要启用 SSL 会话缓存模式,并设置回调函数来管理 SSL 会话的存储和检索。

以下是一个服务器示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <iostream>

using namespace boost::asio;

void session_new_cb(SSL* ssl, SSL_SESSION* session) {
std::cout << "New session created." << std::endl;
// 存储会话逻辑(如存储在内存或数据库中)
}

SSL_SESSION* get_session_cb(SSL* ssl, const unsigned char* id, int id_len, int* do_copy) {
std::cout << "Session requested." << std::endl;
// 检索先前存储的会话
return nullptr; // 返回存储的会话(如从内存或数据库中检索)
}

int main() {
try {
io_context io_context;
ssl::context ssl_context(ssl::context::sslv23);

// 配置证书和私钥
ssl_context.use_certificate_file("server.crt", ssl::context::pem);
ssl_context.use_private_key_file("server.key", ssl::context::pem);

// 启用服务器会话缓存
SSL_CTX_set_session_cache_mode(ssl_context.native_handle(), SSL_SESS_CACHE_SERVER);

// 设置回调函数
SSL_CTX_sess_set_new_cb(ssl_context.native_handle(), session_new_cb);
SSL_CTX_sess_set_get_cb(ssl_context.native_handle(), get_session_cb);

ip::tcp::acceptor acceptor(io_context, ip::tcp::endpoint(ip::tcp::v4(), 4433));
for (;;) {
ip::tcp::socket socket(io_context);
acceptor.accept(socket);

ssl::stream<ip::tcp::socket> ssl_stream(std::move(socket), ssl_context);
ssl_stream.handshake(ssl::stream_base::server);

// 进行数据传输...

ssl_stream.shutdown();
}
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}

return 0;
}

关键点总结

  • 客户端

    • 启用会话缓存:SSL_CTX_set_session_cache_mode(ssl_context.native_handle(), SSL_SESS_CACHE_CLIENT);
    • 存储会话:SSL_SESSION* session = SSL_get1_session(ssl_stream.native_handle());
    • 复用会话:SSL_set_session(ssl_stream.native_handle(), session);
  • 服务器

    • 启用会话缓存:SSL_CTX_set_session_cache_mode(ssl_context.native_handle(), SSL_SESS_CACHE_SERVER);
    • 设置会话回调函数:SSL_CTX_sess_set_new_cbSSL_CTX_sess_set_get_cb

通过这种配置,可以启用和管理 SSL 会话缓存,从而实现 SSL 会话的复用,提升连接性能。

nephen wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!