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 消息(如 ClientHelloServerHello)的字节流 调试 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_STARTSSL_CB_ACCEPT_LOOP)。

    • 用途:监控连接生命周期,记录日志或统计信息。

    c
    复制
    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 报文。

    c
    复制
    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 检查)。

    c
    复制
    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 域名选择证书)。

    c
    复制
    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 中的扩展(如强制特定密码套件)。

    c
    复制
    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)。

    c
    复制
    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. 关键注意事项

  1. 作用域

    • SSL_CTX 级回调影响所有关联的 SSL 连接。

    • SSL 级回调仅影响单个连接。

  2. 性能影响

    • 避免在回调中执行阻塞操作(如网络请求),否则会延迟 TLS 握手。

  3. 兼容性

    • 自定义 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_STARTSSL_CB_ACCEPT_LOOP)。

    • 用途:监控连接生命周期,记录日志或统计信息。

    c
    复制
    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 报文。

    c
    复制
    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 检查)。

    c
    复制
    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 域名选择证书)。

    c
    复制
    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 中的扩展(如强制特定密码套件)。

    c
    复制
    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)。

    c
    复制
    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. 关键注意事项

  1. 作用域

    • SSL_CTX 级回调影响所有关联的 SSL 连接。

    • SSL 级回调仅影响单个连接。

  2. 性能影响

    • 避免在回调中执行阻塞操作(如网络请求),否则会延迟 TLS 握手。

  3. 兼容性

    • 自定义 TLS 扩展(如修改 ClientHello)可能导致与标准客户端不兼容。

posted @ 2025-03-25 14:58  codestacklinuxer  阅读(94)  评论(0)    收藏  举报