You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows 2019服务器OpenSSL/OpenLDAP升级后TLS握手故障排查

OpenSSL 3.0.10 + OpenLDAP 2.5.16 升级后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.

排查方向与解决方案

排查方向

  1. 连接模式冲突
    端口636默认是LDAPS(即时加密)模式,而-starttls ldap是明文LDAP(389端口)升级加密的命令,在636端口使用会导致协议不匹配,需确认自研应用是否误用了StartTLS逻辑连接LDAPS端口。

  2. OpenSSL 3.0初始化缺失
    OpenSSL 3.0引入了provider机制,默认依赖default和legacy provider,1.1.1版本无此机制。自研应用可能未完成3.0版本的初始化流程,导致证书读取或TLS协商失败。

  3. LDAP选项兼容性
    部分LDAP_OPT_X_TLS_*选项仅对StartTLS模式生效,LDAPS模式下需通过OpenSSL上下文直接配置证书,当前代码用ldap_set_option配置的选项可能未生效。

  4. 证书路径与权限
    代码中使用相对路径加载证书,需确认应用运行时工作目录包含目标文件;同时验证OpenSSL 3.0是否支持当前证书格式,以及证书与私钥是否匹配。

  5. 库依赖冲突
    Windows系统的wldap32.lib可能默认调用系统SSL库,与自定义编译的OpenSSL 3.0库存在版本冲突,需确认应用优先加载正确的OpenSSL DLL文件。

解决方案建议

  1. 修正连接模式
    确保自研应用使用LDAPS模式连接636端口,无需启用StartTLS;若需使用StartTLS,应连接389端口,并在ldap_connect后调用ldap_start_tls_s。

  2. 添加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);
    
  3. 调整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);
    
  4. 验证证书与路径

    • 用openssl x509 -in c_usr.crt -text -noout验证证书格式,用`openssl rsa
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 10:05:53