Windows 2019服务器OpenSSL/OpenLDAP升级后TLS握手故障排查
问题概述
在Windows 2019服务器上将OpenSSL从1.1.1版本升级至3.0.10、OpenLDAP从2.4.47版本升级至2.5.16后,自研客户端与LDAP服务器连接时出现TLS握手失败,服务器无法拾取客户端证书。
已完成测试
- 命令行配置客户端证书环境变量,升级后的OpenSSL可成功连接LDAP服务器,无报错
- 同样命令行配置环境变量,使用ldapsearch.exe客户端可成功连接LDAP服务器,无报错
- 自研应用连接时,TLS握手失败
自研应用失败代码片段
// Test1.cpp : Defines the entry point for the console application. #include "stdafx.h" #include "windows.h" #include "winldap.h" #include "stdio.h" #include "wincrypt.h" #include <openssl/ssl.h> #include <openssl/err.h> //#include "ldap.h" #pragma comment(lib, "wldap32.lib") #pragma comment(lib, "crypt32.lib") #pragma comment(lib, "D:/Test1/libssl.lib") //lib built using openssl3.0.10 #pragma comment(lib, "D:/Test1/libcrypto.lib") //lib built using openssl3.0.10 //The following LDAP TLS options are mentioned in ldap.h #define LDAP_OPT_X_TLS_REQUIRE_CERT 0x6006 #define LDAP_OPT_X_TLS_HARD 1 #define LDAP_OPT_X_TLS_ALLOW 3 #define LDAP_OPT_X_TLS_CACERTFILE 0x6002 #define LDAP_OPT_X_TLS_CACERTDIR 0x6003 #define LDAP_OPT_X_TLS_CERTFILE 0x6004 #define LDAP_OPT_X_TLS_KEYFILE 0x6005 #define LDAP_OPT_X_TLS_CIPHER 0x6014 #define LDAP_OPT_X_TLS_PROTOCOL_TLS1_3 ((3 << 8) + 4) const size_t newsize = 100; // Entry point for application int main(int argc, char* argv[]) { PWCHAR hostName = NULL; LDAP* pLdapConnection = NULL; ULONG version = LDAP_VERSION3; ULONG getOptSuccess = 0; ULONG connectSuccess = 0; INT returnCode = 0; PCCERT_CONTEXT cert_ctx = NULL; ULONG lv; const char *cert_path = "D:/Test1/c_usr"; const char *cert_file = "c_usr.crt"; // Path to the client certificate file const char *key_file = "c_usr.key.pem"; // Path to the private key file associated with the client certificate const char *CA_file = "root_ca.crt"; const char *cipher_tls = "TLS_AES_256_GCM_SHA384"; // Pass the hostname. if (argc > 1) { // Convert argv[] to a wchar_t* size_t origsize = strlen(argv[1]) + 1; size_t convertedChars = 0; wchar_t wcstring[newsize]; mbstowcs_s(&convertedChars, wcstring, origsize, argv[1], _TRUNCATE); wcscat_s(wcstring, L" (wchar_t *)"); hostName = wcstring; } else { hostName = NULL; } // Initialize a session. LDAP_PORT is the default port, 389. pLdapConnection=ldap_sslinit(hostName, 636, 1); if (pLdapConnection == NULL) { // Set the HRESULT based on the Windows error code. char hr = HRESULT_FROM_WIN32(GetLastError()); printf( "ldap_init failed with 0x%x.\n",hr); goto error_exit; } else printf("ldap_init succeeded \n"); // Set the version to 3.0 . returnCode = ldap_set_option(pLdapConnection, LDAP_OPT_PROTOCOL_VERSION, (void*)&version); if(returnCode == LDAP_SUCCESS) printf("ldap_set_option succeeded - version set to 3\n"); else { printf("SetOption Error:%0X\n", returnCode); goto error_exit; } returnCode = ldap_set_option(pLdapConnection, LDAP_OPT_X_TLS_CACERTDIR, (void*)cert_path); if(returnCode == LDAP_SUCCESS) printf("ldap_set_option succeeded - certificate path\n"); else { printf("SetOption Error:%0X\n", returnCode); goto error_exit; } // Set TLS/SSL options returnCode = ldap_set_option(pLdapConnection, LDAP_OPT_X_TLS_CIPHER, cipher_tls); if(returnCode == LDAP_SUCCESS) printf("ldap_set_option succeeded - cipher set to TLS_AES_256_GCM_SHA384\n"); else { printf("SetOption Error:%0X\n", returnCode); goto error_exit; } returnCode = ldap_set_option(pLdapConnection, LDAP_OPT_X_TLS_REQUIRE_CERT, (void *)LDAP_OPT_X_TLS_ALLOW); // Require valid server certificate if(returnCode == LDAP_SUCCESS) printf("ldap_set_option succeeded - LDAP_OPT_X_TLS_ALLOW \n"); else { printf("SetOption Error:%0X\n", returnCode); goto error_exit; } returnCode = ldap_set_option(pLdapConnection, LDAP_OPT_X_TLS_CERTFILE, cert_file); // Set client certificate file if(returnCode == LDAP_SUCCESS) printf("ldap_set_option succeeded - client certificate\n"); else { printf("SetOption Error:%0X\n", returnCode); goto error_exit; } returnCode = ldap_set_option(pLdapConnection, LDAP_OPT_X_TLS_KEYFILE, key_file); // Set private key file if(returnCode == LDAP_SUCCESS) printf("ldap_set_option succeeded - certificate key file\n"); else { printf("SetOption Error:%0X\n", returnCode); goto error_exit; } returnCode = ldap_set_option(pLdapConnection, LDAP_OPT_X_TLS_CACERTFILE, CA_file); // Set client certificate file if(returnCode == LDAP_SUCCESS) printf("ldap_set_option succeeded - CA file \n"); else { printf("SetOption Error:%0X\n", returnCode); goto error_exit; } connectSuccess = ldap_connect(pLdapConnection, NULL); if(connectSuccess == LDAP_SUCCESS) printf("ldap_connect succeeded \n"); else { printf("ldap_connect failed with 0x%x.\n",connectSuccess); goto error_exit; } // Bind with current credentials (login credentials). Be // aware that the password itself is never sent over the // network, and encryption is not used. printf("Binding ...\n"); returnCode = ldap_bind_s(pLdapConnection, NULL, NULL, LDAP_AUTH_NEGOTIATE); if (returnCode == LDAP_SUCCESS) printf("The bind was successful"); else goto error_exit; // Normal cleanup and exit. ldap_unbind(pLdapConnection); return 0; // On error cleanup and exit. error_exit: ldap_unbind(pLdapConnection); return -1; }
命令行测试情况
- 执行命令
openssl s_client -debug -connect server:636 -starttls ldap -tls1_3 -cert c_usr.crt -key c_usr.key.pem -CAfile c_ca.crt无法连接,报错如下:
connection_read(3): checking for input on id=1000 TLS trace: SSL_accept:before SSL initialization tls_read: want=5, got=5 0000: 30 1d 02 01 01 0.... TLS trace: SSL_accept:error in error TLS: can't accept: error:0A00010B:SSL routines::wrong version number. connection_read(3): TLS accept failure error=-1 id=1000, closing connection_closing: readying conn=1000 sd=3 for close daemon: activity on 1 descriptor daemon: waked daemon: WSselect: listen=2 active_threads=0 tvp=NULL connection_close: conn=1000 sd=3 daemon: removing 3 conn=1000 fd=3 closed (TLS negotiation failure)
- 移除
-starttls ldap选项后,命令openssl s_client -debug -connect server:636 -tls1_3 -cert c_usr.crt -key c_usr.key.pem -CAfile c_ca.crt可成功连接
失败场景下LDAP服务器日志
conn=1000 fd=3 ACCEPT from IP=XX.XXX.XXX.XX:XXXXX (IP=0.0.0.0:636) connection_get(3) connection_get(3): got connid=1000 connection_read(3): checking for input on id=1000 TLS trace: SSL_accept:before SSL initialization tls_read: want=5, got=5 tls_read: want=142, got=142 TLS trace: SSL_accept:SSLv3/TLS write server done tls_read: want=5 error=Unknown error TLS trace: SSL_accept:error in SSLv3/TLS write server done daemon: activity on 1 descriptor daemon: waked daemon: WSselect: listen=2 active_threads=0 tvp=NULL daemon: activity on 4 descriptors daemon: activity on: daemon: read activity on connection_get(3) daemon: WSselec: listen=2 active_threads=0 tvp=NULL connection_get(3): got connid=1000 daemon: activity on 3 descriptors connection_read(3): checking for input on id=1000 slap_listener_activate(2): tls_read: want=5, got=0 daemon: activity on:65d4802a.10506dd7 00003A10 >>> slap_listener(ldaps://) daemon: accept() = 51328 TLS trace: SSL_accept:error in SSLv3/TLS write server done daemon: WSselect: listen=2 busy daemon: listen=2, new connection on 4 daemon: added 4r (active) listener=00000000 daemon: activity on 1 descriptor TLS: can't accept: (unknown). conn=1001 fd=4 ACCEPT from IP=XX.XXX.XXX.XX:XXXXX (IP=0.0.0.0:636) daemon: waked connection_read(3): TLS accept failure error=-1 id=1000, closing daemon: WSselect: listen=2 active_threads=0 tvp=NULL connection_closing: readying conn=1000 sd=3 for close daemon: activity on 5 descriptors connection_close: conn=1000 sd=3 daemon: waked daemon: removing daemon: WSselect: listen=2 active_threads=0 tvp=NULL conn=1000 fd=3 closed (TLS negotiation failure) daemon: activity on 5 descriptors daemon: waked daemon: WSselect: listen=2 active_threads=0 tvp=NULL daemon: activity on 5 descriptors daemon: activity on: daemon: read activity on connection_get(4) daemon: WSselect: listen=2 active_threads=0 tvp=NULL connection_get(4): got connid=1001 connection_read(4): checking for input on id=1001 TLS trace: SSL_accept:before SSL initialization tls_read: want=5, got=5 tls_read: want=142, got=142 TLS trace: SSL_accept:before SSL initialization TLS trace: SSL_accept:SSLv3/TLS read client hello TLS trace: SSL_accept:SSLv3/TLS write server hello TLS trace: SSL_accept:SSLv3/TLS write certificate TLS trace: SSL_accept:SSLv3/TLS write key exchange TLS trace: SSL_accept:SSLv3/TLS write certificate request tls_write: want=1807, written=1807 TLS trace: SSL_accept:SSLv3/TLS write server done tls_read: want=5 error=Unknown error TLS trace: SSL_accept:error in SSLv3/TLS write server done daemon: activity on 1 descriptor daemon: waked daemon: WSselect: listen=2 active_threads=0 tvp=NULL daemon: shutdown requested and initiated. daemon: removing daemon: closing connection_closing: readying conn=1001 sd=4 for close connection_close: conn=1001 sd=4 daemon: removing 4 conn=1001 fd=4 closed (slapd shutdown) slapd shutdown: waiting for 0 operations/tasks to finish slapd shutdown: initiated slapd destroy: freeing system resources. slapd stopped.
排查方向与解决方案
排查方向
连接模式冲突
端口636默认是LDAPS(即时加密)模式,而-starttls ldap是明文LDAP(389端口)升级加密的命令,在636端口使用会导致协议不匹配,需确认自研应用是否误用了StartTLS逻辑连接LDAPS端口。OpenSSL 3.0初始化缺失
OpenSSL 3.0引入了provider机制,默认依赖default和legacyprovider,1.1.1版本无此机制。自研应用可能未完成3.0版本的初始化流程,导致证书读取或TLS协商失败。LDAP选项兼容性
部分LDAP_OPT_X_TLS_*选项仅对StartTLS模式生效,LDAPS模式下需通过OpenSSL上下文直接配置证书,当前代码用ldap_set_option配置的选项可能未生效。证书路径与权限
代码中使用相对路径加载证书,需确认应用运行时工作目录包含目标文件;同时验证OpenSSL 3.0是否支持当前证书格式,以及证书与私钥是否匹配。库依赖冲突
Windows系统的wldap32.lib可能默认调用系统SSL库,与自定义编译的OpenSSL 3.0库存在版本冲突,需确认应用优先加载正确的OpenSSL DLL文件。
解决方案建议
修正连接模式
确保自研应用使用LDAPS模式连接636端口,无需启用StartTLS;若需使用StartTLS,应连接389端口,并在ldap_connect后调用ldap_start_tls_s。添加OpenSSL 3.0初始化代码
在应用启动阶段加入以下初始化代码,确保provider正常加载:#include <openssl/core.h> #include <openssl/provider.h> // 初始化OpenSSL 3.0 OSSL_PROVIDER_load(NULL, "legacy"); OSSL_PROVIDER_load(NULL, "default"); OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL); OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);调整LDAP证书配置方式
对于LDAPS模式,通过LDAP_OPT_X_TLS_CTX选项直接传递自定义SSL上下文,替代原有的LDAP_OPT_X_TLS_*选项:SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_client_method()); SSL_CTX_use_certificate_file(ssl_ctx, cert_file, SSL_FILETYPE_PEM); SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM); SSL_CTX_load_verify_locations(ssl_ctx, CA_file, cert_path); ldap_set_option(pLdapConnection, LDAP_OPT_X_TLS_CTX, ssl_ctx);验证证书与路径
- 用
openssl x509 -in c_usr.crt -text -noout验证证书格式,用`openssl rsa
- 用

