调用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. 修正凭证句柄参数
将第二次调用的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,
相关产品推荐
相关产品推荐

