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

调用InitializeSecurityContextA返回SEC_E_INVALID_HANDLE问题求助

问题:SChannel第二次调用InitializeSecurityContextA返回SEC_E_INVALID_HANDLE错误

尝试使用SChannel发起HTTPS请求时,第二次调用InitializeSecurityContextA时,尽管传入的是首次调用得到的相同CtxHandle,却返回了SEC_E_INVALID_HANDLE错误。以下是完整源码(忽略编码风格问题):

#include <windows.h>
#include <winternl.h>
#include <security.h>
#include <schannel.h>
#include <stdio.h>
#include <winsock2.h>
#include <WS2tcpip.h>

// 创建客户端凭证句柄
SECURITY_STATUS create_default_client_cred_handle(PCredHandle handle)
{
    SCH_CREDENTIALS cred;
    ZeroMemory(&cred, sizeof(cred));
    cred.dwVersion = SCH_CREDENTIALS_VERSION;
    return AcquireCredentialsHandle(
        NULL,
        UNISP_NAME,
        SECPKG_CRED_OUTBOUND,
        NULL,
        &cred,
        NULL,
        NULL,
        handle,
        0
    );
}

// 创建socket并连接到目标主机
SOCKET connect_to_host(TCHAR *hostname, ULONG port)
{
    SOCKET s = INVALID_SOCKET;
    
    // 创建支持IPv6的TCP socket
    s = socket(AF_INET6, SOCK_STREAM, 0);
    
    if(s == INVALID_SOCKET)
        return s;
    
    // 禁用仅IPv6模式,兼容IPv4
    DWORD ipv6only = 0;
    int res = setsockopt(s,
    IPPROTO_IPV6,
    IPV6_V6ONLY,
    (char*)&ipv6only,
    sizeof(ipv6only));

    if(res == SOCKET_ERROR)
    {
        closesocket(s);
        return INVALID_SOCKET;
    }

    // 连接到目标主机
    BOOL bSuccess = WSAConnectByNameA(
        s,
        "www.google.com",
        "https",
        0,
        NULL,
        0,
        NULL,
        NULL,
        NULL);

    if (!bSuccess){
        closesocket(s);
        return INVALID_SOCKET;
    }

    return s;
}

// Socket连接完成后执行TLS握手
void client_socket_create_security_ctx(SOCKET s, SEC_CHAR *hostname, PCredHandle h)
{
#define TLS_BUFFER_SIZE (1<<14)

    ULONG flags = 
          ISC_REQ_ALLOCATE_MEMORY
        | ISC_REQ_CONFIDENTIALITY
        | ISC_REQ_INTEGRITY
        | ISC_REQ_REPLAY_DETECT
        | ISC_REQ_STREAM;

    CtxtHandle ctxHandle = { 0 };

    // 初始化缓冲区描述符
    char buf[TLS_BUFFER_SIZE];

    SecBuffer outbuffer[1] = { 0 };
    SecBufferDesc outdesc = {SECBUFFER_VERSION, ARRAYSIZE(outbuffer), outbuffer};

    SecBuffer inbuffer[2];
    inbuffer[0].BufferType = SECBUFFER_TOKEN;
    inbuffer[0].pvBuffer = buf;
    inbuffer[0].cbBuffer = 0;

    inbuffer[1].BufferType = SECBUFFER_EMPTY;
    inbuffer[1].cbBuffer = 0;
    inbuffer[1].pvBuffer = NULL;

    SecBufferDesc indesc = {SECBUFFER_VERSION, ARRAYSIZE(inbuffer), inbuffer};
    
    // 首次调用InitializeSecurityContextA
    SECURITY_STATUS sec_status = InitializeSecurityContextA(
        h, // 凭证句柄
        NULL, // 首次调用上下文句柄为NULL
        hostname, // 目标主机名
        flags, // 上下文请求标志
        0, // 保留参数
        0, // 目标数据表示
        NULL, // 首次调用无输入数据
        0, // 保留参数
        &ctxHandle, // 输出新上下文句柄
        &outdesc, // 输出上下文属性
        &flags, // 更新后的标志
        NULL
    );

    while(sec_status != SEC_E_OK)
    {
        // 处理上次调用遗留的额外数据
        if(inbuffer[1].BufferType == SECBUFFER_EXTRA)
        {
            printf("Found extra data, moving to start of buffer\n");
            MoveMemory(
                buf,
                buf + (inbuffer[0].cbBuffer - inbuffer[1].cbBuffer),
                inbuffer[1].cbBuffer
            );
            inbuffer[0].cbBuffer = inbuffer[1].cbBuffer;
        } else
        {
            inbuffer[0].cbBuffer = 0;
        }

        switch(sec_status)
        {
            case SEC_I_COMPLETE_AND_CONTINUE:
            case SEC_I_COMPLETE_NEEDED:
            {
                printf("SEC_I_COMPLETE_NEEDED meaningless, returning...\n");
            } return;
            case SEC_I_CONTINUE_NEEDED:
            {
                printf("SEC_I_CONTINUE_NEEDED...\n");
                // 发送客户端输出令牌
                printf("Send output token...\n");
                char *buffer = outbuffer[0].pvBuffer;
                DWORD size = outbuffer[0].cbBuffer;
                while(size > 0)
                {
                    int d = send(s, buffer, size, 0);
                    if(d <= 0)
                        break;
                    size -= d;
                    buffer += d;
                }
                FreeContextBuffer(outbuffer[0].pvBuffer);

                // 读取服务器响应令牌
                printf("read server token...\n");
                buffer = buf + inbuffer[0].cbBuffer;
                size = TLS_BUFFER_SIZE - inbuffer[0].cbBuffer;
                inbuffer[0].cbBuffer += recv(s, buffer, size, 0);
            } break;
            case SEC_I_INCOMPLETE_CREDENTIALS:
            {
                printf("Incomplete credentials...\n");
            } return;
            case SEC_E_INCOMPLETE_MESSAGE:
            {
                printf("Incomplete message...\n");
                // 读取更多数据
                char *buffer = buf + inbuffer[0].cbBuffer;
                DWORD size;
                if(inbuffer[1].BufferType == SECBUFFER_MISSING)
                {
                    size = inbuffer[1].cbBuffer;
                    printf("Attemting to read %lu more bytes...\n", size);
                    while(size)
                    {
                        int incoming = recv(s, buffer, size, 0);
                        if(incoming <= 0)
                            break;
                        size -= incoming;
                        buffer += incoming;
                        inbuffer[0].cbBuffer += incoming;
                    }
                } else {
                    printf("Reading as much data as we can...\n");
                    size = TLS_BUFFER_SIZE - inbuffer[0].cbBuffer;
                    int incoming = recv(s, buffer, size, 0);
                    inbuffer[0].cbBuffer += incoming;
                }
            } break;
            default:
            {
                printf("Got error code 0x%X\n", sec_status);
            } return;
        }

        // 重置缓冲区
        ZeroMemory(outbuffer, sizeof(outbuffer));
        ZeroMemory(&inbuffer[1], sizeof(inbuffer[1]));
        printf("Calling InitializeSecurityContextA again..\n");
        // 第二次调用InitializeSecurityContextA
        sec_status = InitializeSecurityContextA(
            NULL, // 错误:此处传入了NULL
            &ctxHandle, // 上下文句柄
            NULL, // 目标主机名
            flags, // 上下文请求标志
            0, // 保留参数
            0, // 目标数据表示
            &indesc, // 输入数据
            0, // 保留参数
            &ctxHandle, // 输出新上下文句柄
            &outdesc, // 输出数据
            &flags, // 更新后的标志
            NULL // 过期时间
        );
    }

    printf("Handshake done ...\n");
#undef TLS_BUFFER_SIZE
}

int main()
{
    char buffer[512];
    CredHandle h;

    WSADATA wsaData;
    WSAStartup(MAKEWORD(2,2), &wsaData);

    SECURITY_STATUS s = create_default_client_cred_handle(&h);
    if (s != SEC_E_OK)
        return -1;
    
    SOCKET sock = connect_to_host("www.google.com", 443);
    if(sock == INVALID_SOCKET)
        return -1;
        
    client_socket_create_security_ctx(sock, "www.google.com", &h);
    // 关闭socket
    shutdown(sock, SD_SEND);
    while(recv(sock, buffer, 512, 0) > 0)
        ;
    closesocket(sock);

}

编译使用的CMake文件:

cmake_minimum_required (VERSION 3.8)

project ("SChannelTest" LANGUAGES C)

add_executable (SChannelTest  "main.c")
if(WIN32)
    add_definitions(/DSECURITY_WIN32 /DSCHANNEL_USE_BLACKLISTS /D_CONSOLE /DUNICODE /D_UNICODE /DWIN32_LEAN_AND_MEAN /D_CRT_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE /W3 /wd4005 /wd4996 /nologo )
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO")
endif()

target_link_libraries(SChannelTest PRIVATE Secur32 ws2_32)

问题分析与解决

核心错误原因

第二次调用InitializeSecurityContextA时,第一个参数phCredential被错误传入NULL。根据SChannel官方文档,非首次调用时必须保持phCredential与首次调用一致,否则SChannel无法关联上下文与对应的凭证,直接返回SEC_E_INVALID_HANDLE错误。

此外还有两个次要问题需要修正:

  1. 首次调用后未正确重置输出缓冲区,可能导致后续调用使用已释放的内存指针
  2. 额外数据处理逻辑位置错误,会清空刚读取的服务器令牌数据

关键修正点

1. 修正凭证句柄参数

将第二次调用的phCredential从NULL改为首次使用的凭证句柄h,同时非首次调用时phNewContext可以传NULL(上下文会直接更新到phContext指向的句柄中):

sec_status = InitializeSecurityContextA(
    h, // 正确传入凭证句柄
    &ctxHandle, // 上下文句柄
    NULL,
    flags,
    0,
    0,
    &indesc,
    0,
    NULL, // 非首次调用无需输出新上下文
    &outdesc,
    &flags,
    NULL
);

2. 正确重置输出缓冲区

发送令牌并释放内存后,必须重置输出缓冲区的类型,避免后续调用使用无效内存:

// 发送令牌后添加重置逻辑
FreeContextBuffer(outbuffer[0].pvBuffer);
ZeroMemory(outbuffer, sizeof(outbuffer));
outbuffer[0].BufferType = SECBUFFER_TOKEN;

3. 调整额外数据处理位置

将SECBUFFER_EXTRA的处理逻辑移到读取服务器令牌之后,避免清空刚读取的有效数据:

case SEC_I_CONTINUE_NEEDED:
{
    // ... 发送令牌代码 ...
    
    // ... 读取服务器令牌代码 ...
    
    // 处理额外数据
    if(inbuffer[1].BufferType == SECBUFFER_EXTRA)
    {
        printf("Found extra data, moving to start of buffer\n");
        MoveMemory(
            buf,
            buf + (inbuffer[0].cbBuffer - inbuffer[1].cbBuffer),
            inbuffer[1].cbBuffer
        );
        inbuffer[0].cbBuffer = inbuffer[1].cbBuffer;
    }
} break;

完整修正后的握手函数

void client_socket_create_security_ctx(SOCKET s, SEC_CHAR *hostname, PCredHandle h)
{
#define TLS_BUFFER_SIZE (1<<14)

    ULONG flags = 
          ISC_REQ_ALLOCATE_MEMORY
        | ISC_REQ_CONFIDENTIALITY
        | ISC_REQ_INTEGRITY
        | ISC_REQ_REPLAY_DETECT
        | ISC_REQ_STREAM;

    CtxtHandle ctxHandle = { 0 };

    char buf[TLS_BUFFER_SIZE];

    SecBuffer outbuffer[1] = { 0 };
    SecBufferDesc outdesc = {SECBUFFER_VERSION, ARRAYSIZE(outbuffer), outbuffer};
    outbuffer[0].BufferType = SECBUFFER_TOKEN;

    SecBuffer inbuffer[2];
    inbuffer[0].BufferType = SECBUFFER_TOKEN;
    inbuffer[0].pvBuffer = buf;
    inbuffer[0].cbBuffer = 0;

    inbuffer[1].BufferType = SECBUFFER_EMPTY;
    inbuffer[1].cbBuffer = 0;
    inbuffer[1].pvBuffer = NULL;

    SecBufferDesc indesc = {SECBUFFER_VERSION, ARRAYSIZE(inbuffer), inbuffer};
    
    // 首次调用
    SECURITY_STATUS sec_status = InitializeSecurityContextA(
        h,
        NULL,
        hostname,
        flags,
        0,
        0,
        NULL,
        0,
        &ctxHandle,
        &outdesc,
        &flags,
        NULL
    );

    while(sec_status != SEC_E_OK)
    {
        switch(sec_status)
        {
            case SEC_I_COMPLETE_AND_CONTINUE:
            case SEC_I_COMPLETE_NEEDED:
            {
                printf("SEC_I_COMPLETE_NEEDED meaningless, returning...\n");
            } return;
            case SEC_I_CONTINUE_NEEDED:
            {
                printf("SEC_I_CONTINUE_NEEDED...\n");
                // 发送客户端令牌
                printf("Send output token...\n");
                char *buffer = outbuffer[0].pvBuffer;
                DWORD size = outbuffer[0].cbBuffer;
                while(size > 0)
                {
                    int d = send(s, buffer, size, 0);
                    if(d <= 0)
                        break;
                    size -= d;
                    buffer += d;
                }
                FreeContextBuffer(outbuffer[0].pvBuffer);
                // 重置输出缓冲区
                ZeroMemory(outbuffer, sizeof(outbuffer));
                outbuffer[0].BufferType = SECBUFFER_TOKEN;

                // 读取服务器令牌
                printf("read server token...\n");
                buffer = buf + inbuffer[0].cbBuffer;
                size = TLS_BUFFER_SIZE - inbuffer[0].cbBuffer;
                int recv_len = recv(s, buffer, size, 0);
                if (recv_len <= 0)
                {
                    printf("recv failed\n");
                    return;
                }
                inbuffer[0].cbBuffer += recv_len;

                // 处理额外数据
                if(inbuffer[1].BufferType == SECBUFFER_EXTRA)
                {
                    printf("Found extra data, moving to start of buffer\n");
                    MoveMemory(
                        buf,
                        buf + (inbuffer[0].cbBuffer - inbuffer[1].cbBuffer),
                        inbuffer[1].cbBuffer
                    );
                    inbuffer[0].cbBuffer = inbuffer[1].cbBuffer;
                }
            } break;
            case SEC_I_INCOMPLETE_CREDENTIALS:
            {
                printf("Incomplete credentials...\n");
            } return;
            case SEC_E_INCOMPLETE_MESSAGE:
            {
                printf("Incomplete message...\n");
                char *buffer = buf + inbuffer[0].cbBuffer;
                DWORD size;
                if(inbuffer[1].BufferType == SECBUFFER_MISSING)
                {
                    size = inbuffer[1].cbBuffer;
                    printf("Attemting to read %lu more bytes...\n", size);
                    while(size)
                    {
                        int incoming = recv(s, buffer, size, 0);
                        if(incoming <= 0)
                            break;
                        size -= incoming;
                        buffer += incoming;
                        inbuffer[0].cbBuffer += incoming;
                    }
                } else {
                    printf("Reading as much data as we can...\n");
                    size = TLS_BUFFER_SIZE - inbuffer[0].cbBuffer;
                    int incoming = recv(s, buffer, size, 0);
                    if (incoming <= 0)
                    {
                        printf("recv failed\n");
                        return;
                    }
                    inbuffer[0].cbBuffer += incoming;
                }
            } break;
            default:
            {
                printf("Got error code 0x%X\n", sec_status);
            } return;
        }

        ZeroMemory(&inbuffer[1], sizeof(inbuffer[1]));
        printf("Calling InitializeSecurityContextA again..\n");
        // 修正后的第二次调用
        sec_status = InitializeSecurityContextA(
            h,
            &ctxHandle,
            NULL,
            flags,
            0,
            0,
            &indesc,
            0,
            NULL,
            &outdesc,
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 08:08:26