在Redis中,tls.c 文件通常用于实现与 Transport Layer Security (TLS) 或其前身 Secure Sockets Layer (SSL) 相关的功能。TLS/SSL是一种用于在计算机网络上加密通信的协议,常用于保护敏感信息的传输,如用户名、密码等。
具体来说,tls.c 文件可能包含以下功能:
- TLS初始化: 在文件中可能有用于初始化TLS/SSL库的函数,设置加密算法、随机数生成等。
- TLS配置: 提供配置TLS/SSL连接的函数,包括证书、私钥、CA证书等的设置。
- TLS连接创建和管理: 实现创建TLS/SSL连接的函数,包括客户端和服务端连接的建立,以及连接的状态管理。
- 事件处理: 处理TLS/SSL连接上的事件,例如数据可读、数据可写等,以便在事件发生时采取相应的操作。
- 错误处理: 处理TLS/SSL连接中的错误,记录日志,提供错误信息等。
- 协议版本支持: 支持多个TLS/SSL协议版本的选择,如TLSv1.2、TLSv1.3等。
这些功能共同协作,使得Redis能够支持通过TLS/SSL进行加密通信,从而提高数据传输的安全性。
tls.c文件说明
#ifdef USE_OPENSSL
/*这些行包含了必要的OpenSSL头文件。ssl.h提供与SSL/TLS相关的函数和结构,err.h处理错误报告,
rand.h包含了OpenSSL中与随机数生成相关的函数。*/
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
在redis的tls.c文件中通过宏定义设置USE_OPENSSL这个变量,检测系统中是否可以使用SSL的相关功能。之后在进行redis需要的tls的函数实现。
void tlsInit(void) {
}
int tlsConfigure(redisTLSContextConfig *ctx_config) {
UNUSED(ctx_config);
return C_OK;
}
connection *connCreateTLS(void) {
return NULL;
}
connection *connCreateAcceptedTLS(int fd, int require_auth) {
UNUSED(fd);
UNUSED(require_auth);
return NULL;
}
int tlsHasPendingData() {
return 0;
}
int tlsProcessPendingData() {
return 0;
}
在文件的结尾定义这些空的函数实现。这样的占位函数可以确保代码在编译时没有错误。如果这些函数被调用,编译器不会报未定义的错误,而是会生成一个空的占位符函数。
SSL使用的配置函数
int tlsConfigure(redisTLSContextConfig *ctx_config) {
/*定义了一个字符数组 errbuf 用于存储错误信息,以及一个指向SSL上下文结构的指针 ctx。*/
char errbuf[256];
SSL_CTX *ctx = NULL;
/*检查TLS配置是否完整。如果某些必需的配置项未设置,记录警告信息并跳转到错误处理。*/
if (!ctx_config->cert_file) {
serverLog(LL_WARNING, "No tls-cert-file configured!");
goto error;
}
if (!ctx_config->key_file) {
serverLog(LL_WARNING, "No tls-key-file configured!");
goto error;
}
if (!ctx_config->ca_cert_file && !ctx_config->ca_cert_dir) {
serverLog(LL_WARNING, "Either tls-ca-cert-file or tls-ca-cert-dir must be configured!");
goto error;
}
/*使用 SSL_CTX_new 函数创建一个新的SSL上下文。此处使用 SSLv23_method 表示支持SSLv2、SSLv3、TLSv1及其更高版本。*/
ctx = SSL_CTX_new(SSLv23_method());
/*设置SSL上下文的选项,禁用了SSLv2和SSLv3协议,启用了SSL_OP_SINGLE_DH_USE选项,并在支持的情况下启用了SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS。*/
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
SSL_CTX_set_options(ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
#endif
/*根据配置启用或禁用SSL会话缓存,并进行相应的设置。*/
if (ctx_config->session_caching) {
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER);
SSL_CTX_sess_set_cache_size(ctx, ctx_config->session_cache_size);
SSL_CTX_set_timeout(ctx, ctx_config->session_cache_timeout);
SSL_CTX_set_session_id_context(ctx, (void *) "redis", 5);
} else {
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
}
//调用parseProtocolsConfig函数解析TLS协议配置,并处理解析错误。
int protocols = parseProtocolsConfig(ctx_config->protocols);
if (protocols == -1) goto error;
//根据解析得到的协议配置,设置SSL上下文的相应选项。
if (!(protocols & REDIS_TLS_PROTO_TLSv1))
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1);
if (!(protocols & REDIS_TLS_PROTO_TLSv1_1))
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1);
#ifdef SSL_OP_NO_TLSv1_2
if (!(protocols & REDIS_TLS_PROTO_TLSv1_2))
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_2);
#endif
#ifdef SSL_OP_NO_TLSv1_3
if (!(protocols & REDIS_TLS_PROTO_TLSv1_3))
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_3);
#endif
#ifdef SSL_OP_NO_COMPRESSION
SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
#endif
#ifdef SSL_OP_NO_CLIENT_RENEGOTIATION
SSL_CTX_set_options(ctx, SSL_OP_NO_CLIENT_RENEGOTIATION);
#endif
if (ctx_config->prefer_server_ciphers)
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
#if defined(SSL_CTX_set_ecdh_auto)
SSL_CTX_set_ecdh_auto(ctx, 1);
#endif
//加载证书、私钥和CA证书,处理加载过程中的错误。
if (SSL_CTX_use_certificate_chain_file(ctx, ctx_config->cert_file) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
serverLog(LL_WARNING, "Failed to load certificate: %s: %s", ctx_config->cert_file, errbuf);
goto error;
}
if (SSL_CTX_use_PrivateKey_file(ctx, ctx_config->key_file, SSL_FILETYPE_PEM) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
serverLog(LL_WARNING, "Failed to load private key: %s: %s", ctx_config->key_file, errbuf);
goto error;
}
if (SSL_CTX_load_verify_locations(ctx, ctx_config->ca_cert_file, ctx_config->ca_cert_dir) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
serverLog(LL_WARNING, "Failed to configure CA certificate(s) file/directory: %s", errbuf);
goto error;
}
//如果配置了DH参数文件,尝试加载并设置DH参数。
if (ctx_config->dh_params_file) {
FILE *dhfile = fopen(ctx_config->dh_params_file, "r");
DH *dh = NULL;
if (!dhfile) {
serverLog(LL_WARNING, "Failed to load %s: %s", ctx_config->dh_params_file, strerror(errno));
goto error;
}
dh = PEM_read_DHparams(dhfile, NULL, NULL, NULL);
fclose(dhfile);
if (!dh) {
serverLog(LL_WARNING, "%s: failed to read DH params.", ctx_config->dh_params_file);
goto error;
}
if (SSL_CTX_set_tmp_dh(ctx, dh) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
serverLog(LL_WARNING, "Failed to load DH params file: %s: %s", ctx_config->dh_params_file, errbuf);
DH_free(dh);
goto error;
}
DH_free(dh);
}
//配置密码套件和密码组,处理配置错误。
if (ctx_config->ciphers && !SSL_CTX_set_cipher_list(ctx, ctx_config->ciphers)) {
serverLog(LL_WARNING, "Failed to configure ciphers: %s", ctx_config->ciphers);
goto error;
}
#ifdef TLS1_3_VERSION
if (ctx_config->ciphersuites && !SSL_CTX_set_ciphersuites(ctx, ctx_config->ciphersuites)) {
serverLog(LL_WARNING, "Failed to configure ciphersuites: %s", ctx_config->ciphersuites);
goto error;
}
#endif
/*释放之前的SSL上下文,并将新创建的SSL上下文保存在全局变量 redis_tls_ctx 中。*/
SSL_CTX_free(redis_tls_ctx);
redis_tls_ctx = ctx;
return C_OK;
error:
if (ctx) SSL_CTX_free(ctx);
return C_ERR;
}
这段函数的作用是配置 Redis 的 TLS 上下文,它通过传入的 redisTLSContextConfig 结构体指针 ctx_config 中的配置信息来初始化 TLS 上下文。TLS 上下文包含了一系列 TLS 连接所需的配置,例如证书、私钥、CA 证书、协议版本等。
SSL使用的连接方式
ConnectionType CT_TLS = {
.ae_handler = tlsEventHandler,
.accept = connTLSAccept,
.connect = connTLSConnect,
.blocking_connect = connTLSBlockingConnect,
.read = connTLSRead,
.write = connTLSWrite,
.close = connTLSClose,
.set_write_handler = connTLSSetWriteHandler,
.set_read_handler = connTLSSetReadHandler,
.get_last_error = connTLSGetLastError,
.sync_write = connTLSSyncWrite,
.sync_read = connTLSSyncRead,
.sync_readline = connTLSSyncReadLine,
};
这段代码定义了一个名为 CT_TLS 的 ConnectionType 结构体变量,其中 ConnectionType 可能是Redis中用于管理不同类型连接的数据结构。这里的 CT_TLS 表示 TLS 连接类型,用于描述 TLS 连接的处理方式和相关函数。
具体来说,CT_TLS 的各个成员含义如下:
- ae_handler: 事件处理器,指定处理TLS连接事件的函数,这里是 tlsEventHandler。
- accept: 用于处理 TLS 连接的 accept 操作的函数。
- connect: 用于处理 TLS 连接的 connect 操作的函数。
- blocking_connect: 用于处理阻塞的 TLS 连接的函数。
- read: 用于读取数据的函数。
- write: 用于写入数据的函数。
- close: 关闭连接的函数。
- set_write_handler 和 set_read_handler: 分别用于设置写事件和读事件的处理函数。
- get_last_error: 获取连接的最后一个错误的函数。
- sync_write 和 sync_read: 同步写和读的函数。
- sync_readline: 同步读取一行数据的函数。
这样的定义使得 Redis 能够对 TLS 连接进行管理,通过 CT_TLS 提供的函数来处理TLS连接的各种操作和事件。这种抽象和模块化的设计使得 Redis 能够灵活支持不同类型的连接,并且能够轻松地扩展和定制。在该文件中存在对这些函数的定义。
/*定义了一个枚举类型 WantIOType,表示TLS连接可能期望的I/O类型。
它有两个枚举值:WANT_READ 表示期望进行读取操作,WANT_WRITE 表示期望进行写入操作。*/
typedef enum {
WANT_READ = 1,
WANT_WRITE
} WantIOType;
#define TLS_CONN_FLAG_READ_WANT_WRITE (1<<0)//表示读操作期间发生了写入操作的阻塞。
#define TLS_CONN_FLAG_WRITE_WANT_READ (1<<1)//表示写入操作期间发生了读取操作的阻塞。
#define TLS_CONN_FLAG_FD_SET (1<<2)//表示文件描述符已设置(即连接处于活动状态)。
typedef struct tls_connection {
connection c;//继承自某个 connection 类型的结构体,表示通用的连接信息。
int flags;//表示连接的一些状态标志,使用上述宏定义的标志位。
SSL *ssl;//表示与连接相关联的SSL对象。
char *ssl_error;//表示SSL操作过程中的错误信息。
listNode *pending_list_node;//用于存储连接中已经从套接字读取但尚未传递给读取器的挂起数据的链表节点。
} tls_connection;
通过这些定义来描述SSL连接的当前状态。
在这个tls.s文件中引入了redis处理连接的connhelpers.h的文件。通过对文件中的函数