我正在尝试使用 Winsock 内核和 Schannel 在我的内核模式应用程序中实现安全套接字。我使用this代码作为建立安全连接的参考。但是,我遇到了一个奇怪的问题,即第二次调用
InitializeSecurityContextW
失败并出现错误 SEC_E_INVALID_HANDLE
。这对我来说没有意义,因为第一次调用返回 SEC_I_CONTINUE_NEEDED
被认为是成功的。根据我对文档的理解,现在应该保证phNewContext
参数返回一个部分形成的上下文的句柄,但实际上这个句柄在再次传递给InitializeSecurityContextW
时会产生提到的错误代码(在phContext
参数),更糟糕的是,在将其传递给时会导致 BSOD
DeleteSecurityContext
。文档没有提到这种行为,这让我想知道这是否是 API 的错误?
我当前的代码如下所示(我添加了注释以指示错误发生的位置):
NTSTATUS SecureSocketConnect(
IN SOCKET_SESSION_HANDLE handle,
IN PSOCKADDR remoteAddr,
OUT PSECURE_SOCK_CONNECTION_HANDLE outHandle
)
{
NTSTATUS retVal;
SECURITY_STATUS credStatus = E_FAIL, ctxStatus;
PSECURE_SOCKET_CONNECTION con = NULL;
SECURITY_STRING packageName;
SCHANNEL_CRED creds = { 0 };
BOOLEAN destroyCtx = FALSE, isFirst = TRUE;
DWORD flags, downloaded = 0;
SecBuffer inBuf[2] = { 0 }, outBuf[1] = { 0 };
SecBufferDesc inDesc, outDesc;
//1. Allocate and init. structures
con = AllocUserMemory(sizeof(SECURE_SOCKET_CONNECTION));
if (!con)
{
retVal = STATUS_NO_MEMORY;
goto Done;
}
creds.dwVersion = SCHANNEL_CRED_VERSION;
creds.grbitEnabledProtocols = SP_PROT_TLS1_2;
creds.dwFlags = SCH_USE_STRONG_CRYPTO
| SCH_CRED_MANUAL_CRED_VALIDATION
| SCH_CRED_NO_DEFAULT_CREDS;
flags = ISC_REQ_USE_SUPPLIED_CREDS |
ISC_REQ_ALLOCATE_MEMORY |
ISC_REQ_CONFIDENTIALITY |
ISC_REQ_REPLAY_DETECT |
ISC_REQ_SEQUENCE_DETECT |
ISC_REQ_STREAM;
RtlInitUnicodeString(
&packageName,
L"Microsoft Unified Security Protocol Provider"
);
//2. Get pointer to security function table
con->SecureFuncs = InitSecurityInterfaceW();
if (!con->SecureFuncs)
{
retVal = STATUS_UNSATISFIED_DEPENDENCIES;
goto Done;
}
//3. Acquire credentials handle
credStatus = con->SecureFuncs->AcquireCredentialsHandleW(
NULL,
&packageName,
SECPKG_CRED_OUTBOUND,
NULL,
&creds,
NULL,
NULL,
&con->CredHandle,
NULL
);
if (credStatus != SEC_E_OK)
{
retVal = STATUS_UNSUCCESSFUL;
goto Done;
}
//4. Try to connect to remote address
retVal = SocketConnect(
handle,
SOCK_STREAM,
IPPROTO_TCP,
remoteAddr,
&con->SocketConnection
);
if (!NT_SUCCESS(retVal))
goto Done;
//5. Establish secure connection using InitializeSecurityContextW
for(retVal = STATUS_PENDING; retVal == STATUS_PENDING; )
{
inBuf[0].BufferType = SECBUFFER_TOKEN;
inBuf[0].pvBuffer = con->Incoming;
inBuf[0].cbBuffer = con->Received;
inBuf[1].BufferType = SECBUFFER_EMPTY;
inBuf[1].pvBuffer = NULL;
inBuf[1].cbBuffer = 0;
outBuf[0].BufferType = SECBUFFER_TOKEN;
outBuf[0].pvBuffer = NULL;
outBuf[0].cbBuffer = 0;
inDesc.ulVersion = SECBUFFER_VERSION;
inDesc.pBuffers = inBuf;
inDesc.cBuffers = 2;
outDesc.ulVersion = SECBUFFER_VERSION;
outDesc.cBuffers = 1;
outDesc.pBuffers = outBuf;
if (isFirst)
{
isFirst = FALSE;
//This (first) call succeeds with SEC_I_CONTINUE_NEEDED
ctxStatus = con->SecureFuncs->InitializeSecurityContextW(
&con->CredHandle,
NULL,
NULL,
flags,
0,
0,
NULL,
0,
&con->ContextHandle,
&outDesc,
&flags,
NULL
);
if (ctxStatus != SEC_I_CONTINUE_NEEDED)
{
retVal = STATUS_UNSUCCESSFUL;
continue;
}
else
{
destroyCtx = TRUE;
}
}
else
{
//This (second) call fails with SEC_E_INVALID_HANDLE
ctxStatus = con->SecureFuncs->InitializeSecurityContextW(
&con->CredHandle,
&con->ContextHandle,
NULL,
flags,
0,
0,
&inDesc,
0,
NULL,
&outDesc,
&flags,
NULL
);
}
DbgPrintEx(0, 0, "ctxStatus: %x\n", ctxStatus);
if (inBuf[1].BufferType == SECBUFFER_EXTRA)
{
RtlCopyMemory(
con->Incoming,
con->Incoming + (con->Received - inBuf[1].cbBuffer),
inBuf[1].cbBuffer
);
con->Received = inBuf[1].cbBuffer;
}
else
{
con->Received = 0;
}
switch (ctxStatus)
{
case SEC_E_INCOMPLETE_MESSAGE:
break;
case SEC_I_CONTINUE_NEEDED:
if (!outBuf[0].pvBuffer)
{
retVal = STATUS_NO_MEMORY;
continue;
}
retVal = SocketSend(
con->SocketConnection,
outBuf[0].pvBuffer,
outBuf[0].cbBuffer
);
con->SecureFuncs->FreeContextBuffer(outBuf[0].pvBuffer);
if (!NT_SUCCESS(retVal))
continue;
break;
case SEC_E_OK:
retVal = STATUS_SUCCESS;
*outHandle = con;
continue;
default:
retVal = STATUS_UNSUCCESSFUL;
continue;
}
if (con->Received == TLS_MAX_PACKET_SIZE)
{
retVal = STATUS_INVALID_BUFFER_SIZE;
continue;
}
retVal = SocketRecieve(
con->SocketConnection,
TLS_MAX_PACKET_SIZE - con->Received,
con->Incoming + con->Received,
&downloaded
);
if (NT_SUCCESS(retVal))
{
con->Received += downloaded;
retVal = STATUS_PENDING;
}
}
Done:
//Freeing resources in case of error
if (!NT_SUCCESS(retVal) && con)
{
if (con->SecureFuncs && destroyCtx)
//This causes a BSOD, even though the ContextHandle should be valid.
con->SecureFuncs->DeleteSecurityContext(&con->ContextHandle);
if (con->SecureFuncs && credStatus == SEC_E_OK)
con->SecureFuncs->FreeCredentialsHandle(&con->CredHandle);
if (con->SocketConnection)
SocketCloseConnectionHandle(con->SocketConnection);
FreeUserMemory(con);
}
return retVal;
}