peap中的tls
初始化
| 回调函数 | 作用对象 | 触发阶段 | 用途 | 常见场景 |
|---|---|---|---|---|
SSL_set_verify / SSL_CTX_set_verify |
SSL/SSL_CTX |
证书验证阶段 | 设置是否验证对端证书及验证模式(如 SSL_VERIFY_PEER) |
强制验证客户端/服务器证书 |
SSL_CTX_set_cert_verify_callback |
SSL_CTX |
证书验证阶段 | 完全自定义证书验证逻辑(覆盖默认验证流程) | 自定义证书吊销检查、复杂策略 |
SSL_CTX_set_info_callback |
SSL_CTX |
连接状态变化时 | 监听 SSL 状态变化(如握手开始、完成、错误) | 调试握手流程、记录连接事件 |
SSL_set_info_callback |
SSL |
连接状态变化时 | 同上,但针对单个 SSL 连接 | 单连接的详细状态跟踪 |
SSL_CTX_set_msg_callback |
SSL_CTX |
发送/接收 TLS 记录层消息 | 监听原始 TLS 消息(如 ClientHello、ServerHello)的字节流 |
调试 TLS 协议流量、记录明文消息 |
SSL_set_msg_callback |
SSL |
发送/接收 TLS 记录层消息 | 同上,但针对单个 SSL 连接 | 单连接的 TLS 消息分析 |
SSL_CTX_set_client_hello_cb |
SSL_CTX |
客户端发送 ClientHello |
解析或修改 ClientHello 内容(如 SNI、扩展) |
动态调整密码套件、拦截特定客户端信息 |
SSL_CTX_set_alpn_select_cb |
SSL_CTX |
客户端发送 ClientHello |
根据客户端提议的 ALPN 协议列表选择服务端协议(如 HTTP/2、HTTP/1.1) | 多协议服务端支持 |
SSL_CTX_set_cert_cb |
SSL_CTX |
服务端需要证书时 | 动态加载服务端证书和私钥(如基于 SNI 选择不同证书) | 多域名服务器、证书热更新 |
2. 核心区别详解
2.1 SSL_CTX_set_info_callback vs SSL_CTX_set_msg_callback
-
SSL_CTX_set_info_callback-
触发时机:SSL 状态机变化(如握手开始、握手完成、读写操作)。
-
数据内容:高层事件(如
SSL_CB_HANDSHAKE_START、SSL_CB_ACCEPT_LOOP)。 -
用途:监控连接生命周期,记录日志或统计信息。
void info_callback(const SSL *ssl, int type, int val) { if (type & SSL_CB_HANDSHAKE_START) { printf("Handshake started\n"); } else if (type & SSL_CB_HANDSHAKE_DONE) { printf("Handshake completed\n"); } } SSL_CTX_set_info_callback(ctx, info_callback); -
-
SSL_CTX_set_msg_callback-
触发时机:TLS 记录层消息发送/接收时(如
SSL3_RT_HANDSHAKE类型的消息)。 -
数据内容:原始消息字节流(包括协议头、消息体)。
-
用途:调试协议细节,捕获完整的 TLS 报文。
void msg_callback(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg) { const char *direction = write_p ? "Sent" : "Received"; printf("%s TLS message: type=%d, length=%zu\n", direction, content_type, len); } SSL_CTX_set_msg_callback(ctx, msg_callback); -
2.2 SSL_CTX_set_cert_verify_callback vs SSL_CTX_set_cert_cb
-
SSL_CTX_set_cert_verify_callback-
触发阶段:证书验证阶段(客户端验证服务器证书,或服务器验证客户端证书)。
-
用途:自定义证书验证逻辑(如忽略特定错误、添加 OCSP 检查)。
int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { if (!preverify_ok) { // 可在此添加自定义错误处理 fprintf(stderr, "Certificate verification failed\n"); } return preverify_ok; // 返回 1 接受证书,0 拒绝 } SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback); -
-
SSL_CTX_set_cert_cb-
触发阶段:服务端需要发送证书时(响应客户端请求)。
-
用途:动态加载服务端证书(如根据 SNI 域名选择证书)。
int cert_cb(SSL *ssl, void *arg) { const char *sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (sni && strcmp(sni, "example.com") == 0) { SSL_use_certificate(ssl, load_cert("example.com.crt")); SSL_use_PrivateKey(ssl, load_key("example.com.key")); } return 1; // 成功返回 1,失败返回 0 } SSL_CTX_set_cert_cb(ctx, cert_cb, NULL); -
2.3 SSL_CTX_set_client_hello_cb vs SSL_CTX_set_alpn_select_cb
-
SSL_CTX_set_client_hello_cb-
触发时机:服务端收到
ClientHello后,解析前。 -
用途:拦截或修改
ClientHello中的扩展(如强制特定密码套件)。
int client_hello_cb(SSL *ssl, int *al, void *arg) { const unsigned char *ext_data; size_t ext_len; // 获取 SNI 扩展 if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &ext_data, &ext_len)) { printf("Client requested SNI\n"); } return SSL_CLIENT_HELLO_SUCCESS; } SSL_CTX_set_client_hello_cb(ctx, client_hello_cb, NULL); -
-
SSL_CTX_set_alpn_select_cb-
触发时机:服务端处理
ClientHello中的 ALPN 扩展时。 -
用途:协商应用层协议(如选择 HTTP/2)。
int alpn_select_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { // 客户端支持的协议列表在 `in` 中,服务端选择后设置到 `out` if (SSL_select_next_proto((unsigned char **)out, outlen, in, inlen, "\x02h2", 2) == OPENSSL_NPN_NEGOTIATED) { return SSL_TLSEXT_ERR_OK; } return SSL_TLSEXT_ERR_NOACK; } SSL_CTX_set_alpn_select_cb(ctx, alpn_select_cb, NULL); -
3. 使用场景总结
-
调试与日志:
-
SSL_CTX_set_msg_callback:捕获原始 TLS 消息(如 Wireshark 类工具)。 -
SSL_CTX_set_info_callback:跟踪握手状态变化。
-
-
证书管理:
-
SSL_CTX_set_cert_cb:动态加载证书(多域名服务器)。 -
SSL_CTX_set_cert_verify_callback:自定义证书验证(如 OCSP Stapling)。
-
-
协议扩展:
-
SSL_CTX_set_client_hello_cb:拦截/修改ClientHello。 -
SSL_CTX_set_alpn_select_cb:ALPN 协议协商。
-
-
安全控制:
-
SSL_CTX_set_verify:强制证书验证。
-
4. 关键注意事项
-
作用域:
-
SSL_CTX级回调影响所有关联的SSL连接。 -
SSL级回调仅影响单个连接。
-
-
性能影响:
-
避免在回调中执行阻塞操作(如网络请求),否则会延迟 TLS 握手。
-
-
兼容性:
-
自定义 TLS 扩展(如修改
ClientHello)可能导致与标准客户端不兼容。
-
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/rsa.h>
#include <openssl/x509_vfy.h>
#include <unistd.h>
#include <arpa/inet.h>
struct tls_context {
void (*event_cb)(void *ctx, enum tls_event ev,
union tls_event_data *data);
void *cb_ctx;
int cert_in_cb;
char *ocsp_stapling_response;
};
struct tls_data {
SSL_CTX *ssl;
unsigned int tls_session_lifetime;
int check_crl;
int check_crl_strict;
char *ca_cert;
unsigned int crl_reload_interval;
struct os_reltime crl_last_reload;
char *check_cert_subject;
};
struct tls_connection {
struct tls_context *context;
struct tls_data *data;
SSL_CTX *ssl_ctx;
SSL *ssl;
BIO *ssl_in, *ssl_out;
char *subject_match, *altsubject_match, *suffix_match, *domain_match;
char *check_cert_subject;
int read_alerts, write_alerts, failed;
tls_session_ticket_cb session_ticket_cb;
void *session_ticket_cb_ctx;
/* SessionTicket received from OpenSSL hello_extension_cb (server) */
u8 *session_ticket;
size_t session_ticket_len;
unsigned int ca_cert_verify:1;
unsigned int cert_probe:1;
unsigned int server_cert_only:1;
unsigned int invalid_hb_used:1;
unsigned int success_data:1;
unsigned int client_hello_generated:1;
unsigned int server:1;
};
struct eap_ssl_data {
struct tls_connection *conn;
void *ssl_ctx;
}
struct eap_ssl_data data;
void start_init(){
sm->ssl_ctx = tls_init(&tlsconf);
data->ssl_ctx = sm->ssl_ctx;
data->conn = tls_connection_init(data->ssl_ctx);
res = tls_connection_set_params(data->ssl_ctx, data->conn, params);
conn->flags = params->flags;
}
void * tls_init(const struct tls_config *conf)
{
struct tls_data *data;
SSL_CTX *ssl;
struct tls_context *context;
const char *ciphers;
context = tls_context_new(conf);
if (context == NULL)
return NULL;
tls_openssl_ref_count++;
data = os_zalloc(sizeof(*data));
if (data)
ssl = SSL_CTX_new(SSLv23_method());
else
ssl = NULL;
if (ssl == NULL) {
tls_openssl_ref_count--;
if (context != tls_global)
os_free(context);
if (tls_openssl_ref_count == 0) {
os_free(tls_global);
tls_global = NULL;
}
os_free(data);
return NULL;
}
data->ssl = ssl;
if (conf) {
data->tls_session_lifetime = conf->tls_session_lifetime;
data->crl_reload_interval = conf->crl_reload_interval;
}
SSL_CTX_set_options(ssl, SSL_OP_NO_SSLv2);
SSL_CTX_set_options(ssl, SSL_OP_NO_SSLv3);
SSL_CTX_set_mode(ssl, SSL_MODE_AUTO_RETRY);
#ifdef SSL_MODE_NO_AUTO_CHAIN
/* Number of deployed use cases assume the default OpenSSL behavior of
* auto chaining the local certificate is in use. BoringSSL removed this
* functionality by default, so we need to restore it here to avoid
* breaking existing use cases. */
SSL_CTX_clear_mode(ssl, SSL_MODE_NO_AUTO_CHAIN);
#endif /* SSL_MODE_NO_AUTO_CHAIN */
SSL_CTX_set_info_callback(ssl, ssl_info_cb);
SSL_CTX_set_app_data(ssl, context);
if (data->tls_session_lifetime > 0) {
SSL_CTX_set_quiet_shutdown(ssl, 1);
/*
* Set default context here. In practice, this will be replaced
* by the per-EAP method context in tls_connection_set_verify().
*/
SSL_CTX_set_session_id_context(ssl, (u8 *) "hostapd", 7);
SSL_CTX_set_session_cache_mode(ssl, SSL_SESS_CACHE_SERVER);
SSL_CTX_set_timeout(ssl, data->tls_session_lifetime);
SSL_CTX_sess_set_remove_cb(ssl, remove_session_cb);
} else {
SSL_CTX_set_session_cache_mode(ssl, SSL_SESS_CACHE_OFF);
}
if (tls_ex_idx_session < 0) {
tls_ex_idx_session = SSL_SESSION_get_ex_new_index(
0, NULL, NULL, NULL, NULL);
if (tls_ex_idx_session < 0) {
tls_deinit(data);
return NULL;
}
}
#ifndef OPENSSL_NO_ENGINE
wpa_printf(MSG_DEBUG, "ENGINE: Loading builtin engines");
ENGINE_load_builtin_engines();
if (conf &&
(conf->opensc_engine_path || conf->pkcs11_engine_path ||
conf->pkcs11_module_path)) {
if (tls_engine_load_dynamic_opensc(conf->opensc_engine_path) ||
tls_engine_load_dynamic_pkcs11(conf->pkcs11_engine_path,
conf->pkcs11_module_path)) {
tls_deinit(data);
return NULL;
}
}
#endif /* OPENSSL_NO_ENGINE */
if (conf && conf->openssl_ciphers)
ciphers = conf->openssl_ciphers;
else
ciphers = TLS_DEFAULT_CIPHERS;
if (SSL_CTX_set_cipher_list(ssl, ciphers) != 1) {
wpa_printf(MSG_ERROR,
"OpenSSL: Failed to set cipher string '%s'",
ciphers);
tls_deinit(data);
return NULL;
}
return data;
}
static struct tls_context * tls_context_new(const struct tls_config *conf)
{
struct tls_context *context = os_zalloc(sizeof(*context));
if (context == NULL)
return NULL;
if (conf) {
context->event_cb = conf->event_cb;
context->cb_ctx = conf->cb_ctx;
context->cert_in_cb = conf->cert_in_cb;
}
return context;
}
static int tls_connection_ca_cert(struct tls_data *data,
struct tls_connection *conn,
const char *ca_cert, const u8 *ca_cert_blob,
size_t ca_cert_blob_len, const char *ca_path)
{
SSL_CTX *ssl_ctx = data->ssl;
X509_STORE *store;
/*
* Remove previously configured trusted CA certificates before adding
* new ones.
*/
store = X509_STORE_new();
if (store == NULL) {
wpa_printf(MSG_DEBUG, "OpenSSL: %s - failed to allocate new "
"certificate store", __func__);
return -1;
}
SSL_CTX_set_cert_store(ssl_ctx, store);
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, tls_verify_cb);
conn->ca_cert_verify = 1;
if (ca_cert_blob) {
X509 *cert = d2i_X509(NULL,
(const unsigned char **) &ca_cert_blob,
ca_cert_blob_len);
if (cert == NULL) {
BIO *bio = BIO_new_mem_buf(ca_cert_blob,
ca_cert_blob_len);
if (bio) {
cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
BIO_free(bio);
}
if (!cert) {
tls_show_errors(MSG_WARNING, __func__,
"Failed to parse ca_cert_blob");
return -1;
}
while (ERR_get_error()) {
/* Ignore errors from DER conversion. */
}
}
if (!X509_STORE_add_cert(SSL_CTX_get_cert_store(ssl_ctx),
cert)) {
unsigned long err = ERR_peek_error();
tls_show_errors(MSG_WARNING, __func__,
"Failed to add ca_cert_blob to "
"certificate store");
if (ERR_GET_LIB(err) == ERR_LIB_X509 &&
ERR_GET_REASON(err) ==
X509_R_CERT_ALREADY_IN_HASH_TABLE) {
wpa_printf(MSG_DEBUG, "OpenSSL: %s - ignoring "
"cert already in hash table error",
__func__);
} else {
X509_free(cert);
return -1;
}
}
X509_free(cert);
wpa_printf(MSG_DEBUG, "OpenSSL: %s - added ca_cert_blob "
"to certificate store", __func__);
return 0;
}
if (ca_cert || ca_path) {
if (SSL_CTX_load_verify_locations(ssl_ctx, ca_cert, ca_path) !=
1) {
tls_show_errors(MSG_WARNING, __func__,
"Failed to load root certificates");
if (ca_cert &&
tls_load_ca_der(data, ca_cert) == 0) {
wpa_printf(MSG_DEBUG, "OpenSSL: %s - loaded "
"DER format CA certificate",
__func__);
} else
return -1;
} else {
wpa_printf(MSG_DEBUG, "TLS: Trusted root "
"certificate(s) loaded");
tls_get_errors(data);
}
} else {
conn->ca_cert_verify = 0;
}
return 0;
}
static int tls_connection_client_cert(struct tls_connection *conn,
const char *client_cert,
const u8 *client_cert_blob,
size_t client_cert_blob_len)
{
if (client_cert == NULL && client_cert_blob == NULL)
return 0;
if (client_cert_blob &&
SSL_use_certificate_ASN1(conn->ssl, (u8 *) client_cert_blob,
client_cert_blob_len) == 1) {
wpa_printf(MSG_DEBUG, "OpenSSL: SSL_use_certificate_ASN1 --> "
"OK");
return 0;
} else if (client_cert_blob) {
#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20901000L
tls_show_errors(MSG_DEBUG, __func__,
"SSL_use_certificate_ASN1 failed");
#else
BIO *bio;
X509 *x509;
tls_show_errors(MSG_DEBUG, __func__,
"SSL_use_certificate_ASN1 failed");
bio = BIO_new(BIO_s_mem());
if (!bio)
return -1;
BIO_write(bio, client_cert_blob, client_cert_blob_len);
x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
if (!x509 || SSL_use_certificate(conn->ssl, x509) != 1) {
X509_free(x509);
BIO_free(bio);
return -1;
}
X509_free(x509);
wpa_printf(MSG_DEBUG,
"OpenSSL: Found PEM encoded certificate from blob");
while ((x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL))) {
wpa_printf(MSG_DEBUG,
"OpenSSL: Added an additional certificate into the chain");
SSL_add0_chain_cert(conn->ssl, x509);
}
BIO_free(bio);
return 0;
#endif
}
if (client_cert == NULL)
return -1;
if (SSL_use_certificate_file(conn->ssl, client_cert,
SSL_FILETYPE_ASN1) == 1) {
wpa_printf(MSG_DEBUG, "OpenSSL: SSL_use_certificate_file (DER)"
" --> OK");
return 0;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
!defined(LIBRESSL_VERSION_NUMBER) && !defined(OPENSSL_IS_BORINGSSL)
if (SSL_use_certificate_chain_file(conn->ssl, client_cert) == 1) {
ERR_clear_error();
wpa_printf(MSG_DEBUG, "OpenSSL: SSL_use_certificate_chain_file"
" --> OK");
return 0;
}
#else
if (SSL_use_certificate_file(conn->ssl, client_cert,
SSL_FILETYPE_PEM) == 1) {
ERR_clear_error();
wpa_printf(MSG_DEBUG, "OpenSSL: SSL_use_certificate_file (PEM)"
" --> OK");
return 0;
}
#endif
tls_show_errors(MSG_DEBUG, __func__,
"SSL_use_certificate_file failed");
return -1;
}
static int tls_connection_private_key(struct tls_data *data,
struct tls_connection *conn,
const char *private_key,
const char *private_key_passwd,
const u8 *private_key_blob,
size_t private_key_blob_len)
{
BIO *bio;
int ok;
if (private_key == NULL && private_key_blob == NULL)
return 0;
ok = 0;
while (private_key_blob) {
if (SSL_use_PrivateKey_ASN1(EVP_PKEY_RSA, conn->ssl,
(u8 *) private_key_blob,
private_key_blob_len) == 1) {
wpa_printf(MSG_DEBUG, "OpenSSL: SSL_use_PrivateKey_"
"ASN1(EVP_PKEY_RSA) --> OK");
ok = 1;
break;
}
if (SSL_use_PrivateKey_ASN1(EVP_PKEY_DSA, conn->ssl,
(u8 *) private_key_blob,
private_key_blob_len) == 1) {
wpa_printf(MSG_DEBUG, "OpenSSL: SSL_use_PrivateKey_"
"ASN1(EVP_PKEY_DSA) --> OK");
ok = 1;
break;
}
#ifndef OPENSSL_NO_EC
if (SSL_use_PrivateKey_ASN1(EVP_PKEY_EC, conn->ssl,
(u8 *) private_key_blob,
private_key_blob_len) == 1) {
wpa_printf(MSG_DEBUG,
"OpenSSL: SSL_use_PrivateKey_ASN1(EVP_PKEY_EC) --> OK");
ok = 1;
break;
}
#endif /* OPENSSL_NO_EC */
if (SSL_use_RSAPrivateKey_ASN1(conn->ssl,
(u8 *) private_key_blob,
private_key_blob_len) == 1) {
wpa_printf(MSG_DEBUG, "OpenSSL: "
"SSL_use_RSAPrivateKey_ASN1 --> OK");
ok = 1;
break;
}
bio = BIO_new_mem_buf((u8 *) private_key_blob,
private_key_blob_len);
if (bio) {
EVP_PKEY *pkey;
pkey = PEM_read_bio_PrivateKey(
bio, NULL, tls_passwd_cb,
(void *) private_key_passwd);
if (pkey) {
if (SSL_use_PrivateKey(conn->ssl, pkey) == 1) {
wpa_printf(MSG_DEBUG,
"OpenSSL: SSL_use_PrivateKey --> OK");
ok = 1;
EVP_PKEY_free(pkey);
BIO_free(bio);
break;
}
EVP_PKEY_free(pkey);
}
BIO_free(bio);
}
if (tls_read_pkcs12_blob(data, conn->ssl, private_key_blob,
private_key_blob_len,
private_key_passwd) == 0) {
wpa_printf(MSG_DEBUG, "OpenSSL: PKCS#12 as blob --> "
"OK");
ok = 1;
break;
}
break;
}
while (!ok && private_key) {
if (tls_use_private_key_file(data, conn->ssl, private_key,
private_key_passwd) == 0) {
ok = 1;
break;
}
if (tls_read_pkcs12(data, conn->ssl, private_key,
private_key_passwd) == 0) {
wpa_printf(MSG_DEBUG, "OpenSSL: Reading PKCS#12 file "
"--> OK");
ok = 1;
break;
}
if (tls_cryptoapi_cert(conn->ssl, private_key) == 0) {
wpa_printf(MSG_DEBUG, "OpenSSL: Using CryptoAPI to "
"access certificate store --> OK");
ok = 1;
break;
}
break;
}
if (!ok) {
tls_show_errors(MSG_INFO, __func__,
"Failed to load private key");
return -1;
}
ERR_clear_error();
if (!SSL_check_private_key(conn->ssl)) {
tls_show_errors(MSG_INFO, __func__, "Private key failed "
"verification");
return -1;
}
wpa_printf(MSG_DEBUG, "SSL: Private key loaded successfully");
return 0;
}
int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
const struct tls_connection_params *params)
{
tls_connection_ca_cert(data, conn, params->ca_cert, params->ca_cert_blob,
params->ca_cert_blob_len, params->ca_path)
tls_connection_client_cert(conn, params->client_cert,params->client_cert_blob, params->client_cert_blob_len);
tls_connection_private_key(data, conn, params->private_key,params->private_key_passwd,
params->private_key_blob, params->private_key_blob_len);
tls_connection_dh(conn, params->dh_file);
tls_set_conn_flags(conn, params->flags,
params->openssl_ciphers);
}
static int tls_set_conn_flags(struct tls_connection *conn, unsigned int flags,
const char *openssl_ciphers)
{
SSL *ssl = conn->ssl;
#ifdef SSL_OP_NO_TICKET
if (flags & TLS_CONN_DISABLE_SESSION_TICKET)
SSL_set_options(ssl, SSL_OP_NO_TICKET);
else
SSL_clear_options(ssl, SSL_OP_NO_TICKET);
#endif /* SSL_OP_NO_TICKET */
-----------------------
}
struct tls_connection * tls_connection_init(void *sslctx_tlsdata)
{
struct tls_data *data = sslctx_tlsdata;
SSL_CTX *sslctx = data->ssl;
struct tls_connection *conn;
long options;
struct tls_context *context = SSL_CTX_get_app_data(sslctx);
conn = os_zalloc(sizeof(*conn));
conn->data = data;
conn->ssl_ctx = sslctx;
conn->ssl = SSL_new(sslctx);
conn->context = context;
SSL_set_app_data(conn->ssl, conn);
SSL_set_msg_callback(conn->ssl, tls_msg_cb);
SSL_set_msg_callback_arg(conn->ssl, conn);
options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
SSL_OP_SINGLE_DH_USE;
SSL_set_options(conn->ssl, options);
conn->ssl_in = BIO_new(BIO_s_mem());
conn->ssl_out = BIO_new(BIO_s_mem());
SSL_set_bio(conn->ssl, conn->ssl_in, conn->ssl_out);
return conn;
}
static void tls_msg_cb(int write_p, int version, int content_type,
const void *buf, size_t len, SSL *ssl, void *arg)
{
struct tls_connection *conn = arg;
const u8 *pos = buf;
if (write_p == 2) {
wpa_printf(MSG_DEBUG,
"OpenSSL: session ver=0x%x content_type=%d",
version, content_type);
wpa_hexdump_key(MSG_MSGDUMP, "OpenSSL: Data", buf, len);
return;
}
wpa_printf(MSG_DEBUG, "OpenSSL: %s ver=0x%x content_type=%d (%s/%s)",
write_p ? "TX" : "RX", version, content_type,
openssl_content_type(content_type),
openssl_handshake_type(content_type, buf, len));
wpa_hexdump_key(MSG_MSGDUMP, "OpenSSL: Message", buf, len);
if (content_type == 24 && len >= 3 && pos[0] == 1) {
size_t payload_len = WPA_GET_BE16(pos + 1);
if (payload_len + 3 > len) {
wpa_printf(MSG_ERROR, "OpenSSL: Heartbeat attack detected");
conn->invalid_hb_used = 1;
}
}
#ifdef CONFIG_SUITEB
/*
* Need to parse these handshake messages to be able to check DH prime
* length since OpenSSL does not expose the new cipher suite and DH
* parameters during handshake (e.g., for cert_cb() callback).
*/
if (content_type == 22 && pos && len > 0 && pos[0] == 2)
check_server_hello(conn, pos + 1, pos + len);
if (content_type == 22 && pos && len > 0 && pos[0] == 12)
check_server_key_exchange(ssl, conn, pos + 1, pos + len);
#endif /* CONFIG_SUITEB */
}
static void ssl_info_cb(const SSL *ssl, int where, int ret)
{
const char *str;
int w;
wpa_printf(MSG_DEBUG, "SSL: (where=0x%x ret=0x%x)", where, ret);
w = where & ~SSL_ST_MASK;
if (w & SSL_ST_CONNECT)
str = "SSL_connect";
else if (w & SSL_ST_ACCEPT)
str = "SSL_accept";
else
str = "undefined";
if (where & SSL_CB_LOOP) {
wpa_printf(MSG_DEBUG, "SSL: %s:%s",
str, SSL_state_string_long(ssl));
} else if (where & SSL_CB_ALERT) {
struct tls_connection *conn = SSL_get_app_data((SSL *) ssl);
wpa_printf(MSG_INFO, "SSL: SSL3 alert: %s:%s:%s",
where & SSL_CB_READ ?
"read (remote end reported an error)" :
"write (local SSL3 detected an error)",
SSL_alert_type_string_long(ret),
SSL_alert_desc_string_long(ret));
if ((ret >> 8) == SSL3_AL_FATAL) {
if (where & SSL_CB_READ)
conn->read_alerts++;
else
conn->write_alerts++;
}
if (conn->context->event_cb != NULL) {
union tls_event_data ev;
struct tls_context *context = conn->context;
os_memset(&ev, 0, sizeof(ev));
ev.alert.is_local = !(where & SSL_CB_READ);
ev.alert.type = SSL_alert_type_string_long(ret);
ev.alert.description = SSL_alert_desc_string_long(ret);
context->event_cb(context->cb_ctx, TLS_ALERT, &ev);
}
} else if (where & SSL_CB_EXIT && ret <= 0) {
wpa_printf(MSG_DEBUG, "SSL: %s:%s in %s",
str, ret == 0 ? "failed" : "error",
SSL_state_string_long(ssl));
}
}
static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
{
char buf[256];
X509 *err_cert;
int err, depth;
SSL *ssl;
struct tls_connection *conn;
struct tls_context *context;
char *match, *altmatch, *suffix_match, *domain_match;
const char *check_cert_subject;
const char *err_str;
err_cert = X509_STORE_CTX_get_current_cert(x509_ctx);
if (!err_cert)
return 0;
err = X509_STORE_CTX_get_error(x509_ctx);
depth = X509_STORE_CTX_get_error_depth(x509_ctx);
ssl = X509_STORE_CTX_get_ex_data(x509_ctx,
SSL_get_ex_data_X509_STORE_CTX_idx());
os_snprintf(buf, sizeof(buf), "Peer certificate - depth %d", depth);
debug_print_cert(err_cert, buf);
X509_NAME_oneline(X509_get_subject_name(err_cert), buf, sizeof(buf));
conn = SSL_get_app_data(ssl);
if (conn == NULL)
return 0;
if (depth == 0)
conn->peer_cert = err_cert;
else if (depth == 1)
conn->peer_issuer = err_cert;
else if (depth == 2)
conn->peer_issuer_issuer = err_cert;
context = conn->context;
match = conn->subject_match;
altmatch = conn->altsubject_match;
suffix_match = conn->suffix_match;
domain_match = conn->domain_match;
if (!preverify_ok && !conn->ca_cert_verify)
preverify_ok = 1;
if (!preverify_ok && depth > 0 && conn->server_cert_only)
preverify_ok = 1;
if (!preverify_ok && (conn->flags & TLS_CONN_DISABLE_TIME_CHECKS) &&
(err == X509_V_ERR_CERT_HAS_EXPIRED ||
err == X509_V_ERR_CERT_NOT_YET_VALID)) {
wpa_printf(MSG_DEBUG, "OpenSSL: Ignore certificate validity "
"time mismatch");
preverify_ok = 1;
}
if (!preverify_ok && !conn->data->check_crl_strict &&
(err == X509_V_ERR_CRL_HAS_EXPIRED ||
err == X509_V_ERR_CRL_NOT_YET_VALID)) {
wpa_printf(MSG_DEBUG,
"OpenSSL: Ignore certificate validity CRL time mismatch");
preverify_ok = 1;
}
err_str = X509_verify_cert_error_string(err);
#ifdef CONFIG_SHA256
/*
* Do not require preverify_ok so we can explicity allow otherwise
* invalid pinned server certificates.
*/
if (depth == 0 && conn->server_cert_only) {
struct wpabuf *cert;
cert = get_x509_cert(err_cert);
if (!cert) {
wpa_printf(MSG_DEBUG, "OpenSSL: Could not fetch "
"server certificate data");
preverify_ok = 0;
} else {
u8 hash[32];
const u8 *addr[1];
size_t len[1];
addr[0] = wpabuf_head(cert);
len[0] = wpabuf_len(cert);
if (sha256_vector(1, addr, len, hash) < 0 ||
os_memcmp(conn->srv_cert_hash, hash, 32) != 0) {
err_str = "Server certificate mismatch";
err = X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN;
preverify_ok = 0;
} else if (!preverify_ok) {
/*
* Certificate matches pinned certificate, allow
* regardless of other problems.
*/
wpa_printf(MSG_DEBUG,
"OpenSSL: Ignore validation issues for a pinned server certificate");
preverify_ok = 1;
}
wpabuf_free(cert);
}
}
#endif /* CONFIG_SHA256 */
openssl_tls_cert_event(conn, err_cert, depth, buf);
if (!preverify_ok) {
if (depth > 0) {
/* Send cert event for the peer certificate so that
* the upper layers get information about it even if
* validation of a CA certificate fails. */
STACK_OF(X509) *chain;
chain = X509_STORE_CTX_get1_chain(x509_ctx);
if (chain && sk_X509_num(chain) > 0) {
char buf2[256];
X509 *cert;
cert = sk_X509_value(chain, 0);
X509_NAME_oneline(X509_get_subject_name(cert),
buf2, sizeof(buf2));
openssl_tls_cert_event(conn, cert, 0, buf2);
}
if (chain)
sk_X509_pop_free(chain, X509_free);
}
wpa_printf(MSG_WARNING, "TLS: Certificate verification failed,"
" error %d (%s) depth %d for '%s'", err, err_str,
depth, buf);
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
err_str, TLS_FAIL_UNSPECIFIED);
return preverify_ok;
}
wpa_printf(MSG_DEBUG, "TLS: tls_verify_cb - preverify_ok=%d "
"err=%d (%s) ca_cert_verify=%d depth=%d buf='%s'",
preverify_ok, err, err_str,
conn->ca_cert_verify, depth, buf);
check_cert_subject = conn->check_cert_subject;
if (!check_cert_subject)
check_cert_subject = conn->data->check_cert_subject;
if (check_cert_subject) {
if (depth == 0 &&
!tls_match_dn_field(err_cert, check_cert_subject)) {
preverify_ok = 0;
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
"Distinguished Name",
TLS_FAIL_DN_MISMATCH);
}
}
if (depth == 0 && match && os_strstr(buf, match) == NULL) {
wpa_printf(MSG_WARNING, "TLS: Subject '%s' did not "
"match with '%s'", buf, match);
preverify_ok = 0;
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
"Subject mismatch",
TLS_FAIL_SUBJECT_MISMATCH);
} else if (depth == 0 && altmatch &&
!tls_match_altsubject(err_cert, altmatch)) {
wpa_printf(MSG_WARNING, "TLS: altSubjectName match "
"'%s' not found", altmatch);
preverify_ok = 0;
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
"AltSubject mismatch",
TLS_FAIL_ALTSUBJECT_MISMATCH);
} else if (depth == 0 && suffix_match &&
!tls_match_suffix(err_cert, suffix_match, 0)) {
wpa_printf(MSG_WARNING, "TLS: Domain suffix match '%s' not found",
suffix_match);
preverify_ok = 0;
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
"Domain suffix mismatch",
TLS_FAIL_DOMAIN_SUFFIX_MISMATCH);
} else if (depth == 0 && domain_match &&
!tls_match_suffix(err_cert, domain_match, 1)) {
wpa_printf(MSG_WARNING, "TLS: Domain match '%s' not found",
domain_match);
preverify_ok = 0;
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
"Domain mismatch",
TLS_FAIL_DOMAIN_MISMATCH);
}
if (conn->cert_probe && preverify_ok && depth == 0) {
wpa_printf(MSG_DEBUG, "OpenSSL: Reject server certificate "
"on probe-only run");
preverify_ok = 0;
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
"Server certificate chain probe",
TLS_FAIL_SERVER_CHAIN_PROBE);
}
#ifdef CONFIG_SUITEB
if (conn->flags & TLS_CONN_SUITEB) {
EVP_PKEY *pk;
RSA *rsa;
int len = -1;
pk = X509_get_pubkey(err_cert);
if (pk) {
rsa = EVP_PKEY_get1_RSA(pk);
if (rsa) {
len = RSA_bits(rsa);
RSA_free(rsa);
}
EVP_PKEY_free(pk);
}
if (len >= 0) {
wpa_printf(MSG_DEBUG,
"OpenSSL: RSA modulus size: %d bits", len);
if (len < 3072) {
preverify_ok = 0;
openssl_tls_fail_event(
conn, err_cert, err,
depth, buf,
"Insufficient RSA modulus size",
TLS_FAIL_INSUFFICIENT_KEY_LEN);
}
}
}
#endif /* CONFIG_SUITEB */
#ifdef OPENSSL_IS_BORINGSSL
if (depth == 0 && (conn->flags & TLS_CONN_REQUEST_OCSP) &&
preverify_ok) {
enum ocsp_result res;
res = check_ocsp_resp(conn->ssl_ctx, conn->ssl, err_cert,
conn->peer_issuer,
conn->peer_issuer_issuer);
if (res == OCSP_REVOKED) {
preverify_ok = 0;
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
"certificate revoked",
TLS_FAIL_REVOKED);
if (err == X509_V_OK)
X509_STORE_CTX_set_error(
x509_ctx, X509_V_ERR_CERT_REVOKED);
} else if (res != OCSP_GOOD &&
(conn->flags & TLS_CONN_REQUIRE_OCSP)) {
preverify_ok = 0;
openssl_tls_fail_event(conn, err_cert, err, depth, buf,
"bad certificate status response",
TLS_FAIL_UNSPECIFIED);
}
}
#endif /* OPENSSL_IS_BORINGSSL */
if (depth == 0 && preverify_ok && context->event_cb != NULL)
context->event_cb(context->cb_ctx,
TLS_CERT_CHAIN_SUCCESS, NULL);
if (depth == 0 && preverify_ok) {
os_free(conn->peer_subject);
conn->peer_subject = os_strdup(buf);
}
return preverify_ok;
}
int tls_connection_set_verify(void *ssl_ctx, struct tls_connection *conn,
int verify_peer, unsigned int flags,
const u8 *session_ctx, size_t session_ctx_len)
{
static int counter = 0;
struct tls_data *data = ssl_ctx;
if (conn == NULL)
return -1;
if (verify_peer == 2) {
conn->ca_cert_verify = 1;
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER |
SSL_VERIFY_CLIENT_ONCE, tls_verify_cb);
} else if (verify_peer) {
conn->ca_cert_verify = 1;
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT |
SSL_VERIFY_CLIENT_ONCE, tls_verify_cb);
} else {
conn->ca_cert_verify = 0;
SSL_set_verify(conn->ssl, SSL_VERIFY_NONE, NULL);
}
if (tls_set_conn_flags(conn, flags, NULL) < 0)
return -1;
conn->flags = flags;
SSL_set_accept_state(conn->ssl);
if (data->tls_session_lifetime == 0) {
/*
* Set session id context to a unique value to make sure
* session resumption cannot be used either through session
* caching or TLS ticket extension.
*/
counter++;
SSL_set_session_id_context(conn->ssl,
(const unsigned char *) &counter,
sizeof(counter));
} else if (session_ctx) {
SSL_set_session_id_context(conn->ssl, session_ctx,
session_ctx_len);
}
return 0;
}
2. 核心区别详解
2.1 SSL_CTX_set_info_callback vs SSL_CTX_set_msg_callback
-
SSL_CTX_set_info_callback-
触发时机:SSL 状态机变化(如握手开始、握手完成、读写操作)。
-
数据内容:高层事件(如
SSL_CB_HANDSHAKE_START、SSL_CB_ACCEPT_LOOP)。 -
用途:监控连接生命周期,记录日志或统计信息。
void info_callback(const SSL *ssl, int type, int val) { if (type & SSL_CB_HANDSHAKE_START) { printf("Handshake started\n"); } else if (type & SSL_CB_HANDSHAKE_DONE) { printf("Handshake completed\n"); } } SSL_CTX_set_info_callback(ctx, info_callback);
-
-
SSL_CTX_set_msg_callback-
触发时机:TLS 记录层消息发送/接收时(如
SSL3_RT_HANDSHAKE类型的消息)。 -
数据内容:原始消息字节流(包括协议头、消息体)。
-
用途:调试协议细节,捕获完整的 TLS 报文。
void msg_callback(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg) { const char *direction = write_p ? "Sent" : "Received"; printf("%s TLS message: type=%d, length=%zu\n", direction, content_type, len); } SSL_CTX_set_msg_callback(ctx, msg_callback);
-
2.2 SSL_CTX_set_cert_verify_callback vs SSL_CTX_set_cert_cb
-
SSL_CTX_set_cert_verify_callback-
触发阶段:证书验证阶段(客户端验证服务器证书,或服务器验证客户端证书)。
-
用途:自定义证书验证逻辑(如忽略特定错误、添加 OCSP 检查)。
int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { if (!preverify_ok) { // 可在此添加自定义错误处理 fprintf(stderr, "Certificate verification failed\n"); } return preverify_ok; // 返回 1 接受证书,0 拒绝 } SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
-
-
SSL_CTX_set_cert_cb-
触发阶段:服务端需要发送证书时(响应客户端请求)。
-
用途:动态加载服务端证书(如根据 SNI 域名选择证书)。
int cert_cb(SSL *ssl, void *arg) { const char *sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (sni && strcmp(sni, "example.com") == 0) { SSL_use_certificate(ssl, load_cert("example.com.crt")); SSL_use_PrivateKey(ssl, load_key("example.com.key")); } return 1; // 成功返回 1,失败返回 0 } SSL_CTX_set_cert_cb(ctx, cert_cb, NULL);
-
2.3 SSL_CTX_set_client_hello_cb vs SSL_CTX_set_alpn_select_cb
-
SSL_CTX_set_client_hello_cb-
触发时机:服务端收到
ClientHello后,解析前。 -
用途:拦截或修改
ClientHello中的扩展(如强制特定密码套件)。
int client_hello_cb(SSL *ssl, int *al, void *arg) { const unsigned char *ext_data; size_t ext_len; // 获取 SNI 扩展 if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &ext_data, &ext_len)) { printf("Client requested SNI\n"); } return SSL_CLIENT_HELLO_SUCCESS; } SSL_CTX_set_client_hello_cb(ctx, client_hello_cb, NULL);
-
-
SSL_CTX_set_alpn_select_cb-
触发时机:服务端处理
ClientHello中的 ALPN 扩展时。 -
用途:协商应用层协议(如选择 HTTP/2)。
int alpn_select_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { // 客户端支持的协议列表在 `in` 中,服务端选择后设置到 `out` if (SSL_select_next_proto((unsigned char **)out, outlen, in, inlen, "\x02h2", 2) == OPENSSL_NPN_NEGOTIATED) { return SSL_TLSEXT_ERR_OK; } return SSL_TLSEXT_ERR_NOACK; } SSL_CTX_set_alpn_select_cb(ctx, alpn_select_cb, NULL);
-
3. 使用场景总结
-
调试与日志:
-
SSL_CTX_set_msg_callback:捕获原始 TLS 消息(如 Wireshark 类工具)。 -
SSL_CTX_set_info_callback:跟踪握手状态变化。
-
-
证书管理:
-
SSL_CTX_set_cert_cb:动态加载证书(多域名服务器)。 -
SSL_CTX_set_cert_verify_callback:自定义证书验证(如 OCSP Stapling)。
-
-
协议扩展:
-
SSL_CTX_set_client_hello_cb:拦截/修改ClientHello。 -
SSL_CTX_set_alpn_select_cb:ALPN 协议协商。
-
-
安全控制:
-
SSL_CTX_set_verify:强制证书验证。
-
4. 关键注意事项
-
作用域:
-
SSL_CTX级回调影响所有关联的SSL连接。 -
SSL级回调仅影响单个连接。
-
-
性能影响:
-
避免在回调中执行阻塞操作(如网络请求),否则会延迟 TLS 握手。
-
-
兼容性:
-
自定义 TLS 扩展(如修改
ClientHello)可能导致与标准客户端不兼容。
-

浙公网安备 33010602011771号