I am trying to use SChannel for https request. When I call InitializeSecurityContextA
for the second time, I am getting SEC_E_INVALID_HANDLE
although I am supplying the same CtxHandle from first call. Here is the complete source code; (don't mind the sloppy coding practices)
#include <windows.h>
#include <winternl.h>
#include <security.h>
#include <schannel.h>
#include <stdio.h>
#include <winsock2.h>
#include <WS2tcpip.h>
// call this function to create credhandle for a client
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
);
}
// Open a socket and connect to a host
SOCKET connect_to_host(TCHAR *hostname, ULONG port)
{
SOCKET s = INVALID_SOCKET;
// create tcp socket (implied by sock_stream)
// with ipv6 support
s = socket(AF_INET6, SOCK_STREAM, 0);
if(s == INVALID_SOCKET)
return s;
// disable IPV6 ONLY
DWORD ipv6only = 0;
int res = setsockopt(s,
IPPROTO_IPV6,
IPV6_V6ONLY,
(char*)&ipv6only,
sizeof(ipv6only));
if(res == SOCKET_ERROR)
{
closesocket(s);
return INVALID_SOCKET;
}
// connect to host
BOOL bSuccess = WSAConnectByNameA(
s,
"www.google.com",
"https",
0,
NULL,
0,
NULL,
NULL,
NULL);
if (!bSuccess){
closesocket(s);
return INVALID_SOCKET;
}
return s;
}
// Call this function after socket is connected
// to do TLS handshake
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 };
// setup buffer descriptors
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};
// First call
SECURITY_STATUS sec_status = InitializeSecurityContextA(
h, // phCredential,
NULL, // phContext, (must be null on first call)
hostname, // *pszTargetName,
flags, // fContextReq,
0, // Reserved1
0, // TargetDataRep
NULL, // pInput,(must be null on first call)
0, // Reserved2
&ctxHandle, // phNewContext
&outdesc, // pfContextAttr
&flags, //
NULL
);
while(sec_status != SEC_E_OK)
{
// if there was leftover data from previous call
// move it to start of buffer
if(inbuffer[1].BufferType == SECBUFFER_EXTRA)
{
printf("Found extra data, moving to start of buffer\r\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)
{
// SUCCESS RETURN CODES
case SEC_I_COMPLETE_AND_CONTINUE:
case SEC_I_COMPLETE_NEEDED:
{
// Although these mean InitializeSecurityContextA
// returned successfully, in the context of https
// these return codes are meaningless
printf("SEC_I_COMPLETE_NEEDED meaningless, returning...\r\n");
} return;
case SEC_I_CONTINUE_NEEDED:
{
printf("SEC_I_CONTINUE_NEEDED...\r\n");
// client must send output token
// and read input token
printf("Send output token...\r\n");
// sending client token
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...\r\n");
// read server token
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...\r\n");
// server requested client certificate
// it shouldn't for https...
} return;
case SEC_E_INCOMPLETE_MESSAGE:
{
printf("Incomplete message...\r\n");
// we didn't get enough data from server
// we should read more
char *buffer = buf + inbuffer[0].cbBuffer;
DWORD size;
if(inbuffer[1].BufferType == SECBUFFER_MISSING)
{
// we know how much data we need (approx.)
size = inbuffer[1].cbBuffer;
printf("Attemting to read %lu more bytes...\r\n", size);
while(size)
{
int incoming = recv(s, buffer, size, 0);
if(incoming <= 0)
break;
size -= incoming;
buffer += incoming;
inbuffer[0].cbBuffer += incoming;
}
} else {
// This shouldn't happen, but just in case
printf("Reading as much data as we can...\r\n");
size = TLS_BUFFER_SIZE - inbuffer[0].cbBuffer;
int incoming = recv(s, buffer, size, 0);
inbuffer[0].cbBuffer += incoming;
}
} break;
// ERROR RETURN CODES
default:
{
printf("Got error code 0x%X\r\n", sec_status);
// dont care for individual codes,
// error is error
} return;
}
// prepare buffers
ZeroMemory(outbuffer, sizeof(outbuffer));
ZeroMemory(&inbuffer[1], sizeof(inbuffer[1]));
printf("Calling InitializeSecurityContextA again..\r\n");
// call again
sec_status = InitializeSecurityContextA(
NULL, // phCredential,
&ctxHandle, // phContext
NULL, // *pszTargetName,
flags, // fContextReq,
0, // Reserved1
0, // TargetDataRep
&indesc, // pInput,(must be null on first call)
0, // Reserved2
&ctxHandle, // phNewContext
&outdesc, // pOutput
&flags, //pfContextAttr
NULL // ptsExpiry
);
}
printf("Handshake done ...\r\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);
// close socket
shutdown(sock, SD_SEND);
// drain rest
while(recv(sock, buffer, 512, 0) > 0)
;
closesocket(sock);
}
I am using this Cmake file to compile;
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)
Although relevant msdn documentatation states that;
phCredential [in, optional]
A handle to the credentials returned by AcquireCredentialsHandle (Schannel). This handle is used to build the security context. The InitializeSecurityContext (Schannel) function requires at least OUTBOUND credentials on the first call. On subsequent calls, this can be NULL.
Supplying NULL
for phCredential
parameter causes InitializeSecurityContext
to fail with SEC_E_INVALID_HANDLE
.
Supplying same CredHandle
for subsequent calls fixes the issue.